视频接入接口

This commit is contained in:
mmc
2026-08-10 15:25:56 +08:00
parent 7d81362a88
commit a69190cbdc
4 changed files with 464 additions and 74 deletions

217
build_log2.txt Normal file
View File

@@ -0,0 +1,217 @@

> react-example@0.0.0 build
> vite build
vite v6.4.2 building for production...
transforming...
鉁?[39m 9321 modules transformed.
rendering chunks...
npm : [plugin vite:reporter]
所在位置 行:1 字符: 25
+ cd "D:\code\机器人智慧服务系统"; npm run build 2>&1 | Out-String -Width 300 | ...
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: ([plugin vite:reporter] :String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
(!) D:/code/鏈哄櫒浜烘櫤鎱ф湇鍔$郴缁?src/store/userSlice.ts is dynamically imported by D:/code/鏈哄櫒浜烘櫤鎱ф湇鍔$郴缁?src/api/request.ts but also statically imported by D:/code/鏈哄櫒浜烘櫤鎱ф湇鍔$郴缁?src/components/Layout.tsx, D:/code/鏈哄櫒浜烘櫤鎱ф湇鍔$郴缁?src/pages/Login.tsx, D:/code/鏈哄櫒浜烘櫤鎱ф湇鍔$郴缁?src/pages/Profile.tsx, D:/code/鏈哄櫒浜烘櫤
鎱ф湇鍔$郴缁?src/store/index.ts, dynamic import will not move module into another chunk.

[plugin vite:reporter]
(!) D:/code/鏈哄櫒浜烘櫤鎱ф湇鍔$郴缁?src/store/index.ts is dynamically imported by D:/code/鏈哄櫒浜烘櫤鎱ф湇鍔$郴缁?src/api/request.ts but also statically imported by D:/code/鏈哄櫒浜烘櫤鎱ф湇鍔$郴缁?src/components/Layout.tsx, D:/code/鏈哄櫒浜烘櫤鎱ф湇鍔$郴缁?src/main.tsx, D:/code/鏈哄櫒浜烘櫤鎱ф湇鍔$郴缁?src/pages/Login.tsx, D:/code/鏈哄櫒浜烘櫤鎱ф湇鍔$郴缁?src/p
ages/Profile.tsx, dynamic import will not move module into another chunk.

computing gzip size...
dist/index.html  2.52 kB 鈹?gzip: 1.13 kB
dist/assets/gecaocar-DMp9NnzT.png  16.23 kB
dist/assets/bgmain-DtapCCMy.jpg  71.91 kB
dist/assets/xunjiancar-CAfmDzzl.png  213.39 kB
dist/assets/carplane-f-ISorrw.png  259.24 kB
dist/assets/logincard-B8hQtFRG.png  355.36 kB
dist/assets/logo-BZW_A_MC.png  1,814.58 kB
dist/assets/bgc2-CfINnBvq.png  3,954.80 kB
dist/assets/inspectCar-C-BgLHu6.png 14,801.70 kB
dist/assets/Home-C67sUCdb.css  1.21 kB 鈹?gzip: 0.55 kB
dist/assets/index-bPDl6cOh.css  70.30 kB 鈹?gzip: 12.91 kB
dist/assets/useForceUpdate-CZFCNyeq.js  0.11 kB 鈹?gzip: 0.13 kB
dist/assets/EyeOutlined-BFyfoix3.js  0.20 kB 鈹?gzip: 0.18 kB
dist/assets/index-Bjna-cdU.js  0.29 kB 鈹?gzip: 0.17 kB
dist/assets/constants-DwgWOJkA.js  0.31 kB 鈹?gzip: 0.28 kB
dist/assets/chevron-right-vVBQ_U9m.js  0.31 kB 鈹?gzip: 0.25 kB
dist/assets/SwapRightOutlined-CaVf5wQU.js  0.32 kB 鈹?gzip: 0.26 kB
dist/assets/index-Be-tFikO.js  0.35 kB 鈹?gzip: 0.19 kB
dist/assets/useBreakpoint-DAxx-Njc.js  0.37 kB 鈹?gzip: 0.27 kB
dist/assets/Empty-xLCAL5dw.js  0.40 kB 鈹?gzip: 0.32 kB
dist/assets/CloseOutlined-JMAuRABM.js  0.41 kB 鈹?gzip: 0.31 kB
dist/assets/wind-1zUaWqEo.js  0.43 kB 鈹?gzip: 0.31 kB
dist/assets/CheckOutlined-D2FgLXFS.js  0.44 kB 鈹?gzip: 0.32 kB
dist/assets/PlusOutlined-D6I49DVX.js  0.44 kB 鈹?gzip: 0.32 kB
dist/assets/EditOutlined-BR5vRp4u.js  0.44 kB 鈹?gzip: 0.32 kB
dist/assets/SearchOutlined-DSqH5eOx.js  0.44 kB 鈹?gzip: 0.32 kB
dist/assets/index-B8qYoa6A.js  0.44 kB 鈹?gzip: 0.22 kB
dist/assets/zap-BbovkZw3.js  0.45 kB 鈹?gzip: 0.32 kB
dist/assets/collapse-BbEVqHco.js  0.45 kB 鈹?gzip: 0.20 kB
dist/assets/EyeOutlined-BRmjDxUJ.js  0.45 kB 鈹?gzip: 0.32 kB
dist/assets/DeleteOutlined-DcPIqNbE.js  0.45 kB 鈹?gzip: 0.32 kB
dist/assets/CalendarOutlined-DmuAJ4dw.js  0.45 kB 鈹?gzip: 0.33 kB
dist/assets/DownloadOutlined-Dk0o-amg.js  0.45 kB 鈹?gzip: 0.33 kB
dist/assets/ClockCircleOutlined-BdM1xofC.js  0.46 kB 鈹?gzip: 0.33 kB
dist/assets/DownloadOutlined-DTidZHln.js  0.46 kB 鈹?gzip: 0.30 kB
dist/assets/SwapOutlined-MHggqN5a.js  0.47 kB 鈹?gzip: 0.34 kB
dist/assets/DeleteOutlined-DFutYCzo.js  0.50 kB 鈹?gzip: 0.34 kB
dist/assets/shield-check-sfL9HgxB.js  0.50 kB 鈹?gzip: 0.36 kB
dist/assets/CalendarOutlined-DKq2sctu.js  0.51 kB 鈹?gzip: 0.31 kB
dist/assets/ClockCircleOutlined-CELcQ7Uv.js  0.53 kB 鈹?gzip: 0.34 kB
dist/assets/EyeOutlined-Bmd706kk.js  0.63 kB 鈹?gzip: 0.39 kB
dist/assets/PauseOutlined-BLt2csZU.js  0.65 kB 鈹?gzip: 0.47 kB
dist/assets/play-1ljA3jPR.js  0.66 kB 鈹?gzip: 0.36 kB
dist/assets/MoreOutlined-B1PG76QS.js  0.67 kB 鈹?gzip: 0.43 kB
dist/assets/SystemSetting-tRZ3rI5Y.js  0.67 kB 鈹?gzip: 0.42 kB
dist/assets/index-QGIuQAVc.js  0.75 kB 鈹?gzip: 0.30 kB
dist/assets/ProtectedRoute-DBQ_RFvv.js  0.77 kB 鈹?gzip: 0.47 kB
dist/assets/ThunderboltOutlined-CM9FK6lI.js  0.81 kB 鈹?gzip: 0.57 kB
dist/assets/RiseOutlined-CG9xbajJ.js  0.83 kB 鈹?gzip: 0.57 kB
dist/assets/cpu-COM_qCei.js  0.83 kB 鈹?gzip: 0.41 kB
dist/assets/trending-up-C335pZ95.js  0.83 kB 鈹?gzip: 0.39 kB
dist/assets/WarningOutlined-Btx5sX6A.js  0.84 kB 鈹?gzip: 0.59 kB
dist/assets/DatabaseOutlined-C5nPN0EL.js  0.84 kB 鈹?gzip: 0.53 kB
dist/assets/InfoCircleOutlined-BRsgui7Y.js  0.88 kB 鈹?gzip: 0.56 kB
dist/assets/MailOutlined-CrBBI-jk.js  0.88 kB 鈹?gzip: 0.59 kB
dist/assets/LockOutlined-CpZjW16Y.js  0.88 kB 鈹?gzip: 0.60 kB
dist/assets/ExclamationCircleOutlined-BTwY9HjG.js  0.88 kB 鈹?gzip: 0.57 kB
dist/assets/index-BZNW0Ac-.js  0.89 kB 鈹?gzip: 0.28 kB
dist/assets/VideoCameraOutlined-BpSekwuQ.js  0.89 kB 鈹?gzip: 0.62 kB
dist/assets/PlayCircleOutlined-C8_VtFd1.js  0.90 kB 鈹?gzip: 0.58 kB
dist/assets/LeftOutlined-mtwdKv5a.js  0.90 kB 鈹?gzip: 0.49 kB
dist/assets/index-B5BnD-LK.js  0.93 kB 鈹?gzip: 0.56 kB
dist/assets/CheckCircleOutlined-CiWPVIDh.js  0.94 kB 鈹?gzip: 0.61 kB
dist/assets/droplets-J52jZ87L.js  0.94 kB 鈹?gzip: 0.50 kB
dist/assets/FileTextOutlined-8_Os79cm.js  0.96 kB 鈹?gzip: 0.62 kB
dist/assets/RobotOutlined-D9B_FJ7c.js  0.97 kB 鈹?gzip: 0.63 kB
dist/assets/BellOutlined-DEw0MDNM.js  0.97 kB 鈹?gzip: 0.65 kB
dist/assets/FormOutlined-B5idtZPb.js  1.00 kB 鈹?gzip: 0.66 kB
dist/assets/BarChartOutlined-D19D9dh2.js  1.01 kB 鈹?gzip: 0.53 kB
dist/assets/BgColorsOutlined-D4zNRQ_S.js  1.01 kB 鈹?gzip: 0.66 kB
dist/assets/AppstoreOutlined-vwQa2ti0.js  1.06 kB 鈹?gzip: 0.52 kB
dist/assets/ExperimentOutlined-C4X5fx5X.js  1.06 kB 鈹?gzip: 0.72 kB
dist/assets/BranchesOutlined-BsXN7E28.js  1.07 kB 鈹?gzip: 0.65 kB
dist/assets/index-B7QMvlEx.js  1.10 kB 鈹?gzip: 0.59 kB
dist/assets/ExpandOutlined-Wqi8y5jM.js  1.12 kB 鈹?gzip: 0.59 kB
dist/assets/CloudOutlined-bffXYW10.js  1.13 kB 鈹?gzip: 0.74 kB
dist/assets/UserOutlined-C7S2oBX2.js  1.22 kB 鈹?gzip: 0.76 kB
dist/assets/ZoomOutOutlined-CdPjzMfa.js  1.22 kB 鈹?gzip: 0.42 kB
dist/assets/ReloadOutlined-Xhzi5rUd.js  1.23 kB 鈹?gzip: 0.77 kB
dist/assets/AlertOutlined-BSdXkZNw.js  1.24 kB 鈹?gzip: 0.73 kB
dist/assets/AimOutlined-BQ_60xaP.js  1.25 kB 鈹?gzip: 0.71 kB
dist/assets/alarmApi-D98kS4sF.js  1.25 kB 鈹?gzip: 0.35 kB
dist/assets/SyncOutlined-D5St2s8G.js  1.27 kB 鈹?gzip: 0.81 kB
dist/assets/MoneyCollectOutlined-BEnuS9NY.js  1.30 kB 鈹?gzip: 0.79 kB
dist/assets/FullscreenOutlined-Bd0pqIlF.js  1.34 kB 鈹?gzip: 0.77 kB
dist/assets/SafetyCertificateOutlined-DnZNNQOF.js  1.41 kB 鈹?gzip: 0.76 kB
dist/assets/SwapOutlined-Dmzzor8a.js  1.42 kB 鈹?gzip: 0.65 kB
dist/assets/ZoomOutOutlined-DAOfVl6t.js  1.47 kB 鈹?gzip: 0.67 kB
dist/assets/satellite-D5SYgbBW.js  1.47 kB 鈹?gzip: 0.54 kB
dist/assets/RocketOutlined-DvS5QcPf.js  1.49 kB 鈹?gzip: 0.93 kB
dist/assets/EnvironmentOutlined-DmDILpSG.js  1.49 kB 鈹?gzip: 0.91 kB
dist/assets/useBubbleLock-BcDDtpST.js  1.54 kB 鈹?gzip: 0.75 kB
dist/assets/index-CALtcbsS.js  1.61 kB 鈹?gzip: 0.85 kB
dist/assets/ApiOutlined-CzQyCItv.js  1.61 kB 鈹?gzip: 0.87 kB
dist/assets/SunOutlined-4fVeMoLM.js  1.75 kB 鈹?gzip: 0.90 kB
dist/assets/DashboardOutlined-By3UzQBB.js  1.83 kB 鈹?gzip: 0.93 kB
dist/assets/createLucideIcon-I8N4V_Qv.js  1.86 kB 鈹?gzip: 0.80 kB
dist/assets/CarOutlined-Dl55nQ-v.js  2.00 kB 鈹?gzip: 0.98 kB
dist/assets/index-Bki12rLk.js  2.04 kB 鈹?gzip: 1.00 kB
dist/assets/systemSetting-BrHh5w13.js  2.11 kB 鈹?gzip: 0.45 kB
dist/assets/PlayCircleFilled-BBXiNPP8.js  2.14 kB 鈹?gzip: 1.04 kB
dist/assets/LinkOutlined-DvFl02q6.js  2.19 kB 鈹?gzip: 0.82 kB
dist/assets/index-CFB0_Wn2.js  2.19 kB 鈹?gzip: 1.14 kB
dist/assets/SettingOutlined-DeJS8h5d.js  2.35 kB 鈹?gzip: 1.21 kB
dist/assets/VideoPlayer-CBopx0N2.js  2.39 kB 鈹?gzip: 1.33 kB
dist/assets/PictureTwoTone-BaO8R2Hf.js  2.42 kB 鈹?gzip: 1.04 kB
dist/assets/index-Dzwhnzv1.js  2.42 kB 鈹?gzip: 1.16 kB
dist/assets/SaveOutlined-CPLKsg-u.js  3.28 kB 鈹?gzip: 1.40 kB
dist/assets/device-psUGD0yB.js  3.38 kB 鈹?gzip: 0.76 kB
dist/assets/index-C8BzGSw1.js  3.61 kB 鈹?gzip: 1.27 kB
dist/assets/row-CWgAqdVq.js  3.74 kB 鈹?gzip: 1.64 kB
dist/assets/MonitorOutlined-BblwvURO.js  3.85 kB 鈹?gzip: 1.28 kB
dist/assets/OnlineUser-DP0qCIhg.js  3.87 kB 鈹?gzip: 1.82 kB
dist/assets/index-BU7NlCGd.js  4.14 kB 鈹?gzip: 1.74 kB
dist/assets/MenuUnfoldOutlined-D7wIQeV0.js  4.16 kB 鈹?gzip: 1.30 kB
dist/assets/ScanOutlined-BErT-8VV.js  4.19 kB 鈹?gzip: 1.38 kB
dist/assets/index-D-WWTY9M.js  4.21 kB 鈹?gzip: 1.58 kB
dist/assets/LoadingOutlined-CSGMXG1Q.js  4.25 kB 鈹?gzip: 1.83 kB
dist/assets/index-CwWg_1c5.js  4.38 kB 鈹?gzip: 1.76 kB
dist/assets/Organization-DaUilJNB.js  4.43 kB 鈹?gzip: 1.95 kB
dist/assets/index-DLB_37uE.js  4.58 kB 鈹?gzip: 1.91 kB
dist/assets/VideoCameraAddOutlined-DfZ_JO9-.js  4.79 kB 鈹?gzip: 1.68 kB
dist/assets/WomanOutlined-DfQHI4i_.js  4.82 kB 鈹?gzip: 1.76 kB
dist/assets/LineChartOutlined-DoICXm12.js  5.07 kB 鈹?gzip: 1.82 kB
dist/assets/Roles-BcoDNXzy.js  5.16 kB 鈹?gzip: 2.28 kB
dist/assets/index-DlknX2AJ.js  5.44 kB 鈹?gzip: 2.24 kB
dist/assets/Users-Cc87fC1a.js  5.46 kB 鈹?gzip: 2.17 kB
dist/assets/index-DBrV9klW.js  5.57 kB 鈹?gzip: 2.20 kB
dist/assets/index-BIHkEVGV.js  5.83 kB 鈹?gzip: 2.63 kB
dist/assets/Sider-S9XGzyBR.js  6.64 kB 鈹?gzip: 2.85 kB
dist/assets/Input-C5V4O2--.js  6.73 kB 鈹?gzip: 2.71 kB
dist/assets/LogManagement-DMoKnN3a.js  6.80 kB 鈹?gzip: 3.23 kB
dist/assets/dayjs.min-BQmzeOJK.js  7.15 kB 鈹?gzip: 3.11 kB
dist/assets/Timeline-DTl788NK.js  7.47 kB 鈹?gzip: 2.32 kB
dist/assets/Profile-D6l6crbd.js  7.59 kB 鈹?gzip: 2.67 kB
dist/assets/index-Bxnk55C6.js  7.98 kB 鈹?gzip: 2.71 kB
dist/assets/GlobalOutlined-BBf_4gfp.js  8.13 kB 鈹?gzip: 2.73 kB
dist/assets/StationManage-DqEWUwhR.js  9.55 kB 鈹?gzip: 3.29 kB
dist/assets/CaretUpOutlined-CeF_o8iU.js  9.72 kB 鈹?gzip: 3.73 kB
dist/assets/index-7OmqMvBS.js  9.85 kB 鈹?gzip: 4.12 kB
dist/assets/index-CoZjM-Ur.js  10.86 kB 鈹?gzip: 3.67 kB
dist/assets/index-BcHyRtCu.js  13.09 kB 鈹?gzip: 4.32 kB
dist/assets/RealTimeMonitor-D3QaTkAC.js  13.35 kB 鈹?gzip: 3.86 kB
dist/assets/Downloads-DcgcCi6l.js  14.39 kB 鈹?gzip: 5.34 kB
dist/assets/Layout-Bd_POYYm.js  14.43 kB 鈹?gzip: 5.93 kB
dist/assets/UserPage-32JpRX2A.js  14.58 kB 鈹?gzip: 5.10 kB
dist/assets/mowerAllView-I1S0UCff.js  15.31 kB 鈹?gzip: 5.14 kB
dist/assets/progress-BRDZKBDI.js  16.32 kB 鈹?gzip: 5.97 kB
dist/assets/Video-DCzOHWv-.js  17.54 kB 鈹?gzip: 6.37 kB
dist/assets/VideoMonitorPage-BhzNbDao.js  19.23 kB 鈹?gzip: 5.70 kB
dist/assets/Home-Bn7xrUeS.js  19.49 kB 鈹?gzip: 5.82 kB
dist/assets/ReportCenter-BBu6qFzX.js  19.98 kB 鈹?gzip: 5.52 kB
dist/assets/AIAnalysis-6Etwoj3Z.js  20.65 kB 鈹?gzip: 5.97 kB
dist/assets/SystemMaintain-Cd07s562.js  21.22 kB 鈹?gzip: 6.42 kB
dist/assets/Agri-CAyj5gpp.js  23.35 kB 鈹?gzip: 6.70 kB
dist/assets/CleanOptimization-m28ON6IA.js  23.72 kB 鈹?gzip: 6.78 kB
dist/assets/BasicSettings-mBDMHnai.js  24.03 kB 鈹?gzip: 6.94 kB
dist/assets/Pagination-E56Qk7uV.js  24.07 kB 鈹?gzip: 7.27 kB
dist/assets/index-D2zgn0VX.js  24.29 kB 鈹?gzip: 6.84 kB
dist/assets/index-CIQZPZYn.js  24.61 kB 鈹?gzip: 8.41 kB
dist/assets/DeviceAccessPage-DkChs1SW.js  27.31 kB 鈹?gzip: 7.77 kB
dist/assets/DeviceStatusPage-DeTc4zz3.js  27.77 kB 鈹?gzip: 8.81 kB
dist/assets/AntdIcon-0lBFMey5.js  28.67 kB 鈹?gzip: 10.61 kB
dist/assets/index-3QLgGeSa.js  30.34 kB 鈹?gzip: 10.81 kB
dist/assets/index-4Tny0eKH.js  31.74 kB 鈹?gzip: 11.04 kB
dist/assets/DeviceOverviewPage-OFF9ez_H.js  34.95 kB 鈹?gzip: 9.58 kB
dist/assets/Map-B9M4yO_P.js  35.52 kB 鈹?gzip: 16.50 kB
dist/assets/DeviceParamModal-aOMZwSFn.js  36.02 kB 鈹?gzip: 12.66 kB
dist/assets/Alerts-DSpopPLD.js  43.22 kB 鈹?gzip: 13.57 kB
dist/assets/WayLinePage-Cu3Rj_Of.js  47.81 kB 鈹?gzip: 12.76 kB
dist/assets/RobotTaskPage-W5Q2YiXq.js  50.58 kB 鈹?gzip: 15.72 kB
dist/assets/index-DtVs1TSu.js  53.48 kB 鈹?gzip: 17.46 kB
dist/assets/index-DOfHuV9M.js  55.58 kB 鈹?gzip: 16.79 kB
dist/assets/index-DdSZJIQg.js  85.35 kB 鈹?gzip: 28.38 kB
dist/assets/index-8P5uuj0T.js  104.37 kB 鈹?gzip: 33.62 kB
dist/assets/Table-DEFhGiKk.js  105.22 kB 鈹?gzip: 35.08 kB
dist/assets/index-C4N9F59W.js  119.58 kB 鈹?gzip: 40.54 kB
dist/assets/Login-B_ks4Hrx.js  127.74 kB 鈹?gzip: 42.25 kB
dist/assets/VideoManagement-DTii0Wl_.js  162.67 kB 鈹?gzip: 41.60 kB
dist/assets/useMqtt-CalKBDup.js  372.77 kB 鈹?gzip: 112.37 kB
dist/assets/recharts-oahKx968.js  376.93 kB 鈹?gzip: 108.14 kB
dist/assets/index-_6GufRdM.js  651.35 kB 鈹?gzip: 215.64 kB
dist/assets/DeviceManagement-CdpxXm_d.js  722.93 kB 鈹?gzip: 158.92 kB
dist/assets/MenuList-tLwkyiAS.js  857.15 kB 鈹?gzip: 165.02 kB
dist/assets/WorkOrder-DfwdBKQc.js  1,507.81 kB 鈹?gzip: 445.47 kB
dist/assets/DroneVideoPlayer-BnJcr8Dl.js  2,529.14 kB 鈹?gzip: 708.00 kB

(!) Some chunks are larger than 1000 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
[vite-plugin-static-copy] Copied 1 items.
鉁?built in 1m 39s

23
package-lock.json generated
View File

@@ -24,6 +24,7 @@
"dotenv": "^17.2.3",
"echarts": "^6.0.0",
"express": "^4.21.2",
"flv.js": "^1.6.2",
"js-cookie": "^3.0.5",
"lodash": "^4.18.1",
"lucide-react": "^0.546.0",
@@ -4083,6 +4084,12 @@
"benchmarks"
]
},
"node_modules/es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
"license": "MIT"
},
"node_modules/esbuild": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
@@ -4372,6 +4379,16 @@
"node": ">=6"
}
},
"node_modules/flv.js": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/flv.js/-/flv.js-1.6.2.tgz",
"integrity": "sha512-xre4gUbX1MPtgQRKj2pxJENp/RnaHaxYvy3YToVVCrSmAWUu85b9mug6pTXF6zakUjNP2lFWZ1rkSX7gxhB/2A==",
"license": "Apache-2.0",
"dependencies": {
"es6-promise": "^4.2.8",
"webworkify-webpack": "^2.1.5"
}
},
"node_modules/follow-redirects": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
@@ -8165,6 +8182,12 @@
"npm": ">=3.10.0"
}
},
"node_modules/webworkify-webpack": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/webworkify-webpack/-/webworkify-webpack-2.1.5.tgz",
"integrity": "sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==",
"license": "MIT"
},
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",

