checkBox支持拖拉拽调动位置,改变布局和选择值的顺序
ZoneSelect.tsx组件编写
import React, { useState, useMemo } from 'react';
import { Checkbox } from 'antd';
import {DndContext,closestCenter,KeyboardSensor,PointerSensor,useSensor,useSensors,DragOverlay,
} from '@dnd-kit/core';
import {arrayMove,SortableContext,horizontalListSortingStrategy,useSortable,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';interface ZoneOption {name: string;display: string;
}interface ZoneSelectProps {value?: string;onChange?: (value: string) => void;onOrderChange?: (order: string[]) => void;initialOrder?: string[];
}export const INITIAL_ZONES: ZoneOption[] = [{ name: 'auto', display: 'Auto-Select' },{ name: 'eu', display: 'Europe' },{ name: 'hk', display: 'Hong Kong' },{ name: 'ja', display: 'Japan' },{ name: 'ko', display: 'Korea' },{ name: 'na', display: 'North America' },{ name: 'sg', display: 'Singapore' },
];const SortableItem: React.FC<{id: string;zone: ZoneOption;checked: boolean;onChange: (checked: boolean) => void;
}> = ({ id, zone, checked, onChange }) => {const {attributes,listeners,setNodeRef,transform,transition,isDragging,} = useSortable({ id });const style = {transform: CSS.Transform.toString(transform),transition,opacity: isDragging ? 0.8 : 1,zIndex: isDragging ? 1 : 0,};return (<divref={setNodeRef}style={{...style,width: '150px',marginBottom: '4px',}}{...attributes}><div {...listeners} style={{ cursor: 'grab', padding: '4px 0' }}><Checkboxchecked={checked}onChange={(e) => {e.stopPropagation();onChange(e.target.checked);}}>{zone.display}</Checkbox></div></div>);
};const ZoneSelect: React.FC<ZoneSelectProps> = ({value = '[]',onChange,initialOrder,onOrderChange}) => {// 初始化区服顺序const [zones, setZones] = useState<ZoneOption[]>(() => {// 1. 优先使用传入的initialOrderif (initialOrder && initialOrder.length > 0) {const orderedZones = initialOrder.map(name => INITIAL_ZONES.find(z => z.name === name)).filter(Boolean) as ZoneOption[];// 补全可能缺失的区服const missingZones = INITIAL_ZONES.filter(zone => !initialOrder.includes(zone.name));return [...orderedZones, ...missingZones];}return INITIAL_ZONES;});const [activeId, setActiveId] = useState<string | null>(null);// 解析选中的值const selectedZones = useMemo(() => {try {return JSON.parse(value) || [];} catch {return [];}}, [value]);const sensors = useSensors(useSensor(PointerSensor, {activationConstraint: {distance: 5, // 需要移动5px才触发拖拽},}),useSensor(KeyboardSensor));const handleDragStart = (event: any) => {setActiveId(event.active.id);};const handleDragEnd = (event: any) => {const { active, over } = event;setActiveId(null);if (over && active.id !== over.id) {const oldIndex = zones.findIndex((zone) => zone.name === active.id);const newIndex = zones.findIndex((zone) => zone.name === over.id);const newZones = arrayMove(zones, oldIndex, newIndex);setZones(newZones);// 1. 通知外部排序变化const newOrder = newZones.map(z => z.name);onOrderChange?.(newOrder);// 2. 更新选中项的顺序(按照新顺序)const orderedSelected = newZones.filter(z => selectedZones.some(sz => sz.name === z.name)).map(z => ({name: z.name,display: z.display}));onChange?.(JSON.stringify(orderedSelected));}};const handleCheckboxChange = (zoneName: string, checked: boolean) => {const zone = zones.find((z) => z.name === zoneName);if (!zone) return;const newSelected = checked? [...selectedZones, zone]: selectedZones.filter((z) => z.name !== zoneName);// 按照当前 zones 顺序返回选中项const orderedSelected = zones.filter(z => newSelected.some(sz => sz.name === z.name)).map(z => ({name: z.name,display: z.display}));onChange?.(JSON.stringify(orderedSelected));};const activeZone = activeId ? zones.find((zone) => zone.name === activeId) : null;return (<DndContextsensors={sensors}collisionDetection={closestCenter}onDragStart={handleDragStart}onDragEnd={handleDragEnd}><SortableContext items={zones} strategy={horizontalListSortingStrategy}><div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>{zones.map((zone) => (<SortableItemkey={zone.name}id={zone.name}zone={zone}checked={selectedZones.some((z) => z.name === zone.name)}onChange={(checked) => handleCheckboxChange(zone.name, checked)}/>))}</div></SortableContext><DragOverlay>{activeZone ? (<divstyle={{width: '250px',background: 'rgba(255, 255, 255, 0.9)',boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',borderRadius: '4px',padding: '4px',transform: 'scale(1.02)',}}><Checkbox checked={selectedZones.some((z) => z.name === activeZone.name)}>{activeZone.display}</Checkbox></div>) : null}</DragOverlay></DndContext>);
};export default ZoneSelect;