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" : "示例"}
))}
))}
拖拽列头排序 · 拖拽右边缘调整宽度 · 点击列头选中字段
);
}