lxl-dev #3

Merged
zhanghao merged 122 commits from lxl-dev into dev 2026-08-24 13:55:18 +08:00
Showing only changes of commit b185b64de8 - Show all commits

View File

@ -11,7 +11,7 @@
:model-value="memberOf(role).robotId" :model-value="memberOf(role).robotId"
clearable clearable
filterable filterable
:loading="robotLoading" :loading="robotLoading || autoAssigning"
placeholder="请选择机器人" placeholder="请选择机器人"
style="width: 100%" style="width: 100%"
@change="(robotId) => changeRobot(role, robotId)" @change="(robotId) => changeRobot(role, robotId)"
@ -23,7 +23,7 @@
<el-select <el-select
v-model="memberOf(role).devices[slot.slotKey]" v-model="memberOf(role).devices[slot.slotKey]"
filterable filterable
:loading="loadingRoleKey === role.roleKey" :loading="autoAssigning || loadingRoleKey === role.roleKey"
:disabled="!memberOf(role).robotId" :disabled="!memberOf(role).robotId"
:placeholder="memberOf(role).robotId ? '请选择设备' : '请先选择机器人'" :placeholder="memberOf(role).robotId ? '请选择设备' : '请先选择机器人'"
style="width: 100%" style="width: 100%"
@ -59,9 +59,11 @@ const assignments = ref([])
const robotOptions = ref([]) const robotOptions = ref([])
const devicesByRole = reactive({}) const devicesByRole = reactive({})
const robotLoading = ref(false) const robotLoading = ref(false)
const autoAssigning = ref(false)
const loadingRoleKey = ref('') const loadingRoleKey = ref('')
const robotLoadError = ref('') const robotLoadError = ref('')
const deviceLoadErrors = reactive({}) const deviceLoadErrors = reactive({})
const onlineDevicesByRobot = new Map()
const loadError = computed(() => robotLoadError.value || Object.values(deviceLoadErrors).find(Boolean) || '') const loadError = computed(() => robotLoadError.value || Object.values(deviceLoadErrors).find(Boolean) || '')
const clone = (value) => JSON.parse(JSON.stringify(value || [])) const clone = (value) => JSON.parse(JSON.stringify(value || []))
@ -74,10 +76,10 @@ const loadRobots = async () => {
robotLoadError.value = '' robotLoadError.value = ''
try { try {
const response = await getRobotList({ const response = await getRobotList({
...props.robotQuery,
pageNum: 1, pageNum: 1,
pageSize: 1000, pageSize: 1000,
connectStatus: '1', connectStatus: '1',
...props.robotQuery,
}) })
robotOptions.value = (response.rows || []).map((robot) => ({ robotOptions.value = (response.rows || []).map((robot) => ({
label: `${robot.robotName || robot.robotId} (${robot.robotId})`, 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)) 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 optionsOf = (role, slot) => {
const member = memberOf(role) const member = memberOf(role)
const unavailableDeviceIds = new Set((role.deviceSlots || []) const unavailableDeviceIds = new Set((role.deviceSlots || [])
@ -122,12 +214,11 @@ const loadDevices = async (role, preserve = true) => {
loadingRoleKey.value = role.roleKey loadingRoleKey.value = role.roleKey
delete deviceLoadErrors[role.roleKey] delete deviceLoadErrors[role.roleKey]
try { try {
const response = await getRobotDevicesByRobotId(member.robotId) devicesByRole[role.roleKey] = await getOnlineDevices(member.robotId)
devicesByRole[role.roleKey] = Array.isArray(response.data) ? response.data : []
;(role.deviceSlots || []).forEach((slot) => { ;(role.deviceSlots || []).forEach((slot) => {
const options = optionsOf(role, slot) const options = optionsOf(role, slot)
if (!preserve || !options.some((option) => option.value === member.devices[slot.slotKey])) { 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() emitValue()
@ -148,6 +239,8 @@ const changeRobot = async (role, robotId) => {
const initialize = async () => { const initialize = async () => {
Object.keys(deviceLoadErrors).forEach((roleKey) => delete deviceLoadErrors[roleKey]) Object.keys(deviceLoadErrors).forEach((roleKey) => delete deviceLoadErrors[roleKey])
Object.keys(devicesByRole).forEach((roleKey) => delete devicesByRole[roleKey])
onlineDevicesByRobot.clear()
const existing = clone(props.modelValue) const existing = clone(props.modelValue)
assignments.value = props.roles.map((role) => { assignments.value = props.roles.map((role) => {
const existingAssignment = existing.find((item) => item.roleKey === role.roleKey) const existingAssignment = existing.find((item) => item.roleKey === role.roleKey)
@ -170,10 +263,11 @@ const initialize = async () => {
}) })
emitValue() emitValue()
await loadRobots() await loadRobots()
await Promise.all(props.roles.filter((role) => memberOf(role).robotId).map((role) => loadDevices(role))) await autoAssignRoles()
} }
const validate = () => { const validate = () => {
if (robotLoading.value || autoAssigning.value) return '正在自动匹配在线机器人和设备,请稍候'
const physicalDeviceUses = new Map() const physicalDeviceUses = new Map()
for (const role of props.roles) { for (const role of props.roles) {
const member = memberOf(role) const member = memberOf(role)