瀏覽代碼

update: 移除react-dnd 使用 dnd-kit

xyh 2 年之前
父節點
當前提交
5067fbc7b6

+ 0 - 2
packages/app/package.json

@@ -32,8 +32,6 @@
     "print-js": "^1.6.0",
     "react": "^18.2.0",
     "react-contexify": "^6.0.0",
-    "react-dnd": "^16.0.1",
-    "react-dnd-html5-backend": "^16.0.1",
     "react-dom": "^18.2.0",
     "react-error-boundary": "^3.1.4",
     "react-hook-form": "^7.43.0",

+ 47 - 46
packages/app/src/components/table/header-th/index.tsx

@@ -1,70 +1,71 @@
-import {
-  Header,
-  flexRender,
-  Updater,
-  ColumnOrderState,
-  TableState,
-  Column,
-} from '@tanstack/react-table';
+import {Header, flexRender} from '@tanstack/react-table';
 import classNames from 'classnames';
-import {FC} from 'react';
+import {FC, CSSProperties} from 'react';
 import css from '../index.module.css';
-import {useDrag, useDrop} from 'react-dnd';
-import {arrayMove} from '@dnd-kit/sortable';
+import {useSortable} from '@dnd-kit/sortable';
 
 type Props = {
-  update: (updater: Updater<ColumnOrderState>) => void;
   header: Header<Record<string, any>, unknown>;
-  getState: () => TableState;
+  useDiv?: boolean;
 };
 
