소스 검색

缩放与撤回

CzRger 3 달 전
부모
커밋
4e5ed697c1
9개의 변경된 파일197개의 추가작업 그리고 26개의 파일을 삭제
  1. 2 0
      package.json
  2. 1 0
      src/assets/svg/back.svg
  3. 1 0
      src/assets/svg/history.svg
  4. 1 0
      src/assets/svg/zoom+.svg
  5. 1 0
      src/assets/svg/zoom-.svg
  6. 7 0
      src/style/czr.scss
  7. 167 19
      src/views/workflow/chart/index.vue
  8. 7 7
      src/views/workflow/handle.ts
  9. 10 0
      yarn.lock

+ 2 - 0
package.json

@@ -11,6 +11,8 @@
   },
   "dependencies": {
     "@antv/x6": "^2.18.1",
+    "@antv/x6-plugin-history": "^2.2.4",
+    "@antv/x6-plugin-minimap": "^2.0.7",
     "@antv/x6-plugin-snapline": "^2.1.7",
     "@antv/x6-vue-shape": "^2.1.2",
     "@tailwindcss/vite": "^4.1.6",

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 0
src/assets/svg/back.svg


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 0
src/assets/svg/history.svg


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 0
src/assets/svg/zoom+.svg


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 0
src/assets/svg/zoom-.svg


+ 7 - 0
src/style/czr.scss

@@ -3,6 +3,13 @@
   --czr-main-color-rgb: 21, 90, 239;
 }
 
