// 导入 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; } /** * 设置节点属性 * 包括宽度、高度、文本编辑属性等 */ 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('此节点不可删除'); }, } }) }