From 1f1db18c3e439c595d4e04690b85a309fe61a740 Mon Sep 17 00:00:00 2001
From: yuyuer13 <47338587@qq.com>
Date: Wed, 22 Jan 2025 20:55:10 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=B5=81=E7=A8=8B=E7=AE=A1?=
=?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.vscode/settings.json | 2 +-
package.json | 5 +-
pnpm-lock.yaml | 10 +-
src/api/flow/index.ts | 9 +
src/components/wu-flow-lib/assets/icons.ts | 70 ++
.../wu-flow-lib/assets/icons/index.ts | 61 ++
.../wu-flow-lib/components/Accordion.vue | 124 +++
.../wu-flow-lib/components/FlowEditor.vue | 269 ++++++
.../wu-flow-lib/components/FlowHeader.vue | 25 +
.../wu-flow-lib/components/SplitPane.css | 31 +
.../wu-flow-lib/components/SplitPane.vue | 399 ++++++++
.../components/canvas/FlowCanvas.vue | 172 ++++
.../wu-flow-lib/components/icons.ts | 93 ++
.../wu-flow-lib/components/index.ts | 18 +
.../wu-flow-lib/components/menu/FlowMenu.vue | 310 +++++++
.../components/menu/FlowToolbar.vue | 874 ++++++++++++++++++
.../components/menu/TextEditToolbar.vue | 131 +++
.../wu-flow-lib/components/menu/menuConfig.ts | 81 ++
.../components/node/ShapeLibrary.vue | 261 ++++++
.../wu-flow-lib/components/node/flowNodes.ts | 426 +++++++++
.../components/panel/FlowAccordion.vue | 26 +
.../components/property/ExportPreview.vue | 104 +++
.../components/property/NodeSelector.vue | 324 +++++++
.../components/property/PropertyPanel.vue | 180 ++++
.../components/property/StyleLibrary.vue | 379 ++++++++
.../components/property/TableEditor.vue | 206 +++++
.../wu-flow-lib/components/types.d.ts | 5 +
.../wu-flow-lib/constants/menuIcons.ts | 29 +
src/components/wu-flow-lib/events.ts | 21 +
.../flow/CustomConstraintHandler.ts | 5 +
.../flow/CustomRubberBandHandler.ts | 5 +
.../flow/MyCustomPopupMenuHandler.ts | 164 ++++
.../wu-flow-lib/flow/WuConnectionHandler.ts | 8 +
.../wu-flow-lib/flow/WuCustomGeometryClass.ts | 6 +
.../wu-flow-lib/flow/WuCustomGraph.ts | 405 ++++++++
.../wu-flow-lib/flow/WuSelectHander.ts | 14 +
.../handlers/CanvasEventHandler.ts | 414 +++++++++
.../handlers/CustomPopupMenuHandler.ts | 43 +
.../wu-flow-lib/handlers/KeyHandler.ts | 121 +++
.../handlers/WuConnctionHandler.ts | 53 ++
.../handlers/WuConstraintHandler.ts | 7 +
src/components/wu-flow-lib/icons/index.ts | 417 +++++++++
src/components/wu-flow-lib/index.ts | 21 +
.../wu-flow-lib/styles/flow-canvas.css | 236 +++++
.../wu-flow-lib/utils/dragHandler.ts | 65 ++
.../wu-flow-lib/utils/flow-tools-bars.ts | 7 +
.../wu-flow-lib/utils/flow-utils.ts | 366 ++++++++
.../wu-flow-lib/utils/shape-register.ts | 216 +++++
src/router/index.ts | 3 +-
src/router/modules/flowrouter.ts | 63 ++
src/router/modules/remaining.ts | 2 +-
src/views/flow/FileManager.vue | 10 +-
src/views/flow/FlowEdit.vue | 2 +-
src/views/flow/FlowEditor.vue | 10 +-
src/views/flow/Home.vue | 17 +-
src/views/flow/HomeDefault.vue | 12 +-
src/views/flow/Register.vue | 87 +-
src/views/flow/files/MyFiles.vue | 2 +-
src/views/system/user/index.vue | 3 -
59 files changed, 7344 insertions(+), 85 deletions(-)
create mode 100644 src/api/flow/index.ts
create mode 100644 src/components/wu-flow-lib/assets/icons.ts
create mode 100644 src/components/wu-flow-lib/assets/icons/index.ts
create mode 100644 src/components/wu-flow-lib/components/Accordion.vue
create mode 100644 src/components/wu-flow-lib/components/FlowEditor.vue
create mode 100644 src/components/wu-flow-lib/components/FlowHeader.vue
create mode 100644 src/components/wu-flow-lib/components/SplitPane.css
create mode 100644 src/components/wu-flow-lib/components/SplitPane.vue
create mode 100644 src/components/wu-flow-lib/components/canvas/FlowCanvas.vue
create mode 100644 src/components/wu-flow-lib/components/icons.ts
create mode 100644 src/components/wu-flow-lib/components/index.ts
create mode 100644 src/components/wu-flow-lib/components/menu/FlowMenu.vue
create mode 100644 src/components/wu-flow-lib/components/menu/FlowToolbar.vue
create mode 100644 src/components/wu-flow-lib/components/menu/TextEditToolbar.vue
create mode 100644 src/components/wu-flow-lib/components/menu/menuConfig.ts
create mode 100644 src/components/wu-flow-lib/components/node/ShapeLibrary.vue
create mode 100644 src/components/wu-flow-lib/components/node/flowNodes.ts
create mode 100644 src/components/wu-flow-lib/components/panel/FlowAccordion.vue
create mode 100644 src/components/wu-flow-lib/components/property/ExportPreview.vue
create mode 100644 src/components/wu-flow-lib/components/property/NodeSelector.vue
create mode 100644 src/components/wu-flow-lib/components/property/PropertyPanel.vue
create mode 100644 src/components/wu-flow-lib/components/property/StyleLibrary.vue
create mode 100644 src/components/wu-flow-lib/components/property/TableEditor.vue
create mode 100644 src/components/wu-flow-lib/components/types.d.ts
create mode 100644 src/components/wu-flow-lib/constants/menuIcons.ts
create mode 100644 src/components/wu-flow-lib/events.ts
create mode 100644 src/components/wu-flow-lib/flow/CustomConstraintHandler.ts
create mode 100644 src/components/wu-flow-lib/flow/CustomRubberBandHandler.ts
create mode 100644 src/components/wu-flow-lib/flow/MyCustomPopupMenuHandler.ts
create mode 100644 src/components/wu-flow-lib/flow/WuConnectionHandler.ts
create mode 100644 src/components/wu-flow-lib/flow/WuCustomGeometryClass.ts
create mode 100644 src/components/wu-flow-lib/flow/WuCustomGraph.ts
create mode 100644 src/components/wu-flow-lib/flow/WuSelectHander.ts
create mode 100644 src/components/wu-flow-lib/handlers/CanvasEventHandler.ts
create mode 100644 src/components/wu-flow-lib/handlers/CustomPopupMenuHandler.ts
create mode 100644 src/components/wu-flow-lib/handlers/KeyHandler.ts
create mode 100644 src/components/wu-flow-lib/handlers/WuConnctionHandler.ts
create mode 100644 src/components/wu-flow-lib/handlers/WuConstraintHandler.ts
create mode 100644 src/components/wu-flow-lib/icons/index.ts
create mode 100644 src/components/wu-flow-lib/index.ts
create mode 100644 src/components/wu-flow-lib/styles/flow-canvas.css
create mode 100644 src/components/wu-flow-lib/utils/dragHandler.ts
create mode 100644 src/components/wu-flow-lib/utils/flow-tools-bars.ts
create mode 100644 src/components/wu-flow-lib/utils/flow-utils.ts
create mode 100644 src/components/wu-flow-lib/utils/shape-register.ts
create mode 100644 src/router/modules/flowrouter.ts
diff --git a/.vscode/settings.json b/.vscode/settings.json
index f145f386a..2db1f3dce 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -62,7 +62,7 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
- "editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
diff --git a/package.json b/package.json
index ff94abd74..6b98b4850 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,6 @@
"lint:lint-staged": "lint-staged -c "
},
"dependencies": {
- "@element-plus/icons-vue": "^2.1.0",
"@form-create/designer": "^3.2.6",
"@form-create/element-ui": "^3.2.11",
"@iconify/iconify": "^3.1.1",
@@ -58,7 +57,9 @@
"markmap-toolbar": "^0.17.0",
"markmap-view": "^0.16.0",
"min-dash": "^4.1.1",
- "mitt": "^3.0.1",
+ "mitt": "^3.0.1",
+ "@element-plus/icons-vue": "^2.3.1",
+ "@maxgraph/core": "^0.14.0",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c9d9688c9..78551aca6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9,7 +9,7 @@ importers:
.:
dependencies:
'@element-plus/icons-vue':
- specifier: ^2.1.0
+ specifier: ^2.3.1
version: 2.3.1(vue@3.5.12(typescript@5.3.3))
'@form-create/designer':
specifier: ^3.2.6
@@ -20,6 +20,9 @@ importers:
'@iconify/iconify':
specifier: ^3.1.1
version: 3.1.1
+ '@maxgraph/core':
+ specifier: ^0.14.0
+ version: 0.14.0
'@microsoft/fetch-event-source':
specifier: ^2.0.1
version: 2.0.1
@@ -1341,6 +1344,9 @@ packages:
'@lezer/markdown@1.3.2':
resolution: {integrity: sha512-Wu7B6VnrKTbBEohqa63h5vxXjiC4pO5ZQJ/TDbhJxPQaaIoRD/6UVDhSDtVsCwVZV12vvN9KxuLL3ATMnlG0oQ==}
+ '@maxgraph/core@0.14.0':
+ resolution: {integrity: sha512-PRfoX8Z7jwG3lDm+C+h6nS5pseW+m6PxzP5drP5azSsHHCdHndLt7eFvHPIxseKm0jp3YcGpyuzJvb490s2u9g==}
+
'@microsoft/fetch-event-source@2.0.1':
resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==}
@@ -6974,6 +6980,8 @@ snapshots:
'@lezer/common': 1.2.3
'@lezer/highlight': 1.2.1
+ '@maxgraph/core@0.14.0': {}
+
'@microsoft/fetch-event-source@2.0.1': {}
'@nodelib/fs.scandir@2.1.5':
diff --git a/src/api/flow/index.ts b/src/api/flow/index.ts
new file mode 100644
index 000000000..ad407d2cd
--- /dev/null
+++ b/src/api/flow/index.ts
@@ -0,0 +1,9 @@
+
+import request from '@/config/axios'
+export const register = (data: any) => {
+ return request.post({
+ url: '/api/flow/register',
+ method: 'post',
+ data
+ })
+}
diff --git a/src/components/wu-flow-lib/assets/icons.ts b/src/components/wu-flow-lib/assets/icons.ts
new file mode 100644
index 000000000..cb5c8c944
--- /dev/null
+++ b/src/components/wu-flow-lib/assets/icons.ts
@@ -0,0 +1,70 @@
+export const ToolbarIcons = {
+ grid: ``,
+
+ export: ``,
+
+ undo: ``,
+
+ redo: ``,
+
+ delete: ``,
+
+ fit: ``,
+
+ zoomOut: ``,
+
+ zoomIn: ``,
+
+ back: ``,
+
+ curvedLine: ``,
+
+ orthogonalLine: ``,
+
+ straightLine: ``
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/assets/icons/index.ts b/src/components/wu-flow-lib/assets/icons/index.ts
new file mode 100644
index 000000000..2a83fd656
--- /dev/null
+++ b/src/components/wu-flow-lib/assets/icons/index.ts
@@ -0,0 +1,61 @@
+export const ToolbarIcons = {
+ zoomIn: ``,
+
+ zoomOut: ``,
+
+ fit: ``,
+
+ delete: ``,
+
+ undo: ``,
+
+ redo: ``,
+
+ straightLine: ``,
+
+ orthogonalLine: ``,
+
+ curvedLine: ``,
+
+ autoLayout: ``,
+
+ export: ``,
+
+ back: ``
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/Accordion.vue b/src/components/wu-flow-lib/components/Accordion.vue
new file mode 100644
index 000000000..f7ca5b2b5
--- /dev/null
+++ b/src/components/wu-flow-lib/components/Accordion.vue
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.content }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/FlowEditor.vue b/src/components/wu-flow-lib/components/FlowEditor.vue
new file mode 100644
index 000000000..8c452fa73
--- /dev/null
+++ b/src/components/wu-flow-lib/components/FlowEditor.vue
@@ -0,0 +1,269 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/FlowHeader.vue b/src/components/wu-flow-lib/components/FlowHeader.vue
new file mode 100644
index 000000000..bfd77521b
--- /dev/null
+++ b/src/components/wu-flow-lib/components/FlowHeader.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/SplitPane.css b/src/components/wu-flow-lib/components/SplitPane.css
new file mode 100644
index 000000000..a17eee5f5
--- /dev/null
+++ b/src/components/wu-flow-lib/components/SplitPane.css
@@ -0,0 +1,31 @@
+/* SplitPane.css */
+.split-container {
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ width: 100%;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+
+.toolbar {
+ position: relative;
+ height: 50px;
+ background-color: #fff;
+ border-bottom: 1px solid #e4e7ed;
+ display: flex;
+ align-items: center;
+ padding: 0 20px;
+ z-index: 100;
+}
+
+/* ... 其他样式代码 ... */
+
+.right-panel-content {
+ flex: 1;
+ overflow: auto;
+ padding: 16px;
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/SplitPane.vue b/src/components/wu-flow-lib/components/SplitPane.vue
new file mode 100644
index 000000000..771fbf3ea
--- /dev/null
+++ b/src/components/wu-flow-lib/components/SplitPane.vue
@@ -0,0 +1,399 @@
+
+
+
+
+
+ 左侧面板
+
+
+
+
+ {{ state.leftPanelVisible ? '◀' : '▶' }}
+
+
+
+
+ 中间面板
+
+
+
+
+ {{ state.rightPanelVisible ? '▶' : '◀' }}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/canvas/FlowCanvas.vue b/src/components/wu-flow-lib/components/canvas/FlowCanvas.vue
new file mode 100644
index 000000000..6de15c952
--- /dev/null
+++ b/src/components/wu-flow-lib/components/canvas/FlowCanvas.vue
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/icons.ts b/src/components/wu-flow-lib/components/icons.ts
new file mode 100644
index 000000000..a6bca7d7d
--- /dev/null
+++ b/src/components/wu-flow-lib/components/icons.ts
@@ -0,0 +1,93 @@
+export const FlowIcons = {
+ // 基础流程图标
+ Start: ``,
+
+ End: ``,
+
+ Process: ``,
+
+ Decision: ``,
+
+ Arrow: ``,
+
+ // UML节点图标
+ Class: ``,
+
+ Interface: ``,
+
+ Package: ``,
+
+ Component: ``,
+
+ Object: ``,
+
+ Enumeration: ``,
+
+ Association: ``,
+
+ Inheritance: ``,
+
+ Implementation: ``,
+
+ Dependency: ``,
+
+ // 高级组件图标
+ SubProcess: ``,
+
+ Parallel: ``,
+
+ Timer: ``
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/index.ts b/src/components/wu-flow-lib/components/index.ts
new file mode 100644
index 000000000..75f57e2cb
--- /dev/null
+++ b/src/components/wu-flow-lib/components/index.ts
@@ -0,0 +1,18 @@
+// Menu Components
+export { default as FlowToolbar } from './menu/FlowToolbar.vue';
+export { default as TextEditToolbar } from './menu/TextEditToolbar.vue';
+
+// Node Components
+export { default as ShapeLibrary } from './node/ShapeLibrary.vue';
+export * from './node/flowNodes';
+
+// Property Components
+export { default as StyleLibrary } from './property/StyleLibrary.vue';
+export { default as TableEditor } from './property/TableEditor.vue';
+export { default as ExportPreview } from './property/ExportPreview.vue';
+
+// Canvas Components
+export { default as FlowCanvas } from './canvas/FlowCanvas.vue';
+
+// Panel Components
+export { default as FlowAccordion } from './panel/FlowAccordion.vue';
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/menu/FlowMenu.vue b/src/components/wu-flow-lib/components/menu/FlowMenu.vue
new file mode 100644
index 000000000..cee88f931
--- /dev/null
+++ b/src/components/wu-flow-lib/components/menu/FlowMenu.vue
@@ -0,0 +1,310 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/menu/FlowToolbar.vue b/src/components/wu-flow-lib/components/menu/FlowToolbar.vue
new file mode 100644
index 000000000..4655bde72
--- /dev/null
+++ b/src/components/wu-flow-lib/components/menu/FlowToolbar.vue
@@ -0,0 +1,874 @@
+
+
+
+
+
+
+
diff --git a/src/components/wu-flow-lib/components/menu/TextEditToolbar.vue b/src/components/wu-flow-lib/components/menu/TextEditToolbar.vue
new file mode 100644
index 000000000..26bdffac8
--- /dev/null
+++ b/src/components/wu-flow-lib/components/menu/TextEditToolbar.vue
@@ -0,0 +1,131 @@
+
+
+
+
+
+ {{ font }}
+
+
+
+
+
+
+ {{ size }}px
+
+
+
+
+
+
+
+
+ B
+
+
+
+
+ U
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/menu/menuConfig.ts b/src/components/wu-flow-lib/components/menu/menuConfig.ts
new file mode 100644
index 000000000..5e3d5c91b
--- /dev/null
+++ b/src/components/wu-flow-lib/components/menu/menuConfig.ts
@@ -0,0 +1,81 @@
+export const menuConfig = [
+ {
+ key: 'file',
+ label: '文件',
+ children: [
+ { key: 'new', label: '新建', icon: 'Document', shortcut: 'Ctrl+N' },
+ { key: 'open', label: '打开', icon: 'FolderOpened', shortcut: 'Ctrl+O' },
+ { key: 'save', label: '保存', icon: 'Download', shortcut: 'Ctrl+S' },
+ { type: 'divider' },
+ { key: 'exportImage', label: '导出图片', icon: 'Picture', shortcut: 'Ctrl+E' },
+ { key: 'exportJson', label: '导出JSON', icon: 'Document' }
+ ]
+ },
+ {
+ key: 'edit',
+ label: '编辑',
+ children: [
+ { key: 'undo', label: '撤销', icon: 'Back', shortcut: 'Ctrl+Z' },
+ { key: 'redo', label: '重做', icon: 'Right', shortcut: 'Ctrl+Y' },
+ { type: 'divider' },
+ { key: 'cut', label: '剪切', icon: 'Scissors', shortcut: 'Ctrl+X' },
+ { key: 'copy', label: '复制', icon: 'Document', shortcut: 'Ctrl+C' },
+ { key: 'paste', label: '粘贴', icon: 'DocumentCopy', shortcut: 'Ctrl+V' },
+ { type: 'divider' },
+ { key: 'delete', label: '删除', icon: 'Delete', shortcut: 'Delete' },
+ { key: 'selectAll', label: '全选', icon: 'Select', shortcut: 'Ctrl+A' }
+ ]
+ },
+ {
+ key: 'select',
+ label: '选择',
+ children: [
+ { key: 'selectAll', label: '全选', icon: 'Select' },
+ { key: 'invertSelect', label: '反选', icon: 'RefreshRight' },
+ { key: 'cancelSelect', label: '取消选择', icon: 'Close' }
+ ]
+ },
+ {
+ label: '视图',
+ key: 'view',
+ children: [
+ { key: 'zoomIn', label: '放大', icon: 'ZoomIn' },
+ { key: 'zoomOut', label: '缩小', icon: 'ZoomOut' },
+ { key: 'fit', label: '适应画布', icon: 'FullScreen' },
+ { type: 'divider' },
+ { key: 'showGrid', label: '显示网格', type: 'checkbox', checked: true },
+ { key: 'showMinimap', label: '显示缩略图', type: 'checkbox', checked: true }
+ ]
+ },
+ {
+ label: '插入',
+ key: 'insert',
+ children: [
+ { label: '图片', key: 'insertImage', icon: 'Picture' },
+ { label: '表格', key: 'insertTable', icon: 'Grid' },
+ { label: '文本', key: 'insertText', icon: 'EditPen' }
+ ]
+ },
+ {
+ key: 'canvas',
+ label: '画布',
+ children: [
+ { key: 'backgroundColor', label: '背景颜色', icon: 'Brush', type: 'color', value: '#ffffff' },
+ { key: 'gridSize', label: '网格大小', icon: 'Grid', type: 'number', value: 10, min: 5, max: 50 },
+ { key: 'canvasSize', label: '画布大小', icon: 'FullScreen', type: 'size',
+ value: { width: 1000, height: 800 } }
+ ]
+ },
+ {
+ label: '排列',
+ key: 'arrange',
+ children: [
+ { key: 'hierarchicalLayout', label: '层次布局', icon: 'Sort' },
+ { key: 'circleLayout', label: '圆形布局', icon: 'CirclePlus' },
+ { key: 'organicLayout', label: '有机布局', icon: 'Connection' },
+ { type: 'divider' },
+ { key: 'bringToFront', label: '置于顶层', icon: 'TopRight' },
+ { key: 'sendToBack', label: '置于底层', icon: 'BottomLeft' }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/node/ShapeLibrary.vue b/src/components/wu-flow-lib/components/node/ShapeLibrary.vue
new file mode 100644
index 000000000..8b7aefbfc
--- /dev/null
+++ b/src/components/wu-flow-lib/components/node/ShapeLibrary.vue
@@ -0,0 +1,261 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/wu-flow-lib/components/node/flowNodes.ts b/src/components/wu-flow-lib/components/node/flowNodes.ts
new file mode 100644
index 000000000..b16f1a465
--- /dev/null
+++ b/src/components/wu-flow-lib/components/node/flowNodes.ts
@@ -0,0 +1,426 @@
+import { FlowIcons } from '../icons';
+
+interface FlowNode {
+ type: string;
+ label: string;
+ size?: [number, number] | null;
+ icon: string;
+ backgroundColor?: string;
+ style?: any;
+}
+
+interface NodeCategory {
+ title: string;
+ items: FlowNode[];
+}
+
+const flowCategories: NodeCategory[] = [
+ {
+ title: '基础流程',
+ items: [
+ {
+ type: 'Start',
+ label: '开始',
+ icon: 'Start',
+ size: [50, 50],
+ backgroundColor: '#e8f5e9',
+ style: {
+ shape: 'ellipse',
+ fillColor: '#e8f5e9',
+ strokeColor: '#4caf50',
+ strokeWidth: 2
+ }
+ },
+ {
+ type: 'End',
+ label: '结束',
+ icon: 'End',
+ size: [50, 50],
+ backgroundColor: '#ffebee',
+ style: {
+ shape: 'end',
+ fillColor: '#ffebee',
+ strokeColor: '#f44336',
+ strokeWidth: 2
+ }
+ },
+ {
+ type: 'Process',
+ label: '处理',
+ icon: 'Process',
+ backgroundColor: '#e3f2fd',
+ style: {
+ shape: 'rectangle',
+ fillColor: '#e3f2fd',
+ strokeColor: '#2196f3',
+ strokeWidth: 1
+ }
+ },
+ {
+ type: 'Decision',
+ label: '判断',
+ icon: 'Decision',
+ backgroundColor: '#fff3e0',
+ style: {
+ shape: 'rhombus',
+ fillColor: '#fff3e0',
+ strokeColor: '#ff9800',
+ strokeWidth: 1
+ }
+ },
+ {
+ type: 'Note',
+ label: '备注',
+ icon: 'Note',
+ size: [120, 80],
+ backgroundColor: '#fff9c4',
+ style: {
+ shape: 'note',
+ fillColor: '#fff9c4',
+ strokeColor: '#ffd54f',
+ strokeWidth: 1,
+ shadow: true,
+ rounded: true,
+ html: 1,
+ whiteSpace: 'wrap',
+ verticalAlign: 'top',
+ align: 'left',
+ spacingTop: 5,
+ spacingLeft: 5
+ }
+ },
+ {
+ type: 'Text',
+ label: '文字',
+ icon: 'Text',
+ size: [100, 40],
+ backgroundColor: 'transparent',
+ style: {
+ shape: 'text',
+ strokeColor: 'none',
+ fillColor: 'none',
+ fontColor: '#333333',
+ fontSize: 14,
+ fontFamily: 'Arial',
+ align: 'center',
+ verticalAlign: 'middle',
+ html: 1,
+ whiteSpace: 'wrap',
+ editable: true,
+ autosize: true
+ }
+ }
+ ]
+ },
+ {
+ title: '基础形状',
+ items: [
+ {
+ type: 'Rectangle',
+ label: '矩形',
+ icon: 'Rectangle',
+ style: {
+ shape: 'rectangle',
+ fillColor: '#ffffff',
+ strokeColor: '#333333'
+ }
+ },
+ {
+ type: 'Ellipse',
+ label: '圆形',
+ icon: 'Ellipse',
+ style: {
+ shape: 'ellipse',
+ fillColor: '#ffffff',
+ strokeColor: '#333333'
+ }
+ },
+ {
+ type: 'Rhombus',
+ label: '菱形',
+ icon: 'Rhombus',
+ style: {
+ shape: 'rhombus',
+ fillColor: '#ffffff',
+ strokeColor: '#333333'
+ }
+ },
+ {
+ type: 'Triangle',
+ label: '三角形',
+ icon: 'Triangle',
+ style: {
+ shape: 'triangle',
+ fillColor: '#ffffff',
+ strokeColor: '#333333'
+ }
+ },
+ {
+ type: 'Hexagon',
+ label: '六边形',
+ icon: 'Hexagon',
+ style: {
+ shape: 'hexagon',
+ fillColor: '#ffffff',
+ strokeColor: '#333333'
+ }
+ },
+ {
+ type: 'Star',
+ label: '五角星',
+ icon: 'Star',
+ style: {
+ shape: 'star',
+ fillColor: '#ffffff',
+ strokeColor: '#333333',
+ strokeWidth: 2
+ }
+ }
+ ]
+ },
+ {
+ title: 'UML图形',
+ items: [
+ {
+ type: 'Class',
+ label: '类',
+ icon: 'ClassIcon',
+ style: {
+ shape: 'umlClass',
+ fillColor: '#ffffff',
+ strokeColor: '#333333',
+ verticalAlign: 'top',
+ align: 'left',
+ html: 1,
+ whiteSpace: 'wrap',
+ value: `
+
类名
+
+ 属性1: 类型
+
+ 方法1(): 返回类型
+
`
+ }
+ },
+ {
+ type: 'Interface',
+ label: '接口',
+ icon: 'InterfaceIcon',
+ style: {
+ shape: 'umlInterface',
+ fillColor: '#ffffff',
+ strokeColor: '#333333',
+ dashed: true,
+ verticalAlign: 'top',
+ align: 'left',
+ html: 1,
+ whiteSpace: 'wrap',
+ value: `
+
<>
+
接口名
+
+ 方法1(): 返回类型
+
`
+ }
+ },
+ {
+ type: 'Package',
+ label: '包',
+ icon: 'PackageIcon',
+ style: {
+ shape: 'package',
+ fillColor: '#ffffff',
+ strokeColor: '#333333',
+ verticalAlign: 'top',
+ align: 'left',
+ html: 1,
+ whiteSpace: 'wrap'
+ }
+ },
+ {
+ type: 'Component',
+ label: '组件',
+ icon: 'ComponentIcon',
+ style: {
+ shape: 'component',
+ fillColor: '#ffffff',
+ strokeColor: '#333333',
+ html: 1,
+ whiteSpace: 'wrap',
+ value: ``
+ }
+ },
+ {
+ type: 'Aggregation',
+ label: '聚合',
+ icon: 'AggregationIcon',
+ style: {
+ shape: 'rhombus',
+ fillColor: '#ffffff',
+ strokeColor: '#333333'
+ }
+ },
+ {
+ type: 'Composition',
+ label: '组合',
+ icon: 'CompositionIcon',
+ style: {
+ shape: 'rhombus',
+ fillColor: '#333333',
+ strokeColor: '#333333'
+ }
+ }
+ ]
+ },
+ {
+ title: '泳道',
+ items: [
+ {
+ type: 'VSwimlane',
+ label: '垂直泳道',
+ icon: 'VSwimlane',
+ size: [200, 400],
+ backgroundColor: '#f5f5f5',
+ style: {
+ shape: 'swimlane',
+ vertical: true, // 垂直泳道(设置为 false 则为水平泳道)
+ fillColor: '#f5f5f5',
+ strokeColor: '#bdbdbd',
+ startSize: 40,
+ horizontal: false,
+ separatorColor: '#bdbdbd',
+ fontColor: '#333333',
+ fontSize: 14,
+ fontFamily: 'Arial',
+ align: 'center',
+ verticalAlign: 'middle'
+ }
+ },
+ {
+ type: 'HSwimlane',
+ label: '水平泳道',
+ icon: 'HSwimlane',
+ size: [400, 200],
+ backgroundColor: '#f5f5f5',
+ style: {
+ shape: 'swimlane',
+ vertical: false, // 垂直泳道(设置为 false 则为水平泳道)
+ fillColor: '#f5f5f5',
+ strokeColor: '#bdbdbd',
+ startSize: 40,
+ horizontal: true,
+ separatorColor: '#bdbdbd',
+ fontColor: '#333333',
+ fontSize: 14,
+ fontFamily: 'Arial',
+ align: 'center',
+ verticalAlign: 'middle'
+ }
+ }
+ ]
+ },
+ {
+ title: '表格组件',
+ items: [
+ {
+ type: 'Table',
+ label: '表格',
+ icon: 'TableIcon',
+ size: [300, 200],
+ backgroundColor: '#ffffff',
+ style: {
+ fillColor: '#ffffff',
+ strokeColor: '#333333',
+ strokeWidth: 1,
+ html: 1,
+ whiteSpace: 'wrap',
+ verticalAlign: 'top',
+ align: 'left',
+ value: `
+
+
+
+ | 列1 |
+ 列2 |
+ 列3 |
+
+
+
+
+ | 数据1 |
+ 数据2 |
+ 数据3 |
+
+
+ | 数据4 |
+ 数据5 |
+ 数据6 |
+
+
+
+ `,
+ editable: true,
+ resizable: true,
+ autosize: true,
+ tableConfig: {
+ columns: [
+ { title: '列1', key: 'col1', width: 100 },
+ { title: '列2', key: 'col2', width: 100 },
+ { title: '列3', key: 'col3', width: 100 }
+ ],
+ data: [
+ { col1: '数据1', col2: '数据2', col3: '数据3' },
+ { col1: '数据4', col2: '数据5', col3: '数据6' }
+ ]
+ }
+ }
+ },
+ {
+ type: 'SimpleTable',
+ label: '简单表格',
+ icon: 'SimpleTableIcon',
+ size: [200, 120],
+ backgroundColor: '#ffffff',
+ style: {
+ shape: 'table',
+ fillColor: '#ffffff',
+ strokeColor: '#333333',
+ strokeWidth: 1,
+ html: 1,
+ whiteSpace: 'wrap',
+ verticalAlign: 'top',
+ align: 'left',
+ value: `
+
+
+ | 名称 |
+ 内容 |
+
+
+ | 说明 |
+ 描述 |
+
+
+ `,
+ editable: true,
+ resizable: true,
+ autosize: true,
+ tableConfig: {
+ columns: [
+ { title: '名称', key: 'name', width: 80 },
+ { title: '内容', key: 'content', width: 120 }
+ ],
+ data: [
+ { name: '名称', content: '内容' },
+ { name: '说明', content: '描述' }
+ ]
+ }
+ }
+ }
+ ]
+ }
+];
+
+export default {
+ categories: flowCategories
+};
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/panel/FlowAccordion.vue b/src/components/wu-flow-lib/components/panel/FlowAccordion.vue
new file mode 100644
index 000000000..077c0bb25
--- /dev/null
+++ b/src/components/wu-flow-lib/components/panel/FlowAccordion.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/property/ExportPreview.vue b/src/components/wu-flow-lib/components/property/ExportPreview.vue
new file mode 100644
index 000000000..0dbd306b3
--- /dev/null
+++ b/src/components/wu-flow-lib/components/property/ExportPreview.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
![]()
+
+
+ 背景:
+
+ 透明
+ 白色
+ 黑色
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/property/NodeSelector.vue b/src/components/wu-flow-lib/components/property/NodeSelector.vue
new file mode 100644
index 000000000..5a79127a7
--- /dev/null
+++ b/src/components/wu-flow-lib/components/property/NodeSelector.vue
@@ -0,0 +1,324 @@
+
+
+
+
+
+
+
+
+ 📁
+ {{ category.name }}
+
+
+
+
+
+
+
+
+ ✓
+ {{ node.icon }}
+ {{ node.name }}
+
+
+
+
+ 请选择左侧节点类型
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/property/PropertyPanel.vue b/src/components/wu-flow-lib/components/property/PropertyPanel.vue
new file mode 100644
index 000000000..9556f0697
--- /dev/null
+++ b/src/components/wu-flow-lib/components/property/PropertyPanel.vue
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
基础属性
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
样式属性
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
文字属性
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
请选择一个节点
+
+
+
+
+
+
diff --git a/src/components/wu-flow-lib/components/property/StyleLibrary.vue b/src/components/wu-flow-lib/components/property/StyleLibrary.vue
new file mode 100644
index 000000000..89fd4e9c5
--- /dev/null
+++ b/src/components/wu-flow-lib/components/property/StyleLibrary.vue
@@ -0,0 +1,379 @@
+
+
+
+
+
+
{{ style.name }}
+
开始
+
处理
+
结束
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/property/TableEditor.vue b/src/components/wu-flow-lib/components/property/TableEditor.vue
new file mode 100644
index 000000000..f3761cbcd
--- /dev/null
+++ b/src/components/wu-flow-lib/components/property/TableEditor.vue
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+ 添加行
+ 添加列
+ 删除行
+ 删除列
+
+
+
+
+
+
+
+
+ |
+
+ |
+
+
+
+
+ |
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/components/types.d.ts b/src/components/wu-flow-lib/components/types.d.ts
new file mode 100644
index 000000000..2b5544aa7
--- /dev/null
+++ b/src/components/wu-flow-lib/components/types.d.ts
@@ -0,0 +1,5 @@
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/constants/menuIcons.ts b/src/components/wu-flow-lib/constants/menuIcons.ts
new file mode 100644
index 000000000..05e7bfe1a
--- /dev/null
+++ b/src/components/wu-flow-lib/constants/menuIcons.ts
@@ -0,0 +1,29 @@
+export const MenuIcons = {
+ delete: ``,
+
+ copy: ``,
+
+ layers: ``,
+
+ moveUp: ``,
+
+ moveDown: ``,
+
+ group: ``,
+
+ ungroup: ``,
+
+ lock: ``,
+
+ permission: ``,
+
+ edit: ``,
+
+ color: ``,
+
+ image: ``
+};
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/events.ts b/src/components/wu-flow-lib/events.ts
new file mode 100644
index 000000000..075795d45
--- /dev/null
+++ b/src/components/wu-flow-lib/events.ts
@@ -0,0 +1,21 @@
+import mitt from 'mitt'
+
+type Events = {
+ [K in string]: unknown
+} & {
+ zoom: number
+ fit: void
+ delete: void
+ export: void
+ undo: void
+ reDo: void
+ 'edge-style': string
+ 'auto-layout': void
+ 'change-background': string
+ 'toggle-grid': boolean
+ 'change-layout': string
+ 'change-font': string
+ 'update-text-style': any
+}
+
+export const flowEventBus = mitt()
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/flow/CustomConstraintHandler.ts b/src/components/wu-flow-lib/flow/CustomConstraintHandler.ts
new file mode 100644
index 000000000..0ccae79a7
--- /dev/null
+++ b/src/components/wu-flow-lib/flow/CustomConstraintHandler.ts
@@ -0,0 +1,5 @@
+import { ConstraintHandler } from "@maxgraph/core";
+
+export class CustomConstraintHandler extends ConstraintHandler {
+
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/flow/CustomRubberBandHandler.ts b/src/components/wu-flow-lib/flow/CustomRubberBandHandler.ts
new file mode 100644
index 000000000..b234f63a6
--- /dev/null
+++ b/src/components/wu-flow-lib/flow/CustomRubberBandHandler.ts
@@ -0,0 +1,5 @@
+import { RubberBandHandler } from "@maxgraph/core";
+
+export class CustomRubberBandHandler extends RubberBandHandler {
+
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/flow/MyCustomPopupMenuHandler.ts b/src/components/wu-flow-lib/flow/MyCustomPopupMenuHandler.ts
new file mode 100644
index 000000000..66056861e
--- /dev/null
+++ b/src/components/wu-flow-lib/flow/MyCustomPopupMenuHandler.ts
@@ -0,0 +1,164 @@
+import { Graph, PopupMenuHandler } from "@maxgraph/core";
+import { MenuIcons } from '../constants/menuIcons';
+
+export class MyCustomPopupMenuHandler extends PopupMenuHandler {
+ constructor(graph: Graph) {
+ super(graph);
+ // Configures automatic expand on mouseover
+ this.autoExpand = true; // TODO autoExpand is not working
+
+ this.factoryMethod = (menu, cell, evt) => {
+ // 只对节点显示菜单
+ if (cell && cell.isVertex()) {
+ menu.addItem('删除节点', null, () => {
+ this.graph.removeCells([cell]);
+ }, null, MenuIcons.delete);
+
+ menu.addItem('复制节点', null, () => {
+ const clone = this.graph.cloneCells([cell])[0];
+ const geo = clone.getGeometry();
+ geo.x += 50;
+ geo.y += 50;
+ this.graph.addCell(clone, this.graph.getDefaultParent(), null, null, null);
+ }, null, MenuIcons.copy);
+
+ menu.addSeparator();
+
+ // 层级操作
+ const layerSubmenu = menu.addItem('层级操作', null, null, null, MenuIcons.layers);
+ menu.addItem('置于顶层', null, () => {
+ this.graph.moveCells([cell], 0, 0, true); // true 表示置于顶层
+ }, layerSubmenu, MenuIcons.moveUp);
+ menu.addItem('置于底层', null, () => {
+ this.graph.moveCells([cell], 0, 0, false); // false 表示置于底层
+ }, layerSubmenu, MenuIcons.moveDown);
+ menu.addItem('上移一层', null, () => {
+ this.graph.orderCells(true, [cell]); // true 表示下移一层
+ }, layerSubmenu, MenuIcons.moveUp);
+ menu.addItem('下移一层', null, () => {
+ this.graph.orderCells(false, [cell]); // true 表示下移一层
+ }, layerSubmenu, MenuIcons.moveDown);
+
+ menu.addSeparator();
+
+ // 组合操作
+ const groupSubmenu = menu.addItem('组合操作', null, null, null, MenuIcons.group);
+ menu.addItem('组合', null, () => {
+ const cells = this.graph.getSelectionCells();
+ if (cells.length > 1) {
+ this.graph.groupCells(null, 0, cells);
+ }
+ }, groupSubmenu, MenuIcons.group);
+ menu.addItem('解除组合', null, () => {
+ const cells = this.graph.getSelectionCells();
+ this.graph.ungroupCells(cells);
+ }, groupSubmenu, MenuIcons.ungroup);
+
+ menu.addSeparator();
+
+ // 锁定操作
+ // menu.addItem(cell.getAttribute('locked') ? '解除锁定' : '锁定', null, () => {
+ // this.graph.toggleCellStyles('locked', true, [cell]);
+ // }, null, cell.getAttribute('locked') ? '🔓' : '🔒');
+
+ // 权限操作
+ const permissionSubmenu = menu.addItem('权限设置', null, null, null, MenuIcons.lock);
+ menu.addItem('只读', null, () => {
+ cell.setAttribute('permission', 'readonly');
+ }, permissionSubmenu, MenuIcons.lock);
+ menu.addItem('可编辑', null, () => {
+ cell.setAttribute('permission', 'editable');
+ }, permissionSubmenu, MenuIcons.permission);
+ menu.addItem('完全控制', null, () => {
+ cell.setAttribute('permission', 'full');
+ }, permissionSubmenu, MenuIcons.permission);
+
+ menu.addSeparator();
+
+ // 编辑操作
+ const editSubmenu = menu.addItem('编辑', null, null, null, MenuIcons.edit);
+ menu.addItem('修改文本', null, () => {
+ // 触发文本编辑
+ this.graph.startEditingAtCell(cell);
+ }, editSubmenu, MenuIcons.edit);
+
+ menu.addItem('修改颜色', null, () => {
+ // 这里可以触发颜色选择事件
+ console.log('修改颜色');
+ }, editSubmenu, MenuIcons.color);
+
+ // 在现有菜单项中添加插入图片选项
+ menu.addItem('插入图片', null, () => {
+ // 创建一个隐藏的文件输入框
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.accept = 'image/*';
+ input.style.display = 'none';
+ document.body.appendChild(input);
+
+ input.onchange = (e: Event) => {
+ const target = e.target as HTMLInputElement;
+ const file = target.files?.[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ const imageUrl = event.target?.result as string;
+ // 在当前位置插入图片
+ const geo = cell.getGeometry().clone();
+ const imageCell = this.graph.insertVertex(
+ this.graph.getDefaultParent(),
+ null,
+ '',
+ geo.x + 50,
+ geo.y + 50,
+ 100, // 默认宽度
+ 100, // 默认高度
+ {
+ shape: 'image',
+ image: imageUrl,
+ imageAlign: 'center',
+ imageVerticalAlign: 'middle',
+ resizable: true
+ }
+ );
+ this.graph.setSelectionCell(imageCell);
+ };
+ reader.readAsDataURL(file);
+ }
+ document.body.removeChild(input);
+ };
+
+ input.click();
+ }, null, MenuIcons.image);
+ }
+ };
+ }
+
+ // 重写创建菜单项的方法来添加自定义样式
+ protected createMenuItem(title: string, image: string | null, funct: (() => void) | null, parent: any, icon?: string): HTMLElement {
+ const item = document.createElement('div');
+ item.className = 'custom-menu-item';
+
+ if (icon) {
+ const iconSpan = document.createElement('span');
+ iconSpan.className = 'menu-icon';
+ iconSpan.innerHTML = icon;
+ item.appendChild(iconSpan);
+ }
+
+ const labelSpan = document.createElement('span');
+ labelSpan.className = 'menu-label';
+ labelSpan.textContent = title;
+ item.appendChild(labelSpan);
+
+ if (parent) {
+ item.classList.add('has-submenu');
+ }
+
+ if (funct) {
+ item.onclick = funct;
+ }
+
+ return item;
+ }
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/flow/WuConnectionHandler.ts b/src/components/wu-flow-lib/flow/WuConnectionHandler.ts
new file mode 100644
index 000000000..0030a0099
--- /dev/null
+++ b/src/components/wu-flow-lib/flow/WuConnectionHandler.ts
@@ -0,0 +1,8 @@
+import { ConnectionHandler, Graph } from "@maxgraph/core";
+
+export class WuConnectionHandler extends ConnectionHandler {
+ constructor(graph: Graph) {
+ console.log("WuConnectionHandler constructor");
+ super(graph);
+ }
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/flow/WuCustomGeometryClass.ts b/src/components/wu-flow-lib/flow/WuCustomGeometryClass.ts
new file mode 100644
index 000000000..acb6fdee1
--- /dev/null
+++ b/src/components/wu-flow-lib/flow/WuCustomGeometryClass.ts
@@ -0,0 +1,6 @@
+import { Geometry, ConnectionConstraint, Point } from "@maxgraph/core";
+
+export class WuCustomGeometryClass extends Geometry {
+ // Defines the default constraints for the vertices
+ constraints = [new ConnectionConstraint(new Point(0.25, 0), true), new ConnectionConstraint(new Point(0.5, 0), true), new ConnectionConstraint(new Point(0.75, 0), true), new ConnectionConstraint(new Point(0, 0.25), true), new ConnectionConstraint(new Point(0, 0.5), true), new ConnectionConstraint(new Point(0, 0.75), true), new ConnectionConstraint(new Point(1, 0.25), true), new ConnectionConstraint(new Point(1, 0.5), true), new ConnectionConstraint(new Point(1, 0.75), true), new ConnectionConstraint(new Point(0.25, 1), true), new ConnectionConstraint(new Point(0.5, 1), true), new ConnectionConstraint(new Point(0.75, 1), true)];
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/flow/WuCustomGraph.ts b/src/components/wu-flow-lib/flow/WuCustomGraph.ts
new file mode 100644
index 000000000..7c7753812
--- /dev/null
+++ b/src/components/wu-flow-lib/flow/WuCustomGraph.ts
@@ -0,0 +1,405 @@
+import { Graph, GraphDataModel, CellState, GraphPluginConstructor, InternalEvent, Cell, RectangleShape } from "@maxgraph/core";
+import { WuCustomGeometryClass } from "./WuCustomGeometryClass";
+import { WuConnectionHandler } from "./WuConnectionHandler";
+import { ElMessage } from "element-plus";
+import { flowEventBus } from '../events';
+import { CustomPopupMenuHandler } from '../handlers/CustomPopupMenuHandler';
+import { KeyHandler } from '../handlers/KeyHandler';
+import { WuConstraintHandler } from "../handlers/WuConstraintHandler";
+import { Codec, utils, ModelXmlSerializer } from "@maxgraph/core";
+
+export class WuCustomGraph extends Graph {
+ private copiedStyle: any = null;
+ private isStyleBrushActive = false;
+ private keyHandler: KeyHandler;
+
+ constructor(container: HTMLElement, model: GraphDataModel, plugins: GraphPluginConstructor[]) {
+ super(container, model, plugins);
+ this.initDoubleClickHandler();
+ this.initClickHandler();
+ this.initStyleBrush();
+ this.initEdgeAnimation();
+ this.initConnectorShape();
+ this.initPopupMenu();
+ this.keyHandler = new KeyHandler(this);
+
+ // // 添加选中状态变化监听
+ this.addListener(InternalEvent.CLICK, (sender, evt) => {
+ const cells = this.getSelectionCells();
+ flowEventBus.emit('selection-changed', cells);
+ console.log('select....')
+ flowEventBus.emit('node-selected', cells);
+ });
+
+ flowEventBus.on('apply-graph-style', (style: any) => {
+ this.applyGraphStyle(style);
+ });
+
+ this.initTableEditor();
+
+ // 注册表格形状
+ this.getStylesheet().putCellStyle('table', {
+ shape: 'html',
+ whiteSpace: 'wrap',
+ verticalAlign: 'top',
+ align: 'left',
+ overflow: 'visible'
+ });
+
+ // 监听对齐事件
+ flowEventBus.on('align-cells', (type: string) => {
+ this.alignCells(type);
+ });
+ }
+
+ private initDoubleClickHandler() {
+ // 监听双击事件
+ this.addListener(InternalEvent.CLICK, () => {
+ flowEventBus.emit('hide-text-toolbar');
+ });
+ this.addListener(InternalEvent.DOUBLE_CLICK, (sender: any, evt: any) => {
+ flowEventBus.emit('hide-text-toolbar');
+ const cell = evt.getProperty('cell');
+ if (cell && cell.isVertex()) {
+ // 获取节点的值(标签)
+ const label = cell.getValue() || '未命名节点';
+ // 显示提示信息
+ ElMessage({
+ message: `双击了节点: ${label}`,
+ type: 'info',
+ duration: 2000
+ });
+ }
+ });
+ }
+
+ private initClickHandler() {
+ this.addListener(InternalEvent.DOUBLE_CLICK, (sender: any, evt: any) => {
+ // 先隐藏工具栏
+ flowEventBus.emit('hide-text-toolbar');
+ const cell = evt.getProperty('cell');
+ if (cell && cell.isVertex()) {
+ // 获取点击位置
+ const x = evt.getProperty('event').clientX;
+ const y = evt.getProperty('event').clientY;
+
+ // 获取当前节点的样式
+ const style = cell.getStyle();
+
+ // 延迟一下再显示工具栏,避免闪烁
+ setTimeout(() => {
+ flowEventBus.emit('show-text-toolbar', {
+ position: { left: `${x}px`, top: `${y + 20}px` },
+ cell,
+ style: {
+ fontFamily: style.fontFamily || 'Arial',
+ fontSize: style.fontSize || 14,
+ fontColor: style.fontColor || '#000000',
+ fontWeight: style.fontWeight || 'normal',
+ textDecoration: style.textDecoration || 'none'
+ }
+ });
+ }, 100);
+ }
+ });
+ }
+
+ private initStyleBrush() {
+ flowEventBus.on('activate-style-brush', (style: any) => {
+ this.isStyleBrushActive = true;
+ this.copiedStyle = style;
+ });
+
+ flowEventBus.on('deactivate-style-brush', () => {
+ this.isStyleBrushActive = false;
+ this.copiedStyle = null;
+ });
+
+ this.addListener(InternalEvent.CLICK, (sender: any, evt: any) => {
+ if (this.isStyleBrushActive && this.copiedStyle) {
+ const cell = evt.getProperty('cell');
+ if (cell && cell.isVertex()) {
+ // 应用复制的样式
+ flowEventBus.emit('update-text-style', this.copiedStyle);
+ // 通知样式已应用
+ flowEventBus.emit('style-brush-applied');
+ this.isStyleBrushActive = false;
+ this.copiedStyle = null;
+ }
+ }
+ });
+ }
+
+
+ private initEdgeAnimation() {
+ this.addListener(InternalEvent.CLICK, (sender: any, evt: any) => {
+ const cell = evt.getProperty('cell');
+ if (cell && cell.isEdge()) {
+ const state = this.view.getState(cell);
+ if (state) {
+ // 创建动画效果
+ const animate = () => {
+ state.shape.node.style.strokeDasharray = '5,5';
+ state.shape.node.style.animation = 'flowLine 1s linear infinite';
+ };
+
+ // 添加动画样式
+ const style = document.createElement('style');
+ style.textContent = `
+ @keyframes flowLine {
+ from {
+ stroke-dashoffset: 10;
+ }
+ to {
+ stroke-dashoffset: 0;
+ }
+ }
+ `;
+ document.head.appendChild(style);
+
+ // 应用动画
+ animate();
+
+ // 3秒后移除动画
+ setTimeout(() => {
+ if (state?.shape?.node) {
+ state.shape.node.style.strokeDasharray = '';
+ state.shape.node.style.animation = '';
+ }
+ }, 3000);
+ }
+ }
+ });
+ }
+
+ private initConnectorShape() {
+ this.getStylesheet().putCellStyle('connector', {
+ shape: 'ellipse',
+ perimeter: 'ellipsePerimeter',
+ fillColor: '#fff',
+ strokeColor: '#5b8ffa',
+ strokeWidth: 2,
+ image: `data:image/svg+xml,${encodeURIComponent(`
+
+ `)}`,
+ imageWidth: 24,
+ imageHeight: 24,
+ imageAlign: 'center',
+ verticalAlign: 'middle'
+ });
+ }
+
+ private initPopupMenu() {
+ new CustomPopupMenuHandler();
+ }
+
+ private initTableEditor() {
+ this.addListener(InternalEvent.DOUBLE_CLICK, (sender: any, evt: any) => {
+ const cell = evt.getProperty('cell');
+ if (cell && cell.isVertex() && cell.getStyle()['shape'] === 'table') {
+ const tableConfig = cell.getStyle()['tableConfig'];
+ flowEventBus.emit('edit-table', {
+ cell,
+ config: tableConfig
+ });
+ }
+ });
+
+ flowEventBus.on('table-updated', ({ cell, html, config }) => {
+ this.model.beginUpdate();
+ try {
+ const style = { ...cell.getStyle() };
+ style.value = html;
+ style.tableConfig = config;
+ this.setCellStyle(style, cell);
+ } finally {
+ this.model.endUpdate();
+ }
+ });
+ }
+
+ getAllConnectionConstraints = (terminal: CellState | null, _source: boolean) => {
+ // Overridden to define per-geometry connection points
+ return (terminal?.cell?.geometry as WuCustomGeometryClass)?.constraints ?? null;
+ };
+ createConnectionHandler() {
+ const r = new WuConnectionHandler(this);
+ r.constraintHandler = new WuConstraintHandler(this);
+ return r;
+ }
+
+ handleStyleApply({ style, cells }: { style: any, cells: any[] | null }) {
+ const model = this.model;
+ model.beginUpdate();
+ console.log(model.cells);
+ try {
+ if (cells) {
+ // 应用到指定的节点
+ cells.forEach(cell => {
+ if (cell.isVertex()) {
+ // 应用节点样式
+ this.setCellStyle({
+ fillColor: style.nodeStyle.backgroundColor,
+ strokeColor: style.nodeStyle.borderColor,
+ fontColor: style.nodeStyle.color
+ }, cell);
+ } else if (cell.isEdge()) {
+ // 应用边的样式
+ this.setCellStyle({
+ strokeColor: style.edgeStyle.strokeColor,
+ strokeWidth: style.edgeStyle.strokeWidth
+ }, cell);
+ }
+ });
+ } else {
+ // 应用到所有节点
+ const allCells = this.model.cells
+ Object.values(allCells).forEach(cell => {
+ if (cell.isVertex()) {
+ this.setCellStyle({
+ fillColor: style.nodeStyle.backgroundColor,
+ strokeColor: style.nodeStyle.borderColor,
+ fontColor: style.nodeStyle.color
+ }, [cell]);
+ } else if (cell.isEdge()) {
+ this.setCellStyle({
+ strokeColor: style.edgeStyle.strokeColor,
+ strokeWidth: style.edgeStyle.strokeWidth
+ }, [cell]);
+ }
+ });
+ }
+ } finally {
+ model.endUpdate();
+ }
+ }
+
+ private applyGraphStyle(style: any) {
+ let cells = this.getSelectionCells();
+ console.log(cells);
+ if (cells.length === 0) {
+ cells = Object.values(this.model.cells);
+ }
+ if (!cells.length) {
+ return;
+ }
+ this.model.beginUpdate();
+ try {
+ cells.forEach(cell => {
+ if (cell.isVertex()) {
+ const newStyle = {
+ ...cell.getStyle(),
+ fillColor: style.nodeStyle.backgroundColor,
+ strokeColor: style.nodeStyle.borderColor,
+ fontColor: style.nodeStyle.color
+ };
+ this.setCellStyle(newStyle, [cell]);
+ } else if (cell.isEdge()) {
+ const newStyle = {
+ ...cell.getStyle(),
+ strokeColor: style.edgeStyle.strokeColor,
+ strokeWidth: style.edgeStyle.strokeWidth
+ };
+ this.setCellStyle(newStyle, [cell]);
+ }
+ });
+ } finally {
+ this.model.endUpdate();
+ }
+ }
+
+ getGraphData() {
+
+ const serializer = new ModelXmlSerializer(this.model)
+ return serializer.export()
+
+ }
+
+ loadGraphData(data: string) {
+ const model = new GraphDataModel();
+ const serializer = new ModelXmlSerializer(model);
+ serializer.import(data);
+ }
+
+ getGraphSvg() {
+ const scale = 1
+ const bounds = this.getGraphBounds()
+
+ return ``
+ }
+
+ // 对齐功能
+ alignCells = (type: string) => {
+ const cells = this.getSelectionCells();
+ if (cells.length < 2) return;
+
+ this.model.beginUpdate();
+ try {
+ const bounds = cells.map(cell => this.getBoundingBox([cell]));
+ const first = bounds[0];
+
+ switch (type) {
+ case 'left':
+ bounds.slice(1).forEach((bound, i) => {
+ this.moveCells([cells[i + 1]], first.x - bound.x, 0);
+ });
+ break;
+ case 'center':
+ const centerX = first.x + first.width / 2;
+ bounds.slice(1).forEach((bound, i) => {
+ this.moveCells([cells[i + 1]], centerX - (bound.x + bound.width / 2), 0);
+ });
+ break;
+ case 'right':
+ bounds.slice(1).forEach((bound, i) => {
+ this.moveCells([cells[i + 1]], (first.x + first.width) - (bound.x + bound.width), 0);
+ });
+ break;
+ case 'top':
+ bounds.slice(1).forEach((bound, i) => {
+ this.moveCells([cells[i + 1]], 0, first.y - bound.y);
+ });
+ break;
+ case 'middle':
+ const centerY = first.y + first.height / 2;
+ bounds.slice(1).forEach((bound, i) => {
+ this.moveCells([cells[i + 1]], 0, centerY - (bound.y + bound.height / 2));
+ });
+ break;
+ case 'bottom':
+ bounds.slice(1).forEach((bound, i) => {
+ this.moveCells([cells[i + 1]], 0, (first.y + first.height) - (bound.y + bound.height));
+ });
+ break;
+ case 'distributeHorizontally':
+ const sortedX = [...cells].sort((a, b) => this.getBoundingBox([a]).x - this.getBoundingBox([b]).x);
+ const totalWidth = this.getBoundingBox([sortedX[sortedX.length - 1]]).x - this.getBoundingBox([sortedX[0]]).x;
+ const spacing = totalWidth / (cells.length - 1);
+ sortedX.slice(1, -1).forEach((cell, i) => {
+ const targetX = this.getBoundingBox([sortedX[0]]).x + spacing * (i + 1);
+ const currentX = this.getBoundingBox([cell]).x;
+ this.moveCells([cell], targetX - currentX, 0);
+ });
+ break;
+ case 'distributeVertically':
+ const sortedY = [...cells].sort((a, b) => this.getBoundingBox([a]).y - this.getBoundingBox([b]).y);
+ const totalHeight = this.getBoundingBox([sortedY[sortedY.length - 1]]).y - this.getBoundingBox([sortedY[0]]).y;
+ const spacingY = totalHeight / (cells.length - 1);
+ sortedY.slice(1, -1).forEach((cell, i) => {
+ const targetY = this.getBoundingBox([sortedY[0]]).y + spacingY * (i + 1);
+ const currentY = this.getBoundingBox([cell]).y;
+ this.moveCells([cell], 0, targetY - currentY);
+ });
+ break;
+ }
+ } finally {
+ this.model.endUpdate();
+ }
+ }
+}
diff --git a/src/components/wu-flow-lib/flow/WuSelectHander.ts b/src/components/wu-flow-lib/flow/WuSelectHander.ts
new file mode 100644
index 000000000..0e71e3045
--- /dev/null
+++ b/src/components/wu-flow-lib/flow/WuSelectHander.ts
@@ -0,0 +1,14 @@
+import { SelectionHandler, Graph, Cell, InternalMouseEvent } from "@maxgraph/core";
+
+export class WuSelectHander extends SelectionHandler {
+ constructor(graph: Graph) {
+ console.log("WuSelectHander constructor");
+ super(graph);
+ }
+
+ selectCellForEvent(cell: Cell, me: InternalMouseEvent): Cell {
+ console.log("WuSelectHander selectCellForEvent");
+ return cell;
+ }
+
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/handlers/CanvasEventHandler.ts b/src/components/wu-flow-lib/handlers/CanvasEventHandler.ts
new file mode 100644
index 000000000..acbac31fa
--- /dev/null
+++ b/src/components/wu-flow-lib/handlers/CanvasEventHandler.ts
@@ -0,0 +1,414 @@
+import { Graph,ModelXmlSerializer} from "@maxgraph/core";
+import { flowEventBus } from "../events";
+import { WuFlowAction } from "../utils/flow-utils";
+import { ElMessage } from 'element-plus';
+import { ref } from 'vue';
+import { HierarchicalLayout, CircleLayout, FastOrganicLayout } from "@maxgraph/core";
+
+export class CanvasEventHandler {
+ private showGrid = ref(true);
+ private showTextToolbar = ref(false);
+ private toolbarPosition = ref({ top: '0px', left: '0px' });
+ private currentNodeStyle = ref({
+ fontFamily: 'Arial',
+ fontSize: 14,
+ fontColor: '#000000',
+ fontWeight: 'normal',
+ textDecoration: 'none'
+ });
+
+ constructor(
+ private wuFlowAction: WuFlowAction,
+ private graph: Graph | null,
+ private canvasRef: HTMLElement | undefined
+ ) {
+ this.initEvents();
+ }
+
+ private initEvents() {
+ // 注册事件监听
+ flowEventBus.on("zoom", this.handleZoom);
+ flowEventBus.on("fit", this.handleFit);
+ flowEventBus.on("delete", this.handleDelete);
+ flowEventBus.on("export", this.handleExport);
+ flowEventBus.on("undo", this.handleUndo);
+ flowEventBus.on("reDo", this.handleReDo);
+ flowEventBus.on("edge-style", this.handleEdgeStyle);
+ flowEventBus.on("auto-layout", this.handleAutoLayout);
+ flowEventBus.on('change-background', this.handleBackgroundChange);
+ flowEventBus.on('toggle-grid', this.handleToggleGrid);
+ flowEventBus.on('change-layout', this.handleLayoutChange);
+ flowEventBus.on('change-font', this.handleFontChange);
+ flowEventBus.on('change-node-color', this.handleNodeColorChange);
+ flowEventBus.on('insert-image', this.handleInsertImage);
+ flowEventBus.on('change-border-color', this.handleBorderColor);
+ flowEventBus.on('change-border-style', this.handleBorderStyle);
+ flowEventBus.on('change-border-width', this.handleBorderWidth);
+ flowEventBus.on('file-new', this.handleNewFile);
+ flowEventBus.on('file-open', this.handleOpenFile);
+ flowEventBus.on('file-save', this.handleSaveFile);
+ flowEventBus.on('file-export-image', this.handleExportImage);
+ flowEventBus.on('file-export-json', this.handleExportJson);
+ flowEventBus.on('cut', this.handleCut);
+ flowEventBus.on('copy', this.handleCopy);
+ flowEventBus.on('paste', this.handlePaste);
+ flowEventBus.on('select-all', this.handleSelectAll);
+ flowEventBus.on('invert-select', this.handleInvertSelect);
+ flowEventBus.on('cancel-select', this.handleCancelSelect);
+ flowEventBus.on('zoom-in', this.handleZoomIn);
+ flowEventBus.on('zoom-out', this.handleZoomOut);
+ flowEventBus.on('toggle-grid', this.handleToggleGrid);
+ flowEventBus.on('toggle-minimap', this.handleToggleMinimap);
+ flowEventBus.on('change-background-color', this.handleBackgroundColor);
+ flowEventBus.on('change-grid-size', this.handleGridSize);
+ flowEventBus.on('change-canvas-size', this.handleCanvasSize);
+ flowEventBus.on('hierarchical-layout', this.handleHierarchicalLayout);
+ flowEventBus.on('circle-layout', this.handleCircleLayout);
+ flowEventBus.on('organic-layout', this.handleOrganicLayout);
+ flowEventBus.on('bring-to-front', this.handleBringToFront);
+ flowEventBus.on('send-to-back', this.handleSendToBack);
+ }
+
+ public destroy() {
+ // 清理所有事件监听
+ flowEventBus.off("zoom", this.handleZoom);
+ flowEventBus.off("fit", this.handleFit);
+ flowEventBus.off("delete", this.handleDelete);
+ flowEventBus.off("export", this.handleExport);
+ flowEventBus.off("undo", this.handleUndo);
+ flowEventBus.off("reDo", this.handleReDo);
+ flowEventBus.off("edge-style", this.handleEdgeStyle);
+ flowEventBus.off("auto-layout", this.handleAutoLayout);
+ flowEventBus.off('change-background', this.handleBackgroundChange);
+ flowEventBus.off('toggle-grid', this.handleToggleGrid);
+ flowEventBus.off('change-layout', this.handleLayoutChange);
+ flowEventBus.off('change-font', this.handleFontChange);
+ flowEventBus.off('change-node-color', this.handleNodeColorChange);
+ flowEventBus.off('insert-image', this.handleInsertImage);
+ flowEventBus.off('change-border-color', this.handleBorderColor);
+ flowEventBus.off('change-border-style', this.handleBorderStyle);
+ flowEventBus.off('change-border-width', this.handleBorderWidth);
+ flowEventBus.off('file-new', this.handleNewFile);
+ flowEventBus.off('file-open', this.handleOpenFile);
+ flowEventBus.off('file-save', this.handleSaveFile);
+ flowEventBus.off('file-export-image', this.handleExportImage);
+ flowEventBus.off('file-export-json', this.handleExportJson);
+ flowEventBus.off('cut', this.handleCut);
+ flowEventBus.off('copy', this.handleCopy);
+ flowEventBus.off('paste', this.handlePaste);
+ flowEventBus.off('select-all', this.handleSelectAll);
+ flowEventBus.off('invert-select', this.handleInvertSelect);
+ flowEventBus.off('cancel-select', this.handleCancelSelect);
+ flowEventBus.off('zoom-in', this.handleZoomIn);
+ flowEventBus.off('zoom-out', this.handleZoomOut);
+ flowEventBus.off('toggle-grid', this.handleToggleGrid);
+ flowEventBus.off('toggle-minimap', this.handleToggleMinimap);
+ flowEventBus.off('change-background-color', this.handleBackgroundColor);
+ flowEventBus.off('change-grid-size', this.handleGridSize);
+ flowEventBus.off('change-canvas-size', this.handleCanvasSize);
+ flowEventBus.off('hierarchical-layout', this.handleHierarchicalLayout);
+ flowEventBus.off('circle-layout', this.handleCircleLayout);
+ flowEventBus.off('organic-layout', this.handleOrganicLayout);
+ flowEventBus.off('bring-to-front', this.handleBringToFront);
+ flowEventBus.off('send-to-back', this.handleSendToBack);
+ }
+
+ private handleZoom = (scale: number) => {
+ this.graph?.zoomTo(scale);
+ };
+
+ private handleFit = () => {
+ this.graph?.fit();
+ };
+
+ private handleDelete = () => {
+ const cells = this.graph?.getSelectionCells();
+ if (cells?.length) {
+ this.graph?.removeCells(cells);
+ }
+ };
+
+ private handleExport = () => {
+ const exportedXml = this.wuFlowAction.getXmlSerializer();
+ sessionStorage.setItem("flow-canvas-data", exportedXml);
+ };
+
+ private handleUndo = () => {
+ // 撤销操作
+ };
+
+ private handleReDo = () => {
+ // 重做操作
+ };
+
+ private handleEdgeStyle = (style: string) => {
+ this.wuFlowAction.getDefaultEdgeStyle().edgeStyle = style;
+ };
+
+ private handleAutoLayout = () => {
+ this.wuFlowAction.handleLayoutChange("hierarchical");
+ };
+
+ private handleBackgroundChange = (color: string) => {
+ if (this.canvasRef) {
+ this.canvasRef.style.backgroundColor = color;
+ }
+ };
+
+ private handleFontChange = (fontFamily: string) => {
+ this.wuFlowAction.changeFont(fontFamily);
+ };
+
+ private handleNodeColorChange = (color: string) => {
+ this.wuFlowAction.changeNodeColor(color);
+ };
+
+ private handleInsertImage = (imageUrl: string) => {
+ if (this.canvasRef) {
+ const container = this.canvasRef.getBoundingClientRect();
+ const x = container.width / 2;
+ const y = container.height / 2;
+ this.wuFlowAction.insertImageNode(x, y, imageUrl);
+ }
+ };
+
+ private handleBorderColor = (color: string) => {
+ this.wuFlowAction.changeBorderColor(color);
+ };
+
+ private handleBorderStyle = (style: string) => {
+ this.wuFlowAction.changeBorderStyle(style);
+ };
+
+ private handleBorderWidth = (width: number) => {
+ this.wuFlowAction.changeBorderWidth(width);
+ };
+
+ private handleToggleGrid = (show: boolean) => {
+ this.showGrid.value = show;
+ if (this.graph) {
+ this.graph.setGridEnabled(show);
+ this.graph.setGridVisible(show);
+ }
+ };
+
+ private handleLayoutChange = (type: string) => {
+ this.wuFlowAction.handleLayoutChange(type);
+ };
+
+ private handleNewFile = () => {
+ if (this.graph) {
+ this.graph.removeCells(this.graph.getChildCells(this.graph.getDefaultParent()));
+ }
+ };
+
+ private handleOpenFile = () => {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.accept = '.json';
+ input.onchange = (e: Event) => {
+ const file = (e.target as HTMLInputElement).files?.[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ try {
+ const data = JSON.parse(e.target?.result as string);
+ this.wuFlowAction?.loadGraphData(data);
+ } catch (error) {
+ ElMessage.error('文件格式错误');
+ }
+ };
+ reader.readAsText(file);
+ }
+ };
+ input.click();
+ };
+
+ private handleSaveFile = () => {
+ if (this.graph) {
+ const data = this.wuFlowAction?.getXmlSerializer();
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = 'flow.json';
+ link.click();
+ URL.revokeObjectURL(url);
+ }
+ };
+
+ private handleExportImage = () => {
+ if (this.graph) {
+ const canvas = document.createElement('canvas');
+ const bounds = this.graph.getGraphBounds();
+ const scale = 1;
+ canvas.width = bounds.width * scale;
+ canvas.height = bounds.height * scale;
+
+ const ctx = canvas.getContext('2d');
+ if (ctx) {
+ ctx.scale(scale, scale);
+ const svgString = this.wuFlowAction?.getGraphSvg();
+ const img = new Image();
+ img.onload = () => {
+ ctx.drawImage(img, 0, 0);
+ flowEventBus.emit('export-preview', canvas.toDataURL('image/png'));
+ };
+ img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgString)));
+ }
+ }
+ };
+
+ private handleExportJson = () => {
+ if (this.graph) {
+ const data = this.wuFlowAction?.getXmlSerializer();
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = 'flow.json';
+ link.click();
+ URL.revokeObjectURL(url);
+ }
+ };
+
+ private handleCut = () => {
+ if (this.graph) {
+ const cells = this.graph.getSelectionCells();
+ if (cells?.length) {
+ this.handleCopy();
+ this.graph.removeCells(cells);
+ }
+ }
+ };
+
+ private handleCopy = () => {
+ if (this.graph) {
+ const cells = this.graph.getSelectionCells();
+ if (cells?.length) {
+ const encoder = new ModelXmlSerializer(this.graph.model);
+ const cellsXml = encoder.encode(cells);
+ navigator.clipboard.writeText(cellsXml);
+ }
+ }
+ };
+
+ private handlePaste = async () => {
+ if (this.graph) {
+ try {
+ const text = await navigator.clipboard.readText();
+ const decoder = new ModelXmlSerializer(this.graph.model);
+ const cells = decoder.decode(text);
+ if (cells?.length) {
+ this.graph.importCells(cells);
+ }
+ } catch (error) {
+ console.error('粘贴失败:', error);
+ }
+ }
+ };
+
+ private handleSelectAll = () => {
+ if (this.graph) {
+ const parent = this.graph.getDefaultParent();
+ const children = this.graph.getChildVertices(parent);
+ this.graph.setSelectionCells(children);
+ }
+ };
+
+ private handleInvertSelect = () => {
+ if (this.graph) {
+ const parent = this.graph.getDefaultParent();
+ const allCells = this.graph.getChildVertices(parent);
+ const selectedCells = this.graph.getSelectionCells();
+ const invertedCells = allCells.filter(cell => !selectedCells.includes(cell));
+ this.graph.setSelectionCells(invertedCells);
+ }
+ };
+
+ private handleCancelSelect = () => {
+ if (this.graph) {
+ this.graph.clearSelection();
+ }
+ };
+
+ private handleZoomIn = () => {
+ if (this.graph) {
+ const scale = this.graph.view.scale;
+ this.graph.zoomTo(scale * 1.2);
+ }
+ };
+
+ private handleZoomOut = () => {
+ if (this.graph) {
+ const scale = this.graph.view.scale;
+ this.graph.zoomTo(scale * 0.8);
+ }
+ };
+
+ private handleToggleMinimap = (show: boolean) => {
+ flowEventBus.emit('show-graph-outline', show);
+ };
+
+ private handleBackgroundColor = (color: string) => {
+ if (this.canvasRef) {
+ this.canvasRef.style.backgroundColor = color;
+ }
+ };
+
+ private handleGridSize = (size: number) => {
+ if (this.graph) {
+ this.graph.gridSize = size;
+ // 刷新网格显示
+ const isGridVisible = this.showGrid.value;
+ this.graph.setGridEnabled(isGridVisible);
+ this.graph.setGridVisible(isGridVisible);
+ }
+ };
+
+ private handleCanvasSize = (size: { width: number; height: number }) => {
+ if (this.canvasRef) {
+ this.canvasRef.style.width = `${size.width}px`;
+ this.canvasRef.style.height = `${size.height}px`;
+ // 通知图形需要重新布局
+ this.graph?.fit();
+ }
+ };
+
+ private handleHierarchicalLayout = () => {
+ if (this.graph) {
+ const layout = new HierarchicalLayout(this.graph);
+ layout.execute(this.graph.getDefaultParent());
+ }
+ };
+
+ private handleCircleLayout = () => {
+ if (this.graph) {
+ const layout = new CircleLayout(this.graph);
+ layout.execute(this.graph.getDefaultParent());
+ }
+ };
+
+ private handleOrganicLayout = () => {
+ if (this.graph) {
+ const layout = new FastOrganicLayout(this.graph);
+ layout.execute(this.graph.getDefaultParent());
+ }
+ };
+
+ private handleBringToFront = () => {
+ if (this.graph) {
+ const cells = this.graph.getSelectionCells();
+ this.graph.orderCells(true, cells);
+ }
+ };
+
+ private handleSendToBack = () => {
+ if (this.graph) {
+ const cells = this.graph.getSelectionCells();
+ this.graph.orderCells(false, cells);
+ }
+ };
+
+ // ... 其他事件处理方法
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/handlers/CustomPopupMenuHandler.ts b/src/components/wu-flow-lib/handlers/CustomPopupMenuHandler.ts
new file mode 100644
index 000000000..5018b17de
--- /dev/null
+++ b/src/components/wu-flow-lib/handlers/CustomPopupMenuHandler.ts
@@ -0,0 +1,43 @@
+import { MaxPopupMenu, Cell, Graph } from '@maxgraph/core';
+import { flowEventBus } from '../events';
+
+export class CustomPopupMenuHandler extends MaxPopupMenu {
+
+
+ protected addPopupMenuItems(menu: HTMLElement, cell: Cell, evt: MouseEvent): void {
+ if (cell && cell.isVertex()) {
+ // 删除选项
+ const deleteItem = document.createElement('div');
+ deleteItem.className = 'menu-item';
+ deleteItem.innerHTML = '删除';
+ deleteItem.onclick = () => {
+ flowEventBus.emit('delete');
+ this.hideMenu();
+ };
+ menu.appendChild(deleteItem);
+
+ // 复制选项
+ const copyItem = document.createElement('div');
+ copyItem.className = 'menu-item';
+ copyItem.innerHTML = '复制';
+ copyItem.onclick = () => {
+ const graph = this.getGraph();
+ if (graph) {
+ const clone = graph.cloneCells([cell])[0];
+ const geo = clone.getGeometry();
+ geo.x += 50;
+ geo.y += 50;
+ graph.addCell(clone);
+ }
+ this.hideMenu();
+ };
+ menu.appendChild(copyItem);
+ }
+ }
+
+ protected createMenu(): HTMLElement {
+ const menu = document.createElement('div');
+ menu.className = 'graph-context-menu';
+ return menu;
+ }
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/handlers/KeyHandler.ts b/src/components/wu-flow-lib/handlers/KeyHandler.ts
new file mode 100644
index 000000000..8d00c5e56
--- /dev/null
+++ b/src/components/wu-flow-lib/handlers/KeyHandler.ts
@@ -0,0 +1,121 @@
+import { Graph, Cell } from '@maxgraph/core';
+
+export class KeyHandler {
+ private graph: Graph;
+ private copiedCells: Cell[] = [];
+ private pasteOffset = 30; // 粘贴时的偏移量
+
+ constructor(graph: Graph) {
+ this.graph = graph;
+ this.init();
+ }
+
+ private init() {
+ document.addEventListener('keydown', this.handleKeyDown.bind(this));
+ }
+
+ private handleKeyDown(event: KeyboardEvent) {
+ // Delete 键删除选中元素
+ if (event.key === 'Delete') {
+ this.handleDelete(event);
+ }
+
+ // Ctrl + C 复制
+ if (event.ctrlKey && event.key === 'c') {
+ this.handleCopy(event);
+ }
+
+ // Ctrl + V 粘贴
+ if (event.ctrlKey && event.key === 'v') {
+ this.handlePaste(event);
+ }
+
+ // Ctrl + Z 撤销
+ if (event.ctrlKey && event.key === 'z') {
+ this.handleUndo(event);
+ }
+
+ // Ctrl + Y 重做
+ if (event.ctrlKey && event.key === 'y') {
+ this.handleRedo(event);
+ }
+ }
+
+ private handleDelete(event: KeyboardEvent) {
+ const cells = this.graph.getSelectionCells();
+ if (cells && cells.length > 0) {
+ event.preventDefault();
+ this.graph.model.beginUpdate();
+ try {
+ this.graph.removeCells(cells);
+ this.graph.clearSelection();
+ } finally {
+ this.graph.model.endUpdate();
+ }
+ }
+ }
+
+ private handleCopy(event: KeyboardEvent) {
+ const cells = this.graph.getSelectionCells();
+ if (cells && cells.length > 0) {
+ event.preventDefault();
+ // 存储选中的单元格
+ this.copiedCells = cells;
+
+ // 可选:显示复制成功提示
+ console.log('已复制', cells.length, '个元素');
+ }
+ }
+
+ private handlePaste(event: KeyboardEvent) {
+ if (this.copiedCells.length > 0) {
+ event.preventDefault();
+ this.graph.model.beginUpdate();
+ try {
+ // 克隆所有复制的单元格
+ const clones = this.graph.cloneCells(this.copiedCells);
+
+ // 为每个克隆的单元格添加偏移
+ clones.forEach(clone => {
+ if (clone.isVertex()) {
+ const geo = clone.getGeometry();
+ if (geo) {
+ geo.x += this.pasteOffset;
+ geo.y += this.pasteOffset;
+ }
+ }
+ });
+
+ // 插入克隆的单元格
+ const parent = this.graph.getDefaultParent();
+ this.graph.addCells(clones, parent);
+
+ // 选中新粘贴的单元格
+ this.graph.setSelectionCells(clones);
+
+ // 增加下次粘贴的偏移量
+ this.pasteOffset += 30;
+ // 如果偏移太大,重置
+ if (this.pasteOffset > 150) {
+ this.pasteOffset = 30;
+ }
+ } finally {
+ this.graph.model.endUpdate();
+ }
+ }
+ }
+
+ private handleUndo(event: KeyboardEvent) {
+ event.preventDefault();
+ this.graph.model.undo();
+ }
+
+ private handleRedo(event: KeyboardEvent) {
+ event.preventDefault();
+ this.graph.model.redo();
+ }
+
+ destroy() {
+ document.removeEventListener('keydown', this.handleKeyDown.bind(this));
+ }
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/handlers/WuConnctionHandler.ts b/src/components/wu-flow-lib/handlers/WuConnctionHandler.ts
new file mode 100644
index 000000000..a38df384d
--- /dev/null
+++ b/src/components/wu-flow-lib/handlers/WuConnctionHandler.ts
@@ -0,0 +1,53 @@
+import { ConnectionHandler, CellState ,Graph} from '@maxgraph/core';
+
+export class WuConnctionHandler extends ConnectionHandler {
+ // constructor(graph: Graph, options: any) {
+ // super(graph, options);
+ // }
+ isConnectableCell(cell) {
+ return false;
+ }
+
+ /*
+ * Special case: Snaps source of new connections to fixed points
+ * Without a connect preview in connectionHandler.createEdgeState mouseMove
+ * and getSourcePerimeterPoint should be overriden by setting sourceConstraint
+ * sourceConstraint to null in mouseMove and updating it and returning the
+ * nearest point (cp) in getSourcePerimeterPoint (see below)
+ */
+ updateEdgeState(pt, constraint) {
+ if (pt != null && this.previous != null) {
+ const constraints = this.graph.getAllConnectionConstraints(this.previous, true) ;// .getAllConnectionConstraints(this.previous);
+ let nearestConstraint = null;
+ let dist = null;
+ for (let i = 0; i < constraints.length; i++) {
+ const cp = this.graph.getConnectionPoint(this.previous, constraints[i]);
+ if (cp != null) {
+ const tmp = (cp.x - pt.x) * (cp.x - pt.x) + (cp.y - pt.y) * (cp.y - pt.y);
+ if (dist == null || tmp < dist) {
+ nearestConstraint = constraints[i];
+ dist = tmp;
+ }
+ }
+ }
+ if (nearestConstraint != null) {
+ this.sourceConstraint = nearestConstraint;
+ }
+
+ // In case the edge style must be changed during the preview:
+ // this.edgeState.style.edgeStyle = 'orthogonalEdgeStyle';
+ // And to use the new edge style in the new edge inserted into the graph,
+ // update the cell style as follows:
+ // this.edgeState.cell.style = utils.setStyle(this.edgeState.cell.style, 'edgeStyle', this.edgeState.style.edgeStyle);
+ }
+ return super.updateEdgeState(pt, constraint);
+ }
+ createEdgeState(me) {
+ // Connect preview
+ const edge = this.graph.createEdge(null, null, null, null, null, {
+ edgeStyle: 'orthogonalEdgeStyle'
+ });
+ return new CellState(this.graph.view, edge, this.graph.getCellStyle(edge));
+ }
+
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/handlers/WuConstraintHandler.ts b/src/components/wu-flow-lib/handlers/WuConstraintHandler.ts
new file mode 100644
index 000000000..035c7cc56
--- /dev/null
+++ b/src/components/wu-flow-lib/handlers/WuConstraintHandler.ts
@@ -0,0 +1,7 @@
+import { ConstraintHandler, Graph, mathUtils } from '@maxgraph/core';
+
+export class WuConstraintHandler extends ConstraintHandler {
+ intersects(icon, point, source, existingEdge) {
+ return !source || existingEdge || mathUtils.intersects(icon.bounds, point);
+ }
+}
diff --git a/src/components/wu-flow-lib/icons/index.ts b/src/components/wu-flow-lib/icons/index.ts
new file mode 100644
index 000000000..2b30fb823
--- /dev/null
+++ b/src/components/wu-flow-lib/icons/index.ts
@@ -0,0 +1,417 @@
+export const FlowIcons = {
+ // 基础流程图标
+ Start: ``,
+
+ End: ``,
+
+ Process: ``,
+
+ Decision: ``,
+
+ Arrow: ``,
+
+ // 高级组件图标
+ SubProcess: ``,
+
+ Parallel: ``,
+
+ Timer: ``,
+
+ // 基础形状节点图标
+ Rectangle: ``,
+
+ Ellipse: ``,
+
+ DoubleEllipse: ``,
+
+ Rhombus: ``,
+
+ Line: ``,
+
+ Image: ``,
+
+ ArrowConnector: ``,
+
+ Label: ``,
+
+ Cylinder: ``,
+
+ Swimlane: ``,
+
+ Connector: ``,
+
+ Actor: ``,
+
+ Cloud: ``,
+
+ Triangle: ``,
+
+ Hexagon: ``,
+
+ // UML 图标
+ ClassIcon: ``,
+
+ InterfaceIcon: ``,
+
+ PackageIcon: ``,
+
+ ComponentIcon: ``,
+
+ AggregationIcon: ``,
+
+ CompositionIcon: ``,
+
+ Note: ``,
+
+ Text: ``,
+
+ Star: ``,
+
+ VSwimlane: ``,
+
+ HSwimlane: ``,
+ TableIcon: `
+
+`,
+ SimpleTableIcon: `
+
+`
+}
+
+export const ToolbarIcons = {
+ // ... 其他图标 ...
+
+ // 背景颜色图标
+ backgroundColor: ``,
+
+ // 节点颜色图标
+ nodeColor: ``,
+
+ // 网格图标
+ grid: ``,
+
+ // 导出图标
+ export: ``,
+
+ // 撤销图标
+ undo: ``,
+
+ // 重做图标
+ redo: ``,
+
+ // 删除图标
+ delete: ``,
+
+ // 适应画布图标
+ fit: ``,
+
+ // 缩小图标
+ zoomOut: ``,
+
+ // 放大图标
+ zoomIn: ``,
+
+ // 返回图标
+ back: ``,
+
+ // 直线图标
+ straightLine: ``,
+
+ // 折线图标
+ orthogonalLine: ``,
+
+ // 曲线图标
+ curvedLine: ``,
+
+ borderColor: ``,
+ borderStyle: ``,
+ borderWidth: ``,
+
+ // 对齐图标
+ alignLeft: ``,
+
+ alignCenter: ``,
+
+ alignRight: ``,
+
+ alignTop: ``,
+
+ alignMiddle: ``,
+
+ alignBottom: ``,
+
+ distributeHorizontally: ``,
+
+ distributeVertically: ``,
+};
+
+export const MenuIcons = {
+ delete: ``,
+ copy: ``,
+ image: ``
+};
+
+// UML 图标定义
+export const UMLIcons = {
+ Class: `
+
+ `,
+
+ Interface: `
+
+ `,
+
+ Package: `
+
+ `,
+
+ Component: `
+
+ `,
+
+ Object: `
+
+ `,
+
+ Enumeration: `
+
+ `,
+
+ Association: `
+
+ `,
+
+ Inheritance: `
+
+ `,
+
+ Implementation: `
+
+ `,
+
+ Dependency: `
+
+ `,
+};
+
diff --git a/src/components/wu-flow-lib/index.ts b/src/components/wu-flow-lib/index.ts
new file mode 100644
index 000000000..c790d17f4
--- /dev/null
+++ b/src/components/wu-flow-lib/index.ts
@@ -0,0 +1,21 @@
+import { App } from 'vue'
+import SplitPane from './components/SplitPane.vue'
+import FlowAccordion from './components/panel/FlowAccordion.vue'
+import Accordion from './components/Accordion.vue'
+import FlowCanvas from './components/canvas/FlowCanvas.vue'
+import FlowToolbar from './components/menu/FlowToolbar.vue'
+import { WuFlowAction } from './utils/flow-utils'
+
+// 导出单个组件
+export { SplitPane, FlowAccordion, Accordion, FlowCanvas, FlowToolbar, WuFlowAction }
+
+// 导出插件安装函数
+export default {
+ install: (app: App) => {
+ app.component('WuSplitPane', SplitPane)
+ app.component('WuFlowAccordion', FlowAccordion)
+ app.component('WuAccordion', Accordion)
+ app.component('WuFlowCanvas', FlowCanvas)
+ app.component('WuFlowToolbar', FlowToolbar)
+ }
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/styles/flow-canvas.css b/src/components/wu-flow-lib/styles/flow-canvas.css
new file mode 100644
index 000000000..bcbf17fcc
--- /dev/null
+++ b/src/components/wu-flow-lib/styles/flow-canvas.css
@@ -0,0 +1,236 @@
+.flow-canvas-wrapper {
+ padding: 6px;
+ height: calc(100% - 12px);
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+}
+
+.flow-canvas {
+ width: 100%;
+ height: 100%;
+ background-color: #fff;
+ position: relative;
+ overflow: auto;
+ transition: background-color 0.3s;
+ flex: 1;
+}
+
+.flow-canvas.style-brush-active {
+ cursor: copy;
+}
+
+.flow-canvas.show-grid {
+ background-image:
+ linear-gradient(to right, #f0f0f0 1px, transparent 1px),
+ linear-gradient(to bottom, #f0f0f0 1px, transparent 1px);
+ background-size: 20px 20px;
+ height: 100%;
+}
+
+.canvas {
+ width: 100%;
+ height: 100%;
+ background-size: 20px 20px;
+ background-image: linear-gradient(to right, #f0f0f0 1px, transparent 1px),
+ linear-gradient(to bottom, #f0f0f0 1px, transparent 1px);
+}
+
+.elements {
+ transform-origin: 0 0;
+}
+
+.node {
+ cursor: move;
+}
+
+.node-bg {
+ fill: white;
+ stroke: #dcdfe6;
+ stroke-width: 1;
+}
+
+.node:hover .node-bg {
+ stroke: #409eff;
+}
+
+.node :deep(svg) {
+ width: 16px;
+ height: 16px;
+ overflow: visible;
+}
+
+.node-label {
+ font-size: 12px;
+ fill: #303133;
+ user-select: none;
+}
+
+.graph-outline {
+ width: 200px;
+ height: 200px;
+ background-color: white;
+ border: 1px solid #ccc;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ cursor: move;
+ z-index: 1000;
+ position: absolute;
+ padding: 8px;
+}
+
+.title-bar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background-color: #f0f0f0;
+ padding: 0px;
+ cursor: move;
+}
+
+.close-button {
+ background: none;
+ border: none;
+ font-size: 16px;
+ cursor: pointer;
+}
+
+.text-toolbar {
+ position: fixed;
+ background: white;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ padding: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ display: flex;
+ gap: 8px;
+ z-index: 1001;
+}
+
+.text-toolbar button {
+ padding: 4px 8px;
+ border: 1px solid #ddd;
+ background: white;
+ cursor: pointer;
+ border-radius: 4px;
+}
+
+.text-toolbar button.active {
+ background: #e6f7ff;
+ border-color: #1890ff;
+}
+
+.text-toolbar select {
+ padding: 4px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+}
+
+.text-toolbar input[type="color"] {
+ width: 30px;
+ height: 30px;
+ padding: 0;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+}
+
+.mxRubberband {
+ border-color: #b18426;
+ background: #db9b0b;
+}
+
+/* 右键菜单样式 */
+.mxPopupMenu {
+ position: absolute;
+ background: #ffffff;
+ border-radius: 4px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ padding: 5px 0;
+ min-width: 120px;
+ z-index: 9999;
+ border: 1px solid #e4e7ed;
+ font-size: 14px;
+ color: #606266;
+}
+
+.custom-menu-item {
+ position: relative;
+ display: flex;
+ align-items: center;
+ height: 34px;
+ line-height: 34px;
+ padding: 0 20px;
+ color: #606266;
+ cursor: pointer;
+ transition: all 0.2s;
+ white-space: nowrap;
+}
+
+.custom-menu-item:hover {
+ background-color: #f5f7fa;
+ color: #409eff;
+}
+
+.custom-menu-item:hover .menu-icon svg {
+ fill: #409eff;
+}
+
+.custom-menu-item:active {
+ background-color: #ecf5ff;
+}
+
+.menu-icon {
+ margin-right: 10px;
+ width: 16px;
+ height: 16px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.menu-icon svg {
+ width: 16px;
+ height: 16px;
+ fill: currentColor;
+ vertical-align: middle;
+}
+
+.menu-label {
+ flex: 1;
+ font-size: 14px;
+}
+
+.mxPopupMenu hr {
+ display: block;
+ height: 1px;
+ width: 100%;
+ margin: 5px 0;
+ border: none;
+ background-color: #e4e7ed;
+ transform: scaleY(0.5);
+}
+
+/* 子菜单样式 */
+.custom-menu-item.has-submenu::after {
+ content: '';
+ position: absolute;
+ right: 16px;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 6px;
+ height: 6px;
+ border-right: 2px solid #c0c4cc;
+ border-top: 2px solid #c0c4cc;
+ transform: translateY(-50%) rotate(45deg);
+}
+
+.custom-menu-item.disabled {
+ cursor: not-allowed;
+ color: #c0c4cc;
+ background-color: #fff;
+}
+
+.custom-menu-item.disabled:hover {
+ background-color: #fff;
+ color: #c0c4cc;
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/utils/dragHandler.ts b/src/components/wu-flow-lib/utils/dragHandler.ts
new file mode 100644
index 000000000..8d8abaf2b
--- /dev/null
+++ b/src/components/wu-flow-lib/utils/dragHandler.ts
@@ -0,0 +1,65 @@
+interface DragState {
+ leftPanelWidth: number;
+ rightPanelWidth: number;
+ leftPanelVisible: boolean;
+ rightPanelVisible: boolean;
+}
+
+export function useDragHandler(state: DragState, type: 'left' | 'right') {
+ const minWidth = 0
+ const maxWidth = 800
+ let startX = 0
+ let startWidth = 0
+ let isDragging = false
+
+ const startDrag = (e: MouseEvent) => {
+ const isVisible = type === 'left' ? state.leftPanelVisible : state.rightPanelVisible
+ if (!isVisible) return
+
+ isDragging = true
+ startX = e.clientX
+ startWidth = type === 'left' ? state.leftPanelWidth : state.rightPanelWidth
+
+ document.body.style.userSelect = 'none'
+ document.addEventListener('mousemove', onDrag)
+ document.addEventListener('mouseup', stopDrag)
+ }
+
+ const onDrag = (e: MouseEvent) => {
+ if (!isDragging) return
+
+ const containerWidth = window.innerWidth
+ const diff = e.clientX - startX
+
+ if (type === 'left') {
+ // 左侧面板拖动限制
+ const maxAllowedWidth = containerWidth - (state.rightPanelVisible ? state.rightPanelWidth : 0) - 400 // 保留中间面板最小宽度
+ const newWidth = Math.min(
+ maxAllowedWidth,
+ Math.max(minWidth, startWidth + diff)
+ )
+ state.leftPanelWidth = newWidth
+ } else {
+ // 右侧面板拖动限制
+ const maxAllowedWidth = containerWidth - (state.leftPanelVisible ? state.leftPanelWidth : 0) - 400 // 保留中间面板最小宽度
+ const newWidth = Math.min(
+ maxAllowedWidth,
+ Math.max(minWidth, startWidth - diff)
+ )
+ state.rightPanelWidth = newWidth
+ }
+ }
+
+ const stopDrag = () => {
+ isDragging = false
+ document.body.style.userSelect = ''
+ document.removeEventListener('mousemove', onDrag)
+ document.removeEventListener('mouseup', stopDrag)
+ }
+
+ return {
+ startDrag,
+ onDrag,
+ stopDrag
+ }
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/utils/flow-tools-bars.ts b/src/components/wu-flow-lib/utils/flow-tools-bars.ts
new file mode 100644
index 000000000..bf3976e4b
--- /dev/null
+++ b/src/components/wu-flow-lib/utils/flow-tools-bars.ts
@@ -0,0 +1,7 @@
+import { Graph, EditorToolbar } from "mxgraph/javascript/mxClient";
+
+export class flowToolsBars {
+ constructor(graph: Graph) {
+ new EditorToolbar(graph.container, graph.editor);
+ }
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/utils/flow-utils.ts b/src/components/wu-flow-lib/utils/flow-utils.ts
new file mode 100644
index 000000000..eb92e13cd
--- /dev/null
+++ b/src/components/wu-flow-lib/utils/flow-utils.ts
@@ -0,0 +1,366 @@
+import {
+ Graph, HierarchicalLayout, Outline, InternalEvent,
+ GraphDataModel, ModelXmlSerializer, CircleLayout, FastOrganicLayout,
+ StackLayout, ParallelEdgeLayout, PartitionLayout, ConstraintHandler, getDefaultPlugins, CellStyle,Cell,
+ SelectionCellsHandler, SelectionHandler, GraphPluginConstructor, CellState, ConnectionHandler
+} from "@maxgraph/core";
+import { CustomRubberBandHandler } from "../flow/CustomRubberBandHandler";
+import { WuCustomGraph } from "../flow/WuCustomGraph";
+import { MyCustomPopupMenuHandler } from "../flow/MyCustomPopupMenuHandler";
+import { WuSelectHander } from "../flow/WuSelectHander";
+import { ShapeRegister } from './shape-register';
+import { WuConnctionHandler } from "../handlers/WuConnctionHandler";
+
+export class WuFlowAction {
+ private graph: WuCustomGraph;
+ private canvasRef: HTMLElement;
+ private static shapesRegistered = false; // 添加静态标志
+
+ constructor(container: HTMLElement, graphOutlineRef: HTMLElement) {
+ this.canvasRef = container;
+ // 只在第一次时注册自定义形状
+ if (!WuFlowAction.shapesRegistered) {
+ ShapeRegister.registerCustomShapes();
+ WuFlowAction.shapesRegistered = true;
+ }
+ this.init(container, graphOutlineRef)
+ }
+ init(container: HTMLElement, graphOutlineRef: HTMLElement) {
+ //
+ const plugins: GraphPluginConstructor[] = [...getDefaultPlugins(),MyCustomPopupMenuHandler,CustomRubberBandHandler,WuSelectHander,SelectionCellsHandler,SelectionHandler];
+ if (container) {
+ InternalEvent.disableContextMenu(container);
+ const flowCanvasData = sessionStorage.getItem("flow-canvas-data");
+ if (flowCanvasData) {
+ const model = new GraphDataModel();
+ const serializer = new ModelXmlSerializer(model);
+ serializer.import(flowCanvasData);
+ this.graph = new WuCustomGraph(container, model, plugins);
+ } else {
+ this.graph = new WuCustomGraph(container, undefined, plugins);
+ }
+ // 启用基本功能
+ this.graph.setEnabled(true);
+ // 启用连接功能
+ this.graph.setConnectable(true);
+ // 启用多选功能
+ this.graph.allowLoops = true;
+ // 允许从任意点开始连接
+ // 允许悬浮显示连接点
+ // 允许创建目标连接
+
+ // 允许连接到空白处创建新节点
+ this.graph.setAllowDanglingEdges(true);
+ // 设置默认边样式
+ this.graph.getStylesheet().getDefaultEdgeStyle().edgeStyle = 'orthogonalEdgeStyle';
+ this.graph.getStylesheet().getDefaultEdgeStyle().rounded = true;
+ this.graph.getStylesheet().getDefaultEdgeStyle().strokeColor = "#5b8ffa";
+ this.graph.getStylesheet().getDefaultEdgeStyle().strokeWidth = 2;
+ this.graph.setMultigraph(true);
+ this.graph.setDropEnabled(true);
+ // 启用选择功能
+ this.graph.setPanning(true); // 暂时禁用平移,防止与橡皮筋选择冲突
+ this.graph.swimlaneSelectionEnabled = true;
+
+ const outline = new Outline(this.graph, graphOutlineRef);
+ outline.border = 1;
+ outline.enabled = true;
+ if (graphOutlineRef) {
+ graphOutlineRef.style.right = "10px";
+ graphOutlineRef.style.top = "10px";
+ }
+ }
+ this.graph.setDropEnabled(true);
+ this.graph.addListener(InternalEvent.CELL_CONNECTED, (sender, event: InternalEvent) => {
+ console.log("WuFlowAction sender", sender, event);
+ });
+ }
+
+
+ handleLayoutChange(type: string) {
+ const parent = this.graph.getDefaultParent()
+ this.graph.model.beginUpdate()
+ try {
+ let layout
+ switch (type) {
+ case 'hierarchical':
+ layout = new HierarchicalLayout(this.graph)
+ layout.orientation = 'north'
+ layout.interHierarchySpacing = 30
+ break
+ case 'circle':
+ layout = new CircleLayout(this.graph, 3)
+ break
+ case 'organic':
+ layout = new FastOrganicLayout(this.graph)
+ break
+ case 'stack':
+ layout = new StackLayout(this.graph)
+ break
+ case 'parallel':
+ layout = new ParallelEdgeLayout(this.graph)
+ break
+ case 'partition':
+ layout = new PartitionLayout(this.graph, true, 100, 120)
+ break
+ default:
+ layout = new HierarchicalLayout(this.graph)
+ }
+ layout.execute(parent)
+
+ } catch (error) {
+
+ } finally {
+ this.graph.model.endUpdate()
+ }
+ }
+ changeFont(fontFamily: string) {
+ if (!this.graph) return
+ const cells = this.graph.getSelectionCells()
+ if (cells?.length) {
+ this.graph.model.beginUpdate()
+ try {
+ cells.forEach(cell => {
+ if (cell.isVertex()) {
+ const style = { ...cell.getStyle() }
+ style.fontFamily = fontFamily
+ this.graph.setCellStyle(style, [cell])
+ }
+ })
+ } finally {
+ this.graph.model.endUpdate()
+ }
+ }
+ }
+ getDefaultEdgeStyle() {
+ return this.graph.getStylesheet().getDefaultEdgeStyle()
+ }
+ getXmlSerializer() {
+ const model = this.graph.getDataModel()
+ const serializer = new ModelXmlSerializer(model)
+ return serializer.export()
+ }
+
+ getGraphSvg() {
+ return this.graph.getGraphSvg()
+ }
+ insertVertex(event: DragEvent) {
+ const jsonStr = event.dataTransfer?.getData("application/json");
+ if (jsonStr && this.graph) {
+ const item = JSON.parse(jsonStr);
+ const rect = this.canvasRef.getBoundingClientRect();
+ if (rect) {
+ const x = event.clientX - rect.left;
+ const y = event.clientY - rect.top;
+ this.graph.batchUpdate(() => {
+ const parent = this.graph.getDefaultParent();
+ // 获取节点尺寸,如果未定义则使用默认值
+ console.log('item', item );
+ const size = item?.size || [100, 50];
+ let style: CellStyle = {
+ fillColor: item.backgroundColor || "orange",
+ shape: item.shape ||item.style.shape || "ellipse",
+ verticalAlign: "middle",
+ verticalLabelPosition: "middle",
+ editable: true,
+ movable: true,
+ resizable: true,
+ };
+ if (item.style) {
+ style = {
+ ...item.style, editable: true,
+ movable: true,
+ resizable: true,
+ connectable: true,
+ }
+ }
+
+ const vertex = this.graph.insertVertex({
+ parent,
+ position: [x, y],
+ size: size,
+ style: style,
+ value: item.label,
+ });
+ vertex.connectable = true
+ });
+ }
+ }
+ }
+
+ getGraph() {
+ return this.graph;
+ }
+
+ addAnimation(cell: CellState) {
+ const state = this.graph.view.getState(cell.cell);
+ state?.shape?.node.getElementsByTagName('path')[0].removeAttribute('visibility');
+ state?.shape?.node.getElementsByTagName('path')[0].setAttribute('stroke-width', '6');
+ state?.shape?.node.getElementsByTagName('path')[0].setAttribute('stroke', 'lightGray');
+ state?.shape?.node.getElementsByTagName('path')[1].setAttribute('class', 'flow');
+ }
+
+ changeNodeColor(color: string) {
+ if (!this.graph) return;
+ const cells = this.graph.getSelectionCells();
+ if (cells?.length) {
+ this.graph.model.beginUpdate();
+ try {
+ cells.forEach(cell => {
+ if (cell.isVertex()) {
+ const style = { ...cell.getStyle() };
+ style.fillColor = color;
+ this.graph.setCellStyle(style, [cell]);
+ }
+ });
+ } finally {
+ this.graph.model.endUpdate();
+ }
+ }
+ }
+
+ updateSelectedNodeStyle(styleProps: any) {
+ const graph = this.getGraph();
+ const cell = graph.getSelectionCell();
+ if (cell) {
+ const style = cell.getStyle();
+ const newStyle = { ...cell.getStyle(), ...styleProps };
+ graph.setCellStyle(newStyle, [cell]);
+ }
+ }
+
+ updateTextStyle(styleProps: any) {
+ if (!this.graph) return;
+ console.log("updateTextStyle1", styleProps);
+ const cells = this.graph.getSelectionCells();
+ if (cells?.length) {
+ this.graph.model.beginUpdate();
+ try {
+ cells.forEach(cell => {
+ if (cell.isVertex()) {
+ const style = { ...cell.getStyle() };
+ // 只更新变化的属性
+ Object.entries(styleProps).forEach(([key, value]) => {
+ if (style[key] !== value) {
+ style[key] = value;
+ }
+ });
+ this.graph.setCellStyle(style, [cell]);
+ }
+ });
+ } finally {
+ this.graph.model.endUpdate();
+ }
+ }
+ }
+
+ getSelectedNodeStyle() {
+ if (!this.graph) return null;
+ const cell = this.graph.getSelectionCell();
+ if (cell?.isVertex()) {
+ const style = cell.getStyle();
+ return {
+ fontFamily: style.fontFamily || 'Arial',
+ fontSize: style.fontSize || 14,
+ fontColor: style.fontColor || '#000000',
+ fontWeight: style['font-weight'] || 'normal',
+ textDecoration: style['text-decoration'] || 'none'
+ };
+ }
+ return null;
+ }
+
+ insertImageNode(x: number, y: number, imageUrl: string) {
+ const graph = this.getGraph();
+ if (graph) {
+ const parent = graph.getDefaultParent();
+ graph.model.beginUpdate();
+ try {
+ const vertex = graph.insertVertex(
+ parent,
+ null,
+ '',
+ x - 50, // 居中显示
+ y - 50,
+ 100, // 默认宽度
+ 100, // 默认高度
+ {
+ shape: 'image',
+ image: imageUrl,
+ imageAlign: 'center',
+ verticalAlign: 'middle',
+ resizable: true
+ }
+ );
+ graph.setSelectionCell(vertex);
+ } finally {
+ graph.model.endUpdate();
+ }
+ }
+ }
+
+ changeBorderColor(color: string) {
+ if (!this.graph) return;
+ const cells = this.graph.getSelectionCells();
+ if (cells?.length) {
+ this.graph.model.beginUpdate();
+ try {
+ cells.forEach(cell => {
+ if (cell.isVertex()) {
+ const style = { ...cell.getStyle() };
+ style.strokeColor = color;
+ this.graph.setCellStyle(style, [cell]);
+ }
+ });
+ } finally {
+ this.graph.model.endUpdate();
+ }
+ }
+ }
+
+ changeBorderStyle(style: string) {
+ if (!this.graph) return;
+ const cells = this.graph.getSelectionCells();
+ if (cells?.length) {
+ this.graph.model.beginUpdate();
+ try {
+ cells.forEach(cell => {
+ if (cell.isVertex()) {
+ const cellStyle = { ...cell.getStyle() };
+ cellStyle.dashed = style === 'dashed';
+ // cellStyle.dotted = style === 'dotted';
+ this.graph.setCellStyle(cellStyle, [cell]);
+ }
+ });
+ } finally {
+ this.graph.model.endUpdate();
+ }
+ }
+ }
+
+ changeBorderWidth(width: number) {
+ if (!this.graph) return;
+ const cells = this.graph.getSelectionCells();
+ if (cells?.length) {
+ this.graph.model.beginUpdate();
+ try {
+ cells.forEach(cell => {
+ if (cell.isVertex()) {
+ const style = { ...cell.getStyle() };
+ style.strokeWidth = width;
+ this.graph.setCellStyle(style, [cell]);
+ }
+ });
+ } finally {
+ this.graph.model.endUpdate();
+ }
+ }
+ }
+
+ loadGraphData(data: any) {
+ if (!this.graph) return;
+ this.graph.model.beginUpdate();
+ this.graph.model.import(data);
+ this.graph.model.endUpdate();
+ }
+}
\ No newline at end of file
diff --git a/src/components/wu-flow-lib/utils/shape-register.ts b/src/components/wu-flow-lib/utils/shape-register.ts
new file mode 100644
index 000000000..8aa9740f4
--- /dev/null
+++ b/src/components/wu-flow-lib/utils/shape-register.ts
@@ -0,0 +1,216 @@
+import { Graph, CellRenderer, Shape } from '@maxgraph/core';
+import { FlowIcons, UMLIcons } from '../icons';
+
+export class ShapeRegister {
+ static registerCustomShapes() {
+ // 注册基础流程图形状
+ CellRenderer.registerShape('start', class extends Shape {
+ paintBackground(c: any, x: number, y: number, w: number, h: number) {
+ // 绘制圆形
+ c.ellipse(x, y, w, h);
+ c.fillAndStroke();
+
+ // 绘制内部小圆
+ const insetSize = Math.min(w, h) * 0.3;
+ c.setFillColor('#4caf50');
+ c.ellipse(x + (w-insetSize)/2, y + (h-insetSize)/2, insetSize, insetSize);
+ c.fill();
+ }
+ });
+
+ CellRenderer.registerShape('end', class extends Shape {
+ paintBackground(c: any, x: number, y: number, w: number, h: number) {
+ // 绘制圆形
+ c.ellipse(x, y, w, h);
+ c.fillAndStroke();
+
+ // 绘制内部圆环
+ const insetSize = Math.min(w, h) * 0.3;
+ c.setStrokeWidth(2);
+ c.setStrokeColor('#f44336');
+ c.ellipse(x + (w-insetSize)/2, y + (h-insetSize)/2, insetSize, insetSize);
+ c.stroke();
+ }
+ });
+
+ // 注册 UML 图形状
+ Object.entries(UMLIcons).forEach(([name, svg]) => {
+ CellRenderer.registerShape(name.toLowerCase(), class extends Shape {
+ paintBackground(c: any, x: number, y: number, w: number, h: number) {
+ c.rect(x, y, w, h);
+ c.fillAndStroke();
+
+ if (this.state.style.shape === name.toLowerCase()) {
+ const svgElement = new DOMParser().parseFromString(svg, 'image/svg+xml').documentElement;
+ const svgContent = svgElement.innerHTML;
+ c.setSvg(svgContent, x, y, w, h);
+ }
+ }
+ });
+ });
+
+ // 注册 UML 类图形状
+ CellRenderer.registerShape('umlClass', class extends Shape {
+ paintBackground(c: any, x: number, y: number, w: number, h: number) {
+ c.rect(x, y, w, h);
+ c.fillAndStroke();
+
+ // 绘制分隔线
+ const headerHeight = h / 3;
+ c.begin();
+ c.moveTo(x, y + headerHeight);
+ c.lineTo(x + w, y + headerHeight);
+ c.moveTo(x, y + headerHeight * 2);
+ c.lineTo(x + w, y + headerHeight * 2);
+ c.stroke();
+ }
+ });
+
+ // 注册 UML 接口形状
+ CellRenderer.registerShape('umlInterface', class extends Shape {
+ constructor(state: any) {
+ super(state);
+ this.isDashed = true;
+ }
+
+ paintBackground(c: any, x: number, y: number, w: number, h: number) {
+ c.setDashed(true, [2, 2]);
+ c.rect(x, y, w, h);
+ c.fillAndStroke();
+
+ // 绘制分隔线
+ const headerHeight = h / 3;
+ c.begin();
+ c.moveTo(x, y + headerHeight);
+ c.lineTo(x + w, y + headerHeight);
+ c.moveTo(x, y + headerHeight * 2);
+ c.lineTo(x + w, y + headerHeight * 2);
+ c.stroke();
+ }
+ });
+
+ // 注册包形状
+ CellRenderer.registerShape('package', class extends Shape {
+ paintBackground(c: any, x: number, y: number, w: number, h: number) {
+ const tabWidth = w * 0.3;
+ const tabHeight = h * 0.2;
+
+ c.begin();
+ // 绘制标签部分
+ c.moveTo(x, y);
+ c.lineTo(x + tabWidth, y);
+ c.lineTo(x + tabWidth, y + tabHeight);
+ c.lineTo(x + w, y + tabHeight);
+ c.lineTo(x + w, y + h);
+ c.lineTo(x, y + h);
+ c.close();
+ c.fillAndStroke();
+ }
+ });
+
+ // 注册组件形状
+ CellRenderer.registerShape('component', class extends Shape {
+ paintBackground(c: any, x: number, y: number, w: number, h: number) {
+ c.rect(x, y, w, h);
+ c.fillAndStroke();
+
+ const portSize = Math.min(w, h) * 0.2;
+
+ // 绘制端口
+ c.begin();
+ c.rect(x - portSize, y + h * 0.25, portSize, portSize);
+ c.rect(x - portSize, y + h * 0.6, portSize, portSize);
+ c.fillAndStroke();
+ }
+ });
+
+ // 注册备注形状
+ CellRenderer.registerShape('note', class extends Shape {
+ paintBackground(c: any, x: number, y: number, w: number, h: number) {
+ const fold = Math.min(w, h) * 0.2; // 折角大小
+
+ c.begin();
+ c.moveTo(x, y);
+ c.lineTo(x + w - fold, y);
+ c.lineTo(x + w, y + fold);
+ c.lineTo(x + w, y + h);
+ c.lineTo(x, y + h);
+ c.close();
+ c.fillAndStroke();
+
+ // 绘制折角
+ c.begin();
+ c.moveTo(x + w - fold, y);
+ c.lineTo(x + w - fold, y + fold);
+ c.lineTo(x + w, y + fold);
+ c.stroke();
+ }
+ });
+
+ // 注册五角星形状
+ CellRenderer.registerShape('star', class extends Shape {
+ paintBackground(c: any, x: number, y: number, w: number, h: number) {
+ const cx = x + w/2;
+ const cy = y + h/2;
+ const r = Math.min(w, h)/2;
+ const points = [];
+
+ for (let i = 0; i < 5; i++) {
+ // 外部点
+ const angle = (i * 72 - 90) * Math.PI/180;
+ points.push([
+ cx + r * Math.cos(angle),
+ cy + r * Math.sin(angle)
+ ]);
+ // 内部点
+ const innerAngle = ((i * 72 + 36) - 90) * Math.PI/180;
+ points.push([
+ cx + (r/2) * Math.cos(innerAngle),
+ cy + (r/2) * Math.sin(innerAngle)
+ ]);
+ }
+
+ c.begin();
+ c.moveTo(points[0][0], points[0][1]);
+ for (let i = 1; i < points.length; i++) {
+ c.lineTo(points[i][0], points[i][1]);
+ }
+ c.lineTo(points[0][0], points[0][1]);
+ c.close();
+ c.fillAndStroke();
+ }
+ });
+
+ // 注册表格形状
+ // graph.getStylesheet().putCellStyle('table', {
+ // shape: 'html',
+ // whiteSpace: 'wrap',
+ // verticalAlign: 'top',
+ // align: 'left',
+ // overflow: 'visible'
+ // });
+
+ // // 注册泳道形状
+ // CellRenderer.registerShape('swimlane', class extends Shape {
+ // paintBackground(c: any, x: number, y: number, w: number, h: number) {
+ // const style = this.state.style;
+ // const isHorizontal = style.horizontal;
+ // const startSize = style.startSize || 30;
+
+ // // 绘制外框
+ // c.begin();
+ // c.rect(x, y, w, h);
+ // c.fillAndStroke();
+
+ // // 绘制标题区域
+ // c.begin();
+ // if (isHorizontal) {
+ // c.rect(x, y, w, startSize);
+ // } else {
+ // c.rect(x, y, startSize, h);
+ // }
+ // c.fillAndStroke();
+ // }
+ // });
+ }
+}
\ No newline at end of file
diff --git a/src/router/index.ts b/src/router/index.ts
index b818421c2..58047f747 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -2,12 +2,13 @@ import type { App } from 'vue'
import type { RouteRecordRaw } from 'vue-router'
import { createRouter, createWebHistory } from 'vue-router'
import remainingRouter from './modules/remaining'
+import flowRouter from './modules/flowrouter'
// 创建路由实例
const router = createRouter({
history: createWebHistory(import.meta.env.VITE_BASE_PATH), // createWebHashHistory URL带#,createWebHistory URL不带#
strict: true,
- routes: remainingRouter as RouteRecordRaw[],
+ routes: [...remainingRouter, ...flowRouter] as RouteRecordRaw[],
scrollBehavior: () => ({ left: 0, top: 0 })
})
diff --git a/src/router/modules/flowrouter.ts b/src/router/modules/flowrouter.ts
new file mode 100644
index 000000000..fe2b1b67e
--- /dev/null
+++ b/src/router/modules/flowrouter.ts
@@ -0,0 +1,63 @@
+export default [
+ {
+ path: '/flow',
+ name: 'Flow',
+ component: () => import('@/views/flow/Home.vue'),
+ children: [
+ { path: '', redirect: '/flow/default' },
+ {
+ path: 'default',
+ name: 'default',
+ component: () => import('@/views/flow/HomeDefault.vue')
+ },
+ {
+ path: 'pricing',
+ name: 'pricing',
+ component: () => import('@/views/flow/Pricing.vue')
+ },
+ {
+ path: 'contact',
+ name: 'contact',
+ component: () => import('@/views/flow/Contact.vue')
+ }
+ ]
+ },
+ {
+ path: '/files',
+ name: 'files',
+ component: () => import('@/views/flow/FileManager.vue'),
+ children: [
+ { path: '', redirect: '/files/my-files' },
+ {
+ path: 'my-files',
+ name: 'my-files',
+ component: () => import('@/views/flow/files/MyFiles.vue')
+ },
+ {
+ path: 'recommended',
+ name: 'recommended',
+ component: () => import('@/views/flow/files/Recommended.vue')
+ },
+ {
+ path: 'recent',
+ name: 'recent',
+ component: () => import('@/views/flow/files/Recent.vue')
+ }
+ ]
+ },
+ {
+ path: '/flow/Register',
+ name: 'Register',
+ component: () => import('@/views/flow/Register.vue')
+ },
+ {
+ path: '/flow-editor',
+ name: 'FlowEditor',
+ component: () => import('@/components/wu-flow-lib/components/FlowEditor.vue')
+ },
+ {
+ path: '/register',
+ name: 'Register',
+ component: () => import('@/views/flow/Register.vue')
+ }
+]
diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts
index 806f954da..dfdc692ae 100644
--- a/src/router/modules/remaining.ts
+++ b/src/router/modules/remaining.ts
@@ -53,7 +53,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
{
path: '/',
component: Layout,
- redirect: '/index',
+ redirect: '/flow',
name: 'Home',
meta: {},
children: [
diff --git a/src/views/flow/FileManager.vue b/src/views/flow/FileManager.vue
index 88d8c4d87..bfe56d71d 100644
--- a/src/views/flow/FileManager.vue
+++ b/src/views/flow/FileManager.vue
@@ -7,7 +7,7 @@
←
-
+
Flow Design
@@ -84,7 +84,7 @@
@@ -147,11 +147,11 @@ const processedData = {
defineExpose(processedData)
const handleCreate = () => {
- router.push('/flow-edit')
+ router.push('/flow-editor')
}
const openTemplateDialog = () => {
- router.push('/flow-edit')
+ router.push('/flow-editor')
}
diff --git a/src/views/flow/FlowEdit.vue b/src/views/flow/FlowEdit.vue
index f12e369ec..8f269ba42 100644
--- a/src/views/flow/FlowEdit.vue
+++ b/src/views/flow/FlowEdit.vue
@@ -18,7 +18,7 @@
\ No newline at end of file
+
diff --git a/src/views/flow/Register.vue b/src/views/flow/Register.vue
index dbff5c252..9d27f79c5 100644
--- a/src/views/flow/Register.vue
+++ b/src/views/flow/Register.vue
@@ -29,39 +29,26 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
我已阅读并同意服务条款
-
+
-
-
+