112 lines
4.8 KiB
Vue
112 lines
4.8 KiB
Vue
|
|
<template>
|
||
|
|
<el-form label-position="top">
|
||
|
|
<el-form-item label="机器人" required>
|
||
|
|
<el-select v-model="robotId" filterable style="width: 100%" @change="loadDevices">
|
||
|
|
<el-option v-for="item in robotOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||
|
|
</el-select>
|
||
|
|
</el-form-item>
|
||
|
|
<el-form-item label="队列总超时(毫秒)" required>
|
||
|
|
<el-input-number v-model="totalTimeoutMs" :min="1" :max="2147483647" style="width: 100%" />
|
||
|
|
</el-form-item>
|
||
|
|
<el-form-item label="动作标识(可选)">
|
||
|
|
<el-input v-model="actionId" placeholder="留空时自动生成" />
|
||
|
|
</el-form-item>
|
||
|
|
<div v-for="(step, index) in steps" :key="step.stepId || index" class="single-queue-step">
|
||
|
|
<div class="single-queue-step__title">步骤 {{ index + 1 }} · {{ actionName(step.type) }}</div>
|
||
|
|
<el-form-item v-if="deviceKind(step.type)" label="执行设备" required>
|
||
|
|
<el-select v-model="step.deviceId" filterable :loading="deviceLoading" style="width: 100%">
|
||
|
|
<el-option
|
||
|
|
v-for="device in devicesForStep(step)"
|
||
|
|
:key="device.deviceId"
|
||
|
|
:label="device.deviceName && device.deviceName !== device.deviceId ? `${device.deviceName} (${device.deviceId})` : device.deviceId"
|
||
|
|
:value="device.deviceId"
|
||
|
|
/>
|
||
|
|
</el-select>
|
||
|
|
</el-form-item>
|
||
|
|
<div class="single-queue-step__params">{{ stepSummary(step) }}</div>
|
||
|
|
</div>
|
||
|
|
</el-form>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup>
|
||
|
|
import { ref, watch } from 'vue'
|
||
|
|
import { ElMessage } from 'element-plus'
|
||
|
|
import { getRobotDevicesByRobotId } from '@/api/inspection/robot'
|
||
|
|
import { DEVICE_KIND } from '@/utils/robotDevice'
|
||
|
|
|
||
|
|
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: '底盘按路径导航',
|
||
|
|
}
|
||
|
|
const robotId = ref('')
|
||
|
|
const actionId = ref('')
|
||
|
|
const totalTimeoutMs = ref(60000)
|
||
|
|
const steps = ref([])
|
||
|
|
const devices = ref([])
|
||
|
|
const deviceLoading = ref(false)
|
||
|
|
const actionName = (type) => labels[type] || type
|
||
|
|
const deviceKind = (type) => type?.startsWith('ARM_') ? DEVICE_KIND.ARM : (type?.startsWith('AGV_') ? DEVICE_KIND.AGV : null)
|
||
|
|
const devicesForStep = (step) => devices.value.filter((device) => Number(device.deviceKind) === deviceKind(step.type))
|
||
|
|
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 || {})
|
||
|
|
}
|
||
|
|
|
||
|
|
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 : []))
|
||
|
|
robotId.value = props.initialRobotId || ''
|
||
|
|
if (robotId.value) await loadDevices(robotId.value)
|
||
|
|
}
|
||
|
|
|
||
|
|
const loadDevices = async (value) => {
|
||
|
|
devices.value = []
|
||
|
|
steps.value.forEach((step) => { step.deviceId = '' })
|
||
|
|
if (!value) return
|
||
|
|
deviceLoading.value = true
|
||
|
|
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
|
||
|
|
})
|
||
|
|
} finally {
|
||
|
|
deviceLoading.value = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const validate = () => {
|
||
|
|
if (!robotId.value) return ElMessage.warning('请选择机器人'), false
|
||
|
|
if (!steps.value.length) return ElMessage.warning('动作队列不能为空'), false
|
||
|
|
for (let index = 0; index < steps.value.length; index += 1) {
|
||
|
|
const step = steps.value[index]
|
||
|
|
if (deviceKind(step.type) && !step.deviceId) {
|
||
|
|
ElMessage.warning(`步骤 ${index + 1} 没有可用的执行设备`)
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
const getPayload = () => ({ robotId: robotId.value, actionId: actionId.value, totalTimeoutMs: totalTimeoutMs.value, steps: steps.value })
|
||
|
|
const getRobotId = () => robotId.value
|
||
|
|
|
||
|
|
watch(() => [props.nodeParams, props.initialRobotId], initData, { immediate: true, deep: true })
|
||
|
|
defineExpose({ validate, getPayload, getRobotId })
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
.single-queue-step { margin-bottom: 10px; padding: 12px; border: 1px solid #dfe4ec; border-radius: 8px; }
|
||
|
|
.single-queue-step__title { margin-bottom: 10px; color: #1f2937; font-size: 13px; font-weight: 650; }
|
||
|
|
.single-queue-step__params { overflow: hidden; color: #7a8494; font-size: 12px; line-height: 18px; text-overflow: ellipsis; white-space: nowrap; }
|
||
|
|
</style>
|