2026-04-13 10:31:55 +08:00
|
|
|
|
// 导入 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-15 10:59:17 +08:00
|
|
|
|
closePopover() {
|
|
|
|
|
|
this.componentInstance.closePopover()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 10:31:55 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 设置节点属性
|
|
|
|
|
|
* 包括宽度、高度、文本编辑属性等
|
|
|
|
|
|
*/
|
|
|
|
|
|
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('此节点不可删除');
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-08-21 14:11:58 +08:00
|
|
|
|
}
|