2026-06-18 14:49:21 +08:00
|
|
|
|
// 导入 HtmlNode 及其模型,为后续继承做准备
|
|
|
|
|
|
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
|
|
|
|
|
|
// 导入 Vue 相关方法,用于渲染组件
|
|
|
|
|
|
import { createApp, h, nextTick } from "vue";
|
|
|
|
|
|
import ElementPlus from "element-plus";
|
|
|
|
|
|
// 导入 Vue 组件
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
import CodeNode from "./codeNode.vue";
|
|
|
|
|
|
import OuterNode from "../../components/OuterNode.vue";
|
|
|
|
|
|
import JsonViewer from 'vue3-json-viewer'
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 定义一个 元素的 HTML 节点类,继承自 HtmlNode
|
|
|
|
|
|
* 该类负责在 HTML 中渲染 元素,并处理其交互逻辑
|
|
|
|
|
|
*/
|
|
|
|
|
|
class CodeNodeHtmlNode 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: CodeNode,
|
|
|
|
|
|
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 (CodeNodeHtmlNode.reusePool.has(nodeId)) {
|
|
|
|
|
|
rootEl.appendChild(CodeNodeHtmlNode.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();
|
|
|
|
|
|
CodeNodeHtmlNode.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__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 CodeNodeHtmlModel 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 + 4,
|
|
|
|
|
|
y: y,
|
|
|
|
|
|
name: "right",
|
|
|
|
|
|
id: "".concat(this.id, "_1"),
|
|
|
|
|
|
properties: { connectionType: "source" },
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
x: x - width / 2,
|
|
|
|
|
|
y: y,
|
|
|
|
|
|
name: "left",
|
|
|
|
|
|
id: "".concat(this.id, "_3"),
|
|
|
|
|
|
properties: { connectionType: "target" },
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取节点轮廓样式
|
|
|
|
|
|
* 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果
|
|
|
|
|
|
* @returns {object} 节点轮廓样式
|
|
|
|
|
|
*/
|
|
|
|
|
|
getOutlineStyle() {
|
|
|
|
|
|
const style = super.getOutlineStyle();
|
|
|
|
|
|
style.stroke = "none";
|
2026-07-17 10:58:59 +08:00
|
|
|
|
// style.hover.stroke = "none";
|
2026-06-18 14:49:21 +08:00
|
|
|
|
return style;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 导出方法注册
|
|
|
|
|
|
export function registerCodeNode(lf) {
|
|
|
|
|
|
lf.register({
|
|
|
|
|
|
type: "code",
|
|
|
|
|
|
view: CodeNodeHtmlNode,
|
|
|
|
|
|
model: CodeNodeHtmlModel,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|