feat: 清除上一次运行结果
This commit is contained in:
parent
8c1820e1a6
commit
783e54bab0
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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()
|
||||
// }
|
||||
|
||||
/**
|
||||
* 设置节点属性
|
||||
* 包括宽度、高度、文本编辑属性等
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -142,6 +142,10 @@ class StartNodeHtmlModel extends HtmlNodeModel {
|
||||
this.componentInstance = instance;
|
||||
}
|
||||
|
||||
closePopover() {
|
||||
this.componentInstance.closePopover()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置节点属性
|
||||
* 包括宽度、高度、文本编辑属性等
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -144,6 +144,10 @@ class CodeNodeHtmlModel extends HtmlNodeModel {
|
||||
this.componentInstance = instance;
|
||||
}
|
||||
|
||||
closePopover() {
|
||||
this.componentInstance.closePopover()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置节点属性
|
||||
* 包括宽度、高度、文本编辑属性等
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -150,9 +150,9 @@ class CurrentLoopNodeHtmlModel extends HtmlNodeModel {
|
||||
return result
|
||||
}
|
||||
|
||||
// setCustomProperties() {
|
||||
// this.componentInstance.setNodeProperties()
|
||||
// }
|
||||
closePopover() {
|
||||
this.componentInstance.closePopover()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置节点属性
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置节点属性
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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/, '')
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user