feat: 节点状态

This commit is contained in:
zhanghao 2026-04-14 17:22:08 +08:00
parent 88460d3bb1
commit 8c1820e1a6
11 changed files with 220 additions and 182 deletions

View File

@ -1,151 +1,158 @@
<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">
<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>
</div> <el-tag :type="props.state === 'SUCCESS' ? 'success' : 'danger'">
<div class="resultText" v-popover="popoverRef" @click="popoverVisible = !popoverVisible">{{ popoverVisible ? '隐藏结果' : '展示结果' }}</div> {{ props.runtimes }}ms
</div> </el-tag>
</div> </div>
<div class="resultText" v-popover="popoverRef" @click="popoverVisible = !popoverVisible">{{ popoverVisible ? '隐藏结果' : '展示结果' }}</div>
<el-popover </div>
ref="popoverRef" </div>
virtual-triggering
persistent <el-popover
placement="right-start" ref="popoverRef"
:visible="popoverVisible" virtual-triggering
:teleported="false" persistent
popper-class="popover__container" placement="right-start"
> :visible="popoverVisible"
<template #default> :teleported="false"
<div class="params__container input"> popper-class="popover__container"
<div class="paramsTitle">输入</div> >
<div> <template #default>
<slot name="input"></slot> <div class="params__container input">
</div> <div class="paramsTitle">输入</div>
</div> <div>
<div class="params__container output" v-if="slots.output"> <slot name="input"></slot>
<div class="paramsTitle">输出</div> </div>
<div> </div>
<slot name="output"></slot> <div class="params__container output" v-if="slots.output">
</div> <div class="paramsTitle">输出</div>
</div> <div>
</template> <slot name="output"></slot>
</el-popover> </div>
</div> </div>
</template> </template>
<script setup lang="js"> </el-popover>
import { ref, useSlots } from 'vue' </div>
import { SuccessFilled, CircleCloseFilled, Loading, VideoPause } from '@element-plus/icons-vue' </template>
<script setup lang="js">
const props = defineProps({ import { ref, useSlots } from 'vue'
state: { import { SuccessFilled, CircleCloseFilled, Loading, VideoPause } from '@element-plus/icons-vue'
type: String,
default: 'NORMAL' const props = defineProps({
} state: {
}) type: String,
default: 'NORMAL'
const slots = useSlots() },
runtimes: {
const getStateText = () => { type: Number,
const map = { default: 0
SUCCESS: '成功', }
RUNNING: '运行中', })
FAILED: '失败',
PAUSED: '已暂停', const slots = useSlots()
STOPPED: '已停止',
NORMAL: '' const getStateText = () => {
} const map = {
return map[props.state] SUCCESS: '成功',
} RUNNING: '运行中',
FAILED: '失败',
const popoverRef = ref(); PAUSED: '已暂停',
const popoverVisible = ref(false); STOPPED: '已停止',
NORMAL: ''
</script> }
<style lang="scss" scoped> return map[props.state]
.state__container { }
display: none;
margin-bottom: 8px; const popoverRef = ref();
const popoverVisible = ref(false);
.context {
display: flex; </script>
align-items: center; <style lang="scss" scoped>
justify-content: space-between; .state__container {
padding: 4px; display: none;
font-size: 12px; margin-bottom: 8px;
.state { .context {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
.text { padding: 4px;
margin: 0 8px; font-size: 12px;
}
} .state {
display: flex;
.resultText { align-items: center;
color: #1664ff;
cursor: pointer; .text {
} margin: 0 8px;
} }
} }
.nodeState-SUCCESS { .resultText {
display: block; color: #1664ff;
background-color: #eef9f1; cursor: pointer;
}
.el-icon { }
font-size: 16px; }
color: #309256;
} .nodeState-SUCCESS {
} display: block;
background-color: #eef9f1;
.nodeState-FAILED {
display: block; .el-icon {
background-color: #fdf5f5; font-size: 16px;
color: #309256;
.el-icon { }
font-size: 16px; }
color: #ee3f38;
} .nodeState-FAILED {
} display: block;
background-color: #fdf5f5;
.nodeState-STOPPED {
display: block; .el-icon {
background-color: #fdf5f5; font-size: 16px;
color: #ee3f38;
.el-icon { }
font-size: 16px; }
color: #ee3f38;
} .nodeState-STOPPED {
} display: block;
background-color: #fdf5f5;
.nodeState-PAUSED {
display: block; .el-icon {
background-color: #cbcbcb; font-size: 16px;
color: #ee3f38;
.el-icon { }
font-size: 16px; }
color: #666;
} .nodeState-PAUSED {
} display: block;
background-color: #cbcbcb;
.nodeState-RUNNING {
display: block; .el-icon {
background-color: #f4f7ff; font-size: 16px;
color: #666;
.el-icon { }
color: #68a2e4; }
font-size: 16px;
animation: rotateAnimation 2s linear infinite; .nodeState-RUNNING {
} display: block;
} background-color: #f4f7ff;
.el-icon {
color: #68a2e4;
font-size: 16px;
animation: rotateAnimation 2s linear infinite;
}
}
</style> </style>

View File

@ -889,7 +889,7 @@
@visible-change=" @visible-change="
(visible) => visibleChange(visible, index, property.quote) (visible) => visibleChange(visible, index, property.quote)
" "
@change="(value) => cascaderChange(value, index)" @change="(value) => cascaderChange(value, index, 'httpBody')"
/> />
</el-form-item> </el-form-item>
</el-form-item> </el-form-item>
@ -955,6 +955,8 @@ const confirm = async () => {
ElMessage.error('JSON中存在错误请修正后再保存'); ElMessage.error('JSON中存在错误请修正后再保存');
return; return;
} }
} else {
} }
const nodeParams = [] const nodeParams = []
nodeParams.push({ name: "config", type: "input", input: "", children: httpNodeData.value.config, disabled: true, required: true }) nodeParams.push({ name: "config", type: "input", input: "", children: httpNodeData.value.config, disabled: true, required: true })
@ -1065,6 +1067,9 @@ const addFormItem = (e, type) => {
} else if (type === 'nodeParams' || type === 'outputParams') { } else if (type === 'nodeParams' || type === 'outputParams') {
obj.input = '' obj.input = ''
} }
if (!formData[type]) {
formData[type] = [];
}
formData[type].push(obj); formData[type].push(obj);
}; };
@ -1086,10 +1091,15 @@ const handleTypeChange = (index) => {
}; };
const cascaderRefs = ref([]); const cascaderRefs = ref([]);
const cascaderChange = (value, index) => { const cascaderChange = (value, index, type = 'default') => {
const selectedOptions = cascaderRefs.value[index].getCheckedNodes(true); const selectedOptions = cascaderRefs.value[index].getCheckedNodes(true);
formData.nodeParams[index].quote = value; if (type === 'default') {
formData.nodeParams[index].quoteType = selectedOptions[0].data.type; formData.nodeParams[index].quote = value;
formData.nodeParams[index].quoteType = selectedOptions[0].data.type;
} else {
formData.nodeParams.find(item => item.name === 'body').children.find(child => child.name === 'formData').children[index].quote = value;
formData.nodeParams.find(item => item.name === 'body').children.find(child => child.name === 'formData').children[index].quoteType = selectedOptions[0].data.type;
}
}; };
const visibleChange = (value, index, quote) => { const visibleChange = (value, index, quote) => {
@ -1240,8 +1250,14 @@ const httpNodeData = ref({
const addHttpNodeFormItem = (e, type) => { const addHttpNodeFormItem = (e, type) => {
e.stopPropagation(); e.stopPropagation();
if (type === 'body') { if (type === 'body') {
if (!httpNodeData.value.body.formData) {
httpNodeData.value.body.formData = [];
}
httpNodeData.value.body.formData.push({ name: "", type: "input", input: ""}); httpNodeData.value.body.formData.push({ name: "", type: "input", input: ""});
} else { } else {
if (!httpNodeData.value[type]) {
httpNodeData.value[type] = [];
}
httpNodeData.value[type].push({ name: "", type: "input", input: ""}); httpNodeData.value[type].push({ name: "", type: "input", input: ""});
} }
}; };

View File

@ -307,9 +307,7 @@ export const collapseList = [
{ name: "body", type: "input", input: "", children: [ { name: "body", type: "input", input: "", children: [
{ name: 'bodyType', type: "input", input: "json", disabled: true }, { name: 'bodyType', type: "input", input: "json", disabled: true },
{ name: 'json', type: "input", input: '{}' }, { name: 'json', type: "input", input: '{}' },
{ name: 'formData', type: "input", input: "", children: [ { name: 'formData', type: "input", input: "", children: []}
{ name: "", type: "input", input: ""}
]}
], required: true }, ], required: true },
], ],
outputType: 'json', outputType: 'json',

View File

@ -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"> <NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<template #input> <template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" /> <JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template> </template>
@ -73,6 +73,7 @@ const setNodeName = (name) => {
} }
const nodeOperatingStatus = ref("NORMAL"); const nodeOperatingStatus = ref("NORMAL");
const runtimes = ref(0);
const inputJsonData = ref({}); const inputJsonData = ref({});
const outputJsonData = ref({}); const outputJsonData = ref({});
const errorInfoData = ref(''); const errorInfoData = ref('');
@ -82,6 +83,9 @@ onMounted(() => {
emitter.on("changeNodeState", (data) => { emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) { if (data.nodeId === props.model.id) {
nodeOperatingStatus.value = data.status; nodeOperatingStatus.value = data.status;
if (data.endTime && data.startTime) {
runtimes.value = data.endTime - data.startTime;
}
let inputData = {}; let inputData = {};
let output = {}; let output = {};
errorInfoData.value = data?.message || '' errorInfoData.value = data?.message || ''

View File

@ -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"> <NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<template #input> <template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" /> <JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template> </template>
@ -23,9 +23,7 @@
</template> </template>
<script setup> <script setup>
import { watch, reactive, toRaw, ref, onMounted, onUnmounted, nextTick } from "vue"; import { ref, onMounted, onUnmounted } from "vue";
import { useFlowStore } from "@/store/modules/flow";
import { initNodeZoom } from "@/utils/flow";
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";
import { emitter } from "@/utils/eventBus"; import { emitter } from "@/utils/eventBus";
@ -38,6 +36,7 @@ const emits = defineEmits(["contentChange"]);
const nodeOperatingStatus = ref("NORMAL"); const nodeOperatingStatus = ref("NORMAL");
const runtimes = ref(0);
const inputJsonData = ref({}); const inputJsonData = ref({});
const outputJsonData = ref({}); const outputJsonData = ref({});
@ -45,6 +44,9 @@ onMounted(() => {
emitter.on("changeNodeState", (data) => { emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) { if (data.nodeId === props.model.id) {
nodeOperatingStatus.value = data.status; nodeOperatingStatus.value = data.status;
if (data.endTime && data.startTime) {
runtimes.value = data.endTime - data.startTime;
}
let inputData = {}; let inputData = {};
let output = {}; let output = {};
try { try {

View File

@ -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"> <NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<template #input> <template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" /> <JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template> </template>
@ -69,6 +69,7 @@ const setNodeName = (name) => {
} }
const nodeOperatingStatus = ref("NORMAL"); const nodeOperatingStatus = ref("NORMAL");
const runtimes = ref(0);
const inputJsonData = ref({}); const inputJsonData = ref({});
const outputJsonData = ref({}); const outputJsonData = ref({});
const errorInfoData = ref(''); const errorInfoData = ref('');
@ -77,6 +78,9 @@ onMounted(() => {
emitter.on("changeNodeState", (data) => { emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) { if (data.nodeId === props.model.id) {
nodeOperatingStatus.value = data.status; nodeOperatingStatus.value = data.status;
if (data.endTime && data.startTime) {
runtimes.value = data.endTime - data.startTime;
}
let inputData = {}; let inputData = {};
let output = {}; let output = {};
errorInfoData.value = data?.message || '' errorInfoData.value = data?.message || ''

View File

@ -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"> <NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<template #input> <template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" /> <JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template> </template>
@ -32,9 +32,7 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, onUnmounted, nextTick } from "vue"; import { ref, reactive, onMounted, onUnmounted } from "vue";
import { getInput, initNodeZoom } from "@/utils/flow";
import { useFlowStore } from "@/store/modules/flow";
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";
@ -64,6 +62,7 @@ const setNodeName = (name) => {
} }
const nodeOperatingStatus = ref("NORMAL"); const nodeOperatingStatus = ref("NORMAL");
const runtimes = ref(0);
const inputJsonData = ref({}); const inputJsonData = ref({});
const outputJsonData = ref({}); const outputJsonData = ref({});
@ -71,6 +70,9 @@ onMounted(() => {
emitter.on("changeNodeState", (data) => { emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) { if (data.nodeId === props.model.id) {
nodeOperatingStatus.value = data.status; nodeOperatingStatus.value = data.status;
if (data.endTime && data.startTime) {
runtimes.value = data.endTime - data.startTime;
}
let inputData = {}; let inputData = {};
let output = {}; let output = {};
try { try {

View File

@ -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"> <NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<template #input> <template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" /> <JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template> </template>
@ -48,7 +48,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";
@ -72,6 +72,7 @@ const setNodeName = (name) => {
} }
const nodeOperatingStatus = ref("NORMAL"); const nodeOperatingStatus = ref("NORMAL");
const runtimes = ref(0);
const inputJsonData = ref({}); const inputJsonData = ref({});
const outputJsonData = ref({}); const outputJsonData = ref({});
const errorInfoData = ref(''); const errorInfoData = ref('');
@ -81,6 +82,9 @@ onMounted(() => {
emitter.on("changeNodeState", (data) => { emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) { if (data.nodeId === props.model.id) {
nodeOperatingStatus.value = data.status; nodeOperatingStatus.value = data.status;
if (data.endTime && data.startTime) {
runtimes.value = data.endTime - data.startTime;
}
let inputData = {}; let inputData = {};
let output = {}; let output = {};
errorInfoData.value = data?.message || '' errorInfoData.value = data?.message || ''

View File

@ -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"> <NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<template #input> <template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" /> <JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template> </template>
@ -33,9 +33,7 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, onUnmounted, nextTick } from "vue"; import { ref, reactive, onMounted, onUnmounted } from "vue";
import { getInput, initNodeZoom } from "@/utils/flow";
import { useFlowStore } from "@/store/modules/flow";
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";
@ -49,11 +47,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, {
@ -64,6 +57,7 @@ const setNodeName = (name) => {
} }
const nodeOperatingStatus = ref("NORMAL"); const nodeOperatingStatus = ref("NORMAL");
const runtimes = ref(0);
const inputJsonData = ref({}); const inputJsonData = ref({});
const outputJsonData = ref({}); const outputJsonData = ref({});
@ -71,6 +65,9 @@ onMounted(() => {
emitter.on("changeNodeState", (data) => { emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) { if (data.nodeId === props.model.id) {
nodeOperatingStatus.value = data.status; nodeOperatingStatus.value = data.status;
if (data.endTime && data.startTime) {
runtimes.value = data.endTime - data.startTime;
}
let inputData = {}; let inputData = {};
let output = {}; let output = {};
try { try {

View File

@ -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"> <NodeState :state="nodeOperatingStatus" :runtimes="runtimes">
<template #input> <template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" /> <JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template> </template>
@ -385,6 +385,7 @@ const visibleChange = (value) => {
}; };
const nodeOperatingStatus = ref("NORMAL"); const nodeOperatingStatus = ref("NORMAL");
const runtimes = ref(0);
const inputJsonData = ref({}); const inputJsonData = ref({});
const outputJsonData = ref({}); const outputJsonData = ref({});
const errorInfoData = ref(''); const errorInfoData = ref('');
@ -414,6 +415,9 @@ onMounted(() => {
emitter.on("changeNodeState", (data) => { emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) { if (data.nodeId === props.model.id) {
nodeOperatingStatus.value = data.status; nodeOperatingStatus.value = data.status;
if (data.endTime && data.startTime) {
runtimes.value = data.endTime - data.startTime;
}
let inputData = {}; let inputData = {};
let output = {}; let output = {};
errorInfoData.value = data?.message || '' errorInfoData.value = data?.message || ''

View File

@ -47,11 +47,11 @@ export default defineConfig(({mode, command}) => {
proxy: { proxy: {
// https://cn.vitejs.dev/config/#server-proxy // https://cn.vitejs.dev/config/#server-proxy
'/dev-api': { '/dev-api': {
//杨 http://192.168.0.10:13080 //服务器 http://192.168.0.100:13080
//赵 http://10.148.108.58: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.100:13080', target: command === 'build' ? VITE_API_URL : 'http://192.168.0.5:13080',
changeOrigin: true, changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '') rewrite: (p) => p.replace(/^\/dev-api/, '')
} }