From 0780d298c6e4b5399c4f10613b5791c0dd3caa42 Mon Sep 17 00:00:00 2001
From: zhanghao <774378400@qq.com>
Date: Thu, 13 Aug 2026 10:23:55 +0800
Subject: [PATCH] =?UTF-8?q?doc:=20=E8=B0=83=E4=BB=B7=E6=B3=A8=E9=87=8A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/utils/robotDevice.js | 16 ++++
.../flow/components/ActionQueueSingleRun.vue | 87 +++++++++++++++++--
2 files changed, 95 insertions(+), 8 deletions(-)
diff --git a/src/utils/robotDevice.js b/src/utils/robotDevice.js
index 9fdac75..96c619b 100644
--- a/src/utils/robotDevice.js
+++ b/src/utils/robotDevice.js
@@ -1,3 +1,7 @@
+/**
+ * 平台设备类型枚举,供流程资源匹配、机器人设备筛选和界面展示共同使用。
+ * Object.freeze 防止运行期间被改写,数值与后端 deviceKind 协议保持一致。
+ */
export const DEVICE_KIND = Object.freeze({
AGV: 1,
ARM: 2,
@@ -8,6 +12,10 @@ export const DEVICE_KIND = Object.freeze({
SPEAKER: 13,
})
+/**
+ * 流程动作到所需设备类型的全局映射。
+ * 作用范围为所有根据 action 判断执行设备的流程模块;未列出的动作视为没有固定设备类型。
+ */
export const DEVICE_KIND_BY_ACTION = Object.freeze({
AGV_MOVE_TO_POINT: DEVICE_KIND.AGV,
AGV_MOVE_TO_STATION: DEVICE_KIND.AGV,
@@ -28,8 +36,16 @@ export const DEVICE_KIND_BY_ACTION = Object.freeze({
VI_PLAY_CORPUS: DEVICE_KIND.SPEAKER,
})
+/**
+ * 查询某个流程动作所需的设备类型。
+ * 直接读取统一映射;未知或无需固定设备的动作返回 undefined,由调用方决定降级策略。
+ */
export const getDeviceKindForAction = (action) => DEVICE_KIND_BY_ACTION[action]
+/**
+ * 将机器人设备接口数据转换为 Element Plus 下拉框选项,供全局设备选择器复用。
+ * 每项以 deviceId 作为值;设备名有效且不同于 ID 时展示“名称 (ID)”,否则仅展示 ID。
+ */
export const toDeviceOptions = (devices = []) => devices.map((device) => ({
value: device.deviceId,
label: device.deviceName && device.deviceName !== device.deviceId
diff --git a/src/views/flow/components/ActionQueueSingleRun.vue b/src/views/flow/components/ActionQueueSingleRun.vue
index cc23bf8..f2f9932 100644
--- a/src/views/flow/components/ActionQueueSingleRun.vue
+++ b/src/views/flow/components/ActionQueueSingleRun.vue
@@ -15,12 +15,9 @@
步骤 {{ index + 1 }} · {{ actionName(step.type) }}
-
+ :value="device.deviceId" />
{{ stepSummary(step) }}
@@ -34,31 +31,67 @@ import { ElMessage } from 'element-plus'
import { getRobotDevicesByRobotId } from '@/api/inspection/robot'
import { DEVICE_KIND } from '@/utils/robotDevice'
+/**
+ * 组件输入配置,仅在当前单次动作队列运行表单内使用。
+ * nodeParams 提供流程节点参数,robotOptions 提供可选机器人,initialRobotId 用于初始化默认机器人。
+ */
const props = defineProps({
nodeParams: { type: Array, default: () => [] },
robotOptions: { type: Array, default: () => [] },
initialRobotId: { type: String, default: '' },
})
+/** 动作类型到中文名称的映射,作用范围为当前组件的步骤标题展示。 */
const labels = {
DELAY: '等待', ARM_MOVE_J: '机械臂关节运动', ARM_MOVE_L: '机械臂直线运动',
AGV_NAVIGATE_TO_POSE: '底盘导航到坐标', AGV_NAVIGATE_TO_STATION: '底盘导航到站点', AGV_FOLLOW_PATH: '底盘按路径导航',
}
+/** 当前选中的机器人 ID,作用于设备查询、校验和最终运行请求。 */
const robotId = ref('')
+/** 本次动作队列标识;为空时由服务端自动生成,仅随当前运行请求提交。 */
const actionId = ref('')
+/** 整个动作队列的超时时间(毫秒),作用于当前运行请求。 */
const totalTimeoutMs = ref(60000)
+/** 当前节点解析出的动作步骤副本,组件内可补充 deviceId,不回写原始 props。 */
const steps = ref([])
+/** 当前机器人挂载的设备列表,用于为各动作步骤筛选可执行设备。 */
const devices = ref([])
+/** 设备接口请求状态,仅控制当前组件设备选择框的加载效果。 */
const deviceLoading = ref(false)
+
+/**
+ * 获取动作类型的展示名称。
+ * 先查询本地中文映射,未配置的类型直接显示原值,避免未知动作显示为空。
+ */
const actionName = (type) => labels[type] || type
+
+/**
+ * 根据动作名前缀判断所需设备类型。
+ * 当前组件只处理机械臂和底盘队列动作;等待等无设备动作返回 null。
+ */
const deviceKind = (type) => type?.startsWith('ARM_') ? DEVICE_KIND.ARM : (type?.startsWith('AGV_') ? DEVICE_KIND.AGV : null)
+
+/**
+ * 筛选单个步骤可使用的设备,范围限定为当前所选机器人的在线设备结果。
+ * 将接口 deviceKind 与动作要求统一转为数值后比较,兼容字符串和数字类型。
+ */
const devicesForStep = (step) => devices.value.filter((device) => Number(device.deviceKind) === deviceKind(step.type))
+
+/**
+ * 生成步骤参数摘要,仅用于当前组件的只读预览。
+ * 等待和站点导航使用易读文案,其余动作保留完整 JSON 信息。
+ */
const stepSummary = (step) => {
if (step.type === 'DELAY') return `等待 ${step.params?.durationMs || 0} 毫秒`
if (step.type === 'AGV_NAVIGATE_TO_STATION') return `站点 ${step.params?.stationId || '-'}`
return JSON.stringify(step.params || {})
}
+/**
+ * 使用节点参数重新初始化表单,作用于首次渲染及输入属性变化。
+ * 步骤:1. 将参数数组转成按名称索引的对象;2. 设置基本字段并深拷贝步骤;
+ * 3. 应用默认机器人;4. 若存在机器人,则加载设备并为步骤建立默认设备选择。
+ */
const initData = async () => {
const values = Object.fromEntries(props.nodeParams.map((item) => [item.name, item.input]))
actionId.value = values.actionId || ''
@@ -68,7 +101,13 @@ const initData = async () => {
if (robotId.value) await loadDevices(robotId.value)
}
+/**
+ * 加载指定机器人的设备并重建各步骤设备选择,影响当前组件的 devices 和 steps。
+ * 切换机器人后旧 deviceId 已不再可靠,因此先清空设备及所有步骤选择;无机器人时直接结束。
+ * 接口返回后逐步按动作类型筛选设备,并默认选择第一个匹配项;finally 保证加载状态复位。
+ */
const loadDevices = async (value) => {
+ // 机器人发生变化时,旧设备可能属于另一台机器人,必须整体清除以避免误提交。
devices.value = []
steps.value.forEach((step) => { step.deviceId = '' })
if (!value) return
@@ -76,6 +115,7 @@ const loadDevices = async (value) => {
try {
const response = await getRobotDevicesByRobotId(value)
devices.value = Array.isArray(response.data) ? response.data : []
+ // 每个步骤独立按设备类型匹配;有候选项时预选首项,减少单一设备场景下的重复操作。
steps.value.forEach((step) => {
const options = devicesForStep(step)
if (options.length) step.deviceId = options[0].deviceId
@@ -85,9 +125,14 @@ const loadDevices = async (value) => {
}
}
+/**
+ * 校验当前动作队列是否具备运行条件,供父组件在提交前调用。
+ * 依次检查机器人、队列非空及每个需要设备的步骤;首次失败即提示并终止,全部通过返回 true。
+ */
const validate = () => {
if (!robotId.value) return ElMessage.warning('请选择机器人'), false
if (!steps.value.length) return ElMessage.warning('动作队列不能为空'), false
+ // 等待类步骤无需设备;机械臂或底盘步骤必须存在与当前机器人匹配的 deviceId。
for (let index = 0; index < steps.value.length; index += 1) {
const step = steps.value[index]
if (deviceKind(step.type) && !step.deviceId) {
@@ -97,15 +142,41 @@ const validate = () => {
}
return true
}
+
+/** 汇总当前表单数据为运行接口载荷;返回范围包含机器人、队列配置和全部步骤。 */
const getPayload = () => ({ robotId: robotId.value, actionId: actionId.value, totalTimeoutMs: totalTimeoutMs.value, steps: steps.value })
+
+/** 返回当前选中的机器人 ID,供父组件处理兼容字段或运行上下文。 */
const getRobotId = () => robotId.value
+/** 监听节点配置和默认机器人变化,深度变化时重建当前组件的表单及设备选项。 */
watch(() => [props.nodeParams, props.initialRobotId], initData, { immediate: true, deep: true })
+
+/** 向父组件开放提交前校验、载荷读取和机器人 ID 读取能力。 */
defineExpose({ validate, getPayload, getRobotId })