CMVR-IOT-UI/src/views/flow/components/NodeState.vue

186 lines
4.3 KiB
Vue
Raw Normal View History

2026-07-28 16:26:07 +08:00
<template>
<div v-if="showState !== 'NORMAL'" class="node-state" :class="`node-state--${showState.toLowerCase()}`">
<div class="node-state__summary">
<el-icon class="node-state__icon">
<SuccessFilled v-if="props.state === 'SUCCESS'" />
<CircleCloseFilled v-else-if="['FAILED', 'STOPPED'].includes(props.state)" />
<VideoPause v-else-if="props.state === 'PAUSED'" />
<Loading v-else />
</el-icon>
<span class="node-state__text">{{ stateText }}</span>
<span class="node-state__runtime">{{ props.runtimes }} ms</span>
2026-07-28 16:26:07 +08:00
</div>
<el-popover
ref="popoverRef"
trigger="click"
2026-07-28 16:26:07 +08:00
placement="right-start"
:width="480"
:teleported="true"
popper-class="flow-result-popover"
@show="popoverVisible = true"
@hide="popoverVisible = false"
2026-07-28 16:26:07 +08:00
>
<template #reference>
<button type="button" class="node-state__result nodrag nopan" @mousedown.stop @click.stop>
<el-icon><View /></el-icon>
<span>{{ popoverVisible ? "收起结果" : "查看结果" }}</span>
</button>
2026-07-28 16:26:07 +08:00
</template>
<div class="result-section">
<div class="result-section__title">输入</div>
<div class="result-section__content"><slot name="input" /></div>
</div>
<div v-if="slots.output" class="result-section result-section--output">
<div class="result-section__title">输出</div>
<div class="result-section__content"><slot name="output" /></div>
</div>
2026-07-28 16:26:07 +08:00
</el-popover>
</div>
</template>
<script setup>
import { computed, ref, useSlots, watch } from "vue";
import { CircleCloseFilled, Loading, SuccessFilled, VideoPause, View } from "@element-plus/icons-vue";
2026-07-28 16:26:07 +08:00
const props = defineProps({
state: { type: String, default: "NORMAL" },
runtimes: { type: Number, default: 0 },
});
2026-07-28 16:26:07 +08:00
const slots = useSlots();
2026-07-28 16:26:07 +08:00
const popoverRef = ref();
const popoverVisible = ref(false);
const showState = ref("NORMAL");
const stateText = computed(() => ({
SUCCESS: "执行成功",
RUNNING: "执行中",
FAILED: "执行失败",
PAUSED: "已暂停",
STOPPED: "已停止",
NORMAL: "",
}[props.state] || props.state));
watch(() => props.state, (value) => {
showState.value = value;
2026-07-29 11:43:07 +08:00
}, { immediate: true });
2026-07-28 16:26:07 +08:00
const closePopover = () => {
showState.value = "NORMAL";
2026-07-28 16:26:07 +08:00
popoverVisible.value = false;
popoverRef.value?.hide?.();
2026-07-28 16:26:07 +08:00
};
defineExpose({ closePopover });
2026-07-28 16:26:07 +08:00
</script>
2026-07-28 16:26:07 +08:00
<style lang="scss" scoped>
.node-state {
display: flex;
min-height: 34px;
align-items: center;
justify-content: space-between;
gap: 8px;
margin: -4px -4px 10px;
padding: 5px 7px 5px 9px;
border: 1px solid #dbe3ee;
border-radius: 6px;
background: #f7f9fc;
color: #526071;
font-size: 12px;
}
2026-07-28 16:26:07 +08:00
.node-state__summary {
display: flex;
min-width: 0;
align-items: center;
gap: 6px;
}
2026-07-28 16:26:07 +08:00
.node-state__icon { font-size: 16px; }
.node-state__text { font-weight: 600; white-space: nowrap; }
2026-07-28 16:26:07 +08:00
.node-state__runtime {
padding-left: 6px;
border-left: 1px solid currentColor;
opacity: 0.72;
white-space: nowrap;
2026-07-28 16:26:07 +08:00
}
.node-state__result {
display: flex;
height: 26px;
flex: 0 0 auto;
align-items: center;
gap: 4px;
padding: 0 7px;
border: 0;
border-radius: 4px;
background: rgba(255, 255, 255, 0.72);
color: inherit;
cursor: pointer;
font-size: 11px;
font-weight: 600;
}
2026-07-28 16:26:07 +08:00
.node-state--success {
border-color: #c8ead5;
background: #effaf3;
color: #1e7d49;
2026-07-28 16:26:07 +08:00
}
.node-state--failed,
.node-state--stopped {
border-color: #f1cecb;
background: #fff3f2;
color: #c33b32;
}
2026-07-28 16:26:07 +08:00
.node-state--paused {
border-color: #d9dee7;
background: #f3f5f8;
color: #657084;
2026-07-28 16:26:07 +08:00
}
.node-state--running {
border-color: #cbdcf8;
background: #f0f6ff;
color: #2f6fca;
2026-07-28 16:26:07 +08:00
.node-state__icon { animation: rotate-animation 1.2s linear infinite; }
2026-07-28 16:26:07 +08:00
}
@keyframes rotate-animation {
to { transform: rotate(360deg); }
}
</style>
<style lang="scss">
.flow-result-popover {
max-width: min(480px, calc(100vw - 32px));
padding: 0 !important;
overflow: hidden;
2026-07-28 16:26:07 +08:00
.result-section {
padding: 14px 16px;
2026-07-28 16:26:07 +08:00
}
.result-section--output { border-top: 1px solid #e8ebf0; }
2026-07-28 16:26:07 +08:00
.result-section__title {
margin-bottom: 8px;
color: #344054;
font-size: 13px;
font-weight: 650;
}
.result-section__content {
max-height: 280px;
overflow: auto;
color: #475467;
font-size: 12px;
2026-07-28 16:26:07 +08:00
}
}
</style>