diff --git a/src/store/modules/flow.js b/src/store/modules/flow.js index 287ae45..c7652d3 100644 --- a/src/store/modules/flow.js +++ b/src/store/modules/flow.js @@ -1,22 +1,22 @@ -import { defineStore } from 'pinia' - -export const useFlowStore = defineStore('flow', { - state: () => ({ - disableForm: false, - deviceList: [] - }), - actions: { - updateDisableForm(value) { - this.disableForm = value - }, - - async getDeviceList() { - const data = await new Promise((resolve, reject) => { - resolve( ['cam1', 'cam2', 'cam3', 'cam4']) - }) - this.deviceList = data - return data - } - } -} -) +import { defineStore } from 'pinia' + +export const useFlowStore = defineStore('flow', { + state: () => ({ + disableForm: false, + deviceList: [] + }), + actions: { + updateDisableForm(value) { + this.disableForm = value + }, + + async getDeviceList() { + const data = await new Promise((resolve, reject) => { + resolve( ['cam1', 'cam2', 'cam3', 'cam4']) + }) + this.deviceList = data + return data + } + } +} +) diff --git a/src/utils/flow.js b/src/utils/flow.js index 48ff78a..c1b9351 100644 --- a/src/utils/flow.js +++ b/src/utils/flow.js @@ -109,7 +109,7 @@ export const recursiveFilter = (data, id, type = ['loop', 'customGroup'], result data.forEach((item) => { if (type.includes(item.type) && item.children && item.children.includes(id)) { result.push(item) - recursiveFilter(data, item.id, result) + recursiveFilter(data, item.id, type, result) } }); return result @@ -262,5 +262,109 @@ export const convertToTree = (data) => { } }); + return result; +} + + +export const filterEmptyName = (obj) => { + // 1. 如果是数组,遍历每一项递归处理 + if (Array.isArray(obj)) { + return obj + .map(item => filterEmptyName(item)) // 先递归处理子项 + .filter(item => item !== null); // 过滤掉被剔除的空项 + } + + // 2. 如果是对象,先检查 name 是否为空,为空直接返回 null(剔除) + if (typeof obj === 'object' && obj !== null) { + // 核心:name 为空字符串 → 直接剔除这个对象 + if (obj.name === '') { + return null; + } + + // 创建新对象,保留原有属性 + const newObj = { ...obj }; + + // 如果有 children,递归过滤子节点 + if (newObj.children && Array.isArray(newObj.children)) { + newObj.children = filterEmptyName(newObj.children); + } + + return newObj; + } + + // 基础类型直接返回 + return obj; +} + +/** + * 将原始表单配置数组 转换为 目标对象格式 + * @param {Array} source - 原始数据数组 + * @returns {Object} 转换后的目标格式对象 + */ +export const transformHttpNodeData = (source) => { + // 初始化结果对象 + const result = {}; + + // 遍历每一个顶级节点 + source.forEach(item => { + const { name, children } = item; + if (!name) return; // 过滤无name的无效节点 + + // 处理普通节点(config/headers/params):直接取 children 数组 + if (name !== 'body') { + result[name] = children || []; + return; + } + + // 专门处理 body 节点(特殊结构) + if (name === 'body') { + const bodyObj = { + type: '', + json: '', + formData: [] + }; + + // 遍历body的子项,赋值到对应字段 + children?.forEach(child => { + const childName = child.name; + if (childName === 'bodyType') { + bodyObj.type = child.input; + } + if (childName === 'json') { + bodyObj.json = child.input; + } + if (childName === 'formData') { + bodyObj.formData = child.children || []; + } + }); + + result.body = bodyObj; + } + }); + + return result; +} + +export const transformSdAgentNodeData = (source) => { + // 初始化结果对象 + const result = {}; + + // 遍历每一个顶级节点 + source.forEach(item => { + const { name, children } = item; + if (!name) return; // 过滤无name的无效节点 + + // 处理普通节点(config/headers/params):直接取 children 数组 + if (name !== 'invokeTts') { + result[name] = children || []; + return; + } + + // 专门处理 tts 节点(特殊结构) + if (name === 'invokeTts') { + result.invokeTts = item.input; + } + }); + return result; } \ No newline at end of file diff --git a/src/views/flow/nodes/common/FormItemRecursive.vue b/src/views/flow/components/FormItemRecursive.vue similarity index 88% rename from src/views/flow/nodes/common/FormItemRecursive.vue rename to src/views/flow/components/FormItemRecursive.vue index 08db478..e615217 100644 --- a/src/views/flow/nodes/common/FormItemRecursive.vue +++ b/src/views/flow/components/FormItemRecursive.vue @@ -6,12 +6,12 @@ @@ -47,7 +47,7 @@ :rules="[{ required: true, message: '请输入', trigger: 'blur' }]" > { const handleAddChild = (parentItem) => { parentItem.children.push({ name: "", - type: "", + type: "string", desc: "", required: false, children: [], @@ -184,7 +182,8 @@ const handleChildDelete = (childFullPath) => { \ No newline at end of file diff --git a/src/views/flow/components/NodeState.vue b/src/views/flow/components/NodeState.vue index 6cc7929..7c4b40d 100644 --- a/src/views/flow/components/NodeState.vue +++ b/src/views/flow/components/NodeState.vue @@ -1,151 +1,176 @@ - - - \ No newline at end of file + + + diff --git a/src/views/flow/components/NodeTitle.vue b/src/views/flow/components/NodeTitle.vue index 5e6a4ab..2d47b0d 100644 --- a/src/views/flow/components/NodeTitle.vue +++ b/src/views/flow/components/NodeTitle.vue @@ -6,9 +6,7 @@
{{ nodeName }} - +
@@ -44,7 +42,7 @@ - - + --> { .el-input { margin-left: 12px; + width: 120px; } .input_name_select { diff --git a/src/views/flow/components/OuterNode.vue b/src/views/flow/components/OuterNode.vue index f84d388..f36bdd0 100644 --- a/src/views/flow/components/OuterNode.vue +++ b/src/views/flow/components/OuterNode.vue @@ -1,43 +1,43 @@ - - - - - \ No newline at end of file diff --git a/src/views/flow/components/ParamsDrawer.vue b/src/views/flow/components/ParamsDrawer.vue new file mode 100644 index 0000000..a13ca15 --- /dev/null +++ b/src/views/flow/components/ParamsDrawer.vue @@ -0,0 +1,1794 @@ + + + + + + \ No newline at end of file diff --git a/src/views/flow/config.js b/src/views/flow/config.js index 505faa9..b719201 100644 --- a/src/views/flow/config.js +++ b/src/views/flow/config.js @@ -171,6 +171,7 @@ export const collapseList = [ nodeParams: [ { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true } ], + outputParams: [], outputType: 'json' }, { @@ -196,6 +197,7 @@ export const collapseList = [ nodeParams: [ { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true } ], + outputParams: [], outputType: 'json' }, ], @@ -213,6 +215,7 @@ export const collapseList = [ nodeParams: [ { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true } ], + outputParams: [], outputType: 'json' }, { @@ -239,6 +242,9 @@ export const collapseList = [ type: "loop", action: 'START_LOOP', desc: "实现对列表对象循环执行一系列任务", + nodeParams: [{ name: "loopNum", type: "input", input: null, quote: "" }], + outputParams: [], + outputType: 'json' }, { icon: stopLoopSvg, @@ -247,20 +253,20 @@ export const collapseList = [ action: 'STOP_LOOP', desc: "用于立即终止当前所在的循环,跳出循环体", }, - { - icon: startSvg, - name: "单次循环开始", - type: "subStart", - action: 'SUB_START', - desc: "循环体内部工作流的开始节点,开始循环体内部的单次流程", - }, - { - icon: endSvg, - name: "单次循环结束", - type: "subEnd", - action: 'SUB_END', - desc: "循环体内部工作流的终止节点,结束循环体内部的单次流程", - }, + // { + // icon: startSvg, + // name: "单次循环开始", + // type: "subStart", + // action: 'SUB_START', + // desc: "循环体内部工作流的开始节点,开始循环体内部的单次流程", + // }, + // { + // icon: endSvg, + // name: "单次循环结束", + // type: "subEnd", + // action: 'SUB_END', + // desc: "循环体内部工作流的终止节点,结束循环体内部的单次流程", + // }, { icon: switchSvg, name: "分支", @@ -275,8 +281,9 @@ export const collapseList = [ desc: "用于睡眠整个流程,表示延迟多少毫秒", action: 'sleep', nodeParams: [ - { name: "delayMs", type: "input", componentType: 'number', input: "", disabled: true } + { name: "delayMs", type: "input", componentType: 'number', input: 0, disabled: true } ], + outputParams: [], outputType: 'json' }, { @@ -285,12 +292,24 @@ export const collapseList = [ type: "http", desc: "HTTP请求", action: 'HTTP', + nodeType: 'HTTP', nodeParams: [ - { name: "url", type: "input", input: "", disabled: true, required: true }, - { name: "method", type: "input", input: "POST", componentType: 'select', selectOptions: httpMethodOptions(), disabled: true }, - { name: "timeout", type: "input", input: "10000", componentType: 'number', required: false }, - { name: "headers", type: "input", input: "", disabled: true, required: false }, - { name: "body", type: "input", input: "", disabled: true, required: true }, + { name: "config", type: "input", input: "", children: [ + { name: "url", type: "input", input: "", disabled: true, required: true }, + { name: "method", type: "input", input: "POST", componentType: 'select', selectOptions: httpMethodOptions(), disabled: true }, + { name: "timeout", type: "input", input: "10000", componentType: 'number', required: false, disabled: true } + ], disabled: true, required: true }, + { name: "headers", type: "input", input: "", children: [ + { name: "", type: "input", input: ""} + ], disabled: true, required: true }, + { name: "params", type: "input", input: "", children: [ + { name: "", type: "input", input: ""} + ], disabled: true, required: true }, + { name: "body", type: "input", input: "", children: [ + { name: 'bodyType', type: "input", input: "json", disabled: true }, + { name: 'json', type: "input", input: '{}' }, + { name: 'formData', type: "input", input: "", children: []} + ], required: true }, ], outputType: 'json', outputParams: [{ name: 'result', type: 'Object', children: [], desc: 'HTTP请求结果'}] @@ -303,10 +322,24 @@ export const collapseList = [ action: 'CODE', canAddFormItem: true, nodeParams: [ - { name: "input", type: "input", input: "", required: true } + { name: "input", type: "input", input: "", required: true }, + { name: "code", type: "input", input: ` +// 方法定义不能修改 +function handler(params) { + // 返回值是一个可序列化成 json 的 dict 或 object + const result ={ + type: 2, + message: params.input + } + return result +} + `, required: true } ], outputType: 'json', - outputParams: [{ name: 'result', type: 'Object', children: [], desc: 'js节点返回'}] + outputParams: [{ name: 'result', type: 'object', children: [ + {name: 'type', type: 'number', desc: '占位属性'}, + {name: 'message', type: 'string', desc: '占位属性'} + ], desc: 'js节点返回'}] }, { icon: microphoneSvg, @@ -314,14 +347,15 @@ export const collapseList = [ type: "currentLoop", desc: "获取当前循环对象", action: 'GET_CURRENT_OBJECT', - nodeType: 'getCurrentObject', nodeParams: [ - { name: "array", type: "input", input: "", disabled: true } + { name: "array", type: "input", input: "" } ], outputType: 'json', outputParams: [ - { name: 'index', type: 'number', desc: '索引', disabled: true}, - { name: 'object', type: 'Object', children: [], desc: '当前循环对象', } + { name: 'index', type: 'number', desc: '索引'}, + { name: 'object', type: 'object', children: [ + { name: 'test', type: 'string', desc: '占位属性' } + ], desc: '当前循环对象', } ] }, ], @@ -398,7 +432,10 @@ export const collapseList = [ { name: "vehType", type: "input", input: "su7", disabled: true }, ], outputType: 'json', - outputParams: [{ name: 'coordinates', type: 'Object', children: [], desc: '坐标对象', }] + outputParams: [{ name: 'coordinates', type: 'object', children: [ + { name: 'x', type: 'number', desc: 'x坐标' }, + { name: 'y', type: 'number', desc: 'y坐标' } + ], desc: '坐标对象', }] }, { icon: dialogueSvg, name: "tts语音合成", @@ -422,14 +459,24 @@ export const collapseList = [ }, { icon: dialogueSvg, name: "商道智能体", - type: "serviceNode", + type: "sdAgent", desc: "通过apiKey调用商道大模型", action: 'AI_AGENT_PLATFORM', nodeType: "LLM", outputType: 'json', nodeParams: [ - { name: 'apiKey', type: "input", input: "", disabled: true }, - { name: 'text', type: "input", input: "", disabled: true } + { name: "config", type: "input", input: "", children: [ + { name: 'apiKey', type: "input", input: "", disabled: true }, + { name: 'text', type: "input", input: "", disabled: true }, + ]}, + { name: 'invokeTts', type: "input", input: false }, + { name: 'tts', type: "input", input: "", children: [ + { name: 'url', type: "input", input: "", disabled: true }, + { name: 'text', type: "input", input: "", disabled: true }, + { name: 'speed', type: "input", input: "", max: 100, disabled: true }, + { name: 'voice', type: "input", input: "", componentType: 'select', selectOptions: voiceOptions(), disabled: true }, + { name: 'volume', type: "input", input: "", componentType: 'number', max: 100, disabled: true } + ], disabled: true } ], outputParams: [{ name: 'result', type: 'string', desc: '商道大模型的返回文案', disabled: true}] }, { diff --git a/src/views/flow/index.vue b/src/views/flow/index.vue index fdb5466..995d904 100644 --- a/src/views/flow/index.vue +++ b/src/views/flow/index.vue @@ -8,21 +8,15 @@
+ + {{ totalRunningTime }}ms + + 清除上一次运行结果 分组 - 试运行 - 暂停 - 恢复 - 终止 + 试运行 + 暂停 + 恢复 + 终止
发布 - 发布 + 发布 + +
@@ -117,9 +115,7 @@ diff --git a/src/views/flow/nodes/common/startNode.js b/src/views/flow/nodes/common/startNode.js index 50621d6..a0310f6 100644 --- a/src/views/flow/nodes/common/startNode.js +++ b/src/views/flow/nodes/common/startNode.js @@ -1,209 +1,204 @@ -// 导入 HtmlNode 及其模型,为后续继承做准备 -import { HtmlNode, HtmlNodeModel } from '@logicflow/core'; -// 导入 Vue 相关方法,用于渲染组件 -import { createApp, h, nextTick } from 'vue'; -import ElementPlus from 'element-plus' -// 导入 Vue 组件 -import StartNode from './startNode.vue'; -import OuterNode from "../../components/OuterNode.vue"; -import JsonViewer from 'vue3-json-viewer' - -/** - * 定义一个 元素的 HTML 节点类,继承自 HtmlNode - * 该类负责在 HTML 中渲染 元素,并处理其交互逻辑 - */ -class StartNodeHtmlNode 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: StartNode, - 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 (StartNodeHtmlNode.reusePool.has(nodeId)) { - rootEl.appendChild(StartNodeHtmlNode.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) // 关键:单独注册ElementPlus - this.app.use(JsonViewer) - this.app.mount(this.container); - await nextTick(); - this.setupSizeObserver(); - StartNodeHtmlNode.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__box') - 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 StartNodeHtmlModel 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; - this.deletable = 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 - 24, y: y - 25, name: 'right', id: "".concat(this.id, "_1"), properties: { connectionType: 'source' }, onlyAsSource: true }, - ]; - } - - /** - * 获取节点轮廓样式 - * 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果 - * @returns {object} 节点轮廓样式 - */ - getOutlineStyle() { - const style = super.getOutlineStyle(); - style.stroke = 'none'; - style.hover.stroke = 'none'; - return style; - } -} - -// 导出方法注册 -export function registerStartNode(lf) { - lf.register({ - type: "start", - view: StartNodeHtmlNode, - model: StartNodeHtmlModel, - events: { - remove: (event) => { - // 阻止默认的删除事件 - event.preventDefault(); - // 可以在此处添加自定义的删除逻辑,例如弹出提示框 - alert('此节点不可删除'); - }, - } - }) +// 导入 HtmlNode 及其模型,为后续继承做准备 +import { HtmlNode, HtmlNodeModel } from '@logicflow/core'; +// 导入 Vue 相关方法,用于渲染组件 +import { createApp, h, nextTick } from 'vue'; +import ElementPlus from 'element-plus' +// 导入 Vue 组件 +import StartNode from './startNode.vue'; +import OuterNode from "../../components/OuterNode.vue"; +import JsonViewer from 'vue3-json-viewer' + +/** + * 定义一个 元素的 HTML 节点类,继承自 HtmlNode + * 该类负责在 HTML 中渲染 元素,并处理其交互逻辑 + */ +class StartNodeHtmlNode 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: StartNode, + 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 (StartNodeHtmlNode.reusePool.has(nodeId)) { + rootEl.appendChild(StartNodeHtmlNode.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) // 关键:单独注册ElementPlus + this.app.use(JsonViewer) + this.app.mount(this.container); + await nextTick(); + this.setupSizeObserver(); + StartNodeHtmlNode.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__box') + 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 StartNodeHtmlModel extends HtmlNodeModel { + initNodeData(data) { + super.initNodeData(data); + } + + // 保存组件实例引用 + setComponentInstance(instance) { + this.componentInstance = instance; + } + + closePopover() { + this.componentInstance.closePopover() + } + + /** + * 设置节点属性 + * 包括宽度、高度、文本编辑属性等 + */ + setAttributes() { + // 初始设置为0,后续动态更新 + this.width = 0; + this.height = 0; + this.text.editable = false; + this.deletable = 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 - 24, y: y - 25, name: 'right', id: "".concat(this.id, "_1"), properties: { connectionType: 'source' }, onlyAsSource: true }, + ]; + } + + /** + * 获取节点轮廓样式 + * 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果 + * @returns {object} 节点轮廓样式 + */ + getOutlineStyle() { + const style = super.getOutlineStyle(); + style.stroke = 'none'; + style.hover.stroke = 'none'; + return style; + } +} + +// 导出方法注册 +export function registerStartNode(lf) { + lf.register({ + type: "start", + view: StartNodeHtmlNode, + model: StartNodeHtmlModel, + effect: ['status'], + events: { + remove: (event) => { + // 阻止默认的删除事件 + event.preventDefault(); + // 可以在此处添加自定义的删除逻辑,例如弹出提示框 + alert('此节点不可删除'); + }, + } + }) } \ No newline at end of file diff --git a/src/views/flow/nodes/common/startNode.vue b/src/views/flow/nodes/common/startNode.vue index e593359..289004d 100644 --- a/src/views/flow/nodes/common/startNode.vue +++ b/src/views/flow/nodes/common/startNode.vue @@ -1,7 +1,7 @@ diff --git a/src/views/flow/nodes/function/codeNode.js b/src/views/flow/nodes/function/codeNode.js index 703ce9f..1610a2e 100644 --- a/src/views/flow/nodes/function/codeNode.js +++ b/src/views/flow/nodes/function/codeNode.js @@ -144,14 +144,8 @@ class CodeNodeHtmlModel extends HtmlNodeModel { this.componentInstance = instance; } - // 验证表单 - async validateForm() { - const result = this.componentInstance.validateForm() - return result - } - - setCustomProperties() { - this.componentInstance.setNodeProperties() + closePopover() { + this.componentInstance.closePopover() } /** diff --git a/src/views/flow/nodes/function/codeNode.vue b/src/views/flow/nodes/function/codeNode.vue index 01a154a..32a2469 100644 --- a/src/views/flow/nodes/function/codeNode.vue +++ b/src/views/flow/nodes/function/codeNode.vue @@ -1,6 +1,6 @@ diff --git a/src/views/flow/nodes/function/currentLoop.vue b/src/views/flow/nodes/function/currentLoop.vue index 1470b2f..7703d20 100644 --- a/src/views/flow/nodes/function/currentLoop.vue +++ b/src/views/flow/nodes/function/currentLoop.vue @@ -1,6 +1,6 @@ diff --git a/src/views/flow/nodes/function/index.js b/src/views/flow/nodes/function/index.js index feb499d..3de78bd 100644 --- a/src/views/flow/nodes/function/index.js +++ b/src/views/flow/nodes/function/index.js @@ -7,6 +7,7 @@ import { registerSleepNode } from './sleep' import { registerHttpNode } from './httpNode' import { registerCodeNode } from './codeNode' import { registerCurrentLoopNode } from './currentLoopNode' +import { registerSdAgentNode } from './sdAgent' export const registerFunction = (lf) => { registerLoopBodyNode(lf) @@ -18,4 +19,5 @@ export const registerFunction = (lf) => { registerHttpNode(lf) registerCodeNode(lf) registerCurrentLoopNode(lf) + registerSdAgentNode(lf) } \ No newline at end of file diff --git a/src/views/flow/nodes/function/loop.js b/src/views/flow/nodes/function/loop.js index f6bdd6c..f3af0a8 100644 --- a/src/views/flow/nodes/function/loop.js +++ b/src/views/flow/nodes/function/loop.js @@ -1,276 +1,286 @@ -import { GroupNodeModel } from '@logicflow/extension'; -// 导入 Vue 相关方法,用于渲染组件 -import { createApp, h, nextTick } from "vue"; -import { HtmlNode, h as svgH } from "@logicflow/core"; -import ElementPlus from "element-plus"; -import Loop from './loop.vue'; - -class loopBodyModel extends GroupNodeModel { - - initNodeData(data) { - super.initNodeData(data); - this.children = new Set(data.children || []) - this.zIndex = 0; - this.isGroup = true; - this.resizable = false; - } - - // 保存组件实例引用 - setComponentInstance(instance) { - this.componentInstance = instance; - } - - // 验证表单 - async validateForm() { - if (this.componentInstance && this.componentInstance.validate) { - try { - const result = await this.componentInstance.validate(); - if (result) { - return { valid: true, message: "验证通过" }; - } else { - return { valid: false, message: "验证失败" }; - } - } catch { - return { valid: false, message: "验证失败" }; - } - } - return { valid: true, message: "无验证方法" }; - } - - // 计算子节点的包围盒(包含所有子节点的最小矩形区域) - getChildrenBBox() { - const children = [] - Array.from(this.children).forEach((id) => { - const node = this.graphModel.getNodeModelById(id) - if (node) { - children.push(this.graphModel.getNodeModelById(id)) - } else { - this.children.delete(id) - } - }); - - let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; - children.forEach((node) => { - let width = node.width === 0 ? 600 : node.width - let height = node.height === 0 ? 200 : node.height - minX = Math.min(minX, node.x - width / 2); - maxX = Math.max(maxX, node.x + width / 2); - minY = Math.min(minY, node.y - height / 2); - maxY = Math.max(maxY, node.y + height / 2); - }); - this.width = maxX - minX + 200; // 增加内边距 - this.height = maxY - minY + 260; - this.x = (minX + maxX) / 2; - this.y = (minY + maxY) / 2 - 120; - const properties = lf.getProperties(this.id) - this.setProperties({ - ...properties, - width: this.width, - height: this.height - }) - this.getDefaultAnchor() - } - - updateSize() { - const children = Array.from(this.children); - if (children.length > 0) { - this.getChildrenBBox(); - } - } - - // 添加子节点时立即更新大小 - addChild(childId) { - super.addChild(childId); - this.updateSize(); - } - - // 移除子节点时更新大小 - removeChild(childId) { - return false - } - - // 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。 - getDefaultAnchor() { - let _a = this, - x = _a.x, - y = _a.y, - width = _a.width, - height = _a.height; - return [ - { - x: x + width / 2 , - y: y - 25, - name: "right", - id: "".concat(this.id, "_1"), - properties: { connectionType: "source" }, - }, - { - x: x - width / 2, - y: y - 25, - name: "left", - id: "".concat(this.id, "_3"), - properties: { connectionType: "target" }, - }, - ]; - } - - isAllowAppendIn() { - return false; - } -} - -class loopBodyView 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(Loop, { - model: props.model, - graphModel: props.graphModel, - properties: { - ...props.model.getProperties(), - }, - onAddToGroup: this.addChild.bind(this), - onContentChange: this.handleContentChange.bind(this), - onBindRef: this.handleComponentInstance.bind(this) - }); - - // 创建 Vue 应用实例,并指定渲染函数 - this.app = createApp({ - render: () => this.r, - }); - } - - addChild(data) { - this.addToGroup(data); - } - - handleComponentInstance(data) { - // 将组件实例保存到节点模型 - this.props.model.setComponentInstance(data) - } - - handleContentChange() { - // 内容变化时强制更新尺寸 - this.props.model.updateSize(); - } - - // 关键修改:重写getShape方法 - getShape() { - const { model } = this.props; - const { x, y, width, height } = model; - - // 创建自定义HTML内容 - const html = super.getShape(); - // 创建SVG组,包含HTML内容和锚点 - return svgH('g', {}, [ - // 绘制组节点背景 - svgH('rect', { - x: x - width / 2, - y: y - height / 2, - width, - height, - stroke: '#88f', - strokeWidth: 0, - strokeDasharray: '5,5', - fill: 'rgba(136, 136, 255, 0.1)', - rx: 4, - ry: 4 - }), - - // 添加HTML内容 - html, - - // 渲染锚点 - this.getAnchorShapes() - ]); - } - - // 创建锚点元素 - getAnchorShapes() { - const { model } = this.props; - const anchors = model.anchors || []; - - const anchorShapes = anchors.map((offset, index) => { - const {x, y } = offset - - return svgH('circle', { - cx: x, - cy: y, - r: 7, - hover: { fill: "#FF5722" }, // 禁用悬停变色 - fill: '#FF5722', - stroke: '#FF5722', - strokeWidth: 0, - className: 'lf-node-anchor', - cursor: 'crosshair', - 'data-anchor-id': `anchor_${index}` - }); - }); - - return svgH('g', {}, anchorShapes); - } - - /** - * 将 HTML 内容设置到指定的根元素上 - * @param rootEl 根元素 - */ - async setHtml(rootEl) { - const nodeId = this.props.model.id; - if (loopBodyView.reusePool.has(nodeId)) { - rootEl.appendChild(loopBodyView.reusePool.get(nodeId)); - return; - } - - if (!this.isMounted) { - this.isMounted = true; - this.container = document.createElement("div"); - // this.container.style.display = "inline-block"; // 关键:确保容器自适应内容 - this.container.style.width = "100%"; // 关键:确保容器自适应内容 - this.container.style.height = "100%"; // 关键:确保容器自适应内容 - - rootEl.appendChild(this.container); - this.app.use(ElementPlus); // 关键:单独注册ElementPlus - this.app.mount(this.container); - loopBodyView.reusePool.set(nodeId, this.container); - } else { - this.r.component.props.properties = this.props.model.getProperties(); - } - } - - addToGroup(childId) { - const groupModel = lf.getNodeModelById(this.props.model.id); - groupModel.addChild(childId); - groupModel.updateSize() - } -} - - -// 导出方法注册 -export function registerLoopBodyNode(lf) { - lf.register({ - type: "loop", - view: loopBodyView, - model: loopBodyModel, - }); +import { GroupNodeModel } from '@logicflow/extension'; +// 导入 Vue 相关方法,用于渲染组件 +import { createApp, h, nextTick } from "vue"; +import { HtmlNode, h as svgH } from "@logicflow/core"; +import ElementPlus from "element-plus"; +import Loop from './loop.vue'; + +class loopBodyModel extends GroupNodeModel { + + initNodeData(data) { + super.initNodeData(data); + this.children = new Set(data.children || []) + this.zIndex = 0; + this.isGroup = true; + this.resizable = false; + } + + // 保存组件实例引用 + setComponentInstance(instance) { + this.componentInstance = instance; + } + + // 递归获取所有有效子节点(包含多层嵌套 children) + getAllValidChildren = (nodeIds, graphModel) => { + const validNodes = []; + // 遍历当前层节点 ID + Array.from(nodeIds).forEach((id) => { + const node = graphModel.getNodeModelById(id); + if (node) { + // 当前节点有效,加入结果 + validNodes.push(node); + // 如果节点还有 children,递归获取下级节点 + if (node.children && node.children.size > 0) { + const childNodes = this.getAllValidChildren(node.children, graphModel); + validNodes.push(...childNodes); + } + } else { + // 无效 ID,从集合中删除 + nodeIds.delete(id); + } + }); + return validNodes; + }; + + // 计算子节点的包围盒(包含所有子节点的最小矩形区域) + getChildrenBBox() { + const children = this.getAllValidChildren(this.children, this.graphModel) + console.log('children', children) + // Array.from(this.children).forEach((id) => { + // const node = this.graphModel.getNodeModelById(id) + // if (node) { + // children.push(this.graphModel.getNodeModelById(id)) + // } else { + // this.children.delete(id) + // } + // }); + + let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; + nextTick(() => { + children.forEach((node) => { + let _width = node.type === 'loop' ? 500 : 320 + let _height = node.type === 'loop' ? 300 : 200 + let width = node.width === 0 ? _width : node.width + let height = node.height === 0 ? _height : node.height + minX = Math.min(minX, node.x - width / 2); + maxX = Math.max(maxX, node.x + width / 2); + minY = Math.min(minY, node.y - height / 2); + maxY = Math.max(maxY, node.y + height / 2); + }); + this.width = maxX - minX + 180; // 增加内边距 + this.height = maxY - minY + 300; + this.x = (minX + maxX) / 2; + this.y = (minY + maxY) / 2; + const properties = lf.getProperties(this.id) + this.setProperties({ + ...properties, + width: this.width, + height: this.height + }) + this.getDefaultAnchor() + }) + } + + updateSize() { + const children = Array.from(this.children); + if (children.length > 0) { + this.getChildrenBBox(); + } + } + + // 添加子节点时立即更新大小 + addChild(childId) { + super.addChild(childId); + this.updateSize(); + } + + // 移除子节点时更新大小 + removeChild(childId) { + return false + } + + // 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。 + getDefaultAnchor() { + let _a = this, + x = _a.x, + y = _a.y, + width = _a.width, + height = _a.height; + return [ + { + x: x + width / 2 , + y: y + height / 2, + name: "right", + id: "".concat(this.id, "_1"), + properties: { connectionType: "source" }, + }, + { + x: x - width / 2, + y: y + height / 2, + name: "left", + id: "".concat(this.id, "_3"), + properties: { connectionType: "target" }, + }, + ]; + } + + isAllowAppendIn() { + return false; + } +} + +class loopBodyView 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(Loop, { + model: props.model, + graphModel: props.graphModel, + properties: { + ...props.model.getProperties(), + }, + onAddToGroup: this.addChild.bind(this), + onContentChange: this.handleContentChange.bind(this), + onBindRef: this.handleComponentInstance.bind(this) + }); + + // 创建 Vue 应用实例,并指定渲染函数 + this.app = createApp({ + render: () => this.r, + }); + } + + addChild(data) { + this.addToGroup(data); + } + + handleComponentInstance(data) { + // 将组件实例保存到节点模型 + this.props.model.setComponentInstance(data) + } + + handleContentChange() { + // 内容变化时强制更新尺寸 + this.props.model.updateSize(); + } + + // 关键修改:重写getShape方法 + getShape() { + const { model } = this.props; + const { x, y, width, height } = model; + + // 创建自定义HTML内容 + const html = super.getShape(); + // 创建SVG组,包含HTML内容和锚点 + return svgH('g', {}, [ + // 绘制组节点背景 + svgH('rect', { + x: x - width / 2, + y: y - height / 2, + width, + height, + stroke: '#88f', + strokeWidth: 0, + strokeDasharray: '5,5', + fill: 'rgba(136, 136, 255, 0.1)', + rx: 4, + ry: 4 + }), + + // 添加HTML内容 + html, + + // 渲染锚点 + this.getAnchorShapes() + ]); + } + + // 创建锚点元素 + getAnchorShapes() { + const { model } = this.props; + const anchors = model.anchors || []; + + const anchorShapes = anchors.map((offset, index) => { + const {x, y } = offset + + return svgH('circle', { + cx: x, + cy: y, + r: 7, + hover: { fill: "#FF5722" }, // 禁用悬停变色 + fill: '#FF5722', + stroke: '#FF5722', + strokeWidth: 0, + className: 'lf-node-anchor', + cursor: 'crosshair', + 'data-anchor-id': `anchor_${index}` + }); + }); + + return svgH('g', {}, anchorShapes); + } + + /** + * 将 HTML 内容设置到指定的根元素上 + * @param rootEl 根元素 + */ + async setHtml(rootEl) { + const nodeId = this.props.model.id; + if (loopBodyView.reusePool.has(nodeId)) { + rootEl.appendChild(loopBodyView.reusePool.get(nodeId)); + return; + } + + if (!this.isMounted) { + this.isMounted = true; + this.container = document.createElement("div"); + // this.container.style.display = "inline-block"; // 关键:确保容器自适应内容 + this.container.style.width = "100%"; // 关键:确保容器自适应内容 + this.container.style.height = "100%"; // 关键:确保容器自适应内容 + + rootEl.appendChild(this.container); + this.app.use(ElementPlus); // 关键:单独注册ElementPlus + this.app.mount(this.container); + loopBodyView.reusePool.set(nodeId, this.container); + } else { + this.r.component.props.properties = this.props.model.getProperties(); + } + } + + addToGroup(childId) { + const groupModel = lf.getNodeModelById(this.props.model.id); + groupModel.addChild(childId); + groupModel.updateSize() + } +} + + +// 导出方法注册 +export function registerLoopBodyNode(lf) { + lf.register({ + type: "loop", + view: loopBodyView, + model: loopBodyModel, + }); } \ No newline at end of file diff --git a/src/views/flow/nodes/function/loop.vue b/src/views/flow/nodes/function/loop.vue index f7ed4a4..da2880d 100644 --- a/src/views/flow/nodes/function/loop.vue +++ b/src/views/flow/nodes/function/loop.vue @@ -9,118 +9,22 @@ :showZoom="false" @setNodeName="setNodeName" /> -
-
- -
- - - - - - - - - - - - - - - - - -
-
-
-
-
循环体
-
- -
+
循环体
+
+ +
diff --git a/src/views/flow/nodes/function/sleep.js b/src/views/flow/nodes/function/sleep.js index ee979b1..edd5049 100644 --- a/src/views/flow/nodes/function/sleep.js +++ b/src/views/flow/nodes/function/sleep.js @@ -1,219 +1,213 @@ -// 导入 HtmlNode 及其模型,为后续继承做准备 -import { HtmlNode, HtmlNodeModel } from "@logicflow/core"; -// 导入 Vue 相关方法,用于渲染组件 -import { createApp, h, nextTick } from "vue"; -import ElementPlus from "element-plus"; -// 导入 Vue 组件 -// @ts-ignore -import Sleep from "./sleep.vue"; -import OuterNode from "../../components/OuterNode.vue"; -import JsonViewer from 'vue3-json-viewer' - -/** - * 定义一个 元素的 HTML 节点类,继承自 HtmlNode - * 该类负责在 HTML 中渲染 元素,并处理其交互逻辑 - */ -class SleepNodeHtmlNode 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: Sleep, - 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 (SleepNodeHtmlNode.reusePool.has(nodeId)) { - rootEl.appendChild(SleepNodeHtmlNode.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(); - SleepNodeHtmlNode.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 SleepNodeHtmlModel 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 registerSleepNode(lf) { - lf.register({ - type: "sleep", - view: SleepNodeHtmlNode, - model: SleepNodeHtmlModel - }); -} +// 导入 HtmlNode 及其模型,为后续继承做准备 +import { HtmlNode, HtmlNodeModel } from "@logicflow/core"; +// 导入 Vue 相关方法,用于渲染组件 +import { createApp, h, nextTick } from "vue"; +import ElementPlus from "element-plus"; +// 导入 Vue 组件 +// @ts-ignore +import Sleep from "./sleep.vue"; +import OuterNode from "../../components/OuterNode.vue"; +import JsonViewer from 'vue3-json-viewer' + +/** + * 定义一个 元素的 HTML 节点类,继承自 HtmlNode + * 该类负责在 HTML 中渲染 元素,并处理其交互逻辑 + */ +class SleepNodeHtmlNode 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: Sleep, + 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 (SleepNodeHtmlNode.reusePool.has(nodeId)) { + rootEl.appendChild(SleepNodeHtmlNode.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(); + SleepNodeHtmlNode.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 SleepNodeHtmlModel extends HtmlNodeModel { + initNodeData(data) { + super.initNodeData(data); + } + + // 保存组件实例引用 + setComponentInstance(instance) { + this.componentInstance = instance; + } + + closePopover() { + this.componentInstance.closePopover() + } + + /** + * 设置节点属性 + * 包括宽度、高度、文本编辑属性等 + */ + 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 registerSleepNode(lf) { + lf.register({ + type: "sleep", + view: SleepNodeHtmlNode, + model: SleepNodeHtmlModel + }); +} diff --git a/src/views/flow/nodes/function/sleep.vue b/src/views/flow/nodes/function/sleep.vue index 97379fa..3a031ce 100644 --- a/src/views/flow/nodes/function/sleep.vue +++ b/src/views/flow/nodes/function/sleep.vue @@ -1,6 +1,6 @@ diff --git a/src/views/flow/nodes/function/stopLoop.js b/src/views/flow/nodes/function/stopLoop.js index dee7769..83370be 100644 --- a/src/views/flow/nodes/function/stopLoop.js +++ b/src/views/flow/nodes/function/stopLoop.js @@ -1,208 +1,191 @@ -// 导入 HtmlNode 及其模型,为后续继承做准备 -import { HtmlNode, HtmlNodeModel } from "@logicflow/core"; -// 导入 Vue 相关方法,用于渲染组件 -import { createApp, h, nextTick } from "vue"; -import ElementPlus from "element-plus"; -// 导入 Vue 组件 -import StopLoop from "./stopLoop.vue"; -import JsonViewer from 'vue3-json-viewer' - -/** - * 定义一个 元素的 HTML 节点类,继承自 HtmlNode - * 该类负责在 HTML 中渲染 元素,并处理其交互逻辑 - */ -class StopLoopHtmlNode extends HtmlNode { - resizeObserver = null; - isMounted; // 标记组件是否已挂载 - r; // 渲染函数 - app; // Vue 应用实例 - container = null; - - /** - * 构造函数 - * @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(StopLoop, { - model: props.model, - graphModel: props.graphModel, - properties: { - ...props.model.getProperties(), - }, - text: props.model.text.value, - onContentChange: this.handleContentChange.bind(this), - onBindRef: this.handleComponentInstance.bind(this), - }); - - // 创建 Vue 应用实例,并指定渲染函数 - this.app = createApp({ - render: () => this.r, - }); - } - - /** - * 将 HTML 内容设置到指定的根元素上 - * @param rootEl 根元素 - */ - async setHtml(rootEl) { - 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); // 关键:单独注册ElementPlus - this.app.use(JsonViewer) - this.app.mount(this.container); - await nextTick(); - this.setupSizeObserver(); - } 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 StopLoopHtmlModel extends HtmlNodeModel { - initNodeData(data) { - super.initNodeData(data); - } - - // 保存组件实例引用 - setComponentInstance(instance) { - this.componentInstance = instance; - } - - // 验证表单 - async validateForm() { - if (this.componentInstance && this.componentInstance.validate) { - try { - const result = await this.componentInstance.validate(); - if (result) { - return { valid: true, message: "验证通过" }; - } else { - return { valid: false, message: "验证失败" }; - } - } catch { - return { valid: false, message: "验证失败" }; - } - } - return { valid: true, message: "无验证方法" }; - } - - /** - * 设置节点属性 - * 包括宽度、高度、文本编辑属性等 - */ - 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, - y: y - 25, - 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 registerStopLoopNode(lf) { - lf.register({ - type: "stopLoop", - view: StopLoopHtmlNode, - model: StopLoopHtmlModel, - }); -} +// 导入 HtmlNode 及其模型,为后续继承做准备 +import { HtmlNode, HtmlNodeModel } from "@logicflow/core"; +// 导入 Vue 相关方法,用于渲染组件 +import { createApp, h, nextTick } from "vue"; +import ElementPlus from "element-plus"; +// 导入 Vue 组件 +import StopLoop from "./stopLoop.vue"; +import JsonViewer from 'vue3-json-viewer' + +/** + * 定义一个 元素的 HTML 节点类,继承自 HtmlNode + * 该类负责在 HTML 中渲染 元素,并处理其交互逻辑 + */ +class StopLoopHtmlNode extends HtmlNode { + resizeObserver = null; + isMounted; // 标记组件是否已挂载 + r; // 渲染函数 + app; // Vue 应用实例 + container = null; + + /** + * 构造函数 + * @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(StopLoop, { + model: props.model, + graphModel: props.graphModel, + properties: { + ...props.model.getProperties(), + }, + text: props.model.text.value, + onContentChange: this.handleContentChange.bind(this), + onBindRef: this.handleComponentInstance.bind(this), + }); + + // 创建 Vue 应用实例,并指定渲染函数 + this.app = createApp({ + render: () => this.r, + }); + } + + /** + * 将 HTML 内容设置到指定的根元素上 + * @param rootEl 根元素 + */ + async setHtml(rootEl) { + 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); // 关键:单独注册ElementPlus + this.app.use(JsonViewer) + this.app.mount(this.container); + await nextTick(); + this.setupSizeObserver(); + } 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 StopLoopHtmlModel extends HtmlNodeModel { + initNodeData(data) { + super.initNodeData(data); + } + + // 保存组件实例引用 + setComponentInstance(instance) { + this.componentInstance = instance; + } + + /** + * 设置节点属性 + * 包括宽度、高度、文本编辑属性等 + */ + 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, + y: y - 25, + 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 registerStopLoopNode(lf) { + lf.register({ + type: "stopLoop", + view: StopLoopHtmlNode, + model: StopLoopHtmlModel, + }); +} diff --git a/src/views/flow/nodes/function/subEnd.js b/src/views/flow/nodes/function/subEnd.js index a872114..fd7c6ae 100644 --- a/src/views/flow/nodes/function/subEnd.js +++ b/src/views/flow/nodes/function/subEnd.js @@ -1,203 +1,186 @@ -// 导入 HtmlNode 及其模型,为后续继承做准备 -import { HtmlNode, HtmlNodeModel } from "@logicflow/core"; -// 导入 Vue 相关方法,用于渲染组件 -import { createApp, h, nextTick } from "vue"; -import ElementPlus from "element-plus"; -// 导入 Vue 组件 -import SubEndNode from "./subEnd.vue"; - -/** - * 定义一个 元素的 HTML 节点类,继承自 HtmlNode - * 该类负责在 HTML 中渲染 元素,并处理其交互逻辑 - */ -class SubEndNodeHtmlNode extends HtmlNode { - resizeObserver = null; - isMounted; // 标记组件是否已挂载 - r; // 渲染函数 - app; // Vue 应用实例 - container = null; - - /** - * 构造函数 - * @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(SubEndNode, { - model: props.model, - graphModel: props.graphModel, - properties: { - ...props.model.getProperties(), - } - }); - - // 创建 Vue 应用实例,并指定渲染函数 - this.app = createApp({ - render: () => this.r, - }); - } - - /** - * 将 HTML 内容设置到指定的根元素上 - * @param rootEl 根元素 - */ - async setHtml(rootEl) { - 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); // 关键:单独注册ElementPlus - this.app.mount(this.container); - await nextTick(); - this.setupSizeObserver(); - } 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 SubEndNodeHtmlModel extends HtmlNodeModel { - initNodeData(data) { - super.initNodeData(data); - } - - // 保存组件实例引用 - setComponentInstance(instance) { - this.componentInstance = instance; - } - - // 验证表单 - async validateForm() { - if (this.componentInstance && this.componentInstance.validate) { - try { - const result = await this.componentInstance.validate(); - if (result) { - return { valid: true, message: "验证通过" }; - } else { - return { valid: false, message: "验证失败" }; - } - } catch { - return { valid: false, message: "验证失败" }; - } - } - return { valid: true, message: "无验证方法" }; - } - - /** - * 设置节点属性 - * 包括宽度、高度、文本编辑属性等 - */ - 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, - y: y - 25, - 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 registerSubEndNode(lf) { - lf.register({ - type: "subEnd", - view: SubEndNodeHtmlNode, - model: SubEndNodeHtmlModel, - }); -} +// 导入 HtmlNode 及其模型,为后续继承做准备 +import { HtmlNode, HtmlNodeModel } from "@logicflow/core"; +// 导入 Vue 相关方法,用于渲染组件 +import { createApp, h, nextTick } from "vue"; +import ElementPlus from "element-plus"; +// 导入 Vue 组件 +import SubEndNode from "./subEnd.vue"; + +/** + * 定义一个 元素的 HTML 节点类,继承自 HtmlNode + * 该类负责在 HTML 中渲染 元素,并处理其交互逻辑 + */ +class SubEndNodeHtmlNode extends HtmlNode { + resizeObserver = null; + isMounted; // 标记组件是否已挂载 + r; // 渲染函数 + app; // Vue 应用实例 + container = null; + + /** + * 构造函数 + * @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(SubEndNode, { + model: props.model, + graphModel: props.graphModel, + properties: { + ...props.model.getProperties(), + } + }); + + // 创建 Vue 应用实例,并指定渲染函数 + this.app = createApp({ + render: () => this.r, + }); + } + + /** + * 将 HTML 内容设置到指定的根元素上 + * @param rootEl 根元素 + */ + async setHtml(rootEl) { + 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); // 关键:单独注册ElementPlus + this.app.mount(this.container); + await nextTick(); + this.setupSizeObserver(); + } 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 SubEndNodeHtmlModel extends HtmlNodeModel { + initNodeData(data) { + super.initNodeData(data); + } + + // 保存组件实例引用 + setComponentInstance(instance) { + this.componentInstance = instance; + } + + /** + * 设置节点属性 + * 包括宽度、高度、文本编辑属性等 + */ + 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, + y: y - 25, + 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 registerSubEndNode(lf) { + lf.register({ + type: "subEnd", + view: SubEndNodeHtmlNode, + model: SubEndNodeHtmlModel, + }); +} diff --git a/src/views/flow/nodes/function/subStart.js b/src/views/flow/nodes/function/subStart.js index a160f5e..9006d2a 100644 --- a/src/views/flow/nodes/function/subStart.js +++ b/src/views/flow/nodes/function/subStart.js @@ -1,202 +1,185 @@ -// 导入 HtmlNode 及其模型,为后续继承做准备 -import { HtmlNode, HtmlNodeModel } from "@logicflow/core"; -// 导入 Vue 相关方法,用于渲染组件 -import { createApp, h, nextTick } from "vue"; -import ElementPlus from "element-plus"; -// 导入 Vue 组件 -import SubStartNode from "./subStart.vue"; - -/** - * 定义一个 元素的 HTML 节点类,继承自 HtmlNode - * 该类负责在 HTML 中渲染 元素,并处理其交互逻辑 - */ -class SubStartNodeHtmlNode extends HtmlNode { - resizeObserver = null; - isMounted; // 标记组件是否已挂载 - r; // 渲染函数 - app; // Vue 应用实例 - container = null; - - /** - * 构造函数 - * @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(SubStartNode, { - model: props.model, - graphModel: props.graphModel, - properties: { - ...props.model.getProperties(), - } - }); - - // 创建 Vue 应用实例,并指定渲染函数 - this.app = createApp({ - render: () => this.r, - }); - } - - /** - * 将 HTML 内容设置到指定的根元素上 - * @param rootEl 根元素 - */ - async setHtml(rootEl) { - 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); // 关键:单独注册ElementPlus - this.app.mount(this.container); - await nextTick(); - this.setupSizeObserver(); - } 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 SubStartNodeHtmlModel extends HtmlNodeModel { - initNodeData(data) { - super.initNodeData(data); - } - - // 保存组件实例引用 - setComponentInstance(instance) { - this.componentInstance = instance; - } - - // 验证表单 - async validateForm() { - if (this.componentInstance && this.componentInstance.validate) { - try { - const result = await this.componentInstance.validate(); - if (result) { - return { valid: true, message: "验证通过" }; - } else { - return { valid: false, message: "验证失败" }; - } - } catch { - return { valid: false, message: "验证失败" }; - } - } - return { valid: true, message: "无验证方法" }; - } - - /** - * 设置节点属性 - * 包括宽度、高度、文本编辑属性等 - */ - 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 - 24, - y: y - 25, - name: "right", - id: "".concat(this.id, "_1") - }, - ]; - } - - /** - * 获取节点轮廓样式 - * 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果 - * @returns {object} 节点轮廓样式 - */ - getOutlineStyle() { - const style = super.getOutlineStyle(); - style.stroke = "none"; - style.hover.stroke = "none"; - return style; - } -} - -// 导出方法注册 -export function registerSubStartNode(lf) { - lf.register({ - type: "subStart", - view: SubStartNodeHtmlNode, - model: SubStartNodeHtmlModel, - }); -} +// 导入 HtmlNode 及其模型,为后续继承做准备 +import { HtmlNode, HtmlNodeModel } from "@logicflow/core"; +// 导入 Vue 相关方法,用于渲染组件 +import { createApp, h, nextTick } from "vue"; +import ElementPlus from "element-plus"; +// 导入 Vue 组件 +import SubStartNode from "./subStart.vue"; + +/** + * 定义一个 元素的 HTML 节点类,继承自 HtmlNode + * 该类负责在 HTML 中渲染 元素,并处理其交互逻辑 + */ +class SubStartNodeHtmlNode extends HtmlNode { + resizeObserver = null; + isMounted; // 标记组件是否已挂载 + r; // 渲染函数 + app; // Vue 应用实例 + container = null; + + /** + * 构造函数 + * @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(SubStartNode, { + model: props.model, + graphModel: props.graphModel, + properties: { + ...props.model.getProperties(), + } + }); + + // 创建 Vue 应用实例,并指定渲染函数 + this.app = createApp({ + render: () => this.r, + }); + } + + /** + * 将 HTML 内容设置到指定的根元素上 + * @param rootEl 根元素 + */ + async setHtml(rootEl) { + 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); // 关键:单独注册ElementPlus + this.app.mount(this.container); + await nextTick(); + this.setupSizeObserver(); + } 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 SubStartNodeHtmlModel extends HtmlNodeModel { + initNodeData(data) { + super.initNodeData(data); + } + + // 保存组件实例引用 + setComponentInstance(instance) { + this.componentInstance = instance; + } + + /** + * 设置节点属性 + * 包括宽度、高度、文本编辑属性等 + */ + 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 - 24, + y: y - 25, + name: "right", + id: "".concat(this.id, "_1") + }, + ]; + } + + /** + * 获取节点轮廓样式 + * 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果 + * @returns {object} 节点轮廓样式 + */ + getOutlineStyle() { + const style = super.getOutlineStyle(); + style.stroke = "none"; + style.hover.stroke = "none"; + return style; + } +} + +// 导出方法注册 +export function registerSubStartNode(lf) { + lf.register({ + type: "subStart", + view: SubStartNodeHtmlNode, + model: SubStartNodeHtmlModel, + }); +} diff --git a/src/views/flow/nodes/function/switch.js b/src/views/flow/nodes/function/switch.js index ddb7f65..e7ac4f5 100644 --- a/src/views/flow/nodes/function/switch.js +++ b/src/views/flow/nodes/function/switch.js @@ -1,314 +1,318 @@ -// 导入 HtmlNode 及其模型,为后续继承做准备 -import { HtmlNode, HtmlNodeModel, h as svgH } from "@logicflow/core"; -// 导入 Vue 相关方法,用于渲染组件 -import { createApp, h, nextTick } from "vue"; -import ElementPlus from "element-plus"; -// 导入 Vue 组件 -import Switch from "./switch.vue"; -import JsonViewer from 'vue3-json-viewer' - -/** - * 定义一个 元素的 HTML 节点类,继承自 HtmlNode - * 该类负责在 HTML 中渲染 元素,并处理其交互逻辑 - */ -class SwitchHtmlNode 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(Switch, { - model: props.model, - graphModel: props.graphModel, - properties: { - ...props.model.getProperties(), - }, - text: props.model.text.value, - onContentChange: this.handleContentChange.bind(this), - onBindRef: this.handleComponentInstance.bind(this), - // 添加锚点相关回调 - onAddAnchor: this.handleAddAnchor.bind(this), - onRemoveAnchor: this.handleRemoveAnchor.bind(this), - onChangeAnchor: this.handleChangeAnchor.bind(this) - }); - - // 创建 Vue 应用实例,并指定渲染函数 - this.app = createApp({ - render: () => this.r, - }); - } - - /** - * 将 HTML 内容设置到指定的根元素上 - * @param rootEl 根元素 - */ - async setHtml(rootEl) { - const nodeId = this.props.model.id; - if (SwitchHtmlNode.reusePool.has(nodeId)) { - rootEl.appendChild(SwitchHtmlNode.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(); - SwitchHtmlNode.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(); - } - - // 处理添加锚点 - handleAddAnchor(height, anchorId) { - const { model } = this.props - model.addAnchor(height, anchorId) - } - - // 处理删除锚点 - handleRemoveAnchor(anchorId) { - this.props.model.removeAnchor(anchorId); - } - - handleChangeAnchor(height, anchorId) { - const { model } = this.props - model.changeAnchorHeight(height, anchorId) - } - - // 渲染完成后获取实际尺寸 - 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 SwitchHtmlModel extends HtmlNodeModel { - - initNodeData(data) { - super.initNodeData(data); - } - - // 返回动态锚点 - addAnchor(height, anchorId) { - const properties = this.getProperties() - const anchor = properties?.anchor || [] - anchor.push({ - id: anchorId, - name: 'right', - x: this.x, - y: this.y, - height - }) - this.setProperties({ - ...properties, - anchor - }) - this.getDefaultAnchor() - this.initNodeData(this) - } - - removeAnchor(anchorId) { - const properties = this.getProperties() - const anchor = properties?.anchor || [] - let nweAnchor = anchor.filter(item => item.id !== anchorId); - this.setProperties({ - ...properties, - anchor: nweAnchor - }) - } - - changeAnchorHeight(height, anchorId) { - const properties = this.getProperties() - const anchor = properties?.anchor || [] - anchor.forEach(item => { - if (item.id === anchorId) { - item.height = height - } - }) - this.setProperties({ - ...properties, - anchor - }) - this.getDefaultAnchor() - this.initNodeData(this) - } - - // 保存组件实例引用 - setComponentInstance(instance) { - this.componentInstance = instance; - } - - // 验证表单 - async validateForm() { - if (this.componentInstance && this.componentInstance.validate) { - try { - const result = await this.componentInstance.validate(); - if (result) { - return { valid: true, message: "验证通过" }; - } else { - return { valid: false, message: "验证失败" }; - } - } catch { - return { valid: false, message: "验证失败" }; - } - } - return { valid: true, message: "无验证方法" }; - } - - /** - * 设置节点属性 - * 包括宽度、高度、文本编辑属性等 - */ - setAttributes() { - // 初始设置为0,后续动态更新 - this.width = 0; - this.height = 0; - this.text.editable = false; - } - - updateSize(width, height) { - this.width = width + 24; - this.height = height + 50; - // const properties = lf.getProperties(this.id) - // this.setProperties({ - // ...properties, - // width: this.width, - // height: this.height - // }) - this.initNodeData(this); // 触发节点重绘 - } - - // 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。 - getDefaultAnchor() { - let _a = this, - x = _a.x, - y = _a.y, - width = _a.width, - height = _a.height; - const properties = this.getProperties() - const anchor = properties?.anchor || [] - // const { SCALE_Y } = this.graphModel.transformModel; - const ids = [] - anchor.forEach(item => { - ids.push(item.id) - if (item.id !== "".concat(this.id, "_else")) { - item.x = x + width / 2 - 40 - // item.y = (y - height / 2 + item.height + 60) / SCALE_Y - item.y = y - height / 2 + item.height + 60 - } else { - item.x = x + width / 2 - 40 - item.y = y + height / 2 - 80 - } - }) - - return [ - // { - // x: x + width / 2 - 40, - // y: y + height / 2 - 80, - // name: "right", - // id: "".concat(this.id, "_else") - // }, - { - x: x - width / 2, - y: y - 25, - name: "left", - id: "".concat(this.id, "_entry") - }, - ...anchor - ]; - } - - /** - * 获取节点轮廓样式 - * 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果 - * @returns {object} 节点轮廓样式 - */ - getOutlineStyle() { - const style = super.getOutlineStyle(); - style.stroke = "none"; - style.hover.stroke = "none"; - return style; - } -} - -// 导出方法注册 -export function registerSwitch(lf) { - lf.register({ - type: "branch", - view: SwitchHtmlNode, - model: SwitchHtmlModel, - }); -} +// 导入 HtmlNode 及其模型,为后续继承做准备 +import { HtmlNode, HtmlNodeModel, h as svgH } from "@logicflow/core"; +// 导入 Vue 相关方法,用于渲染组件 +import { createApp, h, nextTick } from "vue"; +import ElementPlus from "element-plus"; +// 导入 Vue 组件 +import Switch from "./switch.vue"; +import JsonViewer from 'vue3-json-viewer' + +/** + * 定义一个 元素的 HTML 节点类,继承自 HtmlNode + * 该类负责在 HTML 中渲染 元素,并处理其交互逻辑 + */ +class SwitchHtmlNode 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(Switch, { + model: props.model, + graphModel: props.graphModel, + properties: { + ...props.model.getProperties(), + }, + text: props.model.text.value, + onContentChange: this.handleContentChange.bind(this), + onBindRef: this.handleComponentInstance.bind(this), + // 添加锚点相关回调 + onAddAnchor: this.handleAddAnchor.bind(this), + onRemoveAnchor: this.handleRemoveAnchor.bind(this), + onChangeAnchor: this.handleChangeAnchor.bind(this) + }); + + // 创建 Vue 应用实例,并指定渲染函数 + this.app = createApp({ + render: () => this.r, + }); + } + + /** + * 将 HTML 内容设置到指定的根元素上 + * @param rootEl 根元素 + */ + async setHtml(rootEl) { + const nodeId = this.props.model.id; + if (SwitchHtmlNode.reusePool.has(nodeId)) { + rootEl.appendChild(SwitchHtmlNode.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(); + SwitchHtmlNode.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(); + } + + // 处理添加锚点 + handleAddAnchor(height, anchorId) { + const { model } = this.props + model.addAnchor(height, anchorId) + } + + // 处理删除锚点 + handleRemoveAnchor(anchorId) { + this.props.model.removeAnchor(anchorId); + } + + handleChangeAnchor(height, anchorId) { + const { model } = this.props + model.changeAnchorHeight(height, anchorId) + } + + // 渲染完成后获取实际尺寸 + 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 SwitchHtmlModel extends HtmlNodeModel { + + initNodeData(data) { + super.initNodeData(data); + } + + // 返回动态锚点 + addAnchor(height, anchorId) { + const properties = this.getProperties() + const anchor = properties?.anchor || [] + anchor.push({ + id: anchorId, + name: 'right', + x: this.x, + y: this.y, + height + }) + this.setProperties({ + ...properties, + anchor + }) + this.getDefaultAnchor() + this.initNodeData(this) + } + + removeAnchor(anchorId) { + const properties = this.getProperties() + const anchor = properties?.anchor || [] + let nweAnchor = anchor.filter(item => item.id !== anchorId); + this.setProperties({ + ...properties, + anchor: nweAnchor + }) + } + + changeAnchorHeight(height, anchorId) { + const properties = this.getProperties() + const anchor = properties?.anchor || [] + anchor.forEach(item => { + if (item.id === anchorId) { + item.height = height + } + }) + this.setProperties({ + ...properties, + anchor + }) + this.getDefaultAnchor() + this.initNodeData(this) + } + + // 保存组件实例引用 + setComponentInstance(instance) { + this.componentInstance = instance; + } + + closePopover() { + this.componentInstance.closePopover() + } + + // 验证表单 + async validateForm() { + if (this.componentInstance && this.componentInstance.validate) { + try { + const result = await this.componentInstance.validate(); + if (result) { + return { valid: true, message: "验证通过" }; + } else { + return { valid: false, message: "验证失败" }; + } + } catch { + return { valid: false, message: "验证失败" }; + } + } + return { valid: true, message: "无验证方法" }; + } + + /** + * 设置节点属性 + * 包括宽度、高度、文本编辑属性等 + */ + setAttributes() { + // 初始设置为0,后续动态更新 + this.width = 0; + this.height = 0; + this.text.editable = false; + } + + updateSize(width, height) { + this.width = width + 24; + this.height = height + 50; + // const properties = lf.getProperties(this.id) + // this.setProperties({ + // ...properties, + // width: this.width, + // height: this.height + // }) + this.initNodeData(this); // 触发节点重绘 + } + + // 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。 + getDefaultAnchor() { + let _a = this, + x = _a.x, + y = _a.y, + width = _a.width, + height = _a.height; + const properties = this.getProperties() + const anchor = properties?.anchor || [] + // const { SCALE_Y } = this.graphModel.transformModel; + const ids = [] + anchor.forEach(item => { + ids.push(item.id) + if (item.id !== "".concat(this.id, "_else")) { + item.x = x + width / 2 - 40 + // item.y = (y - height / 2 + item.height + 60) / SCALE_Y + item.y = y - height / 2 + item.height + 60 + } else { + item.x = x + width / 2 - 40 + item.y = y + height / 2 - 80 + } + }) + + return [ + // { + // x: x + width / 2 - 40, + // y: y + height / 2 - 80, + // name: "right", + // id: "".concat(this.id, "_else") + // }, + { + x: x - width / 2, + y: y - 25, + name: "left", + id: "".concat(this.id, "_entry") + }, + ...anchor + ]; + } + + /** + * 获取节点轮廓样式 + * 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果 + * @returns {object} 节点轮廓样式 + */ + getOutlineStyle() { + const style = super.getOutlineStyle(); + style.stroke = "none"; + style.hover.stroke = "none"; + return style; + } +} + +// 导出方法注册 +export function registerSwitch(lf) { + lf.register({ + type: "branch", + view: SwitchHtmlNode, + model: SwitchHtmlModel, + }); +} diff --git a/src/views/flow/nodes/function/switch.vue b/src/views/flow/nodes/function/switch.vue index 66133a6..7bcc0f9 100644 --- a/src/views/flow/nodes/function/switch.vue +++ b/src/views/flow/nodes/function/switch.vue @@ -1,6 +1,6 @@