feat(flow): 优化机器人设备绑定功能
- 将terminalId替换为robotId以统一机器人标识 - 实现机器人设备动态加载和选择功能 - 添加设备类型过滤和状态显示 - 重构设备注册页面为机器人设备管理界面 - 优化API调用参数传递方式 - 添加设备自动匹配和锁定机制
This commit is contained in:
parent
d55f95e7d3
commit
45b190ff02
@ -1,9 +1,9 @@
|
|||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
|
|
||||||
export function getRgbStreamUrl(terminalId, deviceId) {
|
export function getRgbStreamUrl(robotId, deviceId) {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/edge/camera/rgb-stream-url',
|
url: '/api/edge/camera/rgb-stream-url',
|
||||||
method: 'get',
|
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
|
* @param {*} robotId
|
||||||
|
|||||||
@ -2,20 +2,11 @@ import { defineStore } from 'pinia'
|
|||||||
|
|
||||||
export const useFlowStore = defineStore('flow', {
|
export const useFlowStore = defineStore('flow', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
disableForm: false,
|
disableForm: false
|
||||||
deviceList: []
|
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
updateDisableForm(value) {
|
updateDisableForm(value) {
|
||||||
this.disableForm = value
|
this.disableForm = value
|
||||||
},
|
|
||||||
|
|
||||||
async getDeviceList() {
|
|
||||||
const data = await new Promise((resolve, reject) => {
|
|
||||||
resolve( ['cam1', 'cam2', 'cam3', 'cam4'])
|
|
||||||
})
|
|
||||||
this.deviceList = data
|
|
||||||
return data
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,7 +47,7 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
initialTerminalId: {
|
initialRobotId: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
@ -60,7 +60,7 @@ const data = reactive({
|
|||||||
stereoModule: false,
|
stereoModule: false,
|
||||||
rgbCamera: false,
|
rgbCamera: false,
|
||||||
},
|
},
|
||||||
terminalId: null,
|
robotId: null,
|
||||||
cameraId: null,
|
cameraId: null,
|
||||||
type: 'camera',
|
type: 'camera',
|
||||||
callbacks: {},
|
callbacks: {},
|
||||||
@ -95,7 +95,7 @@ function getVideoRef(method) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function startDirectRgbStream() {
|
async function startDirectRgbStream() {
|
||||||
if (!data.terminalId || !data.cameraId || !colorVideo.value) return;
|
if (!data.robotId || !data.cameraId || !colorVideo.value) return;
|
||||||
rgbStreamAbortController?.abort();
|
rgbStreamAbortController?.abort();
|
||||||
rgbStreamAbortController = null;
|
rgbStreamAbortController = null;
|
||||||
const generation = ++rgbStreamGeneration;
|
const generation = ++rgbStreamGeneration;
|
||||||
@ -103,7 +103,7 @@ async function startDirectRgbStream() {
|
|||||||
rgbStreamRetryTimer = null;
|
rgbStreamRetryTimer = null;
|
||||||
streamStatus.getRGBImageStream = 'connecting';
|
streamStatus.getRGBImageStream = 'connecting';
|
||||||
try {
|
try {
|
||||||
const response = await getRgbStreamUrl(data.terminalId, data.cameraId);
|
const response = await getRgbStreamUrl(data.robotId, data.cameraId);
|
||||||
if (generation !== rgbStreamGeneration || !data.form.rgbCamera) return;
|
if (generation !== rgbStreamGeneration || !data.form.rgbCamera) return;
|
||||||
const relativeUrl = response.data;
|
const relativeUrl = response.data;
|
||||||
const baseUrl = import.meta.env.VITE_APP_BASE_API.replace(/\/$/, '');
|
const baseUrl = import.meta.env.VITE_APP_BASE_API.replace(/\/$/, '');
|
||||||
@ -342,11 +342,11 @@ function destroyMediaPlayer(method) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function channelFor(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) {
|
async function setSubscription(enabled, method, force = false) {
|
||||||
if (!socket || !data.terminalId || !data.cameraId) return;
|
if (!socket || !data.robotId || !data.cameraId) return;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
const channel = channelFor(method);
|
const channel = channelFor(method);
|
||||||
|
|
||||||
@ -401,7 +401,7 @@ function scheduleRestart(method) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleSocketOpen() {
|
function handleSocketOpen() {
|
||||||
if (data.form.stereoModule && data.terminalId && data.cameraId) {
|
if (data.form.stereoModule && data.robotId && data.cameraId) {
|
||||||
destroyMediaPlayer('getDepthImageStream');
|
destroyMediaPlayer('getDepthImageStream');
|
||||||
setSubscription(true, 'getDepthImageStream', true);
|
setSubscription(true, 'getDepthImageStream', true);
|
||||||
}
|
}
|
||||||
@ -418,12 +418,12 @@ socket?.on('open', handleSocketOpen);
|
|||||||
socket?.on('close', handleSocketClose);
|
socket?.on('close', handleSocketClose);
|
||||||
|
|
||||||
watch(() => data.form.stereoModule, async (newVal) => {
|
watch(() => data.form.stereoModule, async (newVal) => {
|
||||||
if (!data.terminalId || !data.cameraId) return;
|
if (!data.robotId || !data.cameraId) return;
|
||||||
await setSubscription(newVal, 'getDepthImageStream');
|
await setSubscription(newVal, 'getDepthImageStream');
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(() => data.form.rgbCamera, async (newVal) => {
|
watch(() => data.form.rgbCamera, async (newVal) => {
|
||||||
if (!data.terminalId || !data.cameraId) return;
|
if (!data.robotId || !data.cameraId) return;
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
await nextTick();
|
await nextTick();
|
||||||
await startDirectRgbStream();
|
await startDirectRgbStream();
|
||||||
@ -436,10 +436,19 @@ watch(
|
|||||||
() => props.initialDeviceId || route.path.split('/')[3],
|
() => props.initialDeviceId || route.path.split('/')[3],
|
||||||
async (deviceId) => {
|
async (deviceId) => {
|
||||||
if (!deviceId) return;
|
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 {
|
try {
|
||||||
const response = await getRegister(deviceId);
|
const response = await getRegister(deviceId);
|
||||||
data.cameraId = response.data.deviceCode;
|
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.stereoModule) await setSubscription(true, 'getDepthImageStream');
|
||||||
if (data.form.rgbCamera) await startDirectRgbStream();
|
if (data.form.rgbCamera) await startDirectRgbStream();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -455,7 +464,7 @@ onUnmounted(() => {
|
|||||||
stopDirectRgbStream();
|
stopDirectRgbStream();
|
||||||
['getDepthImageStream'].forEach(method => {
|
['getDepthImageStream'].forEach(method => {
|
||||||
clearTimeout(retryTimers.get(method));
|
clearTimeout(retryTimers.get(method));
|
||||||
if (data.terminalId && data.cameraId) {
|
if (data.robotId && data.cameraId) {
|
||||||
const channel = channelFor(method);
|
const channel = channelFor(method);
|
||||||
socket?.send({ type: 'channel_subscription', action: 'unsubscribe', channel });
|
socket?.send({ type: 'channel_subscription', action: 'unsubscribe', channel });
|
||||||
if (data.callbacks[channel]) socket.off(channel, data.callbacks[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 emit = defineEmits(['update:bottomSeriesData']);
|
||||||
const socket = inject('ws');
|
const socket = inject('ws');
|
||||||
const handCanvas = ref(null);
|
const handCanvas = ref(null);
|
||||||
const terminalId = ref(''); // 待您修改
|
const robotId = ref(''); // 待您修改
|
||||||
const deviceId = ref(''); // 待您修改
|
const deviceId = ref(''); // 待您修改
|
||||||
const frameData = ref({ count: 0, lastTime: 0 });
|
const frameData = ref({ count: 0, lastTime: 0 });
|
||||||
const isHandSeries = ref(false);
|
const isHandSeries = ref(false);
|
||||||
@ -29,7 +29,7 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
initialTerminalId: { // 示例 prop,用于从弹窗接收 terminalId
|
initialRobotId: { // 示例 prop,用于从弹窗接收 robotId
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
@ -37,7 +37,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
// 2. 使用 watchEffect 来响应 props 和路由的变化
|
// 2. 使用 watchEffect 来响应 props 和路由的变化
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
terminalId.value = props.initialTerminalId;
|
robotId.value = props.initialRobotId;
|
||||||
deviceId.value = props.initialDeviceId;
|
deviceId.value = props.initialDeviceId;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -225,11 +225,11 @@ const sensorMap = [
|
|||||||
|
|
||||||
// WebSocket 订阅
|
// WebSocket 订阅
|
||||||
const subscribeSensorData = async (sub) => {
|
const subscribeSensorData = async (sub) => {
|
||||||
if (!socket || !terminalId.value || !deviceId.value) {
|
if (!socket || !robotId.value || !deviceId.value) {
|
||||||
console.warn(`订阅失败: socket=${!!socket}, terminalId=${terminalId.value}, deviceId=${deviceId.value}`);
|
console.warn(`订阅失败: socket=${!!socket}, robotId=${robotId.value}, deviceId=${deviceId.value}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const channel = `edgeDexHandServiceImpl/getSensorDataStream/${terminalId.value}/${deviceId.value}`;
|
const channel = `edgeDexHandServiceImpl/getSensorDataStream/${robotId.value}/${deviceId.value}`;
|
||||||
console.log(sub ? '订阅' : '取消订阅', channel);
|
console.log(sub ? '订阅' : '取消订阅', channel);
|
||||||
socket.send({
|
socket.send({
|
||||||
type: 'channel_subscription',
|
type: 'channel_subscription',
|
||||||
|
|||||||
@ -35,7 +35,7 @@ const handImage = ref(null);
|
|||||||
const containerRef = ref(null); // 新增:引用父容器
|
const containerRef = ref(null); // 新增:引用父容器
|
||||||
const defaultHandImageInfo = { width: 891, height: 981 };
|
const defaultHandImageInfo = { width: 891, height: 981 };
|
||||||
const imageAspectRatio = defaultHandImageInfo.width / defaultHandImageInfo.height;
|
const imageAspectRatio = defaultHandImageInfo.width / defaultHandImageInfo.height;
|
||||||
const terminalId = ref('');
|
const robotId = ref('');
|
||||||
const deviceId = ref('');
|
const deviceId = ref('');
|
||||||
// 1. 定义 props
|
// 1. 定义 props
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -43,7 +43,7 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
initialTerminalId: { // 示例 prop,用于从弹窗接收 terminalId
|
initialRobotId: { // 示例 prop,用于从弹窗接收 robotId
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
@ -51,9 +51,9 @@ const props = defineProps({
|
|||||||
|
|
||||||
// 2. 使用 watchEffect 来响应 props 和路由的变化
|
// 2. 使用 watchEffect 来响应 props 和路由的变化
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
terminalId.value = props.initialTerminalId;
|
robotId.value = props.initialRobotId;
|
||||||
deviceId.value = props.initialDeviceId;
|
deviceId.value = props.initialDeviceId;
|
||||||
console.log(deviceId.value, terminalId.value,props)
|
console.log(deviceId.value, robotId.value,props)
|
||||||
});
|
});
|
||||||
|
|
||||||
const sliders = ref([
|
const sliders = ref([
|
||||||
@ -66,9 +66,9 @@ const sliders = ref([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
function updateSeriesData(value, i) {
|
function updateSeriesData(value, i) {
|
||||||
console.log(deviceId.value, terminalId.value)
|
console.log(deviceId.value, robotId.value)
|
||||||
loading.value = true;
|
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;
|
loading.value = false;
|
||||||
updateToSeriesData();
|
updateToSeriesData();
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
@ -131,7 +131,7 @@ const updateSliderPositions = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateToSeriesData = () => {
|
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);
|
const newTopSeriesData = res.data.handsList.map(item => item.force);
|
||||||
emit('update:topSeriesData', newTopSeriesData);
|
emit('update:topSeriesData', newTopSeriesData);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,11 +4,11 @@
|
|||||||
<div class="left-panel">
|
<div class="left-panel">
|
||||||
<div class="hand-container">
|
<div class="hand-container">
|
||||||
<!-- 确保这里使用的是 index.vue 中响应式的 deviceId ref -->
|
<!-- 确保这里使用的是 index.vue 中响应式的 deviceId ref -->
|
||||||
<LeftTopHand :initialDeviceId="deviceId" :initialTerminalId="terminalId" @update:topSeriesData="handleUpdateTopSeriesData" />
|
<LeftTopHand :initialDeviceId="deviceId" :initialRobotId="robotId" @update:topSeriesData="handleUpdateTopSeriesData" />
|
||||||
</div>
|
</div>
|
||||||
<div class="hand-container">
|
<div class="hand-container">
|
||||||
<!-- 确保这里使用的是 index.vue 中响应式的 terminalId ref -->
|
<!-- 确保这里使用的是 index.vue 中响应式的 robotId ref -->
|
||||||
<LeftBottomHand :initialDeviceId="deviceId" :initialTerminalId="terminalId" @update:bottomSeriesData="handleUpdateBottomSeriesData" />
|
<LeftBottomHand :initialDeviceId="deviceId" :initialRobotId="robotId" @update:bottomSeriesData="handleUpdateBottomSeriesData" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-panel">
|
<div class="right-panel">
|
||||||
@ -34,7 +34,7 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
initialTerminalId: {
|
initialRobotId: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
@ -62,7 +62,7 @@ const avgData = ref({
|
|||||||
|
|
||||||
// 声明组件内部使用的响应式变量
|
// 声明组件内部使用的响应式变量
|
||||||
const deviceId = ref("");
|
const deviceId = ref("");
|
||||||
const terminalId = ref("");
|
const robotId = ref("");
|
||||||
|
|
||||||
const handleUpdateTopSeriesData = (newData) => {
|
const handleUpdateTopSeriesData = (newData) => {
|
||||||
topSeriesData.value = newData;
|
topSeriesData.value = newData;
|
||||||
@ -77,19 +77,24 @@ const handleUpdateBottomSeriesData = ({ maxData: newMaxData, avgData: newAvgData
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
// 统一处理获取 register 数据的方法
|
// 统一处理获取 register 数据的方法
|
||||||
const fetchRegisterData = (currentDeviceId) => {
|
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) {
|
if (currentDeviceId) {
|
||||||
getRegister(currentDeviceId).then(res => {
|
getRegister(currentDeviceId).then(res => {
|
||||||
deviceId.value = res.data.deviceCode; // 再次更新,确保与 API 返回一致
|
deviceId.value = res.data.deviceCode; // 再次更新,确保与 API 返回一致
|
||||||
terminalId.value = res.data.idDeDeviceTerminalConfig;
|
robotId.value = res.data.idDeDeviceTerminalConfig;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error("Error fetching register:", error);
|
console.error("Error fetching register:", error);
|
||||||
// 可以根据需要重置 deviceId 和 terminalId
|
// 可以根据需要重置 deviceId 和 robotId
|
||||||
deviceId.value = "";
|
deviceId.value = "";
|
||||||
terminalId.value = "";
|
robotId.value = "";
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 如果 deviceId 为空,可以考虑重置 terminalId
|
// 如果 deviceId 为空,可以考虑重置 robotId
|
||||||
terminalId.value = "";
|
robotId.value = "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -107,6 +112,11 @@ watch(() => props.initialDeviceId, (newVal) => {
|
|||||||
// 注意:这里假设 route.path.split("/")[3] 是cameraId
|
// 注意:这里假设 route.path.split("/")[3] 是cameraId
|
||||||
// 如果你的路由结构不同,请调整这里的逻辑
|
// 如果你的路由结构不同,请调整这里的逻辑
|
||||||
watch(() => route.path, (newPath) => {
|
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];
|
const routeDeviceId = newPath.split("/")[3];
|
||||||
if (!deviceId.value && routeDeviceId) { // 只有当 deviceId 尚未从 props 或其他地方设置时才从路由获取
|
if (!deviceId.value && routeDeviceId) { // 只有当 deviceId 尚未从 props 或其他地方设置时才从路由获取
|
||||||
deviceId.value = routeDeviceId;
|
deviceId.value = routeDeviceId;
|
||||||
@ -115,10 +125,10 @@ watch(() => route.path, (newPath) => {
|
|||||||
}, { immediate: true }); // immediate: true 确保在组件挂载时也检查路由
|
}, { immediate: true }); // immediate: true 确保在组件挂载时也检查路由
|
||||||
|
|
||||||
|
|
||||||
// 如果需要 initialTerminalId prop 也影响 terminalId ref,可以添加一个 watch
|
// 如果需要 initialRobotId prop 也影响 robotId ref,可以添加一个 watch
|
||||||
watch(() => props.initialTerminalId, (newVal) => {
|
watch(() => props.initialRobotId, (newVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
terminalId.value = newVal;
|
robotId.value = newVal;
|
||||||
}
|
}
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -80,10 +80,12 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
// import { setServo } from '@/api/device/head'; // 假设接口
|
// import { setServo } from '@/api/device/head'; // 假设接口
|
||||||
|
|
||||||
const terminalId = ref('25449ff3fcc7b27da5f69462c5efcec2');
|
const route = useRoute();
|
||||||
const deviceId = ref('head1');
|
const robotId = ref(String(route.query.robotId || ''));
|
||||||
|
const deviceId = ref(String(route.query.deviceId || ''));
|
||||||
|
|
||||||
const servo1 = ref(0.5); // 当前 Yaw
|
const servo1 = ref(0.5); // 当前 Yaw
|
||||||
const servo2 = ref(0.5); // 当前 Pitch
|
const servo2 = ref(0.5); // 当前 Pitch
|
||||||
@ -151,7 +153,7 @@ const applyPitchFromPresets = () => {
|
|||||||
const sendServoValues = async () => {
|
const sendServoValues = async () => {
|
||||||
try {
|
try {
|
||||||
// await setServo({
|
// await setServo({
|
||||||
// terminalId: terminalId.value,
|
// robotId: robotId.value,
|
||||||
// deviceId: deviceId.value,
|
// deviceId: deviceId.value,
|
||||||
// servo1: servo1.value,
|
// servo1: servo1.value,
|
||||||
// servo2: servo2.value,
|
// servo2: servo2.value,
|
||||||
|
|||||||
@ -66,7 +66,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { Microphone, VideoPause, Mute, Mic, VideoPlay } from "@element-plus/icons-vue";
|
import { Microphone, VideoPause, Mute, Mic, VideoPlay } from "@element-plus/icons-vue";
|
||||||
import {
|
import {
|
||||||
startMicrophoneApi,
|
startMicrophoneApi,
|
||||||
@ -77,19 +77,27 @@ import {
|
|||||||
} from '@/api/device/microphone'
|
} from '@/api/device/microphone'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
|
import { getRobotDevicesByRobotId } from '@/api/inspection/robot'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
|
||||||
const terminalId = route.query.terminalId
|
const robotId = route.query.robotId
|
||||||
const deviceId = route.query.deviceId
|
const deviceId = route.query.deviceId
|
||||||
|
|
||||||
const recordStatus = ref('end');
|
const recordStatus = ref('end');
|
||||||
const isRecording = ref(false)
|
const isRecording = ref(false)
|
||||||
const list = []
|
const list = []
|
||||||
const audiosList = ref([])
|
const audiosList = ref([])
|
||||||
const speaker = ref('spk1')
|
const speaker = ref('')
|
||||||
const speakerOptions = ref(['spk1'])
|
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'
|
const fileName = new Date().getTime() + '.wav'
|
||||||
isRecording.value = true
|
isRecording.value = true
|
||||||
const res = await startMicrophoneApi({
|
const res = await startMicrophoneApi({
|
||||||
terminalId,
|
robotId,
|
||||||
deviceId,
|
deviceId,
|
||||||
filePath: `/home/share/record/audios/${terminalId}_${deviceId}_${fileName}`
|
filePath: `/home/share/record/audios/${robotId}_${deviceId}_${fileName}`
|
||||||
})
|
})
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
recordStatus.value = 'recording'
|
recordStatus.value = 'recording'
|
||||||
list.push(`${terminalId}_${deviceId}_${fileName}`)
|
list.push(`${robotId}_${deviceId}_${fileName}`)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -113,7 +121,7 @@ const startRecording = async () => {
|
|||||||
*/
|
*/
|
||||||
const pauseRecording = async () => {
|
const pauseRecording = async () => {
|
||||||
const res = await pauseMicrophoneApi({
|
const res = await pauseMicrophoneApi({
|
||||||
terminalId,
|
robotId,
|
||||||
deviceId
|
deviceId
|
||||||
})
|
})
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
@ -126,7 +134,7 @@ const pauseRecording = async () => {
|
|||||||
*/
|
*/
|
||||||
const recoverRecording = async () => {
|
const recoverRecording = async () => {
|
||||||
const res = await resumeMicrophoneApi({
|
const res = await resumeMicrophoneApi({
|
||||||
terminalId,
|
robotId,
|
||||||
deviceId
|
deviceId
|
||||||
})
|
})
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
@ -139,7 +147,7 @@ const recoverRecording = async () => {
|
|||||||
*/
|
*/
|
||||||
const stopRecording = async () => {
|
const stopRecording = async () => {
|
||||||
const res = await stopMicrophoneApi({
|
const res = await stopMicrophoneApi({
|
||||||
terminalId,
|
robotId,
|
||||||
deviceId
|
deviceId
|
||||||
})
|
})
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
@ -154,7 +162,7 @@ const stopRecording = async () => {
|
|||||||
*/
|
*/
|
||||||
const handlePlay = async (audioPath) => {
|
const handlePlay = async (audioPath) => {
|
||||||
const res = await playSpeaker({
|
const res = await playSpeaker({
|
||||||
terminalId,
|
robotId,
|
||||||
deviceId: speaker.value,
|
deviceId: speaker.value,
|
||||||
audioPath: `/home/share/record/audios/${audioPath}`
|
audioPath: `/home/share/record/audios/${audioPath}`
|
||||||
})
|
})
|
||||||
|
|||||||
@ -52,6 +52,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted } from 'vue';
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
import {
|
import {
|
||||||
pause,
|
pause,
|
||||||
play,
|
play,
|
||||||
@ -73,11 +74,12 @@ const currentIndex = ref(0);
|
|||||||
const isPlaying = ref(false);
|
const isPlaying = ref(false);
|
||||||
const volume = ref(0);
|
const volume = ref(0);
|
||||||
|
|
||||||
const terminalId = ref('25449ff3fcc7b27da5f69462c5efcec2');
|
const route = useRoute();
|
||||||
const deviceId = ref('spk1');
|
const robotId = ref(String(route.query.robotId || ''));
|
||||||
|
const deviceId = ref(String(route.query.deviceId || ''));
|
||||||
|
|
||||||
const getCommonParams = () => ({
|
const getCommonParams = () => ({
|
||||||
terminalId: terminalId.value,
|
robotId: robotId.value,
|
||||||
deviceId: deviceId.value
|
deviceId: deviceId.value
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,414 +1,184 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container robot-device-page">
|
||||||
<div ref="topContainerRef">
|
<section class="page-heading">
|
||||||
<TableSearch
|
<div>
|
||||||
:queryParams="queryParams"
|
<h2>机器人设备</h2>
|
||||||
:showSearch="showSearch"
|
<p>设备由机器人在线上报,切换机器人后自动刷新。</p>
|
||||||
label-width="80px"
|
</div>
|
||||||
queryRef="queryRef"
|
<div class="robot-selector">
|
||||||
@refresh="resetQuery"
|
<span>当前机器人</span>
|
||||||
@search="handleQuery"
|
<el-select v-model="selectedRobotId" filterable placeholder="请选择机器人"
|
||||||
>
|
:loading="robotLoading" @change="loadDevices">
|
||||||
<template #one>
|
<el-option v-for="robot in robotOptions" :key="robot.value"
|
||||||
<el-col :lg="6" :md="12" :sm="24" :xl="6" :xs="24" :xxl="6">
|
:label="robot.label" :value="robot.value" />
|
||||||
<el-form-item label="设备编号" prop="deviceCode">
|
</el-select>
|
||||||
<el-input
|
<el-button :icon="Refresh" circle title="刷新设备" :loading="loading" @click="loadDevices" />
|
||||||
v-model="queryParams.deviceCode"
|
</div>
|
||||||
clearable
|
</section>
|
||||||
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>
|
|
||||||
|
|
||||||
<el-row :gutter="10" class="mb8">
|
<section class="status-strip">
|
||||||
<el-col :span="1.5">
|
<div><strong>{{ devices.length }}</strong><span>设备总数</span></div>
|
||||||
<el-button
|
<div><strong class="online">{{ onlineCount }}</strong><span>在线设备</span></div>
|
||||||
v-hasPermi="['device:register:add']"
|
<div><strong class="fault">{{ faultCount }}</strong><span>故障设备</span></div>
|
||||||
icon="Plus"
|
<div class="filters">
|
||||||
plain
|
<el-input v-model="keyword" clearable :prefix-icon="Search" placeholder="搜索名称或设备 ID" />
|
||||||
type="primary"
|
<el-select v-model="deviceKind" clearable placeholder="全部类型">
|
||||||
@click="handleAdd"
|
<el-option v-for="item in deviceKinds" :key="item.value"
|
||||||
>新增
|
:label="item.label" :value="item.value" />
|
||||||
</el-button>
|
</el-select>
|
||||||
</el-col>
|
</div>
|
||||||
<el-col :span="1.5">
|
</section>
|
||||||
<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>
|
|
||||||
|
|
||||||
<div :style="containerHeight">
|
<el-table v-loading="loading" :data="filteredDevices" height="calc(100vh - 285px)"
|
||||||
<el-table height="100%" v-loading="loading" :data="registerList" @selection-change="handleSelectionChange">
|
empty-text="当前机器人尚未上报设备">
|
||||||
<el-table-column align="center" type="selection" width="55" />
|
<el-table-column label="设备" min-width="220">
|
||||||
<el-table-column align="center" label="id" prop="id" show-overflow-tooltip/>
|
<template #default="{ row }">
|
||||||
<el-table-column align="center" label="设备终端配置id" prop="idDeDeviceTerminalConfig" show-overflow-tooltip
|
<div class="device-name">
|
||||||
width="120"/>
|
<span class="device-icon"><el-icon><Cpu /></el-icon></span>
|
||||||
<el-table-column align="center" label="设备编号" prop="deviceCode" show-overflow-tooltip/>
|
<div><b>{{ row.deviceName || row.deviceId }}</b><small>{{ row.deviceId }}</small></div>
|
||||||
<el-table-column align="center" label="设备名称" prop="deviceName" show-overflow-tooltip/>
|
</div>
|
||||||
<!-- <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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<!-- <el-table-column label="设备厂家" align="center" prop="factory"/>-->
|
<el-table-column label="类型" width="130">
|
||||||
<!-- <el-table-column label="设备图片" align="center" prop="images"/>-->
|
<template #default="{ row }">{{ kindLabel(row.deviceKind, row.typeName) }}</template>
|
||||||
<!-- <el-table-column show-overflow-tooltip label="备注" align="center" prop="remake"/>-->
|
</el-table-column>
|
||||||
<el-table-column align="center" label="设备状态" prop="status">
|
<el-table-column label="运行状态" width="120" align="center">
|
||||||
<template #default="scope">
|
<template #default="{ row }">
|
||||||
<el-switch
|
<el-tag :type="row.onlineStatus === '1' ? 'success' : 'info'" effect="light">
|
||||||
v-model="scope.row.status"
|
{{ row.onlineStatus === '1' ? '在线' : '离线' }}
|
||||||
active-text="启用"
|
</el-tag>
|
||||||
active-value="0"
|
|
||||||
inactive-text="停用"
|
|
||||||
inactive-value="1"
|
|
||||||
inline-prompt
|
|
||||||
@change="handleChange(scope.row)"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="健康状态" width="120" align="center">
|
||||||
<el-table-column align="center" class-name="small-padding fixed-width" label="操作" width="200">
|
<template #default="{ row }">
|
||||||
<template #default="scope">
|
<el-tag :type="isFault(row) ? 'danger' : 'success'" effect="plain">
|
||||||
<el-button v-hasPermi="['device:register:edit']" icon="Edit" link type="primary"
|
{{ isFault(row) ? '故障' : '正常' }}
|
||||||
@click="handleUpdate(scope.row)">修改
|
</el-tag>
|
||||||
</el-button>
|
</template>
|
||||||
<el-button v-hasPermi="['device:register:deploy']" icon="Cpu" link type="primary"
|
</el-table-column>
|
||||||
@click="handleControl(scope.row)">示教
|
<el-table-column prop="errorMessage" label="状态信息" min-width="180" show-overflow-tooltip>
|
||||||
</el-button>
|
<template #default="{ row }">{{ row.errorMessage || '运行正常' }}</template>
|
||||||
<el-button v-hasPermi="['device:register:remove']" icon="Delete" link type="primary"
|
</el-table-column>
|
||||||
@click="handleDelete(scope.row)">删除
|
<el-table-column prop="lastSeenTime" label="最后上报" width="180" />
|
||||||
</el-button>
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script name="Register" setup>
|
<script setup>
|
||||||
import {addRegister, delRegister, getRegister, listRegister, updateRegister} from "@/api/device/register"
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import TableSearch from "@/components/TableSearch/index.vue"
|
import { useRouter } from 'vue-router'
|
||||||
import {useRouter} from "vue-router"
|
import { Cpu, Refresh, Search } from '@element-plus/icons-vue'
|
||||||
import {intoControlPage} from "@/views/device/register/components/index.js";
|
import { getRobotList, getRobotDevicesByRobotId } from '@/api/inspection/robot'
|
||||||
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 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 kindLabel = (kind, fallback) => deviceKinds.find(item => item.value === kind)?.label || fallback || '未知设备'
|
||||||
|
const isFault = (device) => device.hasError === '1' || Number(device.healthStatus) === 3
|
||||||
const registerList = ref([])
|
const onlineCount = computed(() => devices.value.filter(item => item.onlineStatus === '1').length)
|
||||||
const open = ref(false)
|
const faultCount = computed(() => devices.value.filter(isFault).length)
|
||||||
const loading = ref(true)
|
const filteredDevices = computed(() => {
|
||||||
const showSearch = ref(true)
|
const search = keyword.value.trim().toLowerCase()
|
||||||
const ids = ref([])
|
return devices.value.filter(item => {
|
||||||
const single = ref(true)
|
const matchesKind = deviceKind.value == null || item.deviceKind === deviceKind.value
|
||||||
const multiple = ref(true)
|
const matchesSearch = !search || `${item.deviceName || ''} ${item.deviceId || ''}`.toLowerCase().includes(search)
|
||||||
const total = ref(0)
|
return matchesKind && matchesSearch
|
||||||
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 {queryParams, form, rules} = toRefs(data)
|
const loadRobots = async () => {
|
||||||
|
robotLoading.value = true
|
||||||
/** 查询设备注册列表 */
|
try {
|
||||||
function getList() {
|
const response = await getRobotList({ pageNum: 1, pageSize: 1000 })
|
||||||
loading.value = true
|
robotOptions.value = (response.rows || []).map(robot => ({
|
||||||
listRegister(queryParams.value).then(response => {
|
value: robot.robotId,
|
||||||
registerList.value = response.rows
|
label: `${robot.robotName || robot.robotId} (${robot.robotId})`,
|
||||||
total.value = response.total
|
online: robot.connectStatus === '1'
|
||||||
loading.value = false
|
})).filter(item => item.value)
|
||||||
})
|
const preferred = robotOptions.value.find(item => item.online) || robotOptions.value[0]
|
||||||
}
|
selectedRobotId.value = preferred?.value || ''
|
||||||
|
} finally {
|
||||||
// 取消按钮
|
robotLoading.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
|
|
||||||
}
|
}
|
||||||
proxy.resetForm("registerRef")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
const loadDevices = async () => {
|
||||||
function handleQuery() {
|
devices.value = []
|
||||||
queryParams.value.pageNum = 1
|
if (!selectedRobotId.value) return
|
||||||
getList()
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const response = await getRobotDevicesByRobotId(selectedRobotId.value, { onlineOnly: false })
|
||||||
|
devices.value = Array.isArray(response.data) ? response.data : []
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
const controlRoute = (kind) => ({
|
||||||
function resetQuery() {
|
2: 'mechanical_arm', 4: 'head', 5: 'camera', 7: 'hand', 9: 'microphone', 13: 'speaker'
|
||||||
proxy.resetForm("queryRef")
|
})[kind]
|
||||||
handleQuery()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 多选框选中数据
|
const openControl = (device) => {
|
||||||
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({
|
router.push({
|
||||||
path: fullPath,
|
path: `/device/${controlRoute(device.deviceKind)}/${device.id}`,
|
||||||
query: {
|
query: { robotId: selectedRobotId.value, deviceId: device.deviceId }
|
||||||
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()
|
onMounted(async () => {
|
||||||
|
await loadRobots()
|
||||||
|
await loadDevices()
|
||||||
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>
|
</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>
|
<span class="control-icon close-icon" @click="closePopup(index)">×</span>
|
||||||
</div>
|
</div>
|
||||||
</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 top" @mousedown="startResizing($event, index, 'top')"></div>
|
||||||
<div class="resize-edge bottom" @mousedown="startResizing($event, index, 'bottom')"></div>
|
<div class="resize-edge bottom" @mousedown="startResizing($event, index, 'bottom')"></div>
|
||||||
<div class="resize-edge left" @mousedown="startResizing($event, index, 'left')"></div>
|
<div class="resize-edge left" @mousedown="startResizing($event, index, 'left')"></div>
|
||||||
@ -398,7 +398,7 @@ const openPopup = (area) => {
|
|||||||
originalTop: `${area.y}px`,
|
originalTop: `${area.y}px`,
|
||||||
originalLeft: `${area.x + leftOffset}px`,
|
originalLeft: `${area.x + leftOffset}px`,
|
||||||
isMaximized: false,
|
isMaximized: false,
|
||||||
props: { terminalId: 'your-terminal-id', cameraId: 'your-camera-id' } // 示例props
|
props: { robotId: area.robotId || '', cameraId: area.deviceId || '' }
|
||||||
};
|
};
|
||||||
popups.value.push(popup);
|
popups.value.push(popup);
|
||||||
isResizing.value.push(false);
|
isResizing.value.push(false);
|
||||||
|
|||||||
@ -11,6 +11,18 @@
|
|||||||
:rules="rules"
|
:rules="rules"
|
||||||
label-width="auto"
|
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
|
<el-table
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
style="width: 100%; margin-bottom: 20px"
|
style="width: 100%; margin-bottom: 20px"
|
||||||
@ -30,10 +42,10 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="value" label="入参值">
|
<el-table-column prop="value" label="入参值">
|
||||||
<template #default="{ row, column, $index }">
|
<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-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" style="width: 200px">
|
<el-select v-model="row.value" filterable style="width: 100%" @change="handleRobotChange">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in terminalIdOptions"
|
v-for="item in robotIdOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
:label="item.label"
|
:label="item.label"
|
||||||
:value="item.value"
|
:value="item.value"
|
||||||
@ -55,7 +67,7 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="handleClose">关闭</el-button>
|
<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>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
@ -66,7 +78,8 @@ import { getStartNodeFormData, formatTableData } from '@/utils/flow'
|
|||||||
import { flowExecuteTrial } from '@/api/flow/flow'
|
import { flowExecuteTrial } from '@/api/flow/flow'
|
||||||
import { emitter } from '@/utils/eventBus';
|
import { emitter } from '@/utils/eventBus';
|
||||||
import { ElMessage } from 'element-plus';
|
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({
|
const props = defineProps({
|
||||||
drawer: Boolean,
|
drawer: Boolean,
|
||||||
@ -76,6 +89,8 @@ const props = defineProps({
|
|||||||
const ruleFormRef = ref()
|
const ruleFormRef = ref()
|
||||||
const rules = ref({});
|
const rules = ref({});
|
||||||
const tableData = ref([])
|
const tableData = ref([])
|
||||||
|
const bindingLoading = ref(false)
|
||||||
|
const bindingSummary = ref({ robotId: '', matched: 0, missing: 0 })
|
||||||
|
|
||||||
const emits = defineEmits(['close', 'changeState'])
|
const emits = defineEmits(['close', 'changeState'])
|
||||||
|
|
||||||
@ -89,7 +104,8 @@ watch(() => props.drawer,
|
|||||||
if (newVal) {
|
if (newVal) {
|
||||||
const data = getStartNodeFormData()
|
const data = getStartNodeFormData()
|
||||||
tableData.value = data
|
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 flowData = JSON.stringify(lf.getGraphData())
|
||||||
const { terminalId, robotId, ...runParams } = formData
|
const { robotId, ...runParams } = formData
|
||||||
const res = await flowExecuteTrial({
|
const res = await flowExecuteTrial({
|
||||||
flowData,
|
flowData,
|
||||||
itemId: props.flowId,
|
itemId: props.flowId,
|
||||||
robotId: robotId || terminalId,
|
robotId,
|
||||||
runParams
|
runParams
|
||||||
})
|
})
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
@ -123,13 +139,69 @@ const confirm = () => {
|
|||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
const terminalIdOptions = ref([])
|
const robotIdOptions = ref([])
|
||||||
|
|
||||||
/** 获取设备终端列表 */
|
const handleRobotChange = async (robotId) => {
|
||||||
const handleGetTerminalGroup = async (row) => {
|
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' })
|
const res = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' })
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
terminalIdOptions.value = res.rows?.map((item) => {
|
robotIdOptions.value = res.rows?.map((item) => {
|
||||||
return {
|
return {
|
||||||
label: `${item.robotName || item.robotId} (${item.robotId})`,
|
label: `${item.robotName || item.robotId} (${item.robotId})`,
|
||||||
value: item.robotId
|
value: item.robotId
|
||||||
@ -139,3 +211,40 @@ const handleGetTerminalGroup = async (row) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</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>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<div class="form__container">
|
<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"
|
<el-form :inline="true" :model="formData" :rules="rules" ref="dynamicFormRef" label-position="top"
|
||||||
label-width="auto">
|
label-width="auto">
|
||||||
<div v-for="(property, index) in formData.nodeParams" :key="index">
|
<div v-for="(property, index) in formData.nodeParams" :key="index">
|
||||||
@ -82,15 +82,25 @@
|
|||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="robotFormAction === 'AGV_MOVE_TO_STATION' ? '加载AGV站点列表' : '获取位置信息'"
|
:title="robotDialogTitle"
|
||||||
width="360"
|
width="460"
|
||||||
>
|
>
|
||||||
<el-form ref="robotFormRef" :model="robotForm" :rules="robotFormRules" label-width="auto">
|
<div class="robot-device-tip">设备来自机器人最近一次在线上报,单个匹配设备会自动锁定。</div>
|
||||||
<el-form-item label="终端ID" prop="terminalId">
|
<el-form ref="robotFormRef" :model="robotForm" :rules="robotFormRules" label-width="90px">
|
||||||
<el-input v-model="robotForm.terminalId" />
|
<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>
|
||||||
<el-form-item label="设备ID" prop="deviceId">
|
<el-form-item label="执行设备" prop="deviceId">
|
||||||
<el-input v-model="robotForm.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-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@ -107,7 +117,9 @@ import FormItemRecursive from '../FormItemRecursive.vue'
|
|||||||
import { getInput } from '@/utils/flow'
|
import { getInput } from '@/utils/flow'
|
||||||
import { useQuote } from './useQuote.js'
|
import { useQuote } from './useQuote.js'
|
||||||
import { getArmStatus, getArmJointState, getAgvStatus, getAgvStations } from '@/api/flow/flow'
|
import { getArmStatus, getArmJointState, getAgvStatus, getAgvStations } from '@/api/flow/flow'
|
||||||
|
import { getRobotList, getRobotDevicesByRobotId } from '@/api/inspection/robot'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { getDeviceKindForAction, toDeviceOptions } from '@/utils/robotDevice'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: Object
|
data: Object
|
||||||
@ -201,13 +213,42 @@ const validateAndSave = async () => {
|
|||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const robotFormRef = ref(null)
|
const robotFormRef = ref(null)
|
||||||
const robotFormRules = reactive({
|
const robotFormRules = reactive({
|
||||||
terminalId: [{ required: true, message: '终端ID不能为空', trigger: 'blur' }],
|
robotId: [{ required: true, message: '请选择机器人', trigger: 'change' }],
|
||||||
deviceId: [{ required: true, message: '设备ID不能为空', trigger: 'blur' }]
|
deviceId: [{ required: true, message: '设备ID不能为空', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
const robotForm = reactive({
|
const robotForm = reactive({
|
||||||
deviceId: '',
|
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) => {
|
const testRule = (property) => {
|
||||||
if (property?.name === 'stationId' && props.data.properties.action === 'AGV_MOVE_TO_STATION') {
|
if (property?.name === 'stationId' && props.data.properties.action === 'AGV_MOVE_TO_STATION') {
|
||||||
@ -254,16 +295,28 @@ const testRule = (property) => {
|
|||||||
|
|
||||||
const robotFormAction = ref()
|
const robotFormAction = ref()
|
||||||
const stationListLoading = ref(false)
|
const stationListLoading = ref(false)
|
||||||
const robotActionButtonText = computed(() =>
|
const POSITION_ACTIONS = ['AGV_MOVE_TO_POINT', 'ARM_MOVE_TO_POINT', 'ARM_MOVE_TO_J']
|
||||||
props.data.properties.action === 'AGV_MOVE_TO_STATION' ? '加载站点列表' : '获取位置'
|
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 = () => {
|
const submitRobotForm = () => {
|
||||||
if (robotFormAction.value === 'AGV_MOVE_TO_POINT') {
|
if (robotFormAction.value === 'AGV_MOVE_TO_POINT') {
|
||||||
robotFormRef.value.validate(async (valid) => {
|
robotFormRef.value.validate(async (valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
try {
|
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) {
|
if (response && response.data) {
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
const { x, y, theta } = response.data.pose
|
const { x, y, theta } = response.data.pose
|
||||||
@ -292,7 +345,7 @@ const submitRobotForm = () => {
|
|||||||
try {
|
try {
|
||||||
const response = await getAgvStations({
|
const response = await getAgvStations({
|
||||||
deviceId: robotForm.deviceId,
|
deviceId: robotForm.deviceId,
|
||||||
terminalId: robotForm.terminalId
|
robotId: robotForm.robotId
|
||||||
})
|
})
|
||||||
const stationOptions = toStationOptions(response?.data)
|
const stationOptions = toStationOptions(response?.data)
|
||||||
if (stationOptions.length === 0) {
|
if (stationOptions.length === 0) {
|
||||||
@ -322,7 +375,7 @@ const submitRobotForm = () => {
|
|||||||
robotFormRef.value.validate(async (valid) => {
|
robotFormRef.value.validate(async (valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
try {
|
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) {
|
if (response && response.data) {
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
const { x, y, z, rx, ry, rz } = response.data
|
const { x, y, z, rx, ry, rz } = response.data
|
||||||
@ -353,7 +406,7 @@ const submitRobotForm = () => {
|
|||||||
try {
|
try {
|
||||||
const response = await getArmJointState({
|
const response = await getArmJointState({
|
||||||
deviceId: robotForm.deviceId,
|
deviceId: robotForm.deviceId,
|
||||||
terminalId: robotForm.terminalId
|
robotId: robotForm.robotId
|
||||||
})
|
})
|
||||||
if (response && response.data) {
|
if (response && response.data) {
|
||||||
const target = response.data.position || response.data.positions
|
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
|
dialogVisible.value = true
|
||||||
robotFormAction.value = action
|
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 })
|
defineExpose({ validateAndSave })
|
||||||
@ -467,4 +537,19 @@ defineExpose({ validateAndSave })
|
|||||||
cursor: pointer;
|
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>
|
</style>
|
||||||
|
|||||||
@ -14,29 +14,6 @@ import audioSvg from './icon/audio.svg'
|
|||||||
import touchSvg from './icon/touch.svg'
|
import touchSvg from './icon/touch.svg'
|
||||||
import expressionSvg from './icon/expression.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 = () => [
|
const booleanOptions = () => [
|
||||||
{ value: false, label: '否' },
|
{ value: false, label: '否' },
|
||||||
{ value: true, label: '是' }
|
{ value: true, label: '是' }
|
||||||
@ -119,7 +96,7 @@ export const collapseList = [
|
|||||||
action: 'CAMERA_START',
|
action: 'CAMERA_START',
|
||||||
nodeType: 'EDGE',
|
nodeType: 'EDGE',
|
||||||
nodeParams: [
|
nodeParams: [
|
||||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
|
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||||
],
|
],
|
||||||
outputParams: [],
|
outputParams: [],
|
||||||
outputType: 'json'
|
outputType: 'json'
|
||||||
@ -132,7 +109,7 @@ export const collapseList = [
|
|||||||
action: 'CAMERA_GETRGBIMAGE',
|
action: 'CAMERA_GETRGBIMAGE',
|
||||||
nodeType: 'EDGE',
|
nodeType: 'EDGE',
|
||||||
nodeParams: [
|
nodeParams: [
|
||||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
|
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||||
],
|
],
|
||||||
outputType: 'img',
|
outputType: 'img',
|
||||||
outputParams: [{ name: 'imageUrl', type: 'array<string>', desc: '图片地址数组', children: [], disabled: true }]
|
outputParams: [{ name: 'imageUrl', type: 'array<string>', desc: '图片地址数组', children: [], disabled: true }]
|
||||||
@ -145,7 +122,7 @@ export const collapseList = [
|
|||||||
action: 'CAMERA_STOP',
|
action: 'CAMERA_STOP',
|
||||||
nodeType: 'EDGE',
|
nodeType: 'EDGE',
|
||||||
nodeParams: [
|
nodeParams: [
|
||||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
|
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||||
],
|
],
|
||||||
outputParams: [],
|
outputParams: [],
|
||||||
outputType: 'json'
|
outputType: 'json'
|
||||||
@ -163,7 +140,7 @@ export const collapseList = [
|
|||||||
action: 'CAMERA_RECORDING_START',
|
action: 'CAMERA_RECORDING_START',
|
||||||
nodeType: 'EDGE',
|
nodeType: 'EDGE',
|
||||||
nodeParams: [
|
nodeParams: [
|
||||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
|
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||||
],
|
],
|
||||||
outputParams: [],
|
outputParams: [],
|
||||||
outputType: 'json'
|
outputType: 'json'
|
||||||
@ -176,7 +153,7 @@ export const collapseList = [
|
|||||||
action: 'CAMERA_RECORDING_STOP',
|
action: 'CAMERA_RECORDING_STOP',
|
||||||
nodeType: 'EDGE',
|
nodeType: 'EDGE',
|
||||||
nodeParams: [
|
nodeParams: [
|
||||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
|
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||||
],
|
],
|
||||||
outputType: 'video',
|
outputType: 'video',
|
||||||
outputParams: [{ name: 'videoUrl', type: 'array<string>', children: [], desc: '视频播放地址数组', disabled: true}]
|
outputParams: [{ name: 'videoUrl', type: 'array<string>', children: [], desc: '视频播放地址数组', disabled: true}]
|
||||||
@ -400,7 +377,7 @@ function handler(params) {
|
|||||||
action: 'INSPECTION_ALERT_LISTEN_START',
|
action: 'INSPECTION_ALERT_LISTEN_START',
|
||||||
nodeType: 'EDGE',
|
nodeType: 'EDGE',
|
||||||
nodeParams: [
|
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 }
|
{ name: "eventTypes", type: "input", input: "", componentType: 'select', selectOptions: inspectionEventTypes(), disabled: true }
|
||||||
],
|
],
|
||||||
outputParams: [],
|
outputParams: [],
|
||||||
@ -414,7 +391,7 @@ function handler(params) {
|
|||||||
action: 'INSPECTION_ALERT_LISTEN_STOP',
|
action: 'INSPECTION_ALERT_LISTEN_STOP',
|
||||||
nodeType: 'EDGE',
|
nodeType: 'EDGE',
|
||||||
nodeParams: [
|
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 }
|
{ name: "eventTypes", type: "input", input: "", componentType: 'select', selectOptions: inspectionEventTypes(), disabled: true }
|
||||||
],
|
],
|
||||||
outputParams: [],
|
outputParams: [],
|
||||||
@ -465,7 +442,7 @@ function handler(params) {
|
|||||||
action: 'MICROPHONE_START',
|
action: 'MICROPHONE_START',
|
||||||
nodeType: 'EDGE',
|
nodeType: 'EDGE',
|
||||||
nodeParams: [
|
nodeParams: [
|
||||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: audioOptions(), disabled: true }
|
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||||
],
|
],
|
||||||
outputType: 'json'
|
outputType: 'json'
|
||||||
},
|
},
|
||||||
@ -477,7 +454,7 @@ function handler(params) {
|
|||||||
action: 'MICROPHONE_STOP',
|
action: 'MICROPHONE_STOP',
|
||||||
nodeType: 'EDGE',
|
nodeType: 'EDGE',
|
||||||
nodeParams: [
|
nodeParams: [
|
||||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: audioOptions(), disabled: true }
|
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||||
],
|
],
|
||||||
outputType: 'json'
|
outputType: 'json'
|
||||||
},
|
},
|
||||||
@ -488,7 +465,7 @@ function handler(params) {
|
|||||||
desc: "用于播放音频的节点",
|
desc: "用于播放音频的节点",
|
||||||
action: 'SPEAKER_PLAYAUDIO',
|
action: 'SPEAKER_PLAYAUDIO',
|
||||||
nodeParams: [
|
nodeParams: [
|
||||||
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: audioOptions(), disabled: true }
|
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: [], disabled: true }
|
||||||
],
|
],
|
||||||
outputType: 'json'
|
outputType: 'json'
|
||||||
},
|
},
|
||||||
|
|||||||
@ -155,7 +155,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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 }">
|
<template #header="{ close, titleId, titleClass }">
|
||||||
<div class="my-header">
|
<div class="my-header">
|
||||||
<h4 :id="titleId" :class="titleClass">执行{{ nodeName }}节点</h4>
|
<h4 :id="titleId" :class="titleClass">执行{{ nodeName }}节点</h4>
|
||||||
@ -181,8 +181,9 @@
|
|||||||
:disabled="flowStore.disableForm"
|
:disabled="flowStore.disableForm"
|
||||||
>
|
>
|
||||||
<div v-for="(property, index) in formData.nodeParams" :key="index">
|
<div v-for="(property, index) in formData.nodeParams" :key="index">
|
||||||
<el-row>
|
<el-row class="single-node-param-row">
|
||||||
<el-form-item
|
<el-form-item
|
||||||
|
class="single-node-param-name"
|
||||||
:label="index === 0 ? '参数名' : ''"
|
:label="index === 0 ? '参数名' : ''"
|
||||||
:prop="`nodeParams.${index}.name`"
|
:prop="`nodeParams.${index}.name`"
|
||||||
:rules="[{ required: true, message: '请输入参数名', trigger: 'blur' }]"
|
:rules="[{ required: true, message: '请输入参数名', trigger: 'blur' }]"
|
||||||
@ -195,6 +196,7 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item
|
||||||
|
class="single-node-param-value"
|
||||||
:label="index === 0 ? '参数值' : ''"
|
:label="index === 0 ? '参数值' : ''"
|
||||||
:prop="`nodeParams.${index}.input`"
|
:prop="`nodeParams.${index}.input`"
|
||||||
:rules="[{ required: true, message: '请输入参数值', trigger: 'blur' }]"
|
:rules="[{ required: true, message: '请输入参数值', trigger: 'blur' }]"
|
||||||
@ -212,6 +214,8 @@
|
|||||||
<el-select
|
<el-select
|
||||||
v-model="property.input"
|
v-model="property.input"
|
||||||
v-else-if="property.componentType === 'select'"
|
v-else-if="property.componentType === 'select'"
|
||||||
|
:disabled="property.deviceLocked"
|
||||||
|
@change="property.name === 'robotId' && handleSingleNodeRobotChange(property.input)"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in property.selectOptions"
|
v-for="item in property.selectOptions"
|
||||||
@ -225,9 +229,13 @@
|
|||||||
v-model="property.input"
|
v-model="property.input"
|
||||||
autosize
|
autosize
|
||||||
type="textarea"
|
type="textarea"
|
||||||
|
resize="none"
|
||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
clearable
|
clearable
|
||||||
/>
|
/>
|
||||||
|
<div v-if="property.name === 'deviceId' && property.deviceLocked" class="device-lock-hint">
|
||||||
|
已自动匹配当前机器人设备
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
@ -269,7 +277,8 @@ import {
|
|||||||
getAgvStations,
|
getAgvStations,
|
||||||
} from "@/api/flow/flow";
|
} from "@/api/flow/flow";
|
||||||
import { getDetect } from "@/api/test/detect";
|
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 {
|
import {
|
||||||
CircleClose,
|
CircleClose,
|
||||||
CollectionTag,
|
CollectionTag,
|
||||||
@ -447,7 +456,6 @@ const handleEdgeClick = ({ event, edge }) => {
|
|||||||
const handlePaneClick = () => lf.clearSelectElements();
|
const handlePaneClick = () => lf.clearSelectElements();
|
||||||
|
|
||||||
const flowStore = useFlowStore();
|
const flowStore = useFlowStore();
|
||||||
flowStore.getDeviceList();
|
|
||||||
|
|
||||||
const isOpen = ref(false);
|
const isOpen = ref(false);
|
||||||
const stateMap = {
|
const stateMap = {
|
||||||
@ -899,6 +907,37 @@ const singleNodeDeviceId = computed(() =>
|
|||||||
formData.nodeParams.find((item) => item.name === "deviceId")?.input
|
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 : [])
|
const stationOptions = (stations) => (Array.isArray(stations) ? stations : [])
|
||||||
.filter((station) => station?.id !== undefined && station?.id !== null && String(station.id).trim())
|
.filter((station) => station?.id !== undefined && station?.id !== null && String(station.id).trim())
|
||||||
.map((station) => {
|
.map((station) => {
|
||||||
@ -977,7 +1016,7 @@ const execute = async () => {
|
|||||||
const correctSource = stationParam?.stationRobotId === singleNodeRobotId.value
|
const correctSource = stationParam?.stationRobotId === singleNodeRobotId.value
|
||||||
&& stationParam?.stationDeviceId === singleNodeDeviceId.value;
|
&& stationParam?.stationDeviceId === singleNodeDeviceId.value;
|
||||||
if (!validStation || !correctSource) {
|
if (!validStation || !correctSource) {
|
||||||
ElMessage.warning("请按当前终端和设备重新加载并选择站点");
|
ElMessage.warning("请按当前机器人和设备重新加载并选择站点");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1182,6 +1221,32 @@ onUnmounted(() => {
|
|||||||
<style lang="scss" scoped>
|
<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;
|
$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 {
|
.single-node-station-tools {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -1195,6 +1260,14 @@ $flow-canvas-cursor: url("data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.device-lock-hint {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 6px;
|
||||||
|
color: var(--el-color-success);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@ -68,6 +68,10 @@ const props = defineProps({
|
|||||||
robot: {
|
robot: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
|
},
|
||||||
|
deviceId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -103,8 +107,8 @@ const onPress = async (_dir) => {
|
|||||||
}
|
}
|
||||||
moveRobotData.value.vz = rotationAngle.value
|
moveRobotData.value.vz = rotationAngle.value
|
||||||
const res = await moveRobot({
|
const res = await moveRobot({
|
||||||
terminalId: props.robot.terminalId,
|
robotId: props.robot.robotId,
|
||||||
deviceId: props.robot.robotCode,
|
deviceId: props.deviceId,
|
||||||
...moveRobotData.value
|
...moveRobotData.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -113,8 +117,8 @@ function onRelease() {
|
|||||||
moveRobotData.value.vx = 0;
|
moveRobotData.value.vx = 0;
|
||||||
moveRobotData.value.vy = 0;
|
moveRobotData.value.vy = 0;
|
||||||
stopRobot({
|
stopRobot({
|
||||||
terminalId: props.robot.terminalId,
|
robotId: props.robot.robotId,
|
||||||
deviceId: props.robot.robotCode,
|
deviceId: props.deviceId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -150,6 +150,10 @@ const props = defineProps({
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
},
|
},
|
||||||
|
deviceId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'end'
|
default: 'end'
|
||||||
@ -174,8 +178,8 @@ const errorNum = ref(0)
|
|||||||
const getPose = async (robot) => {
|
const getPose = async (robot) => {
|
||||||
try {
|
try {
|
||||||
const res = await getPoseApi({
|
const res = await getPoseApi({
|
||||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
deviceId: props.deviceId,
|
||||||
terminalId: robot.terminalId
|
robotId: robot.robotId
|
||||||
})
|
})
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
const { x, y, z, rx, ry, rz } = res.data
|
const { x, y, z, rx, ry, rz } = res.data
|
||||||
@ -249,8 +253,8 @@ const move = async (axis, direction) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
speedLApi({
|
speedLApi({
|
||||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
deviceId: props.deviceId,
|
||||||
terminalId: props.robot.terminalId,
|
robotId: props.robot.robotId,
|
||||||
duration: 60,
|
duration: 60,
|
||||||
acceleration: speed.value + 0.1,
|
acceleration: speed.value + 0.1,
|
||||||
...speedData
|
...speedData
|
||||||
@ -262,14 +266,14 @@ const move = async (axis, direction) => {
|
|||||||
*/
|
*/
|
||||||
const stop = async () => {
|
const stop = async () => {
|
||||||
await stopMotionApi({
|
await stopMotionApi({
|
||||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
deviceId: props.deviceId,
|
||||||
terminalId: props.robot.terminalId
|
robotId: props.robot.robotId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const timer = ref(null)
|
const timer = ref(null)
|
||||||
watch(() => props.robot, (newRobot) => {
|
watch(() => props.robot, (newRobot) => {
|
||||||
if (newRobot && newRobot.terminalId) {
|
if (newRobot && newRobot.robotId) {
|
||||||
if (timer.value) {
|
if (timer.value) {
|
||||||
clearInterval(timer.value)
|
clearInterval(timer.value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -102,11 +102,14 @@ import { computed, onBeforeUnmount, ref } from 'vue'
|
|||||||
import { ElMessage, ElNotification } from 'element-plus'
|
import { ElMessage, ElNotification } from 'element-plus'
|
||||||
import { setMicVolume, getMicVolume, setSpeakerVolume, getSpeakerVolume } from '@/api/inspection/cockpit'
|
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。
|
// WebSocket 只承载控制 JSON 和二进制 PCM,不再使用 WebRTC、SDP 或 ICE。
|
||||||
const AUDIO_SERVER_URL = import.meta.env.VITE_AUDIO_SERVER_URL
|
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。
|
// 后端约定的 PCM 格式:S16LE、48kHz、单声道、10ms。
|
||||||
const PCM_SAMPLE_RATE = 48000
|
const PCM_SAMPLE_RATE = 48000
|
||||||
@ -324,9 +327,9 @@ function connectAudioServer(sessionToken) {
|
|||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
sendControlMessage({
|
sendControlMessage({
|
||||||
type: 'join',
|
type: 'join',
|
||||||
terminalId: TERMINAL_ID,
|
robotId: props.robotId,
|
||||||
speakerDeviceId: speakerDeviceId,
|
speakerDeviceId: props.speakerDeviceId,
|
||||||
micDeviceId: micDeviceId
|
micDeviceId: props.microphoneDeviceId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,6 +522,10 @@ async function setupAudioPipeline(stream) {
|
|||||||
*/
|
*/
|
||||||
async function startCall() {
|
async function startCall() {
|
||||||
if (isCalling.value || isStarting.value || webSocket.value) return
|
if (isCalling.value || isStarting.value || webSocket.value) return
|
||||||
|
if (!props.robotId || !props.speakerDeviceId || !props.microphoneDeviceId) {
|
||||||
|
ElMessage.warning('当前机器人缺少在线麦克风或扬声器')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const sessionToken = ++callSessionId
|
const sessionToken = ++callSessionId
|
||||||
isStarting.value = true
|
isStarting.value = true
|
||||||
@ -654,8 +661,8 @@ async function updateMicrophoneVolume(volume) {
|
|||||||
isMicrophoneVolumeLoading.value = true
|
isMicrophoneVolumeLoading.value = true
|
||||||
try {
|
try {
|
||||||
const response = await setMicVolume({
|
const response = await setMicVolume({
|
||||||
deviceId: micDeviceId,
|
deviceId: props.microphoneDeviceId,
|
||||||
terminalId: TERMINAL_ID,
|
robotId: props.robotId,
|
||||||
volume: Math.round(volume)
|
volume: Math.round(volume)
|
||||||
})
|
})
|
||||||
if (response.code !== 200) throw new Error(response.message || '设置机器人音量失败')
|
if (response.code !== 200) throw new Error(response.message || '设置机器人音量失败')
|
||||||
@ -675,8 +682,8 @@ async function updateSpeakerVolume(volume) {
|
|||||||
isSpeakerVolumeLoading.value = true
|
isSpeakerVolumeLoading.value = true
|
||||||
try {
|
try {
|
||||||
const response = await setSpeakerVolume({
|
const response = await setSpeakerVolume({
|
||||||
deviceId: speakerDeviceId,
|
deviceId: props.speakerDeviceId,
|
||||||
terminalId: TERMINAL_ID,
|
robotId: props.robotId,
|
||||||
volume: Math.round(volume)
|
volume: Math.round(volume)
|
||||||
})
|
})
|
||||||
if (response.code !== 200) throw new Error(response.message || '设置扬声器音量失败')
|
if (response.code !== 200) throw new Error(response.message || '设置扬声器音量失败')
|
||||||
@ -695,8 +702,8 @@ async function loadMicrophoneVolume({ silent = false } = {}) {
|
|||||||
isMicrophoneVolumeLoading.value = true
|
isMicrophoneVolumeLoading.value = true
|
||||||
try {
|
try {
|
||||||
const response = await getMicVolume({
|
const response = await getMicVolume({
|
||||||
deviceId: micDeviceId,
|
deviceId: props.microphoneDeviceId,
|
||||||
terminalId: TERMINAL_ID
|
robotId: props.robotId
|
||||||
})
|
})
|
||||||
if (response.code !== 200) throw new Error(response.message || '获取机器人音量失败')
|
if (response.code !== 200) throw new Error(response.message || '获取机器人音量失败')
|
||||||
const volume = Math.max(0, Math.min(100, Number(response.data) || 0))
|
const volume = Math.max(0, Math.min(100, Number(response.data) || 0))
|
||||||
@ -713,8 +720,8 @@ async function loadSpeakerVolume({ silent = false } = {}) {
|
|||||||
isSpeakerVolumeLoading.value = true
|
isSpeakerVolumeLoading.value = true
|
||||||
try {
|
try {
|
||||||
const response = await getSpeakerVolume({
|
const response = await getSpeakerVolume({
|
||||||
deviceId: speakerDeviceId,
|
deviceId: props.speakerDeviceId,
|
||||||
terminalId: TERMINAL_ID
|
robotId: props.robotId
|
||||||
})
|
})
|
||||||
if (response.code !== 200) throw new Error(response.message || '获取扬声器音量失败')
|
if (response.code !== 200) throw new Error(response.message || '获取扬声器音量失败')
|
||||||
const volume = Math.max(0, Math.min(100, Number(response.data) || 0))
|
const volume = Math.max(0, Math.min(100, Number(response.data) || 0))
|
||||||
|
|||||||
@ -38,7 +38,14 @@
|
|||||||
<el-radio :value="3">相机</el-radio>
|
<el-radio :value="3">相机</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
<div class="controller-panel">人工控制面板</div>
|
<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">
|
<div v-if="activeController === 2" class="robotic-arm-controller-container">
|
||||||
<el-radio-group v-model="activeRoboticArm" size="small">
|
<el-radio-group v-model="activeRoboticArm" size="small">
|
||||||
<el-radio :value="1">末端控制</el-radio>
|
<el-radio :value="1">末端控制</el-radio>
|
||||||
@ -47,6 +54,7 @@
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
|
|
||||||
<RoboticArm :type="activeRoboticArm === 1 ? 'end' : 'posture'" :robot="activeBotData"
|
<RoboticArm :type="activeRoboticArm === 1 ? 'end' : 'posture'" :robot="activeBotData"
|
||||||
|
:device-id="deviceByKind(2)"
|
||||||
v-if="[1, 3].includes(activeRoboticArm)" />
|
v-if="[1, 3].includes(activeRoboticArm)" />
|
||||||
|
|
||||||
<div class="control-panel" v-if="activeRoboticArm === 2">
|
<div class="control-panel" v-if="activeRoboticArm === 2">
|
||||||
@ -107,24 +115,36 @@
|
|||||||
<div class="split-line"></div>
|
<div class="split-line"></div>
|
||||||
<div class="container-title">语音对话</div>
|
<div class="container-title">语音对话</div>
|
||||||
<div class="voice-container">
|
<div class="voice-container">
|
||||||
<VoiceConversation />
|
<VoiceConversation :robot-id="activeBotData.robotId"
|
||||||
|
:microphone-device-id="deviceByKind(9)" :speaker-device-id="deviceByKind(13)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, nextTick } from "vue";
|
import { computed, onMounted, nextTick } from "vue";
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
import SvgIcon from "@/components/SvgIcon";
|
import SvgIcon from "@/components/SvgIcon";
|
||||||
import UrdfViewer from '../UrdfView.vue'
|
import UrdfViewer from '../UrdfView.vue'
|
||||||
import DirectionControl from './DirectionControl.vue'
|
import DirectionControl from './DirectionControl.vue'
|
||||||
import RoboticArm from "./RoboticArm.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 { getJointStateApi, moveJApi, torqueOnApi, speedJApi, stopMotionApi, clearFault } from '@/api/inspection/cockpit'
|
||||||
import { Remove, CirclePlus } from '@element-plus/icons-vue'
|
import { Remove, CirclePlus } from '@element-plus/icons-vue'
|
||||||
import VoiceConversation from "./VoiceConversation.vue";
|
import VoiceConversation from "./VoiceConversation.vue";
|
||||||
import IPlayer from "@/components/IPlayer/index.vue";
|
import IPlayer from "@/components/IPlayer/index.vue";
|
||||||
|
|
||||||
const robotList = ref([])
|
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 () => {
|
const fetchRobotList = async () => {
|
||||||
try {
|
try {
|
||||||
@ -134,6 +154,7 @@ const fetchRobotList = async () => {
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
robotList.value = res.rows
|
robotList.value = res.rows
|
||||||
activeBotData.value = robotList.value[0] || {}
|
activeBotData.value = robotList.value[0] || {}
|
||||||
|
await loadRobotDevices(activeBotData.value.robotId)
|
||||||
} else {
|
} else {
|
||||||
console.error('获取机器人列表失败:', res.message)
|
console.error('获取机器人列表失败:', res.message)
|
||||||
}
|
}
|
||||||
@ -151,9 +172,10 @@ const robotStatus = {
|
|||||||
|
|
||||||
const activeBot = ref(0)
|
const activeBot = ref(0)
|
||||||
const activeBotData = ref({})
|
const activeBotData = ref({})
|
||||||
const changeBot = (index) => {
|
const changeBot = async (index) => {
|
||||||
activeBot.value = index
|
activeBot.value = index
|
||||||
activeBotData.value = robotList.value[activeBot.value] || {}
|
activeBotData.value = robotList.value[activeBot.value] || {}
|
||||||
|
await loadRobotDevices(activeBotData.value.robotId)
|
||||||
}
|
}
|
||||||
|
|
||||||
const colorList = ['#00D4FF', '#FFB300', '#00FF88', '#fff']
|
const colorList = ['#00D4FF', '#FFB300', '#00FF88', '#fff']
|
||||||
@ -176,15 +198,19 @@ const animationId = ref(null)
|
|||||||
|
|
||||||
const handlerController = (value) => {
|
const handlerController = (value) => {
|
||||||
if (value === 2) {
|
if (value === 2) {
|
||||||
|
if (!deviceByKind(2)) {
|
||||||
|
ElMessage.warning('当前机器人没有在线机械臂')
|
||||||
|
return
|
||||||
|
}
|
||||||
// 开启机械臂使能
|
// 开启机械臂使能
|
||||||
torqueOnApi({
|
torqueOnApi({
|
||||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
deviceId: deviceByKind(2),
|
||||||
terminalId: activeBotData.value.terminalId
|
robotId: activeBotData.value.robotId
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('开启机械臂使能失败:', error)
|
console.error('开启机械臂使能失败:', error)
|
||||||
clearFault({
|
clearFault({
|
||||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
deviceId: deviceByKind(2),
|
||||||
terminalId: activeBotData.value.terminalId
|
robotId: activeBotData.value.robotId
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
jointControls.value = urdfViewerRef.value.jointControls
|
jointControls.value = urdfViewerRef.value.jointControls
|
||||||
@ -219,8 +245,8 @@ const errCount = ref(0)
|
|||||||
const getJointState = async (robot) => {
|
const getJointState = async (robot) => {
|
||||||
try {
|
try {
|
||||||
const res = await getJointStateApi({
|
const res = await getJointStateApi({
|
||||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
deviceId: deviceByKind(2),
|
||||||
terminalId: robot.terminalId
|
robotId: robot.robotId
|
||||||
})
|
})
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -256,8 +282,8 @@ const updateJoint = async (jointName, _dir) => {
|
|||||||
const index = jointControls.value.findIndex(joint => joint.name === jointName)
|
const index = jointControls.value.findIndex(joint => joint.name === jointName)
|
||||||
velocities[index] = jointSpeed.value * (_dir === '+' ? 1 : -1)
|
velocities[index] = jointSpeed.value * (_dir === '+' ? 1 : -1)
|
||||||
const res = await speedJApi({
|
const res = await speedJApi({
|
||||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
deviceId: deviceByKind(2),
|
||||||
terminalId: activeBotData.value.terminalId,
|
robotId: activeBotData.value.robotId,
|
||||||
acceleration: jointSpeed.value + 0.1,
|
acceleration: jointSpeed.value + 0.1,
|
||||||
duration: 60,
|
duration: 60,
|
||||||
velocities: velocities
|
velocities: velocities
|
||||||
@ -269,8 +295,8 @@ const updateJoint = async (jointName, _dir) => {
|
|||||||
*/
|
*/
|
||||||
const stop = async () => {
|
const stop = async () => {
|
||||||
await stopMotionApi({
|
await stopMotionApi({
|
||||||
deviceId: import.meta.env.VITE_INSPECTION_ARM_DEVICE_ID,
|
deviceId: deviceByKind(2),
|
||||||
terminalId: activeBotData.value.terminalId
|
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 {
|
.control-panel {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
background: #0F1E38;
|
background: #0F1E38;
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
<el-form-item label="执行机器人" prop="formData.robotId" :rules="[{ required: true, message: '请选择在线机器人', trigger: 'change' }]">
|
<el-form-item label="执行机器人" prop="formData.robotId" :rules="[{ required: true, message: '请选择在线机器人', trigger: 'change' }]">
|
||||||
<el-select v-model="formData.robotId" style="width: 260px" placeholder="请选择在线机器人">
|
<el-select v-model="formData.robotId" style="width: 260px" placeholder="请选择在线机器人">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in terminalIdOptions"
|
v-for="item in robotIdOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
:label="item.label"
|
:label="item.label"
|
||||||
:value="item.value"
|
:value="item.value"
|
||||||
@ -94,13 +94,13 @@ const formData = ref({
|
|||||||
tableData: []
|
tableData: []
|
||||||
})
|
})
|
||||||
|
|
||||||
const terminalIdOptions = ref([]);
|
const robotIdOptions = ref([]);
|
||||||
|
|
||||||
/** 获取设备终端列表 */
|
/** 获取设备终端列表 */
|
||||||
const handleGetTerminalGroup = async (row) => {
|
const handleGetTerminalGroup = async (row) => {
|
||||||
const res = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' });
|
const res = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' });
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
terminalIdOptions.value = res.rows?.map((item) => {
|
robotIdOptions.value = res.rows?.map((item) => {
|
||||||
return {
|
return {
|
||||||
label: `${item.robotName || item.robotId} (${item.robotId})`,
|
label: `${item.robotName || item.robotId} (${item.robotId})`,
|
||||||
value: item.robotId,
|
value: item.robotId,
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
<el-form-item label="执行机器人" prop="formData.robotId" :rules="[{ required: true, message: '请选择在线机器人', trigger: 'change' }]">
|
<el-form-item label="执行机器人" prop="formData.robotId" :rules="[{ required: true, message: '请选择在线机器人', trigger: 'change' }]">
|
||||||
<el-select v-model="formData.robotId" style="width: 260px" placeholder="请选择在线机器人">
|
<el-select v-model="formData.robotId" style="width: 260px" placeholder="请选择在线机器人">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in terminalIdOptions"
|
v-for="item in robotIdOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
:label="item.label"
|
:label="item.label"
|
||||||
:value="item.value"
|
:value="item.value"
|
||||||
@ -142,13 +142,13 @@ const confirm = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const terminalIdOptions = ref([]);
|
const robotIdOptions = ref([]);
|
||||||
|
|
||||||
/** 获取设备终端列表 */
|
/** 获取设备终端列表 */
|
||||||
const handleGetTerminalGroup = async (row) => {
|
const handleGetTerminalGroup = async (row) => {
|
||||||
const res = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' });
|
const res = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' });
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
terminalIdOptions.value = res.rows?.map((item) => {
|
robotIdOptions.value = res.rows?.map((item) => {
|
||||||
return {
|
return {
|
||||||
label: `${item.robotName || item.robotId} (${item.robotId})`,
|
label: `${item.robotName || item.robotId} (${item.robotId})`,
|
||||||
value: item.robotId,
|
value: item.robotId,
|
||||||
|
|||||||
@ -605,7 +605,7 @@ const handleTaskExecute = (formEl) => {
|
|||||||
}
|
}
|
||||||
const data = Object.assign(
|
const data = Object.assign(
|
||||||
{},
|
{},
|
||||||
{ runParams: { terminalId: selectedTerminal.value } },
|
{ runParams: { robotId: selectedTerminal.value } },
|
||||||
{ taskId: currentRow.value.id }
|
{ taskId: currentRow.value.id }
|
||||||
);
|
);
|
||||||
// taskExecute(data).then((res) => {
|
// taskExecute(data).then((res) => {
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
<el-form-item label="执行机器人" prop="formData.robotId" :rules="[{ required: true, message: '请选择在线机器人', trigger: 'change' }]">
|
<el-form-item label="执行机器人" prop="formData.robotId" :rules="[{ required: true, message: '请选择在线机器人', trigger: 'change' }]">
|
||||||
<el-select v-model="formData.robotId" style="width: 260px" placeholder="请选择在线机器人">
|
<el-select v-model="formData.robotId" style="width: 260px" placeholder="请选择在线机器人">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in terminalIdOptions"
|
v-for="item in robotIdOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
:label="item.label"
|
:label="item.label"
|
||||||
:value="item.value"
|
:value="item.value"
|
||||||
@ -92,13 +92,13 @@ const formData = ref({
|
|||||||
tableData: []
|
tableData: []
|
||||||
})
|
})
|
||||||
|
|
||||||
const terminalIdOptions = ref([]);
|
const robotIdOptions = ref([]);
|
||||||
|
|
||||||
/** 获取设备终端列表 */
|
/** 获取设备终端列表 */
|
||||||
const handleGetTerminalGroup = async (row) => {
|
const handleGetTerminalGroup = async (row) => {
|
||||||
const res = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' });
|
const res = await getRobotList({ pageNum: 1, pageSize: 1000, connectStatus: '1' });
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
terminalIdOptions.value = res.rows?.map((item) => {
|
robotIdOptions.value = res.rows?.map((item) => {
|
||||||
return {
|
return {
|
||||||
label: `${item.robotName || item.robotId} (${item.robotId})`,
|
label: `${item.robotName || item.robotId} (${item.robotId})`,
|
||||||
value: item.robotId,
|
value: item.robotId,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user