+.__disabled {
+  opacity: 0.3 !important;
+  &:hover {
+    cursor: not-allowed !important;
+  }
+}
+
 .__hover {
   &:hover {
     opacity: 0.75;

+ 167 - 19
src/views/workflow/chart/index.vue

@@ -3,6 +3,62 @@
     <div class="chart-block">
       <div class="chart-ref" ref="ref_chart"></div>
     </div>
+    <div class="tools">
+      <div class="mini-map" ref="ref_miniMap"/>
+      <div class="operations">
+        <div class="zoom">
+          <el-tooltip content="缩小" effect="light" placement="top" :show-arrow="false">
+            <div class="__hover-bg" :class="{__disabled: state.zoom <= 0.25}" @click="graphZoom(state.zoom - 0.1)">
+              <SvgIcon name="zoom-"/>
+            </div>
+          </el-tooltip>
+          <el-dropdown :teleported="false" :popper-options="{
+            modifiers: [
+              {
+                name: 'offset',
+                options: {
+                  offset: [0, 20],
+                },
+              },
+              {
+                name: 'arrow',
+              },
+            ],
+          }">
+            <div class="__hover-bg px-0.5">{{(state.zoom * 100).toFixed(0)}}%</div>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item @click="graphZoom(2)">200%</el-dropdown-item>
+                <el-dropdown-item @click="graphZoom(1)">100%</el-dropdown-item>
+                <el-dropdown-item @click="graphZoom(0.75)">75%</el-dropdown-item>
+                <el-dropdown-item @click="graphZoom(0.5)">50%</el-dropdown-item>
+                <el-dropdown-item divided @click="graphZoom(0)">自适应视图</el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+          <el-tooltip content="放大" effect="light" placement="top" :show-arrow="false">
+            <div class="__hover-bg" :class="{__disabled: state.zoom >= 2}" @click="graphZoom(state.zoom + 0.1)">
+              <SvgIcon name="zoom+"/>
+            </div>
+          </el-tooltip>
+        </div>
+        <div class="history">
+          <el-tooltip content="撤销" effect="light" placement="top" :show-arrow="false">
+            <div class="__hover-bg" :class="{__disabled: !state.history.canUndo}" @click="onUndo">
+              <SvgIcon name="back"/>
+            </div>
+          </el-tooltip>
+          <el-tooltip content="重做" effect="light" placement="top" :show-arrow="false">
+            <div class="__hover-bg" :class="{__disabled: !state.history.canRedo}" @click="onRedo">
+              <SvgIcon name="back" rotate="180"/>
+            </div>
+          </el-tooltip>
+<!--          <div class="__hover-bg">-->
+<!--            <SvgIcon name="history"/>-->
+<!--          </div>-->
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -18,6 +74,8 @@ import {Snapline} from '@antv/x6-plugin-snapline'
 import {ContextMenuTool} from "./context-menu-tool";
 import {useWorkflowStore} from "@/stores/modules/workflow";
 import {NodeType} from "@/views/workflow/types";
+import { MiniMap } from '@antv/x6-plugin-minimap'
+import { History } from '@antv/x6-plugin-history'
 
 register({
   shape: 'workflow-node',
@@ -100,15 +158,28 @@ const {proxy}: any = getCurrentInstance()
 const state: any = reactive({
   graph: null,
   isPort: false,
-  isInitEdges: false
+  isInitEdges: false,
+  zoom: 1,
+  history: {
+    isInit: false,
+    canUndo: false,
+    canRedo: false,
+  }
 })
 const ref_chart = ref()
+const ref_miniMap = ref()
 const initChart = () => {
   state.graph = new Graph({
     container: ref_chart.value,
     autoResize: true,
     panning: true,
-    mousewheel: true,
+    mousewheel: {
+      enabled: true,
+    },
+    scaling: {
+      min: 0.25,
+      max: 2
+    },
     grid: {
       visible: true,
       type: 'doubleMesh',
@@ -137,13 +208,13 @@ const initChart = () => {
       },
       allowMulti: 'withPort', //  当设置为 'withPort' 时,在起始和终止节点的相同连接桩之间只允许创建一条边(即,起始和终止节点之间可以创建多条边,但必须要要链接在不同的连接桩上)
       highlight: true,
-      router: {
-        name: 'manhattan',
-        args: {
-          startDirections: ['right'],
-          endDirections: ['left'],
-        },
-      },
+      // router: {
+      //   name: 'manhattan',
+      //   args: {
+      //     startDirections: ['right'],
+      //     endDirections: ['left'],
+      //   },
+      // },
       connector: 'algo-connector',
       // connector: {
       //   name: 'rounded',
@@ -168,16 +239,10 @@ const initChart = () => {
     }
   })
   WorkflowStore.init(state.graph)
-  initNodes()
-  state.graph.zoomToFit({ maxScale: 1 })
-  state.graph.centerContent() // 居中显示
-  state.graph.use(
-    new Snapline({
-      enabled: true,
-      clean: false,
-    }),
-  )
+  initPlug()
   initWatch()
+  initNodes()
+  graphZoom(0)
 }
 const initNodes = () => {
   props.data.nodes.forEach(v => {
@@ -198,6 +263,7 @@ const initEdges = () => {
       state.graph.addEdge(handleEdge(v))
     })
     state.isInitEdges = true
+    state.graph.cleanHistory()  // 初始化完成后清空历史队列
   }
 }
 const initWatch = () => {
@@ -214,8 +280,54 @@ const initWatch = () => {
       state.isPort = false
     }, 100)
   })
+  state.graph.on('scale', ({ sx, sy, ox, oy }) => {
+    state.zoom = sx
+  })
+  state.graph.on('history:change', () => {
+    if (state.isInitEdges) {
+      state.history.canRedo = state.graph.canRedo()
+      state.history.canUndo = state.graph.canUndo()
+    }
+  })
+}
+const initPlug = () => {
+  state.graph.use(
+    new Snapline({
+      enabled: true,
+      clean: false,
+    }),
+  )
+  state.graph.use(
+    new MiniMap({
+      container: ref_miniMap.value,
+      width: 100,
+      height: 70,
+      padding: 6
+    }),
+  )
+  state.graph.use(
+    new History({
+      enabled: true,
+    }),
+  )
+}
+const graphZoom = (z) => {
+  if (z) {
+    state.graph.zoomTo(z)
+  } else {
+    state.graph.zoomToFit({ maxScale: 1 })
+  }
+}
+const onUndo = () => {
+  if (state.history.canUndo) {
+    state.graph.undo()
+  }
+}
+const onRedo = () => {
+  if (state.history.canRedo) {
+    state.graph.redo()
+  }
 }
-
 watch(() => props.data, (n) => {
   if (n) {
     initChart()
@@ -233,6 +345,7 @@ defineExpose({
   width: 100%;
   height: 100%;
   background-color: #f2f4f7;
+  position: relative;
   .chart-block {
     width: 100%;
     height: 100%;
@@ -241,5 +354,40 @@ defineExpose({
       height: 100%;
     }
   }
+  .tools {
+    position: absolute;
+    left: 20px;
+    bottom: 20px;
+    z-index: 2;
+    .mini-map {
+      position: absolute;
+      bottom: calc(100% + 10px);
+    }
+    :deep(.operations) {
+      display: flex;
+      align-items: center;
+      font-size: 14px;
+      color: #676f83;
+      gap: 10px;
+      .el-popper__arrow {
+        display: none;
+      }
+      >div {
+        display: flex;
+        align-items: center;
+        background-color: rgba(255, 255, 255, 0.95);
+        box-shadow: 0 0px 8px rgba(0, 0, 0, 0.1);
+        border-radius: 6px;
+        padding: 4px;
+        >div {
+          min-width: 32px;
+          height: 32px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+        }
+      }
+    }
+  }
 }
 </style>

+ 7 - 7
src/views/workflow/handle.ts

@@ -103,13 +103,13 @@ export const handleEdge = (ed) => {
     },
     shape: 'edge',
     attrs: lineStyle,
-    router: {
-      name: 'manhattan',
-      args: {
-        startDirections: ['right'],
-        endDirections: ['left'],
-      },
-    },
+    // router: {
+    //   name: 'manhattan',
+    //   args: {
+    //     startDirections: ['right'],
+    //     endDirections: ['left'],
+    //   },
+    // },
     tools: [
       {
         name: 'contextmenu',

+ 10 - 0
yarn.lock

@@ -23,6 +23,16 @@
   resolved "https://registry.npmmirror.com/@antv/x6-geometry/-/x6-geometry-2.0.5.tgz#c158317d74135bedd78c2fdeb76f9c7cfa0ef0aa"
   integrity sha512-MId6riEQkxphBpVeTcL4ZNXL4lScyvDEPLyIafvWMcWNTGK0jgkK7N20XSzqt8ltJb0mGUso5s56mrk8ysHu2A==
 
+"@antv/x6-plugin-history@^2.2.4":
+  version "2.2.4"
+  resolved "https://registry.npmmirror.com/@antv/x6-plugin-history/-/x6-plugin-history-2.2.4.tgz#c4a543b8a2b6ae8b6934ec69067688459cae6c48"
+  integrity sha512-9gHHvEW4Fla+1hxUV49zNgJyIMoV9CjVM52MrFgAJcvyRn1Kvxz4MfxiKlG+DEZUs+/zvfjl9pS6gJOd8laRkg==
+
+"@antv/x6-plugin-minimap@^2.0.7":
+  version "2.0.7"
+  resolved "https://registry.npmmirror.com/@antv/x6-plugin-minimap/-/x6-plugin-minimap-2.0.7.tgz#1f319ac04de7d05ca895ea6e2784489efe068791"
+  integrity sha512-8zzESCx0jguFPCOKCA0gPFb6JmRgq81CXXtgJYe34XhySdZ6PB23I5Po062lNsEuTjTjnzli4lju8vvU+jzlGw==
+
 "@antv/x6-plugin-snapline@^2.1.7":
   version "2.1.7"
   resolved "https://registry.npmmirror.com/@antv/x6-plugin-snapline/-/x6-plugin-snapline-2.1.7.tgz#1b4e3be0a281ba324117b7ac7b9b8c39e484cce2"