CMVR-IOT-UI/src/views/flow/nodes/common/startNode.js

204 lines
5.7 KiB
JavaScript
Raw Normal View History

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
}