Explorar el Código

feat: 添加锁 添加格式刷功能

liaojiaxing hace 7 meses
padre
commit
db80d373d1

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 0
apps/designer/src/assets/image/brush.svg


+ 1 - 1
apps/designer/src/components/basic/rectangle.tsx

@@ -59,7 +59,7 @@ const Rectangle: CompoundedComponent = {
   node: {
     shape: "custom-react-rectangle",
     data: {
-      label: "矩形",
+      label: "",
       ...defaultData,
     },
     ports,

+ 1 - 1
apps/designer/src/components/basic/triangle.tsx

@@ -86,7 +86,7 @@ const Triangle: CompoundedComponent = {
   node: {
     shape: "custom-react-triangle",
     data: {
-      label: "三角形",
+      label: "",
       ...defaultData,
     },
     ports: {

+ 1 - 0
apps/designer/src/components/data.ts

@@ -120,4 +120,5 @@ export const defaultData = {
     color: "#323232",
     width: 1,
   },
+  lock: false,
 }

+ 65 - 2
apps/designer/src/components/lane/Pool.tsx

@@ -1,6 +1,7 @@
 import { Node } from "@antv/x6";
 import CustomInput from "../CustomInput";
 import { LaneItem, StageItem } from "@/types";
+import { useEffect, useRef, useState } from "react";
 
 export default function Pool({ 
   node,
@@ -19,14 +20,13 @@ export default function Pool({
     lane,
     stage,
     direction,
-    textDirection,
     headerHeight,
     stageWidth,
     laneHeadHeight,
   } = node.getData();
 
   const size = node.getSize();
-  
+  const [control, setControl] = useState();
   const handleStartMove = () => {
     node.setData({
       ignoreDrag: false,
@@ -39,6 +39,61 @@ export default function Pool({
     });
   };
 
+  const handleSetControl = (type: "stage" | "lane", index: number) => {
+
+  };
+
+  const dragStart = useRef<{x: number, y: number}>();
+  const dragRef = useRef<{ 
+    originPosition: {x: number, y: number}, 
+    target: string, 
+    type: 'x' | 'y',
+    el: HTMLDivElement
+  }>();
+
+  // TODO 拖拽边框
+  // 1.拖拽开始,记录开始位置
+  // 记录拖对象、方向、位置
+  const handleDragStart = (e: React.MouseEvent<HTMLDivElement, MouseEvent>, target: string, type: 'x' | 'y') => {
+    console.log('mouse down', e)
+    dragRef.current = {
+      originPosition: {x: e.clientX, y: e.clientY},
+      el: e.currentTarget,
+      target,
+      type,
+    };
+  }
+
+  // 2.拖拽中
+  // 更新拖拽对象位置
+  const handleDragMove = (e: MouseEvent) => {
+    if(dragRef.current) {
+      const { originPosition, type, el} = dragRef.current;
+      const {clientX, clientY} = e;
+      const diffX = clientX - originPosition.x;
+      const diffY = clientY - originPosition.y;
+    }
+  }
+
+  // 3.拖拽结束
+  // 更新设置对象
+  const handleDragEnd = () => {
+    console.log('mouse up', dragStart.current)
+    dragRef.current = undefined;
+    document.removeEventListener("mouseup", handleDragEnd);
+    document.removeEventListener("mousemove", handleDragMove);
+  }
+
+  useEffect(() => {
+    document.addEventListener("mouseup", handleDragEnd);
+    // document.addEventListener("mousemove", handleDragMove);
+
+    return () => {
+      document.removeEventListener("mouseup", handleDragEnd);
+      document.removeEventListener("mousemove", handleDragMove);
+    };
+  }, []);
+
   return direction === "vertical" ? (
     // 垂直泳池
     <>
@@ -58,6 +113,12 @@ export default function Pool({
           node={node}
           onChange={(val) => node.setData({ poolName: val })}
         />
+        {/* <div 
+          className="absolute w-full bottom-0 h-4px transition hover:bg-#239edd cursor-ns-resize"
+          onMouseEnter={handleEndMove}
+          onMouseLeave={handleStartMove}
+          onMouseDown={(e) => handleDragStart(e, 'headerHeight', 'y')}
+        ></div> */}
       </div>
       <div
         className="relative content"
@@ -89,6 +150,7 @@ export default function Pool({
                     }}
                     onMouseEnter={handleStartMove}
                     onMouseLeave={handleEndMove}
+                    onClick={() => handleSetControl("stage", index)}
                   >
                     <CustomInput
                       value={stageItem.name}
@@ -146,6 +208,7 @@ export default function Pool({
                 }}
                 onMouseEnter={handleStartMove}
                 onMouseLeave={handleEndMove}
+                onClick={() => handleSetControl("lane", index)}
               >
                 <CustomInput
                   value={item.name}

+ 4 - 4
apps/designer/src/components/lane/setting.tsx

@@ -27,7 +27,7 @@ export default function setting({
   return (
     <ConfigProvider prefixCls="shalu">
       <div
-        className="w-208px shadow-lg absolute top--45px bg-white p-4px"
+        className="w-140px shadow-lg absolute top--45px bg-white p-4px"
         key={lane.length}
       >
         <InputNumber
@@ -52,8 +52,8 @@ export default function setting({
           icon={<i className="iconfont icon-yongdao" />}
           onClick={() => handleSetDirtion("vertical")}
         />
-        <Divider type="vertical" />
-        <Button
+        {/* <Divider type="vertical" /> */}
+        {/* <Button
           size="small"
           className={`${textDirection === "horizontal" ? "active" : ""}`}
           icon={<i className="iconfont icon-A" />}
@@ -68,7 +68,7 @@ export default function setting({
           onClick={() => {
             node.setData({ textDirection: "vertical" });
           }}
-        />
+        /> */}
       </div>
     </ConfigProvider>
   );

+ 58 - 8
apps/designer/src/models/appModel.ts

@@ -1,6 +1,8 @@
-import { Graph } from "@antv/x6";
-import { useState } from "react";
+import { Cell, EventArgs, Graph } from "@antv/x6";
+import { useEffect, useRef, useState } from "react";
 import { message } from "antd";
+import brushImg from "@/assets/image/brush.svg"
+import { cellStyle } from '@/types'
 
 interface PageSettings {
   // 背景颜色
@@ -34,11 +36,12 @@ export default function appModel() {
   // 格式刷启用
   const [enableFormatBrush, setEnableFormatBrush] = useState(false);
   // 格式刷样式
-  const [formatBrushStyle, setFormatBrusStyle] = useState();
+  const formatBrushStyle = useRef<cellStyle>();
   // 左侧面板激活
   const [leftPanelActiveKey, setLeftPanelActiveKey] = useState("1");
   // 右侧面板tab activeKey
   const [rightPanelTabActiveKey, setRightPanelTabActiveKey] = useState("1");
+  const graphRef = useRef<Graph>();
   const [pageState, setPageState] = useState<PageSettings>({
     backgroundColor: "transparent",
     width: 0,
@@ -72,7 +75,6 @@ export default function appModel() {
   }
   // 处理页面设置
   const onChangePageSettings = (key: keyof PageSettings, value: any) => {
-    console.log(key, value)
     setPageState((state) => {
       return {
         ...state,
@@ -95,21 +97,69 @@ export default function appModel() {
     })
   }
 
+  useEffect(() => {
+    const graphRoot = document.querySelector(
+      "#graph-container"
+    ) as HTMLDivElement;
+    const pageRoot = document.querySelector(
+      "#flow_canvas_container"
+    ) as HTMLDivElement;
+
+    if(enableFormatBrush) {
+      graphRoot && (graphRoot.style.cursor = `url(${brushImg}), auto`);
+      pageRoot && (pageRoot.style.cursor = `url(${brushImg}), auto`);
+    } else {
+      graphRoot && (graphRoot.style.cursor = "default");
+      pageRoot && (pageRoot.style.cursor = "default");
+    }
+  }, [enableFormatBrush])
+
+  const handleClick = (args: EventArgs & { cell: Cell }) => {
+    // 取消格式刷
+    if(!args?.cell || args?.cell?.data?.isPage) {
+      formatBrushStyle.current = undefined;
+      setEnableFormatBrush(false);
+      graphRef.current?.off("cell:click", handleClick);
+      graphRef.current?.off("blank:click", handleClick);
+    } else {
+      if(args.cell.data?.lock) return;
+      const data = args.cell.data;
+      args.cell.setData({
+        text: formatBrushStyle.current?.text || data?.text,
+        fill: formatBrushStyle.current?.fill || data?.fill,
+        stroke: formatBrushStyle.current?.stroke || data?.stroke,
+        opacity: formatBrushStyle.current?.opacity || data?.opacity
+      })
+      console.log(args.cell.data, formatBrushStyle.current)
+    }
+  };
+
   // 开启格式刷
   const toggleFormatBrush = (graph: Graph) => {
+    graphRef.current = graph;
     const cell = graph?.getSelectedCells()?.find(item => item.isNode());
     setEnableFormatBrush((state) => {
-      if(state) {
+      if(!state) {
         const data = cell?.getData();
-        setFormatBrusStyle(data);
+        formatBrushStyle.current = data;
         message.info('格式刷已开启');
+        graph.on("cell:click", handleClick);
+        graph.on("blank:click", handleClick);
       } else {
-        setFormatBrusStyle(undefined);
+        formatBrushStyle.current = undefined;
+        graph.off("cell:click", handleClick);
+        graph.off("blank:click", handleClick);
       }
       return !state;
-    })
+    });
   }
 
+  document.addEventListener("keydown", (e) => {
+    if (e.key === "Escape") {
+      setEnableFormatBrush(false);
+    }
+  });
+
   return {
     showRightPanel,
     toggleRightPanel,

+ 4 - 4
apps/designer/src/models/graphModel.ts

@@ -65,8 +65,8 @@ export default function GraphModel() {
   }, [pageState]);
 
   const enabledTransform = (node: Node) => {
-    const data = node.getData<{ isPage: boolean }>();
-    return !data?.isPage;
+    const data = node.getData<{ isPage: boolean, lock: boolean }>();
+    return !data?.isPage && !data?.lock;
   }
   /**初始化画布 */
   const initGraph = (instance: Graph) => {
@@ -96,8 +96,8 @@ export default function GraphModel() {
         pointerEvents: 'none',
         strict: true,
         filter: (cell: Cell) => {
-          const data = cell.getData<{ isPage: boolean }>();
-          return !data?.isPage;
+          const data = cell.getData<{ isPage: boolean, lock: boolean }>();
+          return !data?.isPage && !data?.lock;
         },
       }),
     )

+ 2 - 26
apps/designer/src/pages/flow/components/Config/GraphStyle.tsx

@@ -31,6 +31,7 @@ import { set, cloneDeep } from "lodash-es";
 import { Cell } from "@antv/x6";
 import { fontFamilyOptions, alignOptionData } from '@/pages/flow/data';
 import { alignCell, matchSize } from '@/utils';
+import { cellStyle } from '@/types'
 
 type FormModel = {
   opacity: number;
@@ -39,35 +40,10 @@ type FormModel = {
   x: number;
   y: number;
   rotation: number;
-  text: {
-    fontFamily: string;
-    color: string;
-    fontSize: number;
-    lineHeight: number;
-    textAlign: "left" | "center" | "right";
-    textVAlign: "top" | "middle" | "bottom";
-    bold: boolean;
-    italic: boolean;
-    textDecoration: "underline" | "line-through" | "none";
-  };
   connectorType: ConnectorType;
   startArrow: string;
   endArrow: string;
-  fill: {
-    fillType: "color" | "gradient" | "image";
-    color1: string;
-    color2: string;
-    gradientType: "linear" | "radial";
-    gradientValue: number;
-    objectFit: ImageFillType;
-    imageUrl: string;
-  };
-  stroke: {
-    type: "solid" | "dashed" | "dotted" | "dashdot";
-    color: string;
-    width: number;
-  };
-};
+} & cellStyle;
 export default function GraphStyle() {
   const { selectedCell } = useModel("graphModel");
   const [isMulit, setIsMulit] = useState(false);

+ 2 - 2
apps/designer/src/pages/flow/components/Content/index.tsx

@@ -65,8 +65,8 @@ export default function Content() {
         edgeLabelMovable: false,
         edgeMovable: true,
         nodeMovable: (view) => {
-          const data = view.cell.getData<{ ignoreDrag: boolean }>();
-          return !data || !data.ignoreDrag;
+          const data = view.cell.getData<{ ignoreDrag: boolean, lock: boolean }>();
+          return !data || (!data?.ignoreDrag && !data?.lock);
         },
         arrowheadMovable: true,
         vertexMovable: true,

+ 2 - 2
apps/designer/src/pages/flow/components/ToolBar/index.tsx

@@ -174,12 +174,12 @@ export default function ToolBar() {
             />
           </Tooltip>
 
-          <Tooltip placement="bottom" title="美化">
+          {/* <Tooltip placement="bottom" title="美化">
             <Button
               type="text"
               icon={<i className="iconfont icon-mofabang"></i>}
             />
-          </Tooltip>
+          </Tooltip> */}
 
           <Divider type="vertical" />
 

+ 29 - 0
apps/designer/src/types.d.ts

@@ -14,4 +14,33 @@ export interface LaneItem {
 export interface StageItem {
   height: number;
   name: string;
+}
+
+export interface cellStyle {
+  opacity: number;
+  text: {
+    fontFamily: string;
+    color: string;
+    fontSize: number;
+    lineHeight: number;
+    textAlign: "left" | "center" | "right";
+    textVAlign: "top" | "middle" | "bottom";
+    bold: boolean;
+    italic: boolean;
+    textDecoration: "underline" | "line-through" | "none";
+  };
+  fill: {
+    fillType: "color" | "gradient" | "image";
+    color1: string;
+    color2: string;
+    gradientType: "linear" | "radial";
+    gradientValue: number;
+    objectFit: ImageFillType;
+    imageUrl: string;
+  };
+  stroke: {
+    type: "solid" | "dashed" | "dotted" | "dashdot";
+    color: string;
+    width: number;
+  };
 }

+ 7 - 0
apps/designer/src/utils/contentMenu.tsx

@@ -157,6 +157,13 @@ const commonMenuData: MenuItem[] = [
     icon: "icon-lock",
     handler: menuHander.lock,
   },
+  {
+    key: "unlock",
+    label: "解锁",
+    fastKey: "Ctrl+Shift+L",
+    icon: "icon-unlock",
+    handler: menuHander.unlock,
+  },
   { type: "divider" },
   {
     key: "selectAll",

+ 2 - 0
apps/designer/src/utils/fastKey.tsx

@@ -144,11 +144,13 @@ export const bindKeys = (graph: Graph) => {
 
   // Ctrl+l 锁定
   graph.bindKey("ctrl+l", (e: KeyboardEvent) => {
+    e.preventDefault();
     handleLock(graph);
   });
 
   // Ctrl+shift+l 解锁
   graph.bindKey("ctrl+shift+l", (e: KeyboardEvent) => {
+    e.preventDefault();
     handleUnLock(graph);
   });
 

+ 22 - 9
apps/designer/src/utils/hander.tsx

@@ -1,7 +1,7 @@
 import { ContextMenuTool, edgeMenu } from "./contentMenu";
 import Text from "@/components/basic/text";
 import baseNode from "@/components/Base";
-import { Cell, Edge, Graph, Node } from "@antv/x6";
+import { Cell, CellView, Edge, Graph, Node } from "@antv/x6";
 import { nodeMenu } from "./contentMenu";
 import { exportImage } from "@/components/ExportImage";
 import { BaseEdge } from "@/components/Edge";
@@ -97,9 +97,8 @@ export const menuHander = {
     handleLock(tool.graph);
   },
   // 解锁
-  unlock(tool: ContextMenuTool, e: MouseEvent) {
-    tool.graph.select(tool.cell);
-    handleLock(tool.graph);
+  unlock(tool: ContextMenuTool) {
+    handleUnLock(tool.graph, tool.cell);
   },
   // 导出图形
   exportImage(tool: ContextMenuTool, e: MouseEvent) {
@@ -784,19 +783,34 @@ export const handleLock = (graph: Graph) => {
       lock: true,
     });
   });
+  graph.cleanSelection();
+  graph.clearTransformWidgets();
 };
 
 /**
  * 解锁
  * @param graph
+ * @param cell
  */
-export const handleUnLock = (graph: Graph) => {
-  const cells = graph.getSelectedCells();
-  cells?.forEach((cell) => {
+export const handleUnLock = (graph: Graph, cell?: Cell) => {
+  if(cell) {
     cell.setData({
       lock: false,
     });
-  });
+    graph.select([cell]);
+    if(cell.isNode()) {
+      graph.createTransformWidget(cell)
+    }
+  } else {
+    const cells = graph.getCells();
+    cells.forEach((cell) => {
+      if(cell.data?.lock) {
+        cell.setData({
+          lock: false,
+        });
+      }
+    });
+  }
 };
 
 /**
@@ -814,7 +828,6 @@ export const handleDelete = (graph: Graph) => {
  * @param style 
  */
 export const handleSetEdgeStyle = (edge: Cell, style: ConnectorType, jumpover: boolean) => {
-  console.log(style)
   if(!edge.isEdge()) return;
   switch(style) {
     case ConnectorType.Rounded: {