forked from SmartBench/CMVR-IOT-UI
- 修改 DeviceUniversal.vue 中的 initData 函数,使用可选链操作符安全访问属性 - 将 setTimeout 替换为 nextTick 确保编辑器值正确设置 - 添加对 props.data 的监听以响应数据变化 - 重构 RobotExecutionTarget.vue 中的初始化函数名称和监听逻辑 - 在 flowExecution.js 中添加 itemOccurrence 字段支持 - 优化 RuntimeAssignmentsEditor.vue 中的设备复用检查逻辑 - 实现基于检测项范围的设备键值生成策略
121 lines
5.0 KiB
JavaScript
121 lines
5.0 KiB
JavaScript
import { getFlowParamsApi } from '@/api/device/terminal'
|
|
import { getDetect } from '@/api/test/detect'
|
|
import {
|
|
getResourceRoles,
|
|
mergeResourceRoleDefinitions,
|
|
normalizeFlowResourceSchema,
|
|
validateFlowResources,
|
|
} from '@/utils/flowResources'
|
|
|
|
const clone = (value) => JSON.parse(JSON.stringify(value || []))
|
|
|
|
const parseFlowData = (itemId, flowData) => {
|
|
if (!flowData) throw new Error(`流程项[${itemId}]还没有配置工作流`)
|
|
try {
|
|
const graph = typeof flowData === 'string' ? JSON.parse(flowData) : flowData
|
|
const normalizedGraph = normalizeFlowResourceSchema(graph)
|
|
const validation = validateFlowResources(normalizedGraph)
|
|
if (validation.errors.length) {
|
|
throw new Error(validation.errors[0].message)
|
|
}
|
|
return validation.graph
|
|
} catch (error) {
|
|
throw new Error(`流程项[${itemId}]不可执行:${error.message || '工作流数据无法解析'}`)
|
|
}
|
|
}
|
|
|
|
const assertCompatibleDefinitions = (roleGroups) => {
|
|
const rolesByKey = new Map()
|
|
roleGroups.flat().forEach((role) => {
|
|
const existing = rolesByKey.get(role.roleKey)
|
|
if (!existing) {
|
|
rolesByKey.set(role.roleKey, clone([role])[0])
|
|
return
|
|
}
|
|
const slotsByKey = new Map((existing.deviceSlots || []).map((slot) => [slot.slotKey, slot]))
|
|
;(role.deviceSlots || []).forEach((slot) => {
|
|
const existingSlot = slotsByKey.get(slot.slotKey)
|
|
if (existingSlot && String(existingSlot.deviceKind ?? '') !== String(slot.deviceKind ?? '')) {
|
|
throw new Error(`机器人角色“${existing.displayName}”的设备用途“${slot.displayName}”在不同流程中类型不一致`)
|
|
}
|
|
if (!existingSlot) existing.deviceSlots.push(clone([slot])[0])
|
|
})
|
|
})
|
|
}
|
|
|
|
export const loadFlowItemRuntimeRoles = async (items = []) => {
|
|
if (items.some((item) => !item?.itemId)) throw new Error('任务编排中存在缺少流程项ID的数据')
|
|
const uniqueItems = Array.from(new Map(
|
|
items.filter((item) => item?.itemId).map((item) => [item.itemId, item]),
|
|
).values())
|
|
if (!uniqueItems.length) throw new Error('当前任务没有编排任何流程项')
|
|
|
|
const roleGroups = await Promise.all(uniqueItems.map(async (item) => {
|
|
const response = await getDetect(item.itemId)
|
|
const graph = parseFlowData(item.itemName || item.itemId, response.data?.flowData)
|
|
return getResourceRoles(graph)
|
|
}))
|
|
assertCompatibleDefinitions(roleGroups)
|
|
return mergeResourceRoleDefinitions(roleGroups)
|
|
}
|
|
|
|
/**
|
|
* Loads runtime roles without merging equal role keys across flow items.
|
|
* Task orchestration needs one independent assignment per detection item because
|
|
* two items may use different physical robots even when their workflows define
|
|
* the same logical role (for example, `default_robot`).
|
|
*/
|
|
export const loadFlowItemScopedRuntimeRoles = async (items = []) => {
|
|
if (items.some((item) => !item?.itemId)) throw new Error('任务编排中存在缺少流程项ID的数据')
|
|
if (!items.length) throw new Error('当前任务没有编排任何流程项')
|
|
|
|
const occurrences = new Map()
|
|
const roleGroups = await Promise.all(items.map(async (item) => {
|
|
const occurrence = (occurrences.get(String(item.itemId)) || 0) + 1
|
|
occurrences.set(String(item.itemId), occurrence)
|
|
const response = await getDetect(item.itemId)
|
|
const graph = parseFlowData(item.itemName || item.itemId, response.data?.flowData)
|
|
return getResourceRoles(graph).map((role) => ({
|
|
...clone([role])[0],
|
|
roleKey: `${item.itemId}:${occurrence}:${role.roleKey}`,
|
|
sourceRoleKey: role.roleKey,
|
|
itemId: item.itemId,
|
|
itemOccurrence: occurrence,
|
|
itemName: item.itemName || String(item.itemId),
|
|
displayName: `${item.itemName || item.itemId} / ${role.displayName}`,
|
|
}))
|
|
}))
|
|
return roleGroups.flat()
|
|
}
|
|
|
|
export const loadTaskRuntimeRoles = async (taskId) => {
|
|
if (!taskId) throw new Error('任务没有关联流程编排')
|
|
const response = await getFlowParamsApi(taskId)
|
|
return loadFlowItemRuntimeRoles(response.rows || [])
|
|
}
|
|
|
|
export const loadTaskScopedRuntimeRoles = async (taskId) => {
|
|
if (!taskId) throw new Error('任务没有关联流程编排')
|
|
const response = await getFlowParamsApi(taskId)
|
|
return loadFlowItemScopedRuntimeRoles(response.rows || [])
|
|
}
|
|
|
|
export const createRuntimeAssignments = (roles = [], primaryRobotId = '') => roles.map((role, index) => ({
|
|
roleKey: role.roleKey,
|
|
...(role.sourceRoleKey ? { sourceRoleKey: role.sourceRoleKey } : {}),
|
|
...(role.itemId != null ? { itemId: role.itemId } : {}),
|
|
...(role.itemOccurrence != null ? { itemOccurrence: role.itemOccurrence } : {}),
|
|
members: [{
|
|
memberKey: `${role.roleKey}_1`,
|
|
robotId: index === 0 ? primaryRobotId || role.legacyRobotId || '' : role.legacyRobotId || '',
|
|
devices: Object.fromEntries((role.deviceSlots || []).map((slot) => [
|
|
slot.slotKey,
|
|
slot.legacyDeviceId || '',
|
|
])),
|
|
}],
|
|
}))
|
|
|
|
export const firstAssignedRobotId = (assignments = []) => (
|
|
clone(assignments).flatMap((item) => item.members || []).find((member) => member.robotId)?.robotId || ''
|
|
)
|