-const HeaderTh: FC<Props> = function ({header, update, getState}) {
-  const [, dropRef] = useDrop({
-    accept: 'table',
-    drop(item: Column<any>) {
-      const {id: fromId} = item,
-        toId = header.column.id;
-
-      if (fromId === toId) return;
-      const {columnOrder} = getState();
-
-      const fromIdx = columnOrder.findIndex(val => val === fromId),
-        toIdx = columnOrder.findIndex(val => val === toId);
-
-      update(arrayMove(columnOrder, fromIdx, toIdx));
-    },
+const HeaderTh: FC<Props> = function ({header, useDiv}) {
+  const {setNodeRef, attributes, listeners} = useSortable({
+    id: header.id,
+    data: {header},
   });
 
-  const [, dragRef] = useDrag({
-    type: 'table',
-    item: () => header.column,
-    collect(monitor) {
-      return {isDragging: monitor.isDragging()};
-    },
-  });
+  const style: CSSProperties = {
+    width: header.getSize(),
+    cursor: 'pointer',
+    opacity: useDiv ? '0.5' : '1',
+  };
 
-  return (
-    <th
-      key={header.id}
-      style={{
-        width: header.getSize(),
-        cursor: 'pointer',
-      }}
-      ref={dropRef}
-    >
-      <div ref={dragRef}>
+  if (useDiv) {
+    return (
+      <div
+        key={header.id}
+        style={style}
+        ref={setNodeRef}
+        {...attributes}
+        {...listeners}
+        className={css.tableHeadTh}
+      >
         {header.isPlaceholder
           ? null
           : flexRender(header.column.columnDef.header, header.getContext())}
 
         <div
           onMouseDown={header.getResizeHandler()}
-          onClick={e => e.stopPropagation()}
+          onPointerDown={e => e.stopPropagation()}
           className={classNames(css.resizer, {
             [css.resizing]: header.column.getIsResizing(),
           })}
         />
       </div>
+    );
+  }
+
+  return (
+    <th
+      key={header.id}
+      style={style}
+      ref={setNodeRef}
+      {...attributes}
+      {...listeners}
+      className={css.tableHeadTh}
+    >
+      {header.isPlaceholder
+        ? null
+        : flexRender(header.column.columnDef.header, header.getContext())}
+
+      <div
+        onMouseDown={header.getResizeHandler()}
+        onPointerDown={e => e.stopPropagation()}
+        className={classNames(css.resizer, {
+          [css.resizing]: header.column.getIsResizing(),
+        })}
+      />
     </th>
   );
 };

+ 34 - 2
packages/app/src/components/table/hooks.ts

@@ -1,11 +1,13 @@
-import {DragMoveEvent} from '@dnd-kit/core';
 import {
   createColumnHelper,
   useReactTable,
   getCoreRowModel,
+  Header,
 } from '@tanstack/react-table';
 import {ColumnType} from 'antd/es/table';
 import {useRef, useState} from 'react';
+import {DragStartEvent, DragEndEvent} from '@dnd-kit/core';
+import {arrayMove} from '@dnd-kit/sortable';
 
 function parseColumn<T>(columns: ColumnType<T>[]) {
   const hepler = createColumnHelper<Record<string, any>>();
@@ -86,5 +88,35 @@ export function useTable<T extends Record<string, any>>(
     onColumnOrderChange: setColumnOrder,
   });
 
-  return [{...table}, setColumnOrder] as const;
+  const [active, setActive] = useState<Header<
+    Record<string, any>,
+    unknown
+  > | null>(null);
+
+  function onDragStart(event: DragStartEvent) {
+    setActive(event.active.data.current?.header);
+  }
+
+  function onDragEnd({active, over}: DragEndEvent) {
+    setActive(null);
+
+    if (!over) return;
+
+    const {id: activeId} = active,
+      {id: overId} = over;
+    if (activeId === overId) return;
+    const {setColumnOrder} = table;
+
+    setColumnOrder(function (prev) {
+      const fromIdx = prev.findIndex(val => val === activeId),
+        toIdx = prev.findIndex(val => val === overId);
+
+      return arrayMove(prev, fromIdx, toIdx);
+    });
+  }
+
+  return [
+    {...table, active},
+    {onDragStart, onDragEnd},
+  ] as const;
 }

+ 18 - 18
packages/app/src/components/table/index.module.css

@@ -31,26 +31,26 @@
   position: sticky;
   top: 0;
   text-align: center;
+}
 
-  & th {
-    position: relative;
-    min-width: 0;
-    padding: 16px;
-    margin: 0;
-    font-weight: 600;
-    color: rgb(0 0 0 / 88%);
-    text-align: center;
-    overflow-wrap: anywhere;
-    list-style: none;
-    background: #fafafa;
-    border-inline-end: 1px solid #f0f0f0;
-    border-bottom: 1px solid #f0f0f0;
-    border-start-start-radius: 8px;
-    transition: background 0.2s ease;
+.table-head-th {
+  position: relative;
+  min-width: 0;
+  padding: 16px;
+  margin: 0;
+  font-weight: 600;
+  color: rgb(0 0 0 / 88%);
+  text-align: center;
+  overflow-wrap: anywhere;
+  list-style: none;
+  background: #fafafa;
+  border-inline-end: 1px solid #f0f0f0;
+  border-bottom: 1px solid #f0f0f0;
+  border-start-start-radius: 8px;
+  transition: background 0.2s ease;
 
-    &:last-child {
-      border-start-end-radius: 8px;
-    }
+  &:last-child {
+    border-start-end-radius: 8px;
   }
 }
 

+ 72 - 53
packages/app/src/components/table/index.tsx

@@ -1,5 +1,5 @@
 import {Spin, Pagination} from 'antd';
-import {ColumnsType} from 'antd/es/table';
+import {ColumnsType, ColumnType} from 'antd/es/table';
 import {
   createPageContext,
   createSearchContext,
@@ -12,6 +12,12 @@ import {useTable} from './hooks';
 import {flexRender} from '@tanstack/react-table';
 import css from './index.module.css';
 import HeaderTh from './header-th';
+import {DndContext, PointerSensor, useSensor, DragOverlay} from '@dnd-kit/core';
+import {
+  SortableContext,
+  horizontalListSortingStrategy,
+} from '@dnd-kit/sortable';
+import {restrictToHorizontalAxis} from '@dnd-kit/modifiers';
 
 type Props<T> = {
   columns: ColumnsType<T>;
@@ -38,63 +44,76 @@ function Table<T extends Record<string, any>>(props: Props<T>): ReactElement {
   const [isSearching] = useTableSearchState(searchContext);
 
   const [
-    {
-      getHeaderGroups,
-      getRowModel,
-      getCenterTotalSize,
-      setColumnOrder,
-      getState,
-    },
+    {getHeaderGroups, getRowModel, getCenterTotalSize, active},
+    {onDragEnd, onDragStart},
   ] = useTable(columns, data);
 
+  const sensor = useSensor(PointerSensor);
+
   return (
     <Spin spinning={isSearching}>
       <div className={css.tableWrapper} id='table_wrapper'>
-        <table className={css.table} style={{width: getCenterTotalSize()}}>
-          <thead className={css.tableHead}>
-            {getHeaderGroups().map(function ({id, headers}) {
-              return (
-                <tr key={id}>
-                  {headers.map(header => (
-                    <HeaderTh
-                      key={header.id}
-                      header={header}
-                      update={setColumnOrder}
-                      getState={getState}
-                    />
-                  ))}
-                </tr>
-              );
-            })}
-          </thead>
-          <tbody className={css.tableBody}>
-            {getRowModel().rows.map(function ({id, getVisibleCells}) {
-              return (
-                <tr key={id}>
-                  {getVisibleCells().map(function ({id, column, getContext}) {
-                    const align =
-                      (
-                        column.columnDef.meta as {
-                          align: 'left' | 'right' | 'center';
-                        }
-                      )?.align ?? 'left';
-                    return (
-                      <td
-                        key={id}
-                        style={{
-                          width: column.getSize(),
-                          textAlign: align,
-                        }}
-                      >
-                        {flexRender(column.columnDef.cell, getContext())}
-                      </td>
-                    );
-                  })}
-                </tr>
-              );
-            })}
-          </tbody>
-        </table>
+        <DndContext
+          onDragStart={onDragStart}
+          onDragEnd={onDragEnd}
+          sensors={[sensor]}
+          modifiers={[restrictToHorizontalAxis]}
+        >
+          <SortableContext
+            items={(columns as ColumnType<T>[]).map(val =>
+              val.dataIndex!.toString(),
+            )}
+            strategy={horizontalListSortingStrategy}
+          >
+            <table className={css.table} style={{width: getCenterTotalSize()}}>
+              <thead className={css.tableHead}>
+                {getHeaderGroups().map(function ({id, headers}) {
+                  return (
+                    <tr key={id}>
+                      {headers.map(header => (
+                        <HeaderTh key={header.id} header={header} />
+                      ))}
+                    </tr>
+                  );
+                })}
+              </thead>
+              <tbody className={css.tableBody}>
+                {getRowModel().rows.map(function ({id, getVisibleCells}) {
+                  return (
+                    <tr key={id}>
+                      {getVisibleCells().map(function ({
+                        id,
+                        column,
+                        getContext,
+                      }) {
+                        const align =
+                          (
+                            column.columnDef.meta as {
+                              align: 'left' | 'right' | 'center';
+                            }
+                          )?.align ?? 'left';
+                        return (
+                          <td
+                            key={id}
+                            style={{
+                              width: column.getSize(),
+                              textAlign: align,
+                            }}
+                          >
+                            {flexRender(column.columnDef.cell, getContext())}
+                          </td>
+                        );
+                      })}
+                    </tr>
+                  );
+                })}
+              </tbody>
+            </table>
+            <DragOverlay>
+              {active ? <HeaderTh header={active} useDiv /> : null}
+            </DragOverlay>
+          </SortableContext>
+        </DndContext>
       </div>
       <Pagination
         className={css.pagination}

+ 11 - 15
packages/app/src/index.tsx

@@ -12,8 +12,6 @@ import 'dayjs/locale/zh-cn';
 import zhCN from 'antd/es/locale/zh_CN';
 import {Loading} from '@components';
 import {QUERY_CLIENT} from '@utils';
-import {DndProvider, useDrop} from 'react-dnd';
-import {HTML5Backend} from 'react-dnd-html5-backend';
 
 const root = createRoot(document.getElementById('root')!);
 
@@ -31,18 +29,16 @@ const themeConfig: ThemeConfig = {
 
 root.render(
   <StrictMode>
-    <DndProvider backend={HTML5Backend}>
-      <QueryClientProvider client={QUERY_CLIENT}>
-        <ConfigProvider theme={themeConfig} locale={zhCN}>
-          <Suspense
-            fallback={<Loading tip='正在加载' width='100vw' height='100vh' />}
-          >
-            <BrowserRouter>
-              <RootRoutes />
-            </BrowserRouter>
-          </Suspense>
-        </ConfigProvider>
-      </QueryClientProvider>
-    </DndProvider>
+    <QueryClientProvider client={QUERY_CLIENT}>
+      <ConfigProvider theme={themeConfig} locale={zhCN}>
+        <Suspense
+          fallback={<Loading tip='正在加载' width='100vw' height='100vh' />}
+        >
+          <BrowserRouter>
+            <RootRoutes />
+          </BrowserRouter>
+        </Suspense>
+      </ConfigProvider>
+    </QueryClientProvider>
   </StrictMode>,
 );

+ 3 - 69
pnpm-lock.yaml

@@ -158,12 +158,6 @@ importers:
       react-contexify:
         specifier: ^6.0.0
         version: 6.0.0(react-dom@18.2.0)(react@18.2.0)
-      react-dnd:
-        specifier: ^16.0.1
-        version: 16.0.1(@types/node@18.11.18)(@types/react@18.0.27)(react@18.2.0)
-      react-dnd-html5-backend:
-        specifier: ^16.0.1
-        version: 16.0.1
       react-dom:
         specifier: ^18.2.0
         version: 18.2.0(react@18.2.0)
@@ -2722,18 +2716,6 @@ packages:
       react-dom: 18.2.0(react@18.2.0)
     dev: false
 
-  /@react-dnd/asap@5.0.2:
-    resolution: {integrity: sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==}
-    dev: false
-
-  /@react-dnd/invariant@4.0.2:
-    resolution: {integrity: sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==}
-    dev: false
-
-  /@react-dnd/shallowequal@4.0.2:
-    resolution: {integrity: sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==}
-    dev: false
-
   /@remix-run/router@1.3.1:
     resolution: {integrity: sha512-+eun1Wtf72RNRSqgU7qM2AMX/oHp+dnx7BHk1qhK5ZHzdHTUU4LA1mGG1vT+jMc8sbhG3orvsfOmryjzx2PzQw==}
     engines: {node: '>=14'}
@@ -3407,6 +3389,7 @@ packages:
 
   /@types/prop-types@15.7.5:
     resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
+    dev: true
 
   /@types/qs@6.9.7:
     resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==}
@@ -3446,6 +3429,7 @@ packages:
       '@types/prop-types': 15.7.5
       '@types/scheduler': 0.16.2
       csstype: 3.1.1
+    dev: true
 
   /@types/retry@0.12.0:
     resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
@@ -3453,6 +3437,7 @@ packages:
 
   /@types/scheduler@0.16.2:
     resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
+    dev: true
 
   /@types/semver@7.3.13:
     resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==}
@@ -5697,14 +5682,6 @@ packages:
       path-type: 4.0.0
     dev: true
 
-  /dnd-core@16.0.1:
-    resolution: {integrity: sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==}
-    dependencies:
-      '@react-dnd/asap': 5.0.2
-      '@react-dnd/invariant': 4.0.2
-      redux: 4.2.1
-    dev: false
-
   /dns-equal@1.0.0:
     resolution: {integrity: sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==}
     dev: true
@@ -7065,12 +7042,6 @@ packages:
     hasBin: true
     dev: true
 
-  /hoist-non-react-statics@3.3.2:
-    resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
-    dependencies:
-      react-is: 16.13.1
-    dev: false
-
   /homedir-polyfill@1.0.3:
     resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==}
     engines: {node: '>=0.10.0'}
@@ -10980,37 +10951,6 @@ packages:
       react-dom: 18.2.0(react@18.2.0)
     dev: false
 
-  /react-dnd-html5-backend@16.0.1:
-    resolution: {integrity: sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==}
-    dependencies:
-      dnd-core: 16.0.1
-    dev: false
-
-  /react-dnd@16.0.1(@types/node@18.11.18)(@types/react@18.0.27)(react@18.2.0):
-    resolution: {integrity: sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==}
-    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
-    dependencies:
-      '@react-dnd/invariant': 4.0.2
-      '@react-dnd/shallowequal': 4.0.2
-      '@types/node': 18.11.18
-      '@types/react': 18.0.27
-      dnd-core: 16.0.1
-      fast-deep-equal: 3.1.3
-      hoist-non-react-statics: 3.3.2
-      react: 18.2.0
-    dev: false
-
   /react-dom@18.2.0(react@18.2.0):
     resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
     peerDependencies:
@@ -11285,12 +11225,6 @@ packages:
       postcss-value-parser: 3.3.1
     dev: false
 
-  /redux@4.2.1:
-    resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==}
-    dependencies:
-      '@babel/runtime': 7.20.13
-    dev: false
-
   /regenerate-unicode-properties@10.1.0:
     resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==}
     engines: {node: '>=4'}