diff --git a/src/utils/actionQueue.js b/src/utils/actionQueue.js new file mode 100644 index 0000000..5911bfa --- /dev/null +++ b/src/utils/actionQueue.js @@ -0,0 +1,32 @@ +const deepClone = (value) => JSON.parse(JSON.stringify(value)) + +export const getActionQueueStepName = (step, index) => { + const name = String(step?.stepName || '').trim() + return name || `步骤 ${index + 1}` +} + +export const moveActionQueueStep = (steps, index, offset) => { + const targetIndex = index + offset + if (!Array.isArray(steps) || index < 0 || targetIndex < 0 || targetIndex >= steps.length) return false + const [step] = steps.splice(index, 1) + steps.splice(targetIndex, 0, step) + return true +} + +export const copyActionQueueStep = (step, overrides = {}) => { + const copied = deepClone(step) + const originalName = String(step?.stepName || '').trim() + return { + ...copied, + ...overrides, + stepName: `${originalName || '未命名步骤'} - 副本`, + } +} + +export const toActionQueueExecutionSteps = (steps) => (Array.isArray(steps) ? steps : []).map((step, index) => ({ + stepId: `step_${index + 1}`, + type: step.type, + ...(step.deviceId ? { deviceId: step.deviceId } : {}), + timeoutMs: Number(step.timeoutMs || 0), + params: deepClone(step.params || {}), +})) diff --git a/src/views/flow/components/ActionQueueSingleRun.vue b/src/views/flow/components/ActionQueueSingleRun.vue index f2f9932..3a6d054 100644 --- a/src/views/flow/components/ActionQueueSingleRun.vue +++ b/src/views/flow/components/ActionQueueSingleRun.vue @@ -11,8 +11,24 @@ -
-
步骤 {{ index + 1 }} · {{ actionName(step.type) }}
+
+
+
{{ getActionQueueStepName(step, index) }} · {{ actionName(step.type) }}
+
+ + + + + + + + + +
+
+ + + import { ref, watch } from 'vue' import { ElMessage } from 'element-plus' +import { ArrowDown, ArrowUp, CopyDocument } from '@element-plus/icons-vue' import { getRobotDevicesByRobotId } from '@/api/inspection/robot' import { DEVICE_KIND } from '@/utils/robotDevice' +import { copyActionQueueStep, getActionQueueStepName, moveActionQueueStep, toActionQueueExecutionSteps } from '@/utils/actionQueue' /** * 组件输入配置,仅在当前单次动作队列运行表单内使用。 @@ -58,6 +76,8 @@ const steps = ref([]) const devices = ref([]) /** 设备接口请求状态,仅控制当前组件设备选择框的加载效果。 */ const deviceLoading = ref(false) +let runtimeKeySequence = 0 +const createRuntimeKey = () => `queue_runtime_${Date.now()}_${runtimeKeySequence += 1}` /** * 获取动作类型的展示名称。 @@ -96,11 +116,26 @@ const initData = async () => { const values = Object.fromEntries(props.nodeParams.map((item) => [item.name, item.input])) actionId.value = values.actionId || '' totalTimeoutMs.value = Number(values.totalTimeoutMs || 60000) - steps.value = JSON.parse(JSON.stringify(Array.isArray(values.steps) ? values.steps : [])) + steps.value = JSON.parse(JSON.stringify(Array.isArray(values.steps) ? values.steps : [])).map((step, index) => ({ + ...step, + stepName: getActionQueueStepName(step, index), + runtimeKey: createRuntimeKey(), + })) robotId.value = props.initialRobotId || '' if (robotId.value) await loadDevices(robotId.value) } +const moveStep = (index, offset) => { + moveActionQueueStep(steps.value, index, offset) +} + +const copyStep = (index) => { + const source = steps.value[index] + if (!source) return + const copied = copyActionQueueStep(source, { runtimeKey: createRuntimeKey() }) + steps.value.splice(index + 1, 0, copied) +} + /** * 加载指定机器人的设备并重建各步骤设备选择,影响当前组件的 devices 和 steps。 * 切换机器人后旧 deviceId 已不再可靠,因此先清空设备及所有步骤选择;无机器人时直接结束。 @@ -144,7 +179,12 @@ const validate = () => { } /** 汇总当前表单数据为运行接口载荷;返回范围包含机器人、队列配置和全部步骤。 */ -const getPayload = () => ({ robotId: robotId.value, actionId: actionId.value, totalTimeoutMs: totalTimeoutMs.value, steps: steps.value }) +const getPayload = () => ({ + robotId: robotId.value, + actionId: actionId.value, + totalTimeoutMs: totalTimeoutMs.value, + steps: toActionQueueExecutionSteps(steps.value), +}) /** 返回当前选中的机器人 ID,供父组件处理兼容字段或运行上下文。 */ const getRobotId = () => robotId.value @@ -164,11 +204,33 @@ defineExpose({ validate, getPayload, getRobotId }) border-radius: 8px; } -.single-queue-step__title { +.single-queue-step__header { + display: flex; + min-width: 0; margin-bottom: 10px; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.single-queue-step__title { + overflow: hidden; color: #1f2937; font-size: 13px; font-weight: 650; + text-overflow: ellipsis; + white-space: nowrap; +} + +.single-queue-step__actions { + display: flex; + flex: 0 0 auto; + align-items: center; + gap: 6px; +} + +.single-queue-step__actions :deep(.el-button + .el-button) { + margin-left: 0; } .single-queue-step__params { diff --git a/src/views/flow/components/params/ActionQueueParams.vue b/src/views/flow/components/params/ActionQueueParams.vue index dcca245..8dc5c2e 100644 --- a/src/views/flow/components/params/ActionQueueParams.vue +++ b/src/views/flow/components/params/ActionQueueParams.vue @@ -28,18 +28,31 @@
+ + + @@ -118,7 +131,7 @@