Bläddra i källkod

增加vueflow功能,可以改变方向。。

jiaxiaoqiang 5 månader sedan
förälder
incheckning
4dc55fbcf8

+ 1 - 0
package.json

@@ -40,6 +40,7 @@
     ]
   },
   "dependencies": {
+    "@dagrejs/dagre": "^1.1.4",
     "@element-plus/icons-vue": "^2.3.1",
     "@kjgl77/datav-vue3": "^1.7.4",
     "@smallwei/avue": "^3.6.0",

+ 16 - 0
pnpm-lock.yaml

@@ -8,6 +8,9 @@ importers:
 
   .:
     dependencies:
+      '@dagrejs/dagre':
+        specifier: ^1.1.4
+        version: 1.1.4
       '@element-plus/icons-vue':
         specifier: ^2.3.1
         version: 2.3.1(vue@3.5.13(typescript@5.5.4))
@@ -945,6 +948,13 @@ packages:
     resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
     engines: {node: '>=10'}
 
+  '@dagrejs/dagre@1.1.4':
+    resolution: {integrity: sha512-QUTc54Cg/wvmlEUxB+uvoPVKFazM1H18kVHBQNmK2NbrDR5ihOCR6CXLnDSZzMcSQKJtabPUWridBOlJM3WkDg==}
+
+  '@dagrejs/graphlib@2.2.4':
+    resolution: {integrity: sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw==}
+    engines: {node: '>17.0.0'}
+
   '@dual-bundle/import-meta-resolve@4.1.0':
     resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==}
 
@@ -6694,6 +6704,12 @@ snapshots:
 
   '@ctrl/tinycolor@3.6.1': {}
 
+  '@dagrejs/dagre@1.1.4':
+    dependencies:
+      '@dagrejs/graphlib': 2.2.4
+
+  '@dagrejs/graphlib@2.2.4': {}
+
   '@dual-bundle/import-meta-resolve@4.1.0': {}
 
   '@element-plus/icons-vue@2.3.1(vue@3.5.13(typescript@5.5.4))':

+ 59 - 0
src/hooks/useLayout.js

@@ -0,0 +1,59 @@
+import dagre from "@dagrejs/dagre";
+import { Position, useVueFlow } from "@vue-flow/core";
+import { ref } from "vue";
+
+/**
+ * Composable to run the layout algorithm on the graph.
+ * It uses the `dagre` library to calculate the layout of the nodes and edges.
+ */
+export function useLayout() {
+  const { findNode } = useVueFlow();
+
+  const graph = ref(new dagre.graphlib.Graph());
+
+  const previousDirection = ref("LR");
+
+  function layout(nodes, edges, direction) {
+    // we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there
+    const dagreGraph = new dagre.graphlib.Graph();
+
+    graph.value = dagreGraph;
+
+    dagreGraph.setDefaultEdgeLabel(() => ({}));
+
+    const isHorizontal = direction === "LR";
+    dagreGraph.setGraph({ rankdir: direction });
+
+    previousDirection.value = direction;
+
+    for (const node of nodes) {
+      // if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type)
+      const graphNode = findNode(node.id);
+
+      dagreGraph.setNode(node.id, {
+        width: graphNode.dimensions.width || 150,
+        height: graphNode.dimensions.height || 50,
+      });
+    }
+
+    for (const edge of edges) {
+      dagreGraph.setEdge(edge.source, edge.target);
+    }
+
+    dagre.layout(dagreGraph);
+
+    // set nodes with updated positions
+    return nodes.map((node) => {
+      const nodeWithPosition = dagreGraph.node(node.id);
+
+      return {
+        ...node,
+        targetPosition: isHorizontal ? Position.Left : Position.Top,
+        sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
+        position: { x: nodeWithPosition.x, y: nodeWithPosition.y },
+      };
+    });
+  }
+
+  return { graph, layout, previousDirection };
+}

+ 120 - 3
src/views/base/craftManagement/route/bindProcess.vue

@@ -88,6 +88,21 @@
             >
               <p v-if="isDragOver">拖拽中</p>
             </DropzoneBackground>
+
+            <Panel class="process-panel" position="top-right">
+              <div class="layout-panel">
+                <button
+                  title="set horizontal layout"
+                  @click="layoutGraph('LR')"
+                >
+                  <PannelIcon name="horizontal" />
+                </button>
+
+                <button title="set vertical layout" @click="layoutGraph('TB')">
+                  <PannelIcon name="vertical" />
+                </button>
+              </div>
+            </Panel>
           </VueFlow>
 
           <div
@@ -204,7 +219,7 @@ import {
   Grid,
 } from "@element-plus/icons-vue";
 import _ from "lodash-es";
-import { VueFlow, useVueFlow } from "@vue-flow/core";
+import { VueFlow, useVueFlow, Panel } from "@vue-flow/core";
 import DropzoneBackground from "./components/DropzoneBackground/index.vue";
 import CustomConnectionLine from "./components/CustomConnectionLine/index.vue";
 import CustomNode from "./components/CustomNode/index.vue";
