Compare commits

..

No commits in common. "e3a566cf286e3e74bfba9cbb0417491591f7fb78" and "d55f95e7d3c7139708c9d0e6a9c383bf8126007b" have entirely different histories.

25 changed files with 580 additions and 717 deletions

View File

@ -1,9 +1,9 @@
import request from '@/utils/request'
export function getRgbStreamUrl(robotId, deviceId) {
export function getRgbStreamUrl(terminalId, deviceId) {
return request({
url: '/api/edge/camera/rgb-stream-url',
method: 'get',
params: { robotId, deviceId }
params: { terminalId, deviceId }
})
}

View File

@ -38,18 +38,6 @@ export function getRobotDevices(id) {
})
}
export function getRobotDevicesByRobotId(robotId, params = {}) {
return request({
url: '/inspection/robot/devices',
method: 'get',
params: {
robotId,
onlineOnly: true,
...params
}
})
}
/**
* 获取机器人地图
* @param {*} robotId

View File

@ -2,11 +2,20 @@ import { defineStore } from 'pinia'
export const useFlowStore = defineStore('flow', {
state: () => ({
disableForm: false
disableForm: false,
deviceList: []
}),
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
}
}
}

View File

@ -1,38 +0,0 @@
export const DEVICE_KIND = Object.freeze({
AGV: 1,
ARM: 2,
BIO_HEAD: 4,
CAMERA: 5,
MICROPHONE: 9,
ROBOT: 12,
SPEAKER: 13,
})
export const DEVICE_KIND_BY_ACTION = Object.freeze({
AGV_MOVE_TO_POINT: DEVICE_KIND.AGV,
AGV_MOVE_TO_STATION: DEVICE_KIND.AGV,
ARM_MOVE_TO_POINT: DEVICE_KIND.ARM,
ARM_MOVE_TO_J: DEVICE_KIND.ARM,
TOUCH: DEVICE_KIND.ARM,
BIO_HEAD_SPEAK_START: DEVICE_KIND.BIO_HEAD,
BIO_HEAD_SPEAK_STOP: DEVICE_KIND.BIO_HEAD,
BIO_HEAD_SPECIAL_EXPRESSION: DEVICE_KIND.BIO_HEAD,
CAMERA_START: DEVICE_KIND.CAMERA,
CAMERA_STOP: DEVICE_KIND.CAMERA,
CAMERA_GETRGBIMAGE: DEVICE_KIND.CAMERA,
CAMERA_RECORDING_START: DEVICE_KIND.CAMERA,
CAMERA_RECORDING_STOP: DEVICE_KIND.CAMERA,
MICROPHONE_START: DEVICE_KIND.MICROPHONE,
MICROPHONE_STOP: DEVICE_KIND.MICROPHONE,
SPEAKER_PLAYAUDIO: DEVICE_KIND.SPEAKER,
VI_PLAY_CORPUS: DEVICE_KIND.SPEAKER,
})
export const getDeviceKindForAction = (action) => DEVICE_KIND_BY_ACTION[action]
export const toDeviceOptions = (devices = []) => devices.map((device) => ({
value: device.deviceId,
label: device.deviceName && device.deviceName !== device.deviceId
? `${device.deviceName} (${device.deviceId})`
: device.deviceId,
}))

View File

@ -47,7 +47,7 @@ const props = defineProps({
type: String,
default: null,
},
initialRobotId: {
initialTerminalId: {
type: String,
default: null,
},
@ -60,7 +60,7 @@ const data = reactive({
stereoModule: false,
rgbCamera: false,
},
robotId: null,
terminalId: null,
cameraId: null,
type: 'camera',
callbacks: {},
@ -95,7 +95,7 @@ function getVideoRef(method) {
}
async function startDirectRgbStream() {
if (!data.robotId || !data.cameraId || !colorVideo.value) return;
if (!data.terminalId || !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.robotId, data.cameraId);
const response = await getRgbStreamUrl(data.terminalId, 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.robotId}/${data.cameraId}`;
return `edgeCameraServiceImpl/${method}/${data.terminalId}/${data.cameraId}`;
}
async function setSubscription(enabled, method, force = false) {
if (!socket || !data.robotId || !data.cameraId) return;
if (!socket || !data.terminalId || !data.cameraId) return;
await nextTick();
const channel = channelFor(method);
@ -401,7 +401,7 @@ function scheduleRestart(method) {
}
function handleSocketOpen() {
if (data.form.stereoModule && data.robotId && data.cameraId) {
if (data.form.stereoModule && data.terminalId && 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.robotId || !data.cameraId) return;
if (!data.terminalId || !data.cameraId) return;
await setSubscription(newVal, 'getDepthImageStream');
});
watch(() => data.form.rgbCamera, async (newVal) => {
if (!data.robotId || !data.cameraId) return;
if (!data.terminalId || !data.cameraId) return;
if (newVal) {
await nextTick();
await startDirectRgbStream();
@ -436,19 +436,10 @@ 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.robotId = props.initialRobotId || response.data.idDeDeviceTerminalConfig;
data.terminalId = props.initialTerminalId || response.data.idDeDeviceTerminalConfig;
if (data.form.stereoModule) await setSubscription(true, 'getDepthImageStream');
if (data.form.rgbCamera) await startDirectRgbStream();
} catch (error) {
@ -464,7 +455,7 @@ onUnmounted(() => {
stopDirectRgbStream();
['getDepthImageStream'].forEach(method => {
clearTimeout(retryTimers.get(method));
if (data.robotId && data.cameraId) {
if (data.terminalId && data.cameraId) {
const channel = channelFor(method);
socket?.send({ type: 'channel_subscription', action: 'unsubscribe', channel });
if (data.callbacks[channel]) socket.off(channel, data.callbacks[channel]);

View File

@ -17,7 +17,7 @@ import { inject } from 'vue';
const emit = defineEmits(['update:bottomSeriesData']);
const socket = inject('ws');
const handCanvas = ref(null);
const robotId = ref(''); //
const terminalId = 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,
},
initialRobotId: { // prop robotId
initialTerminalId: { // prop terminalId
type: String,
default: null,
},
@ -37,7 +37,7 @@ const props = defineProps({
// 2. 使 watchEffect props
watchEffect(() => {
robotId.value = props.initialRobotId;
terminalId.value = props.initialTerminalId;
deviceId.value = props.initialDeviceId;
});
@ -225,11 +225,11 @@ const sensorMap = [
// WebSocket
const subscribeSensorData = async (sub) => {
if (!socket || !robotId.value || !deviceId.value) {
console.warn(`订阅失败: socket=${!!socket}, robotId=${robotId.value}, deviceId=${deviceId.value}`);
if (!socket || !terminalId.value || !deviceId.value) {
console.warn(`订阅失败: socket=${!!socket}, terminalId=${terminalId.value}, deviceId=${deviceId.value}`);
return;
}
const channel = `edgeDexHandServiceImpl/getSensorDataStream/${robotId.value}/${deviceId.value}`;
const channel = `edgeDexHandServiceImpl/getSensorDataStream/${terminalId.value}/${deviceId.value}`;
console.log(sub ? '订阅' : '取消订阅', channel);
socket.send({
type: 'channel_subscription',

View File

@ -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 robotId = ref('');
const terminalId = ref('');
const deviceId = ref('');
// 1. props
const props = defineProps({
@ -43,7 +43,7 @@ const props = defineProps({
type: String,
default: null,
},
initialRobotId: { // prop robotId
initialTerminalId: { // prop terminalId
type: String,
default: null,
},
@ -51,9 +51,9 @@ const props = defineProps({
// 2. 使 watchEffect props
watchEffect(() => {
robotId.value = props.initialRobotId;
terminalId.value = props.initialTerminalId;
deviceId.value = props.initialDeviceId;
console.log(deviceId.value, robotId.value,props)
console.log(deviceId.value, terminalId.value,props)
});
const sliders = ref([
@ -66,9 +66,9 @@ const sliders = ref([
]);
function updateSeriesData(value, i) {
console.log(deviceId.value, robotId.value)
console.log(deviceId.value, terminalId.value)
loading.value = true;
setDexHandAngle({ deviceId: deviceId.value, robotId: robotId.value, value: value / 100, id: i }).then(() => {
setDexHandAngle({ deviceId: deviceId.value, terminalId: terminalId.value, value: value / 100, id: i }).then(() => {
loading.value = false;
updateToSeriesData();
}).catch(() => {
@ -131,7 +131,7 @@ const updateSliderPositions = () => {
};
const updateToSeriesData = () => {
status({ deviceId: deviceId.value, robotId: robotId.value }).then((res) => {
status({ deviceId: deviceId.value, terminalId: terminalId.value }).then((res) => {
const newTopSeriesData = res.data.handsList.map(item => item.force);
emit('update:topSeriesData', newTopSeriesData);
});

View File

@ -4,11 +4,11 @@
<div class="left-panel">
<div class="hand-container">
<!-- 确保这里使用的是 index.vue 中响应式的 deviceId ref -->
<LeftTopHand :initialDeviceId="deviceId" :initialRobotId="robotId" @update:topSeriesData="handleUpdateTopSeriesData" />
<LeftTopHand :initialDeviceId="deviceId" :initialTerminalId="terminalId" @update:topSeriesData="handleUpdateTopSeriesData" />
</div>
<div class="hand-container">
<!-- 确保这里使用的是 index.vue 中响应式的 robotId ref -->
<LeftBottomHand :initialDeviceId="deviceId" :initialRobotId="robotId" @update:bottomSeriesData="handleUpdateBottomSeriesData" />
<!-- 确保这里使用的是 index.vue 中响应式的 terminalId ref -->
<LeftBottomHand :initialDeviceId="deviceId" :initialTerminalId="terminalId" @update:bottomSeriesData="handleUpdateBottomSeriesData" />
</div>
</div>
<div class="right-panel">
@ -34,7 +34,7 @@ const props = defineProps({
type: String,
default: null,
},
initialRobotId: {
initialTerminalId: {
type: String,
default: null,
},
@ -62,7 +62,7 @@ const avgData = ref({
// 使
const deviceId = ref("");
const robotId = ref("");
const terminalId = ref("");
const handleUpdateTopSeriesData = (newData) => {
topSeriesData.value = newData;
@ -77,24 +77,19 @@ 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
robotId.value = res.data.idDeDeviceTerminalConfig;
terminalId.value = res.data.idDeDeviceTerminalConfig;
}).catch(error => {
console.error("Error fetching register:", error);
// deviceId robotId
// deviceId terminalId
deviceId.value = "";
robotId.value = "";
terminalId.value = "";
});
} else {
// deviceId robotId
robotId.value = "";
// deviceId terminalId
terminalId.value = "";
}
};
@ -112,11 +107,6 @@ 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;
@ -125,10 +115,10 @@ watch(() => route.path, (newPath) => {
}, { immediate: true }); // immediate: true
// initialRobotId prop robotId ref watch
watch(() => props.initialRobotId, (newVal) => {
// initialTerminalId prop terminalId ref watch
watch(() => props.initialTerminalId, (newVal) => {
if (newVal) {
robotId.value = newVal;
terminalId.value = newVal;
}
}, { immediate: true });
</script>
@ -165,4 +155,4 @@ watch(() => props.initialRobotId, (newVal) => {
width: 100%;
overflow: hidden;
}
</style>
</style>

View File

@ -80,12 +80,10 @@
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
// import { setServo } from '@/api/device/head'; //
const route = useRoute();
const robotId = ref(String(route.query.robotId || ''));
const deviceId = ref(String(route.query.deviceId || ''));
const terminalId = ref('25449ff3fcc7b27da5f69462c5efcec2');
const deviceId = ref('head1');
const servo1 = ref(0.5); // Yaw
const servo2 = ref(0.5); // Pitch
@ -153,7 +151,7 @@ const applyPitchFromPresets = () => {
const sendServoValues = async () => {
try {
// await setServo({
// robotId: robotId.value,
// terminalId: terminalId.value,
// deviceId: deviceId.value,
// servo1: servo1.value,
// servo2: servo2.value,
@ -313,4 +311,4 @@ const points = ref([
color: #409eff;
min-width: 60px;
}
</style>
</style>

View File

@ -66,7 +66,7 @@
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import { ref } from "vue";
import { Microphone, VideoPause, Mute, Mic, VideoPlay } from "@element-plus/icons-vue";
import {
startMicrophoneApi,
@ -77,27 +77,19 @@ import {
} from '@/api/device/microphone'
import { useRoute } from 'vue-router'
import { ElMessage } from "element-plus";
import { getRobotDevicesByRobotId } from '@/api/inspection/robot'
const route = useRoute()
const robotId = route.query.robotId
const terminalId = route.query.terminalId
const deviceId = route.query.deviceId
const recordStatus = ref('end');
const isRecording = ref(false)
const list = []
const audiosList = ref([])
const speaker = ref('')
const speakerOptions = ref([])
onMounted(async () => {
if (!robotId) return
const response = await getRobotDevicesByRobotId(String(robotId), { deviceKind: 13 })
speakerOptions.value = (response.data || []).map(item => item.deviceId)
speaker.value = speakerOptions.value[0] || ''
})
const speaker = ref('spk1')
const speakerOptions = ref(['spk1'])
/**
* 开始录音
@ -106,13 +98,13 @@ const startRecording = async () => {
const fileName = new Date().getTime() + '.wav'
isRecording.value = true
const res = await startMicrophoneApi({
robotId,
terminalId,
deviceId,
filePath: `/home/share/record/audios/${robotId}_${deviceId}_${fileName}`
filePath: `/home/share/record/audios/${terminalId}_${deviceId}_${fileName}`
})
if (res.code === 200) {
recordStatus.value = 'recording'
list.push(`${robotId}_${deviceId}_${fileName}`)
list.push(`${terminalId}_${deviceId}_${fileName}`)
}
};
@ -121,7 +113,7 @@ const startRecording = async () => {
*/
const pauseRecording = async () => {
const res = await pauseMicrophoneApi({
robotId,
terminalId,
deviceId
})
if (res.code === 200) {
@ -134,7 +126,7 @@ const pauseRecording = async () => {
*/
const recoverRecording = async () => {
const res = await resumeMicrophoneApi({
robotId,
terminalId,
deviceId
})
if (res.code === 200) {
@ -147,7 +139,7 @@ const recoverRecording = async () => {
*/
const stopRecording = async () => {
const res = await stopMicrophoneApi({
robotId,
terminalId,
deviceId
})
if (res.code === 200) {
@ -162,7 +154,7 @@ const stopRecording = async () => {
*/
const handlePlay = async (audioPath) => {
const res = await playSpeaker({
robotId,
terminalId,
deviceId: speaker.value,
audioPath: `/home/share/record/audios/${audioPath}`
})

View File

@ -52,7 +52,6 @@
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { useRoute } from 'vue-router';
import {
pause,
play,
@ -74,12 +73,11 @@ const currentIndex = ref(0);
const isPlaying = ref(false);
const volume = ref(0);
const route = useRoute();
const robotId = ref(String(route.query.robotId || ''));
const deviceId = ref(String(route.query.deviceId || ''));
const terminalId = ref('25449ff3fcc7b27da5f69462c5efcec2');
const deviceId = ref('spk1');
const getCommonParams = () => ({
robotId: robotId.value,
terminalId: terminalId.value,
deviceId: deviceId.value
});
@ -275,4 +273,4 @@ input[type="range"] {
fill: #ddd;
}
</style>
</style>

View File

@ -1,184 +1,414 @@
<template>
<div class="app-container robot-device-page">
<section class="page-heading">
<div>
<h2>机器人设备</h2>
<p>设备由机器人在线上报切换机器人后自动刷新</p>
</div>
<div class="robot-selector">
<span>当前机器人</span>
<el-select v-model="selectedRobotId" filterable placeholder="请选择机器人"
:loading="robotLoading" @change="loadDevices">
<el-option v-for="robot in robotOptions" :key="robot.value"
:label="robot.label" :value="robot.value" />
</el-select>
<el-button :icon="Refresh" circle title="刷新设备" :loading="loading" @click="loadDevices" />
</div>
</section>
<div class="app-container">
<div ref="topContainerRef">
<TableSearch
:queryParams="queryParams"
:showSearch="showSearch"
label-width="80px"
queryRef="queryRef"
@refresh="resetQuery"
@search="handleQuery"
>
<template #one>
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24" :xxl="6">
<el-form-item label="设备编号" prop="deviceCode">
<el-input
v-model="queryParams.deviceCode"
clearable
placeholder="请输入设备编号"
style="width:100%"
@keyup.enter="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24" :xxl="6">
<el-form-item label="设备名称" prop="deviceName">
<el-input
v-model="queryParams.deviceName"
clearable
placeholder="请输入设备名称"
style="width:100%"
@keyup.enter="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24" :xxl="6">
<el-form-item label="设备类型" prop="deviceModel">
<el-select
v-model="queryParams.deviceModel"
placeholder="请输入设备类型"
>
<el-option
v-for="item in controlModuleList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</template>
</TableSearch>
<section class="status-strip">
<div><strong>{{ devices.length }}</strong><span>设备总数</span></div>
<div><strong class="online">{{ onlineCount }}</strong><span>在线设备</span></div>
<div><strong class="fault">{{ faultCount }}</strong><span>故障设备</span></div>
<div class="filters">
<el-input v-model="keyword" clearable :prefix-icon="Search" placeholder="搜索名称或设备 ID" />
<el-select v-model="deviceKind" clearable placeholder="全部类型">
<el-option v-for="item in deviceKinds" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
</div>
</section>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
v-hasPermi="['device:register:add']"
icon="Plus"
plain
type="primary"
@click="handleAdd"
>新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['device:register:edit']"
:disabled="single"
icon="Edit"
plain
type="success"
@click="handleUpdate"
>修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['device:register:remove']"
:disabled="multiple"
icon="Delete"
plain
type="danger"
@click="handleDelete"
>删除
</el-button>
</el-col>
<!-- <el-col :span="1.5">-->
<!-- <el-button-->
<!-- type="warning"-->
<!-- plain-->
<!-- icon="Download"-->
<!-- @click="handleExport"-->
<!-- v-hasPermi="['device:register:export']"-->
<!-- >导出-->
<!-- </el-button>-->
<!-- </el-col>-->
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</div>
<el-table v-loading="loading" :data="filteredDevices" height="calc(100vh - 285px)"
empty-text="当前机器人尚未上报设备">
<el-table-column label="设备" min-width="220">
<template #default="{ row }">
<div class="device-name">
<span class="device-icon"><el-icon><Cpu /></el-icon></span>
<div><b>{{ row.deviceName || row.deviceId }}</b><small>{{ row.deviceId }}</small></div>
</div>
<div :style="containerHeight">
<el-table height="100%" v-loading="loading" :data="registerList" @selection-change="handleSelectionChange">
<el-table-column align="center" type="selection" width="55" />
<el-table-column align="center" label="id" prop="id" show-overflow-tooltip/>
<el-table-column align="center" label="设备终端配置id" prop="idDeDeviceTerminalConfig" show-overflow-tooltip
width="120"/>
<el-table-column align="center" label="设备编号" prop="deviceCode" show-overflow-tooltip/>
<el-table-column align="center" label="设备名称" prop="deviceName" show-overflow-tooltip/>
<!-- <el-table-column label="设备规格" align="center" prop="deviceSpec"/>-->
<el-table-column align="center" label="设备类型" prop="deviceModel">
<template #default="scope">
{{ controlModuleList.find(item => item.value === scope.row.deviceModel)?.label }}
</template>
</el-table-column>
<el-table-column label="类型" width="130">
<template #default="{ row }">{{ kindLabel(row.deviceKind, row.typeName) }}</template>
</el-table-column>
<el-table-column label="运行状态" width="120" align="center">
<template #default="{ row }">
<el-tag :type="row.onlineStatus === '1' ? 'success' : 'info'" effect="light">
{{ row.onlineStatus === '1' ? '在线' : '离线' }}
</el-tag>
<!-- <el-table-column label="设备厂家" align="center" prop="factory"/>-->
<!-- <el-table-column label="设备图片" align="center" prop="images"/>-->
<!-- <el-table-column show-overflow-tooltip label="备注" align="center" prop="remake"/>-->
<el-table-column align="center" label="设备状态" prop="status">
<template #default="scope">
<el-switch
v-model="scope.row.status"
active-text="启用"
active-value="0"
inactive-text="停用"
inactive-value="1"
inline-prompt
@change="handleChange(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="健康状态" width="120" align="center">
<template #default="{ row }">
<el-tag :type="isFault(row) ? 'danger' : 'success'" effect="plain">
{{ isFault(row) ? '故障' : '正常' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="errorMessage" label="状态信息" min-width="180" show-overflow-tooltip>
<template #default="{ row }">{{ row.errorMessage || '运行正常' }}</template>
</el-table-column>
<el-table-column prop="lastSeenTime" label="最后上报" width="180" />
<el-table-column label="操作" width="100" fixed="right" align="center">
<template #default="{ row }">
<el-button v-if="controlRoute(row.deviceKind)" link type="primary"
:disabled="row.onlineStatus !== '1'" @click="openControl(row)">示教</el-button>
<span v-else class="muted">-</span>
<el-table-column align="center" class-name="small-padding fixed-width" label="操作" width="200">
<template #default="scope">
<el-button v-hasPermi="['device:register:edit']" icon="Edit" link type="primary"
@click="handleUpdate(scope.row)">修改
</el-button>
<el-button v-hasPermi="['device:register:deploy']" icon="Cpu" link type="primary"
@click="handleControl(scope.row)">示教
</el-button>
<el-button v-hasPermi="['device:register:remove']" icon="Delete" link type="primary"
@click="handleDelete(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNum"
:total="total"
@pagination="getList"
/>
</div>
<!-- 添加或修改设备注册对话框 -->
<el-dialog v-model="open" :title="title" append-to-body width="500px">
<el-form ref="registerRef" :model="form" :rules="rules" label-width="110px">
<el-form-item label="设备终端配置id" prop="idDeDeviceTerminalConfig">
<el-input v-model="form.idDeDeviceTerminalConfig"
placeholder="请输入设备终端配置id"/>
<!-- <el-input v-model="form.idDeDeviceTerminalConfig" :disabled="form.idDeDeviceTerminalConfig!== ''"
placeholder="请输入设备终端配置id"/> -->
</el-form-item>
<el-form-item label="设备编号" prop="deviceCode">
<el-input v-model="form.deviceCode" placeholder="请输入设备编号"/>
<!-- <el-input v-model="form.deviceCode" :disabled="form.deviceCode!== ''" placeholder="请输入设备编号"/> -->
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="form.deviceName" placeholder="请输入设备名称"/>
</el-form-item>
<!-- <el-form-item label="设备规格" prop="deviceSpec">-->
<!-- <el-input v-model="form.deviceSpec" placeholder="请输入设备规格"/>-->
<!-- </el-form-item>-->
<el-form-item label="设备类型" prop="deviceModel">
<el-select
v-model="form.deviceModel"
placeholder="请输入设备类型"
>
<el-option
v-for="item in controlModuleList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="所属分组">
<el-select v-model="form.groupId" placeholder="请选择所属分组">
<el-option v-for="group in groupList" :key="group.value" :label="group.label"
:value="group.value"></el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="设备厂家" prop="factory">-->
<!-- <el-input v-model="form.factory" placeholder="请输入设备厂家"/>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="设备图片" prop="images">-->
<!-- <el-input v-model="form.images" placeholder="请输入设备图片"/>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="备注" prop="remake">-->
<!-- <el-input :rows="2"-->
<!-- type="textarea" v-model="form.remake" placeholder="请输入备注"/>-->
<!-- </el-form-item>-->
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { Cpu, Refresh, Search } from '@element-plus/icons-vue'
import { getRobotList, getRobotDevicesByRobotId } from '@/api/inspection/robot'
<script name="Register" setup>
import {addRegister, delRegister, getRegister, listRegister, updateRegister} from "@/api/device/register"
import TableSearch from "@/components/TableSearch/index.vue"
import {useRouter} from "vue-router"
import {intoControlPage} from "@/views/device/register/components/index.js";
import {controlModuleList} from "@/enum/index.js";
import {listGroup} from "@/api/system/group.js";
import useTagsViewStore from '@/store/modules/tagsView'
import { useContainerHeight } from "@/hooks/tableHeight";
const topContainerRef = ref();
const containerHeight = useContainerHeight(topContainerRef);
const router = useRouter()
const robotLoading = ref(false)
const loading = ref(false)
const selectedRobotId = ref('')
const robotOptions = ref([])
const devices = ref([])
const keyword = ref('')
const deviceKind = ref()
const deviceKinds = [
{ value: 1, label: '移动底盘' }, { value: 2, label: '机械臂' },
{ value: 3, label: '电池' }, { value: 4, label: '仿生头' },
{ value: 5, label: '摄像头' }, { value: 6, label: 'CAN 总线' },
{ value: 7, label: '灵巧手' }, { value: 8, label: '夹爪' },
{ value: 9, label: '麦克风' }, { value: 10, label: '电机' },
{ value: 11, label: '电机系统' }, { value: 12, label: '机器人控制器' },
{ value: 13, label: '扬声器' }
]
const kindLabel = (kind, fallback) => deviceKinds.find(item => item.value === kind)?.label || fallback || '未知设备'
const isFault = (device) => device.hasError === '1' || Number(device.healthStatus) === 3
const onlineCount = computed(() => devices.value.filter(item => item.onlineStatus === '1').length)
const faultCount = computed(() => devices.value.filter(isFault).length)
const filteredDevices = computed(() => {
const search = keyword.value.trim().toLowerCase()
return devices.value.filter(item => {
const matchesKind = deviceKind.value == null || item.deviceKind === deviceKind.value
const matchesSearch = !search || `${item.deviceName || ''} ${item.deviceId || ''}`.toLowerCase().includes(search)
return matchesKind && matchesSearch
})
const {proxy} = getCurrentInstance()
const registerList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref("")
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
deviceCode: null,
deviceName: null,
deviceSpec: null,
deviceModel: null,
factory: null,
status: null
},
rules: {}
})
const loadRobots = async () => {
robotLoading.value = true
try {
const response = await getRobotList({ pageNum: 1, pageSize: 1000 })
robotOptions.value = (response.rows || []).map(robot => ({
value: robot.robotId,
label: `${robot.robotName || robot.robotId} (${robot.robotId})`,
online: robot.connectStatus === '1'
})).filter(item => item.value)
const preferred = robotOptions.value.find(item => item.online) || robotOptions.value[0]
selectedRobotId.value = preferred?.value || ''
} finally {
robotLoading.value = false
}
}
const {queryParams, form, rules} = toRefs(data)
const loadDevices = async () => {
devices.value = []
if (!selectedRobotId.value) return
/** 查询设备注册列表 */
function getList() {
loading.value = true
try {
const response = await getRobotDevicesByRobotId(selectedRobotId.value, { onlineOnly: false })
devices.value = Array.isArray(response.data) ? response.data : []
} finally {
listRegister(queryParams.value).then(response => {
registerList.value = response.rows
total.value = response.total
loading.value = false
}
}
const controlRoute = (kind) => ({
2: 'mechanical_arm', 4: 'head', 5: 'camera', 7: 'hand', 9: 'microphone', 13: 'speaker'
})[kind]
const openControl = (device) => {
router.push({
path: `/device/${controlRoute(device.deviceKind)}/${device.id}`,
query: { robotId: selectedRobotId.value, deviceId: device.deviceId }
})
}
onMounted(async () => {
await loadRobots()
await loadDevices()
})
</script>
<style lang="scss" scoped>
.robot-device-page { min-height: 100%; background: #f5f7fa; }
.page-heading { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 4px 0 20px; }
.page-heading h2 { margin: 0 0 5px; color: #1f2937; font-size: 22px; letter-spacing: 0; }
.page-heading p { margin: 0; color: #8492a6; font-size: 13px; }
.robot-selector { display: flex; align-items: center; gap: 10px; color: #606266; font-size: 13px; }
.robot-selector .el-select { width: 330px; }
.status-strip { display: flex; align-items: center; gap: 34px; margin-bottom: 14px; padding: 14px 18px; border: 1px solid #e4e7ed; border-radius: 6px; background: #fff; }
.status-strip > div:not(.filters) { display: grid; min-width: 74px; }
.status-strip strong { color: #303133; font-size: 24px; line-height: 28px; }
.status-strip strong.online { color: #16a34a; }
.status-strip strong.fault { color: #dc2626; }
.status-strip span { color: #909399; font-size: 12px; }
.filters { display: flex; flex: 1; justify-content: flex-end; gap: 10px; }
.filters .el-input { width: 240px; }
.filters .el-select { width: 150px; }
.device-name { display: flex; align-items: center; gap: 12px; }
.device-icon { display: grid; width: 34px; height: 34px; place-items: center; border-radius: 6px; background: #ecf5ff; color: #409eff; }
.device-name div { display: grid; min-width: 0; }
.device-name b { color: #303133; font-weight: 600; }
.device-name small { overflow: hidden; color: #909399; text-overflow: ellipsis; }
.muted { color: #c0c4cc; }
@media (max-width: 900px) {
.page-heading, .status-strip { align-items: stretch; flex-direction: column; }
.robot-selector, .filters { justify-content: flex-start; flex-wrap: wrap; }
.robot-selector .el-select, .filters .el-input { width: 100%; }
//
function cancel() {
open.value = false
reset()
}
</style>
//
function reset() {
form.value = {
// id: null,
// createBy: null,
// createTime: null,
// updateBy: null,
// updateTime: null,
// remake: null,
// idDeDeviceTerminalConfig: null,
// deviceCode: null,
// deviceName: null,
// deviceSpec: null,
// deviceModel: null,
// factory: null,
// images: null,
// status: null
}
proxy.resetForm("registerRef")
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef")
handleQuery()
}
//
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.id)
single.value = selection.length != 1
multiple.value = !selection.length
}
/** 新增按钮操作 */
function handleAdd() {
reset()
open.value = true
title.value = "添加设备注册"
}
/** 修改按钮操作 */
function handleUpdate(row) {
reset()
const _id = row.id || ids.value
getRegister(_id).then(response => {
form.value = response.data
open.value = true
title.value = "修改设备注册"
})
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["registerRef"].validate(valid => {
if (valid) {
if (form.value.id != null) {
updateRegister(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
open.value = false
getList()
})
} else {
addRegister(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功")
open.value = false
getList()
})
}
}
})
}
/** 删除按钮操作 */
function handleDelete(row) {
const _ids = row.id || ids.value
proxy.$modal.confirm('是否确认删除设备注册编号为"' + _ids + '"的数据项?').then(function () {
return delRegister(_ids)
}).then(() => {
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {
})
}
/** 导出按钮操作 */
function handleExport() {
proxy.download('device/register/export', {
...queryParams.value
}, `register_${new Date().getTime()}.xlsx`)
}
/** 控制按钮操作 */
function handleControl(row) {
const fullPath = `${row.deviceModel}/${row.id}`; //
router.push({
path: fullPath,
query: {
terminalId: row.idDeDeviceTerminalConfig,
deviceId: row.deviceCode
}
}); // 使
// intoControlPage(`/${row.deviceModel}`, `${row.id}`)
}
function handleChange(row) {
updateRegister({status: row.status, id: row.id}).then(response => {
proxy.$modal.msgSuccess("修改成功")
getList()
})
}
getList()
const groupList = ref([])
/** 获取系统分组列表(默认传1查询检测项列表) */
function handleGetSystemGroup(row) {
listGroup({type: 3}).then(response => {
groupList.value = response.rows?.map((item) => {
return {
label: item.groupName,
value: String(item.groupId)
}
})
})
}
handleGetSystemGroup()
</script>

View File

@ -61,7 +61,7 @@
<span class="control-icon close-icon" @click="closePopup(index)">×</span>
</div>
</div>
<component :is="popup.component" v-bind="popup.props" :initialDeviceId = "popup.deviceId" :initialRobotId = "popup.configId" />
<component :is="popup.component" v-bind="popup.props" :initialDeviceId = "popup.deviceId" :initialTerminalId = "popup.configId" />
<div class="resize-edge top" @mousedown="startResizing($event, index, 'top')"></div>
<div class="resize-edge bottom" @mousedown="startResizing($event, index, 'bottom')"></div>
<div class="resize-edge left" @mousedown="startResizing($event, index, 'left')"></div>
@ -398,7 +398,7 @@ const openPopup = (area) => {
originalTop: `${area.y}px`,
originalLeft: `${area.x + leftOffset}px`,
isMaximized: false,
props: { robotId: area.robotId || '', cameraId: area.deviceId || '' }
props: { terminalId: 'your-terminal-id', cameraId: 'your-camera-id' } // props
};
popups.value.push(popup);
isResizing.value.push(false);
@ -871,4 +871,4 @@ onMounted(() => {
flex: 1;
overflow: auto;
}
</style>
</style>

View File

@ -11,18 +11,6 @@
:rules="rules"
label-width="auto"
>
<div class="binding-panel" :class="{ 'binding-panel--warning': bindingSummary.missing > 0 }">
<div class="binding-panel__title">
<span class="binding-panel__dot"></span>
机器人设备自动匹配
</div>
<div class="binding-panel__text" v-if="bindingLoading">正在读取机器人在线设备...</div>
<div class="binding-panel__text" v-else-if="bindingSummary.robotId">
已匹配 {{ bindingSummary.matched }} 个设备节点
<span v-if="bindingSummary.missing">{{ bindingSummary.missing }} 个节点暂无匹配设备</span>
</div>
<div class="binding-panel__text" v-else>选择机器人后将按节点类型自动填充该机器人上报的在线设备</div>
</div>
<el-table
:data="tableData"
style="width: 100%; margin-bottom: 20px"
@ -42,10 +30,10 @@
</el-table-column>
<el-table-column prop="value" label="入参值">
<template #default="{ row, column, $index }">
<el-form-item v-if="row.name === 'robotId'" label="" :prop="`tableData${row.propPath}value`" :rules="row.required ? [{ required: true, message: '请选择执行机器人', trigger: 'change' }] : []">
<el-select v-model="row.value" filterable style="width: 100%" @change="handleRobotChange">
<el-form-item v-if="row.name === 'terminalId' || row.name === 'robotId'" label="" :prop="`tableData${row.propPath}value`" :rules="row.required ? [{ required: true, message: '请选择执行机器人', trigger: 'change' }] : []">
<el-select v-model="row.value" style="width: 200px">
<el-option
v-for="item in robotIdOptions"
v-for="item in terminalIdOptions"
:key="item.value"
:label="item.label"
:value="item.value"
@ -67,7 +55,7 @@
</el-form>
<template #footer>
<el-button @click="handleClose">关闭</el-button>
<el-button type="primary" :loading="bindingLoading" :disabled="bindingLoading" @click="confirm">确定</el-button>
<el-button type="primary" @click="confirm">确定</el-button>
</template>
</el-drawer>
</template>
@ -78,8 +66,7 @@ import { getStartNodeFormData, formatTableData } from '@/utils/flow'
import { flowExecuteTrial } from '@/api/flow/flow'
import { emitter } from '@/utils/eventBus';
import { ElMessage } from 'element-plus';
import { getRobotList, getRobotDevicesByRobotId } from '@/api/inspection/robot'
import { getDeviceKindForAction, toDeviceOptions } from '@/utils/robotDevice'
import { getRobotList } from '@/api/inspection/robot'
const props = defineProps({
drawer: Boolean,
@ -89,8 +76,6 @@ 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'])
@ -104,8 +89,7 @@ watch(() => props.drawer,
if (newVal) {
const data = getStartNodeFormData()
tableData.value = data
bindingSummary.value = { robotId: '', matched: 0, missing: 0 }
loadRobotOptions()
handleGetTerminalGroup()
}
}
)
@ -120,11 +104,11 @@ const confirm = () => {
})
const flowData = JSON.stringify(lf.getGraphData())
const { robotId, ...runParams } = formData
const { terminalId, robotId, ...runParams } = formData
const res = await flowExecuteTrial({
flowData,
itemId: props.flowId,
robotId,
robotId: robotId || terminalId,
runParams
})
if (res.code === 200) {
@ -139,69 +123,13 @@ const confirm = () => {
.catch(() => {})
}
const robotIdOptions = ref([])
const terminalIdOptions = ref([])
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 handleGetTerminalGroup = async (row) => {
const res = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' })
if (res.code === 200) {
robotIdOptions.value = res.rows?.map((item) => {
terminalIdOptions.value = res.rows?.map((item) => {
return {
label: `${item.robotName || item.robotId} (${item.robotId})`,
value: item.robotId
@ -211,40 +139,3 @@ const loadRobotOptions = async () => {
}
</script>
<style lang="scss" scoped>
.binding-panel {
margin-bottom: 16px;
padding: 12px 14px;
border: 1px solid #d7e5f7;
border-radius: 6px;
background: #f5f9ff;
}
.binding-panel--warning {
border-color: #f3d19e;
background: #fdf6ec;
}
.binding-panel__title {
display: flex;
align-items: center;
gap: 8px;
color: #24364b;
font-weight: 600;
}
.binding-panel__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #409eff;
}
.binding-panel__text {
margin-top: 6px;
color: #64748b;
font-size: 13px;
line-height: 20px;
}
</style>

View File

@ -11,7 +11,7 @@
</el-button>
</template>
<div class="form__container">
<el-button type="primary" v-if="hasBindableDevice" size="small" @click="openRobotForm(props.data.properties.action)">{{ robotActionButtonText }}</el-button>
<el-button type="primary" v-if="['AGV_MOVE_TO_POINT', 'AGV_MOVE_TO_STATION', 'ARM_MOVE_TO_POINT', 'ARM_MOVE_TO_J'].includes(props.data.properties.action)" size="small" @click="openRobotForm(props.data.properties.action)">{{ robotActionButtonText }}</el-button>
<el-form :inline="true" :model="formData" :rules="rules" ref="dynamicFormRef" label-position="top"
label-width="auto">
<div v-for="(property, index) in formData.nodeParams" :key="index">
@ -82,25 +82,15 @@
<el-dialog
v-model="dialogVisible"
:title="robotDialogTitle"
width="460"
:title="robotFormAction === 'AGV_MOVE_TO_STATION' ? '加载AGV站点列表' : '获取位置信息'"
width="360"
>
<div class="robot-device-tip">设备来自机器人最近一次在线上报单个匹配设备会自动锁定</div>
<el-form ref="robotFormRef" :model="robotForm" :rules="robotFormRules" label-width="90px">
<el-form-item label="机器人" prop="robotId">
<el-select v-model="robotForm.robotId" filterable placeholder="请选择在线机器人"
style="width: 100%" @change="loadRobotDevices">
<el-option v-for="item in robotOptions" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
<el-form ref="robotFormRef" :model="robotForm" :rules="robotFormRules" label-width="auto">
<el-form-item label="终端ID" prop="terminalId">
<el-input v-model="robotForm.terminalId" />
</el-form-item>
<el-form-item label="执行设备" prop="deviceId">
<el-select v-model="robotForm.deviceId" filterable :loading="deviceLoading"
:disabled="deviceOptions.length === 1" placeholder="请先选择机器人" style="width: 100%">
<el-option v-for="item in deviceOptions" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
<div v-if="deviceOptions.length === 1" class="device-auto-hint">已自动匹配并锁定</div>
<el-form-item label="设备ID" prop="deviceId">
<el-input v-model="robotForm.deviceId" />
</el-form-item>
</el-form>
<template #footer>
@ -117,9 +107,7 @@ import FormItemRecursive from '../FormItemRecursive.vue'
import { getInput } from '@/utils/flow'
import { useQuote } from './useQuote.js'
import { getArmStatus, getArmJointState, getAgvStatus, getAgvStations } from '@/api/flow/flow'
import { getRobotList, getRobotDevicesByRobotId } from '@/api/inspection/robot'
import { ElMessage } from 'element-plus'
import { getDeviceKindForAction, toDeviceOptions } from '@/utils/robotDevice'
const props = defineProps({
data: Object
@ -213,42 +201,13 @@ const validateAndSave = async () => {
const dialogVisible = ref(false)
const robotFormRef = ref(null)
const robotFormRules = reactive({
robotId: [{ required: true, message: '请选择机器人', trigger: 'change' }],
terminalId: [{ required: true, message: '终端ID不能为空', trigger: 'blur' }],
deviceId: [{ required: true, message: '设备ID不能为空', trigger: 'blur' }]
})
const robotForm = reactive({
deviceId: '',
robotId: ''
terminalId: ''
})
const robotOptions = ref([])
const deviceOptions = ref([])
const deviceLoading = ref(false)
const loadRobotOptions = async () => {
const response = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' })
robotOptions.value = (response.rows || []).map(robot => ({
value: robot.robotId,
label: `${robot.robotName || robot.robotId} (${robot.robotId})`
}))
}
const loadRobotDevices = async (robotId) => {
robotForm.deviceId = ''
deviceOptions.value = []
if (!robotId) return
deviceLoading.value = true
try {
const response = await getRobotDevicesByRobotId(robotId, {
deviceKind: getDeviceKindForAction(robotFormAction.value)
})
const devices = Array.isArray(response.data) ? response.data : []
deviceOptions.value = toDeviceOptions(devices)
if (devices.length > 0) robotForm.deviceId = devices[0].deviceId
if (devices.length === 0) ElMessage.warning('当前机器人没有匹配的在线设备')
} finally {
deviceLoading.value = false
}
}
const testRule = (property) => {
if (property?.name === 'stationId' && props.data.properties.action === 'AGV_MOVE_TO_STATION') {
@ -295,28 +254,16 @@ const testRule = (property) => {
const robotFormAction = ref()
const stationListLoading = ref(false)
const POSITION_ACTIONS = ['AGV_MOVE_TO_POINT', 'ARM_MOVE_TO_POINT', 'ARM_MOVE_TO_J']
const hasBindableDevice = computed(() =>
formData.nodeParams.some(param => param.name === 'deviceId')
&& Boolean(getDeviceKindForAction(props.data.properties.action))
const robotActionButtonText = computed(() =>
props.data.properties.action === 'AGV_MOVE_TO_STATION' ? '加载站点列表' : '获取位置'
)
const robotActionButtonText = computed(() => {
if (props.data.properties.action === 'AGV_MOVE_TO_STATION') return '选择机器人并加载站点'
if (POSITION_ACTIONS.includes(props.data.properties.action)) return '选择机器人并获取位置'
return '选择机器人设备'
})
const robotDialogTitle = computed(() => {
if (robotFormAction.value === 'AGV_MOVE_TO_STATION') return '选择AGV并加载站点'
if (POSITION_ACTIONS.includes(robotFormAction.value)) return '选择机器人并读取当前位置'
return '选择机器人设备'
})
const submitRobotForm = () => {
if (robotFormAction.value === 'AGV_MOVE_TO_POINT') {
robotFormRef.value.validate(async (valid) => {
if (valid) {
try {
const response = await getAgvStatus({ deviceId: robotForm.deviceId, robotId: robotForm.robotId })
const response = await getAgvStatus({ deviceId: robotForm.deviceId, terminalId: robotForm.terminalId })
if (response && response.data) {
dialogVisible.value = false
const { x, y, theta } = response.data.pose
@ -345,7 +292,7 @@ const submitRobotForm = () => {
try {
const response = await getAgvStations({
deviceId: robotForm.deviceId,
robotId: robotForm.robotId
terminalId: robotForm.terminalId
})
const stationOptions = toStationOptions(response?.data)
if (stationOptions.length === 0) {
@ -375,7 +322,7 @@ const submitRobotForm = () => {
robotFormRef.value.validate(async (valid) => {
if (valid) {
try {
const response = await getArmStatus({ deviceId: robotForm.deviceId, robotId: robotForm.robotId })
const response = await getArmStatus({ deviceId: robotForm.deviceId, terminalId: robotForm.terminalId })
if (response && response.data) {
dialogVisible.value = false
const { x, y, z, rx, ry, rz } = response.data
@ -406,7 +353,7 @@ const submitRobotForm = () => {
try {
const response = await getArmJointState({
deviceId: robotForm.deviceId,
robotId: robotForm.robotId
terminalId: robotForm.terminalId
})
if (response && response.data) {
const target = response.data.position || response.data.positions
@ -424,19 +371,6 @@ const submitRobotForm = () => {
}
}
})
} else {
robotFormRef.value.validate((valid) => {
if (!valid) return
const deviceParam = formData.nodeParams.find(param => param.name === 'deviceId')
if (deviceParam) {
deviceParam.input = robotForm.deviceId
deviceParam.componentType = 'select'
deviceParam.selectOptions = [...deviceOptions.value]
deviceParam.deviceLocked = deviceOptions.value.length === 1
}
dialogVisible.value = false
ElMessage.success('机器人设备已匹配')
})
}
}
@ -451,13 +385,9 @@ const toStationOptions = (stations) => (Array.isArray(stations) ? stations : [])
}
})
const openRobotForm = async (action) => {
const openRobotForm = (action) => {
dialogVisible.value = true
robotFormAction.value = action
const currentDeviceId = formData.nodeParams.find(param => param.name === 'deviceId')?.input || ''
robotForm.deviceId = currentDeviceId
await loadRobotOptions()
if (robotForm.robotId) await loadRobotDevices(robotForm.robotId)
}
defineExpose({ validateAndSave })
@ -537,19 +467,4 @@ defineExpose({ validateAndSave })
cursor: pointer;
}
}
.robot-device-tip {
margin: -4px 0 18px;
padding: 10px 12px;
border-left: 3px solid var(--el-color-primary);
background: var(--el-fill-color-light);
color: var(--el-text-color-regular);
font-size: 13px;
line-height: 20px;
}
.device-auto-hint {
color: var(--el-color-success);
font-size: 12px;
}
</style>

View File

@ -14,6 +14,29 @@ import audioSvg from './icon/audio.svg'
import touchSvg from './icon/touch.svg'
import expressionSvg from './icon/expression.svg'
const deviceOptions = () => {
return [{
value: 'cam1',
label: 'cam1'
}, {
value: 'cam2',
label: 'cam2'
}, {
value: 'cam3',
label: 'cam3'
}, {
value: 'cam4',
label: 'cam4'
}]
}
const audioOptions = () => {
return [{
value: 'spk1',
label: 'spk1'
}]
}
const booleanOptions = () => [
{ value: false, label: '否' },
{ value: true, label: '是' }
@ -96,7 +119,7 @@ export const collapseList = [
action: 'CAMERA_START',
nodeType: 'EDGE',
nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
],
outputParams: [],
outputType: 'json'
@ -109,7 +132,7 @@ export const collapseList = [
action: 'CAMERA_GETRGBIMAGE',
nodeType: 'EDGE',
nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
],
outputType: 'img',
outputParams: [{ name: 'imageUrl', type: 'array<string>', desc: '图片地址数组', children: [], disabled: true }]
@ -122,7 +145,7 @@ export const collapseList = [
action: 'CAMERA_STOP',
nodeType: 'EDGE',
nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
],
outputParams: [],
outputType: 'json'
@ -140,7 +163,7 @@ export const collapseList = [
action: 'CAMERA_RECORDING_START',
nodeType: 'EDGE',
nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
],
outputParams: [],
outputType: 'json'
@ -153,7 +176,7 @@ export const collapseList = [
action: 'CAMERA_RECORDING_STOP',
nodeType: 'EDGE',
nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
],
outputType: 'video',
outputParams: [{ name: 'videoUrl', type: 'array<string>', children: [], desc: '视频播放地址数组', disabled: true}]
@ -377,7 +400,7 @@ function handler(params) {
action: 'INSPECTION_ALERT_LISTEN_START',
nodeType: 'EDGE',
nodeParams: [
{ name: "robotId", type: "input", input: "", disabled: true },
{ name: "terminalId", type: "input", input: "", disabled: true },
{ name: "eventTypes", type: "input", input: "", componentType: 'select', selectOptions: inspectionEventTypes(), disabled: true }
],
outputParams: [],
@ -391,7 +414,7 @@ function handler(params) {
action: 'INSPECTION_ALERT_LISTEN_STOP',
nodeType: 'EDGE',
nodeParams: [
{ name: "robotId", type: "input", input: "", disabled: true },
{ name: "terminalId", type: "input", input: "", disabled: true },
{ name: "eventTypes", type: "input", input: "", componentType: 'select', selectOptions: inspectionEventTypes(), disabled: true }
],
outputParams: [],
@ -442,7 +465,7 @@ function handler(params) {
action: 'MICROPHONE_START',
nodeType: 'EDGE',
nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: audioOptions(), disabled: true }
],
outputType: 'json'
},
@ -454,7 +477,7 @@ function handler(params) {
action: 'MICROPHONE_STOP',
nodeType: 'EDGE',
nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: audioOptions(), disabled: true }
],
outputType: 'json'
},
@ -465,7 +488,7 @@ function handler(params) {
desc: "用于播放音频的节点",
action: 'SPEAKER_PLAYAUDIO',
nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: audioOptions(), disabled: true }
],
outputType: 'json'
},

View File

@ -155,7 +155,7 @@
/>
</div>
<el-dialog v-model="visible" width="640" class="single-node-dialog" append-to-body>
<el-dialog v-model="visible" width="500" append-to-body>
<template #header="{ close, titleId, titleClass }">
<div class="my-header">
<h4 :id="titleId" :class="titleClass">执行{{ nodeName }}节点</h4>
@ -181,9 +181,8 @@
:disabled="flowStore.disableForm"
>
<div v-for="(property, index) in formData.nodeParams" :key="index">
<el-row class="single-node-param-row">
<el-row>
<el-form-item
class="single-node-param-name"
:label="index === 0 ? '参数名' : ''"
:prop="`nodeParams.${index}.name`"
:rules="[{ required: true, message: '请输入参数名', trigger: 'blur' }]"
@ -196,7 +195,6 @@
/>
</el-form-item>
<el-form-item
class="single-node-param-value"
:label="index === 0 ? '参数值' : ''"
:prop="`nodeParams.${index}.input`"
:rules="[{ required: true, message: '请输入参数值', trigger: 'blur' }]"
@ -214,8 +212,6 @@
<el-select
v-model="property.input"
v-else-if="property.componentType === 'select'"
:disabled="property.deviceLocked"
@change="property.name === 'robotId' && handleSingleNodeRobotChange(property.input)"
>
<el-option
v-for="item in property.selectOptions"
@ -229,13 +225,9 @@
v-model="property.input"
autosize
type="textarea"
resize="none"
placeholder="请输入"
clearable
/>
<div v-if="property.name === 'deviceId' && property.deviceLocked" class="device-lock-hint">
已自动匹配当前机器人设备
</div>
</el-form-item>
</el-row>
</div>
@ -277,8 +269,7 @@ import {
getAgvStations,
} from "@/api/flow/flow";
import { getDetect } from "@/api/test/detect";
import { getRobotList, getRobotDevicesByRobotId } from '@/api/inspection/robot'
import { getDeviceKindForAction, toDeviceOptions } from '@/utils/robotDevice'
import { getRobotList } from '@/api/inspection/robot'
import {
CircleClose,
CollectionTag,
@ -456,6 +447,7 @@ const handleEdgeClick = ({ event, edge }) => {
const handlePaneClick = () => lf.clearSelectElements();
const flowStore = useFlowStore();
flowStore.getDeviceList();
const isOpen = ref(false);
const stateMap = {
@ -907,37 +899,6 @@ const singleNodeDeviceId = computed(() =>
formData.nodeParams.find((item) => item.name === "deviceId")?.input
);
const handleSingleNodeRobotChange = async (robotId) => {
const deviceParam = formData.nodeParams.find((item) => item.name === "deviceId");
if (!deviceParam) return;
deviceParam.input = "";
deviceParam.deviceLocked = false;
deviceParam.componentType = "select";
deviceParam.selectOptions = [];
const stationParam = formData.nodeParams.find((item) => item.name === "stationId");
if (stationParam) {
stationParam.input = "";
stationParam.selectOptions = [];
}
if (!robotId) return;
try {
const deviceKind = getDeviceKindForAction(nodeAction.value);
const response = await getRobotDevicesByRobotId(robotId,
deviceKind ? { deviceKind } : {});
const devices = Array.isArray(response.data) ? response.data : [];
deviceParam.selectOptions = toDeviceOptions(devices);
if (devices.length > 0) {
deviceParam.input = devices[0].deviceId;
deviceParam.deviceLocked = devices.length === 1;
} else {
ElMessage.warning("当前机器人没有匹配的在线设备");
}
} catch (error) {
console.error("加载机器人设备失败:", error);
ElMessage.error("加载机器人设备失败");
}
};
const stationOptions = (stations) => (Array.isArray(stations) ? stations : [])
.filter((station) => station?.id !== undefined && station?.id !== null && String(station.id).trim())
.map((station) => {
@ -1016,7 +977,7 @@ const execute = async () => {
const correctSource = stationParam?.stationRobotId === singleNodeRobotId.value
&& stationParam?.stationDeviceId === singleNodeDeviceId.value;
if (!validStation || !correctSource) {
ElMessage.warning("请按当前机器人和设备重新加载并选择站点");
ElMessage.warning("请按当前终端和设备重新加载并选择站点");
return;
}
}
@ -1221,32 +1182,6 @@ onUnmounted(() => {
<style lang="scss" scoped>
$flow-canvas-cursor: url("data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20width=%2724%27%20height=%2724%27%20viewBox=%270%200%2024%2024%27%3E%3Cpath%20d=%27M4%202.5v16l4.2-4%203.1%207.1%203.4-1.5-3.1-7.1h5.8z%27%20fill=%27%23fff%27%20stroke=%27%231f2937%27%20stroke-width=%271.8%27%20stroke-linejoin=%27round%27/%3E%3C/svg%3E") 4 3, default;
.single-node-param-row {
display: grid;
grid-template-columns: 190px minmax(0, 1fr);
gap: 16px;
align-items: start;
}
.single-node-param-name,
.single-node-param-value {
width: 100%;
margin-right: 0;
margin-bottom: 16px;
:deep(.el-form-item__content),
:deep(.el-input),
:deep(.el-input-number),
:deep(.el-select) {
width: 100%;
}
}
.single-node-param-value :deep(.el-textarea__inner) {
min-height: 32px !important;
line-height: 20px;
}
.single-node-station-tools {
display: flex;
align-items: center;
@ -1260,14 +1195,6 @@ $flow-canvas-cursor: url("data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org
font-size: 13px;
}
.device-lock-hint {
width: 100%;
margin-top: 6px;
color: var(--el-color-success);
font-size: 12px;
line-height: 18px;
}
.page {
width: 100%;
height: 100%;

View File

@ -68,10 +68,6 @@ const props = defineProps({
robot: {
type: Object,
default: () => ({})
},
deviceId: {
type: String,
default: ''
}
})
@ -107,8 +103,8 @@ const onPress = async (_dir) => {
}
moveRobotData.value.vz = rotationAngle.value
const res = await moveRobot({
robotId: props.robot.robotId,
deviceId: props.deviceId,
terminalId: props.robot.terminalId,
deviceId: props.robot.robotCode,
...moveRobotData.value
})
}
@ -117,8 +113,8 @@ function onRelease() {
moveRobotData.value.vx = 0;
moveRobotData.value.vy = 0;
stopRobot({
robotId: props.robot.robotId,
deviceId: props.deviceId,
terminalId: props.robot.terminalId,
deviceId: props.robot.robotCode,
})
}
</script>
@ -322,4 +318,4 @@ function onRelease() {
}
}
}
</style>
</style>

View File

@ -150,10 +150,6 @@ const props = defineProps({
type: Object,
default: () => ({})
},
deviceId: {
type: String,
default: ''
},
type: {
type: String,
default: 'end'
@ -178,8 +174,8 @@ const errorNum = ref(0)
const getPose = async (robot) => {
try {
const res = await getPoseApi({
deviceId: props.deviceId,
robotId: robot.robotId
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: robot.terminalId
})
if (res.code === 200) {
const { x, y, z, rx, ry, rz } = res.data
@ -253,8 +249,8 @@ const move = async (axis, direction) => {
}
speedLApi({
deviceId: props.deviceId,
robotId: props.robot.robotId,
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: props.robot.terminalId,
duration: 60,
acceleration: speed.value + 0.1,
...speedData
@ -266,14 +262,14 @@ const move = async (axis, direction) => {
*/
const stop = async () => {
await stopMotionApi({
deviceId: props.deviceId,
robotId: props.robot.robotId
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: props.robot.terminalId
})
}
const timer = ref(null)
watch(() => props.robot, (newRobot) => {
if (newRobot && newRobot.robotId) {
if (newRobot && newRobot.terminalId) {
if (timer.value) {
clearInterval(timer.value)
}
@ -410,4 +406,4 @@ watch(() => props.robot, (newRobot) => {
}
}
}
</style>
</style>

View File

@ -102,14 +102,11 @@ import { computed, onBeforeUnmount, ref } from 'vue'
import { ElMessage, ElNotification } from 'element-plus'
import { setMicVolume, getMicVolume, setSpeakerVolume, getSpeakerVolume } from '@/api/inspection/cockpit'
const props = defineProps({
robotId: { type: String, default: '' },
speakerDeviceId: { type: String, default: '' },
microphoneDeviceId: { type: String, default: '' }
})
// WebSocket JSON PCM使 WebRTCSDP ICE
const AUDIO_SERVER_URL = import.meta.env.VITE_AUDIO_SERVER_URL
const TERMINAL_ID = 'c2f8a06826baf03843925c1a2a13bcfd'
const speakerDeviceId = import.meta.env.VITE_SPEAKER_DEVICE_ID
const micDeviceId = import.meta.env.VITE_MIC_DEVICE_ID
// PCM S16LE48kHz10ms
const PCM_SAMPLE_RATE = 48000
@ -327,9 +324,9 @@ function connectAudioServer(sessionToken) {
socket.onopen = () => {
sendControlMessage({
type: 'join',
robotId: props.robotId,
speakerDeviceId: props.speakerDeviceId,
micDeviceId: props.microphoneDeviceId
terminalId: TERMINAL_ID,
speakerDeviceId: speakerDeviceId,
micDeviceId: micDeviceId
})
}
@ -522,10 +519,6 @@ async function setupAudioPipeline(stream) {
*/
async function startCall() {
if (isCalling.value || isStarting.value || webSocket.value) return
if (!props.robotId || !props.speakerDeviceId || !props.microphoneDeviceId) {
ElMessage.warning('当前机器人缺少在线麦克风或扬声器')
return
}
const sessionToken = ++callSessionId
isStarting.value = true
@ -661,8 +654,8 @@ async function updateMicrophoneVolume(volume) {
isMicrophoneVolumeLoading.value = true
try {
const response = await setMicVolume({
deviceId: props.microphoneDeviceId,
robotId: props.robotId,
deviceId: micDeviceId,
terminalId: TERMINAL_ID,
volume: Math.round(volume)
})
if (response.code !== 200) throw new Error(response.message || '设置机器人音量失败')
@ -682,8 +675,8 @@ async function updateSpeakerVolume(volume) {
isSpeakerVolumeLoading.value = true
try {
const response = await setSpeakerVolume({
deviceId: props.speakerDeviceId,
robotId: props.robotId,
deviceId: speakerDeviceId,
terminalId: TERMINAL_ID,
volume: Math.round(volume)
})
if (response.code !== 200) throw new Error(response.message || '设置扬声器音量失败')
@ -702,8 +695,8 @@ async function loadMicrophoneVolume({ silent = false } = {}) {
isMicrophoneVolumeLoading.value = true
try {
const response = await getMicVolume({
deviceId: props.microphoneDeviceId,
robotId: props.robotId
deviceId: micDeviceId,
terminalId: TERMINAL_ID
})
if (response.code !== 200) throw new Error(response.message || '获取机器人音量失败')
const volume = Math.max(0, Math.min(100, Number(response.data) || 0))
@ -720,8 +713,8 @@ async function loadSpeakerVolume({ silent = false } = {}) {
isSpeakerVolumeLoading.value = true
try {
const response = await getSpeakerVolume({
deviceId: props.speakerDeviceId,
robotId: props.robotId
deviceId: speakerDeviceId,
terminalId: TERMINAL_ID
})
if (response.code !== 200) throw new Error(response.message || '获取扬声器音量失败')
const volume = Math.max(0, Math.min(100, Number(response.data) || 0))

View File

@ -38,14 +38,7 @@
<el-radio :value="3">相机</el-radio>
</el-radio-group>
<div class="controller-panel">人工控制面板</div>
<div class="device-binding">
<span>设备随机器人自动切换</span>
<el-tag size="small" :type="activeDeviceId ? 'success' : 'danger'">
{{ activeDeviceId || '无匹配在线设备' }}
</el-tag>
</div>
<DirectionControl v-if="activeController === 1" :robot="activeBotData"
:device-id="deviceByKind(1)" />
<DirectionControl v-if="activeController === 1" :robot="activeBotData" />
<div v-if="activeController === 2" class="robotic-arm-controller-container">
<el-radio-group v-model="activeRoboticArm" size="small">
<el-radio :value="1">末端控制</el-radio>
@ -54,7 +47,6 @@
</el-radio-group>
<RoboticArm :type="activeRoboticArm === 1 ? 'end' : 'posture'" :robot="activeBotData"
:device-id="deviceByKind(2)"
v-if="[1, 3].includes(activeRoboticArm)" />
<div class="control-panel" v-if="activeRoboticArm === 2">
@ -115,36 +107,24 @@
<div class="split-line"></div>
<div class="container-title">语音对话</div>
<div class="voice-container">
<VoiceConversation :robot-id="activeBotData.robotId"
:microphone-device-id="deviceByKind(9)" :speaker-device-id="deviceByKind(13)" />
<VoiceConversation />
</div>
</div>
</div>
</template>
<script setup>
import { computed, onMounted, nextTick } from "vue";
import { ElMessage } from 'element-plus'
import { onMounted, nextTick } from "vue";
import SvgIcon from "@/components/SvgIcon";
import UrdfViewer from '../UrdfView.vue'
import DirectionControl from './DirectionControl.vue'
import RoboticArm from "./RoboticArm.vue";
import { getRobotList, getRobotDevicesByRobotId } from '@/api/inspection/robot'
import { getRobotList } from '@/api/inspection/robot'
import { getJointStateApi, moveJApi, torqueOnApi, speedJApi, stopMotionApi, clearFault } from '@/api/inspection/cockpit'
import { Remove, CirclePlus } from '@element-plus/icons-vue'
import VoiceConversation from "./VoiceConversation.vue";
import IPlayer from "@/components/IPlayer/index.vue";
const robotList = ref([])
const robotDevices = ref([])
const deviceByKind = (kind) => robotDevices.value.find(device => device.deviceKind === kind)?.deviceId || ''
const activeDeviceId = computed(() => deviceByKind(activeController.value === 2 ? 2 : activeController.value === 3 ? 5 : 1))
const loadRobotDevices = async (robotId) => {
robotDevices.value = []
if (!robotId) return
const response = await getRobotDevicesByRobotId(robotId)
robotDevices.value = Array.isArray(response.data) ? response.data : []
}
const fetchRobotList = async () => {
try {
@ -154,7 +134,6 @@ const fetchRobotList = async () => {
if (res.code === 200) {
robotList.value = res.rows
activeBotData.value = robotList.value[0] || {}
await loadRobotDevices(activeBotData.value.robotId)
} else {
console.error('获取机器人列表失败:', res.message)
}
@ -172,10 +151,9 @@ const robotStatus = {
const activeBot = ref(0)
const activeBotData = ref({})
const changeBot = async (index) => {
const changeBot = (index) => {
activeBot.value = index
activeBotData.value = robotList.value[activeBot.value] || {}
await loadRobotDevices(activeBotData.value.robotId)
}
const colorList = ['#00D4FF', '#FFB300', '#00FF88', '#fff']
@ -198,19 +176,15 @@ const animationId = ref(null)
const handlerController = (value) => {
if (value === 2) {
if (!deviceByKind(2)) {
ElMessage.warning('当前机器人没有在线机械臂')
return
}
// 使
torqueOnApi({
deviceId: deviceByKind(2),
robotId: activeBotData.value.robotId
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: activeBotData.value.terminalId
}).catch(error => {
console.error('开启机械臂使能失败:', error)
clearFault({
deviceId: deviceByKind(2),
robotId: activeBotData.value.robotId
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: activeBotData.value.terminalId
})
})
jointControls.value = urdfViewerRef.value.jointControls
@ -245,8 +219,8 @@ const errCount = ref(0)
const getJointState = async (robot) => {
try {
const res = await getJointStateApi({
deviceId: deviceByKind(2),
robotId: robot.robotId
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: robot.terminalId
})
if (res.code === 200) {
setTimeout(() => {
@ -282,8 +256,8 @@ const updateJoint = async (jointName, _dir) => {
const index = jointControls.value.findIndex(joint => joint.name === jointName)
velocities[index] = jointSpeed.value * (_dir === '+' ? 1 : -1)
const res = await speedJApi({
deviceId: deviceByKind(2),
robotId: activeBotData.value.robotId,
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: activeBotData.value.terminalId,
acceleration: jointSpeed.value + 0.1,
duration: 60,
velocities: velocities
@ -295,8 +269,8 @@ const updateJoint = async (jointName, _dir) => {
*/
const stop = async () => {
await stopMotionApi({
deviceId: deviceByKind(2),
robotId: activeBotData.value.robotId
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
terminalId: activeBotData.value.terminalId
})
}
@ -507,16 +481,6 @@ onUnmounted(() => {
}
}
.device-binding {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin: 10px 0 14px;
color: #64748b;
font-size: 12px;
}
.control-panel {
margin-top: 12px;
background: #0F1E38;
@ -587,4 +551,4 @@ onUnmounted(() => {
}
}
}
</style>
</style>

View File

@ -10,7 +10,7 @@
<el-form-item label="执行机器人" prop="formData.robotId" :rules="[{ required: true, message: '请选择在线机器人', trigger: 'change' }]">
<el-select v-model="formData.robotId" style="width: 260px" placeholder="请选择在线机器人">
<el-option
v-for="item in robotIdOptions"
v-for="item in terminalIdOptions"
:key="item.value"
:label="item.label"
:value="item.value"
@ -94,13 +94,13 @@ const formData = ref({
tableData: []
})
const robotIdOptions = ref([]);
const terminalIdOptions = ref([]);
/** 获取设备终端列表 */
const handleGetTerminalGroup = async (row) => {
const res = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' });
if (res.code === 200) {
robotIdOptions.value = res.rows?.map((item) => {
terminalIdOptions.value = res.rows?.map((item) => {
return {
label: `${item.robotName || item.robotId} (${item.robotId})`,
value: item.robotId,

View File

@ -10,7 +10,7 @@
<el-form-item label="执行机器人" prop="formData.robotId" :rules="[{ required: true, message: '请选择在线机器人', trigger: 'change' }]">
<el-select v-model="formData.robotId" style="width: 260px" placeholder="请选择在线机器人">
<el-option
v-for="item in robotIdOptions"
v-for="item in terminalIdOptions"
:key="item.value"
:label="item.label"
:value="item.value"
@ -142,13 +142,13 @@ const confirm = () => {
});
};
const robotIdOptions = ref([]);
const terminalIdOptions = ref([]);
/** 获取设备终端列表 */
const handleGetTerminalGroup = async (row) => {
const res = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' });
if (res.code === 200) {
robotIdOptions.value = res.rows?.map((item) => {
terminalIdOptions.value = res.rows?.map((item) => {
return {
label: `${item.robotName || item.robotId} (${item.robotId})`,
value: item.robotId,

View File

@ -605,7 +605,7 @@ const handleTaskExecute = (formEl) => {
}
const data = Object.assign(
{},
{ runParams: { robotId: selectedTerminal.value } },
{ runParams: { terminalId: selectedTerminal.value } },
{ taskId: currentRow.value.id }
);
// taskExecute(data).then((res) => {

View File

@ -10,7 +10,7 @@
<el-form-item label="执行机器人" prop="formData.robotId" :rules="[{ required: true, message: '请选择在线机器人', trigger: 'change' }]">
<el-select v-model="formData.robotId" style="width: 260px" placeholder="请选择在线机器人">
<el-option
v-for="item in robotIdOptions"
v-for="item in terminalIdOptions"
:key="item.value"
:label="item.label"
:value="item.value"
@ -92,13 +92,13 @@ const formData = ref({
tableData: []
})
const robotIdOptions = ref([]);
const terminalIdOptions = ref([]);
/** 获取设备终端列表 */
const handleGetTerminalGroup = async (row) => {
const res = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' });
if (res.code === 200) {
robotIdOptions.value = res.rows?.map((item) => {
terminalIdOptions.value = res.rows?.map((item) => {
return {
label: `${item.robotName || item.robotId} (${item.robotId})`,
value: item.robotId,