重构工作流绘制

This commit is contained in:
2026-05-28 17:55:49 +08:00
parent 9bd4a44ab6
commit 032f258912
9 changed files with 2202 additions and 25 deletions

262
package-lock.json generated
View File

@@ -14,6 +14,10 @@
"@element-plus/icons-vue": "^2.3.1",
"@logicflow/core": "^2.2.1",
"@logicflow/extension": "^2.2.1",
"@vue-flow/background": "^1.3.2",
"@vue-flow/controls": "^1.1.3",
"@vue-flow/core": "^1.48.2",
"@vue-flow/minimap": "^1.5.4",
"@wangeditor/editor": "^5.1.23",
"axios": "1.8.2",
"codemirror": "^6.0.1",
@@ -923,6 +927,7 @@
"resolved": "https://registry.npmjs.org/@interactjs/core/-/core-1.10.27.tgz",
"integrity": "sha512-SliUr/3ZbLAdED8LokzYzWHWMdCB5Cq+UnpXuRy+BIod1j97m4IUFf/D1iIKUBBjBcucgXbz28z96WnenVCB7Q==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"@interactjs/utils": "1.10.27"
}
@@ -993,6 +998,7 @@
"resolved": "https://registry.npmjs.org/@interactjs/modifiers/-/modifiers-1.10.27.tgz",
"integrity": "sha512-ei/qfoQ+9/8k6WzNzdNqHI6cWkIV576N4Ap16r5CoqOWwhA6Xzj3OMHf1g0t1O4eSq2HdJsVJn3eLNfw9HsbeQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@interactjs/snappers": "1.10.27"
},
@@ -1059,7 +1065,8 @@
"version": "1.10.27",
"resolved": "https://registry.npmjs.org/@interactjs/utils/-/utils-1.10.27.tgz",
"integrity": "sha512-+qfLOio2OxQqg1cXSnRaCl+N8MQDQLDS9w+aOGxH8YLAhIMyt7Asxx/46//sT8orgsi16pmlBPtngPHT9s8zKw==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@intlify/core-base": {
"version": "11.1.2",
@@ -1151,6 +1158,7 @@
"resolved": "https://registry.npmjs.org/@logicflow/core/-/core-2.2.1.tgz",
"integrity": "sha512-VzLPrCrT4eXnOLjoGQ5v4GUSay3+6rd3YNZD0qOJw4vME5e4WjQ5fd+hKK2zlIzgdRI4D54dXiEFJrS6xdV6yQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"classnames": "^2.3.2",
"lodash-es": "^4.17.21",
@@ -1904,6 +1912,7 @@
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/lodash": "*"
}
@@ -1914,6 +1923,7 @@
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -1985,6 +1995,7 @@
"integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "7.18.0",
"@typescript-eslint/types": "7.18.0",
@@ -2160,6 +2171,7 @@
"resolved": "https://registry.npmjs.org/@uppy/core/-/core-2.3.4.tgz",
"integrity": "sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@transloadit/prettier-bytes": "0.0.7",
"@uppy/store-default": "^2.1.1",
@@ -2191,6 +2203,7 @@
"resolved": "https://registry.npmjs.org/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz",
"integrity": "sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@uppy/companion-client": "^2.2.2",
"@uppy/utils": "^4.1.2",
@@ -2243,6 +2256,99 @@
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vue-flow/background": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@vue-flow/background/-/background-1.3.2.tgz",
"integrity": "sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w==",
"license": "MIT",
"peerDependencies": {
"@vue-flow/core": "^1.23.0",
"vue": "^3.3.0"
}
},
"node_modules/@vue-flow/controls": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@vue-flow/controls/-/controls-1.1.3.tgz",
"integrity": "sha512-XCf+G+jCvaWURdFlZmOjifZGw3XMhN5hHlfMGkWh9xot+9nH9gdTZtn+ldIJKtarg3B21iyHU8JjKDhYcB6JMw==",
"license": "MIT",
"peerDependencies": {
"@vue-flow/core": "^1.23.0",
"vue": "^3.3.0"
}
},
"node_modules/@vue-flow/core": {
"version": "1.48.2",
"resolved": "https://registry.npmjs.org/@vue-flow/core/-/core-1.48.2.tgz",
"integrity": "sha512-raxhgKWE+G/mcEvXJjGFUDYW9rAI3GOtiHR3ZkNpwBWuIaCC1EYiBmKGwJOoNzVFgwO7COgErnK7i08i287AFA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vueuse/core": "^10.5.0",
"d3-drag": "^3.0.0",
"d3-interpolate": "^3.0.1",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0"
},
"peerDependencies": {
"vue": "^3.3.0"
}
},
"node_modules/@vue-flow/core/node_modules/@types/web-bluetooth": {
"version": "0.0.20",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
"license": "MIT"
},
"node_modules/@vue-flow/core/node_modules/@vueuse/core": {
"version": "10.11.1",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz",
"integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==",
"license": "MIT",
"dependencies": {
"@types/web-bluetooth": "^0.0.20",
"@vueuse/metadata": "10.11.1",
"@vueuse/shared": "10.11.1",
"vue-demi": ">=0.14.8"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vue-flow/core/node_modules/@vueuse/metadata": {
"version": "10.11.1",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz",
"integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vue-flow/core/node_modules/@vueuse/shared": {
"version": "10.11.1",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz",
"integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
"license": "MIT",
"dependencies": {
"vue-demi": ">=0.14.8"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vue-flow/minimap": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/@vue-flow/minimap/-/minimap-1.5.4.tgz",
"integrity": "sha512-l4C+XTAXnRxsRpUdN7cAVFBennC1sVRzq4bDSpVK+ag7tdMczAnhFYGgbLkUw3v3sY6gokyWwMl8CDonp8eB2g==",
"license": "MIT",
"dependencies": {
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0"
},
"peerDependencies": {
"@vue-flow/core": "^1.23.0",
"vue": "^3.3.0"
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.25",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.25.tgz",
@@ -2419,6 +2525,7 @@
"resolved": "https://registry.npmjs.org/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz",
"integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==",
"license": "MIT",
"peer": true,
"dependencies": {
"is-url": "^1.2.4"
},
@@ -2451,6 +2558,7 @@
"resolved": "https://registry.npmjs.org/@wangeditor/core/-/core-1.1.19.tgz",
"integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/event-emitter": "^0.3.3",
"event-emitter": "^0.3.5",
@@ -2569,6 +2677,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2824,6 +2933,7 @@
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
@@ -2950,6 +3060,112 @@
"node": ">=0.12"
}
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dispatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-drag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-selection": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-transition": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3",
"d3-dispatch": "1 - 3",
"d3-ease": "1 - 3",
"d3-interpolate": "1 - 3",
"d3-timer": "1 - 3"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"d3-selection": "2 - 3"
}
},
"node_modules/d3-zoom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-drag": "2 - 3",
"d3-interpolate": "1 - 3",
"d3-selection": "2 - 3",
"d3-transition": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/dayjs": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
@@ -3041,6 +3257,7 @@
"resolved": "https://registry.npmjs.org/dom7/-/dom7-3.0.0.tgz",
"integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==",
"license": "MIT",
"peer": true,
"dependencies": {
"ssr-window": "^3.0.0-alpha.1"
}
@@ -3077,6 +3294,7 @@
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.6.1"
@@ -3305,6 +3523,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -4097,7 +4316,8 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz",
"integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/is-number": {
"version": "7.0.0",
@@ -4284,13 +4504,15 @@
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/lodash-unified": {
"version": "1.0.3",
@@ -4307,32 +4529,37 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/lodash.foreach": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
"integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/lodash.merge": {
"version": "4.6.2",
@@ -4345,13 +4572,15 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/lodash.toarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
"integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/magic-string": {
"version": "0.30.21",
@@ -4464,6 +4693,7 @@
"resolved": "https://registry.npmjs.org/mobx/-/mobx-5.15.7.tgz",
"integrity": "sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==",
"license": "MIT",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mobx"
@@ -4528,6 +4758,7 @@
}
],
"license": "MIT",
"peer": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -4814,6 +5045,7 @@
"resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz",
"integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==",
"license": "MIT",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
@@ -5074,6 +5306,7 @@
"integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
@@ -5292,6 +5525,7 @@
"resolved": "https://registry.npmjs.org/slate/-/slate-0.72.8.tgz",
"integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==",
"license": "MIT",
"peer": true,
"dependencies": {
"immer": "^9.0.6",
"is-plain-object": "^5.0.0",
@@ -5315,6 +5549,7 @@
"resolved": "https://registry.npmjs.org/snabbdom/-/snabbdom-3.6.3.tgz",
"integrity": "sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12.17.0"
}
@@ -5480,6 +5715,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -5557,6 +5793,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5626,6 +5863,7 @@
"integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -5794,6 +6032,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -5813,6 +6052,7 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.25.tgz",
"integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.25",
"@vue/compiler-sfc": "3.5.25",

View File

@@ -19,6 +19,10 @@
"@element-plus/icons-vue": "^2.3.1",
"@logicflow/core": "^2.2.1",
"@logicflow/extension": "^2.2.1",
"@vue-flow/background": "^1.3.2",
"@vue-flow/controls": "^1.1.3",
"@vue-flow/core": "^1.48.2",
"@vue-flow/minimap": "^1.5.4",
"@wangeditor/editor": "^5.1.23",
"axios": "1.8.2",
"codemirror": "^6.0.1",

View File

@@ -3856,15 +3856,11 @@ const deleteSelectedElement = async () => {
if (affectedNodeNames.length > 0) {
const previewNames = affectedNodeNames.slice(0, 8);
const overflowText = affectedNodeNames.length > 8 ? `\n...等 ${affectedNodeNames.length} 个节点` : '';
await ElMessageBox.confirm(
`删除该节点将清理以下下级节点中的引用:\n${previewNames.join('、')}${overflowText}`,
'删除确认',
{
confirmButtonText: '继续删除',
cancelButtonText: '取消',
type: 'warning',
}
);
await ElMessageBox.confirm(`删除该节点将清理以下下级节点中的引用:\n${previewNames.join('、')}${overflowText}`, '删除确认', {
confirmButtonText: '继续删除',
cancelButtonText: '取消',
type: 'warning',
});
}
affectedCount = cleanupReferencesToNode(cur.id);
@@ -3878,7 +3874,7 @@ const deleteSelectedElement = async () => {
if (error === 'cancel') return;
ElMessage.error('删除失败');
}
};// 从后端 DSL 恢复工作流
}; // 从后端 DSL 恢复工作流
const loadWorkflowFromDsl = (dsl: any) => {
const lf = logicFlowInstance.value;
if (!lf || !dsl) return;
@@ -5626,7 +5622,3 @@ onBeforeUnmount(() => {
justify-content: center;
}
</style>

View File

@@ -0,0 +1,279 @@
<template>
<div class="input-source-manager">
<el-divider content-position="left">上级参数引用</el-divider>
<!-- 已引用的参数列表 -->
<div v-if="currentInputSource && currentInputSource.length > 0" class="input-source-list">
<div v-for="(sourceNode, index) in currentInputSource" :key="index" class="input-source-item">
<div class="input-source-header">
<span class="input-source-node-name">{{ getNodeName(sourceNode.nodeId) }}</span>
</div>
<div v-if="sourceNode.field && sourceNode.field.length > 0" class="input-source-fields">
<div v-for="fieldName in sourceNode.field" :key="fieldName" class="field-tag">
<el-tag size="small">{{ fieldName }}</el-tag>
<el-button type="danger" link size="small" @click="emit('removeField', sourceNode.nodeId, fieldName)">删除</el-button>
</div>
</div>
<div class="input-source-output">
<el-switch
:model-value="sourceNode.quoteOutput === true"
@change="(val: boolean) => emit('toggleOutput', sourceNode.nodeId, val)"
size="small"
active-text="引入输出"
inactive-text=""
/>
</div>
</div>
</div>
<!-- 显示所有上级节点的输出引用选项 -->
<div v-if="availableParentNodes.length > 0" class="parent-nodes-output">
<div class="parent-nodes-title">上级节点输出</div>
<div v-for="parentNode in availableParentNodes" :key="parentNode.id" class="parent-node-output-item">
<span class="parent-node-name">{{ parentNode.name }}</span>
<el-switch
:model-value="isNodeOutputQuoted(parentNode.id)"
@change="(val: boolean) => emit('toggleOutput', parentNode.id, val)"
size="small"
active-text="引入输出"
inactive-text=""
/>
</div>
</div>
<!-- 选择参数下拉框 -->
<el-form-item label="选择参数">
<el-select :model-value="selectedParam" @update:model-value="handleParamSelect" placeholder="选择上级节点的参数" class="w100">
<el-option v-for="param in availableParams" :key="param.value" :label="param.label" :value="param.value" />
</el-select>
</el-form-item>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import type { Node } from '@vue-flow/core';
interface NodeData {
label: string;
nodeCode: string;
inputSource: Array<{ nodeId: string; field: string[]; quoteOutput?: boolean }> | null;
formConfig?: any[];
modelConfig?: any;
skillName?: string;
}
interface ParentNode {
id: string;
name: string;
}
interface ParamOption {
label: string;
value: string;
}
const props = defineProps<{
selectedNode: Node<NodeData> | null;
nodes: Node<NodeData>[];
edges: any[];
}>();
const emit = defineEmits<{
(e: 'removeField', nodeId: string, fieldName: string): void;
(e: 'toggleOutput', nodeId: string, enabled: boolean): void;
(e: 'addParam', paramValue: string): void;
}>();
const selectedParam = ref('');
// 当前节点的 inputSource
const currentInputSource = computed(() => {
if (!props.selectedNode?.data.inputSource) return [];
return props.selectedNode.data.inputSource.filter((item) => item.field && item.field.length > 0);
});
// 获取节点名称
const getNodeName = (nodeId: string) => {
const node = props.nodes.find((n) => n.id === nodeId);
return node?.data.label || nodeId;
};
// 获取所有上级节点(用于显示输出引用选项)
const availableParentNodes = computed(() => {
if (!props.selectedNode) return [];
// 获取已经引用了字段的节点ID列表
const inputSource = props.selectedNode.data.inputSource;
const nodesWithFields = new Set<string>();
if (Array.isArray(inputSource)) {
inputSource.forEach((item) => {
if (item.field && item.field.length > 0) {
nodesWithFields.add(item.nodeId);
}
});
}
// 递归查找所有上级节点
const findAllParentNodes = (nodeId: string, visited = new Set<string>()): string[] => {
if (visited.has(nodeId)) return [];
visited.add(nodeId);
const incomingEdges = props.edges.filter((e) => e.target === nodeId);
const parentIds: string[] = [];
incomingEdges.forEach((edge) => {
parentIds.push(edge.source);
parentIds.push(...findAllParentNodes(edge.source, visited));
});
return parentIds;
};
const allParentIds = findAllParentNodes(props.selectedNode.id);
const parentNodes = allParentIds
.map((parentId) => {
const parentNode = props.nodes.find((n) => n.id === parentId);
if (!parentNode) return null;
const nodeCode = String(parentNode.data.nodeCode || '').toLowerCase();
const isJudge = ['判断', 'judge', 'condition', 'if', 'branch', 'gateway'].some((k) => nodeCode.includes(k));
const isStart = nodeCode === '__start__';
if (isJudge || isStart || nodesWithFields.has(parentId)) return null;
return {
id: parentId,
name: parentNode.data.label,
};
})
.filter(Boolean);
return parentNodes as ParentNode[];
});
// 检查节点输出是否被引用
const isNodeOutputQuoted = (nodeId: string): boolean => {
if (!props.selectedNode) return false;
const inputSource = props.selectedNode.data.inputSource;
if (!Array.isArray(inputSource)) return false;
const node = inputSource.find((item) => item.nodeId === nodeId);
return node?.quoteOutput === true;
};
// 获取可用的参数选项
const availableParams = computed(() => {
if (!props.selectedNode) return [];
const params: ParamOption[] = [];
const visited = new Set<string>();
const findParents = (nodeId: string) => {
if (visited.has(nodeId)) return;
visited.add(nodeId);
props.edges
.filter((e) => e.target === nodeId)
.forEach((edge) => {
const parent = props.nodes.find((n) => n.id === edge.source);
if (parent && parent.data.nodeCode !== '__start__' && parent.data.nodeCode !== 'judge') {
params.push({
label: `${parent.data.label}.output`,
value: `\${${parent.id}.output}`,
});
}
findParents(edge.source);
});
};
findParents(props.selectedNode.id);
return params;
});
const handleParamSelect = (value: string) => {
if (!value) return;
emit('addParam', value);
selectedParam.value = '';
};
</script>
<style scoped lang="scss">
.input-source-manager {
margin-top: 16px;
}
.input-source-list {
margin-bottom: 16px;
}
.input-source-item {
padding: 12px;
margin-bottom: 12px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
}
.input-source-header {
margin-bottom: 8px;
}
.input-source-node-name {
font-size: 14px;
font-weight: 600;
color: #334155;
}
.input-source-fields {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 8px;
}
.field-tag {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 8px;
background: #fff;
border-radius: 4px;
}
.input-source-output {
padding-top: 8px;
border-top: 1px solid #e2e8f0;
}
.parent-nodes-output {
margin-bottom: 16px;
}
.parent-nodes-title {
font-size: 13px;
font-weight: 600;
color: #64748b;
margin-bottom: 8px;
}
.parent-node-output-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
margin-bottom: 6px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 6px;
}
.parent-node-name {
font-size: 13px;
color: #475569;
}
.w100 {
width: 100%;
}
</style>

View File

@@ -0,0 +1,263 @@
<template>
<div class="config-panel">
<h3>节点配置</h3>
<div v-if="selectedNode">
<el-form label-position="top">
<el-form-item label="节点名称">
<el-input :model-value="selectedNode.data.label" @update:model-value="updateNodeLabel" />
</el-form-item>
<el-form-item label="节点类型">
<el-tag>{{ selectedNode.data.nodeCode }}</el-tag>
</el-form-item>
<!-- 模型选择 -->
<el-form-item v-if="nodeConfig?.modelConfig && nodeConfig.modelConfig.length > 0" label="选择模型">
<el-button type="primary" @click="emit('openModelSelector')" style="width: 100%">选择模型</el-button>
<div v-if="selectedNode.data.modelConfig?.modelName" class="selected-tag">
<el-tag type="success" size="large" closable @close="emit('removeModel')">
{{ selectedNode.data.modelConfig.modelName }}
</el-tag>
</div>
</el-form-item>
<!-- 技能选择 -->
<el-form-item v-if="nodeConfig?.skillOption" label="选择技能">
<el-button type="primary" @click="emit('openSkillSelector')" style="width: 100%">选择技能</el-button>
<div v-if="selectedNode.data.skillName" class="selected-tag">
<el-tag type="success" size="large" closable @close="emit('removeSkill')">
{{ selectedNode.data.skillName }}
</el-tag>
</div>
</el-form-item>
<!-- 动态表单字段 -->
<template v-if="nodeConfig?.formConfig && nodeConfig.formConfig.length > 0">
<el-divider content-position="left">节点参数</el-divider>
<el-form-item v-for="field in nodeConfig.formConfig" :key="field.field" :label="field.label" :required="field.required">
<el-input
v-if="field.type === 'input' || field.type === 'string'"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
:placeholder="field.required ? '必填' : '选填'"
/>
<el-input-number
v-else-if="field.type === 'number' || field.type === 'inputNumber'"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
class="w100"
/>
<el-input
v-else-if="field.type === 'textarea'"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
type="textarea"
:rows="3"
:placeholder="field.required ? '必填' : '选填'"
/>
<el-switch
v-else-if="field.type === 'switch'"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
/>
<el-select
v-else-if="field.type === 'select' && field.options"
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
:placeholder="field.required ? '必填' : '选填'"
class="w100"
>
<el-option v-for="opt in field.options" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
<el-input
v-else
:model-value="getFieldValue(field.field)"
@update:model-value="updateFieldValue(field.field, $event)"
:placeholder="field.required ? '必填' : '选填'"
/>
</el-form-item>
</template>
</el-form>
<!-- 上级参数管理 -->
<InputSourceManager
v-if="selectedNode.data.nodeCode !== '__start__'"
:selected-node="selectedNode"
:nodes="allNodes"
:edges="allEdges"
@remove-field="(nodeId: string, fieldName: string) => emit('removeField', nodeId, fieldName)"
@toggle-output="(nodeId: string, enabled: boolean) => emit('toggleOutput', nodeId, enabled)"
@add-param="(paramValue: string) => emit('addParamByValue', paramValue)"
/>
</div>
<el-empty v-else description="请选择一个节点" :image-size="100" />
</div>
</template>
<script setup lang="ts">
import type { Node } from '@vue-flow/core';
import InputSourceManager from './InputSourceManager.vue';
interface NodeData {
label: string;
nodeCode: string;
inputSource: Array<{ nodeId: string; field: string[]; quoteOutput?: boolean }> | null;
formConfig?: any[];
modelConfig?: any;
skillName?: string;
}
interface ParamRef {
id: string;
label: string;
}
interface NodeConfig {
formConfig: any[];
modelConfig: any[];
skillOption: boolean;
}
const props = defineProps<{
selectedNode: Node<NodeData> | null;
availableParams: ParamRef[];
nodeConfig: NodeConfig | null;
allNodes: Node<NodeData>[];
allEdges: any[];
}>();
const emit = defineEmits<{
(e: 'update:selectedNode', node: Node<NodeData>): void;
(e: 'addParam', param: ParamRef): void;
(e: 'openModelSelector'): void;
(e: 'removeModel'): void;
(e: 'openSkillSelector'): void;
(e: 'removeSkill'): void;
(e: 'removeField', nodeId: string, fieldName: string): void;
(e: 'toggleOutput', nodeId: string, enabled: boolean): void;
(e: 'addParamByValue', paramValue: string): void;
}>();
const updateNodeLabel = (newLabel: string) => {
if (!props.selectedNode) return;
const updatedNode = {
...props.selectedNode,
data: {
...props.selectedNode.data,
label: newLabel,
},
};
emit('update:selectedNode', updatedNode);
};
const getFieldValue = (fieldName: string) => {
if (!props.selectedNode?.data.formConfig) return '';
const field = props.selectedNode.data.formConfig.find((f: any) => f.field === fieldName);
return field?.value ?? '';
};
const updateFieldValue = (fieldName: string, value: any) => {
if (!props.selectedNode) return;
const formConfig = props.selectedNode.data.formConfig || [];
const existingIndex = formConfig.findIndex((f: any) => f.field === fieldName);
let updatedFormConfig;
if (existingIndex >= 0) {
updatedFormConfig = [...formConfig];
updatedFormConfig[existingIndex] = { ...updatedFormConfig[existingIndex], value };
} else {
const fieldDef = props.nodeConfig?.formConfig.find((f) => f.field === fieldName);
if (fieldDef) {
updatedFormConfig = [...formConfig, { ...fieldDef, value }];
} else {
updatedFormConfig = formConfig;
}
}
const updatedNode = {
...props.selectedNode,
data: {
...props.selectedNode.data,
formConfig: updatedFormConfig,
},
};
emit('update:selectedNode', updatedNode);
};
</script>
<style scoped lang="scss">
.config-panel {
background: #ffffff;
border-radius: 10px;
padding: 16px;
overflow-y: auto;
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
border: 1px solid #e6eaf0;
h3 {
margin: 0 0 16px 0;
font-size: 15px;
font-weight: 700;
color: #334155;
letter-spacing: 0;
}
:deep(.el-form) {
.el-form-item {
margin-bottom: 14px;
.el-form-item__label {
font-weight: 600;
color: #475569;
margin-bottom: 6px;
font-size: 13px;
}
.el-input__wrapper {
border-radius: 6px;
box-shadow: none;
border: 1px solid #dbe3ee;
transition: all 0.2s ease;
&:hover {
border-color: #b8c4d6;
}
&.is-focus {
border-color: #3b82f6;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12);
}
}
}
}
.param-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
margin-bottom: 8px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
transition: all 0.2s ease;
&:hover {
border-color: #cbd5e1;
background: #f1f5f9;
}
}
.selected-tag {
margin-top: 10px;
}
.w100 {
width: 100%;
}
:deep(.el-empty) {
padding: 32px 12px;
}
}
</style>

View File

@@ -0,0 +1,149 @@
<template>
<div class="node-library-panel" :class="{ collapsed: collapsed }">
<div class="node-library-header">
<span>节点库</span>
<el-button class="collapse-btn" text circle @click="emit('update:collapsed', !collapsed)">
<el-icon>
<ArrowRightBold v-if="collapsed" />
<ArrowLeftBold v-else />
</el-icon>
</el-button>
</div>
<div v-if="!collapsed" class="node-library-content">
<el-empty v-if="nodeLibraryGroups.length === 0" description="暂无节点" :image-size="40" />
<div v-else class="node-library-groups">
<div v-for="group in nodeLibraryGroups" :key="group.group" class="node-group">
<div class="node-group-title">{{ group.label }}</div>
<div class="node-group-items">
<el-button
v-for="item in group.items"
:key="item.nodeCode"
text
class="node-item"
@click="emit('addNode', item.nodeCode, item.nodeName)"
>
{{ item.nodeName }}
</el-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue';
import type { NodeLibraryGroup } from '/@/api/settings/creation';
defineProps<{
nodeLibraryGroups: NodeLibraryGroup[];
collapsed: boolean;
}>();
const emit = defineEmits<{
(e: 'update:collapsed', value: boolean): void;
(e: 'addNode', nodeCode: string, nodeName: string): void;
}>();
</script>
<style scoped lang="scss">
.node-library-panel {
position: absolute;
top: 10px;
left: 10px;
z-index: 10;
background: #fff;
border: 1px solid #d8e0eb;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
width: 132px;
max-height: calc(100% - 20px);
display: flex;
flex-direction: column;
overflow: hidden;
transition: width 0.18s ease;
&.collapsed {
width: 40px;
.node-library-header {
padding: 8px;
border-bottom: none;
> span {
display: none;
}
}
.node-library-content {
display: none;
}
}
}
.node-library-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
border-bottom: 1px solid #e7ecf3;
font-size: 12px;
font-weight: 700;
color: #1f2937;
background: #f8fafc;
.collapse-btn {
width: 20px;
height: 20px;
border: 1px solid #cfd8e6;
color: #64748b;
}
}
.node-library-content {
padding: 12px;
overflow-y: auto;
max-height: 480px;
}
.node-library-groups {
display: flex;
flex-direction: column;
gap: 16px;
}
.node-group {
.node-group-title {
font-size: 12px;
font-weight: 700;
color: #64748b;
margin-bottom: 8px;
padding-bottom: 4px;
border-bottom: 1px solid #e7ecf3;
}
.node-group-items {
display: flex;
flex-direction: column;
gap: 2px;
}
.node-item {
justify-content: flex-start;
width: 100%;
padding: 8px 10px;
color: #475569;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
background: transparent;
border: none;
transition: all 0.15s ease;
&:hover {
background: #f1f5f9;
color: #1e293b;
}
}
}
</style>

View File

@@ -0,0 +1,40 @@
<template>
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" :close-on-click-modal="false">
<el-form :model="saveForm" label-position="top">
<el-form-item label="工作流名称" required>
<el-input v-model="saveForm.flowName" placeholder="请输入工作流名称" maxlength="50" show-word-limit />
</el-form-item>
<el-form-item label="工作流描述">
<el-input v-model="saveForm.description" type="textarea" :rows="4" placeholder="请输入工作流描述(选填)" maxlength="200" show-word-limit />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="emit('confirm')">{{ confirmText }}</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
modelValue: boolean;
saveForm: { flowName: string; description: string };
currentEditingWorkflowId: string | null;
saving: boolean;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void;
(e: 'confirm'): void;
}>();
const dialogVisible = computed({
get: () => props.modelValue,
set: (value: boolean) => emit('update:modelValue', value),
});
const dialogTitle = computed(() => (props.currentEditingWorkflowId ? '编辑工作流' : '保存工作流'));
const confirmText = computed(() => (props.currentEditingWorkflowId ? '确定更新' : '确定保存'));
</script>

