测试优化实时监控中心,接收全部的机器信息 ,优化显示图标的问题,优化且任务的时候显示 规划路径
This commit is contained in:
@@ -5,7 +5,7 @@ import { Badge, message } from "antd";
|
|||||||
AgoraRTC.setLogLevel(2); // 2=只输出warning/error,屏蔽debug日志
|
AgoraRTC.setLogLevel(2); // 2=只输出warning/error,屏蔽debug日志
|
||||||
|
|
||||||
const APP_ID = "7887b4db6033410d9ac3264b40ca9c1a";
|
const APP_ID = "7887b4db6033410d9ac3264b40ca9c1a";
|
||||||
const TOKEN = "007eJxTYDhxaLPL8617mtfHFi2tfJhQNSXh5vNJnqvu8b/3PenKKdumwGBuYWGeZJKSZGZgbGxiaJBimZhsbGRmkmRikJxomWyYuHHFiqyGQEaGB6+fMDEyQCCIz81gYqhbklGUmphrYMTAAAAyfSTX";
|
const TOKEN = "007eJxTYPh6QSj59sUzD3mPNm64dK9n/n1PP64UzrhzdwUeqMUKv1BVYDC3sDBPMklJMjMwNjYxNEixTEw2NjIzSTIxSE60TDZMlOdYldUQyMiQXKbEwAiFID43g4mhbklGUWpiroExAwMAjGAhHQ==";
|
||||||
|
|
||||||
export default function AgrtcRoomRobot({ CHANNEL }: { CHANNEL: string }) {
|
export default function AgrtcRoomRobot({ CHANNEL }: { CHANNEL: string }) {
|
||||||
const [uid] = useState(() => Number(Math.floor(Math.random() * 100000)));
|
const [uid] = useState(() => Number(Math.floor(Math.random() * 100000)));
|
||||||
@@ -22,7 +22,7 @@ export default function AgrtcRoomRobot({ CHANNEL }: { CHANNEL: string }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const client = AgoraRTC.createClient({
|
const client = AgoraRTC.createClient({
|
||||||
mode: "rtc",
|
mode: "live",
|
||||||
codec: "h264",
|
codec: "h264",
|
||||||
});
|
});
|
||||||
clientRef.current = client;
|
clientRef.current = client;
|
||||||
@@ -145,7 +145,7 @@ export default function AgrtcRoomRobot({ CHANNEL }: { CHANNEL: string }) {
|
|||||||
|
|
||||||
const onUserUnpublished = (user: any, mediaType: "video" | "audio") => {
|
const onUserUnpublished = (user: any, mediaType: "video" | "audio") => {
|
||||||
if (isUnmounted) return;
|
if (isUnmounted) return;
|
||||||
console.log("user-unpublished", user.uid, mediaType);
|
console.log("user-unpublished", user.uid, mediaType, subscribedUidRef.current);
|
||||||
if (mediaType === "video" && subscribedUidRef.current === user.uid) {
|
if (mediaType === "video" && subscribedUidRef.current === user.uid) {
|
||||||
if (unpublishTimer) clearTimeout(unpublishTimer);
|
if (unpublishTimer) clearTimeout(unpublishTimer);
|
||||||
// 延迟2s清空,短闪断自动恢复不黑屏
|
// 延迟2s清空,短闪断自动恢复不黑屏
|
||||||
|
|||||||
@@ -795,7 +795,6 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine,
|
|||||||
};
|
};
|
||||||
|
|
||||||
function showCoordinateTooltip(tooltip, position, t, lon, lat, robot) {
|
function showCoordinateTooltip(tooltip, position, t, lon, lat, robot) {
|
||||||
console.log('showCoordinateTooltip', position, lon, lat, robot);
|
|
||||||
tooltip.style.display = "block";
|
tooltip.style.display = "block";
|
||||||
tooltip.style.left = position.x + 12 + "px";
|
tooltip.style.left = position.x + 12 + "px";
|
||||||
tooltip.style.top = position.y + 12 + "px";
|
tooltip.style.top = position.y + 12 + "px";
|
||||||
@@ -1309,7 +1308,7 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine,
|
|||||||
if (focusLocation.type === "plane" || focusLocation.type === "drone") {
|
if (focusLocation.type === "plane" || focusLocation.type === "drone") {
|
||||||
image = planeImg;
|
image = planeImg;
|
||||||
} else if (focusLocation.type === "robot") {
|
} else if (focusLocation.type === "robot") {
|
||||||
image = droneInspectImg;
|
image = carImg;
|
||||||
} else if (focusLocation.type === "mower") {
|
} else if (focusLocation.type === "mower") {
|
||||||
image = carImg;
|
image = carImg;
|
||||||
}
|
}
|
||||||
@@ -1739,7 +1738,6 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine,
|
|||||||
// ==============================
|
// ==============================
|
||||||
|
|
||||||
// 光圈颜色:工作中蓝 / 空闲绿 / 离线红
|
// 光圈颜色:工作中蓝 / 空闲绿 / 离线红
|
||||||
const haloColor = getDeviceHaloColor(device);
|
|
||||||
|
|
||||||
let entity = entityMapRef.current.get(id);
|
let entity = entityMapRef.current.get(id);
|
||||||
|
|
||||||
@@ -1781,6 +1779,7 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine,
|
|||||||
// 设备底部状态光晕:比机器稍大一点
|
// 设备底部状态光晕:比机器稍大一点
|
||||||
// ==============================
|
// ==============================
|
||||||
const haloId = `${id}_halo`;
|
const haloId = `${id}_halo`;
|
||||||
|
|
||||||
const haloColor = getDeviceHaloColor(device);
|
const haloColor = getDeviceHaloColor(device);
|
||||||
|
|
||||||
// 尺寸可根据设备类型微调:车辆类稍大,IOT/相机类稍小
|
// 尺寸可根据设备类型微调:车辆类稍大,IOT/相机类稍小
|
||||||
|
|||||||
@@ -1236,7 +1236,7 @@ export default function DeviceStatusPage() {
|
|||||||
) : mowerView === 'front' ? (
|
) : mowerView === 'front' ? (
|
||||||
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
|
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
|
||||||
{/*<AgrtcRoomRobot CHANNEL={device?.serialNumber || ''} />*/}
|
{/*<AgrtcRoomRobot CHANNEL={device?.serialNumber || ''} />*/}
|
||||||
<AgrtcRoomRobot CHANNEL={"41-thream02"} />
|
<AgrtcRoomRobot CHANNEL={"41-thream03"} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -241,6 +241,7 @@ export default function RobotTaskPage() {
|
|||||||
const handlePoolTaskClick = (record) => {
|
const handlePoolTaskClick = (record) => {
|
||||||
setRunningPoolTask(record);
|
setRunningPoolTask(record);
|
||||||
loadTaskPath(record.id);
|
loadTaskPath(record.id);
|
||||||
|
fetchRouteDetail(record.id);
|
||||||
|
|
||||||
// 查找对应的设备
|
// 查找对应的设备
|
||||||
const targetDevice = devices?.find(d => d.serialNumber === record.deviceId);
|
const targetDevice = devices?.find(d => d.serialNumber === record.deviceId);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import { wsManager } from '../WebSocketManager.ts';
|
import { wsManager } from '../WebSocketManager.ts';
|
||||||
import {
|
import {
|
||||||
getJurisdiction,
|
getJurisdiction,
|
||||||
@@ -9,9 +10,10 @@ import envConfig from '../../../env';
|
|||||||
import i18n from '../../i18n';
|
import i18n from '../../i18n';
|
||||||
|
|
||||||
export default class DeviceController {
|
export default class DeviceController {
|
||||||
constructor(userInfo, messageApi) {
|
constructor(userInfo, messageApi, siteId) {
|
||||||
this.userInfo = userInfo;
|
this.userInfo = userInfo;
|
||||||
this.messageApi = messageApi;
|
this.messageApi = messageApi;
|
||||||
|
this.siteId = siteId;
|
||||||
|
|
||||||
this.wsUrl =
|
this.wsUrl =
|
||||||
envConfig.WS_URL || 'wss://ri.satabot.com/ws/';
|
envConfig.WS_URL || 'wss://ri.satabot.com/ws/';
|
||||||
@@ -44,6 +46,12 @@ export default class DeviceController {
|
|||||||
this.onIsFinished = null;
|
this.onIsFinished = null;
|
||||||
this.handleArrivedPoint = null;
|
this.handleArrivedPoint = null;
|
||||||
|
|
||||||
|
// 全量设备实时状态(mapPush)连接
|
||||||
|
this.mapPushWs = null;
|
||||||
|
this.mapPushManualClose = false;
|
||||||
|
this.mapPushReconnectTimer = null;
|
||||||
|
this.onMapPush = null;
|
||||||
|
|
||||||
this.handleWsMessage = this.handleWsMessage.bind(this);
|
this.handleWsMessage = this.handleWsMessage.bind(this);
|
||||||
this.handleWsStatus = this.handleWsStatus.bind(this);
|
this.handleWsStatus = this.handleWsStatus.bind(this);
|
||||||
this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
|
this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
|
||||||
@@ -78,6 +86,98 @@ export default class DeviceController {
|
|||||||
this.onControlStatusChange?.(false);
|
this.onControlStatusChange?.(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============ 全量设备实时状态(mapPush)连接 ============
|
||||||
|
// 进入实时监控页时连接一次,接收后端广播的所有设备状态;退出页面时调用 closeMapPush 断开。
|
||||||
|
initMapPush(onMapPush) {
|
||||||
|
console.log('DeviceController initMapPush: 连接全量设备 WebSocket');
|
||||||
|
this.onMapPush = onMapPush || null;
|
||||||
|
this.mapPushManualClose = false;
|
||||||
|
this.connectMapPush();
|
||||||
|
}
|
||||||
|
|
||||||
|
connectMapPush() {
|
||||||
|
if (this.mapPushWs && this.mapPushWs.readyState === WebSocket.OPEN) return;
|
||||||
|
try {
|
||||||
|
this.mapPushManualClose = false;
|
||||||
|
const ws = new WebSocket(this.wsUrl);
|
||||||
|
this.mapPushWs = ws;
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: 'mapPush',
|
||||||
|
token: this.userInfo?.token,
|
||||||
|
siteId: this.siteId,
|
||||||
|
userName: this.userInfo?.username,
|
||||||
|
deviceId: '',
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
let msg = null;
|
||||||
|
try { msg = JSON.parse(event.data); } catch (e) { return; }
|
||||||
|
|
||||||
|
console.log('[DeviceController] 收到全量设备状态 初始状态', msg);
|
||||||
|
if (msg?.type !== 'mapPush') return;
|
||||||
|
const list = Array.isArray(msg?.data) && msg.data.length
|
||||||
|
? msg.data
|
||||||
|
: [];
|
||||||
|
if (!list.length) return;
|
||||||
|
|
||||||
|
const next = {};
|
||||||
|
list.forEach((it) => {
|
||||||
|
const key = String(it?.id ?? '');
|
||||||
|
if (!key) return;
|
||||||
|
next[key] = {
|
||||||
|
id: key,
|
||||||
|
lng: String(it?.lng ?? it?.lon ?? it?.longitude),
|
||||||
|
lat: String(it?.lat ?? it?.latitude),
|
||||||
|
yaw: it?.y,
|
||||||
|
fix: it?.f !== undefined ? String(it.f) : undefined,
|
||||||
|
fixStatus: it?.f !== undefined ? String(it.f) : undefined,
|
||||||
|
altitude: it?.a !== undefined ? String(it.a) : undefined,
|
||||||
|
status: it?.s ?? it?.status,
|
||||||
|
taskId: it?.t ?? it?.taskId ?? null,
|
||||||
|
battery: it?.b !== undefined ? Number(it.b) : (it?.batteryLevel !== undefined ? Number(it.batteryLevel) : undefined),
|
||||||
|
hasAlarm: !!it?.ha || !!it?.hasAlarm,
|
||||||
|
updateTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
console.log('[DeviceController] 收到全量设备状态', next);
|
||||||
|
this.onMapPush?.(next);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = () => {
|
||||||
|
console.error('[DeviceController] mapPush WebSocket 错误');
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = () => {
|
||||||
|
if (this.mapPushWs === ws) this.mapPushWs = null;
|
||||||
|
if (!this.mapPushManualClose) {
|
||||||
|
this.mapPushReconnectTimer = setTimeout(() => this.connectMapPush(), 5000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[DeviceController] 连接全量设备 WebSocket 失败', err);
|
||||||
|
this.mapPushReconnectTimer = setTimeout(() => this.connectMapPush(), 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeMapPush() {
|
||||||
|
this.mapPushManualClose = true;
|
||||||
|
if (this.mapPushReconnectTimer) {
|
||||||
|
clearTimeout(this.mapPushReconnectTimer);
|
||||||
|
this.mapPushReconnectTimer = null;
|
||||||
|
}
|
||||||
|
if (this.mapPushWs) {
|
||||||
|
this.mapPushWs.onopen = null;
|
||||||
|
this.mapPushWs.onmessage = null;
|
||||||
|
this.mapPushWs.onerror = null;
|
||||||
|
this.mapPushWs.onclose = null;
|
||||||
|
try { this.mapPushWs.close(); } catch (e) { /* ignore */ }
|
||||||
|
this.mapPushWs = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleVisibilityChange() {
|
handleVisibilityChange() {
|
||||||
if (document.visibilityState === 'hidden') {
|
if (document.visibilityState === 'hidden') {
|
||||||
console.log('Page hidden, release control');
|
console.log('Page hidden, release control');
|
||||||
|
|||||||
@@ -1193,7 +1193,7 @@ const en = {
|
|||||||
|
|
||||||
s24: 'Recharging',
|
s24: 'Recharging',
|
||||||
|
|
||||||
fixed2StopFixed2VideoFixed2: 'Fixed2 Stop Fixed2 Video Fixed2',
|
fixed2StopFixed2VideoFixed2: ' Stop Video ',
|
||||||
|
|
||||||
s29: 'Emergency Stop',
|
s29: 'Emergency Stop',
|
||||||
fixed2AiActions: 'AI Analysis and Actions Suggestions',
|
fixed2AiActions: 'AI Analysis and Actions Suggestions',
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
NodeIndexOutlined, SaveOutlined, MaximizeOutlined, ReloadOutlined
|
NodeIndexOutlined, SaveOutlined, MaximizeOutlined, ReloadOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Line, Bar, Pie } from '@ant-design/charts';
|
import { Line, Bar, Pie } from '@ant-design/charts';
|
||||||
import bgMain from '../assets/images/bgmain.jpg';
|
|
||||||
import { UniversalBarChart, UniversalLineChart, UniversalPieChart } from '../components/recharts';
|
import { UniversalBarChart, UniversalLineChart, UniversalPieChart } from '../components/recharts';
|
||||||
import CesiumMap from '../components/Map';
|
import CesiumMap from '../components/Map';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -106,7 +105,6 @@ const RealTimeMonitorPage = () => {
|
|||||||
const [filterStatus, setFilterStatus] = useState('all');
|
const [filterStatus, setFilterStatus] = useState('all');
|
||||||
const [filterType, setFilterType] = useState('all');
|
const [filterType, setFilterType] = useState('all');
|
||||||
const [searchName, setSearchName] = useState('');
|
const [searchName, setSearchName] = useState('');
|
||||||
const [mapViewMode, setMapViewMode] = useState('all');
|
|
||||||
|
|
||||||
const cardStyle = { borderRadius: 12, border: '1px solid #f0f0f0', boxShadow: '0 2px 8px rgba(0,0,0,0.04)' };
|
const cardStyle = { borderRadius: 12, border: '1px solid #f0f0f0', boxShadow: '0 2px 8px rgba(0,0,0,0.04)' };
|
||||||
|
|
||||||
@@ -122,28 +120,16 @@ const RealTimeMonitorPage = () => {
|
|||||||
const [listPage, setListPage] = useState(1);
|
const [listPage, setListPage] = useState(1);
|
||||||
const [listPageSize, setListPageSize] = useState(8);
|
const [listPageSize, setListPageSize] = useState(8);
|
||||||
|
|
||||||
// ========= WebSocket + 地图交互相关 =========
|
// ========= WebSocket(全量设备 mapPush)+ 地图交互相关 =========
|
||||||
const controllerRef = useRef(null);
|
|
||||||
const isFirstConnect = useRef(true);
|
|
||||||
const [focusLocation, setFocusLocation] = useState(null);
|
const [focusLocation, setFocusLocation] = useState(null);
|
||||||
const [execute, setExecute] = useState(false);
|
|
||||||
const [excuteStatus, setExcuteStatus] = useState('stop');
|
const [excuteStatus, setExcuteStatus] = useState('stop');
|
||||||
const [points, setPoints] = useState([]);
|
const [points, setPoints] = useState([]);
|
||||||
const [polyPoints, setPolyPoints] = useState([]);
|
const [polyPoints, setPolyPoints] = useState([]);
|
||||||
const [routeMode, setRouteMode] = useState('');
|
const [routeMode, setRouteMode] = useState('');
|
||||||
const [clearNavLine, setClearNavLine] = useState(0);
|
const [clearNavLine, setClearNavLine] = useState(0);
|
||||||
const [clearPlanLine, setClearPlanLine] = useState(0);
|
const [clearPlanLine, setClearPlanLine] = useState(0);
|
||||||
const [arrivedPoint, setArrivedPoint] = useState(null);
|
const [realtimeMap, setRealtimeMap] = useState<any>({});
|
||||||
const [robotRealtimePosition, setRobotRealtimePosition] = useState(null);
|
|
||||||
const runningRealtimePosition = useMemo(() => {
|
|
||||||
return {
|
|
||||||
lon: robotRealtimePosition?.longitude || robotRealtimePosition?.lon || 0,
|
|
||||||
lat: robotRealtimePosition?.latitude || robotRealtimePosition?.lat || 0,
|
|
||||||
heading: robotRealtimePosition?.yaw || robotRealtimePosition?.heading || 0,
|
|
||||||
};
|
|
||||||
}, [robotRealtimePosition]);
|
|
||||||
const [runningPoolTask, setRunningPoolTask] = useState(null);
|
const [runningPoolTask, setRunningPoolTask] = useState(null);
|
||||||
const [proPoints, setProPoints] = useState({});//规划点
|
|
||||||
|
|
||||||
// ========= UI 状态 =========
|
// ========= UI 状态 =========
|
||||||
const [selectedDevice, setSelectedDevice] = useState(null);
|
const [selectedDevice, setSelectedDevice] = useState(null);
|
||||||
@@ -168,62 +154,22 @@ const RealTimeMonitorPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// DeviceController 初始化(仅一次)
|
// 全量设备状态 WebSocket 连接(mapPush:进入页面自动连接,退出页面自动断开)
|
||||||
|
// 连接逻辑统一封装在 DeviceController 中
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
const controllerRef = useRef<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userInfo) return;
|
if (!userInfo || !stationId) return;
|
||||||
const controller = new DeviceController(userInfo, messageApi);
|
if (!controllerRef.current) {
|
||||||
|
controllerRef.current = new DeviceController(userInfo, messageApi, stationId);
|
||||||
controller.onLocationInfoUpdate = (pos) => {
|
|
||||||
setRobotRealtimePosition(pos);
|
|
||||||
};
|
|
||||||
controller.onIsFinished = handleFinish;
|
|
||||||
|
|
||||||
|
|
||||||
controller.onRoutePost = (msg) => {
|
|
||||||
const { taskId, data } = msg;
|
|
||||||
//const matchedTask = taskPoolList.find(task => String(task.id) === String(taskId));
|
|
||||||
//const sameAsSelected = runningPoolTask && String(runningPoolTask.id) === String(taskId);
|
|
||||||
//if ((!runningPoolTask && matchedTask) || sameAsSelected) {
|
|
||||||
// if (!runningPoolTask && matchedTask) setRunningPoolTask(matchedTask);
|
|
||||||
console.log('[RTMonitor] route_post:', data)
|
|
||||||
setProPoints(msg);
|
|
||||||
//}
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.init();
|
|
||||||
controllerRef.current = controller;
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (controllerRef.current) {
|
|
||||||
controllerRef.current.destroy?.();
|
|
||||||
}
|
|
||||||
controllerRef.current = null;
|
|
||||||
};
|
|
||||||
// eslint-disable-next-line
|
|
||||||
}, []);
|
|
||||||
useEffect(() => {
|
|
||||||
//console.log('runningPoolTask?.id, proPoints?.taskId', proPoints)
|
|
||||||
if (!runningPoolTask?.id || !proPoints?.taskId) return;
|
|
||||||
if (runningPoolTask?.id == proPoints?.taskId) {
|
|
||||||
console.log('1111111111', proPoints?.data);
|
|
||||||
setPoints(proPoints?.data);
|
|
||||||
}
|
}
|
||||||
|
const controller = controllerRef.current;
|
||||||
|
controller.initMapPush((rtMap: any) => setRealtimeMap(rtMap || {}));
|
||||||
}, [runningPoolTask, proPoints]);
|
return () => {
|
||||||
const handleFinish = (data) => {
|
controller.closeMapPush();
|
||||||
setExecute(false);
|
};
|
||||||
|
}, [userInfo, stationId]);
|
||||||
utils.message.success(t('devices.fixed2TaskSuspended2Fixed2CompletedFixed2'));
|
|
||||||
setPoints([]);
|
|
||||||
setPolyPoints([]);
|
|
||||||
setRunningPoolTask(null);
|
|
||||||
setExcuteStatus('stop');
|
|
||||||
};
|
|
||||||
// ============================================================
|
|
||||||
// WebSocket 连接
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 拉取基础数据
|
// 拉取基础数据
|
||||||
@@ -301,7 +247,6 @@ const RealTimeMonitorPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载路线详情(预设路径点击执行时)
|
|
||||||
// 加载路线详情(预设路径点击执行时)
|
// 加载路线详情(预设路径点击执行时)
|
||||||
const fetchRouteDetailAndShow = async (routeId, routeData) => {
|
const fetchRouteDetailAndShow = async (routeId, routeData) => {
|
||||||
try {
|
try {
|
||||||
@@ -403,21 +348,79 @@ const RealTimeMonitorPage = () => {
|
|||||||
return [...robots, ...planeDeviceRows, ...mocks];
|
return [...robots, ...planeDeviceRows, ...mocks];
|
||||||
}, [devices, planeDeviceRows, currentStation]);
|
}, [devices, planeDeviceRows, currentStation]);
|
||||||
|
|
||||||
const mapDevices = useMemo(() => {
|
const pickRealtimeRecord = (rtMap: any, d: any) => {
|
||||||
const all = [...devices, ...planeDeviceRows];
|
const candidates = [d?.serialNumber, d?.device_sn, d?.sn, d?.id, d?.deviceId];
|
||||||
if (mapViewMode === 'selected' && (selectedDevice?.serialNumber || selectedDevice?.device_sn)) {
|
for (const key of candidates) {
|
||||||
const sn = selectedDevice?.serialNumber || selectedDevice?.device_sn;
|
if (key == null || String(key) === '') continue;
|
||||||
return all
|
const hit = rtMap[String(key)];
|
||||||
.filter(d => (d.serialNumber == sn) || (d.device_sn == sn))
|
if (hit) return hit;
|
||||||
|
|
||||||
}
|
}
|
||||||
return all
|
return null;
|
||||||
.filter(d => (d?.lastRunningStatus?.latitude && d?.lastRunningStatus?.longitude) || (d?.locationMessage?.latitude && d?.locationMessage?.longitude) || (d?.longitude && d?.latitude));
|
};
|
||||||
|
|
||||||
}, [devices, planeDeviceRows, mapViewMode, selectedDevice]);
|
const realtimeMapList = useMemo(() => Object.values(realtimeMap || {}), [realtimeMap]);
|
||||||
|
|
||||||
|
const deviceListWithRT = useMemo(() => {
|
||||||
|
return (devices || []).map((d: any) => {
|
||||||
|
const rt = pickRealtimeRecord(realtimeMap, d);
|
||||||
|
if (!rt) return d;
|
||||||
|
const lon = rt.lng;
|
||||||
|
const lat = rt.lat;
|
||||||
|
//if (!isFinite(lon) || !isFinite(lat) || (lon === 0 && lat === 0)) return d;
|
||||||
|
return {
|
||||||
|
...d,
|
||||||
|
longitude: lon,
|
||||||
|
latitude: lat,
|
||||||
|
heading: rt.yaw,
|
||||||
|
batteryLevel: rt.battery,
|
||||||
|
feStatus: rt.status,
|
||||||
|
taskId: rt.taskId,
|
||||||
|
updateTime: rt.updateTime || d.updateTime,
|
||||||
|
raw: { ...(d.raw || d), heading: rt.yaw, batteryLevel: rt.b, longitude: lon, latitude: lat },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [devices, realtimeMap]);
|
||||||
|
|
||||||
|
const mapDevices = useMemo(() => {
|
||||||
|
const base = [...deviceListWithRT];
|
||||||
|
const knownKeys = new Set();
|
||||||
|
base.forEach((d: any) => {
|
||||||
|
const k = d?.serialNumber ?? d?.device_sn ?? d?.id;
|
||||||
|
if (k != null && k !== '') knownKeys.add(String(k));
|
||||||
|
});
|
||||||
|
const extras = realtimeMapList
|
||||||
|
.filter((rt: any) => isFinite(rt.lng) && isFinite(rt.lat) && !(rt.lng === 0 && rt.lat === 0) && !knownKeys.has(String(rt.id)))
|
||||||
|
.map((rt: any) => ({
|
||||||
|
id: rt.id,
|
||||||
|
name: rt.name || rt.alias || `设备-${String(rt.id).slice(-4)}`,
|
||||||
|
serialNumber: rt.id,
|
||||||
|
deviceType: 'ROBOT',
|
||||||
|
type: 'ROBOT',
|
||||||
|
category: 'robot',
|
||||||
|
typeText: '机器人',
|
||||||
|
status: rt.status,
|
||||||
|
onlineStatus: rt.status,
|
||||||
|
longitude: rt.lng,
|
||||||
|
latitude: rt.lat,
|
||||||
|
heading: rt.yaw,
|
||||||
|
batteryLevel: rt.battery,
|
||||||
|
updateTime: rt.updateTime,
|
||||||
|
feStatus: rt.feStatus,
|
||||||
|
taskId: rt.t,
|
||||||
|
isRealtimeOnly: true,
|
||||||
|
raw: { ...rt },
|
||||||
|
}));
|
||||||
|
const all = [...base, ...extras];
|
||||||
|
|
||||||
|
return all.filter((d: any) =>
|
||||||
|
(d?.lastRunningStatus?.latitude && d?.lastRunningStatus?.longitude) ||
|
||||||
|
(d?.locationMessage?.latitude && d?.locationMessage?.longitude) ||
|
||||||
|
(d?.longitude && d?.latitude)
|
||||||
|
);
|
||||||
|
}, [deviceListWithRT, planeDeviceRows, realtimeMapList, selectedDevice]);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 点击设备:WebSocket 连接 + 地图定位
|
// 点击设备:仅打开侧边栏 + 地图定位(不再连接单台设备 WebSocket)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
const handleDeviceClick = (robot) => {
|
const handleDeviceClick = (robot) => {
|
||||||
console.log('[RTMonitor] 点击设备:', robot);
|
console.log('[RTMonitor] 点击设备:', robot);
|
||||||
@@ -445,13 +448,11 @@ const RealTimeMonitorPage = () => {
|
|||||||
setMqttDrone(null);
|
setMqttDrone(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setExecute(true);
|
|
||||||
setDeviceDrawerOpen(true);
|
setDeviceDrawerOpen(true);
|
||||||
|
|
||||||
|
|
||||||
// ---- 无人机:先断开 WebSocket,再订阅该无人机 MQTT 实时数据 ----
|
// ---- 无人机:先断开 WebSocket,再订阅该无人机 MQTT 实时数据 ----
|
||||||
if (isDroneDevice) {
|
if (isDroneDevice) {
|
||||||
controllerRef.current?.disconnect?.();
|
|
||||||
setMqttDrone(dev);
|
setMqttDrone(dev);
|
||||||
if (activeDroneTask?.device_sn !== dev.device_sn) {
|
if (activeDroneTask?.device_sn !== dev.device_sn) {
|
||||||
setDroneReturningHome(false);
|
setDroneReturningHome(false);
|
||||||
@@ -465,22 +466,10 @@ const RealTimeMonitorPage = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 机器人:先取消 MQTT 订阅,再连接 WebSocket ----
|
// ---- 机器人:仅打开侧边栏,不做实时 WebSocket 连接 ----
|
||||||
setMqttDrone(null);
|
setMqttDrone(null);
|
||||||
if (dev.serialNumber && controllerRef.current) {
|
|
||||||
const isSameDevice = controllerRef.current.connectedDeviceSn === dev.serialNumber;
|
|
||||||
if (isSameDevice) {
|
|
||||||
console.log('[RTMonitor] 已连接相同设备,跳过');
|
|
||||||
} else if (isFirstConnect.current) {
|
|
||||||
isFirstConnect.current = false;
|
|
||||||
controllerRef.current.connectDevice(dev);
|
|
||||||
} else {
|
|
||||||
controllerRef.current.switchDevice(dev);
|
|
||||||
}
|
|
||||||
setClearNavLine(c => c + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- 刷新该设备任务数据,并在地图显示该设备正在执行任务的路线和区域 ----
|
// ---- 刷新该设备任务数据(仅更新侧边栏当前任务,路线点击时才绘制规划路径) ----
|
||||||
fetchTaskPoolList(dev.serialNumber).then(poolRows => {
|
fetchTaskPoolList(dev.serialNumber).then(poolRows => {
|
||||||
const rows = poolRows || [];
|
const rows = poolRows || [];
|
||||||
const runningTask = rows.find(t =>
|
const runningTask = rows.find(t =>
|
||||||
@@ -490,9 +479,9 @@ const RealTimeMonitorPage = () => {
|
|||||||
console.log('[RTMonitor] 设备正在执行的任务:', runningTask);
|
console.log('[RTMonitor] 设备正在执行的任务:', runningTask);
|
||||||
if (runningTask) {
|
if (runningTask) {
|
||||||
setRunningPoolTask(runningTask);
|
setRunningPoolTask(runningTask);
|
||||||
|
setExcuteStatus(runningTask.taskStaus === 'EXECUTING' ? 'running' : 'pause');
|
||||||
loadTaskPath(runningTask.id);
|
loadTaskPath(runningTask.id);
|
||||||
fetchDetailTaskPath(runningTask.id);
|
fetchDetailTaskPath(runningTask.id);
|
||||||
setExcuteStatus(runningTask.taskStaus === 'EXECUTING' ? 'running' : 'pause');
|
|
||||||
} else {
|
} else {
|
||||||
setRunningPoolTask(null);
|
setRunningPoolTask(null);
|
||||||
setPoints([]);
|
setPoints([]);
|
||||||
@@ -820,7 +809,6 @@ const RealTimeMonitorPage = () => {
|
|||||||
const newTask = [...rest].sort((a, b) => new Date(b.createTime) - new Date(a.createTime))[0];
|
const newTask = [...rest].sort((a, b) => new Date(b.createTime) - new Date(a.createTime))[0];
|
||||||
if (newTask) {
|
if (newTask) {
|
||||||
setRunningPoolTask(newTask);
|
setRunningPoolTask(newTask);
|
||||||
await loadTaskPath(newTask.id);
|
|
||||||
setExcuteStatus('running');
|
setExcuteStatus('running');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -907,7 +895,7 @@ const RealTimeMonitorPage = () => {
|
|||||||
<Text type="secondary" style={{ fontSize: '12px' }}>{t('page.deviceFixed2StatusAndGeneration')} · {currentStation?.siteName || t('common.loading')}</Text>
|
<Text type="secondary" style={{ fontSize: '12px' }}>{t('page.deviceFixed2StatusAndGeneration')} · {currentStation?.siteName || t('common.loading')}</Text>
|
||||||
</div>
|
</div>
|
||||||
<Space>
|
<Space>
|
||||||
<Segmented
|
{/*<Segmented
|
||||||
value={mapViewMode}
|
value={mapViewMode}
|
||||||
onChange={setMapViewMode}
|
onChange={setMapViewMode}
|
||||||
size="small"
|
size="small"
|
||||||
@@ -915,7 +903,7 @@ const RealTimeMonitorPage = () => {
|
|||||||
{ label: t('page.showAllDevices'), value: 'all' },
|
{ label: t('page.showAllDevices'), value: 'all' },
|
||||||
{ label: t('page.showOnlySelected'), value: 'selected' }
|
{ label: t('page.showOnlySelected'), value: 'selected' }
|
||||||
]}
|
]}
|
||||||
/>
|
/>*/}
|
||||||
<Button type="text" icon={<SyncOutlined spin={loading} />} onClick={handleRefresh} style={{ color: '#1677ff' }}>
|
<Button type="text" icon={<SyncOutlined spin={loading} />} onClick={handleRefresh} style={{ color: '#1677ff' }}>
|
||||||
{t('common.refresh')}
|
{t('common.refresh')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -971,11 +959,7 @@ const RealTimeMonitorPage = () => {
|
|||||||
onCameraClick={handleDeviceClick}
|
onCameraClick={handleDeviceClick}
|
||||||
points={points}
|
points={points}
|
||||||
focusLocation={focusLocation}
|
focusLocation={focusLocation}
|
||||||
realtimePosition={runningRealtimePosition}
|
|
||||||
polyPoints={polyPoints}
|
polyPoints={polyPoints}
|
||||||
realtimeType="robot"
|
|
||||||
drawRealTime={execute}
|
|
||||||
finishPoint={arrivedPoint}
|
|
||||||
ifClearNavLine={clearNavLine}
|
ifClearNavLine={clearNavLine}
|
||||||
ifClearPlanLine={clearPlanLine}
|
ifClearPlanLine={clearPlanLine}
|
||||||
routeMode={routeMode}
|
routeMode={routeMode}
|
||||||
@@ -988,6 +972,21 @@ const RealTimeMonitorPage = () => {
|
|||||||
{t('page.foundDevices', { count: mapDevices.length })}
|
{t('page.foundDevices', { count: mapDevices.length })}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
{/*图例:工作中蓝 / 空闲绿 / 离线红
|
||||||
|
<div style={{ background: 'rgba(0,0,0,0.55)', color: 'white', padding: '6px 12px', borderRadius: 8, fontSize: 12, backdropFilter: 'blur(4px)', display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||||
|
<Space size={10}>
|
||||||
|
<span style={{ display: 'inline-block', width: 10, height: 10, borderRadius: '50%', background: '#1677ff' }} />
|
||||||
|
{t('devices.s59')}
|
||||||
|
</Space>
|
||||||
|
<Space size={10}>
|
||||||
|
<span style={{ display: 'inline-block', width: 10, height: 10, borderRadius: '50%', background: '#52c41a' }} />
|
||||||
|
{t('devices.idle')}
|
||||||
|
</Space>
|
||||||
|
<Space size={10}>
|
||||||
|
<span style={{ display: 'inline-block', width: 10, height: 10, borderRadius: '50%', background: '#ff4d4f' }} />
|
||||||
|
{t('devices.offline')}
|
||||||
|
</Space>
|
||||||
|
</div>*/}
|
||||||
{runningPoolTask && (
|
{runningPoolTask && (
|
||||||
<div style={{ background: excuteStatus === 'running' ? 'rgba(82,196,26,0.95)' : 'rgba(250,173,20,0.95)', color: 'white', padding: '8px 12px', borderRadius: 8, fontSize: 12, backdropFilter: 'blur(4px)', maxWidth: 280 }}>
|
<div style={{ background: excuteStatus === 'running' ? 'rgba(82,196,26,0.95)' : 'rgba(250,173,20,0.95)', color: 'white', padding: '8px 12px', borderRadius: 8, fontSize: 12, backdropFilter: 'blur(4px)', maxWidth: 280 }}>
|
||||||
<div style={{ fontWeight: 600, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 6 }}>
|
<div style={{ fontWeight: 600, marginBottom: 4, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
@@ -999,15 +998,7 @@ const RealTimeMonitorPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{robotRealtimePosition && (
|
|
||||||
<div style={{ background: 'rgba(22,119,255,0.9)', color: 'white', padding: '8px 12px', borderRadius: 8, fontSize: 11, backdropFilter: 'blur(4px)' }}>
|
|
||||||
<div>📡 {t('devices.realtimePosition')}</div>
|
|
||||||
<div>Lng: {Number(robotRealtimePosition.longitude || robotRealtimePosition.lng || 0).toFixed(5)}</div>
|
|
||||||
<div>Lat: {Number(robotRealtimePosition.latitude || robotRealtimePosition.lat || 0).toFixed(5)}</div>
|
|
||||||
<div>yaw: {Number(robotRealtimePosition.heading || 0).toFixed(5)}</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -1298,7 +1289,7 @@ const RealTimeMonitorPage = () => {
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
extra={
|
extra={
|
||||||
<Space size={4}>
|
<Space size={4} onClick={(e) => e.stopPropagation()}>
|
||||||
<Tooltip title={t('devices.pause')}>
|
<Tooltip title={t('devices.pause')}>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -1332,7 +1323,14 @@ const RealTimeMonitorPage = () => {
|
|||||||
style={{
|
style={{
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
border: `1px solid ${runningPoolTask ? (excuteStatus === 'running' ? 'rgba(82,196,26,0.3)' : 'rgba(250,173,20,0.3)') : '#f0f0f0'}`,
|
border: `1px solid ${runningPoolTask ? (excuteStatus === 'running' ? 'rgba(82,196,26,0.3)' : 'rgba(250,173,20,0.3)') : '#f0f0f0'}`,
|
||||||
background: runningPoolTask ? (excuteStatus === 'running' ? 'rgba(82,196,26,0.03)' : 'rgba(250,173,20,0.03)') : '#fff'
|
background: runningPoolTask ? (excuteStatus === 'running' ? 'rgba(82,196,26,0.03)' : 'rgba(250,173,20,0.03)') : '#fff',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
const task = runningPoolTask || (deviceTasks && deviceTasks.current);
|
||||||
|
if (task?.id) {
|
||||||
|
loadTaskPath(task.id); fetchDetailTaskPath(task.id);
|
||||||
|
};
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(runningPoolTask || deviceTasks.current) ? (
|
{(runningPoolTask || deviceTasks.current) ? (
|
||||||
@@ -1415,7 +1413,6 @@ const RealTimeMonitorPage = () => {
|
|||||||
<div
|
<div
|
||||||
key={route.id || route.workName}
|
key={route.id || route.workName}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setExecute(true);
|
|
||||||
fetchRouteDetailAndShow(route.id, route);
|
fetchRouteDetailAndShow(route.id, route);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
Reference in New Issue
Block a user