Compare commits
2 Commits
d55f95e7d3
...
e3a566cf28
| Author | SHA1 | Date | |
|---|---|---|---|
| e3a566cf28 | |||
| 45b190ff02 |
@ -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 }
|
||||
})
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
src/utils/robotDevice.js
Normal file
38
src/utils/robotDevice.js
Normal file
@ -0,0 +1,38 @@
|
||||
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,
|
||||
}))
|
||||
@ -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]);
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -4,11 +4,11 @@
|
||||
<div class="left-panel">
|
||||
<div class="hand-container">
|
||||
<!-- 确保这里使用的是 index.vue 中响应式的 deviceId ref -->
|
||||
<LeftTopHand :initialDeviceId="deviceId" :initialTerminalId="terminalId" @update:topSeriesData="handleUpdateTopSeriesData" />
|
||||
<LeftTopHand :initialDeviceId="deviceId" :initialRobotId="robotId" @update:topSeriesData="handleUpdateTopSeriesData" />
|
||||
</div>
|
||||
<div class="hand-container">
|
||||
<!-- 确保这里使用的是 index.vue 中响应式的 terminalId ref -->
|
||||
<LeftBottomHand :initialDeviceId="deviceId" :initialTerminalId="terminalId" @update:bottomSeriesData="handleUpdateBottomSeriesData" />
|
||||
<!-- 确保这里使用的是 index.vue 中响应式的 robotId ref -->
|
||||
<LeftBottomHand :initialDeviceId="deviceId" :initialRobotId="robotId" @update:bottomSeriesData="handleUpdateBottomSeriesData" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
@ -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 });
|
||||
</script>
|
||||
@ -155,4 +165,4 @@ watch(() => props.initialTerminalId, (newVal) => {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -80,10 +80,12 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
// import { setServo } from '@/api/device/head'; // 假设接口
|
||||
|
||||
const terminalId = ref('25449ff3fcc7b27da5f69462c5efcec2');
|
||||
const deviceId = ref('head1');
|
||||
const route = useRoute();
|
||||
const robotId = ref(String(route.query.robotId || ''));
|
||||
const deviceId = ref(String(route.query.deviceId || ''));
|
||||
|
||||
const servo1 = ref(0.5); // 当前 Yaw
|
||||
const servo2 = ref(0.5); // 当前 Pitch
|
||||
@ -151,7 +153,7 @@ const applyPitchFromPresets = () => {
|
||||
const sendServoValues = async () => {
|
||||
try {
|
||||
// await setServo({
|
||||
// terminalId: terminalId.value,
|
||||
// robotId: robotId.value,
|
||||
// deviceId: deviceId.value,
|
||||
// servo1: servo1.value,
|
||||
// servo2: servo2.value,
|
||||
@ -311,4 +313,4 @@ const points = ref([
|
||||
color: #409eff;
|
||||
min-width: 60px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -66,7 +66,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { Microphone, VideoPause, Mute, Mic, VideoPlay } from "@element-plus/icons-vue";
|
||||
import {
|
||||
startMicrophoneApi,
|
||||
@ -77,19 +77,27 @@ 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 terminalId = route.query.terminalId
|
||||
const robotId = route.query.robotId
|
||||
const deviceId = route.query.deviceId
|
||||
|
||||
const recordStatus = ref('end');
|
||||
const isRecording = ref(false)
|
||||
const list = []
|
||||
const audiosList = ref([])
|
||||
const speaker = ref('spk1')
|
||||
const speakerOptions = ref(['spk1'])
|
||||
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] || ''
|
||||
})
|
||||
|
||||
/**
|
||||
* 开始录音
|
||||
@ -98,13 +106,13 @@ const startRecording = async () => {
|
||||
const fileName = new Date().getTime() + '.wav'
|
||||
isRecording.value = true
|
||||
const res = await startMicrophoneApi({
|
||||
terminalId,
|
||||
robotId,
|
||||
deviceId,
|
||||
filePath: `/home/share/record/audios/${terminalId}_${deviceId}_${fileName}`
|
||||
filePath: `/home/share/record/audios/${robotId}_${deviceId}_${fileName}`
|
||||
})
|
||||
if (res.code === 200) {
|
||||
recordStatus.value = 'recording'
|
||||
list.push(`${terminalId}_${deviceId}_${fileName}`)
|
||||
list.push(`${robotId}_${deviceId}_${fileName}`)
|
||||
}
|
||||
};
|
||||
|
||||
@ -113,7 +121,7 @@ const startRecording = async () => {
|
||||
*/
|
||||
const pauseRecording = async () => {
|
||||
const res = await pauseMicrophoneApi({
|
||||
terminalId,
|
||||
robotId,
|
||||
deviceId
|
||||
})
|
||||
if (res.code === 200) {
|
||||
@ -126,7 +134,7 @@ const pauseRecording = async () => {
|
||||
*/
|
||||
const recoverRecording = async () => {
|
||||
const res = await resumeMicrophoneApi({
|
||||
terminalId,
|
||||
robotId,
|
||||
deviceId
|
||||
})
|
||||
if (res.code === 200) {
|
||||
@ -139,7 +147,7 @@ const recoverRecording = async () => {
|
||||
*/
|
||||
const stopRecording = async () => {
|
||||
const res = await stopMicrophoneApi({
|
||||
terminalId,
|
||||
robotId,
|
||||
deviceId
|
||||
})
|
||||
if (res.code === 200) {
|
||||
@ -154,7 +162,7 @@ const stopRecording = async () => {
|
||||
*/
|
||||
const handlePlay = async (audioPath) => {
|
||||
const res = await playSpeaker({
|
||||
terminalId,
|
||||
robotId,
|
||||
deviceId: speaker.value,
|
||||
audioPath: `/home/share/record/audios/${audioPath}`
|
||||
})
|
||||
|
||||
@ -52,6 +52,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import {
|
||||
pause,
|
||||
play,
|
||||
@ -73,11 +74,12 @@ const currentIndex = ref(0);
|
||||
const isPlaying = ref(false);
|
||||
const volume = ref(0);
|
||||
|
||||
const terminalId = ref('25449ff3fcc7b27da5f69462c5efcec2');
|
||||
const deviceId = ref('spk1');
|
||||
const route = useRoute();
|
||||
const robotId = ref(String(route.query.robotId || ''));
|
||||
const deviceId = ref(String(route.query.deviceId || ''));
|
||||
|
||||
const getCommonParams = () => ({
|
||||
terminalId: terminalId.value,
|
||||
robotId: robotId.value,
|
||||
deviceId: deviceId.value
|
||||
});
|
||||
|
||||
@ -273,4 +275,4 @@ input[type="range"] {
|
||||
fill: #ddd;
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -1,414 +1,184 @@
|
||||
<template>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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 }}
|
||||
<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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <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)"
|
||||
/>
|
||||
<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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
</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 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);
|
||||
<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'
|
||||
|
||||
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 {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 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 {queryParams, form, rules} = toRefs(data)
|
||||
|
||||
/** 查询设备注册列表 */
|
||||
function getList() {
|
||||
loading.value = true
|
||||
listRegister(queryParams.value).then(response => {
|
||||
registerList.value = response.rows
|
||||
total.value = response.total
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 取消按钮
|
||||
function cancel() {
|
||||
open.value = false
|
||||
reset()
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
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
|
||||
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
|
||||
}
|
||||
proxy.resetForm("registerRef")
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1
|
||||
getList()
|
||||
const loadDevices = async () => {
|
||||
devices.value = []
|
||||
if (!selectedRobotId.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await getRobotDevicesByRobotId(selectedRobotId.value, { onlineOnly: false })
|
||||
devices.value = Array.isArray(response.data) ? response.data : []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
proxy.resetForm("queryRef")
|
||||
handleQuery()
|
||||
}
|
||||
const controlRoute = (kind) => ({
|
||||
2: 'mechanical_arm', 4: 'head', 5: 'camera', 7: 'hand', 9: 'microphone', 13: 'speaker'
|
||||
})[kind]
|
||||
|
||||
// 多选框选中数据
|
||||
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}`; // 构造路由路径
|
||||
const openControl = (device) => {
|
||||
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()
|
||||
path: `/device/${controlRoute(device.deviceKind)}/${device.id}`,
|
||||
query: { robotId: selectedRobotId.value, deviceId: device.deviceId }
|
||||
})
|
||||
}
|
||||
|
||||
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()
|
||||
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%; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -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" :initialTerminalId = "popup.configId" />
|
||||
<component :is="popup.component" v-bind="popup.props" :initialDeviceId = "popup.deviceId" :initialRobotId = "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: { 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;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -11,6 +11,18 @@
|
||||
: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"
|
||||
@ -30,10 +42,10 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="value" label="入参值">
|
||||
<template #default="{ row, column, $index }">
|
||||
<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-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-option
|
||||
v-for="item in terminalIdOptions"
|
||||
v-for="item in robotIdOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
@ -55,7 +67,7 @@
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
<el-button type="primary" @click="confirm">确定</el-button>
|
||||
<el-button type="primary" :loading="bindingLoading" :disabled="bindingLoading" @click="confirm">确定</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
@ -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) => {
|
||||
}
|
||||
|
||||
</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>
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="form__container">
|
||||
<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-button type="primary" v-if="hasBindableDevice" 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,15 +82,25 @@
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="robotFormAction === 'AGV_MOVE_TO_STATION' ? '加载AGV站点列表' : '获取位置信息'"
|
||||
width="360"
|
||||
:title="robotDialogTitle"
|
||||
width="460"
|
||||
>
|
||||
<el-form ref="robotFormRef" :model="robotForm" :rules="robotFormRules" label-width="auto">
|
||||
<el-form-item label="终端ID" prop="terminalId">
|
||||
<el-input v-model="robotForm.terminalId" />
|
||||
<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-item>
|
||||
<el-form-item label="设备ID" prop="deviceId">
|
||||
<el-input v-model="robotForm.deviceId" />
|
||||
<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>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@ -107,7 +117,9 @@ 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
|
||||
@ -201,13 +213,42 @@ const validateAndSave = async () => {
|
||||
const dialogVisible = ref(false)
|
||||
const robotFormRef = ref(null)
|
||||
const robotFormRules = reactive({
|
||||
terminalId: [{ required: true, message: '终端ID不能为空', trigger: 'blur' }],
|
||||
robotId: [{ required: true, message: '请选择机器人', trigger: 'change' }],
|
||||
deviceId: [{ required: true, message: '设备ID不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const robotForm = reactive({
|
||||
deviceId: '',
|
||||
terminalId: ''
|
||||
robotId: ''
|
||||
})
|
||||
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') {
|
||||
@ -254,16 +295,28 @@ const testRule = (property) => {
|
||||
|
||||
const robotFormAction = ref()
|
||||
const stationListLoading = ref(false)
|
||||
const robotActionButtonText = computed(() =>
|
||||
props.data.properties.action === 'AGV_MOVE_TO_STATION' ? '加载站点列表' : '获取位置'
|
||||
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(() => {
|
||||
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, terminalId: robotForm.terminalId })
|
||||
const response = await getAgvStatus({ deviceId: robotForm.deviceId, robotId: robotForm.robotId })
|
||||
if (response && response.data) {
|
||||
dialogVisible.value = false
|
||||
const { x, y, theta } = response.data.pose
|
||||
@ -292,7 +345,7 @@ const submitRobotForm = () => {
|
||||
try {
|
||||
const response = await getAgvStations({
|
||||
deviceId: robotForm.deviceId,
|
||||
terminalId: robotForm.terminalId
|
||||
robotId: robotForm.robotId
|
||||
})
|
||||
const stationOptions = toStationOptions(response?.data)
|
||||
if (stationOptions.length === 0) {
|
||||
@ -322,7 +375,7 @@ const submitRobotForm = () => {
|
||||
robotFormRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
const response = await getArmStatus({ deviceId: robotForm.deviceId, terminalId: robotForm.terminalId })
|
||||
const response = await getArmStatus({ deviceId: robotForm.deviceId, robotId: robotForm.robotId })
|
||||
if (response && response.data) {
|
||||
dialogVisible.value = false
|
||||
const { x, y, z, rx, ry, rz } = response.data
|
||||
@ -353,7 +406,7 @@ const submitRobotForm = () => {
|
||||
try {
|
||||
const response = await getArmJointState({
|
||||
deviceId: robotForm.deviceId,
|
||||
terminalId: robotForm.terminalId
|
||||
robotId: robotForm.robotId
|
||||
})
|
||||
if (response && response.data) {
|
||||
const target = response.data.position || response.data.positions
|
||||
@ -371,6 +424,19 @@ 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('机器人设备已匹配')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -385,9 +451,13 @@ const toStationOptions = (stations) => (Array.isArray(stations) ? stations : [])
|
||||
}
|
||||
})
|
||||
|
||||
const openRobotForm = (action) => {
|
||||
const openRobotForm = async (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 })
|
||||
@ -467,4 +537,19 @@ 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>
|
||||
|
||||
@ -14,29 +14,6 @@ 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: '是' }
|
||||
@ -119,7 +96,7 @@ export const collapseList = [
|
||||
action: 'CAMERA_START',
|
||||
nodeType: 'EDGE',
|
||||
nodeParams: [
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||
],
|
||||
outputParams: [],
|
||||
outputType: 'json'
|
||||
@ -132,7 +109,7 @@ export const collapseList = [
|
||||
action: 'CAMERA_GETRGBIMAGE',
|
||||
nodeType: 'EDGE',
|
||||
nodeParams: [
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||
],
|
||||
outputType: 'img',
|
||||
outputParams: [{ name: 'imageUrl', type: 'array<string>', desc: '图片地址数组', children: [], disabled: true }]
|
||||
@ -145,7 +122,7 @@ export const collapseList = [
|
||||
action: 'CAMERA_STOP',
|
||||
nodeType: 'EDGE',
|
||||
nodeParams: [
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||
],
|
||||
outputParams: [],
|
||||
outputType: 'json'
|
||||
@ -163,7 +140,7 @@ export const collapseList = [
|
||||
action: 'CAMERA_RECORDING_START',
|
||||
nodeType: 'EDGE',
|
||||
nodeParams: [
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||
],
|
||||
outputParams: [],
|
||||
outputType: 'json'
|
||||
@ -176,7 +153,7 @@ export const collapseList = [
|
||||
action: 'CAMERA_RECORDING_STOP',
|
||||
nodeType: 'EDGE',
|
||||
nodeParams: [
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||
],
|
||||
outputType: 'video',
|
||||
outputParams: [{ name: 'videoUrl', type: 'array<string>', children: [], desc: '视频播放地址数组', disabled: true}]
|
||||
@ -400,7 +377,7 @@ function handler(params) {
|
||||
action: 'INSPECTION_ALERT_LISTEN_START',
|
||||
nodeType: 'EDGE',
|
||||
nodeParams: [
|
||||
{ name: "terminalId", type: "input", input: "", disabled: true },
|
||||
{ name: "robotId", type: "input", input: "", disabled: true },
|
||||
{ name: "eventTypes", type: "input", input: "", componentType: 'select', selectOptions: inspectionEventTypes(), disabled: true }
|
||||
],
|
||||
outputParams: [],
|
||||
@ -414,7 +391,7 @@ function handler(params) {
|
||||
action: 'INSPECTION_ALERT_LISTEN_STOP',
|
||||
nodeType: 'EDGE',
|
||||
nodeParams: [
|
||||
{ name: "terminalId", type: "input", input: "", disabled: true },
|
||||
{ name: "robotId", type: "input", input: "", disabled: true },
|
||||
{ name: "eventTypes", type: "input", input: "", componentType: 'select', selectOptions: inspectionEventTypes(), disabled: true }
|
||||
],
|
||||
outputParams: [],
|
||||
@ -465,7 +442,7 @@ function handler(params) {
|
||||
action: 'MICROPHONE_START',
|
||||
nodeType: 'EDGE',
|
||||
nodeParams: [
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: audioOptions(), disabled: true }
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||
],
|
||||
outputType: 'json'
|
||||
},
|
||||
@ -477,7 +454,7 @@ function handler(params) {
|
||||
action: 'MICROPHONE_STOP',
|
||||
nodeType: 'EDGE',
|
||||
nodeParams: [
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: audioOptions(), disabled: true }
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||
],
|
||||
outputType: 'json'
|
||||
},
|
||||
@ -488,7 +465,7 @@ function handler(params) {
|
||||
desc: "用于播放音频的节点",
|
||||
action: 'SPEAKER_PLAYAUDIO',
|
||||
nodeParams: [
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: audioOptions(), disabled: true }
|
||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||
],
|
||||
outputType: 'json'
|
||||
},
|
||||
|
||||
@ -155,7 +155,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="visible" width="500" append-to-body>
|
||||
<el-dialog v-model="visible" width="640" class="single-node-dialog" append-to-body>
|
||||
<template #header="{ close, titleId, titleClass }">
|
||||
<div class="my-header">
|
||||
<h4 :id="titleId" :class="titleClass">执行{{ nodeName }}节点</h4>
|
||||
@ -181,8 +181,9 @@
|
||||
:disabled="flowStore.disableForm"
|
||||
>
|
||||
<div v-for="(property, index) in formData.nodeParams" :key="index">
|
||||
<el-row>
|
||||
<el-row class="single-node-param-row">
|
||||
<el-form-item
|
||||
class="single-node-param-name"
|
||||
:label="index === 0 ? '参数名' : ''"
|
||||
:prop="`nodeParams.${index}.name`"
|
||||
:rules="[{ required: true, message: '请输入参数名', trigger: 'blur' }]"
|
||||
@ -195,6 +196,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
class="single-node-param-value"
|
||||
:label="index === 0 ? '参数值' : ''"
|
||||
:prop="`nodeParams.${index}.input`"
|
||||
:rules="[{ required: true, message: '请输入参数值', trigger: 'blur' }]"
|
||||
@ -212,6 +214,8 @@
|
||||
<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"
|
||||
@ -225,9 +229,13 @@
|
||||
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>
|
||||
@ -269,7 +277,8 @@ import {
|
||||
getAgvStations,
|
||||
} from "@/api/flow/flow";
|
||||
import { getDetect } from "@/api/test/detect";
|
||||
import { getRobotList } from '@/api/inspection/robot'
|
||||
import { getRobotList, getRobotDevicesByRobotId } from '@/api/inspection/robot'
|
||||
import { getDeviceKindForAction, toDeviceOptions } from '@/utils/robotDevice'
|
||||
import {
|
||||
CircleClose,
|
||||
CollectionTag,
|
||||
@ -447,7 +456,6 @@ const handleEdgeClick = ({ event, edge }) => {
|
||||
const handlePaneClick = () => lf.clearSelectElements();
|
||||
|
||||
const flowStore = useFlowStore();
|
||||
flowStore.getDeviceList();
|
||||
|
||||
const isOpen = ref(false);
|
||||
const stateMap = {
|
||||
@ -899,6 +907,37 @@ 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) => {
|
||||
@ -977,7 +1016,7 @@ const execute = async () => {
|
||||
const correctSource = stationParam?.stationRobotId === singleNodeRobotId.value
|
||||
&& stationParam?.stationDeviceId === singleNodeDeviceId.value;
|
||||
if (!validStation || !correctSource) {
|
||||
ElMessage.warning("请按当前终端和设备重新加载并选择站点");
|
||||
ElMessage.warning("请按当前机器人和设备重新加载并选择站点");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1182,6 +1221,32 @@ 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;
|
||||
@ -1195,6 +1260,14 @@ $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%;
|
||||
|
||||
@ -68,6 +68,10 @@ const props = defineProps({
|
||||
robot: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
deviceId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
@ -103,8 +107,8 @@ const onPress = async (_dir) => {
|
||||
}
|
||||
moveRobotData.value.vz = rotationAngle.value
|
||||
const res = await moveRobot({
|
||||
terminalId: props.robot.terminalId,
|
||||
deviceId: props.robot.robotCode,
|
||||
robotId: props.robot.robotId,
|
||||
deviceId: props.deviceId,
|
||||
...moveRobotData.value
|
||||
})
|
||||
}
|
||||
@ -113,8 +117,8 @@ function onRelease() {
|
||||
moveRobotData.value.vx = 0;
|
||||
moveRobotData.value.vy = 0;
|
||||
stopRobot({
|
||||
terminalId: props.robot.terminalId,
|
||||
deviceId: props.robot.robotCode,
|
||||
robotId: props.robot.robotId,
|
||||
deviceId: props.deviceId,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@ -318,4 +322,4 @@ function onRelease() {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -150,6 +150,10 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
deviceId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'end'
|
||||
@ -174,8 +178,8 @@ const errorNum = ref(0)
|
||||
const getPose = async (robot) => {
|
||||
try {
|
||||
const res = await getPoseApi({
|
||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
||||
terminalId: robot.terminalId
|
||||
deviceId: props.deviceId,
|
||||
robotId: robot.robotId
|
||||
})
|
||||
if (res.code === 200) {
|
||||
const { x, y, z, rx, ry, rz } = res.data
|
||||
@ -249,8 +253,8 @@ const move = async (axis, direction) => {
|
||||
}
|
||||
|
||||
speedLApi({
|
||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
||||
terminalId: props.robot.terminalId,
|
||||
deviceId: props.deviceId,
|
||||
robotId: props.robot.robotId,
|
||||
duration: 60,
|
||||
acceleration: speed.value + 0.1,
|
||||
...speedData
|
||||
@ -262,14 +266,14 @@ const move = async (axis, direction) => {
|
||||
*/
|
||||
const stop = async () => {
|
||||
await stopMotionApi({
|
||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
||||
terminalId: props.robot.terminalId
|
||||
deviceId: props.deviceId,
|
||||
robotId: props.robot.robotId
|
||||
})
|
||||
}
|
||||
|
||||
const timer = ref(null)
|
||||
watch(() => props.robot, (newRobot) => {
|
||||
if (newRobot && newRobot.terminalId) {
|
||||
if (newRobot && newRobot.robotId) {
|
||||
if (timer.value) {
|
||||
clearInterval(timer.value)
|
||||
}
|
||||
@ -406,4 +410,4 @@ watch(() => props.robot, (newRobot) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -102,11 +102,14 @@ 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,不再使用 WebRTC、SDP 或 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 格式:S16LE、48kHz、单声道、10ms。
|
||||
const PCM_SAMPLE_RATE = 48000
|
||||
@ -324,9 +327,9 @@ function connectAudioServer(sessionToken) {
|
||||
socket.onopen = () => {
|
||||
sendControlMessage({
|
||||
type: 'join',
|
||||
terminalId: TERMINAL_ID,
|
||||
speakerDeviceId: speakerDeviceId,
|
||||
micDeviceId: micDeviceId
|
||||
robotId: props.robotId,
|
||||
speakerDeviceId: props.speakerDeviceId,
|
||||
micDeviceId: props.microphoneDeviceId
|
||||
})
|
||||
}
|
||||
|
||||
@ -519,6 +522,10 @@ 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
|
||||
@ -654,8 +661,8 @@ async function updateMicrophoneVolume(volume) {
|
||||
isMicrophoneVolumeLoading.value = true
|
||||
try {
|
||||
const response = await setMicVolume({
|
||||
deviceId: micDeviceId,
|
||||
terminalId: TERMINAL_ID,
|
||||
deviceId: props.microphoneDeviceId,
|
||||
robotId: props.robotId,
|
||||
volume: Math.round(volume)
|
||||
})
|
||||
if (response.code !== 200) throw new Error(response.message || '设置机器人音量失败')
|
||||
@ -675,8 +682,8 @@ async function updateSpeakerVolume(volume) {
|
||||
isSpeakerVolumeLoading.value = true
|
||||
try {
|
||||
const response = await setSpeakerVolume({
|
||||
deviceId: speakerDeviceId,
|
||||
terminalId: TERMINAL_ID,
|
||||
deviceId: props.speakerDeviceId,
|
||||
robotId: props.robotId,
|
||||
volume: Math.round(volume)
|
||||
})
|
||||
if (response.code !== 200) throw new Error(response.message || '设置扬声器音量失败')
|
||||
@ -695,8 +702,8 @@ async function loadMicrophoneVolume({ silent = false } = {}) {
|
||||
isMicrophoneVolumeLoading.value = true
|
||||
try {
|
||||
const response = await getMicVolume({
|
||||
deviceId: micDeviceId,
|
||||
terminalId: TERMINAL_ID
|
||||
deviceId: props.microphoneDeviceId,
|
||||
robotId: props.robotId
|
||||
})
|
||||
if (response.code !== 200) throw new Error(response.message || '获取机器人音量失败')
|
||||
const volume = Math.max(0, Math.min(100, Number(response.data) || 0))
|
||||
@ -713,8 +720,8 @@ async function loadSpeakerVolume({ silent = false } = {}) {
|
||||
isSpeakerVolumeLoading.value = true
|
||||
try {
|
||||
const response = await getSpeakerVolume({
|
||||
deviceId: speakerDeviceId,
|
||||
terminalId: TERMINAL_ID
|
||||
deviceId: props.speakerDeviceId,
|
||||
robotId: props.robotId
|
||||
})
|
||||
if (response.code !== 200) throw new Error(response.message || '获取扬声器音量失败')
|
||||
const volume = Math.max(0, Math.min(100, Number(response.data) || 0))
|
||||
|
||||
@ -38,7 +38,14 @@
|
||||
<el-radio :value="3">相机</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="controller-panel">人工控制面板</div>
|
||||
<DirectionControl v-if="activeController === 1" :robot="activeBotData" />
|
||||
<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)" />
|
||||
<div v-if="activeController === 2" class="robotic-arm-controller-container">
|
||||
<el-radio-group v-model="activeRoboticArm" size="small">
|
||||
<el-radio :value="1">末端控制</el-radio>
|
||||
@ -47,6 +54,7 @@
|
||||
</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">
|
||||
@ -107,24 +115,36 @@
|
||||
<div class="split-line"></div>
|
||||
<div class="container-title">语音对话</div>
|
||||
<div class="voice-container">
|
||||
<VoiceConversation />
|
||||
<VoiceConversation :robot-id="activeBotData.robotId"
|
||||
:microphone-device-id="deviceByKind(9)" :speaker-device-id="deviceByKind(13)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, nextTick } from "vue";
|
||||
import { computed, onMounted, nextTick } from "vue";
|
||||
import { ElMessage } from 'element-plus'
|
||||
import SvgIcon from "@/components/SvgIcon";
|
||||
import UrdfViewer from '../UrdfView.vue'
|
||||
import DirectionControl from './DirectionControl.vue'
|
||||
import RoboticArm from "./RoboticArm.vue";
|
||||
import { getRobotList } from '@/api/inspection/robot'
|
||||
import { getRobotList, getRobotDevicesByRobotId } 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 {
|
||||
@ -134,6 +154,7 @@ 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)
|
||||
}
|
||||
@ -151,9 +172,10 @@ const robotStatus = {
|
||||
|
||||
const activeBot = ref(0)
|
||||
const activeBotData = ref({})
|
||||
const changeBot = (index) => {
|
||||
const changeBot = async (index) => {
|
||||
activeBot.value = index
|
||||
activeBotData.value = robotList.value[activeBot.value] || {}
|
||||
await loadRobotDevices(activeBotData.value.robotId)
|
||||
}
|
||||
|
||||
const colorList = ['#00D4FF', '#FFB300', '#00FF88', '#fff']
|
||||
@ -176,15 +198,19 @@ const animationId = ref(null)
|
||||
|
||||
const handlerController = (value) => {
|
||||
if (value === 2) {
|
||||
if (!deviceByKind(2)) {
|
||||
ElMessage.warning('当前机器人没有在线机械臂')
|
||||
return
|
||||
}
|
||||
// 开启机械臂使能
|
||||
torqueOnApi({
|
||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
||||
terminalId: activeBotData.value.terminalId
|
||||
deviceId: deviceByKind(2),
|
||||
robotId: activeBotData.value.robotId
|
||||
}).catch(error => {
|
||||
console.error('开启机械臂使能失败:', error)
|
||||
clearFault({
|
||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
||||
terminalId: activeBotData.value.terminalId
|
||||
deviceId: deviceByKind(2),
|
||||
robotId: activeBotData.value.robotId
|
||||
})
|
||||
})
|
||||
jointControls.value = urdfViewerRef.value.jointControls
|
||||
@ -219,8 +245,8 @@ const errCount = ref(0)
|
||||
const getJointState = async (robot) => {
|
||||
try {
|
||||
const res = await getJointStateApi({
|
||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
||||
terminalId: robot.terminalId
|
||||
deviceId: deviceByKind(2),
|
||||
robotId: robot.robotId
|
||||
})
|
||||
if (res.code === 200) {
|
||||
setTimeout(() => {
|
||||
@ -256,8 +282,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: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
||||
terminalId: activeBotData.value.terminalId,
|
||||
deviceId: deviceByKind(2),
|
||||
robotId: activeBotData.value.robotId,
|
||||
acceleration: jointSpeed.value + 0.1,
|
||||
duration: 60,
|
||||
velocities: velocities
|
||||
@ -269,8 +295,8 @@ const updateJoint = async (jointName, _dir) => {
|
||||
*/
|
||||
const stop = async () => {
|
||||
await stopMotionApi({
|
||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
||||
terminalId: activeBotData.value.terminalId
|
||||
deviceId: deviceByKind(2),
|
||||
robotId: activeBotData.value.robotId
|
||||
})
|
||||
}
|
||||
|
||||
@ -481,6 +507,16 @@ 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;
|
||||
@ -551,4 +587,4 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -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 terminalIdOptions"
|
||||
v-for="item in robotIdOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
@ -94,13 +94,13 @@ const formData = ref({
|
||||
tableData: []
|
||||
})
|
||||
|
||||
const terminalIdOptions = ref([]);
|
||||
const robotIdOptions = ref([]);
|
||||
|
||||
/** 获取设备终端列表 */
|
||||
const handleGetTerminalGroup = async (row) => {
|
||||
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,
|
||||
|
||||
@ -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 terminalIdOptions"
|
||||
v-for="item in robotIdOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
@ -142,13 +142,13 @@ const confirm = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const terminalIdOptions = ref([]);
|
||||
const robotIdOptions = ref([]);
|
||||
|
||||
/** 获取设备终端列表 */
|
||||
const handleGetTerminalGroup = async (row) => {
|
||||
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,
|
||||
|
||||
@ -605,7 +605,7 @@ const handleTaskExecute = (formEl) => {
|
||||
}
|
||||
const data = Object.assign(
|
||||
{},
|
||||
{ runParams: { terminalId: selectedTerminal.value } },
|
||||
{ runParams: { robotId: selectedTerminal.value } },
|
||||
{ taskId: currentRow.value.id }
|
||||
);
|
||||
// taskExecute(data).then((res) => {
|
||||
|
||||
@ -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 terminalIdOptions"
|
||||
v-for="item in robotIdOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
@ -92,13 +92,13 @@ const formData = ref({
|
||||
tableData: []
|
||||
})
|
||||
|
||||
const terminalIdOptions = ref([]);
|
||||
const robotIdOptions = ref([]);
|
||||
|
||||
/** 获取设备终端列表 */
|
||||
const handleGetTerminalGroup = async (row) => {
|
||||
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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user