View File

@@ -0,0 +1,191 @@
<template>
<div class="workflow-list-panel">
<div class="panel-header">
<el-tabs v-model="activeTab" class="workflow-tabs">
<el-tab-pane label="我的工作流" name="user"></el-tab-pane>
<el-tab-pane label="模板工作流" name="template"></el-tab-pane>
</el-tabs>
<el-button type="success" size="small" @click="emit('create')">新建</el-button>
</div>
<div class="workflow-list-content" v-loading="loading">
<el-empty v-if="currentList.length === 0" description="暂无工作流" :image-size="60" />
<div v-else class="workflow-items">
<div
v-for="workflow in currentList"
:key="workflow.id"
class="workflow-item"
:class="{ active: currentEditingId === workflow.id }"
@click="emit('edit', workflow)"
>
<div class="workflow-item-content">
<div class="workflow-item-name">{{ workflow.flowName || workflow.flowTemplateName || '未命名工作流' }}</div>
<div class="workflow-item-desc">{{ workflow.description || '暂无描述' }}</div>
</div>
<div class="workflow-item-actions">
<el-button type="primary" link size="small" @click.stop="emit('edit', workflow)">编辑</el-button>
<el-button type="danger" link size="small" @click.stop="emit('delete', workflow)">删除</el-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import type { WorkflowItem } from '/@/api/settings/creation';
const props = defineProps<{
userWorkflowList: WorkflowItem[];
templateWorkflowList: WorkflowItem[];
currentEditingId: string | null;
loading: boolean;
}>();
const emit = defineEmits<{
(e: 'edit', workflow: WorkflowItem): void;
(e: 'delete', workflow: WorkflowItem): void;
(e: 'create'): void;
}>();
const activeTab = ref<'user' | 'template'>('user');
const currentList = computed(() => {
return activeTab.value === 'user' ? props.userWorkflowList : props.templateWorkflowList;
});
</script>
<style scoped lang="scss">
.workflow-list-panel {
background: #ffffff;
border-radius: 10px;
padding: 16px;
overflow-y: auto;
display: flex;
flex-direction: column;
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
border: 1px solid #e6eaf0;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
:deep(.el-button) {
border-radius: 6px;
font-weight: 500;
font-size: 12px;
padding: 6px 12px;
}
}
.workflow-tabs {
flex: 1;
:deep(.el-tabs__header) {
margin: 0;
}
:deep(.el-tabs__nav-wrap::after) {
display: none;
}
:deep(.el-tabs__item) {
font-size: 14px;
font-weight: 600;
color: #64748b;
padding: 0 16px;
height: 36px;
line-height: 36px;
&.is-active {
color: #3b82f6;
}
}
:deep(.el-tabs__active-bar) {
background-color: #3b82f6;
}
}
.workflow-list-content {
flex: 1;
overflow-y: auto;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: rgba(102, 126, 234, 0.05);
border-radius: 10px;
}
&::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
}
}
.workflow-items {
display: flex;
flex-direction: column;
gap: 8px;
}
.workflow-item {
padding: 12px;
border: 1px solid #e2e8f0;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
background: #fff;
&:hover {
border-color: #c5d2e6;
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.06);
}
&.active {
border-color: #3b82f6;
background: #eff6ff;
}
}
.workflow-item-content {
margin-bottom: 8px;
}
.workflow-item-name {
font-size: 14px;
font-weight: 600;
color: #1e293b;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.workflow-item-desc {
font-size: 12px;
color: #64748b;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.workflow-item-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
:deep(.el-button) {
border-radius: 6px;
font-weight: 500;
font-size: 12px;
padding: 4px 8px;
}
}
</style>

File diff suppressed because it is too large Load Diff