Преглед на файлове

feat: 添加字段拖拽排序

liaojiaxing преди 4 месеца
родител
ревизия
b501bc2e15
променени са 4 файла, в които са добавени 249 реда и са изтрити 215 реда
  1. 205 0
      apps/er-designer/src/pages/er/components/ColumnItem.tsx
  2. 24 215
      apps/er-designer/src/pages/er/components/TableItem.tsx
  3. 2 0
      package.json
  4. 18 0
      pnpm-lock.yaml

+ 205 - 0
apps/er-designer/src/pages/er/components/ColumnItem.tsx

@@ -0,0 +1,205 @@
+import React from "react";
+import { HolderOutlined } from "@ant-design/icons";
+import {
+  Button,
+  Form,
+  Input,
+  InputNumber,
+  Popover,
+  Row,
+  Col,
+  Tooltip,
+  Select,
+} from "antd";
+import { ColumnItem as ColumnItemType } from "@/type";
+import { DATA_TYPE_OPTIONS } from "@/constants";
+import { useSortable } from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+
+export default function ColumnItem({
+  column,
+  onChange,
+  onDelete,
+}: {
+  column: ColumnItemType;
+  onChange: (key: string, value: any) => void;
+  onDelete: (id: string) => void;
+}) {
+  const { setNodeRef, attributes, listeners, transform, transition } =
+    useSortable({
+      id: column.id,
+      transition: {
+        duration: 500,
+        easing: "cubic-bezier(0.25, 1, 0.5, 1)",
+      },
+    });
+
+    const styles = {
+      transform: CSS.Transform.toString(transform),
+      transition,
+    };
+
+  return (
+    <div
+      key={column.id}
+      className="column-item flex gap-4px items-center jutify-space-between hover:bg-gray-100 mb-4px"
+      style={styles}
+      ref={setNodeRef}
+      {...attributes}
+      data-cypress="draggable-item"
+    >
+      <HolderOutlined className="cursor-move" data-cypress="draggable-handle" {...listeners}/>
+      <Tooltip title="字段编码">
+        <Input
+          placeholder="编码"
+          defaultValue={column.schemaName}
+          className="flex-1"
+          onChange={(e) => onChange("schemaName", e.target.value)}
+        />
+      </Tooltip>
+      <Tooltip title="字段类型">
+        <Select
+          placeholder="类型"
+          className="w-80px"
+          options={DATA_TYPE_OPTIONS}
+          value={column.type}
+          onChange={(value) => onChange("type", value)}
+          dropdownStyle={{ width: 120 }}
+        />
+      </Tooltip>
+      <Tooltip title="非空">
+        <div
+          className="
+            rounded-4px 
+            cus-btn 
+            w-32px 
+            h-32px 
+            bg-#eee 
+            flex-none 
+            text-center 
+            leading-32px 
+            cursor-pointer 
+            hover:bg-#ddd"
+          style={
+            column.isRequired ? { background: "#1677ff", color: "#fff" } : {}
+          }
+          onClick={() => onChange("isRequired", !column.isRequired)}
+        >
+          !
+        </div>
+      </Tooltip>
+      <Tooltip title="唯一">
+        <div
+          className="
+            rounded-4px 
+            cus-btn 
+            w-32px 
+            h-32px 
+            bg-#eee 
+            flex-none 
+            text-center 
+            leading-32px 
+            cursor-pointer 
+            hover:bg-#ddd"
+          style={
+            column.isUnique ? { background: "#1677ff", color: "#fff" } : {}
+          }
+          onClick={() => onChange("isUnique", !column.isUnique)}
+        >
+          1
+        </div>
+      </Tooltip>
+      <Popover
+        trigger="click"
+        placement="right"
+        content={
+          <div
+            className="w-360px max-h-400px overflow-hidden"
+            onClick={(e) => e.stopPropagation()}
+          >
+            <Form layout="vertical">
+              <Row gutter={8}>
+                <Col span={12}>
+                  <Form.Item label="字段名称" name="pkName">
+                    <Input
+                      className="w-full"
+                      placeholder="中文"
+                      value={column.cn_name}
+                      onChange={(e) => onChange("cn_name", e.target.value)}
+                    />
+                    <Input
+                      className="w-full"
+                      placeholder="英文"
+                      value={column.en_name}
+                      onChange={(e) => onChange("en_name", e.target.value)}
+                    />
+                  </Form.Item>
+                </Col>
+                <Col span={12}>
+                  <Form.Item label="默认值" name="pkName">
+                    <Input
+                      className="w-full"
+                      placeholder="默认值"
+                      value={column.defaultValue}
+                      onChange={(e) => onChange("defaultValue", e.target.value)}
+                    />
+                  </Form.Item>
+                </Col>
+              </Row>
+              <Row gutter={8}>
+                <Col span={12}>
+                  <Form.Item label="长度">
+                    <InputNumber
+                      placeholder="请输入"
+                      min={0}
+                      className="w-full"
+                      value={column.maxLength}
+                      onChange={(num) => onChange("maxLength", num)}
+                    />
+                  </Form.Item>
+                </Col>
+                <Col span={12}>
+                  <Form.Item label="精度">
+                    <InputNumber
+                      placeholder="请输入"
+                      min={0}
+                      className="w-full"
+                      value={column.precision}
+                      onChange={(num) => onChange("precision", num)}
+                    />
+                  </Form.Item>
+                </Col>
+              </Row>
+              <Row>
+                <Col span={24}>
+                  <Form.Item label="描述" name="pkName">
+                    <Input.TextArea
+                      className="w-full"
+                      placeholder="描述中文..."
+                    />
+                    <Input.TextArea
+                      className="w-full"
+                      placeholder="描述英文..."
+                    />
+                  </Form.Item>
+                </Col>
+              </Row>
+            </Form>
+            <Button
+              type="default"
+              danger
+              className="w-full"
+              onClick={() => onDelete(column.id)}
+            >
+              删除
+            </Button>
+          </div>
+        }
+      >
+        <div className="rounded-4px cus-btn w-32px h-32px bg-#eee flex-none text-center leading-32px cursor-pointer hover:bg-#ddd">
+          <i className="iconfont icon-gengduo" />
+        </div>
+      </Popover>
+    </div>
+  );
+}

