feat: 清除上一次运行结果

This commit is contained in:
zhanghao 2026-04-15 10:59:17 +08:00
parent 8c1820e1a6
commit 783e54bab0
21 changed files with 1393 additions and 1371 deletions

View File

@ -1,22 +1,22 @@
import { defineStore } from 'pinia'
export const useFlowStore = defineStore('flow', {
state: () => ({
disableForm: false,
deviceList: []
}),
actions: {
updateDisableForm(value) {
this.disableForm = value
},
async getDeviceList() {
const data = await new Promise((resolve, reject) => {
resolve( ['cam1', 'cam2', 'cam3', 'cam4'])
})
this.deviceList = data
return data
}
}
}
)
import { defineStore } from 'pinia'
export const useFlowStore = defineStore('flow', {
state: () => ({
disableForm: false,
deviceList: []
}),
actions: {
updateDisableForm(value) {
this.disableForm = value
},
async getDeviceList() {
const data = await new Promise((resolve, reject) => {
resolve( ['cam1', 'cam2', 'cam3', 'cam4'])
})
this.deviceList = data
return data
}
}
}
)

View File

@ -1,51 +1,57 @@
<template>
<div>
<div class="state__container" :class="`nodeState-${props.state}`">
<div class="context">
<div class="state">
<el-icon>
<SuccessFilled v-if="props.state === 'SUCCESS'" />
<CircleCloseFilled v-if="['FAILED', 'STOPPED'].includes(props.state)" />
<VideoPause v-if="props.state === 'PAUSED'" />
<Loading v-if="props.state === 'RUNNING'" />
</el-icon>
<div class="text">{{ getStateText() }}</div>
<el-tag :type="props.state === 'SUCCESS' ? 'success' : 'danger'">
{{ props.runtimes }}ms
</el-tag>
</div>
<div class="resultText" v-popover="popoverRef" @click="popoverVisible = !popoverVisible">{{ popoverVisible ? '隐藏结果' : '展示结果' }}</div>
<div>
<div class="state__container" :class="`nodeState-${props.state}`">
<div class="context" v-show="showState !== 'NORMAL'">
<div class="state">
<el-icon>
<SuccessFilled v-if="props.state === 'SUCCESS'" />
<CircleCloseFilled v-if="['FAILED', 'STOPPED'].includes(props.state)" />
<VideoPause v-if="props.state === 'PAUSED'" />
<Loading v-if="props.state === 'RUNNING'" />
</el-icon>
<div class="text">{{ getStateText() }}</div>
<el-tag :type="props.state === 'SUCCESS' ? 'success' : 'danger'">
{{ props.runtimes }}ms
</el-tag>
</div>
<div
class="resultText"
v-popover="popoverRef"
@click="popoverVisible = !popoverVisible"
>
{{ popoverVisible ? "隐藏结果" : "展示结果" }}
</div>
<el-popover
ref="popoverRef"
virtual-triggering
persistent
placement="right-start"
:visible="popoverVisible"
:teleported="false"
popper-class="popover__container"
>
<template #default>
<div class="params__container input">
<div class="paramsTitle">输入</div>
<div>
<slot name="input"></slot>
</div>
</div>
<div class="params__container output" v-if="slots.output">
<div class="paramsTitle">输出</div>
<div>
<slot name="output"></slot>
</div>
</div>
</template>
</el-popover>
</div>
</div>
<el-popover
ref="popoverRef"
virtual-triggering
persistent
placement="right-start"
:visible="popoverVisible"
:teleported="false"
popper-class="popover__container"
>
<template #default>
<div class="params__container input">
<div class="paramsTitle">输入</div>
<div>
<slot name="input"></slot>
</div>
</div>
<div class="params__container output" v-if="slots.output">
<div class="paramsTitle">输出</div>
<div>
<slot name="output"></slot>
</div>
</div>
</template>
</el-popover>
</div>
</template>
<script setup lang="js">
import { ref, useSlots } from 'vue'
import { ref, useSlots, watch } from 'vue'
import { SuccessFilled, CircleCloseFilled, Loading, VideoPause } from '@element-plus/icons-vue'
const props = defineProps({
@ -56,7 +62,7 @@ const props = defineProps({
runtimes: {
type: Number,
default: 0
}
},
})
const slots = useSlots()
@ -76,83 +82,95 @@ const getStateText = () => {
const popoverRef = ref();
const popoverVisible = ref(false);
const showState = ref('NORMAL')
watch(() => props.state, (newVal) => {
showState.value = newVal;
});
const closePopover = () => {
showState.value = 'NORMAL';
popoverVisible.value = false;
};
defineExpose({ closePopover })
</script>
<style lang="scss" scoped>
.state__container {
display: none;
margin-bottom: 8px;
display: none;
margin-bottom: 8px;
.context {
.context {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px;
font-size: 12px;
.state {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px;
font-size: 12px;
.state {
display: flex;
align-items: center;
.text {
margin: 0 8px;
}
}
.resultText {
color: #1664ff;
cursor: pointer;
.text {
margin: 0 8px;
}
}
}
.nodeState-SUCCESS {
display: block;
background-color: #eef9f1;
.el-icon {
font-size: 16px;
color: #309256;
.resultText {
color: #1664ff;
cursor: pointer;
}
}
}
.nodeState-FAILED {
display: block;
background-color: #fdf5f5;
.nodeState-SUCCESS {
display: block;
background-color: #eef9f1;
.el-icon {
font-size: 16px;
color: #ee3f38;
}
.el-icon {
font-size: 16px;
color: #309256;
}
}
.nodeState-STOPPED {
display: block;
background-color: #fdf5f5;
.nodeState-FAILED {
display: block;
background-color: #fdf5f5;
.el-icon {
font-size: 16px;
color: #ee3f38;
}
.el-icon {
font-size: 16px;
color: #ee3f38;
}
}
.nodeState-PAUSED {
display: block;
background-color: #cbcbcb;
.nodeState-STOPPED {
display: block;
background-color: #fdf5f5;
.el-icon {
font-size: 16px;
color: #666;
}
.el-icon {
font-size: 16px;
color: #ee3f38;
}
}
.nodeState-RUNNING {
display: block;
background-color: #f4f7ff;
.nodeState-PAUSED {
display: block;
background-color: #cbcbcb;
.el-icon {
color: #68a2e4;
font-size: 16px;
animation: rotateAnimation 2s linear infinite;
}
.el-icon {
font-size: 16px;
color: #666;
}
</style>
}
.nodeState-RUNNING {
display: block;
background-color: #f4f7ff;
.el-icon {
color: #68a2e4;
font-size: 16px;
animation: rotateAnimation 2s linear infinite;
}
}
</style>

View File

@ -8,21 +8,12 @@
</div>
<div class="btn__container">
<div class="btn">
<el-button v-if="['testRunError', 'testRunFinish', 'stop'].includes(flowState)" @click="clearRun">清除上一次运行结果</el-button>
<el-button @click="group">分组</el-button>
<el-button v-if="flowState !== 'testRunning'" @click="testRun"
>试运行</el-button
>
<el-button v-if="flowState === 'testRunning'" @click="flowPauseFn"
>暂停</el-button
>
<el-button v-if="flowState === 'pause'" @click="flowResumeFn"
>恢复</el-button
>
<el-button
v-if="['testRunning', 'pause'].includes(flowState)"
@click="flowStopFn"
>终止</el-button
>
<el-button v-if="flowState !== 'testRunning'" @click="testRun">试运行</el-button>
<el-button v-if="flowState === 'testRunning'" @click="flowPauseFn">暂停</el-button>
<el-button v-if="flowState === 'pause'" @click="flowResumeFn">恢复</el-button>
<el-button v-if="['testRunning', 'pause'].includes(flowState)" @click="flowStopFn">终止</el-button>
</div>
<div class="btn">
<el-tooltip
@ -34,9 +25,7 @@
>
<el-button type="primary" :disabled="true">发布</el-button>
</el-tooltip>
<el-button v-else @click="deployFlow" type="primary"
>发布</el-button
>
<el-button v-else @click="deployFlow" type="primary">发布</el-button>
<el-popover
:width="260"
@ -123,9 +112,7 @@
<el-form-item
:label="index === 0 ? '参数名' : ''"
:prop="`nodeParams.${index}.name`"
:rules="[
{ required: true, message: '请输入参数名', trigger: 'blur' },
]"
:rules="[{ required: true, message: '请输入参数名', trigger: 'blur' }]"
>
<el-input
:disabled="property?.disabled || false"
@ -137,9 +124,7 @@
<el-form-item
:label="index === 0 ? '参数值' : ''"
:prop="`nodeParams.${index}.input`"
:rules="[
{ required: true, message: '请输入参数值', trigger: 'blur' },
]"
:rules="[{ required: true, message: '请输入参数值', trigger: 'blur' }]"
>
<el-input-number
v-if="property.componentType === 'number'"
@ -176,9 +161,7 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="execute">
确定
</el-button>
<el-button type="primary" @click="execute"> 确定 </el-button>
</div>
</template>
</el-dialog>
@ -203,7 +186,7 @@ import {
flowPause,
flowResume,
flowStop,
flowAction
flowAction,
} from "@/api/device/flow";
import { getDetect } from "@/api/test/detect";
import { Location } from "@element-plus/icons-vue";
@ -233,9 +216,11 @@ const drop = (e) => {
},
});
if (!["selectArea", 'branch', 'stopLoop', 'subStart', 'subEnd'].includes(newNode.type)) {
if (
!["selectArea", "branch", "stopLoop", "subStart", "subEnd"].includes(newNode.type)
) {
showParamsDrawer.value = true;
console.log('newNode', newNode)
console.log("newNode", newNode);
paramsDrawerData.value = newNode;
}
};
@ -444,8 +429,7 @@ const loopFlowView = async (instId, taskId = null) => {
} else {
if (!flowInfoData.value.isLog) {
if (["PAUSED", "STOPPED"].includes(arr[arr.length - 1]?.status)) {
flowState.value =
arr[arr.length - 1]?.status === "PAUSED" ? "pause" : "stop";
flowState.value = arr[arr.length - 1]?.status === "PAUSED" ? "pause" : "stop";
} else {
flowState.value = "testRunError";
flowStore.updateDisableForm(false);
@ -562,10 +546,7 @@ const initFlow = () => {
keys: ["backspace", "delete"], //
callback: () => {
const activeElem = document.activeElement;
if (
activeElem.tagName === "INPUT" ||
activeElem.tagName === "TEXTAREA"
) {
if (activeElem.tagName === "INPUT" || activeElem.tagName === "TEXTAREA") {
return; //
}
@ -657,10 +638,12 @@ const initFlow = () => {
});
lf.on("node:dbclick", ({ data, e }) => {
if (["selectArea", 'branch', 'stopLoop', 'subStart', 'subEnd'].includes(data.type)) {
if (
["selectArea", "branch", "stopLoop", "subStart", "subEnd"].includes(data.type)
) {
return;
}
console.log('data', data)
console.log("data", data);
showParamsDrawer.value = true;
paramsDrawerData.value = data;
});
@ -734,55 +717,65 @@ const fitView = () => {
};
const nodeName = ref("");
const visible = ref(false)
const visible = ref(false);
const formData = reactive({
nodeParams: [],
})
const rules = reactive({})
const executeForm = ref()
const nodeAction = ref('')
});
const rules = reactive({});
const executeForm = ref();
const nodeAction = ref("");
const singleNodeExecution = (e) => {
const { name, nodeType, nodeParams, action } = e.detail;
nodeName.value = name;
visible.value = true;
nodeAction.value = action
if (nodeType === 'EDGE') {
nodeAction.value = action;
if (nodeType === "EDGE") {
formData.nodeParams = [
{
disabled: true,
input: "",
name: "terminalId",
type: "input"
type: "input",
},
...nodeParams
]
...nodeParams,
];
} else {
formData.nodeParams = nodeParams
formData.nodeParams = nodeParams;
}
};
const execute = async () => {
const result = await executeForm.value.validate();
if (result) {
const obj = {}
formData.nodeParams.forEach(item => {
obj[item.name] = item.input
})
const obj = {};
formData.nodeParams.forEach((item) => {
obj[item.name] = item.input;
});
const res = await flowAction({
action: nodeAction.value,
payload: obj
})
payload: obj,
});
if (res.code === 200) {
ElMessage.success('执行成功')
ElMessage.success("执行成功");
} else {
ElMessage.error(res.msg)
ElMessage.error(res.msg);
}
}
}
};
const showParamsDrawer = ref(false);
const paramsDrawerData = ref({})
const paramsDrawerData = ref({});
const clearRun = () => {
const { nodes } = lf.getGraphData();
nodes.forEach((item) => {
const node = lf.getNodeModelById(item.id);
if (node.closePopover) {
node.closePopover();
}
});
}
onMounted(() => {
justLoadFlow();
@ -876,8 +869,6 @@ onUnmounted(() => {
}
}
}
</style>
<style lang="scss">
.node-relationship-network-popover {
@ -903,16 +894,16 @@ onUnmounted(() => {
}
.el-dialog {
.el-input {
width: 200px;
}
.el-input {
width: 200px;
}
.el-select {
width: 200px;
}
.el-select {
width: 200px;
}
.el-textarea {
width: 200px
}
.el-textarea {
width: 200px;
}
}
</style>

View File

@ -144,16 +144,10 @@ class ServiceNodeHtmlModel extends HtmlNodeModel {
this.componentInstance = instance;
}
// 验证表单
async validateForm() {
const result = this.componentInstance.validateForm()
return result
closePopover() {
this.componentInstance.closePopover()
}
// setCustomProperties() {
// this.componentInstance.setNodeProperties()
// }
/**
* 设置节点属性
* 包括宽度高度文本编辑属性等

View File

@ -1,6 +1,6 @@
<template>
<div class="node__container" :class="props.model.id">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
<template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template>
@ -78,6 +78,14 @@ const inputJsonData = ref({});
const outputJsonData = ref({});
const errorInfoData = ref('');
const nodeStateRef = ref(null);
const closePopover = () => {
if (nodeStateRef.value) {
nodeStateRef.value.closePopover();
}
}
onMounted(() => {
emitter.on("changeNodeState", (data) => {
@ -124,6 +132,8 @@ onUnmounted(() => {
emitter.off("setProperties");
});
defineExpose({ closePopover })
</script>
<style lang="scss" scoped>

View File

@ -142,6 +142,10 @@ class StartNodeHtmlModel extends HtmlNodeModel {
this.componentInstance = instance;
}
closePopover() {
this.componentInstance.closePopover()
}
/**
* 设置节点属性
* 包括宽度高度文本编辑属性等

View File

@ -1,7 +1,7 @@
<template>
<div class="node__container" :class="props.model.id">
<keep-alive>
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
<template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template>
@ -40,6 +40,14 @@ const runtimes = ref(0);
const inputJsonData = ref({});
const outputJsonData = ref({});
const nodeStateRef = ref(null);
const closePopover = () => {
if (nodeStateRef.value) {
nodeStateRef.value.closePopover();
}
}
onMounted(() => {
emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) {
@ -87,6 +95,8 @@ onUnmounted(() => {
emitter.off("setProperties");
});
defineExpose({ closePopover })
</script>
<style lang="scss" scoped>

View File

@ -144,6 +144,10 @@ class CodeNodeHtmlModel extends HtmlNodeModel {
this.componentInstance = instance;
}
closePopover() {
this.componentInstance.closePopover()
}
/**
* 设置节点属性
* 包括宽度高度文本编辑属性等

View File

@ -1,6 +1,6 @@
<template>
<div class="node__container" :class="props.model.id">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
<template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template>
@ -74,6 +74,15 @@ const inputJsonData = ref({});
const outputJsonData = ref({});
const errorInfoData = ref('');
const nodeStateRef = ref(null);
const closePopover = () => {
if (nodeStateRef.value) {
nodeStateRef.value.closePopover();
}
}
onMounted(() => {
emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) {
@ -119,6 +128,7 @@ onUnmounted(() => {
emitter.off("setProperties");
});
defineExpose({ closePopover })
</script>
<style lang="scss" scoped>

View File

@ -1,6 +1,6 @@
<template>
<div class="node__container" :class="props.model.id">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
<template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template>
@ -46,12 +46,6 @@ const props = defineProps({
const emits = defineEmits(["contentChange"]);
const formData = reactive({
nodeParams: [],
});
const setNodeName = (name) => {
const properties = lf.getProperties(props.model.id);
lf.setProperties(props.model.id, {
@ -66,6 +60,14 @@ const runtimes = ref(0);
const inputJsonData = ref({});
const outputJsonData = ref({});
const nodeStateRef = ref(null);
const closePopover = () => {
if (nodeStateRef.value) {
nodeStateRef.value.closePopover();
}
}
onMounted(() => {
emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) {
@ -96,13 +98,22 @@ onMounted(() => {
emits("contentChange");
}
});
emitter.on("setProperties", (data) => {
if (data.id === props.model.id) {
emits("contentChange");
}
});
});
onUnmounted(() => {
emitter.off("changeNodeState");
emitter.off("contentChange");
emitter.off("setProperties");
});
defineExpose({ closePopover })
</script>
<style lang="scss" scoped>

View File

@ -150,9 +150,9 @@ class CurrentLoopNodeHtmlModel extends HtmlNodeModel {
return result
}
// setCustomProperties() {
// this.componentInstance.setNodeProperties()
// }
closePopover() {
this.componentInstance.closePopover()
}
/**
* 设置节点属性

View File

@ -144,15 +144,9 @@ class HttpNodeHtmlModel extends HtmlNodeModel {
this.componentInstance = instance;
}
// 验证表单
// async validateForm() {
// const result = this.componentInstance.validateForm()
// return result
// }
// setCustomProperties() {
// this.componentInstance.setNodeProperties()
// }
closePopover() {
this.componentInstance.closePopover()
}
/**
* 设置节点属性

View File

@ -1,6 +1,6 @@
<template>
<div class="node__container" :class="props.model.id">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
<template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template>
@ -77,6 +77,13 @@ const inputJsonData = ref({});
const outputJsonData = ref({});
const errorInfoData = ref('');
const nodeStateRef = ref(null);
const closePopover = () => {
if (nodeStateRef.value) {
nodeStateRef.value.closePopover();
}
}
onMounted(() => {
emitter.on("changeNodeState", (data) => {
@ -122,6 +129,8 @@ onUnmounted(() => {
emitter.off("contentChange");
emitter.off("setProperties");
});
defineExpose({ closePopover })
</script>
<style lang="scss" scoped>

View File

@ -1,219 +1,213 @@
// 导入 HtmlNode 及其模型,为后续继承做准备
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
// 导入 Vue 相关方法,用于渲染组件
import { createApp, h, nextTick } from "vue";
import ElementPlus from "element-plus";
// 导入 Vue 组件
// @ts-ignore
import Sleep from "./sleep.vue";
import OuterNode from "../../components/OuterNode.vue";
import JsonViewer from 'vue3-json-viewer'
/**
* 定义一个 元素的 HTML 节点类继承自 HtmlNode
* 该类负责在 HTML 中渲染 元素并处理其交互逻辑
*/
class SleepNodeHtmlNode 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: Sleep,
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 (SleepNodeHtmlNode.reusePool.has(nodeId)) {
rootEl.appendChild(SleepNodeHtmlNode.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();
SleepNodeHtmlNode.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 SleepNodeHtmlModel extends HtmlNodeModel {
initNodeData(data) {
super.initNodeData(data);
}
// 保存组件实例引用
setComponentInstance(instance) {
this.componentInstance = instance;
}
// 验证表单
async validateForm() {
const result = this.componentInstance.validateForm()
return result
}
setCustomProperties() {
this.componentInstance.setNodeProperties()
}
/**
* 设置节点属性
* 包括宽度高度文本编辑属性等
*/
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";
style.hover.stroke = "none";
return style;
}
}
// 导出方法注册
export function registerSleepNode(lf) {
lf.register({
type: "sleep",
view: SleepNodeHtmlNode,
model: SleepNodeHtmlModel
});
}
// 导入 HtmlNode 及其模型,为后续继承做准备
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
// 导入 Vue 相关方法,用于渲染组件
import { createApp, h, nextTick } from "vue";
import ElementPlus from "element-plus";
// 导入 Vue 组件
// @ts-ignore
import Sleep from "./sleep.vue";
import OuterNode from "../../components/OuterNode.vue";
import JsonViewer from 'vue3-json-viewer'
/**
* 定义一个 元素的 HTML 节点类继承自 HtmlNode
* 该类负责在 HTML 中渲染 元素并处理其交互逻辑
*/
class SleepNodeHtmlNode 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: Sleep,
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 (SleepNodeHtmlNode.reusePool.has(nodeId)) {
rootEl.appendChild(SleepNodeHtmlNode.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();
SleepNodeHtmlNode.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 SleepNodeHtmlModel 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";
style.hover.stroke = "none";
return style;
}
}
// 导出方法注册
export function registerSleepNode(lf) {
lf.register({
type: "sleep",
view: SleepNodeHtmlNode,
model: SleepNodeHtmlModel
});
}

View File

@ -1,6 +1,6 @@
<template>
<div class="node__container" :class="props.model.id">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
<template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template>
@ -33,7 +33,7 @@
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from "vue";
import { ref, onMounted, onUnmounted } from "vue";
import NodeTitle from "../../components/NodeTitle.vue";
import NodeState from "../../components/NodeState.vue";
import "vue3-json-viewer/dist/index.css";
@ -61,6 +61,14 @@ const runtimes = ref(0);
const inputJsonData = ref({});
const outputJsonData = ref({});
const nodeStateRef = ref(null);
const closePopover = () => {
if (nodeStateRef.value) {
nodeStateRef.value.closePopover();
}
}
onMounted(() => {
emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) {
@ -105,6 +113,8 @@ onUnmounted(() => {
emitter.off("setProperties");
});
defineExpose({ closePopover })
</script>
<style lang="scss" scoped>

View File

@ -1,208 +1,191 @@
// 导入 HtmlNode 及其模型,为后续继承做准备
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
// 导入 Vue 相关方法,用于渲染组件
import { createApp, h, nextTick } from "vue";
import ElementPlus from "element-plus";
// 导入 Vue 组件
import StopLoop from "./stopLoop.vue";
import JsonViewer from 'vue3-json-viewer'
/**
* 定义一个 元素的 HTML 节点类继承自 HtmlNode
* 该类负责在 HTML 中渲染 元素并处理其交互逻辑
*/
class StopLoopHtmlNode extends HtmlNode {
resizeObserver = null;
isMounted; // 标记组件是否已挂载
r; // 渲染函数
app; // Vue 应用实例
container = null;
/**
* 构造函数
* @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(StopLoop, {
model: props.model,
graphModel: props.graphModel,
properties: {
...props.model.getProperties(),
},
text: props.model.text.value,
onContentChange: this.handleContentChange.bind(this),
onBindRef: this.handleComponentInstance.bind(this),
});
// 创建 Vue 应用实例,并指定渲染函数
this.app = createApp({
render: () => this.r,
});
}
/**
* HTML 内容设置到指定的根元素上
* @param rootEl 根元素
*/
async setHtml(rootEl) {
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();
} 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 StopLoopHtmlModel extends HtmlNodeModel {
initNodeData(data) {
super.initNodeData(data);
}
// 保存组件实例引用
setComponentInstance(instance) {
this.componentInstance = instance;
}
// 验证表单
async validateForm() {
if (this.componentInstance && this.componentInstance.validate) {
try {
const result = await this.componentInstance.validate();
if (result) {
return { valid: true, message: "验证通过" };
} else {
return { valid: false, message: "验证失败" };
}
} catch {
return { valid: false, message: "验证失败" };
}
}
return { valid: true, message: "无验证方法" };
}
/**
* 设置节点属性
* 包括宽度高度文本编辑属性等
*/
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,
y: y - 25,
name: "left",
id: "".concat(this.id, "_3"),
properties: { connectionType: "target" },
},
];
}
/**
* 获取节点轮廓样式
* 覆盖父类方法设置 stroke 属性为 none以适应特定的视觉效果
* @returns {object} 节点轮廓样式
*/
getOutlineStyle() {
const style = super.getOutlineStyle();
style.stroke = "none";
style.hover.stroke = "none";
return style;
}
}
// 导出方法注册
export function registerStopLoopNode(lf) {
lf.register({
type: "stopLoop",
view: StopLoopHtmlNode,
model: StopLoopHtmlModel,
});
}
// 导入 HtmlNode 及其模型,为后续继承做准备
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
// 导入 Vue 相关方法,用于渲染组件
import { createApp, h, nextTick } from "vue";
import ElementPlus from "element-plus";
// 导入 Vue 组件
import StopLoop from "./stopLoop.vue";
import JsonViewer from 'vue3-json-viewer'
/**
* 定义一个 元素的 HTML 节点类继承自 HtmlNode
* 该类负责在 HTML 中渲染 元素并处理其交互逻辑
*/
class StopLoopHtmlNode extends HtmlNode {
resizeObserver = null;
isMounted; // 标记组件是否已挂载
r; // 渲染函数
app; // Vue 应用实例
container = null;
/**
* 构造函数
* @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(StopLoop, {
model: props.model,
graphModel: props.graphModel,
properties: {
...props.model.getProperties(),
},
text: props.model.text.value,
onContentChange: this.handleContentChange.bind(this),
onBindRef: this.handleComponentInstance.bind(this),
});
// 创建 Vue 应用实例,并指定渲染函数
this.app = createApp({
render: () => this.r,
});
}
/**
* HTML 内容设置到指定的根元素上
* @param rootEl 根元素
*/
async setHtml(rootEl) {
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();
} 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 StopLoopHtmlModel extends HtmlNodeModel {
initNodeData(data) {
super.initNodeData(data);
}
// 保存组件实例引用
setComponentInstance(instance) {
this.componentInstance = instance;
}
/**
* 设置节点属性
* 包括宽度高度文本编辑属性等
*/
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,
y: y - 25,
name: "left",
id: "".concat(this.id, "_3"),
properties: { connectionType: "target" },
},
];
}
/**
* 获取节点轮廓样式
* 覆盖父类方法设置 stroke 属性为 none以适应特定的视觉效果
* @returns {object} 节点轮廓样式
*/
getOutlineStyle() {
const style = super.getOutlineStyle();
style.stroke = "none";
style.hover.stroke = "none";
return style;
}
}
// 导出方法注册
export function registerStopLoopNode(lf) {
lf.register({
type: "stopLoop",
view: StopLoopHtmlNode,
model: StopLoopHtmlModel,
});
}

View File

@ -1,203 +1,186 @@
// 导入 HtmlNode 及其模型,为后续继承做准备
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
// 导入 Vue 相关方法,用于渲染组件
import { createApp, h, nextTick } from "vue";
import ElementPlus from "element-plus";
// 导入 Vue 组件
import SubEndNode from "./subEnd.vue";
/**
* 定义一个 元素的 HTML 节点类继承自 HtmlNode
* 该类负责在 HTML 中渲染 元素并处理其交互逻辑
*/
class SubEndNodeHtmlNode extends HtmlNode {
resizeObserver = null;
isMounted; // 标记组件是否已挂载
r; // 渲染函数
app; // Vue 应用实例
container = null;
/**
* 构造函数
* @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(SubEndNode, {
model: props.model,
graphModel: props.graphModel,
properties: {
...props.model.getProperties(),
}
});
// 创建 Vue 应用实例,并指定渲染函数
this.app = createApp({
render: () => this.r,
});
}
/**
* HTML 内容设置到指定的根元素上
* @param rootEl 根元素
*/
async setHtml(rootEl) {
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.mount(this.container);
await nextTick();
this.setupSizeObserver();
} 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 SubEndNodeHtmlModel extends HtmlNodeModel {
initNodeData(data) {
super.initNodeData(data);
}
// 保存组件实例引用
setComponentInstance(instance) {
this.componentInstance = instance;
}
// 验证表单
async validateForm() {
if (this.componentInstance && this.componentInstance.validate) {
try {
const result = await this.componentInstance.validate();
if (result) {
return { valid: true, message: "验证通过" };
} else {
return { valid: false, message: "验证失败" };
}
} catch {
return { valid: false, message: "验证失败" };
}
}
return { valid: true, message: "无验证方法" };
}
/**
* 设置节点属性
* 包括宽度高度文本编辑属性等
*/
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,
y: y - 25,
name: "left",
id: "".concat(this.id, "_3"),
properties: { connectionType: "target" },
},
];
}
/**
* 获取节点轮廓样式
* 覆盖父类方法设置 stroke 属性为 none以适应特定的视觉效果
* @returns {object} 节点轮廓样式
*/
getOutlineStyle() {
const style = super.getOutlineStyle();
style.stroke = "none";
style.hover.stroke = "none";
return style;
}
}
// 导出方法注册
export function registerSubEndNode(lf) {
lf.register({
type: "subEnd",
view: SubEndNodeHtmlNode,
model: SubEndNodeHtmlModel,
});
}
// 导入 HtmlNode 及其模型,为后续继承做准备
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
// 导入 Vue 相关方法,用于渲染组件
import { createApp, h, nextTick } from "vue";
import ElementPlus from "element-plus";
// 导入 Vue 组件
import SubEndNode from "./subEnd.vue";
/**
* 定义一个 元素的 HTML 节点类继承自 HtmlNode
* 该类负责在 HTML 中渲染 元素并处理其交互逻辑
*/
class SubEndNodeHtmlNode extends HtmlNode {
resizeObserver = null;
isMounted; // 标记组件是否已挂载
r; // 渲染函数
app; // Vue 应用实例
container = null;
/**
* 构造函数
* @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(SubEndNode, {
model: props.model,
graphModel: props.graphModel,
properties: {
...props.model.getProperties(),
}
});
// 创建 Vue 应用实例,并指定渲染函数
this.app = createApp({
render: () => this.r,
});
}
/**
* HTML 内容设置到指定的根元素上
* @param rootEl 根元素
*/
async setHtml(rootEl) {
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.mount(this.container);
await nextTick();
this.setupSizeObserver();
} 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 SubEndNodeHtmlModel extends HtmlNodeModel {
initNodeData(data) {
super.initNodeData(data);
}
// 保存组件实例引用
setComponentInstance(instance) {
this.componentInstance = instance;
}
/**
* 设置节点属性
* 包括宽度高度文本编辑属性等
*/
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,
y: y - 25,
name: "left",
id: "".concat(this.id, "_3"),
properties: { connectionType: "target" },
},
];
}
/**
* 获取节点轮廓样式
* 覆盖父类方法设置 stroke 属性为 none以适应特定的视觉效果
* @returns {object} 节点轮廓样式
*/
getOutlineStyle() {
const style = super.getOutlineStyle();
style.stroke = "none";
style.hover.stroke = "none";
return style;
}
}
// 导出方法注册
export function registerSubEndNode(lf) {
lf.register({
type: "subEnd",
view: SubEndNodeHtmlNode,
model: SubEndNodeHtmlModel,
});
}

View File

@ -1,202 +1,185 @@
// 导入 HtmlNode 及其模型,为后续继承做准备
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
// 导入 Vue 相关方法,用于渲染组件
import { createApp, h, nextTick } from "vue";
import ElementPlus from "element-plus";
// 导入 Vue 组件
import SubStartNode from "./subStart.vue";
/**
* 定义一个 元素的 HTML 节点类继承自 HtmlNode
* 该类负责在 HTML 中渲染 元素并处理其交互逻辑
*/
class SubStartNodeHtmlNode extends HtmlNode {
resizeObserver = null;
isMounted; // 标记组件是否已挂载
r; // 渲染函数
app; // Vue 应用实例
container = null;
/**
* 构造函数
* @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(SubStartNode, {
model: props.model,
graphModel: props.graphModel,
properties: {
...props.model.getProperties(),
}
});
// 创建 Vue 应用实例,并指定渲染函数
this.app = createApp({
render: () => this.r,
});
}
/**
* HTML 内容设置到指定的根元素上
* @param rootEl 根元素
*/
async setHtml(rootEl) {
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.mount(this.container);
await nextTick();
this.setupSizeObserver();
} 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 SubStartNodeHtmlModel extends HtmlNodeModel {
initNodeData(data) {
super.initNodeData(data);
}
// 保存组件实例引用
setComponentInstance(instance) {
this.componentInstance = instance;
}
// 验证表单
async validateForm() {
if (this.componentInstance && this.componentInstance.validate) {
try {
const result = await this.componentInstance.validate();
if (result) {
return { valid: true, message: "验证通过" };
} else {
return { valid: false, message: "验证失败" };
}
} catch {
return { valid: false, message: "验证失败" };
}
}
return { valid: true, message: "无验证方法" };
}
/**
* 设置节点属性
* 包括宽度高度文本编辑属性等
*/
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 - 24,
y: y - 25,
name: "right",
id: "".concat(this.id, "_1")
},
];
}
/**
* 获取节点轮廓样式
* 覆盖父类方法设置 stroke 属性为 none以适应特定的视觉效果
* @returns {object} 节点轮廓样式
*/
getOutlineStyle() {
const style = super.getOutlineStyle();
style.stroke = "none";
style.hover.stroke = "none";
return style;
}
}
// 导出方法注册
export function registerSubStartNode(lf) {
lf.register({
type: "subStart",
view: SubStartNodeHtmlNode,
model: SubStartNodeHtmlModel,
});
}
// 导入 HtmlNode 及其模型,为后续继承做准备
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
// 导入 Vue 相关方法,用于渲染组件
import { createApp, h, nextTick } from "vue";
import ElementPlus from "element-plus";
// 导入 Vue 组件
import SubStartNode from "./subStart.vue";
/**
* 定义一个 元素的 HTML 节点类继承自 HtmlNode
* 该类负责在 HTML 中渲染 元素并处理其交互逻辑
*/
class SubStartNodeHtmlNode extends HtmlNode {
resizeObserver = null;
isMounted; // 标记组件是否已挂载
r; // 渲染函数
app; // Vue 应用实例
container = null;
/**
* 构造函数
* @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(SubStartNode, {
model: props.model,
graphModel: props.graphModel,
properties: {
...props.model.getProperties(),
}
});
// 创建 Vue 应用实例,并指定渲染函数
this.app = createApp({
render: () => this.r,
});
}
/**
* HTML 内容设置到指定的根元素上
* @param rootEl 根元素
*/
async setHtml(rootEl) {
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.mount(this.container);
await nextTick();
this.setupSizeObserver();
} 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 SubStartNodeHtmlModel extends HtmlNodeModel {
initNodeData(data) {
super.initNodeData(data);
}
// 保存组件实例引用
setComponentInstance(instance) {
this.componentInstance = instance;
}
/**
* 设置节点属性
* 包括宽度高度文本编辑属性等
*/
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 - 24,
y: y - 25,
name: "right",
id: "".concat(this.id, "_1")
},
];
}
/**
* 获取节点轮廓样式
* 覆盖父类方法设置 stroke 属性为 none以适应特定的视觉效果
* @returns {object} 节点轮廓样式
*/
getOutlineStyle() {
const style = super.getOutlineStyle();
style.stroke = "none";
style.hover.stroke = "none";
return style;
}
}
// 导出方法注册
export function registerSubStartNode(lf) {
lf.register({
type: "subStart",
view: SubStartNodeHtmlNode,
model: SubStartNodeHtmlModel,
});
}

View File

@ -1,314 +1,318 @@
// 导入 HtmlNode 及其模型,为后续继承做准备
import { HtmlNode, HtmlNodeModel, h as svgH } from "@logicflow/core";
// 导入 Vue 相关方法,用于渲染组件
import { createApp, h, nextTick } from "vue";
import ElementPlus from "element-plus";
// 导入 Vue 组件
import Switch from "./switch.vue";
import JsonViewer from 'vue3-json-viewer'
/**
* 定义一个 元素的 HTML 节点类继承自 HtmlNode
* 该类负责在 HTML 中渲染 元素并处理其交互逻辑
*/
class SwitchHtmlNode 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(Switch, {
model: props.model,
graphModel: props.graphModel,
properties: {
...props.model.getProperties(),
},
text: props.model.text.value,
onContentChange: this.handleContentChange.bind(this),
onBindRef: this.handleComponentInstance.bind(this),
// 添加锚点相关回调
onAddAnchor: this.handleAddAnchor.bind(this),
onRemoveAnchor: this.handleRemoveAnchor.bind(this),
onChangeAnchor: this.handleChangeAnchor.bind(this)
});
// 创建 Vue 应用实例,并指定渲染函数
this.app = createApp({
render: () => this.r,
});
}
/**
* HTML 内容设置到指定的根元素上
* @param rootEl 根元素
*/
async setHtml(rootEl) {
const nodeId = this.props.model.id;
if (SwitchHtmlNode.reusePool.has(nodeId)) {
rootEl.appendChild(SwitchHtmlNode.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();
SwitchHtmlNode.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();
}
// 处理添加锚点
handleAddAnchor(height, anchorId) {
const { model } = this.props
model.addAnchor(height, anchorId)
}
// 处理删除锚点
handleRemoveAnchor(anchorId) {
this.props.model.removeAnchor(anchorId);
}
handleChangeAnchor(height, anchorId) {
const { model } = this.props
model.changeAnchorHeight(height, anchorId)
}
// 渲染完成后获取实际尺寸
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 SwitchHtmlModel extends HtmlNodeModel {
initNodeData(data) {
super.initNodeData(data);
}
// 返回动态锚点
addAnchor(height, anchorId) {
const properties = this.getProperties()
const anchor = properties?.anchor || []
anchor.push({
id: anchorId,
name: 'right',
x: this.x,
y: this.y,
height
})
this.setProperties({
...properties,
anchor
})
this.getDefaultAnchor()
this.initNodeData(this)
}
removeAnchor(anchorId) {
const properties = this.getProperties()
const anchor = properties?.anchor || []
let nweAnchor = anchor.filter(item => item.id !== anchorId);
this.setProperties({
...properties,
anchor: nweAnchor
})
}
changeAnchorHeight(height, anchorId) {
const properties = this.getProperties()
const anchor = properties?.anchor || []
anchor.forEach(item => {
if (item.id === anchorId) {
item.height = height
}
})
this.setProperties({
...properties,
anchor
})
this.getDefaultAnchor()
this.initNodeData(this)
}
// 保存组件实例引用
setComponentInstance(instance) {
this.componentInstance = instance;
}
// 验证表单
async validateForm() {
if (this.componentInstance && this.componentInstance.validate) {
try {
const result = await this.componentInstance.validate();
if (result) {
return { valid: true, message: "验证通过" };
} else {
return { valid: false, message: "验证失败" };
}
} catch {
return { valid: false, message: "验证失败" };
}
}
return { valid: true, message: "无验证方法" };
}
/**
* 设置节点属性
* 包括宽度高度文本编辑属性等
*/
setAttributes() {
// 初始设置为0后续动态更新
this.width = 0;
this.height = 0;
this.text.editable = false;
}
updateSize(width, height) {
this.width = width + 24;
this.height = height + 50;
// const properties = lf.getProperties(this.id)
// this.setProperties({
// ...properties,
// width: this.width,
// height: this.height
// })
this.initNodeData(this); // 触发节点重绘
}
// 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。
getDefaultAnchor() {
let _a = this,
x = _a.x,
y = _a.y,
width = _a.width,
height = _a.height;
const properties = this.getProperties()
const anchor = properties?.anchor || []
// const { SCALE_Y } = this.graphModel.transformModel;
const ids = []
anchor.forEach(item => {
ids.push(item.id)
if (item.id !== "".concat(this.id, "_else")) {
item.x = x + width / 2 - 40
// item.y = (y - height / 2 + item.height + 60) / SCALE_Y
item.y = y - height / 2 + item.height + 60
} else {
item.x = x + width / 2 - 40
item.y = y + height / 2 - 80
}
})
return [
// {
// x: x + width / 2 - 40,
// y: y + height / 2 - 80,
// name: "right",
// id: "".concat(this.id, "_else")
// },
{
x: x - width / 2,
y: y - 25,
name: "left",
id: "".concat(this.id, "_entry")
},
...anchor
];
}
/**
* 获取节点轮廓样式
* 覆盖父类方法设置 stroke 属性为 none以适应特定的视觉效果
* @returns {object} 节点轮廓样式
*/
getOutlineStyle() {
const style = super.getOutlineStyle();
style.stroke = "none";
style.hover.stroke = "none";
return style;
}
}
// 导出方法注册
export function registerSwitch(lf) {
lf.register({
type: "branch",
view: SwitchHtmlNode,
model: SwitchHtmlModel,
});
}
// 导入 HtmlNode 及其模型,为后续继承做准备
import { HtmlNode, HtmlNodeModel, h as svgH } from "@logicflow/core";
// 导入 Vue 相关方法,用于渲染组件
import { createApp, h, nextTick } from "vue";
import ElementPlus from "element-plus";
// 导入 Vue 组件
import Switch from "./switch.vue";
import JsonViewer from 'vue3-json-viewer'
/**
* 定义一个 元素的 HTML 节点类继承自 HtmlNode
* 该类负责在 HTML 中渲染 元素并处理其交互逻辑
*/
class SwitchHtmlNode 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(Switch, {
model: props.model,
graphModel: props.graphModel,
properties: {
...props.model.getProperties(),
},
text: props.model.text.value,
onContentChange: this.handleContentChange.bind(this),
onBindRef: this.handleComponentInstance.bind(this),
// 添加锚点相关回调
onAddAnchor: this.handleAddAnchor.bind(this),
onRemoveAnchor: this.handleRemoveAnchor.bind(this),
onChangeAnchor: this.handleChangeAnchor.bind(this)
});
// 创建 Vue 应用实例,并指定渲染函数
this.app = createApp({
render: () => this.r,
});
}
/**
* HTML 内容设置到指定的根元素上
* @param rootEl 根元素
*/
async setHtml(rootEl) {
const nodeId = this.props.model.id;
if (SwitchHtmlNode.reusePool.has(nodeId)) {
rootEl.appendChild(SwitchHtmlNode.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();
SwitchHtmlNode.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();
}
// 处理添加锚点
handleAddAnchor(height, anchorId) {
const { model } = this.props
model.addAnchor(height, anchorId)
}
// 处理删除锚点
handleRemoveAnchor(anchorId) {
this.props.model.removeAnchor(anchorId);
}
handleChangeAnchor(height, anchorId) {
const { model } = this.props
model.changeAnchorHeight(height, anchorId)
}
// 渲染完成后获取实际尺寸
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 SwitchHtmlModel extends HtmlNodeModel {
initNodeData(data) {
super.initNodeData(data);
}
// 返回动态锚点
addAnchor(height, anchorId) {
const properties = this.getProperties()
const anchor = properties?.anchor || []
anchor.push({
id: anchorId,
name: 'right',
x: this.x,
y: this.y,
height
})
this.setProperties({
...properties,
anchor
})
this.getDefaultAnchor()
this.initNodeData(this)
}
removeAnchor(anchorId) {
const properties = this.getProperties()
const anchor = properties?.anchor || []
let nweAnchor = anchor.filter(item => item.id !== anchorId);
this.setProperties({
...properties,
anchor: nweAnchor
})
}
changeAnchorHeight(height, anchorId) {
const properties = this.getProperties()
const anchor = properties?.anchor || []
anchor.forEach(item => {
if (item.id === anchorId) {
item.height = height
}
})
this.setProperties({
...properties,
anchor
})
this.getDefaultAnchor()
this.initNodeData(this)
}
// 保存组件实例引用
setComponentInstance(instance) {
this.componentInstance = instance;
}
closePopover() {
this.componentInstance.closePopover()
}
// 验证表单
async validateForm() {
if (this.componentInstance && this.componentInstance.validate) {
try {
const result = await this.componentInstance.validate();
if (result) {
return { valid: true, message: "验证通过" };
} else {
return { valid: false, message: "验证失败" };
}
} catch {
return { valid: false, message: "验证失败" };
}
}
return { valid: true, message: "无验证方法" };
}
/**
* 设置节点属性
* 包括宽度高度文本编辑属性等
*/
setAttributes() {
// 初始设置为0后续动态更新
this.width = 0;
this.height = 0;
this.text.editable = false;
}
updateSize(width, height) {
this.width = width + 24;
this.height = height + 50;
// const properties = lf.getProperties(this.id)
// this.setProperties({
// ...properties,
// width: this.width,
// height: this.height
// })
this.initNodeData(this); // 触发节点重绘
}
// 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。
getDefaultAnchor() {
let _a = this,
x = _a.x,
y = _a.y,
width = _a.width,
height = _a.height;
const properties = this.getProperties()
const anchor = properties?.anchor || []
// const { SCALE_Y } = this.graphModel.transformModel;
const ids = []
anchor.forEach(item => {
ids.push(item.id)
if (item.id !== "".concat(this.id, "_else")) {
item.x = x + width / 2 - 40
// item.y = (y - height / 2 + item.height + 60) / SCALE_Y
item.y = y - height / 2 + item.height + 60
} else {
item.x = x + width / 2 - 40
item.y = y + height / 2 - 80
}
})
return [
// {
// x: x + width / 2 - 40,
// y: y + height / 2 - 80,
// name: "right",
// id: "".concat(this.id, "_else")
// },
{
x: x - width / 2,
y: y - 25,
name: "left",
id: "".concat(this.id, "_entry")
},
...anchor
];
}
/**
* 获取节点轮廓样式
* 覆盖父类方法设置 stroke 属性为 none以适应特定的视觉效果
* @returns {object} 节点轮廓样式
*/
getOutlineStyle() {
const style = super.getOutlineStyle();
style.stroke = "none";
style.hover.stroke = "none";
return style;
}
}
// 导出方法注册
export function registerSwitch(lf) {
lf.register({
type: "branch",
view: SwitchHtmlNode,
model: SwitchHtmlModel,
});
}

View File

@ -1,6 +1,6 @@
<template>
<div class="node__container" ref="switchRef" @mouseleave="setNodeProperties">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
<template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template>
@ -407,6 +407,14 @@ watch(
}
);
const nodeStateRef = ref(null);
const closePopover = () => {
if (nodeStateRef.value) {
nodeStateRef.value.closePopover();
}
}
onMounted(() => {
emits("addAnchor", 280, "".concat(props.model.id, "_else"));
@ -450,6 +458,8 @@ onUnmounted(() => {
emitter.off("changeNodeState");
emitter.off("contentChange");
});
defineExpose({ closePopover })
</script>
<style lang="scss" scoped>
.node__container {

View File

@ -51,7 +51,7 @@ export default defineConfig(({mode, command}) => {
//李小龙 http://192.168.0.5:13080
// dev http://10.148.20.34:13080
// target: VITE_API_URL,
target: command === 'build' ? VITE_API_URL : 'http://192.168.0.5:13080',
target: command === 'build' ? VITE_API_URL : 'http://192.168.0.100:13080',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
}