From 101e100c7576b6c5ba3c6b94e9d607e3af5fc72b Mon Sep 17 00:00:00 2001 From: zhanghao <774378400@qq.com> Date: Wed, 4 Mar 2026 14:41:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BB=A3=E7=A0=81=E8=8A=82?= =?UTF-8?q?=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + src/views/flow/config.js | 13 + src/views/flow/nodes/function/codeNode.js | 219 ++++++ src/views/flow/nodes/function/codeNode.vue | 755 +++++++++++++++++++++ src/views/flow/nodes/function/index.js | 2 + vite.config.js | 27 +- 6 files changed, 1017 insertions(+), 1 deletion(-) create mode 100644 src/views/flow/nodes/function/codeNode.js create mode 100644 src/views/flow/nodes/function/codeNode.vue diff --git a/package.json b/package.json index 8c592c0..e9d032f 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "json-editor-vue": "^0.18.1", "lodash": "^4.17.21", "mitt": "^3.0.1", + "monaco-editor": "^0.55.1", "nprogress": "0.2.0", "pako": "^2.1.0", "pinia": "2.1.7", @@ -42,6 +43,7 @@ "three": "^0.181.2", "urdf-loader": "^0.12.6", "uuid": "^11.1.0", + "vite-plugin-monaco-editor": "^1.1.0", "vue": "3.4.31", "vue-cropper": "1.1.1", "vue-router": "4.4.0", diff --git a/src/views/flow/config.js b/src/views/flow/config.js index 6ab4a77..fe771b1 100644 --- a/src/views/flow/config.js +++ b/src/views/flow/config.js @@ -294,6 +294,19 @@ export const collapseList = [ ], outputType: 'json', outputParams: [{ name: 'result', type: 'Object', children: [], desc: 'HTTP请求结果'}] + }, + { + icon: expressionSvg, + name: "代码", + type: "code", + desc: "js代码", + action: 'CODE', + canAddFormItem: true, + nodeParams: [ + { name: "input", type: "input", input: "", required: true } + ], + outputType: 'json', + outputParams: [{ name: 'result', type: 'Object', children: [], desc: 'HTTP请求结果'}] } ], }, diff --git a/src/views/flow/nodes/function/codeNode.js b/src/views/flow/nodes/function/codeNode.js new file mode 100644 index 0000000..703ce9f --- /dev/null +++ b/src/views/flow/nodes/function/codeNode.js @@ -0,0 +1,219 @@ +// 导入 HtmlNode 及其模型,为后续继承做准备 +import { HtmlNode, HtmlNodeModel } from "@logicflow/core"; +// 导入 Vue 相关方法,用于渲染组件 +import { createApp, h, nextTick } from "vue"; +import ElementPlus from "element-plus"; +// 导入 Vue 组件 +// @ts-ignore +import CodeNode from "./codeNode.vue"; +import OuterNode from "../../components/OuterNode.vue"; +import JsonViewer from 'vue3-json-viewer' + +/** + * 定义一个 元素的 HTML 节点类,继承自 HtmlNode + * 该类负责在 HTML 中渲染 元素,并处理其交互逻辑 + */ +class CodeNodeHtmlNode extends HtmlNode { + resizeObserver = null; + isMounted; // 标记组件是否已挂载 + r; // 渲染函数 + app; // Vue 应用实例 + container = null; + + static reusePool = new Map(); // 节点复用池 + + /** + * 构造函数 + * @param props 传递给节点的属性,包括模型、图模型等 + */ + constructor(props) { + super(props); + this.initVueApp(props); + } + + initVueApp(props) { + this.isMounted = false; + + this.hideAnchor = false; + this.autoExpand = true; // 防止锚点被折叠 + this.anchorsPreset = "default"; // 重置锚点预设 + // 创建 元素的渲染函数 + this.r = h(OuterNode, { + model: props.model, + component: CodeNode, + properties: { + ...props.model.getProperties(), + }, + onContentChange: this.handleContentChange.bind(this), + onBindRef: this.handleComponentInstance.bind(this) + }); + + // 创建 Vue 应用实例,并指定渲染函数 + this.app = createApp({ + render: () => this.r, + }); + } + + /** + * 将 HTML 内容设置到指定的根元素上 + * @param rootEl 根元素 + */ + async setHtml(rootEl) { + const nodeId = this.props.model.id; + if (CodeNodeHtmlNode.reusePool.has(nodeId)) { + rootEl.appendChild(CodeNodeHtmlNode.reusePool.get(nodeId)); + return; + } + + if (!this.isMounted) { + this.isMounted = true; + this.container = document.createElement("div"); + this.container.style.display = "inline-block"; // 关键:确保容器自适应内容 + + rootEl.appendChild(this.container); + this.app.use(ElementPlus); + this.app.use(JsonViewer); + + this.app.mount(this.container); + await nextTick(); + this.setupSizeObserver(); + CodeNodeHtmlNode.reusePool.set(nodeId, this.container); + } else { + this.r.component.props.properties = this.props.model.getProperties(); + } + } + + /** + * 获取节点文本内容 + * 对于元素,返回 null,因为其内容由特定组件渲染 + * @returns {null} + */ + getText() { + return null; + } + + handleComponentInstance(data) { + // 将组件实例保存到节点模型 + this.props.model.setComponentInstance(data) + } + + handleContentChange() { + // 内容变化时强制更新尺寸 + this.updateNodeSize(); + } + + // 渲染完成后获取实际尺寸 + updateNodeSize() { + if (this.container) { + const { SCALE_X, SCALE_Y } = this.props.graphModel.transformModel; + const node = this.container.querySelector(".node__container"); + const rect = node.getBoundingClientRect(); + this.props.model.updateSize(rect.width / SCALE_X, rect.height / SCALE_Y); + } + } + + setupSizeObserver() { + // 首次渲染立即检测 + requestAnimationFrame(() => { + this.updateNodeSize(); + // 持续监听变化 + this.resizeObserver = new ResizeObserver(() => { + this.updateNodeSize(); + }); + this.resizeObserver.observe(this.container); + }); + } + + // 组件卸载时移除监听 + onDestroy() { + this.resizeObserver?.disconnect(); + } +} + +/** + * 定义一个元素的 HTML 模型类,继承自 HtmlNodeModel + * 该类主要设置节点的属性和样式 + */ +class CodeNodeHtmlModel extends HtmlNodeModel { + initNodeData(data) { + super.initNodeData(data); + } + + // 保存组件实例引用 + setComponentInstance(instance) { + this.componentInstance = instance; + } + + // 验证表单 + async validateForm() { + const result = this.componentInstance.validateForm() + return result + } + + setCustomProperties() { + this.componentInstance.setNodeProperties() + } + + /** + * 设置节点属性 + * 包括宽度、高度、文本编辑属性等 + */ + setAttributes() { + // 初始设置为0,后续动态更新 + this.width = 0; + this.height = 0; + this.text.editable = false; + } + + updateSize(width, height) { + this.width = width + 24; + this.height = height + 50; + this.initNodeData(this); // 触发节点重绘 + } + + // 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。 + getDefaultAnchor() { + let _a = this, + x = _a.x, + y = _a.y, + width = _a.width, + height = _a.height; + return [ + { + x: x + width / 2 + 4, + y: y, + name: "right", + id: "".concat(this.id, "_1"), + properties: { connectionType: "source" }, + }, + { + x: x - width / 2, + y: y, + name: "left", + id: "".concat(this.id, "_3"), + properties: { connectionType: "target" }, + }, + ]; + } + + /** + * 获取节点轮廓样式 + * 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果 + * @returns {object} 节点轮廓样式 + */ + getOutlineStyle() { + const style = super.getOutlineStyle(); + style.stroke = "none"; + style.hover.stroke = "none"; + return style; + } +} + +// 导出方法注册 +export function registerCodeNode(lf) { + lf.register({ + type: "code", + view: CodeNodeHtmlNode, + model: CodeNodeHtmlModel, + }); +} diff --git a/src/views/flow/nodes/function/codeNode.vue b/src/views/flow/nodes/function/codeNode.vue new file mode 100644 index 0000000..c357af6 --- /dev/null +++ b/src/views/flow/nodes/function/codeNode.vue @@ -0,0 +1,755 @@ + + + + + diff --git a/src/views/flow/nodes/function/index.js b/src/views/flow/nodes/function/index.js index 9f26f42..9fb774d 100644 --- a/src/views/flow/nodes/function/index.js +++ b/src/views/flow/nodes/function/index.js @@ -5,6 +5,7 @@ import { registerSubEndNode } from './subEnd' import { registerSubStartNode } from './subStart' import { registerSleepNode } from './sleep' import { registerHttpNode } from './httpNode' +import { registerCodeNode } from './codeNode' export const registerFunction = (lf) => { registerLoopBodyNode(lf) @@ -14,4 +15,5 @@ export const registerFunction = (lf) => { registerSubStartNode(lf) registerSleepNode(lf) registerHttpNode(lf) + registerCodeNode(lf) } \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index 3e3b7a3..5757441 100644 --- a/vite.config.js +++ b/vite.config.js @@ -13,6 +13,21 @@ export default defineConfig(({mode, command}) => { // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 base: VITE_APP_ENV === 'production' ? '/' : '/', plugins: createVitePlugins(env, command === 'build'), + optimizeDeps: { + exclude: [ + // 排除 Monaco Editor 相关的依赖 + 'monaco-editor', + 'monaco-editor/esm/vs/base/worker/workerMain.js', + 'monaco-editor/esm/vs/editor/editor.main.js', + // 排除 TypeScript worker + 'ts.worker.js', + 'typescript', + ], + include: [ + // 确保其他依赖被优化 + 'monaco-editor/esm/vs/language/typescript/monaco.contribution', + ], + }, resolve: { // https://cn.vitejs.dev/config/#resolve-alias alias: { @@ -63,6 +78,16 @@ export default defineConfig(({mode, command}) => { additionalData: `@import "${variablePath}";` }, }, - } + }, + build: { + rollupOptions: { + // 确保 Monaco Editor 的 worker 文件被正确处理 + output: { + manualChunks: { + monaco: ['monaco-editor'], + }, + }, + }, + }, } })