diff --git a/src/api/device/camera.js b/src/api/device/camera.js index 71781d4..c10a7be 100644 --- a/src/api/device/camera.js +++ b/src/api/device/camera.js @@ -1,9 +1,9 @@ import request from '@/utils/request' -export function getRgbStreamUrl(terminalId, deviceId) { +export function getRgbStreamUrl(robotId, deviceId) { return request({ url: '/api/edge/camera/rgb-stream-url', method: 'get', - params: { terminalId, deviceId } + params: { robotId, deviceId } }) } diff --git a/src/api/inspection/robot.js b/src/api/inspection/robot.js index a0a2e21..a1ee25b 100644 --- a/src/api/inspection/robot.js +++ b/src/api/inspection/robot.js @@ -38,6 +38,18 @@ export function getRobotDevices(id) { }) } +export function getRobotDevicesByRobotId(robotId, params = {}) { + return request({ + url: '/inspection/robot/devices', + method: 'get', + params: { + robotId, + onlineOnly: true, + ...params + } + }) +} + /** * 获取机器人地图 * @param {*} robotId diff --git a/src/store/modules/flow.js b/src/store/modules/flow.js index 287ae45..a78c228 100644 --- a/src/store/modules/flow.js +++ b/src/store/modules/flow.js @@ -2,20 +2,11 @@ import { defineStore } from 'pinia' export const useFlowStore = defineStore('flow', { state: () => ({ - disableForm: false, - deviceList: [] + disableForm: false }), actions: { updateDisableForm(value) { this.disableForm = value - }, - - async getDeviceList() { - const data = await new Promise((resolve, reject) => { - resolve( ['cam1', 'cam2', 'cam3', 'cam4']) - }) - this.deviceList = data - return data } } } diff --git a/src/views/device/register/components/Camera/index.vue b/src/views/device/register/components/Camera/index.vue index a7becbe..8e820bf 100644 --- a/src/views/device/register/components/Camera/index.vue +++ b/src/views/device/register/components/Camera/index.vue @@ -47,7 +47,7 @@ const props = defineProps({ type: String, default: null, }, - initialTerminalId: { + initialRobotId: { type: String, default: null, }, @@ -60,7 +60,7 @@ const data = reactive({ stereoModule: false, rgbCamera: false, }, - terminalId: null, + robotId: null, cameraId: null, type: 'camera', callbacks: {}, @@ -95,7 +95,7 @@ function getVideoRef(method) { } async function startDirectRgbStream() { - if (!data.terminalId || !data.cameraId || !colorVideo.value) return; + if (!data.robotId || !data.cameraId || !colorVideo.value) return; rgbStreamAbortController?.abort(); rgbStreamAbortController = null; const generation = ++rgbStreamGeneration; @@ -103,7 +103,7 @@ async function startDirectRgbStream() { rgbStreamRetryTimer = null; streamStatus.getRGBImageStream = 'connecting'; try { - const response = await getRgbStreamUrl(data.terminalId, data.cameraId); + const response = await getRgbStreamUrl(data.robotId, data.cameraId); if (generation !== rgbStreamGeneration || !data.form.rgbCamera) return; const relativeUrl = response.data; const baseUrl = import.meta.env.VITE_APP_BASE_API.replace(/\/$/, ''); @@ -342,11 +342,11 @@ function destroyMediaPlayer(method) { } function channelFor(method) { - return `edgeCameraServiceImpl/${method}/${data.terminalId}/${data.cameraId}`; + return `edgeCameraServiceImpl/${method}/${data.robotId}/${data.cameraId}`; } async function setSubscription(enabled, method, force = false) { - if (!socket || !data.terminalId || !data.cameraId) return; + if (!socket || !data.robotId || !data.cameraId) return; await nextTick(); const channel = channelFor(method); @@ -401,7 +401,7 @@ function scheduleRestart(method) { } function handleSocketOpen() { - if (data.form.stereoModule && data.terminalId && data.cameraId) { + if (data.form.stereoModule && data.robotId && data.cameraId) { destroyMediaPlayer('getDepthImageStream'); setSubscription(true, 'getDepthImageStream', true); } @@ -418,12 +418,12 @@ socket?.on('open', handleSocketOpen); socket?.on('close', handleSocketClose); watch(() => data.form.stereoModule, async (newVal) => { - if (!data.terminalId || !data.cameraId) return; + if (!data.robotId || !data.cameraId) return; await setSubscription(newVal, 'getDepthImageStream'); }); watch(() => data.form.rgbCamera, async (newVal) => { - if (!data.terminalId || !data.cameraId) return; + if (!data.robotId || !data.cameraId) return; if (newVal) { await nextTick(); await startDirectRgbStream(); @@ -436,10 +436,19 @@ watch( () => props.initialDeviceId || route.path.split('/')[3], async (deviceId) => { if (!deviceId) return; + const queryRobotId = props.initialRobotId || route.query.robotId; + const queryDeviceId = props.initialDeviceId || route.query.deviceId; + if (queryRobotId && queryDeviceId) { + data.cameraId = String(queryDeviceId); + data.robotId = String(queryRobotId); + if (data.form.stereoModule) await setSubscription(true, 'getDepthImageStream'); + if (data.form.rgbCamera) await startDirectRgbStream(); + return; + } try { const response = await getRegister(deviceId); data.cameraId = response.data.deviceCode; - data.terminalId = props.initialTerminalId || response.data.idDeDeviceTerminalConfig; + data.robotId = props.initialRobotId || response.data.idDeDeviceTerminalConfig; if (data.form.stereoModule) await setSubscription(true, 'getDepthImageStream'); if (data.form.rgbCamera) await startDirectRgbStream(); } catch (error) { @@ -455,7 +464,7 @@ onUnmounted(() => { stopDirectRgbStream(); ['getDepthImageStream'].forEach(method => { clearTimeout(retryTimers.get(method)); - if (data.terminalId && data.cameraId) { + if (data.robotId && data.cameraId) { const channel = channelFor(method); socket?.send({ type: 'channel_subscription', action: 'unsubscribe', channel }); if (data.callbacks[channel]) socket.off(channel, data.callbacks[channel]); diff --git a/src/views/device/register/components/DexHand/LeftBottomHand.vue b/src/views/device/register/components/DexHand/LeftBottomHand.vue index 27d3d37..302711f 100644 --- a/src/views/device/register/components/DexHand/LeftBottomHand.vue +++ b/src/views/device/register/components/DexHand/LeftBottomHand.vue @@ -17,7 +17,7 @@ import { inject } from 'vue'; const emit = defineEmits(['update:bottomSeriesData']); const socket = inject('ws'); const handCanvas = ref(null); -const terminalId = ref(''); // 待您修改 +const robotId = ref(''); // 待您修改 const deviceId = ref(''); // 待您修改 const frameData = ref({ count: 0, lastTime: 0 }); const isHandSeries = ref(false); @@ -29,7 +29,7 @@ const props = defineProps({ type: String, default: null, }, - initialTerminalId: { // 示例 prop,用于从弹窗接收 terminalId + initialRobotId: { // 示例 prop,用于从弹窗接收 robotId type: String, default: null, }, @@ -37,7 +37,7 @@ const props = defineProps({ // 2. 使用 watchEffect 来响应 props 和路由的变化 watchEffect(() => { - terminalId.value = props.initialTerminalId; + robotId.value = props.initialRobotId; deviceId.value = props.initialDeviceId; }); @@ -225,11 +225,11 @@ const sensorMap = [ // WebSocket 订阅 const subscribeSensorData = async (sub) => { - if (!socket || !terminalId.value || !deviceId.value) { - console.warn(`订阅失败: socket=${!!socket}, terminalId=${terminalId.value}, deviceId=${deviceId.value}`); + if (!socket || !robotId.value || !deviceId.value) { + console.warn(`订阅失败: socket=${!!socket}, robotId=${robotId.value}, deviceId=${deviceId.value}`); return; } - const channel = `edgeDexHandServiceImpl/getSensorDataStream/${terminalId.value}/${deviceId.value}`; + const channel = `edgeDexHandServiceImpl/getSensorDataStream/${robotId.value}/${deviceId.value}`; console.log(sub ? '订阅' : '取消订阅', channel); socket.send({ type: 'channel_subscription', diff --git a/src/views/device/register/components/DexHand/LeftTopHand.vue b/src/views/device/register/components/DexHand/LeftTopHand.vue index 5261e51..faeae7c 100644 --- a/src/views/device/register/components/DexHand/LeftTopHand.vue +++ b/src/views/device/register/components/DexHand/LeftTopHand.vue @@ -35,7 +35,7 @@ const handImage = ref(null); const containerRef = ref(null); // 新增:引用父容器 const defaultHandImageInfo = { width: 891, height: 981 }; const imageAspectRatio = defaultHandImageInfo.width / defaultHandImageInfo.height; -const terminalId = ref(''); +const robotId = ref(''); const deviceId = ref(''); // 1. 定义 props const props = defineProps({ @@ -43,7 +43,7 @@ const props = defineProps({ type: String, default: null, }, - initialTerminalId: { // 示例 prop,用于从弹窗接收 terminalId + initialRobotId: { // 示例 prop,用于从弹窗接收 robotId type: String, default: null, }, @@ -51,9 +51,9 @@ const props = defineProps({ // 2. 使用 watchEffect 来响应 props 和路由的变化 watchEffect(() => { - terminalId.value = props.initialTerminalId; + robotId.value = props.initialRobotId; deviceId.value = props.initialDeviceId; - console.log(deviceId.value, terminalId.value,props) + console.log(deviceId.value, robotId.value,props) }); const sliders = ref([ @@ -66,9 +66,9 @@ const sliders = ref([ ]); function updateSeriesData(value, i) { - console.log(deviceId.value, terminalId.value) + console.log(deviceId.value, robotId.value) loading.value = true; - setDexHandAngle({ deviceId: deviceId.value, terminalId: terminalId.value, value: value / 100, id: i }).then(() => { + setDexHandAngle({ deviceId: deviceId.value, robotId: robotId.value, value: value / 100, id: i }).then(() => { loading.value = false; updateToSeriesData(); }).catch(() => { @@ -131,7 +131,7 @@ const updateSliderPositions = () => { }; const updateToSeriesData = () => { - status({ deviceId: deviceId.value, terminalId: terminalId.value }).then((res) => { + status({ deviceId: deviceId.value, robotId: robotId.value }).then((res) => { const newTopSeriesData = res.data.handsList.map(item => item.force); emit('update:topSeriesData', newTopSeriesData); }); diff --git a/src/views/device/register/components/DexHand/index.vue b/src/views/device/register/components/DexHand/index.vue index fd33381..892ad7a 100644 --- a/src/views/device/register/components/DexHand/index.vue +++ b/src/views/device/register/components/DexHand/index.vue @@ -4,11 +4,11 @@
- +
- - + +
@@ -34,7 +34,7 @@ const props = defineProps({ type: String, default: null, }, - initialTerminalId: { + initialRobotId: { type: String, default: null, }, @@ -62,7 +62,7 @@ const avgData = ref({ // 声明组件内部使用的响应式变量 const deviceId = ref(""); -const terminalId = ref(""); +const robotId = ref(""); const handleUpdateTopSeriesData = (newData) => { topSeriesData.value = newData; @@ -77,19 +77,24 @@ const handleUpdateBottomSeriesData = ({ maxData: newMaxData, avgData: newAvgData const route = useRoute(); // 统一处理获取 register 数据的方法 const fetchRegisterData = (currentDeviceId) => { + if (route.query.robotId && route.query.deviceId) { + robotId.value = String(route.query.robotId); + deviceId.value = String(route.query.deviceId); + return; + } if (currentDeviceId) { getRegister(currentDeviceId).then(res => { deviceId.value = res.data.deviceCode; // 再次更新,确保与 API 返回一致 - terminalId.value = res.data.idDeDeviceTerminalConfig; + robotId.value = res.data.idDeDeviceTerminalConfig; }).catch(error => { console.error("Error fetching register:", error); - // 可以根据需要重置 deviceId 和 terminalId + // 可以根据需要重置 deviceId 和 robotId deviceId.value = ""; - terminalId.value = ""; + robotId.value = ""; }); } else { - // 如果 deviceId 为空,可以考虑重置 terminalId - terminalId.value = ""; + // 如果 deviceId 为空,可以考虑重置 robotId + robotId.value = ""; } }; @@ -107,6 +112,11 @@ watch(() => props.initialDeviceId, (newVal) => { // 注意:这里假设 route.path.split("/")[3] 是cameraId // 如果你的路由结构不同,请调整这里的逻辑 watch(() => route.path, (newPath) => { + if (route.query.robotId && route.query.deviceId) { + robotId.value = String(route.query.robotId); + deviceId.value = String(route.query.deviceId); + return; + } const routeDeviceId = newPath.split("/")[3]; if (!deviceId.value && routeDeviceId) { // 只有当 deviceId 尚未从 props 或其他地方设置时才从路由获取 deviceId.value = routeDeviceId; @@ -115,10 +125,10 @@ watch(() => route.path, (newPath) => { }, { immediate: true }); // immediate: true 确保在组件挂载时也检查路由 -// 如果需要 initialTerminalId prop 也影响 terminalId ref,可以添加一个 watch -watch(() => props.initialTerminalId, (newVal) => { +// 如果需要 initialRobotId prop 也影响 robotId ref,可以添加一个 watch +watch(() => props.initialRobotId, (newVal) => { if (newVal) { - terminalId.value = newVal; + robotId.value = newVal; } }, { immediate: true }); @@ -155,4 +165,4 @@ watch(() => props.initialTerminalId, (newVal) => { width: 100%; overflow: hidden; } - \ No newline at end of file + diff --git a/src/views/device/register/components/Head/index.vue b/src/views/device/register/components/Head/index.vue index 3142033..ae48490 100644 --- a/src/views/device/register/components/Head/index.vue +++ b/src/views/device/register/components/Head/index.vue @@ -80,10 +80,12 @@ + + diff --git a/src/views/device/robotManger/index.vue b/src/views/device/robotManger/index.vue index 058ce86..7eb5638 100644 --- a/src/views/device/robotManger/index.vue +++ b/src/views/device/robotManger/index.vue @@ -61,7 +61,7 @@ ×
- +
@@ -398,7 +398,7 @@ const openPopup = (area) => { originalTop: `${area.y}px`, originalLeft: `${area.x + leftOffset}px`, isMaximized: false, - props: { terminalId: 'your-terminal-id', cameraId: 'your-camera-id' } // 示例props + props: { robotId: area.robotId || '', cameraId: area.deviceId || '' } }; popups.value.push(popup); isResizing.value.push(false); @@ -871,4 +871,4 @@ onMounted(() => { flex: 1; overflow: auto; } - \ No newline at end of file + diff --git a/src/views/flow/components/TestRun.vue b/src/views/flow/components/TestRun.vue index bdd1b69..241f387 100644 --- a/src/views/flow/components/TestRun.vue +++ b/src/views/flow/components/TestRun.vue @@ -11,6 +11,18 @@ :rules="rules" label-width="auto" > +
+
+ + 机器人设备自动匹配 +
+
正在读取机器人在线设备...
+
+ 已匹配 {{ bindingSummary.matched }} 个设备节点 + ,{{ bindingSummary.missing }} 个节点暂无匹配设备 +
+
选择机器人后,将按节点类型自动填充该机器人上报的在线设备。
+
@@ -66,7 +78,8 @@ import { getStartNodeFormData, formatTableData } from '@/utils/flow' import { flowExecuteTrial } from '@/api/flow/flow' import { emitter } from '@/utils/eventBus'; import { ElMessage } from 'element-plus'; -import { getRobotList } from '@/api/inspection/robot' +import { getRobotList, getRobotDevicesByRobotId } from '@/api/inspection/robot' +import { getDeviceKindForAction, toDeviceOptions } from '@/utils/robotDevice' const props = defineProps({ drawer: Boolean, @@ -76,6 +89,8 @@ const props = defineProps({ const ruleFormRef = ref() const rules = ref({}); const tableData = ref([]) +const bindingLoading = ref(false) +const bindingSummary = ref({ robotId: '', matched: 0, missing: 0 }) const emits = defineEmits(['close', 'changeState']) @@ -89,7 +104,8 @@ watch(() => props.drawer, if (newVal) { const data = getStartNodeFormData() tableData.value = data - handleGetTerminalGroup() + bindingSummary.value = { robotId: '', matched: 0, missing: 0 } + loadRobotOptions() } } ) @@ -104,11 +120,11 @@ const confirm = () => { }) const flowData = JSON.stringify(lf.getGraphData()) - const { terminalId, robotId, ...runParams } = formData + const { robotId, ...runParams } = formData const res = await flowExecuteTrial({ flowData, itemId: props.flowId, - robotId: robotId || terminalId, + robotId, runParams }) if (res.code === 200) { @@ -123,13 +139,69 @@ const confirm = () => { .catch(() => {}) } -const terminalIdOptions = ref([]) +const robotIdOptions = ref([]) -/** 获取设备终端列表 */ -const handleGetTerminalGroup = async (row) => { +const handleRobotChange = async (robotId) => { + bindingSummary.value = { robotId: robotId || '', matched: 0, missing: 0 } + if (!robotId) return + bindingLoading.value = true + try { + const response = await getRobotDevicesByRobotId(robotId) + const devices = Array.isArray(response.data) ? response.data : [] + let matched = 0 + let missing = 0 + const missingNodes = [] + const { nodes } = lf.getGraphData() + + nodes.forEach((node) => { + if (node.properties?.nodeType !== 'EDGE') return + const nodeParams = JSON.parse(JSON.stringify(node.properties.nodeParams || [])) + const robotParam = nodeParams.find(param => param.name === 'robotId') + if (robotParam) robotParam.input = robotId + + const deviceParam = nodeParams.find(param => param.name === 'deviceId') + if (!deviceParam) { + if (robotParam) lf.setProperties(node.id, { ...node.properties, nodeParams }) + return + } + + const kind = getDeviceKindForAction(node.properties.action) + const candidates = kind + ? devices.filter(device => Number(device.deviceKind) === kind) + : devices + deviceParam.input = candidates[0]?.deviceId || '' + deviceParam.componentType = 'select' + deviceParam.selectOptions = toDeviceOptions(candidates) + deviceParam.deviceLocked = candidates.length === 1 + lf.setProperties(node.id, { ...node.properties, nodeParams }) + + if (candidates.length > 0) matched += 1 + else { + missing += 1 + missingNodes.push(node.properties.name || node.id) + } + }) + + bindingSummary.value = { robotId, matched, missing } + if (missingNodes.length) { + ElMessage.warning(`以下节点没有匹配的在线设备:${missingNodes.join('、')}`) + } else if (matched > 0) { + ElMessage.success(`已自动匹配 ${matched} 个设备节点`) + } + } catch (error) { + bindingSummary.value = { robotId, matched: 0, missing: 0 } + console.error('自动匹配机器人设备失败:', error) + ElMessage.error('读取机器人设备失败') + } finally { + bindingLoading.value = false + } +} + +/** 获取在线机器人列表 */ +const loadRobotOptions = async () => { const res = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' }) if (res.code === 200) { - terminalIdOptions.value = res.rows?.map((item) => { + robotIdOptions.value = res.rows?.map((item) => { return { label: `${item.robotName || item.robotId} (${item.robotId})`, value: item.robotId @@ -139,3 +211,40 @@ const handleGetTerminalGroup = async (row) => { } + + diff --git a/src/views/flow/components/params/BasicNodeParams.vue b/src/views/flow/components/params/BasicNodeParams.vue index 040539d..59fdd4d 100644 --- a/src/views/flow/components/params/BasicNodeParams.vue +++ b/src/views/flow/components/params/BasicNodeParams.vue @@ -11,7 +11,7 @@
- {{ robotActionButtonText }} + {{ robotActionButtonText }}
@@ -82,15 +82,25 @@ - - - +
设备来自机器人最近一次在线上报,单个匹配设备会自动锁定。
+ + + + + - - + + + + +
已自动匹配并锁定