CMVR-IOT-UI/src/views/flow/index.vue

908 lines
24 KiB
Vue
Raw Normal View History

2025-09-03 09:16:13 +08:00
<template>
<div class="page">
<el-container class="page__container">
2025-11-17 17:32:33 +08:00
<el-header v-if="!flowInfoData.isLog">
2025-09-03 09:16:13 +08:00
<div>
<div>{{ flowInfoData.name }}</div>
<div>{{ stateMap[flowState] }}</div>
</div>
<div class="btn__container">
<div class="btn">
<el-button @click="group">分组</el-button>
2025-11-17 17:32:33 +08:00
<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
>
2025-09-03 09:16:13 +08:00
</div>
<div class="btn">
<el-tooltip
class="box-item"
effect="dark"
content="试运行成功后才可以发布"
placement="top"
v-if="flowState !== 'testRunFinish'"
>
2025-11-17 17:32:33 +08:00
<el-button type="primary" :disabled="true">发布</el-button>
2025-09-03 09:16:13 +08:00
</el-tooltip>
2025-11-17 17:32:33 +08:00
<el-button v-else @click="deployFlow" type="primary"
>发布</el-button
>
2025-09-03 09:16:13 +08:00
2025-11-17 17:32:33 +08:00
<el-popover
:width="260"
@show="showNodeRelationship"
popper-class="node-relationship-network-popover"
>
2025-09-03 09:16:13 +08:00
<template #reference>
2025-11-17 17:32:33 +08:00
<el-button style="margin-right: 16px"
><el-icon><Location /></el-icon
></el-button>
2025-09-03 09:16:13 +08:00
</template>
<div>
<div class="title">节点关系网</div>
<div class="fit">
2025-11-17 17:32:33 +08:00
<el-tooltip
class="box-item"
effect="dark"
content="适应屏幕"
placement="bottom"
>
<el-button size="small" circle @click="fitView"
><el-icon><Location /></el-icon
></el-button>
</el-tooltip>
2025-09-03 09:16:13 +08:00
</div>
<el-tree
style="max-width: 600px"
:data="treeData"
:props="defaultProps"
:default-expand-all="true"
@node-click="handleTreeNodeClick"
/>
</div>
</el-popover>
</div>
</div>
</el-header>
2025-11-17 14:54:32 +08:00
<el-container class="container">
2025-09-03 09:16:13 +08:00
<el-aside v-if="!flowStore.disableForm">
<Aside />
</el-aside>
<el-main>
2025-11-17 17:32:33 +08:00
<div
class="flow__container"
ref="flowContainerRef"
@dragover="dragover"
@drop="drop"
></div>
2025-09-03 09:16:13 +08:00
</el-main>
</el-container>
</el-container>
2025-11-17 17:32:33 +08:00
<TestRun
:drawer="isOpen"
@close="isOpen = false"
@changeState="changeState"
:flowId="flowInfoData.itemId"
/>
2026-04-13 10:31:55 +08:00
<ParamsDrawer
:drawer="showParamsDrawer"
:data="paramsDrawerData"
@close="showParamsDrawer = false"
/>
2025-09-03 09:16:13 +08:00
</div>
2025-11-17 17:32:33 +08:00
<el-dialog v-model="visible" width="500" append-to-body>
<template #header="{ close, titleId, titleClass }">
<div class="my-header">
<h4 :id="titleId" :class="titleClass">执行{{ nodeName }}节点</h4>
</div>
</template>
<el-form
:inline="true"
:model="formData"
:rules="rules"
ref="executeForm"
label-position="top"
label-width="auto"
:disabled="flowStore.disableForm"
>
<div v-for="(property, index) in formData.nodeParams" :key="index">
<el-row>
<el-form-item
:label="index === 0 ? '参数名' : ''"
:prop="`nodeParams.${index}.name`"
:rules="[
{ required: true, message: '请输入参数名', trigger: 'blur' },
]"
>
<el-input
:disabled="property?.disabled || false"
v-model="property.name"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
:label="index === 0 ? '参数值' : ''"
:prop="`nodeParams.${index}.input`"
:rules="[
{ required: true, message: '请输入参数值', trigger: 'blur' },
]"
>
<el-input-number
v-if="property.componentType === 'number'"
v-model="property.input"
:min="0"
:controls="false"
:step-strictly="true"
placeholder="请输入"
clearable
/>
<el-select
v-model="property.input"
v-else-if="property.componentType === 'select'"
>
<el-option
v-for="item in property.selectOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-else
v-model="property.input"
autosize
type="textarea"
placeholder="请输入"
clearable
/>
</el-form-item>
</el-row>
</div>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="execute">
确定
</el-button>
</div>
</template>
</el-dialog>
2025-09-03 09:16:13 +08:00
</template>
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from "vue";
import LogicFlow, { action } from "@logicflow/core";
import "@logicflow/core/es/index.css";
import "@logicflow/core/lib/style/index.css";
2025-11-17 17:32:33 +08:00
import "@logicflow/extension/lib/style/index.css";
import { lfConfig, registerCustomizeNode } from "./config";
2025-09-03 09:16:13 +08:00
import TestRun from "./components/TestRun.vue";
2026-04-13 10:31:55 +08:00
import ParamsDrawer from "./components/ParamsDrawer.vue";
2025-09-03 09:16:13 +08:00
import Aside from "./components/Aside.vue";
2025-11-17 17:32:33 +08:00
import { ElMessage } from "element-plus";
import { useFlowStore } from "@/store/modules/flow";
import { emitter } from "@/utils/eventBus";
import { recursiveFilter, newEdge, convertToTree } from "@/utils/flow";
import {
flowDeploy,
flowView,
flowPause,
flowResume,
flowStop,
flowAction
} from "@/api/device/flow";
import { getDetect } from "@/api/test/detect";
2025-09-03 09:16:13 +08:00
import { Location } from "@element-plus/icons-vue";
const flowContainerRef = ref(null);
const lfRef = ref(null);
const dragover = (e) => {
2025-11-17 17:32:33 +08:00
e.preventDefault();
};
2025-09-03 09:16:13 +08:00
const drop = (e) => {
2025-11-17 17:32:33 +08:00
let node = e.dataTransfer.getData("flowNode");
node = JSON.parse(node);
if (["stopLoop", "subEnd", "subStart", "currentLoop"].includes(node.type)) {
2025-11-17 17:32:33 +08:00
ElMessage.warning("此节点只能拖拽到循环节点的循环体中");
return;
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
const point = lf.getPointByClient(e.clientX, e.clientY);
const { type, ...other } = node;
2025-09-03 09:16:13 +08:00
lf.addNode({
2025-11-17 17:32:33 +08:00
type,
x: point.canvasOverlayPosition.x,
y: point.canvasOverlayPosition.y,
properties: {
...other,
},
});
};
2025-09-03 09:16:13 +08:00
2025-11-17 17:32:33 +08:00
const flowStore = useFlowStore();
flowStore.getDeviceList();
2025-09-03 09:16:13 +08:00
2025-11-17 17:32:33 +08:00
const isOpen = ref(false);
2025-09-03 09:16:13 +08:00
const stateMap = {
2025-11-17 17:32:33 +08:00
unpublished: "未发布",
testRunning: "试运行中",
testRunFinish: "试运行成功",
testRunError: "试运行失败",
release: "已发布",
updateData: "修改中",
pause: "已暂停",
stop: "已终止",
};
const flowState = ref("unpublished");
2025-09-03 09:16:13 +08:00
const flowInfoData = ref({
2025-11-17 17:32:33 +08:00
itemId: "",
instId: "",
taskId: "",
2025-09-03 09:16:13 +08:00
isLog: false,
2025-11-17 17:32:33 +08:00
name: "流程",
});
2025-09-03 09:16:13 +08:00
/**
* 试运行
*/
const testRun = async () => {
// 1、验证画布内所有的表单通过验证
2025-11-17 17:32:33 +08:00
const { nodes, edges } = lf.getGraphData();
2025-09-03 09:16:13 +08:00
2025-11-17 17:32:33 +08:00
const isolatedNodes = nodes.filter((node) => {
2025-09-03 09:16:13 +08:00
const nodeId = node.id;
// 检查是否存在关联边
2025-11-17 17:32:33 +08:00
return !edges.some(
(edge) => edge.sourceNodeId === nodeId || edge.targetNodeId === nodeId
2025-09-03 09:16:13 +08:00
);
});
2025-11-17 17:32:33 +08:00
const gl = isolatedNodes.filter((item) => {
return !item.properties?.parentId && item.type !== "selectArea";
});
2025-09-03 09:16:13 +08:00
if (gl && gl.length > 0) {
2025-11-17 17:32:33 +08:00
ElMessage.warning("存在孤立的节点,请检查!");
return;
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
2025-09-03 09:16:13 +08:00
let allValid = true;
for (const node of nodes) {
const nodeModel = lf.getNodeModelById(node.id);
if (nodeModel && nodeModel.validateForm) {
const result = await nodeModel.validateForm();
if (!result.valid) {
allValid = false;
}
}
}
2025-11-17 17:32:33 +08:00
2025-09-03 09:16:13 +08:00
if (allValid) {
2025-11-17 17:32:33 +08:00
isOpen.value = true;
2025-09-03 09:16:13 +08:00
} else {
2025-11-17 17:32:33 +08:00
ElMessage.error("部分节点验证失败,请查看详情");
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
};
2025-09-03 09:16:13 +08:00
// 流程暂停
const flowPauseFn = async () => {
2025-11-17 17:32:33 +08:00
const res = await flowPause(instId.value);
2025-09-03 09:16:13 +08:00
if (res.code === 200) {
2025-11-17 17:32:33 +08:00
flowState.value = "pause";
2025-09-03 09:16:13 +08:00
ElMessage.success(res.msg);
}
2025-11-17 17:32:33 +08:00
};
2025-09-03 09:16:13 +08:00
const flowResumeFn = async () => {
2025-11-17 17:32:33 +08:00
const res = await flowResume(instId.value);
2025-09-03 09:16:13 +08:00
if (res.code === 200) {
2025-11-17 17:32:33 +08:00
flowState.value = "testRunning";
loopFlowView(instId.value);
2025-09-03 09:16:13 +08:00
ElMessage.success(res.msg);
}
2025-11-17 17:32:33 +08:00
};
2025-09-03 09:16:13 +08:00
const flowStopFn = async () => {
2025-11-17 17:32:33 +08:00
const res = await flowStop(instId.value);
2025-09-03 09:16:13 +08:00
if (res.code === 200) {
2025-11-17 17:32:33 +08:00
flowState.value = "stop";
flowStore.updateDisableForm(false);
2025-09-03 09:16:13 +08:00
ElMessage.success(res.msg);
}
2025-11-17 17:32:33 +08:00
};
2025-09-03 09:16:13 +08:00
const initFlowData = {
nodes: [
{
type: "start",
x: 300,
y: 100,
properties: {
2025-11-17 17:32:33 +08:00
name: "Start",
action: "start",
},
2025-09-03 09:16:13 +08:00
},
{
type: "end",
x: 1300,
y: 700,
properties: {
2025-11-17 17:32:33 +08:00
name: "End",
action: "end",
},
},
2025-09-03 09:16:13 +08:00
],
// 边
edges: [],
};
const validEdge = (data) => {
2025-11-17 17:32:33 +08:00
const sourceAnchorArr = lf.getNodeModelById(data.sourceNodeId).anchors;
const [sourceName] = sourceAnchorArr
.filter((item) => {
return item.id === data.sourceAnchorId;
})
.map((item) => {
return item.name;
});
const targetAnchorArr = lf.getNodeModelById(data.targetNodeId).anchors;
const [targetName] = targetAnchorArr
.filter((item) => {
return item.id === data.targetAnchorId;
})
.map((item) => {
return item.name;
});
if (sourceName === "right" && targetName === "left") {
return;
} else if (
(sourceName === "right" && targetName === "right") ||
(sourceName === "left" && targetName === "left")
) {
2025-09-03 09:16:13 +08:00
lf.deleteEdge(data.id);
2025-11-17 17:32:33 +08:00
} else if (sourceName === "left" && targetName === "right") {
const sourceNodeId = data.targetNodeId;
const sourceAnchorId = data.targetAnchorId;
const targetNodeId = data.sourceNodeId;
const targetAnchorId = data.sourceAnchorId;
2025-09-03 09:16:13 +08:00
lf.deleteEdge(data.id);
lf.addEdge({
2025-11-17 17:32:33 +08:00
type: "bezier",
2025-09-03 09:16:13 +08:00
sourceNodeId,
targetNodeId,
sourceAnchorId,
2025-11-17 17:32:33 +08:00
targetAnchorId,
2025-09-03 09:16:13 +08:00
});
}
2025-11-17 17:32:33 +08:00
};
2025-09-03 09:16:13 +08:00
2025-11-17 17:32:33 +08:00
const instId = ref(null);
2025-09-03 09:16:13 +08:00
const changeState = (value) => {
2025-11-17 17:32:33 +08:00
const flowData = JSON.parse(JSON.stringify(lf.getGraphData()));
oldFlowData = flowData;
isOpen.value = false;
flowStore.updateDisableForm(true);
flowState.value = value.type;
instId.value = value.instId;
loopFlowView(value.instId);
};
2025-09-03 09:16:13 +08:00
const loopFlowView = async (instId, taskId = null) => {
const params = {
itemId: flowInfoData.value.itemId,
instId,
2025-11-17 17:32:33 +08:00
};
2025-09-03 09:16:13 +08:00
if (taskId) {
2025-11-17 17:32:33 +08:00
params.taskId = taskId;
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
const res = await flowView(params);
2025-09-03 09:16:13 +08:00
if (res.code === 200) {
2025-11-17 17:32:33 +08:00
const arr = res.data || [];
arr.forEach((item) => {
emitter.emit("changeNodeState", item);
});
oldFlowData = JSON.parse(JSON.stringify(lf.getGraphData()));
if (
arr[arr.length - 1]?.nodeType !== "END" &&
!["FAILED", "STOPPED"].includes(arr[arr.length - 1]?.status)
) {
2025-09-03 09:16:13 +08:00
setTimeout(() => {
2025-11-17 17:32:33 +08:00
loopFlowView(instId);
}, 2000);
} else if (
arr[arr.length - 1]?.nodeType === "END" &&
arr[arr.length - 1]?.status === "SUCCESS"
) {
if (!flowInfoData.value.isLog) {
flowStore.updateDisableForm(false);
flowState.value = "testRunFinish";
}
2025-09-03 09:16:13 +08:00
} else {
if (!flowInfoData.value.isLog) {
2025-11-17 17:32:33 +08:00
if (["PAUSED", "STOPPED"].includes(arr[arr.length - 1]?.status)) {
flowState.value =
arr[arr.length - 1]?.status === "PAUSED" ? "pause" : "stop";
2025-09-03 09:16:13 +08:00
} else {
2025-11-17 17:32:33 +08:00
flowState.value = "testRunError";
flowStore.updateDisableForm(false);
}
2025-09-03 09:16:13 +08:00
}
}
2025-11-17 17:32:33 +08:00
}
};
2025-09-03 09:16:13 +08:00
2025-11-17 17:32:33 +08:00
let oldFlowData = {};
2025-09-03 09:16:13 +08:00
/**
* 发布
*/
const deployFlow = async () => {
2025-11-17 17:32:33 +08:00
if (flowState.value !== "testRunFinish") {
ElMessage.warning("试运行成功后才可以发布");
return;
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
2025-09-03 09:16:13 +08:00
try {
2025-11-17 17:32:33 +08:00
const res = await flowDeploy({ id: flowInfoData.value.itemId });
2025-09-03 09:16:13 +08:00
if (res.code === 200) {
2025-11-17 17:32:33 +08:00
ElMessage.success(res.msg);
flowState.value = "release";
2025-09-03 09:16:13 +08:00
} else {
2025-11-17 17:32:33 +08:00
ElMessage.error(res.msg);
2025-09-03 09:16:13 +08:00
}
} catch (error) {
2025-11-17 17:32:33 +08:00
ElMessage.error("部署操作产生异常");
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
};
2025-09-03 09:16:13 +08:00
const loadFlowData = async (id, lf) => {
try {
2025-11-17 17:32:33 +08:00
const res = await getDetect(id);
2025-09-03 09:16:13 +08:00
if (res.data && res.data.flowData) {
2025-11-17 17:32:33 +08:00
const flowData = JSON.parse(res.data.flowData);
const selectArea = [];
flowData.nodes.forEach((item) => {
if (item.type === "selectArea") {
selectArea.push(...item.children);
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
});
2025-09-03 09:16:13 +08:00
lf.render(flowData);
2025-11-17 17:32:33 +08:00
nextTick(() => {
selectArea.forEach((item) => {
lf.getNodeModelById(item).draggable = false;
});
oldFlowData = JSON.parse(JSON.stringify(lf.getGraphData()));
lf.fitView();
});
2025-09-03 09:16:13 +08:00
} else {
// 渲染数据 data
lf.render(initFlowData);
2025-11-17 17:32:33 +08:00
oldFlowData = initFlowData;
nextTick(() => lf.zoom(0.8, [850, 450]));
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
} catch (err) {
2025-09-03 09:16:13 +08:00
lf.render(initFlowData);
2025-11-17 17:32:33 +08:00
oldFlowData = initFlowData;
nextTick(() => lf.zoom(0.8, [850, 450]));
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
};
2025-09-03 09:16:13 +08:00
const registerBeforeUnload = () => {
2025-11-17 17:32:33 +08:00
window.addEventListener("beforeunload", handleBeforeUnload);
};
2025-09-03 09:16:13 +08:00
const handleBeforeUnload = (event) => {
2025-11-17 17:32:33 +08:00
if (flowState.value === "pause" && !flowInfoData.value.isLog) {
flowStopFn();
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
};
2025-09-03 09:16:13 +08:00
const justLoadFlow = () => {
const url = new URL(window.location.href);
2025-11-17 17:32:33 +08:00
const locationParams = Object.fromEntries(url.searchParams.entries());
2025-09-03 09:16:13 +08:00
if (locationParams?.itemId) {
2025-11-17 17:32:33 +08:00
flowState.value = locationParams.state === "0" ? "release" : "unpublished";
2025-09-03 09:16:13 +08:00
flowInfoData.value = {
itemId: locationParams.itemId,
2025-11-17 17:32:33 +08:00
instId: locationParams?.instId || "",
taskId: locationParams?.taskId || "",
2025-09-03 09:16:13 +08:00
isLog: locationParams?.isLog || false,
2025-11-17 17:32:33 +08:00
name: decodeURIComponent(locationParams.name),
};
2025-09-03 09:16:13 +08:00
if (flowInfoData.value.isLog) {
2025-11-17 17:32:33 +08:00
flowStore.updateDisableForm(true);
2025-09-03 09:16:13 +08:00
setTimeout(() => {
2025-11-17 17:32:33 +08:00
loopFlowView(flowInfoData.value.instId, flowInfoData.value.taskId);
}, 1000);
2025-09-03 09:16:13 +08:00
}
}
2025-11-17 17:32:33 +08:00
};
2025-09-03 09:16:13 +08:00
const initFlow = () => {
if (flowContainerRef.value) {
const lf = new LogicFlow({
container: flowContainerRef.value,
...lfConfig,
zoomOptions: {
2025-11-17 17:32:33 +08:00
initScale: 0.5, // 初始缩放 50%
minScale: 0.1, // 最小缩放 10%
maxScale: 3, // 最大缩放 300%
2025-09-03 09:16:13 +08:00
},
keyboard: {
enabled: true,
shortcuts: [
{
keys: ["backspace", "delete"], // 覆盖删除键
callback: () => {
const activeElem = document.activeElement;
2025-11-17 17:32:33 +08:00
if (
activeElem.tagName === "INPUT" ||
activeElem.tagName === "TEXTAREA"
) {
return; // 不处理输入框内的删除
2025-09-03 09:16:13 +08:00
}
const { nodes, edges } = lf.getSelectElements();
if (nodes.length > 0) {
// 检查是否存在受保护节点
2025-11-17 17:32:33 +08:00
const hasProtectedNode = nodes.some((node) =>
["start", "end"].includes(node.type)
);
2025-09-03 09:16:13 +08:00
if (hasProtectedNode) {
lf.selectElementById(null); // 取消选中
return; // 阻断删除
}
2025-11-17 17:32:33 +08:00
2025-09-03 09:16:13 +08:00
// 默认删除逻辑
2025-11-17 17:32:33 +08:00
nodes.forEach((node) => {
if (node.type === "selectArea") {
lf.getNodeModelById(node.id).children = [];
node.children.forEach((item) => {
lf.getNodeModelById(item).draggable = true;
});
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
lf.deleteNode(node.id);
2025-09-03 09:16:13 +08:00
// 删除节点所有的边
lf.deleteEdgeByNodeId(node.id);
});
2025-11-17 17:32:33 +08:00
return;
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
if (nodes.length === 0 && edges.length > 0) {
edges.forEach((node) => {
2025-09-03 09:16:13 +08:00
// 删除边
lf.deleteEdge(node.id);
2025-11-17 17:32:33 +08:00
});
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
},
2025-09-03 09:16:13 +08:00
},
{
2025-11-17 17:32:33 +08:00
keys: ["ctrl+z"], // 自定义快捷键
2025-09-03 09:16:13 +08:00
callback: () => {
2025-11-17 17:32:33 +08:00
return false;
},
2025-09-03 09:16:13 +08:00
},
{
keys: ["ctrl+shift+z"],
2025-11-17 17:32:33 +08:00
callback: () => false,
},
],
2025-09-03 09:16:13 +08:00
},
2025-11-17 17:32:33 +08:00
});
2025-09-03 09:16:13 +08:00
2025-11-17 17:32:33 +08:00
registerCustomizeNode(lf);
2025-09-03 09:16:13 +08:00
2025-11-17 17:32:33 +08:00
loadFlowData(flowInfoData.value.itemId, lf);
2025-09-03 09:16:13 +08:00
2025-11-17 17:32:33 +08:00
lf.on("edge:add", ({ data }) => {
validEdge(data);
2025-09-03 09:16:13 +08:00
});
// 监听历史记录变化
2026-04-13 10:31:55 +08:00
// lf.on("history:change", ({ data }) => {
// if (flowStore.disableForm) {
// return;
// }
// const flowData = lf.getGraphData();
// if (JSON.stringify(oldFlowData) === JSON.stringify(flowData)) {
// return;
// }
// flowState.value = "updateData";
// });
2025-09-03 09:16:13 +08:00
// 监听子节点拖动事件
lf.on("node:mousemove", ({ data, e }) => {
const { nodes } = lf.getGraphData();
2025-11-17 17:32:33 +08:00
const arr = recursiveFilter(nodes, data.id, "loop");
arr.forEach((item) => {
const node = lf.getNodeModelById(item.id);
node.updateSize();
});
operationEdge(arr);
2025-09-03 09:16:13 +08:00
});
lf.on("node:mouseleave", ({ data }) => {
2025-11-17 17:32:33 +08:00
const node = lf.getNodeModelById(data.id);
node.setCustomProperties && node.setCustomProperties();
});
2025-09-03 09:16:13 +08:00
2026-04-13 10:31:55 +08:00
lf.on("node:dbclick", ({ data, e }) => {
showParamsDrawer.value = true;
paramsDrawerData.value = data;
console.log(data)
});
2025-09-03 09:16:13 +08:00
lfRef.value = lf;
2025-11-17 17:32:33 +08:00
window.lf = lf;
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
};
2025-09-03 09:16:13 +08:00
const group = () => {
2025-11-17 17:32:33 +08:00
const PADDING = 200;
2025-09-03 09:16:13 +08:00
lf.extension.selectionSelect.openSelectionSelect();
2025-11-17 17:32:33 +08:00
lf.once("selection:selected", (data) => {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
let children = [];
data.elements.forEach((item) => {
if (item.BaseType === "node") {
minX = Math.min(minX, item.x - item.width / 2);
minY = Math.min(minY, item.y - item.height / 2);
maxX = Math.max(maxX, item.x + item.width / 2);
maxY = Math.max(maxY, item.y + item.height / 2);
children.push(item.id);
lf.getNodeModelById(item.id).draggable = false;
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
});
2025-09-03 09:16:13 +08:00
if (children.length === 0) {
2025-11-17 17:32:33 +08:00
return;
2025-09-03 09:16:13 +08:00
}
2025-11-17 17:32:33 +08:00
let width = maxX - minX + PADDING;
let height = maxY - minY + PADDING;
2025-09-03 09:16:13 +08:00
lf.addNode({
2025-11-17 17:32:33 +08:00
type: "selectArea",
2025-09-03 09:16:13 +08:00
x: minX + (maxX - minX) / 2,
y: minY + (maxY - minY) / 2,
properties: {
width,
height,
},
2025-11-17 17:32:33 +08:00
children,
});
lf.clearSelectElements();
2025-09-03 09:16:13 +08:00
lf.extension.selectionSelect.closeSelectionSelect();
});
2025-11-17 17:32:33 +08:00
};
2025-09-03 09:16:13 +08:00
2025-11-17 17:32:33 +08:00
const treeData = ref(null);
2025-09-03 09:16:13 +08:00
const defaultProps = {
2025-11-17 17:32:33 +08:00
children: "children",
label: "name",
};
2025-09-03 09:16:13 +08:00
const showNodeRelationship = () => {
2025-11-17 17:32:33 +08:00
const { nodes } = lf.getGraphData();
2025-09-03 09:16:13 +08:00
const outputData = convertToTree(nodes);
2025-11-17 17:32:33 +08:00
treeData.value = outputData;
};
2025-09-03 09:16:13 +08:00
const handleTreeNodeClick = (node) => {
lf.focusOn({
2025-11-17 17:32:33 +08:00
id: node.id,
});
lf.selectElementById(node.id);
};
2025-09-03 09:16:13 +08:00
const fitView = () => {
2025-11-17 17:32:33 +08:00
lf.fitView();
};
const nodeName = ref("");
const visible = ref(false)
const formData = reactive({
nodeParams: [],
})
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') {
formData.nodeParams = [
{
disabled: true,
input: "",
name: "terminalId",
type: "input"
},
...nodeParams
]
} else {
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 res = await flowAction({
action: nodeAction.value,
payload: obj
})
if (res.code === 200) {
ElMessage.success('执行成功')
} else {
ElMessage.error(res.msg)
}
}
2025-09-03 09:16:13 +08:00
}
2026-04-13 10:31:55 +08:00
const showParamsDrawer = ref(false);
const paramsDrawerData = ref({})
2025-09-03 09:16:13 +08:00
onMounted(() => {
2025-11-17 17:32:33 +08:00
justLoadFlow();
initFlow();
document.addEventListener("singleNodeExecution", singleNodeExecution);
registerBeforeUnload();
2025-09-03 09:16:13 +08:00
});
const operationEdge = (arr) => {
2025-11-17 17:32:33 +08:00
arr.forEach((item) => {
newEdge(item.id);
});
};
2025-09-03 09:16:13 +08:00
onUnmounted(() => {
2025-11-17 17:32:33 +08:00
emitter.off("changeNodeState");
window.removeEventListener("beforeunload", handleBeforeUnload);
document.removeEventListener("singleNodeExecution", singleNodeExecution);
});
2025-09-03 09:16:13 +08:00
</script>
<style lang="scss" scoped>
.page {
width: 100%;
height: 100%;
2025-11-17 14:54:32 +08:00
overflow: hidden;
2025-09-03 09:16:13 +08:00
.page__container {
width: 100%;
height: 100%;
2025-11-17 17:32:33 +08:00
position: relative;
2025-09-03 09:16:13 +08:00
.el-header {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #ddd;
.btn__container {
display: flex;
.btn {
margin-right: 12px;
}
}
}
2025-11-17 14:54:32 +08:00
.container {
height: calc(100% - 60px);
}
2025-09-03 09:16:13 +08:00
.el-main {
padding: 0;
}
.el-aside {
margin: 0;
width: 260px;
padding: 0;
}
.flow__container {
width: 100%;
height: 100%;
:deep(.lf-base) {
foreignObject {
overflow: visible !important;
}
.lf-node-content {
.lf-node-anchor {
display: block !important; /* 强制显示 */
2025-11-17 17:32:33 +08:00
opacity: 1 !important; /* 取消透明度 */
2025-09-03 09:16:13 +08:00
}
}
.lf-node-selected {
.node__box {
border-color: #8dc99d;
}
.node__container {
border-color: #8dc99d;
}
.group__container {
border-color: #8dc99d;
}
}
}
}
}
}
2025-11-17 17:32:33 +08:00
2025-09-03 09:16:13 +08:00
</style>
<style lang="scss">
.node-relationship-network-popover {
position: relative;
.title {
font-size: 16px;
font-weight: bold;
text-align: center;
}
.fit {
position: absolute;
right: 20px;
top: 10px;
}
.el-tree {
margin-top: 20px;
max-height: 200px;
overflow-y: auto;
}
}
2025-11-17 17:32:33 +08:00
.el-dialog {
.el-input {
width: 200px;
}
.el-select {
width: 200px;
}
.el-textarea {
width: 200px
}
}
2025-09-03 09:16:13 +08:00
</style>