+ 24 - 215
apps/er-designer/src/pages/er/components/TableItem.tsx

@@ -13,7 +13,7 @@ import {
 } from "antd";
 import React, { useEffect, useState } from "react";
 import CustomColorPicker from "@/components/CustomColorPicker";
-import { ColumnItem, TableItemType } from "@/type";
+import { ColumnItem as ColumnItemType, TableItemType } from "@/type";
 import { TABLE_TYPE_OPTIONS, DATA_TYPE_OPTIONS } from "@/constants";
 import { uuid } from "@/utils";
 import { DataType } from "@/enum";
@@ -21,6 +21,8 @@ import { useModel } from "umi";
 import { DndContext } from "@dnd-kit/core";
 import type { DragEndEvent } from "@dnd-kit/core";
 import { SortableContext, arrayMove, verticalListSortingStrategy } from "@dnd-kit/sortable";
+import { restrictToParentElement } from "@dnd-kit/modifiers";
+import ColumnItem from "./ColumnItem";
 
 export default function TableItem({
   data,
@@ -74,7 +76,7 @@ export default function TableItem({
 
   // 添加字段
   const handleAddColumn = () => {
-    const newColumn: ColumnItem = {
+    const newColumn: ColumnItemType = {
       id: uuid(),
       schemaName: "",
       name: "",
@@ -104,8 +106,20 @@ export default function TableItem({
     });
   };
 
-  const handleDragEnd = (event: DragEndEvent) => {
-
+  const handleDragEnd = (dragItem: DragEndEvent) => {
+    const { active, over } = dragItem;
+    if (!active || !over) return; // 处理边界情况
+    
+    const activeIndex = tableColumnList.findIndex((item) => item.id === active.id);
+    const overIndex = tableColumnList.findIndex((item) => item.id === over.id);
+    
+    const newList = arrayMove(tableColumnList, activeIndex, overIndex);
+    setList(newList);
+    onChange({
+      table,
+      tableColumnList: newList,
+      isTable: true,
+    });
   }
 
   return (
@@ -274,221 +288,16 @@ export default function TableItem({
 
           <div className="column-content border-solid border-1px border-#e4e4e4 border-x-none p-y-10px">
             {/* 字段内容 */}
-            <DndContext onDragEnd={handleDragEnd}>
+            <DndContext onDragEnd={handleDragEnd} modifiers={[restrictToParentElement]}>
               <SortableContext items={list.map(item => item.id)} strategy={verticalListSortingStrategy}>
                 {list.map((column, index) => {
                   return (
-                    <div
+                    <ColumnItem
                       key={column.id}
-                      className="column-item flex gap-4px items-center jutify-space-between hover:bg-gray-100 mb-4px"
-                    >
-                      <HolderOutlined className="cursor-move" />
-                      <Tooltip title="字段编码">
-                        <Input
-                          placeholder="编码"
-                          defaultValue={column.schemaName}
-                          className="flex-1"
-                          onChange={(e) =>
-                            handleChangeColumn(
-                              index,
-                              "schemaName",
-                              e.target.value
-                            )
-                          }
-                        />
-                      </Tooltip>
-                      <Tooltip title="字段类型">
-                        <Select
-                          placeholder="类型"
-                          className="w-80px"
-                          options={DATA_TYPE_OPTIONS}
-                          value={column.type}
-                          onChange={(value) =>
-                            handleChangeColumn(index, "type", value)
-                          }
-                          dropdownStyle={{ width: 120 }}
-                        />
-                      </Tooltip>
-                      <Tooltip title="非空">
-                        <div
-                          className="
-                      rounded-4px 
-                      cus-btn 
-                      w-32px 
-                      h-32px 
-                      bg-#eee 
-                      flex-none 
-                      text-center 
-                      leading-32px 
-                      cursor-pointer 
-                      hover:bg-#ddd"
-                          style={
-                            column.isRequired
-                              ? { background: "#1677ff", color: "#fff" }
-                              : {}
-                          }
-                          onClick={() =>
-                            handleChangeColumn(
-                              index,
-                              "isRequired",
-                              !column.isRequired
-                            )
-                          }
-                        >
-                          !
-                        </div>
-                      </Tooltip>
-                      <Tooltip title="唯一">
-                        <div
-                          className="
-                      rounded-4px 
-                      cus-btn 
-                      w-32px 
-                      h-32px 
-                      bg-#eee 
-                      flex-none 
-                      text-center 
-                      leading-32px 
-                      cursor-pointer 
-                      hover:bg-#ddd"
-                          style={
-                            column.isUnique
-                              ? { background: "#1677ff", color: "#fff" }
-                              : {}
-                          }
-                          onClick={() =>
-                            handleChangeColumn(
-                              index,
-                              "isUnique",
-                              !column.isUnique
-                            )
-                          }
-                        >
-                          1
-                        </div>
-                      </Tooltip>
-                      <Popover
-                        trigger="click"
-                        placement="right"
-                        content={
-                          <div
-                            className="w-360px max-h-400px overflow-hidden"
-                            onClick={(e) => e.stopPropagation()}
-                          >
-                            <Form layout="vertical">
-                              <Row gutter={8}>
-                                <Col span={12}>
-                                  <Form.Item label="字段名称" name="pkName">
-                                    <Input
-                                      className="w-full"
-                                      placeholder="中文"
-                                      value={column.cn_name}
-                                      onChange={(e) =>
-                                        handleChangeColumn(
-                                          index,
-                                          "cn_name",
-                                          e.target.value
-                                        )
-                                      }
-                                    />
-                                    <Input
-                                      className="w-full"
-                                      placeholder="英文"
-                                      value={column.en_name}
-                                      onChange={(e) =>
-                                        handleChangeColumn(
-                                          index,
-                                          "en_name",
-                                          e.target.value
-                                        )
-                                      }
-                                    />
-                                  </Form.Item>
-                                </Col>
-                                <Col span={12}>
-                                  <Form.Item label="默认值" name="pkName">
-                                    <Input
-                                      className="w-full"
-                                      placeholder="默认值"
-                                      value={column.defaultValue}
-                                      onChange={(e) =>
-                                        handleChangeColumn(
-                                          index,
-                                          "defaultValue",
-                                          e.target.value
-                                        )
-                                      }
-                                    />
-                                  </Form.Item>
-                                </Col>
-                              </Row>
-                              <Row gutter={8}>
-                                <Col span={12}>
-                                  <Form.Item label="长度">
-                                    <InputNumber
-                                      placeholder="请输入"
-                                      min={0}
-                                      className="w-full"
-                                      value={column.maxLength}
-                                      onChange={(num) =>
-                                        handleChangeColumn(
-                                          index,
-                                          "maxLength",
-                                          num
-                                        )
-                                      }
-                                    />
-                                  </Form.Item>
-                                </Col>
-                                <Col span={12}>
-                                  <Form.Item label="精度">
-                                    <InputNumber
-                                      placeholder="请输入"
-                                      min={0}
-                                      className="w-full"
-                                      value={column.precision}
-                                      onChange={(num) =>
-                                        handleChangeColumn(
-                                          index,
-                                          "precision",
-                                          num
-                                        )
-                                      }
-                                    />
-                                  </Form.Item>
-                                </Col>
-                              </Row>
-                              <Row>
-                                <Col span={24}>
-                                  <Form.Item label="描述" name="pkName">
-                                    <Input.TextArea
-                                      className="w-full"
-                                      placeholder="描述中文..."
-                                    />
-                                    <Input.TextArea
-                                      className="w-full"
-                                      placeholder="描述英文..."
-                                    />
-                                  </Form.Item>
-                                </Col>
-                              </Row>
-                            </Form>
-                            <Button
-                              type="default"
-                              danger
-                              className="w-full"
-                              onClick={() => handleDeleteColumn(column.id)}
-                            >
-                              删除
-                            </Button>
-                          </div>
-                        }
-                      >
-                        <div className="rounded-4px cus-btn w-32px h-32px bg-#eee flex-none text-center leading-32px cursor-pointer hover:bg-#ddd">
-                          <i className="iconfont icon-gengduo" />
-                        </div>
-                      </Popover>
-                    </div>
+                      column={column}
+                      onChange={(key, val) => handleChangeColumn(index, key, val)}
+                      onDelete={handleDeleteColumn}
+                    />
                   );
                 })}
               </SortableContext>

+ 2 - 0
package.json

@@ -48,7 +48,9 @@
     "@codemirror/lang-xml": "^6.1.0",
     "@codemirror/lang-yaml": "^6.1.1",
     "@dnd-kit/core": "^6.3.1",
+    "@dnd-kit/modifiers": "^9.0.0",
     "@dnd-kit/sortable": "^10.0.0",
+    "@dnd-kit/utilities": "^3.2.2",
     "@emotion/css": "^11.13.0",
     "@types/lodash-es": "^4.17.12",
     "@uiw/react-codemirror": "^4.23.3",

+ 18 - 0
pnpm-lock.yaml

@@ -98,9 +98,15 @@ importers:
       '@dnd-kit/core':
         specifier: ^6.3.1
         version: 6.3.1(react-dom@18.3.1)(react@18.3.1)
+      '@dnd-kit/modifiers':
+        specifier: ^9.0.0
+        version: 9.0.0(@dnd-kit/core@6.3.1)(react@18.3.1)
       '@dnd-kit/sortable':
         specifier: ^10.0.0
         version: 10.0.0(@dnd-kit/core@6.3.1)(react@18.3.1)
+      '@dnd-kit/utilities':
+        specifier: ^3.2.2
+        version: 3.2.2(react@18.3.1)
       '@emotion/css':
         specifier: ^11.13.0
         version: 11.13.0
@@ -1761,6 +1767,18 @@ packages:
       react: 18.3.1
       tslib: 2.7.0
 
+  /@dnd-kit/modifiers@9.0.0(@dnd-kit/core@6.3.1)(react@18.3.1):
+    resolution: {integrity: sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==}
+    peerDependencies:
+      '@dnd-kit/core': ^6.3.0
+      react: '>=16.8.0'
+    dependencies:
+      '@dnd-kit/core': 6.3.1(react-dom@18.3.1)(react@18.3.1)
+      '@dnd-kit/utilities': 3.2.2(react@18.3.1)
+      react: 18.3.1
+      tslib: 2.7.0
+    dev: false
+
   /@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1)(react@18.3.1):
     resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==}
     peerDependencies: