feat: 清除上一次运行结果
This commit is contained in:
parent
8c1820e1a6
commit
783e54bab0
@ -1,22 +1,22 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
export const useFlowStore = defineStore('flow', {
|
export const useFlowStore = defineStore('flow', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
disableForm: false,
|
disableForm: false,
|
||||||
deviceList: []
|
deviceList: []
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
updateDisableForm(value) {
|
updateDisableForm(value) {
|
||||||
this.disableForm = value
|
this.disableForm = value
|
||||||
},
|
},
|
||||||
|
|
||||||
async getDeviceList() {
|
async getDeviceList() {
|
||||||
const data = await new Promise((resolve, reject) => {
|
const data = await new Promise((resolve, reject) => {
|
||||||
resolve( ['cam1', 'cam2', 'cam3', 'cam4'])
|
resolve( ['cam1', 'cam2', 'cam3', 'cam4'])
|
||||||
})
|
})
|
||||||
this.deviceList = data
|
this.deviceList = data
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,51 +1,57 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="state__container" :class="`nodeState-${props.state}`">
|
<div class="state__container" :class="`nodeState-${props.state}`">
|
||||||
<div class="context">
|
<div class="context" v-show="showState !== 'NORMAL'">
|
||||||
<div class="state">
|
<div class="state">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<SuccessFilled v-if="props.state === 'SUCCESS'" />
|
<SuccessFilled v-if="props.state === 'SUCCESS'" />
|
||||||
<CircleCloseFilled v-if="['FAILED', 'STOPPED'].includes(props.state)" />
|
<CircleCloseFilled v-if="['FAILED', 'STOPPED'].includes(props.state)" />
|
||||||
<VideoPause v-if="props.state === 'PAUSED'" />
|
<VideoPause v-if="props.state === 'PAUSED'" />
|
||||||
<Loading v-if="props.state === 'RUNNING'" />
|
<Loading v-if="props.state === 'RUNNING'" />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<div class="text">{{ getStateText() }}</div>
|
<div class="text">{{ getStateText() }}</div>
|
||||||
<el-tag :type="props.state === 'SUCCESS' ? 'success' : 'danger'">
|
<el-tag :type="props.state === 'SUCCESS' ? 'success' : 'danger'">
|
||||||
{{ props.runtimes }}ms
|
{{ props.runtimes }}ms
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
|
||||||
<div class="resultText" v-popover="popoverRef" @click="popoverVisible = !popoverVisible">{{ popoverVisible ? '隐藏结果' : '展示结果' }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="resultText"
|
||||||
|
v-popover="popoverRef"
|
||||||
|
@click="popoverVisible = !popoverVisible"
|
||||||
|
>
|
||||||
|
{{ popoverVisible ? "隐藏结果" : "展示结果" }}
|
||||||
</div>
|
</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>
|
</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>
|
</template>
|
||||||
<script setup lang="js">
|
<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'
|
import { SuccessFilled, CircleCloseFilled, Loading, VideoPause } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -56,7 +62,7 @@ const props = defineProps({
|
|||||||
runtimes: {
|
runtimes: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
@ -76,83 +82,95 @@ const getStateText = () => {
|
|||||||
const popoverRef = ref();
|
const popoverRef = ref();
|
||||||
const popoverVisible = ref(false);
|
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>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.state__container {
|
.state__container {
|
||||||
display: none;
|
display: none;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
|
||||||
.context {
|
.context {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.state {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
padding: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
.state {
|
.text {
|
||||||
display: flex;
|
margin: 0 8px;
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.text {
|
|
||||||
margin: 0 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.resultText {
|
|
||||||
color: #1664ff;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.nodeState-SUCCESS {
|
.resultText {
|
||||||
display: block;
|
color: #1664ff;
|
||||||
background-color: #eef9f1;
|
cursor: pointer;
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #309256;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.nodeState-FAILED {
|
.nodeState-SUCCESS {
|
||||||
display: block;
|
display: block;
|
||||||
background-color: #fdf5f5;
|
background-color: #eef9f1;
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #ee3f38;
|
color: #309256;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.nodeState-STOPPED {
|
.nodeState-FAILED {
|
||||||
display: block;
|
display: block;
|
||||||
background-color: #fdf5f5;
|
background-color: #fdf5f5;
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #ee3f38;
|
color: #ee3f38;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.nodeState-PAUSED {
|
.nodeState-STOPPED {
|
||||||
display: block;
|
display: block;
|
||||||
background-color: #cbcbcb;
|
background-color: #fdf5f5;
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #666;
|
color: #ee3f38;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.nodeState-RUNNING {
|
.nodeState-PAUSED {
|
||||||
display: block;
|
display: block;
|
||||||
background-color: #f4f7ff;
|
background-color: #cbcbcb;
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
color: #68a2e4;
|
font-size: 16px;
|
||||||
font-size: 16px;
|
color: #666;
|
||||||
animation: rotateAnimation 2s linear infinite;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</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>
|
||||||
<div class="btn__container">
|
<div class="btn__container">
|
||||||
<div class="btn">
|
<div class="btn">
|
||||||
|
<el-button v-if="['testRunError', 'testRunFinish', 'stop'].includes(flowState)" @click="clearRun">清除上一次运行结果</el-button>
|
||||||
<el-button @click="group">分组</el-button>
|
<el-button @click="group">分组</el-button>
|
||||||
<el-button v-if="flowState !== 'testRunning'" @click="testRun"
|
<el-button v-if="flowState !== 'testRunning'" @click="testRun">试运行</el-button>
|
||||||
>试运行</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="flowState === 'testRunning'" @click="flowPauseFn"
|
<el-button v-if="['testRunning', 'pause'].includes(flowState)" @click="flowStopFn">终止</el-button>
|
||||||
>暂停</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>
|
||||||
<div class="btn">
|
<div class="btn">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
@ -34,9 +25,7 @@
|
|||||||
>
|
>
|
||||||
<el-button type="primary" :disabled="true">发布</el-button>
|
<el-button type="primary" :disabled="true">发布</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-button v-else @click="deployFlow" type="primary"
|
<el-button v-else @click="deployFlow" type="primary">发布</el-button>
|
||||||
>发布</el-button
|
|
||||||
>
|
|
||||||
|
|
||||||
<el-popover
|
<el-popover
|
||||||
:width="260"
|
:width="260"
|
||||||
@ -123,9 +112,7 @@
|
|||||||
<el-form-item
|
<el-form-item
|
||||||
:label="index === 0 ? '参数名' : ''"
|
:label="index === 0 ? '参数名' : ''"
|
||||||
:prop="`nodeParams.${index}.name`"
|
:prop="`nodeParams.${index}.name`"
|
||||||
:rules="[
|
:rules="[{ required: true, message: '请输入参数名', trigger: 'blur' }]"
|
||||||
{ required: true, message: '请输入参数名', trigger: 'blur' },
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<el-input
|
<el-input
|
||||||
:disabled="property?.disabled || false"
|
:disabled="property?.disabled || false"
|
||||||
@ -137,9 +124,7 @@
|
|||||||
<el-form-item
|
<el-form-item
|
||||||
:label="index === 0 ? '参数值' : ''"
|
:label="index === 0 ? '参数值' : ''"
|
||||||
:prop="`nodeParams.${index}.input`"
|
:prop="`nodeParams.${index}.input`"
|
||||||
:rules="[
|
:rules="[{ required: true, message: '请输入参数值', trigger: 'blur' }]"
|
||||||
{ required: true, message: '请输入参数值', trigger: 'blur' },
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-if="property.componentType === 'number'"
|
v-if="property.componentType === 'number'"
|
||||||
@ -176,9 +161,7 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button @click="visible = false">取消</el-button>
|
<el-button @click="visible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="execute">
|
<el-button type="primary" @click="execute"> 确定 </el-button>
|
||||||
确定
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@ -203,7 +186,7 @@ import {
|
|||||||
flowPause,
|
flowPause,
|
||||||
flowResume,
|
flowResume,
|
||||||
flowStop,
|
flowStop,
|
||||||
flowAction
|
flowAction,
|
||||||
} from "@/api/device/flow";
|
} from "@/api/device/flow";
|
||||||
import { getDetect } from "@/api/test/detect";
|
import { getDetect } from "@/api/test/detect";
|
||||||
import { Location } from "@element-plus/icons-vue";
|
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;
|
showParamsDrawer.value = true;
|
||||||
console.log('newNode', newNode)
|
console.log("newNode", newNode);
|
||||||
paramsDrawerData.value = newNode;
|
paramsDrawerData.value = newNode;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -444,8 +429,7 @@ const loopFlowView = async (instId, taskId = null) => {
|
|||||||
} else {
|
} else {
|
||||||
if (!flowInfoData.value.isLog) {
|
if (!flowInfoData.value.isLog) {
|
||||||
if (["PAUSED", "STOPPED"].includes(arr[arr.length - 1]?.status)) {
|
if (["PAUSED", "STOPPED"].includes(arr[arr.length - 1]?.status)) {
|
||||||
flowState.value =
|
flowState.value = arr[arr.length - 1]?.status === "PAUSED" ? "pause" : "stop";
|
||||||
arr[arr.length - 1]?.status === "PAUSED" ? "pause" : "stop";
|
|
||||||
} else {
|
} else {
|
||||||
flowState.value = "testRunError";
|
flowState.value = "testRunError";
|
||||||
flowStore.updateDisableForm(false);
|
flowStore.updateDisableForm(false);
|
||||||
@ -562,10 +546,7 @@ const initFlow = () => {
|
|||||||
keys: ["backspace", "delete"], // 覆盖删除键
|
keys: ["backspace", "delete"], // 覆盖删除键
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const activeElem = document.activeElement;
|
const activeElem = document.activeElement;
|
||||||
if (
|
if (activeElem.tagName === "INPUT" || activeElem.tagName === "TEXTAREA") {
|
||||||
activeElem.tagName === "INPUT" ||
|
|
||||||
activeElem.tagName === "TEXTAREA"
|
|
||||||
) {
|
|
||||||
return; // 不处理输入框内的删除
|
return; // 不处理输入框内的删除
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -657,10 +638,12 @@ const initFlow = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
lf.on("node:dbclick", ({ data, e }) => {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
console.log('data', data)
|
console.log("data", data);
|
||||||
showParamsDrawer.value = true;
|
showParamsDrawer.value = true;
|
||||||
paramsDrawerData.value = data;
|
paramsDrawerData.value = data;
|
||||||
});
|
});
|
||||||
@ -734,55 +717,65 @@ const fitView = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const nodeName = ref("");
|
const nodeName = ref("");
|
||||||
const visible = ref(false)
|
const visible = ref(false);
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
nodeParams: [],
|
nodeParams: [],
|
||||||
})
|
});
|
||||||
const rules = reactive({})
|
const rules = reactive({});
|
||||||
const executeForm = ref()
|
const executeForm = ref();
|
||||||
const nodeAction = ref('')
|
const nodeAction = ref("");
|
||||||
|
|
||||||
const singleNodeExecution = (e) => {
|
const singleNodeExecution = (e) => {
|
||||||
const { name, nodeType, nodeParams, action } = e.detail;
|
const { name, nodeType, nodeParams, action } = e.detail;
|
||||||
nodeName.value = name;
|
nodeName.value = name;
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
nodeAction.value = action
|
nodeAction.value = action;
|
||||||
if (nodeType === 'EDGE') {
|
if (nodeType === "EDGE") {
|
||||||
formData.nodeParams = [
|
formData.nodeParams = [
|
||||||
{
|
{
|
||||||
disabled: true,
|
disabled: true,
|
||||||
input: "",
|
input: "",
|
||||||
name: "terminalId",
|
name: "terminalId",
|
||||||
type: "input"
|
type: "input",
|
||||||
},
|
},
|
||||||
...nodeParams
|
...nodeParams,
|
||||||
]
|
];
|
||||||
} else {
|
} else {
|
||||||
formData.nodeParams = nodeParams
|
formData.nodeParams = nodeParams;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const execute = async () => {
|
const execute = async () => {
|
||||||
const result = await executeForm.value.validate();
|
const result = await executeForm.value.validate();
|
||||||
if (result) {
|
if (result) {
|
||||||
const obj = {}
|
const obj = {};
|
||||||
formData.nodeParams.forEach(item => {
|
formData.nodeParams.forEach((item) => {
|
||||||
obj[item.name] = item.input
|
obj[item.name] = item.input;
|
||||||
})
|
});
|
||||||
const res = await flowAction({
|
const res = await flowAction({
|
||||||
action: nodeAction.value,
|
action: nodeAction.value,
|
||||||
payload: obj
|
payload: obj,
|
||||||
})
|
});
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
ElMessage.success('执行成功')
|
ElMessage.success("执行成功");
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.msg)
|
ElMessage.error(res.msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const showParamsDrawer = ref(false);
|
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(() => {
|
onMounted(() => {
|
||||||
justLoadFlow();
|
justLoadFlow();
|
||||||
@ -876,8 +869,6 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.node-relationship-network-popover {
|
.node-relationship-network-popover {
|
||||||
@ -903,16 +894,16 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-dialog {
|
.el-dialog {
|
||||||
.el-input {
|
.el-input {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-select {
|
.el-select {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-textarea {
|
.el-textarea {
|
||||||
width: 200px
|
width: 200px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -144,16 +144,10 @@ class ServiceNodeHtmlModel extends HtmlNodeModel {
|
|||||||
this.componentInstance = instance;
|
this.componentInstance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证表单
|
closePopover() {
|
||||||
async validateForm() {
|
this.componentInstance.closePopover()
|
||||||
const result = this.componentInstance.validateForm()
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setCustomProperties() {
|
|
||||||
// this.componentInstance.setNodeProperties()
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置节点属性
|
* 设置节点属性
|
||||||
* 包括宽度、高度、文本编辑属性等
|
* 包括宽度、高度、文本编辑属性等
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="node__container" :class="props.model.id">
|
<div class="node__container" :class="props.model.id">
|
||||||
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
|
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
|
||||||
<template #input>
|
<template #input>
|
||||||
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
||||||
</template>
|
</template>
|
||||||
@ -78,6 +78,14 @@ const inputJsonData = ref({});
|
|||||||
const outputJsonData = ref({});
|
const outputJsonData = ref({});
|
||||||
const errorInfoData = ref('');
|
const errorInfoData = ref('');
|
||||||
|
|
||||||
|
const nodeStateRef = ref(null);
|
||||||
|
|
||||||
|
const closePopover = () => {
|
||||||
|
if (nodeStateRef.value) {
|
||||||
|
nodeStateRef.value.closePopover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
emitter.on("changeNodeState", (data) => {
|
emitter.on("changeNodeState", (data) => {
|
||||||
@ -124,6 +132,8 @@ onUnmounted(() => {
|
|||||||
emitter.off("setProperties");
|
emitter.off("setProperties");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({ closePopover })
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -142,6 +142,10 @@ class StartNodeHtmlModel extends HtmlNodeModel {
|
|||||||
this.componentInstance = instance;
|
this.componentInstance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closePopover() {
|
||||||
|
this.componentInstance.closePopover()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置节点属性
|
* 设置节点属性
|
||||||
* 包括宽度、高度、文本编辑属性等
|
* 包括宽度、高度、文本编辑属性等
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="node__container" :class="props.model.id">
|
<div class="node__container" :class="props.model.id">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
|
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
|
||||||
<template #input>
|
<template #input>
|
||||||
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
||||||
</template>
|
</template>
|
||||||
@ -40,6 +40,14 @@ const runtimes = ref(0);
|
|||||||
const inputJsonData = ref({});
|
const inputJsonData = ref({});
|
||||||
const outputJsonData = ref({});
|
const outputJsonData = ref({});
|
||||||
|
|
||||||
|
const nodeStateRef = ref(null);
|
||||||
|
|
||||||
|
const closePopover = () => {
|
||||||
|
if (nodeStateRef.value) {
|
||||||
|
nodeStateRef.value.closePopover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
emitter.on("changeNodeState", (data) => {
|
emitter.on("changeNodeState", (data) => {
|
||||||
if (data.nodeId === props.model.id) {
|
if (data.nodeId === props.model.id) {
|
||||||
@ -87,6 +95,8 @@ onUnmounted(() => {
|
|||||||
emitter.off("setProperties");
|
emitter.off("setProperties");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({ closePopover })
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -144,6 +144,10 @@ class CodeNodeHtmlModel extends HtmlNodeModel {
|
|||||||
this.componentInstance = instance;
|
this.componentInstance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closePopover() {
|
||||||
|
this.componentInstance.closePopover()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置节点属性
|
* 设置节点属性
|
||||||
* 包括宽度、高度、文本编辑属性等
|
* 包括宽度、高度、文本编辑属性等
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="node__container" :class="props.model.id">
|
<div class="node__container" :class="props.model.id">
|
||||||
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
|
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
|
||||||
<template #input>
|
<template #input>
|
||||||
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
||||||
</template>
|
</template>
|
||||||
@ -74,6 +74,15 @@ const inputJsonData = ref({});
|
|||||||
const outputJsonData = ref({});
|
const outputJsonData = ref({});
|
||||||
const errorInfoData = ref('');
|
const errorInfoData = ref('');
|
||||||
|
|
||||||
|
const nodeStateRef = ref(null);
|
||||||
|
|
||||||
|
const closePopover = () => {
|
||||||
|
if (nodeStateRef.value) {
|
||||||
|
nodeStateRef.value.closePopover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
emitter.on("changeNodeState", (data) => {
|
emitter.on("changeNodeState", (data) => {
|
||||||
if (data.nodeId === props.model.id) {
|
if (data.nodeId === props.model.id) {
|
||||||
@ -119,6 +128,7 @@ onUnmounted(() => {
|
|||||||
emitter.off("setProperties");
|
emitter.off("setProperties");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({ closePopover })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="node__container" :class="props.model.id">
|
<div class="node__container" :class="props.model.id">
|
||||||
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
|
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
|
||||||
<template #input>
|
<template #input>
|
||||||
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
||||||
</template>
|
</template>
|
||||||
@ -46,12 +46,6 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emits = defineEmits(["contentChange"]);
|
const emits = defineEmits(["contentChange"]);
|
||||||
|
|
||||||
|
|
||||||
const formData = reactive({
|
|
||||||
nodeParams: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const setNodeName = (name) => {
|
const setNodeName = (name) => {
|
||||||
const properties = lf.getProperties(props.model.id);
|
const properties = lf.getProperties(props.model.id);
|
||||||
lf.setProperties(props.model.id, {
|
lf.setProperties(props.model.id, {
|
||||||
@ -66,6 +60,14 @@ const runtimes = ref(0);
|
|||||||
const inputJsonData = ref({});
|
const inputJsonData = ref({});
|
||||||
const outputJsonData = ref({});
|
const outputJsonData = ref({});
|
||||||
|
|
||||||
|
const nodeStateRef = ref(null);
|
||||||
|
|
||||||
|
const closePopover = () => {
|
||||||
|
if (nodeStateRef.value) {
|
||||||
|
nodeStateRef.value.closePopover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
emitter.on("changeNodeState", (data) => {
|
emitter.on("changeNodeState", (data) => {
|
||||||
if (data.nodeId === props.model.id) {
|
if (data.nodeId === props.model.id) {
|
||||||
@ -96,13 +98,22 @@ onMounted(() => {
|
|||||||
emits("contentChange");
|
emits("contentChange");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
emitter.on("setProperties", (data) => {
|
||||||
|
if (data.id === props.model.id) {
|
||||||
|
emits("contentChange");
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
emitter.off("changeNodeState");
|
emitter.off("changeNodeState");
|
||||||
emitter.off("contentChange");
|
emitter.off("contentChange");
|
||||||
|
emitter.off("setProperties");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({ closePopover })
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -150,9 +150,9 @@ class CurrentLoopNodeHtmlModel extends HtmlNodeModel {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// setCustomProperties() {
|
closePopover() {
|
||||||
// this.componentInstance.setNodeProperties()
|
this.componentInstance.closePopover()
|
||||||
// }
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置节点属性
|
* 设置节点属性
|
||||||
|
|||||||
@ -144,15 +144,9 @@ class HttpNodeHtmlModel extends HtmlNodeModel {
|
|||||||
this.componentInstance = instance;
|
this.componentInstance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证表单
|
closePopover() {
|
||||||
// async validateForm() {
|
this.componentInstance.closePopover()
|
||||||
// const result = this.componentInstance.validateForm()
|
}
|
||||||
// return result
|
|
||||||
// }
|
|
||||||
|
|
||||||
// setCustomProperties() {
|
|
||||||
// this.componentInstance.setNodeProperties()
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置节点属性
|
* 设置节点属性
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="node__container" :class="props.model.id">
|
<div class="node__container" :class="props.model.id">
|
||||||
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
|
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
|
||||||
<template #input>
|
<template #input>
|
||||||
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
||||||
</template>
|
</template>
|
||||||
@ -77,6 +77,13 @@ const inputJsonData = ref({});
|
|||||||
const outputJsonData = ref({});
|
const outputJsonData = ref({});
|
||||||
const errorInfoData = ref('');
|
const errorInfoData = ref('');
|
||||||
|
|
||||||
|
const nodeStateRef = ref(null);
|
||||||
|
|
||||||
|
const closePopover = () => {
|
||||||
|
if (nodeStateRef.value) {
|
||||||
|
nodeStateRef.value.closePopover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
emitter.on("changeNodeState", (data) => {
|
emitter.on("changeNodeState", (data) => {
|
||||||
@ -122,6 +129,8 @@ onUnmounted(() => {
|
|||||||
emitter.off("contentChange");
|
emitter.off("contentChange");
|
||||||
emitter.off("setProperties");
|
emitter.off("setProperties");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({ closePopover })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -1,219 +1,213 @@
|
|||||||
// 导入 HtmlNode 及其模型,为后续继承做准备
|
// 导入 HtmlNode 及其模型,为后续继承做准备
|
||||||
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
|
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
|
||||||
// 导入 Vue 相关方法,用于渲染组件
|
// 导入 Vue 相关方法,用于渲染组件
|
||||||
import { createApp, h, nextTick } from "vue";
|
import { createApp, h, nextTick } from "vue";
|
||||||
import ElementPlus from "element-plus";
|
import ElementPlus from "element-plus";
|
||||||
// 导入 Vue 组件
|
// 导入 Vue 组件
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import Sleep from "./sleep.vue";
|
import Sleep from "./sleep.vue";
|
||||||
import OuterNode from "../../components/OuterNode.vue";
|
import OuterNode from "../../components/OuterNode.vue";
|
||||||
import JsonViewer from 'vue3-json-viewer'
|
import JsonViewer from 'vue3-json-viewer'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义一个 元素的 HTML 节点类,继承自 HtmlNode
|
* 定义一个 元素的 HTML 节点类,继承自 HtmlNode
|
||||||
* 该类负责在 HTML 中渲染 元素,并处理其交互逻辑
|
* 该类负责在 HTML 中渲染 元素,并处理其交互逻辑
|
||||||
*/
|
*/
|
||||||
class SleepNodeHtmlNode extends HtmlNode {
|
class SleepNodeHtmlNode extends HtmlNode {
|
||||||
resizeObserver = null;
|
resizeObserver = null;
|
||||||
isMounted; // 标记组件是否已挂载
|
isMounted; // 标记组件是否已挂载
|
||||||
r; // 渲染函数
|
r; // 渲染函数
|
||||||
app; // Vue 应用实例
|
app; // Vue 应用实例
|
||||||
container = null;
|
container = null;
|
||||||
|
|
||||||
static reusePool = new Map(); // 节点复用池
|
static reusePool = new Map(); // 节点复用池
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
* @param props 传递给节点的属性,包括模型、图模型等
|
* @param props 传递给节点的属性,包括模型、图模型等
|
||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.initVueApp(props);
|
this.initVueApp(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
initVueApp(props) {
|
initVueApp(props) {
|
||||||
this.isMounted = false;
|
this.isMounted = false;
|
||||||
|
|
||||||
this.hideAnchor = false;
|
this.hideAnchor = false;
|
||||||
this.autoExpand = true; // 防止锚点被折叠
|
this.autoExpand = true; // 防止锚点被折叠
|
||||||
this.anchorsPreset = "default"; // 重置锚点预设
|
this.anchorsPreset = "default"; // 重置锚点预设
|
||||||
// 创建 元素的渲染函数
|
// 创建 元素的渲染函数
|
||||||
this.r = h(OuterNode, {
|
this.r = h(OuterNode, {
|
||||||
model: props.model,
|
model: props.model,
|
||||||
component: Sleep,
|
component: Sleep,
|
||||||
properties: {
|
properties: {
|
||||||
...props.model.getProperties(),
|
...props.model.getProperties(),
|
||||||
},
|
},
|
||||||
onContentChange: this.handleContentChange.bind(this),
|
onContentChange: this.handleContentChange.bind(this),
|
||||||
onBindRef: this.handleComponentInstance.bind(this)
|
onBindRef: this.handleComponentInstance.bind(this)
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建 Vue 应用实例,并指定渲染函数
|
// 创建 Vue 应用实例,并指定渲染函数
|
||||||
this.app = createApp({
|
this.app = createApp({
|
||||||
render: () => this.r,
|
render: () => this.r,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将 HTML 内容设置到指定的根元素上
|
* 将 HTML 内容设置到指定的根元素上
|
||||||
* @param rootEl 根元素
|
* @param rootEl 根元素
|
||||||
*/
|
*/
|
||||||
async setHtml(rootEl) {
|
async setHtml(rootEl) {
|
||||||
const nodeId = this.props.model.id;
|
const nodeId = this.props.model.id;
|
||||||
if (SleepNodeHtmlNode.reusePool.has(nodeId)) {
|
if (SleepNodeHtmlNode.reusePool.has(nodeId)) {
|
||||||
rootEl.appendChild(SleepNodeHtmlNode.reusePool.get(nodeId));
|
rootEl.appendChild(SleepNodeHtmlNode.reusePool.get(nodeId));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isMounted) {
|
if (!this.isMounted) {
|
||||||
this.isMounted = true;
|
this.isMounted = true;
|
||||||
this.container = document.createElement("div");
|
this.container = document.createElement("div");
|
||||||
this.container.style.display = "inline-block"; // 关键:确保容器自适应内容
|
this.container.style.display = "inline-block"; // 关键:确保容器自适应内容
|
||||||
|
|
||||||
rootEl.appendChild(this.container);
|
rootEl.appendChild(this.container);
|
||||||
this.app.use(ElementPlus);
|
this.app.use(ElementPlus);
|
||||||
this.app.use(JsonViewer);
|
this.app.use(JsonViewer);
|
||||||
|
|
||||||
this.app.mount(this.container);
|
this.app.mount(this.container);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
this.setupSizeObserver();
|
this.setupSizeObserver();
|
||||||
SleepNodeHtmlNode.reusePool.set(nodeId, this.container);
|
SleepNodeHtmlNode.reusePool.set(nodeId, this.container);
|
||||||
} else {
|
} else {
|
||||||
this.r.component.props.properties = this.props.model.getProperties();
|
this.r.component.props.properties = this.props.model.getProperties();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取节点文本内容
|
* 获取节点文本内容
|
||||||
* 对于元素,返回 null,因为其内容由特定组件渲染
|
* 对于元素,返回 null,因为其内容由特定组件渲染
|
||||||
* @returns {null}
|
* @returns {null}
|
||||||
*/
|
*/
|
||||||
getText() {
|
getText() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleComponentInstance(data) {
|
handleComponentInstance(data) {
|
||||||
// 将组件实例保存到节点模型
|
// 将组件实例保存到节点模型
|
||||||
this.props.model.setComponentInstance(data)
|
this.props.model.setComponentInstance(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContentChange() {
|
handleContentChange() {
|
||||||
// 内容变化时强制更新尺寸
|
// 内容变化时强制更新尺寸
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染完成后获取实际尺寸
|
// 渲染完成后获取实际尺寸
|
||||||
updateNodeSize() {
|
updateNodeSize() {
|
||||||
if (this.container) {
|
if (this.container) {
|
||||||
const { SCALE_X, SCALE_Y } = this.props.graphModel.transformModel;
|
const { SCALE_X, SCALE_Y } = this.props.graphModel.transformModel;
|
||||||
const node = this.container.querySelector(".node__container");
|
const node = this.container.querySelector(".node__container");
|
||||||
const rect = node.getBoundingClientRect();
|
const rect = node.getBoundingClientRect();
|
||||||
this.props.model.updateSize(rect.width / SCALE_X, rect.height / SCALE_Y);
|
this.props.model.updateSize(rect.width / SCALE_X, rect.height / SCALE_Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSizeObserver() {
|
setupSizeObserver() {
|
||||||
// 首次渲染立即检测
|
// 首次渲染立即检测
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
// 持续监听变化
|
// 持续监听变化
|
||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
});
|
});
|
||||||
this.resizeObserver.observe(this.container);
|
this.resizeObserver.observe(this.container);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组件卸载时移除监听
|
// 组件卸载时移除监听
|
||||||
onDestroy() {
|
onDestroy() {
|
||||||
this.resizeObserver?.disconnect();
|
this.resizeObserver?.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义一个元素的 HTML 模型类,继承自 HtmlNodeModel
|
* 定义一个元素的 HTML 模型类,继承自 HtmlNodeModel
|
||||||
* 该类主要设置节点的属性和样式
|
* 该类主要设置节点的属性和样式
|
||||||
*/
|
*/
|
||||||
class SleepNodeHtmlModel extends HtmlNodeModel {
|
class SleepNodeHtmlModel extends HtmlNodeModel {
|
||||||
initNodeData(data) {
|
initNodeData(data) {
|
||||||
super.initNodeData(data);
|
super.initNodeData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存组件实例引用
|
// 保存组件实例引用
|
||||||
setComponentInstance(instance) {
|
setComponentInstance(instance) {
|
||||||
this.componentInstance = instance;
|
this.componentInstance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证表单
|
closePopover() {
|
||||||
async validateForm() {
|
this.componentInstance.closePopover()
|
||||||
const result = this.componentInstance.validateForm()
|
}
|
||||||
return result
|
|
||||||
}
|
/**
|
||||||
|
* 设置节点属性
|
||||||
setCustomProperties() {
|
* 包括宽度、高度、文本编辑属性等
|
||||||
this.componentInstance.setNodeProperties()
|
*/
|
||||||
}
|
setAttributes() {
|
||||||
|
// 初始设置为0,后续动态更新
|
||||||
/**
|
this.width = 0;
|
||||||
* 设置节点属性
|
this.height = 0;
|
||||||
* 包括宽度、高度、文本编辑属性等
|
this.text.editable = false;
|
||||||
*/
|
}
|
||||||
setAttributes() {
|
|
||||||
// 初始设置为0,后续动态更新
|
updateSize(width, height) {
|
||||||
this.width = 0;
|
this.width = width + 24;
|
||||||
this.height = 0;
|
this.height = height + 50;
|
||||||
this.text.editable = false;
|
this.initNodeData(this); // 触发节点重绘
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSize(width, height) {
|
// 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。
|
||||||
this.width = width + 24;
|
getDefaultAnchor() {
|
||||||
this.height = height + 50;
|
let _a = this,
|
||||||
this.initNodeData(this); // 触发节点重绘
|
x = _a.x,
|
||||||
}
|
y = _a.y,
|
||||||
|
width = _a.width,
|
||||||
// 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。
|
height = _a.height;
|
||||||
getDefaultAnchor() {
|
return [
|
||||||
let _a = this,
|
{
|
||||||
x = _a.x,
|
x: x + width / 2 + 4,
|
||||||
y = _a.y,
|
y: y,
|
||||||
width = _a.width,
|
name: "right",
|
||||||
height = _a.height;
|
id: "".concat(this.id, "_1"),
|
||||||
return [
|
properties: { connectionType: "source" },
|
||||||
{
|
},
|
||||||
x: x + width / 2 + 4,
|
{
|
||||||
y: y,
|
x: x - width / 2,
|
||||||
name: "right",
|
y: y,
|
||||||
id: "".concat(this.id, "_1"),
|
name: "left",
|
||||||
properties: { connectionType: "source" },
|
id: "".concat(this.id, "_3"),
|
||||||
},
|
properties: { connectionType: "target" },
|
||||||
{
|
},
|
||||||
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";
|
||||||
* 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果
|
style.hover.stroke = "none";
|
||||||
* @returns {object} 节点轮廓样式
|
return style;
|
||||||
*/
|
}
|
||||||
getOutlineStyle() {
|
}
|
||||||
const style = super.getOutlineStyle();
|
|
||||||
style.stroke = "none";
|
// 导出方法注册
|
||||||
style.hover.stroke = "none";
|
export function registerSleepNode(lf) {
|
||||||
return style;
|
lf.register({
|
||||||
}
|
type: "sleep",
|
||||||
}
|
view: SleepNodeHtmlNode,
|
||||||
|
model: SleepNodeHtmlModel
|
||||||
// 导出方法注册
|
});
|
||||||
export function registerSleepNode(lf) {
|
}
|
||||||
lf.register({
|
|
||||||
type: "sleep",
|
|
||||||
view: SleepNodeHtmlNode,
|
|
||||||
model: SleepNodeHtmlModel
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="node__container" :class="props.model.id">
|
<div class="node__container" :class="props.model.id">
|
||||||
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
|
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
|
||||||
<template #input>
|
<template #input>
|
||||||
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
||||||
</template>
|
</template>
|
||||||
@ -33,7 +33,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
import NodeTitle from "../../components/NodeTitle.vue";
|
import NodeTitle from "../../components/NodeTitle.vue";
|
||||||
import NodeState from "../../components/NodeState.vue";
|
import NodeState from "../../components/NodeState.vue";
|
||||||
import "vue3-json-viewer/dist/index.css";
|
import "vue3-json-viewer/dist/index.css";
|
||||||
@ -61,6 +61,14 @@ const runtimes = ref(0);
|
|||||||
const inputJsonData = ref({});
|
const inputJsonData = ref({});
|
||||||
const outputJsonData = ref({});
|
const outputJsonData = ref({});
|
||||||
|
|
||||||
|
const nodeStateRef = ref(null);
|
||||||
|
|
||||||
|
const closePopover = () => {
|
||||||
|
if (nodeStateRef.value) {
|
||||||
|
nodeStateRef.value.closePopover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
emitter.on("changeNodeState", (data) => {
|
emitter.on("changeNodeState", (data) => {
|
||||||
if (data.nodeId === props.model.id) {
|
if (data.nodeId === props.model.id) {
|
||||||
@ -105,6 +113,8 @@ onUnmounted(() => {
|
|||||||
emitter.off("setProperties");
|
emitter.off("setProperties");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
defineExpose({ closePopover })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@ -1,208 +1,191 @@
|
|||||||
// 导入 HtmlNode 及其模型,为后续继承做准备
|
// 导入 HtmlNode 及其模型,为后续继承做准备
|
||||||
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
|
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
|
||||||
// 导入 Vue 相关方法,用于渲染组件
|
// 导入 Vue 相关方法,用于渲染组件
|
||||||
import { createApp, h, nextTick } from "vue";
|
import { createApp, h, nextTick } from "vue";
|
||||||
import ElementPlus from "element-plus";
|
import ElementPlus from "element-plus";
|
||||||
// 导入 Vue 组件
|
// 导入 Vue 组件
|
||||||
import StopLoop from "./stopLoop.vue";
|
import StopLoop from "./stopLoop.vue";
|
||||||
import JsonViewer from 'vue3-json-viewer'
|
import JsonViewer from 'vue3-json-viewer'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义一个 元素的 HTML 节点类,继承自 HtmlNode
|
* 定义一个 元素的 HTML 节点类,继承自 HtmlNode
|
||||||
* 该类负责在 HTML 中渲染 元素,并处理其交互逻辑
|
* 该类负责在 HTML 中渲染 元素,并处理其交互逻辑
|
||||||
*/
|
*/
|
||||||
class StopLoopHtmlNode extends HtmlNode {
|
class StopLoopHtmlNode extends HtmlNode {
|
||||||
resizeObserver = null;
|
resizeObserver = null;
|
||||||
isMounted; // 标记组件是否已挂载
|
isMounted; // 标记组件是否已挂载
|
||||||
r; // 渲染函数
|
r; // 渲染函数
|
||||||
app; // Vue 应用实例
|
app; // Vue 应用实例
|
||||||
container = null;
|
container = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
* @param props 传递给节点的属性,包括模型、图模型等
|
* @param props 传递给节点的属性,包括模型、图模型等
|
||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.initVueApp(props);
|
this.initVueApp(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
initVueApp(props) {
|
initVueApp(props) {
|
||||||
this.isMounted = false;
|
this.isMounted = false;
|
||||||
|
|
||||||
this.hideAnchor = false;
|
this.hideAnchor = false;
|
||||||
this.autoExpand = true; // 防止锚点被折叠
|
this.autoExpand = true; // 防止锚点被折叠
|
||||||
this.anchorsPreset = "default"; // 重置锚点预设
|
this.anchorsPreset = "default"; // 重置锚点预设
|
||||||
// 创建 元素的渲染函数
|
// 创建 元素的渲染函数
|
||||||
this.r = h(StopLoop, {
|
this.r = h(StopLoop, {
|
||||||
model: props.model,
|
model: props.model,
|
||||||
graphModel: props.graphModel,
|
graphModel: props.graphModel,
|
||||||
properties: {
|
properties: {
|
||||||
...props.model.getProperties(),
|
...props.model.getProperties(),
|
||||||
},
|
},
|
||||||
text: props.model.text.value,
|
text: props.model.text.value,
|
||||||
onContentChange: this.handleContentChange.bind(this),
|
onContentChange: this.handleContentChange.bind(this),
|
||||||
onBindRef: this.handleComponentInstance.bind(this),
|
onBindRef: this.handleComponentInstance.bind(this),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建 Vue 应用实例,并指定渲染函数
|
// 创建 Vue 应用实例,并指定渲染函数
|
||||||
this.app = createApp({
|
this.app = createApp({
|
||||||
render: () => this.r,
|
render: () => this.r,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将 HTML 内容设置到指定的根元素上
|
* 将 HTML 内容设置到指定的根元素上
|
||||||
* @param rootEl 根元素
|
* @param rootEl 根元素
|
||||||
*/
|
*/
|
||||||
async setHtml(rootEl) {
|
async setHtml(rootEl) {
|
||||||
if (!this.isMounted) {
|
if (!this.isMounted) {
|
||||||
this.isMounted = true;
|
this.isMounted = true;
|
||||||
this.container = document.createElement("div");
|
this.container = document.createElement("div");
|
||||||
this.container.style.display = "inline-block"; // 关键:确保容器自适应内容
|
this.container.style.display = "inline-block"; // 关键:确保容器自适应内容
|
||||||
|
|
||||||
rootEl.appendChild(this.container);
|
rootEl.appendChild(this.container);
|
||||||
this.app.use(ElementPlus); // 关键:单独注册ElementPlus
|
this.app.use(ElementPlus); // 关键:单独注册ElementPlus
|
||||||
this.app.use(JsonViewer)
|
this.app.use(JsonViewer)
|
||||||
this.app.mount(this.container);
|
this.app.mount(this.container);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
this.setupSizeObserver();
|
this.setupSizeObserver();
|
||||||
} else {
|
} else {
|
||||||
this.r.component.props.properties = this.props.model.getProperties();
|
this.r.component.props.properties = this.props.model.getProperties();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取节点文本内容
|
* 获取节点文本内容
|
||||||
* 对于元素,返回 null,因为其内容由特定组件渲染
|
* 对于元素,返回 null,因为其内容由特定组件渲染
|
||||||
* @returns {null}
|
* @returns {null}
|
||||||
*/
|
*/
|
||||||
getText() {
|
getText() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleComponentInstance(data) {
|
handleComponentInstance(data) {
|
||||||
// 将组件实例保存到节点模型
|
// 将组件实例保存到节点模型
|
||||||
this.props.model.setComponentInstance(data);
|
this.props.model.setComponentInstance(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContentChange() {
|
handleContentChange() {
|
||||||
// 内容变化时强制更新尺寸
|
// 内容变化时强制更新尺寸
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染完成后获取实际尺寸
|
// 渲染完成后获取实际尺寸
|
||||||
updateNodeSize() {
|
updateNodeSize() {
|
||||||
if (this.container) {
|
if (this.container) {
|
||||||
const { SCALE_X, SCALE_Y } = this.props.graphModel.transformModel;
|
const { SCALE_X, SCALE_Y } = this.props.graphModel.transformModel;
|
||||||
const node = this.container.querySelector(".node__container");
|
const node = this.container.querySelector(".node__container");
|
||||||
const rect = node.getBoundingClientRect();
|
const rect = node.getBoundingClientRect();
|
||||||
this.props.model.updateSize(rect.width / SCALE_X, rect.height / SCALE_Y);
|
this.props.model.updateSize(rect.width / SCALE_X, rect.height / SCALE_Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSizeObserver() {
|
setupSizeObserver() {
|
||||||
// 首次渲染立即检测
|
// 首次渲染立即检测
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
// 持续监听变化
|
// 持续监听变化
|
||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
});
|
});
|
||||||
this.resizeObserver.observe(this.container);
|
this.resizeObserver.observe(this.container);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组件卸载时移除监听
|
// 组件卸载时移除监听
|
||||||
onDestroy() {
|
onDestroy() {
|
||||||
this.resizeObserver?.disconnect();
|
this.resizeObserver?.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义一个元素的 HTML 模型类,继承自 HtmlNodeModel
|
* 定义一个元素的 HTML 模型类,继承自 HtmlNodeModel
|
||||||
* 该类主要设置节点的属性和样式
|
* 该类主要设置节点的属性和样式
|
||||||
*/
|
*/
|
||||||
class StopLoopHtmlModel extends HtmlNodeModel {
|
class StopLoopHtmlModel extends HtmlNodeModel {
|
||||||
initNodeData(data) {
|
initNodeData(data) {
|
||||||
super.initNodeData(data);
|
super.initNodeData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存组件实例引用
|
// 保存组件实例引用
|
||||||
setComponentInstance(instance) {
|
setComponentInstance(instance) {
|
||||||
this.componentInstance = instance;
|
this.componentInstance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证表单
|
/**
|
||||||
async validateForm() {
|
* 设置节点属性
|
||||||
if (this.componentInstance && this.componentInstance.validate) {
|
* 包括宽度、高度、文本编辑属性等
|
||||||
try {
|
*/
|
||||||
const result = await this.componentInstance.validate();
|
setAttributes() {
|
||||||
if (result) {
|
// 初始设置为0,后续动态更新
|
||||||
return { valid: true, message: "验证通过" };
|
this.width = 0;
|
||||||
} else {
|
this.height = 0;
|
||||||
return { valid: false, message: "验证失败" };
|
this.text.editable = false;
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
return { valid: false, message: "验证失败" };
|
updateSize(width, height) {
|
||||||
}
|
this.width = width + 24;
|
||||||
}
|
this.height = height + 50;
|
||||||
return { valid: true, message: "无验证方法" };
|
this.initNodeData(this); // 触发节点重绘
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。
|
||||||
* 设置节点属性
|
getDefaultAnchor() {
|
||||||
* 包括宽度、高度、文本编辑属性等
|
let _a = this,
|
||||||
*/
|
x = _a.x,
|
||||||
setAttributes() {
|
y = _a.y,
|
||||||
// 初始设置为0,后续动态更新
|
width = _a.width,
|
||||||
this.width = 0;
|
height = _a.height;
|
||||||
this.height = 0;
|
return [
|
||||||
this.text.editable = false;
|
{
|
||||||
}
|
x: x - width / 2,
|
||||||
|
y: y - 25,
|
||||||
updateSize(width, height) {
|
name: "left",
|
||||||
this.width = width + 24;
|
id: "".concat(this.id, "_3"),
|
||||||
this.height = height + 50;
|
properties: { connectionType: "target" },
|
||||||
this.initNodeData(this); // 触发节点重绘
|
},
|
||||||
}
|
];
|
||||||
|
}
|
||||||
// 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。
|
|
||||||
getDefaultAnchor() {
|
/**
|
||||||
let _a = this,
|
* 获取节点轮廓样式
|
||||||
x = _a.x,
|
* 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果
|
||||||
y = _a.y,
|
* @returns {object} 节点轮廓样式
|
||||||
width = _a.width,
|
*/
|
||||||
height = _a.height;
|
getOutlineStyle() {
|
||||||
return [
|
const style = super.getOutlineStyle();
|
||||||
{
|
style.stroke = "none";
|
||||||
x: x - width / 2,
|
style.hover.stroke = "none";
|
||||||
y: y - 25,
|
return style;
|
||||||
name: "left",
|
}
|
||||||
id: "".concat(this.id, "_3"),
|
}
|
||||||
properties: { connectionType: "target" },
|
|
||||||
},
|
// 导出方法注册
|
||||||
];
|
export function registerStopLoopNode(lf) {
|
||||||
}
|
lf.register({
|
||||||
|
type: "stopLoop",
|
||||||
/**
|
view: StopLoopHtmlNode,
|
||||||
* 获取节点轮廓样式
|
model: StopLoopHtmlModel,
|
||||||
* 覆盖父类方法,设置 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 及其模型,为后续继承做准备
|
// 导入 HtmlNode 及其模型,为后续继承做准备
|
||||||
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
|
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
|
||||||
// 导入 Vue 相关方法,用于渲染组件
|
// 导入 Vue 相关方法,用于渲染组件
|
||||||
import { createApp, h, nextTick } from "vue";
|
import { createApp, h, nextTick } from "vue";
|
||||||
import ElementPlus from "element-plus";
|
import ElementPlus from "element-plus";
|
||||||
// 导入 Vue 组件
|
// 导入 Vue 组件
|
||||||
import SubEndNode from "./subEnd.vue";
|
import SubEndNode from "./subEnd.vue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义一个 元素的 HTML 节点类,继承自 HtmlNode
|
* 定义一个 元素的 HTML 节点类,继承自 HtmlNode
|
||||||
* 该类负责在 HTML 中渲染 元素,并处理其交互逻辑
|
* 该类负责在 HTML 中渲染 元素,并处理其交互逻辑
|
||||||
*/
|
*/
|
||||||
class SubEndNodeHtmlNode extends HtmlNode {
|
class SubEndNodeHtmlNode extends HtmlNode {
|
||||||
resizeObserver = null;
|
resizeObserver = null;
|
||||||
isMounted; // 标记组件是否已挂载
|
isMounted; // 标记组件是否已挂载
|
||||||
r; // 渲染函数
|
r; // 渲染函数
|
||||||
app; // Vue 应用实例
|
app; // Vue 应用实例
|
||||||
container = null;
|
container = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
* @param props 传递给节点的属性,包括模型、图模型等
|
* @param props 传递给节点的属性,包括模型、图模型等
|
||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.initVueApp(props);
|
this.initVueApp(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
initVueApp(props) {
|
initVueApp(props) {
|
||||||
this.isMounted = false;
|
this.isMounted = false;
|
||||||
|
|
||||||
this.hideAnchor = false;
|
this.hideAnchor = false;
|
||||||
this.autoExpand = true; // 防止锚点被折叠
|
this.autoExpand = true; // 防止锚点被折叠
|
||||||
this.anchorsPreset = "default"; // 重置锚点预设
|
this.anchorsPreset = "default"; // 重置锚点预设
|
||||||
// 创建 元素的渲染函数
|
// 创建 元素的渲染函数
|
||||||
this.r = h(SubEndNode, {
|
this.r = h(SubEndNode, {
|
||||||
model: props.model,
|
model: props.model,
|
||||||
graphModel: props.graphModel,
|
graphModel: props.graphModel,
|
||||||
properties: {
|
properties: {
|
||||||
...props.model.getProperties(),
|
...props.model.getProperties(),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建 Vue 应用实例,并指定渲染函数
|
// 创建 Vue 应用实例,并指定渲染函数
|
||||||
this.app = createApp({
|
this.app = createApp({
|
||||||
render: () => this.r,
|
render: () => this.r,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将 HTML 内容设置到指定的根元素上
|
* 将 HTML 内容设置到指定的根元素上
|
||||||
* @param rootEl 根元素
|
* @param rootEl 根元素
|
||||||
*/
|
*/
|
||||||
async setHtml(rootEl) {
|
async setHtml(rootEl) {
|
||||||
if (!this.isMounted) {
|
if (!this.isMounted) {
|
||||||
this.isMounted = true;
|
this.isMounted = true;
|
||||||
this.container = document.createElement("div");
|
this.container = document.createElement("div");
|
||||||
this.container.style.display = "inline-block"; // 关键:确保容器自适应内容
|
this.container.style.display = "inline-block"; // 关键:确保容器自适应内容
|
||||||
|
|
||||||
rootEl.appendChild(this.container);
|
rootEl.appendChild(this.container);
|
||||||
this.app.use(ElementPlus); // 关键:单独注册ElementPlus
|
this.app.use(ElementPlus); // 关键:单独注册ElementPlus
|
||||||
this.app.mount(this.container);
|
this.app.mount(this.container);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
this.setupSizeObserver();
|
this.setupSizeObserver();
|
||||||
} else {
|
} else {
|
||||||
this.r.component.props.properties = this.props.model.getProperties();
|
this.r.component.props.properties = this.props.model.getProperties();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取节点文本内容
|
* 获取节点文本内容
|
||||||
* 对于元素,返回 null,因为其内容由特定组件渲染
|
* 对于元素,返回 null,因为其内容由特定组件渲染
|
||||||
* @returns {null}
|
* @returns {null}
|
||||||
*/
|
*/
|
||||||
getText() {
|
getText() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleComponentInstance(data) {
|
handleComponentInstance(data) {
|
||||||
// 将组件实例保存到节点模型
|
// 将组件实例保存到节点模型
|
||||||
this.props.model.setComponentInstance(data);
|
this.props.model.setComponentInstance(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContentChange() {
|
handleContentChange() {
|
||||||
// 内容变化时强制更新尺寸
|
// 内容变化时强制更新尺寸
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染完成后获取实际尺寸
|
// 渲染完成后获取实际尺寸
|
||||||
updateNodeSize() {
|
updateNodeSize() {
|
||||||
if (this.container) {
|
if (this.container) {
|
||||||
const { SCALE_X, SCALE_Y } = this.props.graphModel.transformModel;
|
const { SCALE_X, SCALE_Y } = this.props.graphModel.transformModel;
|
||||||
const node = this.container.querySelector(".node__container");
|
const node = this.container.querySelector(".node__container");
|
||||||
const rect = node.getBoundingClientRect();
|
const rect = node.getBoundingClientRect();
|
||||||
this.props.model.updateSize(rect.width / SCALE_X, rect.height / SCALE_Y);
|
this.props.model.updateSize(rect.width / SCALE_X, rect.height / SCALE_Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSizeObserver() {
|
setupSizeObserver() {
|
||||||
// 首次渲染立即检测
|
// 首次渲染立即检测
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
// 持续监听变化
|
// 持续监听变化
|
||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
});
|
});
|
||||||
this.resizeObserver.observe(this.container);
|
this.resizeObserver.observe(this.container);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组件卸载时移除监听
|
// 组件卸载时移除监听
|
||||||
onDestroy() {
|
onDestroy() {
|
||||||
this.resizeObserver?.disconnect();
|
this.resizeObserver?.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义一个元素的 HTML 模型类,继承自 HtmlNodeModel
|
* 定义一个元素的 HTML 模型类,继承自 HtmlNodeModel
|
||||||
* 该类主要设置节点的属性和样式
|
* 该类主要设置节点的属性和样式
|
||||||
*/
|
*/
|
||||||
class SubEndNodeHtmlModel extends HtmlNodeModel {
|
class SubEndNodeHtmlModel extends HtmlNodeModel {
|
||||||
initNodeData(data) {
|
initNodeData(data) {
|
||||||
super.initNodeData(data);
|
super.initNodeData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存组件实例引用
|
// 保存组件实例引用
|
||||||
setComponentInstance(instance) {
|
setComponentInstance(instance) {
|
||||||
this.componentInstance = instance;
|
this.componentInstance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证表单
|
/**
|
||||||
async validateForm() {
|
* 设置节点属性
|
||||||
if (this.componentInstance && this.componentInstance.validate) {
|
* 包括宽度、高度、文本编辑属性等
|
||||||
try {
|
*/
|
||||||
const result = await this.componentInstance.validate();
|
setAttributes() {
|
||||||
if (result) {
|
// 初始设置为0,后续动态更新
|
||||||
return { valid: true, message: "验证通过" };
|
this.width = 0;
|
||||||
} else {
|
this.height = 0;
|
||||||
return { valid: false, message: "验证失败" };
|
this.text.editable = false;
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
return { valid: false, message: "验证失败" };
|
updateSize(width, height) {
|
||||||
}
|
this.width = width + 24;
|
||||||
}
|
this.height = height + 50;
|
||||||
return { valid: true, message: "无验证方法" };
|
this.initNodeData(this); // 触发节点重绘
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。
|
||||||
* 设置节点属性
|
getDefaultAnchor() {
|
||||||
* 包括宽度、高度、文本编辑属性等
|
let _a = this,
|
||||||
*/
|
x = _a.x,
|
||||||
setAttributes() {
|
y = _a.y,
|
||||||
// 初始设置为0,后续动态更新
|
width = _a.width,
|
||||||
this.width = 0;
|
height = _a.height;
|
||||||
this.height = 0;
|
return [
|
||||||
this.text.editable = false;
|
{
|
||||||
}
|
x: x - width / 2,
|
||||||
|
y: y - 25,
|
||||||
updateSize(width, height) {
|
name: "left",
|
||||||
this.width = width + 24;
|
id: "".concat(this.id, "_3"),
|
||||||
this.height = height + 50;
|
properties: { connectionType: "target" },
|
||||||
this.initNodeData(this); // 触发节点重绘
|
},
|
||||||
}
|
];
|
||||||
|
}
|
||||||
// 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。
|
|
||||||
getDefaultAnchor() {
|
/**
|
||||||
let _a = this,
|
* 获取节点轮廓样式
|
||||||
x = _a.x,
|
* 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果
|
||||||
y = _a.y,
|
* @returns {object} 节点轮廓样式
|
||||||
width = _a.width,
|
*/
|
||||||
height = _a.height;
|
getOutlineStyle() {
|
||||||
return [
|
const style = super.getOutlineStyle();
|
||||||
{
|
style.stroke = "none";
|
||||||
x: x - width / 2,
|
style.hover.stroke = "none";
|
||||||
y: y - 25,
|
return style;
|
||||||
name: "left",
|
}
|
||||||
id: "".concat(this.id, "_3"),
|
}
|
||||||
properties: { connectionType: "target" },
|
|
||||||
},
|
// 导出方法注册
|
||||||
];
|
export function registerSubEndNode(lf) {
|
||||||
}
|
lf.register({
|
||||||
|
type: "subEnd",
|
||||||
/**
|
view: SubEndNodeHtmlNode,
|
||||||
* 获取节点轮廓样式
|
model: SubEndNodeHtmlModel,
|
||||||
* 覆盖父类方法,设置 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 及其模型,为后续继承做准备
|
// 导入 HtmlNode 及其模型,为后续继承做准备
|
||||||
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
|
import { HtmlNode, HtmlNodeModel } from "@logicflow/core";
|
||||||
// 导入 Vue 相关方法,用于渲染组件
|
// 导入 Vue 相关方法,用于渲染组件
|
||||||
import { createApp, h, nextTick } from "vue";
|
import { createApp, h, nextTick } from "vue";
|
||||||
import ElementPlus from "element-plus";
|
import ElementPlus from "element-plus";
|
||||||
// 导入 Vue 组件
|
// 导入 Vue 组件
|
||||||
import SubStartNode from "./subStart.vue";
|
import SubStartNode from "./subStart.vue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义一个 元素的 HTML 节点类,继承自 HtmlNode
|
* 定义一个 元素的 HTML 节点类,继承自 HtmlNode
|
||||||
* 该类负责在 HTML 中渲染 元素,并处理其交互逻辑
|
* 该类负责在 HTML 中渲染 元素,并处理其交互逻辑
|
||||||
*/
|
*/
|
||||||
class SubStartNodeHtmlNode extends HtmlNode {
|
class SubStartNodeHtmlNode extends HtmlNode {
|
||||||
resizeObserver = null;
|
resizeObserver = null;
|
||||||
isMounted; // 标记组件是否已挂载
|
isMounted; // 标记组件是否已挂载
|
||||||
r; // 渲染函数
|
r; // 渲染函数
|
||||||
app; // Vue 应用实例
|
app; // Vue 应用实例
|
||||||
container = null;
|
container = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
* @param props 传递给节点的属性,包括模型、图模型等
|
* @param props 传递给节点的属性,包括模型、图模型等
|
||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.initVueApp(props);
|
this.initVueApp(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
initVueApp(props) {
|
initVueApp(props) {
|
||||||
this.isMounted = false;
|
this.isMounted = false;
|
||||||
|
|
||||||
this.hideAnchor = false;
|
this.hideAnchor = false;
|
||||||
this.autoExpand = true; // 防止锚点被折叠
|
this.autoExpand = true; // 防止锚点被折叠
|
||||||
this.anchorsPreset = "default"; // 重置锚点预设
|
this.anchorsPreset = "default"; // 重置锚点预设
|
||||||
// 创建 元素的渲染函数
|
// 创建 元素的渲染函数
|
||||||
this.r = h(SubStartNode, {
|
this.r = h(SubStartNode, {
|
||||||
model: props.model,
|
model: props.model,
|
||||||
graphModel: props.graphModel,
|
graphModel: props.graphModel,
|
||||||
properties: {
|
properties: {
|
||||||
...props.model.getProperties(),
|
...props.model.getProperties(),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建 Vue 应用实例,并指定渲染函数
|
// 创建 Vue 应用实例,并指定渲染函数
|
||||||
this.app = createApp({
|
this.app = createApp({
|
||||||
render: () => this.r,
|
render: () => this.r,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将 HTML 内容设置到指定的根元素上
|
* 将 HTML 内容设置到指定的根元素上
|
||||||
* @param rootEl 根元素
|
* @param rootEl 根元素
|
||||||
*/
|
*/
|
||||||
async setHtml(rootEl) {
|
async setHtml(rootEl) {
|
||||||
if (!this.isMounted) {
|
if (!this.isMounted) {
|
||||||
this.isMounted = true;
|
this.isMounted = true;
|
||||||
this.container = document.createElement("div");
|
this.container = document.createElement("div");
|
||||||
this.container.style.display = "inline-block"; // 关键:确保容器自适应内容
|
this.container.style.display = "inline-block"; // 关键:确保容器自适应内容
|
||||||
|
|
||||||
rootEl.appendChild(this.container);
|
rootEl.appendChild(this.container);
|
||||||
this.app.use(ElementPlus); // 关键:单独注册ElementPlus
|
this.app.use(ElementPlus); // 关键:单独注册ElementPlus
|
||||||
this.app.mount(this.container);
|
this.app.mount(this.container);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
this.setupSizeObserver();
|
this.setupSizeObserver();
|
||||||
} else {
|
} else {
|
||||||
this.r.component.props.properties = this.props.model.getProperties();
|
this.r.component.props.properties = this.props.model.getProperties();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取节点文本内容
|
* 获取节点文本内容
|
||||||
* 对于元素,返回 null,因为其内容由特定组件渲染
|
* 对于元素,返回 null,因为其内容由特定组件渲染
|
||||||
* @returns {null}
|
* @returns {null}
|
||||||
*/
|
*/
|
||||||
getText() {
|
getText() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleComponentInstance(data) {
|
handleComponentInstance(data) {
|
||||||
// 将组件实例保存到节点模型
|
// 将组件实例保存到节点模型
|
||||||
this.props.model.setComponentInstance(data);
|
this.props.model.setComponentInstance(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContentChange() {
|
handleContentChange() {
|
||||||
// 内容变化时强制更新尺寸
|
// 内容变化时强制更新尺寸
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染完成后获取实际尺寸
|
// 渲染完成后获取实际尺寸
|
||||||
updateNodeSize() {
|
updateNodeSize() {
|
||||||
if (this.container) {
|
if (this.container) {
|
||||||
const { SCALE_X, SCALE_Y } = this.props.graphModel.transformModel;
|
const { SCALE_X, SCALE_Y } = this.props.graphModel.transformModel;
|
||||||
const node = this.container.querySelector(".node__container");
|
const node = this.container.querySelector(".node__container");
|
||||||
const rect = node.getBoundingClientRect();
|
const rect = node.getBoundingClientRect();
|
||||||
this.props.model.updateSize(rect.width / SCALE_X, rect.height / SCALE_Y);
|
this.props.model.updateSize(rect.width / SCALE_X, rect.height / SCALE_Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSizeObserver() {
|
setupSizeObserver() {
|
||||||
// 首次渲染立即检测
|
// 首次渲染立即检测
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
// 持续监听变化
|
// 持续监听变化
|
||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
});
|
});
|
||||||
this.resizeObserver.observe(this.container);
|
this.resizeObserver.observe(this.container);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组件卸载时移除监听
|
// 组件卸载时移除监听
|
||||||
onDestroy() {
|
onDestroy() {
|
||||||
this.resizeObserver?.disconnect();
|
this.resizeObserver?.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义一个元素的 HTML 模型类,继承自 HtmlNodeModel
|
* 定义一个元素的 HTML 模型类,继承自 HtmlNodeModel
|
||||||
* 该类主要设置节点的属性和样式
|
* 该类主要设置节点的属性和样式
|
||||||
*/
|
*/
|
||||||
class SubStartNodeHtmlModel extends HtmlNodeModel {
|
class SubStartNodeHtmlModel extends HtmlNodeModel {
|
||||||
initNodeData(data) {
|
initNodeData(data) {
|
||||||
super.initNodeData(data);
|
super.initNodeData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存组件实例引用
|
// 保存组件实例引用
|
||||||
setComponentInstance(instance) {
|
setComponentInstance(instance) {
|
||||||
this.componentInstance = instance;
|
this.componentInstance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证表单
|
/**
|
||||||
async validateForm() {
|
* 设置节点属性
|
||||||
if (this.componentInstance && this.componentInstance.validate) {
|
* 包括宽度、高度、文本编辑属性等
|
||||||
try {
|
*/
|
||||||
const result = await this.componentInstance.validate();
|
setAttributes() {
|
||||||
if (result) {
|
// 初始设置为0,后续动态更新
|
||||||
return { valid: true, message: "验证通过" };
|
this.width = 0;
|
||||||
} else {
|
this.height = 0;
|
||||||
return { valid: false, message: "验证失败" };
|
this.text.editable = false;
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
return { valid: false, message: "验证失败" };
|
updateSize(width, height) {
|
||||||
}
|
this.width = width + 24;
|
||||||
}
|
this.height = height + 50;
|
||||||
return { valid: true, message: "无验证方法" };
|
this.initNodeData(this); // 触发节点重绘
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。
|
||||||
* 设置节点属性
|
getDefaultAnchor() {
|
||||||
* 包括宽度、高度、文本编辑属性等
|
let _a = this,
|
||||||
*/
|
x = _a.x,
|
||||||
setAttributes() {
|
y = _a.y,
|
||||||
// 初始设置为0,后续动态更新
|
width = _a.width,
|
||||||
this.width = 0;
|
height = _a.height;
|
||||||
this.height = 0;
|
return [
|
||||||
this.text.editable = false;
|
{
|
||||||
}
|
x: x + width / 2 - 24,
|
||||||
|
y: y - 25,
|
||||||
updateSize(width, height) {
|
name: "right",
|
||||||
this.width = width + 24;
|
id: "".concat(this.id, "_1")
|
||||||
this.height = height + 50;
|
},
|
||||||
this.initNodeData(this); // 触发节点重绘
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。
|
/**
|
||||||
getDefaultAnchor() {
|
* 获取节点轮廓样式
|
||||||
let _a = this,
|
* 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果
|
||||||
x = _a.x,
|
* @returns {object} 节点轮廓样式
|
||||||
y = _a.y,
|
*/
|
||||||
width = _a.width,
|
getOutlineStyle() {
|
||||||
height = _a.height;
|
const style = super.getOutlineStyle();
|
||||||
return [
|
style.stroke = "none";
|
||||||
{
|
style.hover.stroke = "none";
|
||||||
x: x + width / 2 - 24,
|
return style;
|
||||||
y: y - 25,
|
}
|
||||||
name: "right",
|
}
|
||||||
id: "".concat(this.id, "_1")
|
|
||||||
},
|
// 导出方法注册
|
||||||
];
|
export function registerSubStartNode(lf) {
|
||||||
}
|
lf.register({
|
||||||
|
type: "subStart",
|
||||||
/**
|
view: SubStartNodeHtmlNode,
|
||||||
* 获取节点轮廓样式
|
model: SubStartNodeHtmlModel,
|
||||||
* 覆盖父类方法,设置 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 及其模型,为后续继承做准备
|
// 导入 HtmlNode 及其模型,为后续继承做准备
|
||||||
import { HtmlNode, HtmlNodeModel, h as svgH } from "@logicflow/core";
|
import { HtmlNode, HtmlNodeModel, h as svgH } from "@logicflow/core";
|
||||||
// 导入 Vue 相关方法,用于渲染组件
|
// 导入 Vue 相关方法,用于渲染组件
|
||||||
import { createApp, h, nextTick } from "vue";
|
import { createApp, h, nextTick } from "vue";
|
||||||
import ElementPlus from "element-plus";
|
import ElementPlus from "element-plus";
|
||||||
// 导入 Vue 组件
|
// 导入 Vue 组件
|
||||||
import Switch from "./switch.vue";
|
import Switch from "./switch.vue";
|
||||||
import JsonViewer from 'vue3-json-viewer'
|
import JsonViewer from 'vue3-json-viewer'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义一个 元素的 HTML 节点类,继承自 HtmlNode
|
* 定义一个 元素的 HTML 节点类,继承自 HtmlNode
|
||||||
* 该类负责在 HTML 中渲染 元素,并处理其交互逻辑
|
* 该类负责在 HTML 中渲染 元素,并处理其交互逻辑
|
||||||
*/
|
*/
|
||||||
class SwitchHtmlNode extends HtmlNode {
|
class SwitchHtmlNode extends HtmlNode {
|
||||||
resizeObserver = null;
|
resizeObserver = null;
|
||||||
isMounted; // 标记组件是否已挂载
|
isMounted; // 标记组件是否已挂载
|
||||||
r; // 渲染函数
|
r; // 渲染函数
|
||||||
app; // Vue 应用实例
|
app; // Vue 应用实例
|
||||||
container = null;
|
container = null;
|
||||||
|
|
||||||
static reusePool = new Map(); // 节点复用池
|
static reusePool = new Map(); // 节点复用池
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
* @param props 传递给节点的属性,包括模型、图模型等
|
* @param props 传递给节点的属性,包括模型、图模型等
|
||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.initVueApp(props);
|
this.initVueApp(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
initVueApp(props) {
|
initVueApp(props) {
|
||||||
this.isMounted = false;
|
this.isMounted = false;
|
||||||
|
|
||||||
this.hideAnchor = false;
|
this.hideAnchor = false;
|
||||||
this.autoExpand = true; // 防止锚点被折叠
|
this.autoExpand = true; // 防止锚点被折叠
|
||||||
this.anchorsPreset = "default"; // 重置锚点预设
|
this.anchorsPreset = "default"; // 重置锚点预设
|
||||||
// 创建 元素的渲染函数
|
// 创建 元素的渲染函数
|
||||||
this.r = h(Switch, {
|
this.r = h(Switch, {
|
||||||
model: props.model,
|
model: props.model,
|
||||||
graphModel: props.graphModel,
|
graphModel: props.graphModel,
|
||||||
properties: {
|
properties: {
|
||||||
...props.model.getProperties(),
|
...props.model.getProperties(),
|
||||||
},
|
},
|
||||||
text: props.model.text.value,
|
text: props.model.text.value,
|
||||||
onContentChange: this.handleContentChange.bind(this),
|
onContentChange: this.handleContentChange.bind(this),
|
||||||
onBindRef: this.handleComponentInstance.bind(this),
|
onBindRef: this.handleComponentInstance.bind(this),
|
||||||
// 添加锚点相关回调
|
// 添加锚点相关回调
|
||||||
onAddAnchor: this.handleAddAnchor.bind(this),
|
onAddAnchor: this.handleAddAnchor.bind(this),
|
||||||
onRemoveAnchor: this.handleRemoveAnchor.bind(this),
|
onRemoveAnchor: this.handleRemoveAnchor.bind(this),
|
||||||
onChangeAnchor: this.handleChangeAnchor.bind(this)
|
onChangeAnchor: this.handleChangeAnchor.bind(this)
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建 Vue 应用实例,并指定渲染函数
|
// 创建 Vue 应用实例,并指定渲染函数
|
||||||
this.app = createApp({
|
this.app = createApp({
|
||||||
render: () => this.r,
|
render: () => this.r,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将 HTML 内容设置到指定的根元素上
|
* 将 HTML 内容设置到指定的根元素上
|
||||||
* @param rootEl 根元素
|
* @param rootEl 根元素
|
||||||
*/
|
*/
|
||||||
async setHtml(rootEl) {
|
async setHtml(rootEl) {
|
||||||
const nodeId = this.props.model.id;
|
const nodeId = this.props.model.id;
|
||||||
if (SwitchHtmlNode.reusePool.has(nodeId)) {
|
if (SwitchHtmlNode.reusePool.has(nodeId)) {
|
||||||
rootEl.appendChild(SwitchHtmlNode.reusePool.get(nodeId));
|
rootEl.appendChild(SwitchHtmlNode.reusePool.get(nodeId));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isMounted) {
|
if (!this.isMounted) {
|
||||||
this.isMounted = true;
|
this.isMounted = true;
|
||||||
this.container = document.createElement("div");
|
this.container = document.createElement("div");
|
||||||
this.container.style.display = "inline-block"; // 关键:确保容器自适应内容
|
this.container.style.display = "inline-block"; // 关键:确保容器自适应内容
|
||||||
|
|
||||||
rootEl.appendChild(this.container);
|
rootEl.appendChild(this.container);
|
||||||
this.app.use(ElementPlus);
|
this.app.use(ElementPlus);
|
||||||
this.app.use(JsonViewer);
|
this.app.use(JsonViewer);
|
||||||
|
|
||||||
this.app.mount(this.container);
|
this.app.mount(this.container);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
this.setupSizeObserver();
|
this.setupSizeObserver();
|
||||||
SwitchHtmlNode.reusePool.set(nodeId, this.container);
|
SwitchHtmlNode.reusePool.set(nodeId, this.container);
|
||||||
} else {
|
} else {
|
||||||
this.r.component.props.properties = this.props.model.getProperties();
|
this.r.component.props.properties = this.props.model.getProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取节点文本内容
|
* 获取节点文本内容
|
||||||
* 对于元素,返回 null,因为其内容由特定组件渲染
|
* 对于元素,返回 null,因为其内容由特定组件渲染
|
||||||
* @returns {null}
|
* @returns {null}
|
||||||
*/
|
*/
|
||||||
getText() {
|
getText() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleComponentInstance(data) {
|
handleComponentInstance(data) {
|
||||||
// 将组件实例保存到节点模型
|
// 将组件实例保存到节点模型
|
||||||
this.props.model.setComponentInstance(data)
|
this.props.model.setComponentInstance(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContentChange() {
|
handleContentChange() {
|
||||||
// 内容变化时强制更新尺寸
|
// 内容变化时强制更新尺寸
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理添加锚点
|
// 处理添加锚点
|
||||||
handleAddAnchor(height, anchorId) {
|
handleAddAnchor(height, anchorId) {
|
||||||
const { model } = this.props
|
const { model } = this.props
|
||||||
model.addAnchor(height, anchorId)
|
model.addAnchor(height, anchorId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理删除锚点
|
// 处理删除锚点
|
||||||
handleRemoveAnchor(anchorId) {
|
handleRemoveAnchor(anchorId) {
|
||||||
this.props.model.removeAnchor(anchorId);
|
this.props.model.removeAnchor(anchorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChangeAnchor(height, anchorId) {
|
handleChangeAnchor(height, anchorId) {
|
||||||
const { model } = this.props
|
const { model } = this.props
|
||||||
model.changeAnchorHeight(height, anchorId)
|
model.changeAnchorHeight(height, anchorId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染完成后获取实际尺寸
|
// 渲染完成后获取实际尺寸
|
||||||
updateNodeSize() {
|
updateNodeSize() {
|
||||||
if (this.container) {
|
if (this.container) {
|
||||||
const { SCALE_X, SCALE_Y } = this.props.graphModel.transformModel;
|
const { SCALE_X, SCALE_Y } = this.props.graphModel.transformModel;
|
||||||
const node = this.container.querySelector(".node__container");
|
const node = this.container.querySelector(".node__container");
|
||||||
const rect = node.getBoundingClientRect();
|
const rect = node.getBoundingClientRect();
|
||||||
this.props.model.updateSize(rect.width / SCALE_X, rect.height / SCALE_Y);
|
this.props.model.updateSize(rect.width / SCALE_X, rect.height / SCALE_Y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSizeObserver() {
|
setupSizeObserver() {
|
||||||
// 首次渲染立即检测
|
// 首次渲染立即检测
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
// 持续监听变化
|
// 持续监听变化
|
||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
this.updateNodeSize();
|
this.updateNodeSize();
|
||||||
});
|
});
|
||||||
this.resizeObserver.observe(this.container);
|
this.resizeObserver.observe(this.container);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组件卸载时移除监听
|
// 组件卸载时移除监听
|
||||||
onDestroy() {
|
onDestroy() {
|
||||||
this.resizeObserver?.disconnect();
|
this.resizeObserver?.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 定义一个元素的 HTML 模型类,继承自 HtmlNodeModel
|
* 定义一个元素的 HTML 模型类,继承自 HtmlNodeModel
|
||||||
* 该类主要设置节点的属性和样式
|
* 该类主要设置节点的属性和样式
|
||||||
*/
|
*/
|
||||||
class SwitchHtmlModel extends HtmlNodeModel {
|
class SwitchHtmlModel extends HtmlNodeModel {
|
||||||
|
|
||||||
initNodeData(data) {
|
initNodeData(data) {
|
||||||
super.initNodeData(data);
|
super.initNodeData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回动态锚点
|
// 返回动态锚点
|
||||||
addAnchor(height, anchorId) {
|
addAnchor(height, anchorId) {
|
||||||
const properties = this.getProperties()
|
const properties = this.getProperties()
|
||||||
const anchor = properties?.anchor || []
|
const anchor = properties?.anchor || []
|
||||||
anchor.push({
|
anchor.push({
|
||||||
id: anchorId,
|
id: anchorId,
|
||||||
name: 'right',
|
name: 'right',
|
||||||
x: this.x,
|
x: this.x,
|
||||||
y: this.y,
|
y: this.y,
|
||||||
height
|
height
|
||||||
})
|
})
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
...properties,
|
...properties,
|
||||||
anchor
|
anchor
|
||||||
})
|
})
|
||||||
this.getDefaultAnchor()
|
this.getDefaultAnchor()
|
||||||
this.initNodeData(this)
|
this.initNodeData(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAnchor(anchorId) {
|
removeAnchor(anchorId) {
|
||||||
const properties = this.getProperties()
|
const properties = this.getProperties()
|
||||||
const anchor = properties?.anchor || []
|
const anchor = properties?.anchor || []
|
||||||
let nweAnchor = anchor.filter(item => item.id !== anchorId);
|
let nweAnchor = anchor.filter(item => item.id !== anchorId);
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
...properties,
|
...properties,
|
||||||
anchor: nweAnchor
|
anchor: nweAnchor
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
changeAnchorHeight(height, anchorId) {
|
changeAnchorHeight(height, anchorId) {
|
||||||
const properties = this.getProperties()
|
const properties = this.getProperties()
|
||||||
const anchor = properties?.anchor || []
|
const anchor = properties?.anchor || []
|
||||||
anchor.forEach(item => {
|
anchor.forEach(item => {
|
||||||
if (item.id === anchorId) {
|
if (item.id === anchorId) {
|
||||||
item.height = height
|
item.height = height
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
...properties,
|
...properties,
|
||||||
anchor
|
anchor
|
||||||
})
|
})
|
||||||
this.getDefaultAnchor()
|
this.getDefaultAnchor()
|
||||||
this.initNodeData(this)
|
this.initNodeData(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存组件实例引用
|
// 保存组件实例引用
|
||||||
setComponentInstance(instance) {
|
setComponentInstance(instance) {
|
||||||
this.componentInstance = instance;
|
this.componentInstance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证表单
|
closePopover() {
|
||||||
async validateForm() {
|
this.componentInstance.closePopover()
|
||||||
if (this.componentInstance && this.componentInstance.validate) {
|
}
|
||||||
try {
|
|
||||||
const result = await this.componentInstance.validate();
|
// 验证表单
|
||||||
if (result) {
|
async validateForm() {
|
||||||
return { valid: true, message: "验证通过" };
|
if (this.componentInstance && this.componentInstance.validate) {
|
||||||
} else {
|
try {
|
||||||
return { valid: false, message: "验证失败" };
|
const result = await this.componentInstance.validate();
|
||||||
}
|
if (result) {
|
||||||
} catch {
|
return { valid: true, message: "验证通过" };
|
||||||
return { valid: false, message: "验证失败" };
|
} else {
|
||||||
}
|
return { valid: false, message: "验证失败" };
|
||||||
}
|
}
|
||||||
return { valid: true, message: "无验证方法" };
|
} catch {
|
||||||
}
|
return { valid: false, message: "验证失败" };
|
||||||
|
}
|
||||||
/**
|
}
|
||||||
* 设置节点属性
|
return { valid: true, message: "无验证方法" };
|
||||||
* 包括宽度、高度、文本编辑属性等
|
}
|
||||||
*/
|
|
||||||
setAttributes() {
|
/**
|
||||||
// 初始设置为0,后续动态更新
|
* 设置节点属性
|
||||||
this.width = 0;
|
* 包括宽度、高度、文本编辑属性等
|
||||||
this.height = 0;
|
*/
|
||||||
this.text.editable = false;
|
setAttributes() {
|
||||||
}
|
// 初始设置为0,后续动态更新
|
||||||
|
this.width = 0;
|
||||||
updateSize(width, height) {
|
this.height = 0;
|
||||||
this.width = width + 24;
|
this.text.editable = false;
|
||||||
this.height = height + 50;
|
}
|
||||||
// const properties = lf.getProperties(this.id)
|
|
||||||
// this.setProperties({
|
updateSize(width, height) {
|
||||||
// ...properties,
|
this.width = width + 24;
|
||||||
// width: this.width,
|
this.height = height + 50;
|
||||||
// height: this.height
|
// const properties = lf.getProperties(this.id)
|
||||||
// })
|
// this.setProperties({
|
||||||
this.initNodeData(this); // 触发节点重绘
|
// ...properties,
|
||||||
}
|
// width: this.width,
|
||||||
|
// height: this.height
|
||||||
// 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。
|
// })
|
||||||
getDefaultAnchor() {
|
this.initNodeData(this); // 触发节点重绘
|
||||||
let _a = this,
|
}
|
||||||
x = _a.x,
|
|
||||||
y = _a.y,
|
// 定义节点只有左右两个锚点. 锚点位置通过中心点和宽度算出来。
|
||||||
width = _a.width,
|
getDefaultAnchor() {
|
||||||
height = _a.height;
|
let _a = this,
|
||||||
const properties = this.getProperties()
|
x = _a.x,
|
||||||
const anchor = properties?.anchor || []
|
y = _a.y,
|
||||||
// const { SCALE_Y } = this.graphModel.transformModel;
|
width = _a.width,
|
||||||
const ids = []
|
height = _a.height;
|
||||||
anchor.forEach(item => {
|
const properties = this.getProperties()
|
||||||
ids.push(item.id)
|
const anchor = properties?.anchor || []
|
||||||
if (item.id !== "".concat(this.id, "_else")) {
|
// const { SCALE_Y } = this.graphModel.transformModel;
|
||||||
item.x = x + width / 2 - 40
|
const ids = []
|
||||||
// item.y = (y - height / 2 + item.height + 60) / SCALE_Y
|
anchor.forEach(item => {
|
||||||
item.y = y - height / 2 + item.height + 60
|
ids.push(item.id)
|
||||||
} else {
|
if (item.id !== "".concat(this.id, "_else")) {
|
||||||
item.x = x + width / 2 - 40
|
item.x = x + width / 2 - 40
|
||||||
item.y = y + height / 2 - 80
|
// 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
|
||||||
return [
|
item.y = y + height / 2 - 80
|
||||||
// {
|
}
|
||||||
// x: x + width / 2 - 40,
|
})
|
||||||
// y: y + height / 2 - 80,
|
|
||||||
// name: "right",
|
return [
|
||||||
// id: "".concat(this.id, "_else")
|
// {
|
||||||
// },
|
// x: x + width / 2 - 40,
|
||||||
{
|
// y: y + height / 2 - 80,
|
||||||
x: x - width / 2,
|
// name: "right",
|
||||||
y: y - 25,
|
// id: "".concat(this.id, "_else")
|
||||||
name: "left",
|
// },
|
||||||
id: "".concat(this.id, "_entry")
|
{
|
||||||
},
|
x: x - width / 2,
|
||||||
...anchor
|
y: y - 25,
|
||||||
];
|
name: "left",
|
||||||
}
|
id: "".concat(this.id, "_entry")
|
||||||
|
},
|
||||||
/**
|
...anchor
|
||||||
* 获取节点轮廓样式
|
];
|
||||||
* 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果
|
}
|
||||||
* @returns {object} 节点轮廓样式
|
|
||||||
*/
|
/**
|
||||||
getOutlineStyle() {
|
* 获取节点轮廓样式
|
||||||
const style = super.getOutlineStyle();
|
* 覆盖父类方法,设置 stroke 属性为 none,以适应特定的视觉效果
|
||||||
style.stroke = "none";
|
* @returns {object} 节点轮廓样式
|
||||||
style.hover.stroke = "none";
|
*/
|
||||||
return style;
|
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,
|
export function registerSwitch(lf) {
|
||||||
});
|
lf.register({
|
||||||
}
|
type: "branch",
|
||||||
|
view: SwitchHtmlNode,
|
||||||
|
model: SwitchHtmlModel,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="node__container" ref="switchRef" @mouseleave="setNodeProperties">
|
<div class="node__container" ref="switchRef" @mouseleave="setNodeProperties">
|
||||||
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
|
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
|
||||||
<template #input>
|
<template #input>
|
||||||
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
||||||
</template>
|
</template>
|
||||||
@ -407,6 +407,14 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const nodeStateRef = ref(null);
|
||||||
|
|
||||||
|
const closePopover = () => {
|
||||||
|
if (nodeStateRef.value) {
|
||||||
|
nodeStateRef.value.closePopover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
emits("addAnchor", 280, "".concat(props.model.id, "_else"));
|
emits("addAnchor", 280, "".concat(props.model.id, "_else"));
|
||||||
|
|
||||||
@ -450,6 +458,8 @@ onUnmounted(() => {
|
|||||||
emitter.off("changeNodeState");
|
emitter.off("changeNodeState");
|
||||||
emitter.off("contentChange");
|
emitter.off("contentChange");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({ closePopover })
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.node__container {
|
.node__container {
|
||||||
|
|||||||
@ -51,7 +51,7 @@ export default defineConfig(({mode, command}) => {
|
|||||||
//李小龙 http://192.168.0.5:13080
|
//李小龙 http://192.168.0.5:13080
|
||||||
// dev http://10.148.20.34:13080
|
// dev http://10.148.20.34:13080
|
||||||
// target: VITE_API_URL,
|
// 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,
|
changeOrigin: true,
|
||||||
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user