这是一个基于@dnd-kit 实现拖拽Table的一行进行排序,点击单行可点开弹框进行更换数据,@dnd-kit其中的一些原理不多赘述,我们直接上案例 下载安装包:@mantine/core @dnd-kit/core
1.首先写一个展示列表
import { useEffect, useState } from "react";
import { Button, Group, Stack, Title } from "@mantine/core";
import SortableList from "./SortableList";
const data = [
{
id: "1",
name: "黑森林蛋糕"
},
{
id: "2",
name: "青提树莓桃桃"
},
{
id: "3",
name: "半熟芝士"
}
];
export default function App() {
const [isEdit, setIsEdit] = useState(false);
const [items, setItems] = useState<any[]>(data || []);
useEffect(() => {
setItems(data || []);
}, []);
return (
<Stack>
<Group position="apart">
<Title order={3} color="blue">
今日贩卖
</Title>
{!isEdit ? (
<Button size="sm" onClick={() => setIsEdit(true)}>
编辑
</Button>
) : (
<Group>
<Button
variant="outline"
size="sm"
onClick={() => {
setIsEdit(false);
setItems(data || []);
}}
>
取消
</Button>
<Button size="sm" onClick={() => setIsEdit(false)}>
保存
</Button>
</Group>
)}
</Group>
<SortableList items={items || []} onChange={setItems} isEdit={isEdit} />
</Stack>
)}
export const getDefaultData = () => {
return {
id: "",
name: ""
};
};
2.接下来写可拖拽时的列表
import { Active, DndContext, useSensors, KeyboardSensor, PointerSensor, useSensor, Over } from "@dnd-kit/core";
import { SortableContext, arrayMove, sortableKeyboardCoordinates, horizontalListSortingStrategy } from "@dnd-kit/sortable";
import { useMemo, useState } from "react";
import SortableOverlay from "./SortableOverlay";
import SortableItem, { DragHandle } from "./SortableItem";
import { Table } from "@mantine/core";
type SortableListProps = {
items: any[];
onChange: (items: any[]) => void;
isEdit: boolean;
};
export default function SortableList(props: SortableListProps) {
const { items, onChange, isEdit } = props;
const [active, setActive] = useState<Active | null>(null);
const activeItem = useMemo(
() => items.find((item) => item.id === active?.id),
[active, items]
);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
);
const handleDragStart = (active: Active) => {
setActive(active);
};
const handleDragEnd = (active: Active, over: Over | null) => {
if (over && active.id !== over?.id) {
const activeIndex = items.findIndex(({ id }) => id === active.id);
const overIndex = items.findIndex(({ id }) => id === over.id);
onChange(arrayMove(items, activeIndex, overIndex));
}
setActive(null);
};
const handleDragCancel = () => {
setActive(null);
};
return (
<DndContext
sensors={sensors}
onDragStart={({ active }) => handleDragStart(active)}
onDragEnd={({ active, over }) => handleDragEnd(active, over)}
onDragCancel={handleDragCancel}>
<Table withBorder>
<thead>
<tr>
{isEdit ? <th>数据排序</th> : <th>展示序号</th>}
<th>名称</th>
</tr>
</thead>
<tbody>
{isEdit ? (
<SortableContext
items={items}
strategy={horizontalListSortingStrategy}>
{items.map((item, index) => {
return (
<SortableItem
key={index}
item={item}
items={items}
onChange={onChange}
index={index}
/>
);
})}
</SortableContext>
) : (
<>
{items.map((t, i) => {
return (
<tr key={i}>
<td>{i + 1}</td>
<td>{t.name}</td>
</tr>
);
})}
</>
)}
</tbody>
</Table>
<SortableOverlay>
{activeItem ? (
<Table>
<tbody>
<SortableItem item={activeItem} />
</tbody>
</Table>
) : null}
</SortableOverlay>
</DndContext>
);
}
SortableList.DragHandle = DragHandle;
3.列表项
import { DraggableSyntheticListeners } from "@dnd-kit/core";
import { createContext, CSSProperties, useContext, useMemo, useState } from "react";
import { useSortable } from "@dnd-kit/sortable";
import { Anchor } from "@mantine/core";
import { CSS } from "@dnd-kit/utilities";
import SortableList from "./SortableList";
import AnnouncementPublishListDialog from "./AnnouncementPublishListDialog";
type SortableItemProps = {
item: any;
items?: any[];
index?: number;
onChange?: (items: any[]) => void;
};
interface Context {
attributes: Record<string, any>;
listeners: DraggableSyntheticListeners;
ref(node: HTMLElement | null): void;
}
const SortableItemContext = createContext<Context>({
attributes: {},
listeners: undefined,
ref: () => {}
});
export default function SortableItem({
item,
items,
index,
onChange
}: SortableItemProps) {
const {
attributes,
isDragging,
listeners,
setNodeRef,
transform,
transition,
setActivatorNodeRef
} = useSortable({
id: item.id
});
const context = useMemo(
() => ({ attributes, listeners, ref: setActivatorNodeRef }),
[attributes, listeners, setActivatorNodeRef]
);
const style: CSSProperties = {
opacity: isDragging ? 0.4 : undefined,
transform: CSS.Translate.toString(transform),
transition
};
const [opened, setOpened] = useState(false);
const newData = Array.from(items || []);
const onSelect = (item: any) => {
if (items?.some((i) => i.id === item.id)) {
const clonedData = [...newData];
const idx = items.findIndex((i) => i.id === item.id);
clonedData[idx] = {};
clonedData[index || 0] = item;
onChange?.(clonedData);
} else {
const clonedData = [...newData];
clonedData[index || 0] = item;
onChange?.(clonedData);
}
};
return (
<SortableItemContext.Provider value={context}>
<AnnouncementPublishListDialog
opened={opened}
onClose={() => setOpened(false)}
onSelect={onSelect}
/>
<tr ref={setNodeRef} style={style} onClick={() => setOpened(true)}>
<td>
<SortableList.DragHandle color="black" />
</td>
<td>{item.name}</td>
</tr>
</SortableItemContext.Provider>
);
}
export function DragHandle({ color }: { color: string }) {
const { attributes, listeners, ref } = useContext(SortableItemContext);
return (
<Anchor className="DragHandle" {...attributes} {...listeners} ref={ref}>
<svg viewBox="0 0 20 20" width="12">
<path
d="M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z"
fill={color}
/>
</svg>
</Anchor>
);
}
4.拖拽的元素
import { DragOverlay, DropAnimation, defaultDropAnimationSideEffects } from "@dnd-kit/core";
export default function SortableOverlay({ children }: { children: any }) {
const dropAnimationConfig: DropAnimation = {
sideEffects: defaultDropAnimationSideEffects({
styles: {
active: {
opacity: "0.4"
}
})
};
return (
<DragOverlay dropAnimation={dropAnimationConfig}>{children}</DragOverlay>
);
}
5.弹窗里面的内容
import { Modal, Stack, Table, Button } from "@mantine/core";
type ProductListDialogProps = {
opened: boolean;
onClose: () => void;
onSelect: (o: any) => void;
};
const elements = [
{ id: 65, name: "冰可乐" },
{ id: 75, name: "杨梅冰冰桶" },
{ id: 39, name: "芝士桃桃" },
{ id: 56, name: "榴莲千层" },
{ id: 58, name: "覆盆子千层" }
];
export default function AnnouncementPublishListDialog({
opened,
onClose,
onSelect
}: ProductListDialogProps) {
const rows = elements.map((element, index) => (
<tr key={element.name}>
<td>{index + 1}</td>
<td>{element.name}</td>
<td>
<Button
size="xs"
variant="outline"
onClick={() => {
onSelect(element);
onClose();
}}
>
选择
</Button>
</td>
</tr>
));
return (
<Modal
opened={opened}
onClose={onClose}
centered
size="900px">
<Stack>
<Table>
<thead>
<tr>
<th>序号</th>
<th>名称</th>
<th>操作</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</Table>
</Stack>
</Modal>
);
}
实现结果:
最后附上源码:codesandbox.io/s/gifted-mo…