From 055f3847c2e66bf37ef38eb537a9ae56db23797c Mon Sep 17 00:00:00 2001 From: suixy <2277317060@qq.com> Date: Fri, 27 Mar 2026 17:11:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BC=96=E8=BE=91=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 + src/main.ts | 3 + src/permission.ts | 2 +- src/router/index.ts | 5 + src/views/visualEditor/index.vue | 144 ++++++++++++++++++ .../visualEditor/nodes/circuitComponent.vue | 73 +++++++++ src/views/visualEditor/nodes/line.vue | 72 +++++++++ src/views/visualEditor/tool.js | 87 +++++++++++ 8 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 src/views/visualEditor/index.vue create mode 100644 src/views/visualEditor/nodes/circuitComponent.vue create mode 100644 src/views/visualEditor/nodes/line.vue create mode 100644 src/views/visualEditor/tool.js diff --git a/package.json b/package.json index cf44b03..d7b4967 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,9 @@ "dependencies": { "@element-plus/icons-vue": "2.3.2", "@highlightjs/vue-plugin": "2.1.2", + "@vue-flow/background": "^1.3.2", + "@vue-flow/core": "^1.43.1", + "@vue-flow/node-resizer": "^1.4.0", "@vueup/vue-quill": "1.2.0", "@vueuse/core": "14.2.1", "animate.css": "4.1.1", @@ -38,6 +41,7 @@ "nprogress": "0.2.0", "pinia": "3.0.4", "screenfull": "6.0.2", + "uuid": "^13.0.0", "vue": "3.5.30", "vue-cropper": "1.1.4", "vue-i18n": "11.3.0", diff --git a/src/main.ts b/src/main.ts index 16ac610..a0d62bb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,9 @@ import { createApp } from 'vue'; import 'virtual:uno.css'; import 'element-plus/theme-chalk/dark/css-vars.css'; import '@/assets/styles/index.scss'; +import '@vue-flow/core/dist/style.css'; +import '@vue-flow/core/dist/theme-default.css'; +import '@vue-flow/node-resizer/dist/style.css'; // App、router、store import App from './App.vue'; diff --git a/src/permission.ts b/src/permission.ts index fb60b07..fa40865 100644 --- a/src/permission.ts +++ b/src/permission.ts @@ -11,7 +11,7 @@ import { usePermissionStore } from '@/store/modules/permission'; import { ElMessage } from 'element-plus/es'; NProgress.configure({ showSpinner: false }); -const whiteList = ['/login', '/register', '/social-callback', '/register*', '/register/*']; +const whiteList = ['/login', '/register', '/social-callback', '/register*', '/register/*', '/visualEditor']; const isWhiteList = (path: string) => { return whiteList.some((pattern) => isPathMatch(pattern, path)); diff --git a/src/router/index.ts b/src/router/index.ts index a6497db..8438e11 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -26,6 +26,11 @@ import Layout from '@/layout/index.vue'; // 公共路由 export const constantRoutes: RouteRecordRaw[] = [ + { + path: '/visualEditor', + hidden: true, + component: () => import('@/views/visualEditor/index.vue') + }, { path: '/redirect', component: Layout, diff --git a/src/views/visualEditor/index.vue b/src/views/visualEditor/index.vue new file mode 100644 index 0000000..cd007ab --- /dev/null +++ b/src/views/visualEditor/index.vue @@ -0,0 +1,144 @@ + + + diff --git a/src/views/visualEditor/nodes/circuitComponent.vue b/src/views/visualEditor/nodes/circuitComponent.vue new file mode 100644 index 0000000..5f9c1ac --- /dev/null +++ b/src/views/visualEditor/nodes/circuitComponent.vue @@ -0,0 +1,73 @@ + + + diff --git a/src/views/visualEditor/nodes/line.vue b/src/views/visualEditor/nodes/line.vue new file mode 100644 index 0000000..a1bd4b7 --- /dev/null +++ b/src/views/visualEditor/nodes/line.vue @@ -0,0 +1,72 @@ + + + diff --git a/src/views/visualEditor/tool.js b/src/views/visualEditor/tool.js new file mode 100644 index 0000000..c025b7f --- /dev/null +++ b/src/views/visualEditor/tool.js @@ -0,0 +1,87 @@ +import { useVueFlow } from '@vue-flow/core'; +import { ref, watch } from 'vue'; +import { v4 as uuidv4 } from 'uuid'; + +const getId = (type) => { + return `${type}_${uuidv4().replaceAll('-', '_')}`; +}; + +const getOption = (e) => { + if (e === 'circuitComponent') { + return { + title: '' + }; + } +}; +const getNodeSize = (e) => { + if (e === 'circuitComponent') { + return { width: 50, height: 50 }; + } else if (e === 'line') { + return { width: 100, height: 20 }; + } +}; +const nameEnum = {}; +const tool = () => { + const nodeType = ref(''); + const customData = ref({ type: 1 }); + const { addNodes, screenToFlowCoordinate, onNodesInitialized, updateNode } = useVueFlow({ id: 'flowA' }); + const onDragStart = (event, type, data) => { + if (event.dataTransfer) { + event.dataTransfer.setData('application/vueflow', type); + event.dataTransfer.effectAllowed = 'move'; + } + + nodeType.value = type; + customData.value = data || { type: 1 }; + document.addEventListener('drop', onDragEnd); + }; + const onDragEnd = () => { + nodeType.value = null; + document.removeEventListener('drop', onDragEnd); + }; + const onDrop = (event) => { + const dimensions = getNodeSize(nodeType.value); + + const position = screenToFlowCoordinate({ + x: event.clientX, + y: event.clientY + }); + + const nodeId = getId(nodeType.value); + const newNode = { + id: nodeId, + name: nameEnum[nodeType.value], + draggable: true, + type: nodeType.value, + dimensions, + position, + data: { options: getOption(nodeType.value), outputData: {}, customData: customData.value } + }; + + const { off } = onNodesInitialized(() => { + updateNode(nodeId, (node) => ({ + position: { x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2 } + })); + + off(); + }); + + addNodes(newNode); + }; + const onDragOver = (event) => { + event.preventDefault(); + + if (nodeType.value) { + if (event.dataTransfer) { + event.dataTransfer.dropEffect = 'move'; + } + } + }; + return { + onDragStart, + onDragEnd, + onDrop, + onDragOver + }; +}; +export default tool;