测权限控制 释放权限 优化ws连接

This commit is contained in:
mmc
2026-08-25 11:11:29 +08:00
parent d6b5c9ad44
commit a3b2f14620
9 changed files with 97 additions and 51 deletions

10
env.js
View File

@@ -9,17 +9,17 @@ const currentEnv = import.meta.env?.MODE || process.env.NODE_ENV;
// 环境配置映射
const envConfig = {
[ENV.DEV]: {
//baseURL: 'http://192.168.2.59:8081',//http://192.168.2.56:8081
baseURL: 'http://1.95.137.212:8081',//http://192.168.2.56:8081
////baseURL: 'http://1.95.137.212:59015',//http://192.168.2.56:8081
//WS_URL: 'ws://192.168.2.59:9002/ws/',
//mqtt_url: "ws://1.95.137.212:8083/mqtt",
//geoserver_url: "http://192.168.2.59:7000/geoserver"
baseURL: 'http://1.95.137.212:8081',//http://192.168.2.56:8081
//baseURL: 'http://192.168.2.34:8081',//http://192.168.2.56:8081
//baseURL: 'http://1.95.137.212:8081',//http://192.168.2.56:8081
//baseURL: 'http://192.168.2.62:8081',//http://192.168.2.56:8081
//baseURL: 'https://serviceri.satabot.com',
//WS_URL: 'ws://1.95.137.212:9002/ws/',
WS_URL: 'ws://192.168.2.34:9002/ws/',
WS_URL: 'ws://1.95.137.212:9002/ws/',
//WS_URL: 'ws://192.168.2.62:9002/ws/',
mqtt_url: "ws://1.95.137.212:8083/mqtt",
geoserver_url: "http://1.95.137.212:7900/geoserver"

View File

@@ -39,6 +39,8 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine,
const planLineRef = useRef(null); // points 规划航线
const navLineRef = useRef(null); // 完成点实时轨迹
const markedPointsRef = useRef([]); // 标记点实体
const { t } = useTranslation();
const realtimeEntityRef = useRef(null);
const realtimeLineRef = useRef(null);
@@ -343,8 +345,7 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine,
}
}, [markedPoints]);
function showRobotTooltip(tooltip, robot, position) {
const { t } = useTranslation();
function showRobotTooltip(tooltip, robot, position, t) {
const statusMap = {
1: { text: t('common.working'), color: "#10b981" },
2: { text: t('devices.idle'), color: "#f59e0b" },
@@ -1400,7 +1401,7 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine,
const picked = viewer.scene.pick(movement.endPosition);
if (window.Cesium.defined(picked) && picked.id?.properties?.robot) {
const robot = picked.id.properties.robot.getValue();
showRobotTooltip(tooltipRef.current, robot, movement.endPosition);
showRobotTooltip(tooltipRef.current, robot, movement.endPosition, t);
} else {
tooltipRef.current.style.display = "none";
}

View File

@@ -459,8 +459,17 @@ const RobotControlCenter = ({ setView }) => {
}
const getControl = (serialnum) => {
setIsConnecting(true);
let userParse = null;
try {
const dataStr = localStorage.getItem("userInfo");
userParse = dataStr ? JSON.parse(dataStr) : null;
} catch (e) {
console.error("用户信息解析失败", e);
}
const data = { platform: "web", deviceId: serialnum };
const token = userParse.token || Cookies.get("Admin-Token");
const data = { platform: "web", deviceId: serialnum, token: token };
getJurisdiction(data).then(res => {
if (res?.data?.code === 200 && res?.data?.data?.remoteControl == true) {
console.log("获取远程遥控权限成功");

View File

@@ -1,4 +1,4 @@
import React, {
import React, {
useState,
useEffect,
useMemo,
@@ -78,6 +78,7 @@ export default function DeviceStatusPage() {
const [arrivedPoint, setArrivedPoint] = useState(null);//完成点
const [isLoopMode, setIsLoopMode] = useState(true);
const controllerRef = useRef<DeviceController | null>(null);
const isFirstConnect = useRef(true);
const [messageApi, contextHolder] = message.useMessage();
@@ -388,7 +389,6 @@ export default function DeviceStatusPage() {
const runningStatus = useMemo(() => {
console.log(locationInfo, "locationInfo")
// 判断对象是否为空
if ((!wsDeviceInfo || Object.keys(wsDeviceInfo).length === 0) && (!locationInfo || Object.keys(locationInfo).length === 0) && !isUAV) {
return {};
@@ -408,7 +408,6 @@ export default function DeviceStatusPage() {
...locationInfo
};
}, [device, wsDeviceInfo, isUAV, locationInfo]);
console.log(runningStatus, "runningStatus")
const { video: robotVideo, error, ready } = useWebRTCPlayer(
(!isUAV && isVideoPlaying) ? (device?.serialNumber || '') : '',
@@ -485,14 +484,19 @@ export default function DeviceStatusPage() {
useEffect(() => {
if (!device || !controllerRef.current || !device?.serialNumber || isUAV) {
//controllerRef.current?.destroy();
//setWsDeviceInfo({});
return;
}
setWsDeviceInfo({});
setLocationInfo({});
controllerRef.current.connectDevice(device);
if (isFirstConnect.current) {
isFirstConnect.current = false;
controllerRef.current.connectDevice(device);
} else {
controllerRef.current.switchDevice(device);
}
}, [device?.serialNumber]);

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef, useMemo } from 'react';
import React, { useState, useEffect, useRef, useMemo } from 'react';
import {
Row, Col, Card, Button, Space, Table, Tag, Typography, Progress, Badge,
Tabs, Input, Switch, Divider, Tooltip, Timeline, Select, message, DatePicker,
@@ -141,6 +141,7 @@ export default function RobotTaskPage() {
});
const [messageApi, contextHolder] = message.useMessage();
const controllerRef = useRef<DeviceController | null>(null);
const isFirstConnect = useRef(true);
const handleFinish = (data) => {
setExecute(false);
setShowVideo(false); // 任务完成关闭视频
@@ -240,7 +241,7 @@ export default function RobotTaskPage() {
setExecute(true);
setExecutingRobot(targetDevice);
setClearNavLine(v => v + 1); // 清空地图轨迹
controllerRef.current?.connectDevice(targetDevice);
if (isFirstConnect.current) { isFirstConnect.current = false; controllerRef.current?.connectDevice(targetDevice); } else { controllerRef.current?.switchDevice(targetDevice); }
setShowVideo(true);
};
@@ -300,7 +301,7 @@ export default function RobotTaskPage() {
setExecute(true);
setExecutingRobot(targetDevice);
setClearNavLine(v => v + 1); // 清空地图轨迹
controllerRef.current?.connectDevice(targetDevice);
if (isFirstConnect.current) { isFirstConnect.current = false; controllerRef.current?.connectDevice(targetDevice); } else { controllerRef.current?.switchDevice(targetDevice); }
setShowVideo(true);
}, [devices, taskPoolList, executingRobot, runningPoolTask]);
@@ -450,7 +451,7 @@ export default function RobotTaskPage() {
setExecute(true);
setExecutingRobot(targetDevice);
setClearNavLine(v => v + 1); // 清空地图轨迹
controllerRef.current?.connectDevice(targetDevice);
if (isFirstConnect.current) { isFirstConnect.current = false; controllerRef.current?.connectDevice(targetDevice); } else { controllerRef.current?.switchDevice(targetDevice); }
setShowVideo(true);
}
}
@@ -556,7 +557,7 @@ export default function RobotTaskPage() {
fetchTaskList();
fetchTaskPoolList();
fetchTaskHistoryList();
controllerRef.current?.connectDevice(selectedRobotForExecute);
if (isFirstConnect.current) { isFirstConnect.current = false; controllerRef.current?.connectDevice(selectedRobotForExecute); } else { controllerRef.current?.switchDevice(selectedRobotForExecute); }
setShowVideo(true); // 连接成功后自动打开视频
} else {

View File

@@ -116,7 +116,7 @@ const XboxController = ({ onGamepadUpdate, handleControlModal, isControlled, isC
return (
<div className="relative h-[252px] aspect-[4/3] flex items-center justify-center "
onClick={() => {
if (connected && !isControlled && hasControlRight) {
if (!isControlled) {
console.log("点击了无控制权限,发起权限请求");
// 示例:调用你代码中已有的权限请求弹窗逻辑
handleControlModal({ control: true, type: "switchPermission" });
@@ -125,7 +125,7 @@ const XboxController = ({ onGamepadUpdate, handleControlModal, isControlled, isC
{/* Gamepad Status Badge */}
<div
className={`absolute top-0 right-0 px-2 py-0.5 rounded text-[8px] font-bold tracking-widest border
${connected || !hasControlRight
${connected
? isConnecting
? 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30'
: isControlled
@@ -137,8 +137,6 @@ const XboxController = ({ onGamepadUpdate, handleControlModal, isControlled, isC
>
{!connected ? (
t('devices.controllerNotConnected')
) : !hasControlRight ? (
t('devices.otherWebConnected')
) : isConnecting ? (
t('devices.control2')
) : isControlled ? (

View File

@@ -1,7 +1,6 @@
import throttle from 'lodash/throttle';
import { wsManager } from '../WebSocketManager.ts';
import {
getControlRight,
getJurisdiction,
releaseControl
} from '../../api/device.ts';
@@ -63,11 +62,15 @@ export default class DeviceController {
document.addEventListener('visibilitychange', this.handleVisibilityChange);
document.addEventListener('beforeunload', this.handleBeforeUnload);
}
levae() {
wsManager.close();
}
handleVisibilityChange() {
if (document.visibilityState === 'hidden') {
console.log('?? 页<>面进入后台,释放控制权限');
console.log('Page hidden, release control');
//this.release();
wsManager.close();
this.connectedDeviceSn = null;
@@ -76,7 +79,7 @@ export default class DeviceController {
}
if (document.visibilityState === 'visible' && this.currentDevice) {
if (!this.currentDevice?.serialNumber) return;
console.log('? 页面切回前台,自动重连 WebSocket');
console.log('Page visible, reconnect WebSocket');
setTimeout(() => {
this.connectDevice(this.currentDevice);
}, 500);
@@ -84,7 +87,7 @@ export default class DeviceController {
}
handleBeforeUnload() {
console.log('?? 浏览器即将关闭/刷新,释放控制权限');
console.log('Browser closing/refreshing, release control');
this.release();
}
@@ -95,35 +98,30 @@ export default class DeviceController {
this.wsStatus !== 'connected' &&
this.currentDevice?.serialNumber
) {
console.log('?? 后台保活:重新连接 WebSocket');
console.log('Keep alive: reconnect WebSocket');
this.connectDevice(this.currentDevice);
}
}, 8000);
}
async release() {
console.log('?? 释放控制权限');
//if (this.isControlled && this.connectedDeviceSn) {
if (!this.connectedDeviceSn) return;
const data = {
platform: 'web',
type: 1,
deviceId: this.connectedDeviceSn,
token: this.userInfo.token,
};
const url = `${envConfig.baseURL}/forward/device/releaseControl`;
console.log('?? 释放控制权限1111');
try {
await releaseControl(data);
console.log('?? 释放控制权限1111222');
const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
navigator.sendBeacon(url, blob);
console.log('✅ 已发送 releaseControl 请求');
console.log('releaseControl sent');
} catch (e) {
console.error('Failed to send releaseControl beacon:', e);
console.error('Failed to send releaseControl:', e);
}
//}
}
destroy() {
@@ -138,6 +136,7 @@ export default class DeviceController {
document.removeEventListener('beforeunload', this.handleBeforeUnload);
this.release();
this.levae();
this.unSubMessage?.();
this.unSubStatus?.();
@@ -172,6 +171,7 @@ export default class DeviceController {
wsManager.close();
try {
console.log('Connecting to', this.wsUrl);
wsManager.connect(sn, this.wsUrl);
this.connectedDeviceSn = sn;
@@ -182,11 +182,42 @@ export default class DeviceController {
}
}
async switchDevice(device) {
if (!device) return;
if (!device.serialNumber) return;
const newSn = device.serialNumber;
if (this.connectedDeviceSn === newSn && this.wsStatus === 'connected') {
console.log('switchDevice: same device, skip');
return;
}
console.log('Connecting to switchDevice: switching to', newSn);
this.currentDevice = device;
this.connectedDeviceSn = newSn;
this.wsDeviceInfo = {};
this.locationInfo = {};
this.isControlled = false;
this.onControlStatusChange?.(false);
wsManager.send({
type: 'AUTH',
token: this.userInfo.token,
userName: this.userInfo.username,
deviceId: newSn,
});
await this.getControl(newSn);
}
async getControl(serialnum) {
try {
const res = await getJurisdiction({
platform: 'web',
deviceId: serialnum,
token: this.userInfo.token,
});
this.isControlled = res?.code === 200 && res?.data?.remoteControl === true;
@@ -200,10 +231,7 @@ export default class DeviceController {
requestControl() {
if (this.isRequestControl) return;
if (!this.hasControlRight) {
this.messageApi.warning(i18n.t('devices.noneControl'));
return;
}
this.isRequestControl = true;
this.onRequestControlStatusChange?.(true);
@@ -246,7 +274,7 @@ export default class DeviceController {
}
handleWsMessage(msg) {
console.log('?? 收到消息:', msg);
console.log('WS message:', msg);
switch (msg.type) {
case 'deviceInfo': {
const data = {};
@@ -280,7 +308,7 @@ export default class DeviceController {
this.isRequestControl = false;
this.onRequestControlStatusChange?.(false);
if (msg.success === false || msg?.respond?.switchResult === false) {
if (msg?.success === false || msg?.respond?.switchResult === false) {
this.isControlled = false;
this.onControlStatusChange?.(false);
this.messageApi.error({ key: 'control', content: msg.message || i18n.t('devices.controlFailed') });
@@ -294,15 +322,14 @@ export default class DeviceController {
case "device_task_change": {
if (msg.deviceId != this.connectedDeviceSn) return;
this.onIsFinished?.(true);
console.log('?? 任务结束收到,', msg);
console.log('task change:', msg);
break;
}
case "device_task_arrive_point": {
if (msg.deviceId != this.connectedDeviceSn) return;
this.handleArrivedPoint?.(msg.entity);
console.log('?? 收到抵达,', msg);
console.log('arrive point:', msg);
break;
}
case 'switchPermission': {

View File

@@ -659,6 +659,8 @@ const en = {
optimize: 'Optimize',
optionalEmpty: 'Optional Empty',
otherWebControlling: 'Other Web Controlling',
platformRequestControl: "{{platform}} platformRequestControl",
outDock: 'Out Dock',
outdoor: 'Outdoor',
panorama: 'Panorama',
@@ -1285,9 +1287,10 @@ const en = {
failedFixed22: '权限校验 Failed Fixed2',
noneControl: 'None Control 权限',
noneControl: 'None Control jurisdiction',
fixed2ControlFixed2: 'Fixed2 Control 请求 超时 Fixed2',
controlFailed:"请求被拒绝",
},
video: {
onlineVideo: 'Online Video',

View File

@@ -1302,6 +1302,9 @@ const zh = {
cancel: "取消",
UnitsRobot: "台",
UnitsDrone: "架",
platformRequestControl: "{{platform}} 正在请求控制权限",
controlFailed: "controlFailed",
},
video: {
onlineVideo: '在线视频',