瀏覽代碼

更新表格拖拽功能

shaogen1995 2 年之前
父節點
當前提交
2f8299a977
共有 5 個文件被更改,包括 279 次插入45 次删除
  1. 120 0
      package-lock.json
  2. 3 0
      package.json
  3. 36 4
      src/pages/Wall/WallTable/index.module.scss
  4. 112 41
      src/pages/Wall/WallTable/index.tsx
  5. 8 0
      src/utils/http.ts

+ 120 - 0
package-lock.json

@@ -19,9 +19,12 @@
         "axios": "^1.1.3",
         "dayjs": "^1.11.7",
         "echarts": "^5.4.0",
+        "immutability-helper": "^3.1.1",
         "js-base64": "^3.7.3",
         "js-export-excel": "^1.1.4",
         "react": "^18.2.0",
+        "react-dnd": "^16.0.1",
+        "react-dnd-html5-backend": "^16.0.1",
         "react-dom": "^18.2.0",
         "react-lazyimg-component": "^1.0.1",
         "react-redux": "^8.0.4",
@@ -3144,6 +3147,21 @@
         "react-dom": ">=16.9.0"
       }
     },
+    "node_modules/@react-dnd/asap": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmmirror.com/@react-dnd/asap/-/asap-5.0.2.tgz",
+      "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A=="
+    },
+    "node_modules/@react-dnd/invariant": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/@react-dnd/invariant/-/invariant-4.0.2.tgz",
+      "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw=="
+    },
+    "node_modules/@react-dnd/shallowequal": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
+      "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
+    },
     "node_modules/@rollup/plugin-babel": {
       "version": "5.3.1",
       "resolved": "https://registry.npmmirror.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -6385,6 +6403,16 @@
       "resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz",
       "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
     },
+    "node_modules/dnd-core": {
+      "version": "16.0.1",
+      "resolved": "https://registry.npmmirror.com/dnd-core/-/dnd-core-16.0.1.tgz",
+      "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
+      "dependencies": {
+        "@react-dnd/asap": "^5.0.1",
+        "@react-dnd/invariant": "^4.0.1",
+        "redux": "^4.2.0"
+      }
+    },
     "node_modules/dns-equal": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/dns-equal/-/dns-equal-1.0.0.tgz",
@@ -8572,6 +8600,11 @@
       "resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.16.tgz",
       "integrity": "sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ=="
     },
+    "node_modules/immutability-helper": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/immutability-helper/-/immutability-helper-3.1.1.tgz",
+      "integrity": "sha512-Q0QaXjPjwIju/28TsugCHNEASwoCcJSyJV3uO1sOIQGI0jKgm9f41Lvz0DZj3n46cNCyAZTsEYoY4C2bVRUzyQ=="
+    },
     "node_modules/immutable": {
       "version": "4.1.0",
       "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.1.0.tgz",
@@ -14161,6 +14194,43 @@
         "node": ">=8"
       }
     },
+    "node_modules/react-dnd": {
+      "version": "16.0.1",
+      "resolved": "https://registry.npmmirror.com/react-dnd/-/react-dnd-16.0.1.tgz",
+      "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
+      "dependencies": {
+        "@react-dnd/invariant": "^4.0.1",
+        "@react-dnd/shallowequal": "^4.0.1",
+        "dnd-core": "^16.0.1",
+        "fast-deep-equal": "^3.1.3",
+        "hoist-non-react-statics": "^3.3.2"
+      },
+      "peerDependencies": {
+        "@types/hoist-non-react-statics": ">= 3.3.1",
+        "@types/node": ">= 12",
+        "@types/react": ">= 16",
+        "react": ">= 16.14"
+      },
+      "peerDependenciesMeta": {
+        "@types/hoist-non-react-statics": {
+          "optional": true
+        },
+        "@types/node": {
+          "optional": true
+        },
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/react-dnd-html5-backend": {
+      "version": "16.0.1",
+      "resolved": "https://registry.npmmirror.com/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
+      "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
+      "dependencies": {
+        "dnd-core": "^16.0.1"
+      }
+    },
     "node_modules/react-dom": {
       "version": "18.2.0",
       "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.2.0.tgz",
@@ -19300,6 +19370,21 @@
         "rc-util": "^5.24.4"
       }
     },
+    "@react-dnd/asap": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmmirror.com/@react-dnd/asap/-/asap-5.0.2.tgz",
+      "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A=="
+    },
+    "@react-dnd/invariant": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/@react-dnd/invariant/-/invariant-4.0.2.tgz",
+      "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw=="
+    },
+    "@react-dnd/shallowequal": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
+      "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
+    },
     "@rollup/plugin-babel": {
       "version": "5.3.1",
       "resolved": "https://registry.npmmirror.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -21918,6 +22003,16 @@
       "resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz",
       "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
     },
