import { useState, useCallback, useRef } from "react"; import { DndContext, closestCenter, PointerSensor, useSensor, useSensors } from "@dnd-kit/core"; import type { DragEndEvent, Modifier } from "@dnd-kit/core"; import { SortableContext, useSortable, horizontalListSortingStrategy } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; const restrictToHorizontalAxis: Modifier = ({ transform }) => ({ ...transform, y: 0 }); import type { ModuleField } from "../types"; interface Props { fields: ModuleField[]; activeId: number | null; onActivate: (id: number) => void; onReorder: (activeId: number, overId: number) => void; onResize: (id: number, width: number) => void; } function SortableColumn({ field, active, isLast, onActivate, onResizeStart, }: { field: ModuleField; active: boolean; isLast: boolean; onActivate: (id: number) => void; onResizeStart: (e: React.MouseEvent, id: number) => void; }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: field.id }); const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.4 : 1 }; return (
onActivate(field.id)} >
{field.label} {field.listW}
{!isLast && (
onResizeStart(e, field.id)} onClick={(e) => e.stopPropagation()} /> )}
); } export default function TablePreview({ fields, activeId, onActivate, onReorder, onResize }: Props) { const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } })); const [resizing, setResizing] = useState<{ id: number; startX: number; startW: number } | null>(null); const tableRef = useRef(null); const handleDragEnd = useCallback( (event: DragEndEvent) => { const { active, over } = event; if (over && active.id !== over.id) { onReorder(Number(active.id), Number(over.id)); } }, [onReorder] ); const handleResizeStart = useCallback( (e: React.MouseEvent, id: number) => { e.preventDefault(); e.stopPropagation(); const field = fields.find((f) => f.id === id); if (!field) return; setResizing({ id, startX: e.clientX, startW: field.listW }); }, [fields] ); const handleMouseMove = useCallback( (e: MouseEvent) => { if (!resizing) return; const dx = e.clientX - resizing.startX; const w = Math.max(40, Math.min(400, Math.round(resizing.startW + dx))); onResize(resizing.id, w); setResizing((prev) => (prev ? { ...prev } : null)); }, [resizing, onResize] ); const handleMouseUp = useCallback(() => { setResizing(null); }, []); // Attach global listeners for resize if (resizing) { document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); } const visible = fields.filter((f) => f.listOn).sort((a, b) => a.order - b.order); if (!visible.length) { return
暂无可见列 · 请开启字段的列表开关
; } const totalW = visible.reduce((s, f) => s + f.listW, 0); return (
f.id)} strategy={horizontalListSortingStrategy}>
{visible.map((f, i) => ( ))}
{[0, 1].map((r) => (
{visible.map((f) => (
{f.type === "select" ? "—" : f.type === "date" ? "2024-08-12" : f.type === "number" ? "100.00" : "示例"}
))}
))}
拖拽列头排序 · 拖拽右边缘调整宽度 · 点击列头选中字段
); }