cmvr-iot-common/src/views/flow/nodes/common/serviceNode.vue

228 lines
6.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
* 文件说明:通用服务节点并响应运行状态。
* 作用范围:仅服务于流程设计器模块。
-->
<template>
<div class="node__container" :class="props.model.id">
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
<template #actions>
<el-button
v-if="audioCorrectionAvailable"
class="node-audio-correction-button nodrag nopan"
type="primary"
link
size="small"
@click="audioCorrectionVisible = true"
>
{{ outputJsonData.manualCorrected ? '重新判断' : '人工判断' }}
</el-button>
</template>
<template #input>
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
</template>
<template #output>
<div v-if="nodeOperatingStatus != 'FAILED'">
<JsonViewer
v-if="!['img', 'video', 'audio'].includes(props.properties.outputType)"
:value="outputJsonData"
copyable
boxed
sort
theme="light"
/>
<el-image
v-if="props.properties.outputType === 'img'"
v-for="item in outputJsonData.imageUrl"
style="width: 60px; height: 60px"
:src="item"
:preview-src-list="outputJsonData.imageUrl"
:preview-teleported="true"
show-progress
fit="fill"
/>
<IPlayer
v-if="props.properties.outputType === 'video'"
v-for="item in outputJsonData.videoUrl"
:videoUrl="item"
/>
<audio
v-for="item in audioUrls"
:key="item"
class="node-audio-player"
:src="item"
controls
preload="metadata"
/>
</div>
<div v-else>{{ errorInfoData }}</div>
</template>
</NodeState>
<AudioCorrectionDialog
v-model="audioCorrectionVisible"
:node-inst-id="nodeInstId"
:value="outputJsonData"
@saved="applyAudioCorrection"
/>
<NodeTitle
:icon="props.properties.icon"
:nodeId="props.model.id"
:nodeProperties="props.properties"
:nodeType="props.properties.nodeType || 'NONE'"
:nodeName="props.properties.name"
:nodeDesc="props.properties.desc"
@setNodeName="setNodeName"
/>
<!-- @zoom="zoom" -->
</div>
</template>
<script setup>
import { computed, ref, onMounted } from "vue";
import NodeTitle from "../../components/NodeTitle.vue";
import NodeState from "../../components/NodeState.vue";
import "vue3-json-viewer/dist/index.css";
import { useFlowEvents } from "../../composables/useFlowEvents";
import IPlayer from "@/components/IPlayer/index.vue";
import AudioCorrectionDialog from "../../components/AudioCorrectionDialog.vue";
const { onFlowEvent } = useFlowEvents();
const props = defineProps({
model: Object,
properties: Object,
});
const emits = defineEmits(["contentChange"]);
/**
* 设置 setNodeName 对应的数据或交互;作用范围仅限当前组件或模块。
* @param {*} name 名称或事件名称
*/
const setNodeName = (name) => {
const properties = lf.getProperties(props.model.id);
lf.setProperties(props.model.id, {
...properties,
name
});
emits("contentChange");
}
const nodeOperatingStatus = ref("NORMAL");
const runtimes = ref(0);
const inputJsonData = ref({});
const outputJsonData = ref({});
const errorInfoData = ref('');
const executionLog = ref(null);
const audioCorrectionVisible = ref(false);
const nodeInstIdOf = (log) => log?.nodeInstId
|| log?.nodeInstanceId
|| log?.executionNodeInstId
|| log?.id
|| '';
const nodeInstId = computed(() => nodeInstIdOf(executionLog.value));
const audioCorrectionAvailable = computed(() => (
String(props.properties?.action || '').toUpperCase() === 'AUDIO_EVENT_CLASSIFY'
&& nodeOperatingStatus.value !== 'NORMAL'
&& Boolean(nodeInstId.value)
));
const applyAudioCorrection = (correction) => {
outputJsonData.value = { ...outputJsonData.value, ...correction };
if (!executionLog.value) return;
const currentOutput = parseRuntimeValue(executionLog.value.paramsOut);
executionLog.value.paramsOut = JSON.stringify({ ...currentOutput, ...correction });
};
const parseRuntimeValue = (value) => {
if (!value) return {};
if (typeof value === 'object' && !Array.isArray(value)) return value;
try {
const parsed = JSON.parse(value);
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
} catch {
return {};
}
};
const audioUrls = computed(() => {
const value = outputJsonData.value?.audioUrl;
if (Array.isArray(value)) return value.filter(Boolean);
if (typeof value === "string" && value) return [value];
return [];
});
const nodeStateRef = ref(null);
/**
* 关闭 closePopover 对应的数据或交互;作用范围仅限当前组件或模块。
*/
const closePopover = () => {
if (nodeStateRef.value) {
nodeStateRef.value.closePopover();
}
}
onMounted(() => {
onFlowEvent("changeNodeState", (data) => {
if (data.nodeId === props.model.id) {
executionLog.value = data;
nodeOperatingStatus.value = data.status;
if (data.endTime && data.startTime) {
runtimes.value = data.endTime - data.startTime;
}
let inputData = {};
let output = {};
errorInfoData.value = data?.message || ''
try {
inputData = JSON.parse(data.paramsIn) || {};
output = JSON.parse(data.paramsOut) || {};
} catch (error) {
inputData = data.paramsIn || {};
output = data.paramsOut || {};
}
inputJsonData.value = inputData;
outputJsonData.value = output;
emits("contentChange");
}
});
onFlowEvent("contentChange", (data) => {
if (data.id === props.model.id) {
nodeOperatingStatus.value = "NORMAL";
inputJsonData.value = {};
outputJsonData.value = {};
executionLog.value = null;
emits("contentChange");
}
});
onFlowEvent("setProperties", (data) => {
if (data.id === props.model.id) {
emits("contentChange");
}
});
});
defineExpose({ closePopover })
</script>
<style lang="scss" scoped>
.node__container {
width: 100%;
height: auto;
}
.node-audio-player {
width: min(320px, 100%);
height: 36px;
}
.node-audio-correction-button {
margin-left: 4px;
}
</style>