diff --git a/package.json b/package.json
index d97c6b499b..b87c3e5bd7 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,7 @@
"@pureadmin/descriptions": "^1.2.1",
"@pureadmin/table": "^3.1.2",
"@pureadmin/utils": "^2.4.7",
+ "@vue-flow/core": "^1.33.4",
"@vueuse/core": "^10.9.0",
"@vueuse/motion": "^2.1.0",
"@wangeditor/editor": "^5.1.23",
@@ -114,6 +115,7 @@
"@iconify/vue": "^4.1.1",
"@intlify/unplugin-vue-i18n": "^2.0.0",
"@pureadmin/theme": "^3.2.0",
+ "@types/dagre": "^0.7.52",
"@types/gradient-string": "^1.1.5",
"@types/intro.js": "^5.1.5",
"@types/js-cookie": "^3.0.6",
@@ -130,6 +132,7 @@
"boxen": "^7.1.1",
"cloc": "^2.11.0",
"cssnano": "^6.1.0",
+ "dagre": "^0.8.5",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cc7ee20d1f..fcdd2e532b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,6 +26,9 @@ dependencies:
'@pureadmin/utils':
specifier: ^2.4.7
version: 2.4.7(echarts@5.5.0)(vue@3.4.21)
+ '@vue-flow/core':
+ specifier: ^1.33.4
+ version: 1.33.4(vue@3.4.21)
'@vueuse/core':
specifier: ^10.9.0
version: 10.9.0(vue@3.4.21)
@@ -199,6 +202,9 @@ devDependencies:
'@pureadmin/theme':
specifier: ^3.2.0
version: 3.2.0
+ '@types/dagre':
+ specifier: ^0.7.52
+ version: 0.7.52
'@types/gradient-string':
specifier: ^1.1.5
version: 1.1.5
@@ -247,6 +253,9 @@ devDependencies:
cssnano:
specifier: ^6.1.0
version: 6.1.0(postcss@8.4.35)
+ dagre:
+ specifier: ^0.8.5
+ version: 0.8.5
eslint:
specifier: ^8.57.0
version: 8.57.0
@@ -2003,6 +2012,10 @@ packages:
'@types/node': 20.11.27
dev: true
+ /@types/dagre@0.7.52:
+ resolution: {integrity: sha512-XKJdy+OClLk3hketHi9Qg6gTfe1F3y+UFnHxKA2rn9Dw+oXa4Gb378Ztz9HlMgZKSxpPmn4BNVh9wgkpvrK1uw==}
+ dev: true
+
/@types/estree@1.0.5:
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
@@ -2352,6 +2365,20 @@ packages:
path-browserify: 1.0.1
dev: true
+ /@vue-flow/core@1.33.4(vue@3.4.21):
+ resolution: {integrity: sha512-ryoamKfQ5pgtdv//Gjpyc4nsawMOwfI2jVzOPvZ92VQs78L4lidiWD7UybqeEkrGw6UPue1CGlzoy/4KlOWcSg==}
+ peerDependencies:
+ vue: ^3.3.0
+ dependencies:
+ '@vueuse/core': 10.9.0(vue@3.4.21)
+ d3-drag: 3.0.0
+ d3-selection: 3.0.0
+ d3-zoom: 3.0.0
+ vue: 3.4.21(typescript@5.4.2)
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ dev: false
+
/@vue/babel-helper-vue-transform-on@1.2.1:
resolution: {integrity: sha512-jtEXim+pfyHWwvheYwUwSXm43KwQo8nhOBDyjrUITV6X2tB7lJm6n/+4sqR8137UVZZul5hBzWHdZ2uStYpyRQ==}
dev: true
@@ -3851,6 +3878,71 @@ packages:
/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+ /d3-color@3.1.0:
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+ dev: false
+
+ /d3-dispatch@3.0.1:
+ resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+ engines: {node: '>=12'}
+ dev: false
+
+ /d3-drag@3.0.0:
+ resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+ engines: {node: '>=12'}
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-selection: 3.0.0
+ dev: false
+
+ /d3-ease@3.0.1:
+ resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+ engines: {node: '>=12'}
+ dev: false
+
+ /d3-interpolate@3.0.1:
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+ dependencies:
+ d3-color: 3.1.0
+ dev: false
+
+ /d3-selection@3.0.0:
+ resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+ engines: {node: '>=12'}
+ dev: false
+
+ /d3-timer@3.0.1:
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+ dev: false
+
+ /d3-transition@3.0.1(d3-selection@3.0.0):
+ resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ d3-selection: 2 - 3
+ dependencies:
+ d3-color: 3.1.0
+ d3-dispatch: 3.0.1
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-timer: 3.0.1
+ dev: false
+
+ /d3-zoom@3.0.0:
+ resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+ engines: {node: '>=12'}
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+ dev: false
+
/d@1.0.2:
resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
engines: {node: '>=0.12'}
@@ -3859,6 +3951,13 @@ packages:
type: 2.7.2
dev: false
+ /dagre@0.8.5:
+ resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==}
+ dependencies:
+ graphlib: 2.1.8
+ lodash: 4.17.21
+ dev: true
+
/danmu.js@1.1.13:
resolution: {integrity: sha512-knFd0/cB2HA4FFWiA7eB2suc5vCvoHdqio33FyyCSfP7C+1A+zQcTvnvwfxaZhrxsGj4qaQI2I8XiTqedRaVmg==}
dependencies:
@@ -4962,6 +5061,12 @@ packages:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
dev: true
+ /graphlib@2.1.8:
+ resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==}
+ dependencies:
+ lodash: 4.17.21
+ dev: true
+
/has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
diff --git a/src/router/enums.ts b/src/router/enums.ts
index 9670bffd8d..712277558a 100644
--- a/src/router/enums.ts
+++ b/src/router/enums.ts
@@ -1,29 +1,31 @@
// 完整版菜单比较多,将 rank 抽离出来,在此方便维护
const home = 0, // 平台规定只有 home 路由的 rank 才能为 0 ,所以后端在返回 rank 的时候需要从非 0 开始
- components = 1,
- able = 2,
- table = 3,
- list = 4,
- result = 5,
- error = 6,
- frame = 7,
- nested = 8,
- permission = 9,
- system = 10,
- monitor = 11,
- tabs = 12,
- about = 13,
- editor = 14,
- flowchart = 15,
- formdesign = 16,
- board = 17,
- ppt = 18,
- guide = 19,
- menuoverflow = 20;
+ vueflow = 1,
+ components = 2,
+ able = 3,
+ table = 4,
+ list = 5,
+ result = 6,
+ error = 7,
+ frame = 8,
+ nested = 9,
+ permission = 10,
+ system = 11,
+ monitor = 12,
+ tabs = 13,
+ about = 14,
+ editor = 15,
+ flowchart = 16,
+ formdesign = 17,
+ board = 18,
+ ppt = 19,
+ guide = 20,
+ menuoverflow = 21;
export {
home,
+ vueflow,
components,
able,
table,
diff --git a/src/router/modules/vueflow.ts b/src/router/modules/vueflow.ts
new file mode 100644
index 0000000000..02f3892fd1
--- /dev/null
+++ b/src/router/modules/vueflow.ts
@@ -0,0 +1,21 @@
+import { vueflow } from "@/router/enums";
+
+export default {
+ path: "/vue-flow",
+ redirect: "/vue-flow/index",
+ meta: {
+ icon: "ep:set-up",
+ title: "vue-flow",
+ rank: vueflow
+ },
+ children: [
+ {
+ path: "/vue-flow/index",
+ name: "VueFlow",
+ component: () => import("@/views/vue-flow/layouting/index.vue"),
+ meta: {
+ title: "vue-flow"
+ }
+ }
+ ]
+} satisfies RouteConfigsTable;
diff --git a/src/views/vue-flow/layouting/animationEdge.vue b/src/views/vue-flow/layouting/animationEdge.vue
new file mode 100644
index 0000000000..64f8fbf4f3
--- /dev/null
+++ b/src/views/vue-flow/layouting/animationEdge.vue
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+ 📦
+
+
+
diff --git a/src/views/vue-flow/layouting/index.vue b/src/views/vue-flow/layouting/index.vue
new file mode 100644
index 0000000000..1cd416cba8
--- /dev/null
+++ b/src/views/vue-flow/layouting/index.vue
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/vue-flow/layouting/initialElements.ts b/src/views/vue-flow/layouting/initialElements.ts
new file mode 100644
index 0000000000..2a84ea4b74
--- /dev/null
+++ b/src/views/vue-flow/layouting/initialElements.ts
@@ -0,0 +1,81 @@
+import type { Edge, Node } from "@vue-flow/core";
+
+const position = { x: 0, y: 0 };
+const type: string = "process";
+
+export const initialNodes: Node[] = [
+ {
+ id: "1",
+ position,
+ type
+ },
+ {
+ id: "2",
+ position,
+ type
+ },
+ {
+ id: "2a",
+ position,
+ type
+ },
+ {
+ id: "2b",
+ position,
+ type
+ },
+ {
+ id: "2c",
+ position,
+ type
+ },
+ {
+ id: "2d",
+ position,
+ type
+ },
+ {
+ id: "3",
+ position,
+ type
+ },
+ {
+ id: "4",
+ position,
+ type
+ },
+ {
+ id: "5",
+ position,
+ type
+ },
+ {
+ id: "6",
+ position,
+ type
+ },
+ {
+ id: "7",
+ position,
+ type
+ }
+];
+
+export const initialEdges: Edge[] = [
+ { id: "e1-2", source: "1", target: "2", type: "animation", animated: true },
+ { id: "e1-3", source: "1", target: "3", type: "animation", animated: true },
+ { id: "e2-2a", source: "2", target: "2a", type: "animation", animated: true },
+ { id: "e2-2b", source: "2", target: "2b", type: "animation", animated: true },
+ { id: "e2-2c", source: "2", target: "2c", type: "animation", animated: true },
+ {
+ id: "e2c-2d",
+ source: "2c",
+ target: "2d",
+ type: "animation",
+ animated: true
+ },
+ { id: "e3-7", source: "3", target: "4", type: "animation", animated: true },
+ { id: "e4-5", source: "4", target: "5", type: "animation", animated: true },
+ { id: "e5-6", source: "5", target: "6", type: "animation", animated: true },
+ { id: "e5-7", source: "5", target: "7", type: "animation", animated: true }
+];
diff --git a/src/views/vue-flow/layouting/processNode.vue b/src/views/vue-flow/layouting/processNode.vue
new file mode 100644
index 0000000000..0ff966ab57
--- /dev/null
+++ b/src/views/vue-flow/layouting/processNode.vue
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+ {{ processLabel }}
+
+
+
+
+
diff --git a/src/views/vue-flow/layouting/useRunProcess.ts b/src/views/vue-flow/layouting/useRunProcess.ts
new file mode 100644
index 0000000000..a87a923225
--- /dev/null
+++ b/src/views/vue-flow/layouting/useRunProcess.ts
@@ -0,0 +1,134 @@
+import type dagre from "dagre";
+import type { Node } from "@vue-flow/core";
+import { useVueFlow } from "@vue-flow/core";
+import { type MaybeRefOrGetter, toRef, toValue, ref } from "vue";
+
+export function useRunProcess(
+ dagreGraph: MaybeRefOrGetter
+) {
+ const { updateNodeData } = useVueFlow();
+
+ const graph = toRef(() => toValue(dagreGraph));
+
+ const isRunning = ref(false);
+ const executedNodes = new Set();
+ const runningTasks = new Map();
+
+ async function runNode(node: { id: string }, isStart = false) {
+ if (executedNodes.has(node.id)) {
+ return;
+ }
+
+ executedNodes.add(node.id);
+
+ updateNodeData(node.id, {
+ isRunning: true,
+ isFinished: false,
+ hasError: false,
+ isCancelled: false
+ });
+
+ const delay = Math.floor(Math.random() * 2000) + 1000;
+ return new Promise(resolve => {
+ const timeout = setTimeout(
+ async () => {
+ const children = graph.value.successors(
+ node.id
+ ) as unknown as string[];
+
+ const willThrowError = Math.random() < 0.15;
+
+ if (willThrowError) {
+ updateNodeData(node.id, { isRunning: false, hasError: true });
+
+ await skipDescendants(node.id);
+ runningTasks.delete(node.id);
+
+ resolve();
+ return;
+ }
+
+ updateNodeData(node.id, { isRunning: false, isFinished: true });
+ runningTasks.delete(node.id);
+
+ if (children.length > 0) {
+ await Promise.all(children.map(id => runNode({ id })));
+ }
+
+ resolve();
+ },
+ isStart ? 0 : delay
+ );
+
+ runningTasks.set(node.id, timeout);
+ });
+ }
+
+ async function run(nodes: Node[]) {
+ if (isRunning.value) {
+ return;
+ }
+
+ reset(nodes);
+
+ isRunning.value = true;
+
+ const startingNodes = nodes.filter(
+ node => graph.value.predecessors(node.id)?.length === 0
+ );
+
+ await Promise.all(startingNodes.map(node => runNode(node, true)));
+
+ clear();
+ }
+
+ function reset(nodes: Node[]) {
+ clear();
+
+ for (const node of nodes) {
+ updateNodeData(node.id, {
+ isRunning: false,
+ isFinished: false,
+ hasError: false,
+ isSkipped: false,
+ isCancelled: false
+ });
+ }
+ }
+
+ async function skipDescendants(nodeId: string) {
+ const children = graph.value.successors(nodeId) as unknown as string[];
+
+ for (const child of children) {
+ updateNodeData(child, { isRunning: false, isSkipped: true });
+ await skipDescendants(child);
+ }
+ }
+
+ function stop() {
+ isRunning.value = false;
+
+ for (const [nodeId, task] of runningTasks) {
+ clearTimeout(task);
+ runningTasks.delete(nodeId);
+ updateNodeData(nodeId, {
+ isRunning: false,
+ isFinished: false,
+ hasError: false,
+ isSkipped: false,
+ isCancelled: true
+ });
+ skipDescendants(nodeId);
+ }
+
+ executedNodes.clear();
+ }
+
+ function clear() {
+ isRunning.value = false;
+ executedNodes.clear();
+ runningTasks.clear();
+ }
+
+ return { run, stop, reset, isRunning };
+}