教你如何拿下 ant design 拖拽表格
import { Button, Drawer, message, Modal, Pagination, Switch, Table, Image, RowProps } from "antd";
import dayjs from "dayjs";
import _ from "lodash";
import React, { useMemo, useState } from "react";
import { useMutation, useQuery } from "react-query";
import type { ColumnsType, TableProps } from "antd/es/table";
import CreateVideo from "./CreateVideo";
import { delVideo, editVideo, getVideoList, sortVideo } from "@/services/course";
import type { DragEndEvent } from "@dnd-kit/core";
import { DndContext } from "@dnd-kit/core";
import { CSS } from "@dnd-kit/utilities";
import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { MenuOutlined } from "@ant-design/icons";
type Prop = {
open: boolean;
courseId?: number;
onClose: () => void;
};
export default function Video({ courseId, open, onClose }: Prop) {
const [page, setPage] = useState(1);
const [size, setSize] = useState(50);
const [sort, setSort] = useState("");
const [sortStatus, setSortStatus] = useState("");
// 视频列表
const [videoList, setVideoList] = useState<any>([]);
const { data, isLoading, refetch } = useQuery({
queryKey: [getVideoList.name, page, size, sort, sortStatus, courseId],
queryFn: () => {
if (open) {
return getVideoList({ page, size, sort, sortStatus, courseId: courseId });
}
},
onSuccess: (data) => {
setVideoList(data?.list || []);
},
});
// table 列配置
const columns = useMemo(
() =>
[
{
key: "sort",
},
{
title: "ID",
dataIndex: "id",
},
{
title: "视频名称",
dataIndex: "name",
},
{
title: "视频链接",
dataIndex: "url",
},
{
title: "封面",
dataIndex: "cover",
render: (value) => <>{value ? <Image width={40} height={40} src={value} alt="" /> : ""}</>,
},
{
title: "是否免费",
dataIndex: "isFree",
render: (value) => <span>{value === 1 ? "免费" : "收费"}</span>,
},
{
title: "金额(¥)",
sorter: true,
dataIndex: "price",
},
{
title: "章节",
dataIndex: "chapter",
},
{
title: "播放次数",
sorter: true,
dataIndex: "views",
},
{
title: "创建时间",
dataIndex: "createdAt",
sorter: true,
render: (value) => <span>{dayjs(value).format("YYYY-MM-DD HH:mm:ss")}</span>,
},
{
title: "操作",
dataIndex: "action",
render: (value, record) => (
<>
<Button
size="small"
type="link"
onClick={() => {
setCurId(record.id);
setOpenDrawer(true);
}}
>
编辑
</Button>
<Button size="small" type="link" danger onClick={() => handleRemoveVideo(record)}>
删除
</Button>
</>
),
},
] as ColumnsType<Video>,
[]
);
// table change事件
const onChange: TableProps<any>["onChange"] = (pagination, filters, sorter: any, extra) => {
if (sorter.order) {
setSort(sorter.field);
setSortStatus(sorter.order === "ascend" ? "ASC" : "DESC");
} else {
setSort("");
setSortStatus("");
}
};
// 删除视频
const { mutate: remove, isLoading: removeLoading } = useMutation({
mutationFn: (id: number) => delVideo(id),
onSuccess: () => {
if (data && data?.total - 1 <= (page - 1) * size) {
if (page > 1) {
setPage(page - 1);
}
}
refetch();
message.success("删除成功");
},
});
// 删除提示框
const handleRemoveVideo = (video: Video) => {
Modal.confirm({
title: "提示",
content: `确定要删除此条数据吗?`,
okButtonProps: {
loading: removeLoading,
},
onOk: async () => {
await remove(video.id);
},
});
};
// 修改视频排序
const { mutate: sortVideoList } = useMutation({
mutationFn: (sortList: Array<{ id: number; sort: number }>) => sortVideo(sortList),
onSuccess: () => {
refetch();
},
});
// 拖拽
const onDragEnd = ({ active, over }: DragEndEvent) => {
if (active.id !== over?.id) {
setVideoList((previous: any) => {
const activeIndex = previous.findIndex((i: any) => i.id === active.id);
const overIndex = previous.findIndex((i: any) => i.id === over?.id);
const newArray = arrayMove(previous, activeIndex, overIndex);
const updateSort = _.map(newArray, (item: any, i: number) => ({ id: item.id, sort: i + 1 }));
sortVideoList(updateSort);
return arrayMove(previous, activeIndex, overIndex);
});
}
};
// 行元素
const Row = ({ children, ...props }: any) => {
const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({
id: props["data-row-key"],
});
const style: React.CSSProperties = {
...props.style,
transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 })?.replace(
/translate3d\(([^,]+),/,
"translate3d(0,"
),
transition,
...(isDragging ? { position: "relative", zIndex: 9999 } : {}),
};
return (
<tr {...props} ref={setNodeRef} style={style} {...attributes}>
{React.Children.map(children, (child) => {
if ((child as React.ReactElement).key === "sort") {
return React.cloneElement(child as React.ReactElement, {
children: (
<MenuOutlined
ref={setActivatorNodeRef}
style={{ touchAction: "none", cursor: "move" }}
{...listeners}
/>
),
});
}
return child;
})}
</tr>
);
};
// 当前选中视频
const [curId, setCurId] = useState<number>();
// 抽屉开关
const [openDrawer, setOpenDrawer] = useState(false);
// 关闭抽屉
const closeDrawer = () => {
setCurId(undefined);
setOpenDrawer(false);
refetch();
};
return (
<Drawer open={open} title="视频列表" width={1300} zIndex={900} onClose={onClose}>
<DndContext onDragEnd={onDragEnd}>
<SortableContext items={videoList.map((i: any) => i.id)} strategy={verticalListSortingStrategy}>
<Table
rowKey="id"
size="middle"
pagination={false}
columns={columns}
components={{
body: {
row: Row,
},
}}
dataSource={videoList}
loading={isLoading}
onChange={onChange}
title={() => (
<div className="flex justify-between w-full">
<div>
<Pagination
showSizeChanger
showQuickJumper
size="small"
total={data?.total}
current={page}
pageSize={size}
showTotal={(total) => `共 ${total} 条`}
onShowSizeChange={(current, size) => {
setPage(current);
setSize(size);
}}
onChange={(page, size) => {
setPage(page);
}}
/>
</div>
<div>
<Button type="primary" onClick={() => setOpenDrawer(true)}>
新增视频
</Button>
</div>
</div>
)}
/>
</SortableContext>
</DndContext>
<CreateVideo open={openDrawer} editId={curId} courseId={courseId} onClose={closeDrawer} />
</Drawer>
);
}
后端service
// 视频列表排序
async sort(sortList: Array<{ id: number; sort: number }>) {
const ids = _.map(sortList, (i) => i.id).join(',');
const updateCases = _.map(
sortList,
(i) => `WHEN ${i.id} THEN '${i.sort}'`,
).join(' ');
const sql = `UPDATE tp_course_video SET sort = CASE id ${updateCases} END WHERE id IN (${ids})`;
await this.datasource.manager.transaction(async (transaction) => {
await transaction.query(sql);
});
return null;
}
mysql 批量更新单个字段的多个值
UPDATE categories
SET display_order = CASE id
WHEN 1 THEN 3
WHEN 2 THEN 4
WHEN 3 THEN 5
END
WHERE id IN (1,2,3)
上面这段SQL是批量更新display_order字段,id为1则改为3,id为2则改为4,以此类推。
要更新多个字段时,只需要稍加修改:
UPDATE categories
SET display_order = CASE id
WHEN 1 THEN 3
WHEN 2 THEN 4
WHEN 3 THEN 5
END,
title = CASE id
WHEN 1 THEN 'New Title 1'
WHEN 2 THEN 'New Title 2'
WHEN 3 THEN 'New Title 3'
END
WHERE id IN (1,2,3)