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);
|
2026-03-18 10:41:23 +08:00
|
|
|
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>
|