教你如何拿下 ant design 拖拽表格

2023-06-19 下午前端 83 次浏览暂无评论
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)

目录

ICP备案号:鲁ICP备2020040322号