// 导入 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, }); }