diff --git a/src/api/request.ts b/src/api/request.ts index c0b8f93..466a698 100644 --- a/src/api/request.ts +++ b/src/api/request.ts @@ -28,51 +28,54 @@ api.interceptors.request.use( } ); -// 响应拦截器(处理 401 + 网络错误 + 服务挂了) +// 统一登出跳转方法 +const handleLogoutAndRedirect = () => { + localStorage.clear(); + Cookies.remove('userId'); + Cookies.remove('Admin-Token'); + + // 动态导入避免循环依赖 + import('../store').then(store => { + import('../store/userSlice').then(({ logout }) => { + store.default.dispatch(logout()); + }); + }); + + message.error('登录已过期,请重新登录'); + setTimeout(() => { + window.location.href = '/login'; + }, 800); +}; + +// 响应拦截器 api.interceptors.response.use( - (response) => { - return response.data; + (response: AxiosResponse) => { + const data = response.data; + + // ============================================== + // 👇 重点:如果接口返回 code = 401,直接跳转登录 + // ============================================== + if (data?.code == 401) { + handleLogoutAndRedirect(); + return Promise.reject(new Error('登录已过期')); + } + + return data; }, async (error: AxiosError) => { - // ============================================== - // 👇 这里处理:服务挂了 / 断网 / 请求失败 / 500 - // ============================================== + // 网络异常 / 服务器挂了 if (!error.response) { message.error('服务异常或网络断开,即将重新登录'); - localStorage.clear(); - Cookies.remove('userId'); - Cookies.remove('Admin-Token'); - - // 动态加载 store 避免循环引用 - const store = await import('../store'); - const { logout } = await import('../store/userSlice'); - store.default.dispatch(logout()); - - setTimeout(() => { - window.location.href = '/login'; - }, 800); + handleLogoutAndRedirect(); return Promise.reject(error); } - // ============================================== - // 👇 这里处理:401 未授权 / token 过期 - // ============================================== + // HTTP 401 if (error.response.status === 401) { - localStorage.clear(); - Cookies.remove('userId'); - Cookies.remove('Admin-Token'); - - const store = await import('../store'); - const { logout } = await import('../store/userSlice'); - store.default.dispatch(logout()); - - message.error('登录已过期,请重新登录'); - setTimeout(() => { - window.location.href = '/login'; - }, 800); + handleLogoutAndRedirect(); + return Promise.reject(error); } - // 其他错误(500等)不跳转,只返回错误 return Promise.reject(error); } ); diff --git a/src/components/VolcRtcPlayer.jsx b/src/components/VolcRtcPlayer.jsx index a8645ea..f9fcc67 100644 --- a/src/components/VolcRtcPlayer.jsx +++ b/src/components/VolcRtcPlayer.jsx @@ -54,7 +54,7 @@ export default function DroneLivePlayer({ streamData, videoMode = "NORMAL" }) { timerRef.current = null; } leaveRoom(); // 先离开旧房间 - await new Promise((resolve) => setTimeout(resolve, 100)); // 等待 SDK 内部清理完成 + await new Promise((resolve) => setTimeout(resolve, 100)); // 3. 组件已卸载,直接放弃 if (!mountedRef.current) { diff --git a/src/components/devices/DeviceOverviewPage.tsx b/src/components/devices/DeviceOverviewPage.tsx index f9ff706..4ca243d 100644 --- a/src/components/devices/DeviceOverviewPage.tsx +++ b/src/components/devices/DeviceOverviewPage.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Layout, Card, Row, Col, Statistic, Tag, Button, Table, Typography, Space, Progress, Badge, Timeline, message, - Select + Select, Modal } from 'antd'; import { EnvironmentOutlined, RobotOutlined, BranchesOutlined, @@ -11,7 +11,7 @@ import { CloudOutlined, ClockCircleOutlined, ReloadOutlined, AimOutlined, ApiOutlined, SettingOutlined, PlusOutlined, ArrowRightOutlined, - EditOutlined + EditOutlined, FullscreenOutlined, InfoCircleOutlined } from '@ant-design/icons'; import { useSelector } from 'react-redux'; @@ -44,9 +44,10 @@ const logs = [ ]; // 设备总览组件 -export default function DeviceOverviewPage({ devices, planedevices, devicesAll }) { +export default function DeviceOverviewPage({ devices, planedevices, devicesAll, onViewStatus }) { const [selectedDevice, setSelectedDevice] = useState(null); - const [planeVideo, setPlaneVideo] = useState(''); + const [planeVideo, setPlaneVideo] = useState([]); // 初始为空数组 + const [zoomedVideo, setZoomedVideo] = useState(null); // 放大查看的视频数据 // 动态计算顶部卡片数据 const dynamicTopCards = React.useMemo(() => { @@ -205,8 +206,12 @@ export default function DeviceOverviewPage({ devices, planedevices, devicesAll }