View File

@@ -27,6 +27,7 @@
"dotenv": "^17.2.3",
"echarts": "^6.0.0",
"express": "^4.21.2",
"flv.js": "^1.6.2",
"js-cookie": "^3.0.5",
"lodash": "^4.18.1",
"lucide-react": "^0.546.0",

View File

@@ -1,104 +1,253 @@
import React, { useState } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Card as AntdCard, Tag, Button, Space, Typography, Row, Col, message
Card, Tag, Button, Space, Typography, Row, Col, Select, Table, Modal, message, Empty, Spin
} from 'antd';
import {
VideoCameraOutlined, PlayCircleOutlined, HistoryOutlined, SettingOutlined,
CameraOutlined, EyeOutlined
VideoCameraOutlined, DownloadOutlined, PlayCircleOutlined,
FolderOutlined, ReloadOutlined, EyeOutlined, DatabaseOutlined
} from '@ant-design/icons';
import axios from 'axios';
import flvjs from 'flv.js';
const { Title, Text } = Typography;
// ======================
// 视频管理(仅 admin 可见)
// 说明:当前为前端展示页,数据使用本地 mock 视频通道。
// 接入真实视频时,「实时预览」可跳转至监控页 /monitor(VideoMonitorPage),
// 或对接 WebRTC / 流媒体服务获取实时画面与回放地址。
// 真实数据对接 nodevideo(端口 9528,SRS 录制文件服务):
// POST /list body: { deviceId?, year?, month?, day? }
// 不传参 → 所有通道(deviceId);传 deviceId → 年份目录;逐级下钻 → 当天视频文件
// 返回 { code, path, directories:[], files:[{ fileName, size, sizeFormatted }] }
// GET /video/download?deviceId=&year=&month=&day=&fileName=
// 流式返回录制视频(SRS 多为 .flv,用 flv.js 播放)
// 说明:本机开发直连 http://localhost:9528;若部署到其它主机,请改为反向代理/相对路径。
// ======================
const MOCK_CHANNELS = [
{ id: 'CAM-01', name: '1区组件巡检摄像头', device: '巡检机器人-01', status: 'online', location: 'A排 1-12 组件', lastUpdate: '2026-08-10 12:58' },
{ id: 'CAM-02', name: '升压站全景摄像头', device: '固定球机', status: 'online', location: '升压站', lastUpdate: '2026-08-10 12:59' },
{ id: 'CAM-03', name: '无人机航拍通道', device: '无人机-01', status: 'offline', location: '全场', lastUpdate: '2026-08-09 18:20' },
{ id: 'CAM-04', name: '汇流箱热成像', device: '巡检机器人-02', status: 'online', location: 'B排汇流箱', lastUpdate: '2026-08-10 13:01' },
{ id: 'CAM-05', name: '逆变器舱监控', device: '固定枪机', status: 'online', location: '逆变器室', lastUpdate: '2026-08-10 13:00' },
{ id: 'CAM-06', name: '组件热斑巡检通道', device: '无人机-02', status: 'offline', location: 'C排组件', lastUpdate: '2026-08-09 17:45' },
];
const NODEVIDEO_BASE = 'http://1.95.137.212:30003';
const fetchList = async (body: Record<string, string> = {}) => {
const res = await axios.post(`${NODEVIDEO_BASE}/list`, body);
return res.data as { code: number; path: string; directories: string[]; files: { fileName: string; size: number; sizeFormatted: string }[]; error?: string };
};
const buildDownloadUrl = (p: { deviceId: string; year: string; month: string; day: string; fileName: string }) =>
`${NODEVIDEO_BASE}/video/download?deviceId=${encodeURIComponent(p.deviceId)}&year=${p.year}&month=${p.month}&day=${p.day}&fileName=${encodeURIComponent(p.fileName)}`;
const isFlv = (name: string) => name.toLowerCase().endsWith('.flv');
const VideoManagement = () => {
const navigate = useNavigate();
const [channels] = useState(MOCK_CHANNELS);
const [channels, setChannels] = useState<string[]>([]);
const [channel, setChannel] = useState<string | undefined>();
const [years, setYears] = useState<string[]>([]);
const [year, setYear] = useState<string | undefined>();
const [months, setMonths] = useState<string[]>([]);
const [month, setMonth] = useState<string | undefined>();
const [days, setDays] = useState<string[]>([]);
const [day, setDay] = useState<string | undefined>();
const [files, setFiles] = useState<{ fileName: string; size: number; sizeFormatted: string }[]>([]);
const [loading, setLoading] = useState(false);
const [preview, setPreview] = useState<{ url: string; fileName: string } | null>(null);
const videoRef = useRef<HTMLVideoElement>(null);
const flvPlayerRef = useRef<any>(null);
const onlineCount = channels.filter((c) => c.status === 'online').length;
const handlePreview = (ch: any) => {
if (ch.status !== 'online') {
message.warning(`${ch.name} 当前离线,无法预览`);
return;
// 1. 加载通道列表(deviceId)
const loadChannels = async () => {
setLoading(true);
try {
const data = await fetchList({});
if (data.code === 200) {
setChannels(data.directories || []);
} else {
setChannels([]);
message.warning(data.error || '读取通道失败');
}
} catch (e) {
setChannels([]);
message.error('无法连接 nodevideo 服务(9528)');
} finally {
setLoading(false);
}
// 跳转至实时监控页,可携带通道信息
navigate('/monitor', { state: { channel: ch.id, name: ch.name } });
};
useEffect(() => { loadChannels(); }, []);
// 2. 选通道 → 加载年份
const onChannelChange = async (val: string) => {
setChannel(val);
setYear(undefined); setYears([]);
setMonth(undefined); setMonths([]);
setDay(undefined); setDays([]);
setFiles([]);
if (!val) return;
setLoading(true);
try {
const data = await fetchList({ deviceId: val });
setYears(data.code === 200 ? (data.directories || []) : []);
} catch { setYears([]); }
finally { setLoading(false); }
};
// 3. 选年份 → 加载月份
const onYearChange = async (val: string) => {
setYear(val);
setMonth(undefined); setMonths([]);
setDay(undefined); setDays([]);
setFiles([]);
if (!val || !channel) return;
setLoading(true);
try {
const data = await fetchList({ deviceId: channel, year: val });
setMonths(data.code === 200 ? (data.directories || []) : []);
} catch { setMonths([]); }
finally { setLoading(false); }
};
// 4. 选月份 → 加载日期
const onMonthChange = async (val: string) => {
setMonth(val);
setDay(undefined); setDays([]);
setFiles([]);
if (!val || !channel || !year) return;
setLoading(true);
try {
const data = await fetchList({ deviceId: channel, year, month: val });
setDays(data.code === 200 ? (data.directories || []) : []);
} catch { setDays([]); }
finally { setLoading(false); }
};
// 5. 选日期 → 加载当天视频文件
const onDayChange = async (val: string) => {
setDay(val);
setFiles([]);
if (!val || !channel || !year || !month) return;
setLoading(true);
try {
const data = await fetchList({ deviceId: channel, year, month, day: val });
setFiles(data.code === 200 ? (data.files || []) : []);
if (data.code !== 200) message.warning(data.error || '读取文件失败');
} catch { setFiles([]); message.error('读取文件失败'); }
finally { setLoading(false); }
};
// 预览:flv 用 flv.js,其余用原生 video
useEffect(() => {
if (!preview) return;
const video = videoRef.current;
if (!video) return;
if (isFlv(preview.fileName) && flvjs.isSupported()) {
const player = flvjs.createPlayer({ type: 'flv', url: preview.url, isLive: false });
player.attachMediaElement(video);
player.load();
player.play().catch(() => { /* 自动播放策略可能拦截,用户手动点击 */ });
flvPlayerRef.current = player;
}
return () => {
flvPlayerRef.current?.destroy();
flvPlayerRef.current = null;
};
}, [preview]);
const openPreview = (file: { fileName: string }) => {
if (!channel || !year || !month || !day) return;
setPreview({
url: buildDownloadUrl({ deviceId: channel, year, month, day, fileName: file.fileName }),
fileName: file.fileName,
});
};
const columns = [
{ title: '文件名', dataIndex: 'fileName', key: 'fileName', ellipsis: true },
{ title: '大小', dataIndex: 'sizeFormatted', key: 'sizeFormatted', width: 120 },
{
title: '操作', key: 'action', width: 160,
render: (_: any, rec: { fileName: string }) => (
<Space>
<Button size="small" type="link" icon={<PlayCircleOutlined />} onClick={() => openPreview(rec)}>预览</Button>
<a href={buildDownloadUrl({ deviceId: channel!, year: year!, month: month!, day: day!, fileName: rec.fileName })} target="_blank" rel="noreferrer">
<Button size="small" type="link" icon={<DownloadOutlined />}>下载</Button>
</a>
</Space>
),
},
];
return (
<div style={{ padding: '8px 0px', background: '#f5f7fa', minHeight: '100vh' }}>
<div style={{ marginBottom: 8 }}>
<Title level={4} style={{ margin: 0, fontSize: 'clamp(14px, 1.1vw, 18px)' }}>视频管理</Title>
<Text type="secondary" style={{ fontSize: 12 }}>视频通道总览与预览管理,仅超级管理员可查看 · 在线 {onlineCount}/{channels.length}</Text>
<Text type="secondary" style={{ fontSize: 12 }}>
录像通道与回放管理(对接 nodevideo :9528) · 共 {channels.length} 个通道
</Text>
</div>
<Row gutter={[8, 8]}>
{channels.map((ch) => {
const online = ch.status === 'online';
return (
<Col xs={24} sm={12} lg={8} xl={6} key={ch.id}>
<AntdCard
bordered={false}
style={{ borderRadius: 10, boxShadow: '0 2px 8px rgba(0,0,0,0.04)', overflow: 'hidden' }}
bodyStyle={{ padding: 12 }}
>
{/* 视频快照占位 */}
<div style={{
position: 'relative', height: 120, borderRadius: 8, marginBottom: 10,
background: online
? 'linear-gradient(135deg, #0b2545 0%, #134074 100%)'
: 'linear-gradient(135deg, #2b2b2b 0%, #4a4a4a 100%)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<CameraOutlined style={{ fontSize: 36, color: online ? 'rgba(255,255,255,0.85)' : 'rgba(255,255,255,0.35)' }} />
<div style={{
position: 'absolute', top: 8, left: 8,
padding: '0 8px', borderRadius: 10, fontSize: 10,
background: online ? 'rgba(82,196,26,0.9)' : 'rgba(0,0,0,0.45)',
color: '#fff',
}}>
{online ? '● 在线' : '○ 离线'}
</div>
<div style={{
position: 'absolute', bottom: 8, right: 8,
padding: '0 8px', borderRadius: 10, fontSize: 10,
background: 'rgba(0,0,0,0.45)', color: '#fff',
}}>{ch.id}</div>
</div>
<Card bordered={false} style={{ borderRadius: 10, marginBottom: 8, boxShadow: '0 2px 8px rgba(0,0,0,0.04)' }} bodyStyle={{ padding: 12 }}>
<Row gutter={[8, 8]} align="middle">
<Col xs={24} sm={10} md={7}>
<Text style={{ fontSize: 12, color: '#666' }}>通道</Text>
<Select
size="small" style={{ width: '100%' }} placeholder="选择设备通道"
loading={loading && !channels.length}
value={channel} onChange={onChannelChange}
options={channels.map((c) => ({ value: c, label: c }))}
showSearch optionFilterProp="label"
/>
</Col>
<Col xs={8} sm={5} md={4}>
<Text style={{ fontSize: 12, color: '#666' }}>年</Text>
<Select size="small" style={{ width: '100%' }} placeholder="年" disabled={!channel}
value={year} onChange={onYearChange}
options={years.map((y) => ({ value: y, label: y }))} />
</Col>
<Col xs={8} sm={5} md={4}>
<Text style={{ fontSize: 12, color: '#666' }}>月</Text>
<Select size="small" style={{ width: '100%' }} placeholder="月" disabled={!year}
value={month} onChange={onMonthChange}
options={months.map((m) => ({ value: m, label: m }))} />
</Col>
<Col xs={8} sm={5} md={4}>
<Text style={{ fontSize: 12, color: '#666' }}>日</Text>
<Select size="small" style={{ width: '100%' }} placeholder="日" disabled={!month}
value={day} onChange={onDayChange}
options={days.map((d) => ({ value: d, label: d }))} />
</Col>
<Col xs={24} sm={4} md={5}>
<Button size="small" icon={<ReloadOutlined />} onClick={loadChannels} style={{ width: '100%' }}>刷新通道</Button>
</Col>
</Row>
</Card>
<div style={{ fontSize: 13, fontWeight: 600, color: '#262626', marginBottom: 4 }}>{ch.name}</div>
<Space size={4} style={{ fontSize: 11, color: '#8c8c8c', marginBottom: 8 }} wrap>
<VideoCameraOutlined />
<span>{ch.device}</span>
<Tag bordered={false} style={{ margin: 0, fontSize: 10 }} color={online ? 'green' : 'default'}>{ch.location}</Tag>
</Space>
<Card bordered={false} style={{ borderRadius: 10, boxShadow: '0 2px 8px rgba(0,0,0,0.04)' }} bodyStyle={{ padding: 4 }}>
{loading ? (
<div style={{ textAlign: 'center', padding: 40 }}><Spin /></div>
) : !channel ? (
<Empty description="请先选择视频通道" style={{ padding: 40 }} />
) : !day ? (
<Empty description="请选择 年 / 月 / 日 加载录像" image={Empty.PRESENTED_IMAGE_SIMPLE} style={{ padding: 40 }} />
) : files.length === 0 ? (
<Empty description="该日暂无录像文件" style={{ padding: 40 }} />
) : (
<Table
size="small" rowKey="fileName" columns={columns} dataSource={files}
pagination={{ size: 'small', pageSize: 10, showTotal: (t) => `共 ${t} 个文件` }}
/>
)}
</Card>
<div style={{ display: 'flex', gap: 6, marginTop: 4 }}>
<Button size="small" type="primary" icon={<PlayCircleOutlined />} style={{ flex: 1 }} onClick={() => handlePreview(ch)}>预览</Button>
<Button size="small" icon={<HistoryOutlined />} disabled={!online} onClick={() => message.info(`${ch.name} 回放功能开发中`)}>回放</Button>
<Button size="small" icon={<SettingOutlined />} onClick={() => message.info(`${ch.name} 参数设置`)} />
</div>
</AntdCard>
</Col>
);
})}
</Row>
<Modal
open={!!preview} title={`预览 ${preview?.fileName || ''}`} footer={null}
onCancel={() => setPreview(null)} width={880} destroyOnClose
>
{preview && isFlv(preview.fileName) ? (
<video ref={videoRef} controls style={{ width: '100%', background: '#000', borderRadius: 8 }} />
) : preview && (
<video src={preview.url} controls style={{ width: '100%', background: '#000', borderRadius: 8 }} />
)}
<div style={{ marginTop: 8, color: '#888', fontSize: 12 }}>
若无法播放,请使用「下载」按钮获取文件后用本地播放器查看。
</div>
</Modal>
</div>
);
};