+    "dnd-core": {
+      "version": "16.0.1",
+      "resolved": "https://registry.npmmirror.com/dnd-core/-/dnd-core-16.0.1.tgz",
+      "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
+      "requires": {
+        "@react-dnd/asap": "^5.0.1",
+        "@react-dnd/invariant": "^4.0.1",
+        "redux": "^4.2.0"
+      }
+    },
     "dns-equal": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/dns-equal/-/dns-equal-1.0.0.tgz",
@@ -23646,6 +23741,11 @@
       "resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.16.tgz",
       "integrity": "sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ=="
     },
+    "immutability-helper": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/immutability-helper/-/immutability-helper-3.1.1.tgz",
+      "integrity": "sha512-Q0QaXjPjwIju/28TsugCHNEASwoCcJSyJV3uO1sOIQGI0jKgm9f41Lvz0DZj3n46cNCyAZTsEYoY4C2bVRUzyQ=="
+    },
     "immutable": {
       "version": "4.1.0",
       "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.1.0.tgz",
@@ -27770,6 +27870,26 @@
         }
       }
     },
+    "react-dnd": {
+      "version": "16.0.1",
+      "resolved": "https://registry.npmmirror.com/react-dnd/-/react-dnd-16.0.1.tgz",
+      "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
+      "requires": {
+        "@react-dnd/invariant": "^4.0.1",
+        "@react-dnd/shallowequal": "^4.0.1",
+        "dnd-core": "^16.0.1",
+        "fast-deep-equal": "^3.1.3",
+        "hoist-non-react-statics": "^3.3.2"
+      }
+    },
+    "react-dnd-html5-backend": {
+      "version": "16.0.1",
+      "resolved": "https://registry.npmmirror.com/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
+      "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
+      "requires": {
+        "dnd-core": "^16.0.1"
+      }
+    },
     "react-dom": {
       "version": "18.2.0",
       "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.2.0.tgz",

+ 3 - 0
package.json

@@ -14,9 +14,12 @@
     "axios": "^1.1.3",
     "dayjs": "^1.11.7",
     "echarts": "^5.4.0",
+    "immutability-helper": "^3.1.1",
     "js-base64": "^3.7.3",
     "js-export-excel": "^1.1.4",
     "react": "^18.2.0",
+    "react-dnd": "^16.0.1",
+    "react-dnd-html5-backend": "^16.0.1",
     "react-dom": "^18.2.0",
     "react-lazyimg-component": "^1.0.1",
     "react-redux": "^8.0.4",

+ 36 - 4
src/pages/Wall/WallTable/index.module.scss

@@ -1,17 +1,49 @@
-.WallTable{
+.WallTable {
   width: 1200px;
   height: 100%;
-  :global{
-    .title{
+
+  :global {
+    .title {
       display: flex;
       align-items: center;
       justify-content: space-between;
       font-size: 16px;
       font-weight: 700;
       width: 100%;
+
+      .txt {
+        display: flex;
+        align-items: center;
+
+        .hotTitleInco {
+          cursor: pointer;
+          position: relative;
+          z-index: 10;
+          width: 16px;
+          height: 16px;
+          border-radius: 50%;
+          background-color: #696969;
+          margin-left: 8px;
+          text-align: center;
+          line-height: 16px;
+          color: #fff;
+          font-size: 12px;
+
+        }
+      }
     }
-    .table{
+
+    .table {
       margin-top: 15px;
     }
+
+    // 表头拖拽样式
+    .drop-over-downward td {
+      border-bottom: 2px dashed var(--themeColor) !important;
+    }
+
+    .drop-over-upward td {
+      border-top: 2px dashed var(--themeColor) !important;
+    }
   }
 }

+ 112 - 41
src/pages/Wall/WallTable/index.tsx

@@ -8,7 +8,7 @@ import {
   wallSortAPI,
 } from "@/store/action/wall";
 import { WallTableList } from "@/types";
-import { Button, message, Popconfirm, Switch, Table } from "antd";
+import { Button, message, Popconfirm, Switch, Table, Tooltip } from "antd";
 import React, {
   useCallback,
   useEffect,
@@ -19,6 +19,12 @@ import React, {
 import { useDispatch, useSelector } from "react-redux";
 import WallAdd from "../WallAdd";
 import styles from "./index.module.scss";
+import { ExclamationOutlined } from "@ant-design/icons";
+
+// 表格拖动排序-----------------
+import { DndProvider, useDrag, useDrop } from "react-dnd";
+import { HTML5Backend } from "react-dnd-html5-backend";
+
 function WallTable() {
   const dispatch = useDispatch();
 
@@ -48,19 +54,6 @@ function WallTable() {
     [dispatch]
   );
 
-  // 点击上移或者下移
-
-  const srotFu = useCallback(
-    async (id: number, broId: number) => {
-      const res: any = await wallSortAPI(id, broId);
-      if (res.code === 0) {
-        message.success("操作成功!");
-        dispatch(getWallTableListAPI());
-      }
-    },
-    [dispatch]
-  );
-
   // 从仓库中获取列表数据
   const results = useSelector((state: RootState) => state.wallReducer.list);
 
@@ -134,27 +127,11 @@ function WallTable() {
       {
         width: 200,
         title: "操作",
-        render: (item: WallTableList, _: any, index: any) => (
+        render: (item: WallTableList) => (
           <>
             <Button
               size="small"
               type="text"
-              disabled={index === 0}
-              onClick={() => srotFu(item.id, results[index - 1].id)}
-            >
-              上移
-            </Button>
-            <Button
-              size="small"
-              type="text"
-              disabled={index === results.length - 1}
-              onClick={() => srotFu(item.id, results[index + 1].id)}
-            >
-              下移
-            </Button>
-            <Button
-              size="small"
-              type="text"
               onClick={() => editTableFu(item.id)}
             >
               编辑
@@ -173,12 +150,96 @@ function WallTable() {
         ),
       },
     ];
-  }, [delTableFu, editTableFu, isEnabledClickFu, results, srotFu]);
+  }, [delTableFu, editTableFu, isEnabledClickFu]);
+
+  // 表格拖动排序-----------------
+  interface DraggableBodyRowProps
+    extends React.HTMLAttributes<HTMLTableRowElement> {
+    index: number;
+    moveRow: (dragIndex: number, hoverIndex: number) => void;
+  }
+
+  const type = "DraggableBodyRow";
+
+  const DraggableBodyRow = ({
+    index,
+    moveRow,
+    className,
+    style,
+    ...restProps
+  }: DraggableBodyRowProps) => {
+    const ref = useRef<HTMLTableRowElement>(null);
+    const [{ isOver, dropClassName }, drop] = useDrop({
+      accept: type,
+      collect: (monitor) => {
+        const { index: dragIndex } = monitor.getItem() || {};
+        if (dragIndex === index) {
+          return {};
+        }
+        return {
+          isOver: monitor.isOver(),
+          dropClassName:
+            dragIndex < index ? " drop-over-downward" : " drop-over-upward",
+        };
+      },
+      drop: (item: { index: number }) => {
+        moveRow(item.index, index);
+      },
+    });
+    const [, drag] = useDrag({
+      type,
+      item: { index },
+      collect: (monitor) => ({
+        isDragging: monitor.isDragging(),
+      }),
+    });
+    drop(drag(ref));
+
+    return (
+      <tr
+        ref={ref}
+        className={`${className}${isOver ? dropClassName : ""}`}
+        style={{ cursor: "move", ...style }}
+        {...restProps}
+      />
+    );
+  };
+
+  const components = {
+    body: {
+      row: DraggableBodyRow,
+    },
+  };
+
+  const moveRow = useCallback(
+    async (dragIndex: number, hoverIndex: number) => {
+      if (dragIndex === hoverIndex) return;
+      // 交互位置-之前的id
+      const beforeId = results[dragIndex].id;
+      const afterId = results[hoverIndex].id;
+
+      const res = await wallSortAPI(beforeId, afterId);
+
+      if (res.code === 0) dispatch(getWallTableListAPI());
+    },
+    [dispatch, results]
+  );
 
   return (
     <div className={styles.WallTable}>
       <div className="title">
-        <div className="txt">内容管理</div>
+        <div className="txt">
+          内容管理
+          {results.length >= 2 ? (
+            <div className="hotTitleInco">
+              <Tooltip title="按住鼠标可拖动表格调整顺序">
+                <div className="hotTitleInco1">
+                  <ExclamationOutlined />
+                </div>
+              </Tooltip>
+            </div>
+          ) : null}
+        </div>
         <div className="titleButton">
           <Button type="primary" onClick={() => editTableFu(0)}>
             新增
@@ -186,14 +247,24 @@ function WallTable() {
         </div>
       </div>
       <div className="table">
-        <Table
-          size="small"
-          scroll={{ y: 428 }}
-          dataSource={results}
-          columns={columns}
-          rowKey="id"
-          pagination={false}
-        />
+        <DndProvider backend={HTML5Backend}>
+          <Table
+            size="small"
+            scroll={{ y: 428 }}
+            columns={columns}
+            dataSource={results}
+            components={components}
+            rowKey="id"
+            pagination={false}
+            onRow={(_, index) => {
+              const attr = {
+                index,
+                moveRow,
+              };
+              return attr as React.HTMLAttributes<any>;
+            }}
+          />
+        </DndProvider>
       </div>
       {/* 点击新增或者编辑 */}
       {open ? (

+ 8 - 0
src/utils/http.ts

@@ -12,6 +12,14 @@ export const baseURL =
   process.env.NODE_ENV === "development" ? "https://xuzhouwall.4dage.com" : "";
 // process.env.NODE_ENV === "development" ? "http://192.168.20.55:8039" : "";
 
+// 处理  类型“AxiosResponse<any, any>”上不存在属性“code”
+declare module "axios" {
+  interface AxiosResponse {
+    code: number;
+    // 这里追加你的参数
+  }
+}
+
 // 创建 axios 实例
 const http = axios.create({
   // --------线下的地址不用加/api/