diff --git a/src/views/flow/components/RuntimeAssignmentsEditor.vue b/src/views/flow/components/RuntimeAssignmentsEditor.vue index 1fc7dc9..408a66c 100644 --- a/src/views/flow/components/RuntimeAssignmentsEditor.vue +++ b/src/views/flow/components/RuntimeAssignmentsEditor.vue @@ -11,7 +11,7 @@ :model-value="memberOf(role).robotId" clearable filterable - :loading="robotLoading" + :loading="robotLoading || autoAssigning" placeholder="请选择机器人" style="width: 100%" @change="(robotId) => changeRobot(role, robotId)" @@ -23,7 +23,7 @@ robotLoadError.value || Object.values(deviceLoadErrors).find(Boolean) || '') const clone = (value) => JSON.parse(JSON.stringify(value || [])) @@ -74,10 +76,10 @@ const loadRobots = async () => { robotLoadError.value = '' try { const response = await getRobotList({ + ...props.robotQuery, pageNum: 1, pageSize: 1000, connectStatus: '1', - ...props.robotQuery, }) robotOptions.value = (response.rows || []).map((robot) => ({ label: `${robot.robotName || robot.robotId} (${robot.robotId})`, @@ -98,6 +100,96 @@ const robotOptionsOf = (role) => { return robotOptions.value.filter((robot) => String(robot.currentMapId || '') === String(props.requiredMapId)) } +const getOnlineDevices = async (robotId) => { + if (!onlineDevicesByRobot.has(robotId)) { + const request = getRobotDevicesByRobotId(robotId).then((response) => ( + Array.isArray(response.data) ? response.data : [] + )) + onlineDevicesByRobot.set(robotId, request) + } + try { + return await onlineDevicesByRobot.get(robotId) + } catch (error) { + onlineDevicesByRobot.delete(robotId) + throw error + } +} + +const itemScopeOf = (role) => role.itemId == null + ? 'task' + : `${role.itemId}:${role.itemOccurrence || 1}` + +const planRoleDevices = (role, robotId, devices, committedUses, preferredDevices = {}) => { + const plannedUses = new Map(committedUses) + const selectedDevices = {} + for (const slot of role.deviceSlots || []) { + const matchingDevices = devices.filter((device) => ( + device?.deviceId + && (slot.deviceKind == null || Number(device.deviceKind) === Number(slot.deviceKind)) + )) + const preferredId = preferredDevices[slot.slotKey] + const orderedDevices = preferredId + ? [ + ...matchingDevices.filter((device) => device.deviceId === preferredId), + ...matchingDevices.filter((device) => device.deviceId !== preferredId), + ] + : matchingDevices + const selected = orderedDevices.find((device) => { + const physicalKey = `${itemScopeOf(role)}:${robotId}:${device.deviceId}` + const existingUse = plannedUses.get(physicalKey) + return !existingUse || (slot.allowReuse && existingUse.allowReuse) + }) + if (!selected && slot.required) return null + selectedDevices[slot.slotKey] = selected?.deviceId || '' + if (selected) { + plannedUses.set(`${itemScopeOf(role)}:${robotId}:${selected.deviceId}`, { + allowReuse: slot.allowReuse === true, + }) + } + } + return { selectedDevices, plannedUses } +} + +const autoAssignRoles = async () => { + let committedUses = new Map() + autoAssigning.value = true + try { + for (const role of props.roles) { + const member = memberOf(role) + const candidates = robotOptionsOf(role) + let match = null + for (const robot of candidates) { + try { + const devices = (role.deviceSlots || []).length + ? await getOnlineDevices(robot.value) + : [] + const preferredDevices = robot.value === member.robotId ? member.devices : {} + const plan = planRoleDevices(role, robot.value, devices, committedUses, preferredDevices) + if (plan) { + match = { robot, devices, ...plan } + break + } + } catch (error) { + // A failed device query makes this robot ineligible for automatic assignment. + } + } + if (match) { + member.robotId = match.robot.value + member.devices = match.selectedDevices + devicesByRole[role.roleKey] = match.devices + committedUses = match.plannedUses + } else { + member.robotId = '' + member.devices = Object.fromEntries((role.deviceSlots || []).map((slot) => [slot.slotKey, ''])) + devicesByRole[role.roleKey] = [] + } + } + emitValue() + } finally { + autoAssigning.value = false + } +} + const optionsOf = (role, slot) => { const member = memberOf(role) const unavailableDeviceIds = new Set((role.deviceSlots || []) @@ -122,12 +214,11 @@ const loadDevices = async (role, preserve = true) => { loadingRoleKey.value = role.roleKey delete deviceLoadErrors[role.roleKey] try { - const response = await getRobotDevicesByRobotId(member.robotId) - devicesByRole[role.roleKey] = Array.isArray(response.data) ? response.data : [] + devicesByRole[role.roleKey] = await getOnlineDevices(member.robotId) ;(role.deviceSlots || []).forEach((slot) => { const options = optionsOf(role, slot) if (!preserve || !options.some((option) => option.value === member.devices[slot.slotKey])) { - member.devices[slot.slotKey] = options.length === 1 ? options[0].value : '' + member.devices[slot.slotKey] = options[0]?.value || '' } }) emitValue() @@ -148,6 +239,8 @@ const changeRobot = async (role, robotId) => { const initialize = async () => { Object.keys(deviceLoadErrors).forEach((roleKey) => delete deviceLoadErrors[roleKey]) + Object.keys(devicesByRole).forEach((roleKey) => delete devicesByRole[roleKey]) + onlineDevicesByRobot.clear() const existing = clone(props.modelValue) assignments.value = props.roles.map((role) => { const existingAssignment = existing.find((item) => item.roleKey === role.roleKey) @@ -170,10 +263,11 @@ const initialize = async () => { }) emitValue() await loadRobots() - await Promise.all(props.roles.filter((role) => memberOf(role).robotId).map((role) => loadDevices(role))) + await autoAssignRoles() } const validate = () => { + if (robotLoading.value || autoAssigning.value) return '正在自动匹配在线机器人和设备,请稍候' const physicalDeviceUses = new Map() for (const role of props.roles) { const member = memberOf(role) diff --git a/src/views/inspection/runTask/task-steps.vue b/src/views/inspection/runTask/task-steps.vue index f1d7ea3..6d7e595 100644 --- a/src/views/inspection/runTask/task-steps.vue +++ b/src/views/inspection/runTask/task-steps.vue @@ -1,8 +1,13 @@