@@ -220,14 +235,22 @@ import {
 import { formOption } from "./bindConfig";
 import { ElMessage } from "element-plus";
 import { useScreenshot } from "./screenshot.ts";
+import { useLayout } from "@/hooks/useLayout";
 defineOptions({
   name: "bindProcess/:id/:prodtCode",
 });
 
 const { capture } = useScreenshot();
+const { layout } = useLayout();
 const instance = useVueFlow();
-const { onConnect, addEdges, vueFlowRef, onEdgeUpdateEnd, applyEdgeChanges } =
-  instance;
+const {
+  onConnect,
+  addEdges,
+  vueFlowRef,
+  onEdgeUpdateEnd,
+  applyEdgeChanges,
+  fitView,
+} = instance;
 const { onDragOver, onDrop, onDragLeave, isDragOver, onDragStart } =
   useDragAndDrop();
 const flowData = ref({ edges: [], nodes: [] });
@@ -524,6 +547,20 @@ watch(
     loadTreeData();
   }
 );
+
+async function layoutGraph(direction) {
+  flowData.value.nodes.value = layout(
+    flowData.value.nodes,
+    flowData.value.edges,
+    direction
+  );
+
+  console.log(flowData.value.nodes);
+
+  nextTick(() => {
+    fitView();
+  });
+}
 </script>
 
 <style lang="scss" scoped>
@@ -623,6 +660,86 @@ watch(
   margin-top: 20px;
   color: #e6a23c;
 }
+
+.process-panel,
+.layout-panel {
+  display: flex;
+  gap: 10px;
+}
+
+.process-panel {
+  background-color: #2d3748;
+  padding: 10px;
+  border-radius: 8px;
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
+  display: flex;
+  flex-direction: column;
+}
+
+.process-panel button {
+  border: none;
+  cursor: pointer;
+  background-color: #4a5568;
+  border-radius: 8px;
+  color: white;
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
+}
+
+.process-panel button {
+  font-size: 16px;
+  width: 40px;
+  height: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.checkbox-panel {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.process-panel button:hover,
+.layout-panel button:hover {
+  background-color: #2563eb;
+  transition: background-color 0.2s;
+}
+
+.process-panel label {
+  color: white;
+  font-size: 12px;
+}
+
+.stop-btn svg {
+  display: none;
+}
+
+.stop-btn:hover svg {
+  display: block;
+}
+
+.stop-btn:hover .spinner {
+  display: none;
+}
+
+.spinner {
+  border: 3px solid #f3f3f3;
+  border-top: 3px solid #2563eb;
+  border-radius: 50%;
+  width: 10px;
+  height: 10px;
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
+  }
+}
 </style>
 
 <style>

+ 80 - 0
src/views/base/craftManagement/route/components/PannelIcon.vue

@@ -0,0 +1,80 @@
+<script setup>
+defineProps({
+  name: {
+    type: String,
+    required: true,
+  },
+});
+</script>
+
+<template>
+  <svg
+    v-if="name === 'play'"
+    viewBox="0 0 24 24"
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <path d="M8 5v14l11-7z" fill="currentColor" />
+  </svg>
+
+  <svg
+    v-else-if="name === 'stop'"
+    xmlns="http://www.w3.org/2000/svg"
+    viewBox="0 0 24 24"
+  >
+    <path
+      fill="currentColor"
+      d="M8 16h8V8H8zm4 6q-2.075 0-3.9-.788t-3.175-2.137q-1.35-1.35-2.137-3.175T2 12q0-2.075.788-3.9t2.137-3.175q1.35-1.35 3.175-2.137T12 2q2.075 0 3.9.788t3.175 2.137q1.35 1.35 2.138 3.175T22 12q0 2.075-.788 3.9t-2.137 3.175q-1.35 1.35-3.175 2.138T12 22"
+    />
+  </svg>
+
+  <svg
+    v-else-if="name === 'horizontal'"
+    viewBox="0 0 24 24"
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <path d="M2,12 L22,12" stroke="currentColor" stroke-width="2" />
+    <path
+      d="M7,7 L2,12 L7,17"
+      stroke="currentColor"
+      stroke-width="2"
+      fill="none"
+    />
+    <path
+      d="M17,7 L22,12 L17,17"
+      stroke="currentColor"
+      stroke-width="2"
+      fill="none"
+    />
+  </svg>
+
+  <svg
+    v-else-if="name === 'vertical'"
+    viewBox="0 0 24 24"
+    xmlns="http://www.w3.org/2000/svg"
+  >
+    <path d="M12,2 L12,22" stroke="currentColor" stroke-width="2" />
+    <path
+      d="M7,7 L12,2 L17,7"
+      stroke="currentColor"
+      stroke-width="2"
+      fill="none"
+    />
+    <path
+      d="M7,17 L12,22 L17,17"
+      stroke="currentColor"
+      stroke-width="2"
+      fill="none"
+    />
+  </svg>
+
+  <svg
+    v-else-if="name === 'shuffle'"
+    xmlns="http://www.w3.org/2000/svg"
+    viewBox="0 0 24 24"
+  >
+    <path
+      fill="currentColor"
+      d="M14 20v-2h2.6l-3.175-3.175L14.85 13.4L18 16.55V14h2v6zm-8.6 0L4 18.6L16.6 6H14V4h6v6h-2V7.4zm3.775-9.425L4 5.4L5.4 4l5.175 5.175z"
+    />
+  </svg>
+</template>