204 lines
5.5 KiB
JavaScript
204 lines
5.5 KiB
JavaScript
// 导入 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('此节点不可删除');
|
||
},
|
||
}
|
||
})
|
||
} |