feat(flow): 优化流程节点配置和单节点执行功能
- 移除未使用的 start.svg 和 end.svg 图标导入 - 删除未使用的 booleanOptions 配置函数 - 为 SPEAKER_PLAYAUDIO 节点添加 nodeType 和 audioPath 参数配置 - 重构单节点参数表单渲染逻辑,使用 computed 属性优化字段映射 - 添加单节点执行状态加载指示器和防重复提交功能 - 实现单节点参数验证和数据构建功能 - 修复流程图边连接逻辑,优化相同边删除操作 - 简化旧流程数据获取逻辑,移除不必要的变量赋值 - 更新节点执行权限判断逻辑,支持更多节点类型过滤 - 优化单节点执行时的动作名称处理和节点类型识别逻辑 - 调整单节点表单样式,移除垂直居中对齐属性
This commit is contained in:
parent
b332a5461e
commit
84e1354c88
@ -87,7 +87,22 @@ const isEditing = ref(false);
|
|||||||
const localName = ref("");
|
const localName = ref("");
|
||||||
const nameInputRef = ref();
|
const nameInputRef = ref();
|
||||||
const isSelectArea = computed(() => props.nodeType === "selectArea");
|
const isSelectArea = computed(() => props.nodeType === "selectArea");
|
||||||
const canExecute = computed(() => Boolean(props.nodeType) && !["NONE", "selectArea"].includes(props.nodeType));
|
const flowContextActions = new Set([
|
||||||
|
"NONE",
|
||||||
|
"START",
|
||||||
|
"END",
|
||||||
|
"SUB_START",
|
||||||
|
"SUB_END",
|
||||||
|
"START_LOOP",
|
||||||
|
"STOP_LOOP",
|
||||||
|
"BRANCH",
|
||||||
|
"GET_CURRENT_OBJECT",
|
||||||
|
]);
|
||||||
|
const canExecute = computed(() => {
|
||||||
|
if (isSelectArea.value) return false;
|
||||||
|
const action = String(props.nodeProperties?.action || "").toUpperCase();
|
||||||
|
return Boolean(action) && !flowContextActions.has(action);
|
||||||
|
});
|
||||||
const typeLabel = computed(() => {
|
const typeLabel = computed(() => {
|
||||||
if (!props.nodeType || ["NONE", "selectArea"].includes(props.nodeType)) return "";
|
if (!props.nodeType || ["NONE", "selectArea"].includes(props.nodeType)) return "";
|
||||||
return { EDGE: "设备", LLM: "模型", AE: "评估" }[props.nodeType] || props.nodeType;
|
return { EDGE: "设备", LLM: "模型", AE: "评估" }[props.nodeType] || props.nodeType;
|
||||||
|
|||||||
@ -3,8 +3,6 @@ import videoSvg from './icon/video.svg'
|
|||||||
import loopSvg from './icon/loop.svg'
|
import loopSvg from './icon/loop.svg'
|
||||||
import stopLoopSvg from './icon/stopLoop.svg'
|
import stopLoopSvg from './icon/stopLoop.svg'
|
||||||
import switchSvg from './icon/switch.svg'
|
import switchSvg from './icon/switch.svg'
|
||||||
import startSvg from './icon/start.svg'
|
|
||||||
import endSvg from './icon/end.svg'
|
|
||||||
import microphoneSvg from './icon/microphone.svg'
|
import microphoneSvg from './icon/microphone.svg'
|
||||||
import sleepSvg from './icon/sleep.svg'
|
import sleepSvg from './icon/sleep.svg'
|
||||||
import planSvg from './icon/plan.svg'
|
import planSvg from './icon/plan.svg'
|
||||||
@ -14,11 +12,6 @@ import audioSvg from './icon/audio.svg'
|
|||||||
import touchSvg from './icon/touch.svg'
|
import touchSvg from './icon/touch.svg'
|
||||||
import expressionSvg from './icon/expression.svg'
|
import expressionSvg from './icon/expression.svg'
|
||||||
|
|
||||||
const booleanOptions = () => [
|
|
||||||
{ value: false, label: '否' },
|
|
||||||
{ value: true, label: '是' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const httpMethodOptions = () => {
|
const httpMethodOptions = () => {
|
||||||
return [{
|
return [{
|
||||||
value: 'POST',
|
value: 'POST',
|
||||||
@ -464,8 +457,10 @@ function handler(params) {
|
|||||||
type: "serviceNode",
|
type: "serviceNode",
|
||||||
desc: "用于播放音频的节点",
|
desc: "用于播放音频的节点",
|
||||||
action: 'SPEAKER_PLAYAUDIO',
|
action: 'SPEAKER_PLAYAUDIO',
|
||||||
|
nodeType: 'EDGE',
|
||||||
nodeParams: [
|
nodeParams: [
|
||||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true },
|
||||||
|
{ name: "audioPath", type: "input", input: "", disabled: true }
|
||||||
],
|
],
|
||||||
outputType: 'json'
|
outputType: 'json'
|
||||||
},
|
},
|
||||||
|
|||||||
@ -180,17 +180,15 @@
|
|||||||
label-width="auto"
|
label-width="auto"
|
||||||
:disabled="flowStore.disableForm"
|
:disabled="flowStore.disableForm"
|
||||||
>
|
>
|
||||||
<div v-for="(property, index) in formData.nodeParams" :key="index">
|
<div v-for="(field, index) in singleNodeFields" :key="field.key">
|
||||||
<el-row class="single-node-param-row">
|
<el-row class="single-node-param-row">
|
||||||
<el-form-item
|
<el-form-item
|
||||||
class="single-node-param-name"
|
class="single-node-param-name"
|
||||||
:label="index === 0 ? '参数名' : ''"
|
:label="index === 0 ? '参数名' : ''"
|
||||||
:prop="`nodeParams.${index}.name`"
|
|
||||||
:rules="[{ required: true, message: '请输入参数名', trigger: 'blur' }]"
|
|
||||||
>
|
>
|
||||||
<el-input
|
<el-input
|
||||||
:disabled="property?.disabled || false"
|
:disabled="field.param?.disabled || false"
|
||||||
v-model="property.name"
|
v-model="field.param.name"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
clearable
|
clearable
|
||||||
/>
|
/>
|
||||||
@ -198,27 +196,25 @@
|
|||||||
<el-form-item
|
<el-form-item
|
||||||
class="single-node-param-value"
|
class="single-node-param-value"
|
||||||
:label="index === 0 ? '参数值' : ''"
|
:label="index === 0 ? '参数值' : ''"
|
||||||
:prop="`nodeParams.${index}.input`"
|
|
||||||
:rules="[{ required: true, message: '请输入参数值', trigger: 'blur' }]"
|
|
||||||
>
|
>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-if="property.componentType === 'number'"
|
v-if="field.param.componentType === 'number'"
|
||||||
v-model="property.input"
|
v-model="field.param.input"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="property.max || Infinity"
|
:max="field.param.max || Infinity"
|
||||||
:step="property.step || 1"
|
:step="field.param.step || 1"
|
||||||
:step-strictly="true"
|
:step-strictly="true"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
clearable
|
clearable
|
||||||
/>
|
/>
|
||||||
<el-select
|
<el-select
|
||||||
v-model="property.input"
|
v-model="field.param.input"
|
||||||
v-else-if="property.componentType === 'select'"
|
v-else-if="field.param.componentType === 'select'"
|
||||||
:disabled="property.deviceLocked"
|
:disabled="field.param.deviceLocked"
|
||||||
@change="property.name === 'robotId' && handleSingleNodeRobotChange(property.input)"
|
@change="field.param.name === 'robotId' && handleSingleNodeRobotChange(field.param.input)"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in property.selectOptions"
|
v-for="item in field.param.selectOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
:label="item.label"
|
:label="item.label"
|
||||||
:value="item.value"
|
:value="item.value"
|
||||||
@ -226,14 +222,14 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
<el-input
|
<el-input
|
||||||
v-else
|
v-else
|
||||||
v-model="property.input"
|
v-model="field.param.input"
|
||||||
autosize
|
autosize
|
||||||
type="textarea"
|
type="textarea"
|
||||||
resize="none"
|
resize="none"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
clearable
|
clearable
|
||||||
/>
|
/>
|
||||||
<div v-if="property.name === 'deviceId' && property.deviceLocked" class="device-lock-hint">
|
<div v-if="field.param.name === 'deviceId' && field.param.deviceLocked" class="device-lock-hint">
|
||||||
已自动匹配当前机器人设备
|
已自动匹配当前机器人设备
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -243,7 +239,7 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button @click="visible = false">取消</el-button>
|
<el-button @click="visible = false">取消</el-button>
|
||||||
<el-button type="primary" @click="execute"> 确定 </el-button>
|
<el-button type="primary" :loading="singleNodeExecuting" @click="execute"> 确定 </el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@ -628,7 +624,9 @@ const validEdge = (data) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sourceName === "right" && targetName === "left") {
|
if (sourceName === "right" && targetName === "left") {
|
||||||
if (hasSameEdge(data, data.id)) lf.deleteEdge(data.id);
|
if (hasSameEdge(data, data.id)) {
|
||||||
|
lf.deleteEdge(data.id);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
} else if (
|
} else if (
|
||||||
(sourceName === "right" && targetName === "right") ||
|
(sourceName === "right" && targetName === "right") ||
|
||||||
@ -654,8 +652,7 @@ const validEdge = (data) => {
|
|||||||
|
|
||||||
const instId = ref(null);
|
const instId = ref(null);
|
||||||
const changeState = (value) => {
|
const changeState = (value) => {
|
||||||
const flowData = JSON.parse(JSON.stringify(lf.getGraphData()));
|
oldFlowData = JSON.parse(JSON.stringify(lf.getGraphData()));
|
||||||
oldFlowData = flowData;
|
|
||||||
isOpen.value = false;
|
isOpen.value = false;
|
||||||
flowStore.updateDisableForm(true);
|
flowStore.updateDisableForm(true);
|
||||||
flowState.value = value.type;
|
flowState.value = value.type;
|
||||||
@ -763,7 +760,7 @@ const registerBeforeUnload = () => {
|
|||||||
window.addEventListener("beforeunload", handleBeforeUnload);
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBeforeUnload = (event) => {
|
const handleBeforeUnload = (_event) => {
|
||||||
if (flowState.value === "pause" && !flowInfoData.value.isLog) {
|
if (flowState.value === "pause" && !flowInfoData.value.isLog) {
|
||||||
flowStopFn();
|
flowStopFn();
|
||||||
}
|
}
|
||||||
@ -879,8 +876,7 @@ const defaultProps = {
|
|||||||
};
|
};
|
||||||
const showNodeRelationship = () => {
|
const showNodeRelationship = () => {
|
||||||
const { nodes } = lf.getGraphData();
|
const { nodes } = lf.getGraphData();
|
||||||
const outputData = convertToTree(nodes);
|
treeData.value = convertToTree(nodes);
|
||||||
treeData.value = outputData;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTreeNodeClick = (node) => {
|
const handleTreeNodeClick = (node) => {
|
||||||
@ -902,8 +898,47 @@ const formData = reactive({
|
|||||||
const rules = reactive({});
|
const rules = reactive({});
|
||||||
const executeForm = ref();
|
const executeForm = ref();
|
||||||
const nodeAction = ref("");
|
const nodeAction = ref("");
|
||||||
|
const singleNodeExecuting = ref(false);
|
||||||
const stationLoading = ref(false);
|
const stationLoading = ref(false);
|
||||||
const singleNodeRobotOptions = ref([]);
|
const singleNodeRobotOptions = ref([]);
|
||||||
|
const flattenSingleNodeParams = (params, parentPath = [], fields = []) => {
|
||||||
|
(params || []).forEach((param, index) => {
|
||||||
|
const name = String(param?.name || "").trim();
|
||||||
|
const path = [...parentPath, name || `param-${index}`];
|
||||||
|
if (Array.isArray(param?.children)) {
|
||||||
|
flattenSingleNodeParams(param.children, path, fields);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!name && (param?.input === "" || param?.input === null || param?.input === undefined)) return;
|
||||||
|
fields.push({ key: `${path.join(".")}-${index}`, param });
|
||||||
|
});
|
||||||
|
return fields;
|
||||||
|
};
|
||||||
|
const singleNodeFields = computed(() => flattenSingleNodeParams(formData.nodeParams));
|
||||||
|
const buildSingleNodePayload = (params) => {
|
||||||
|
const payload = {};
|
||||||
|
(params || []).forEach((param) => {
|
||||||
|
const name = String(param?.name || "").trim();
|
||||||
|
if (!name) return;
|
||||||
|
payload[name] = Array.isArray(param.children)
|
||||||
|
? buildSingleNodePayload(param.children)
|
||||||
|
: param.input;
|
||||||
|
});
|
||||||
|
return payload;
|
||||||
|
};
|
||||||
|
const validateSingleNodeParams = () => {
|
||||||
|
for (const { param } of singleNodeFields.value) {
|
||||||
|
if (!String(param?.name || "").trim()) {
|
||||||
|
ElMessage.warning("请输入参数名");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (param.input === "" || param.input === null || param.input === undefined) {
|
||||||
|
ElMessage.warning(`请输入参数 ${param.name} 的值`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
const singleNodeRobotId = computed(() =>
|
const singleNodeRobotId = computed(() =>
|
||||||
formData.nodeParams.find((item) => item.name === "robotId")?.input
|
formData.nodeParams.find((item) => item.name === "robotId")?.input
|
||||||
);
|
);
|
||||||
@ -1002,11 +1037,14 @@ const loadSingleNodeRobots = async () => {
|
|||||||
const singleNodeExecution = async (e) => {
|
const singleNodeExecution = async (e) => {
|
||||||
const { name, nodeType, nodeParams, action } = e.detail;
|
const { name, nodeType, nodeParams, action } = e.detail;
|
||||||
const executionParams = JSON.parse(JSON.stringify(nodeParams || []));
|
const executionParams = JSON.parse(JSON.stringify(nodeParams || []));
|
||||||
|
const normalizedAction = String(action || "").toUpperCase();
|
||||||
|
const effectiveNodeType = nodeType || (getDeviceKindForAction(normalizedAction) ? "EDGE" : "");
|
||||||
nodeName.value = name;
|
nodeName.value = name;
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
nodeAction.value = action;
|
nodeAction.value = normalizedAction;
|
||||||
|
singleNodeExecuting.value = false;
|
||||||
stationLoading.value = false;
|
stationLoading.value = false;
|
||||||
if (nodeType === "EDGE") {
|
if (effectiveNodeType === "EDGE") {
|
||||||
await loadSingleNodeRobots();
|
await loadSingleNodeRobots();
|
||||||
formData.nodeParams = [
|
formData.nodeParams = [
|
||||||
{
|
{
|
||||||
@ -1022,7 +1060,7 @@ const singleNodeExecution = async (e) => {
|
|||||||
} else {
|
} else {
|
||||||
formData.nodeParams = executionParams;
|
formData.nodeParams = executionParams;
|
||||||
}
|
}
|
||||||
if (action === "AGV_MOVE_TO_STATION") {
|
if (nodeAction.value === "AGV_MOVE_TO_STATION") {
|
||||||
const stationParam = formData.nodeParams.find((item) => item.name === "stationId");
|
const stationParam = formData.nodeParams.find((item) => item.name === "stationId");
|
||||||
if (stationParam) {
|
if (stationParam) {
|
||||||
stationParam.input = "";
|
stationParam.input = "";
|
||||||
@ -1042,21 +1080,22 @@ const execute = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const result = await executeForm.value.validate();
|
if (!validateSingleNodeParams()) return;
|
||||||
if (result) {
|
singleNodeExecuting.value = true;
|
||||||
const obj = {};
|
try {
|
||||||
formData.nodeParams.forEach((item) => {
|
|
||||||
obj[item.name] = item.input;
|
|
||||||
});
|
|
||||||
const res = await flowAction({
|
const res = await flowAction({
|
||||||
action: nodeAction.value,
|
action: nodeAction.value,
|
||||||
payload: obj,
|
payload: buildSingleNodePayload(formData.nodeParams),
|
||||||
});
|
});
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
ElMessage.success("执行成功");
|
ElMessage.success("执行成功");
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.msg);
|
ElMessage.error(res.msg);
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("单节点执行失败:", error);
|
||||||
|
} finally {
|
||||||
|
singleNodeExecuting.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1347,7 +1386,6 @@ onUnmounted(() => {
|
|||||||
.single-node-param-row {
|
.single-node-param-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user