设备管理 tab重构 加上 flv预览 试试看 但是说 这个不建议预览 只要 下载

This commit is contained in:
mmc
2026-08-12 09:23:47 +08:00
parent a69190cbdc
commit 3c932c41fd
6 changed files with 588 additions and 524 deletions

View File

@@ -1,223 +0,0 @@

> react-example@0.0.0 build
> vite build
vite v6.4.2 building for production...
transforming...
鉁?[39m 9318 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-DtM2zGu_.js  0.11 kB 鈹?gzip: 0.13 kB
dist/assets/EyeOutlined-BpIrDvhk.js  0.20 kB 鈹?gzip: 0.18 kB
dist/assets/index-BZ2XYiLY.js  0.29 kB 鈹?gzip: 0.17 kB
dist/assets/constants-DwgWOJkA.js  0.31 kB 鈹?gzip: 0.28 kB
dist/assets/chevron-right-C-2t-OOD.js  0.31 kB 鈹?gzip: 0.25 kB
dist/assets/SwapRightOutlined-CaVf5wQU.js  0.32 kB 鈹?gzip: 0.26 kB
dist/assets/index-CPl-Ppnu.js  0.35 kB 鈹?gzip: 0.19 kB
dist/assets/useBreakpoint-B_LmyIb1.js  0.37 kB 鈹?gzip: 0.27 kB
dist/assets/Empty-Ctr5z1y_.js  0.40 kB 鈹?gzip: 0.32 kB
dist/assets/CloseOutlined-DNEu5lOW.js  0.41 kB 鈹?gzip: 0.31 kB
dist/assets/wind-BGkejR8u.js  0.43 kB 鈹?gzip: 0.31 kB
dist/assets/CheckOutlined-DWAq-EXD.js  0.44 kB 鈹?gzip: 0.31 kB
dist/assets/PlusOutlined-9xQFYjfY.js  0.44 kB 鈹?gzip: 0.31 kB
dist/assets/index-Bw21YrWE.js  0.44 kB 鈹?gzip: 0.22 kB
dist/assets/zap-T1mbixAe.js  0.45 kB 鈹?gzip: 0.32 kB
dist/assets/collapse-BbEVqHco.js  0.45 kB 鈹?gzip: 0.20 kB
dist/assets/EyeOutlined-ChMUbuX5.js  0.45 kB 鈹?gzip: 0.32 kB
dist/assets/EditOutlined-D_m5HT9j.js  0.45 kB 鈹?gzip: 0.33 kB
dist/assets/DeleteOutlined-DJWa8o0s.js  0.45 kB 鈹?gzip: 0.32 kB
dist/assets/SearchOutlined-D93E4Oj1.js  0.45 kB 鈹?gzip: 0.33 kB
dist/assets/CalendarOutlined-DZsP2fe7.js  0.45 kB 鈹?gzip: 0.33 kB
dist/assets/DownloadOutlined-RqkGX7Gr.js  0.45 kB 鈹?gzip: 0.33 kB
dist/assets/ClockCircleOutlined-BPcFT2WX.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-D8S1FByg.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/EditOutlined-DdmGLkPL.js  0.56 kB 鈹?gzip: 0.38 kB
dist/assets/EyeOutlined-Bmd706kk.js  0.63 kB 鈹?gzip: 0.39 kB
dist/assets/PauseOutlined-BqDFOIsB.js  0.65 kB 鈹?gzip: 0.47 kB
dist/assets/play-B5DtZ9KN.js  0.66 kB 鈹?gzip: 0.36 kB
dist/assets/MoreOutlined-Bojj3j6_.js  0.67 kB 鈹?gzip: 0.43 kB
dist/assets/index-EuwyWURW.js  0.75 kB 鈹?gzip: 0.30 kB
dist/assets/ThunderboltOutlined-Dpsk7I4X.js  0.81 kB 鈹?gzip: 0.57 kB
dist/assets/SearchOutlined-DBDe8SEH.js  0.82 kB 鈹?gzip: 0.52 kB
dist/assets/RiseOutlined-CBId0fo2.js  0.83 kB 鈹?gzip: 0.57 kB
dist/assets/cpu-Sb-QLlDK.js  0.83 kB 鈹?gzip: 0.41 kB
dist/assets/trending-up-CfL4zUOM.js  0.83 kB 鈹?gzip: 0.39 kB
dist/assets/WarningOutlined-iECt8d0C.js  0.84 kB 鈹?gzip: 0.59 kB
dist/assets/DatabaseOutlined-BwouSYc2.js  0.84 kB 鈹?gzip: 0.53 kB
dist/assets/EnterOutlined-DahysLq8.js  0.85 kB 鈹?gzip: 0.46 kB
dist/assets/InfoCircleOutlined-3BD64K2v.js  0.88 kB 鈹?gzip: 0.56 kB
dist/assets/MailOutlined-CqThN_M3.js  0.88 kB 鈹?gzip: 0.59 kB
dist/assets/LockOutlined-Cj1K_c-k.js  0.88 kB 鈹?gzip: 0.60 kB
dist/assets/ExclamationCircleOutlined-DwOXB-Gw.js  0.88 kB 鈹?gzip: 0.57 kB
dist/assets/index-DRJqI9JD.js  0.89 kB 鈹?gzip: 0.28 kB
dist/assets/VideoCameraOutlined-B27jqIy_.js  0.89 kB 鈹?gzip: 0.62 kB
dist/assets/PlayCircleOutlined-BYW1xJTi.js  0.90 kB 鈹?gzip: 0.59 kB
dist/assets/LeftOutlined-D6OBLFDp.js  0.90 kB 鈹?gzip: 0.49 kB
dist/assets/ProtectedRoute-IM0Sy2Xs.js  0.93 kB 鈹?gzip: 0.55 kB
dist/assets/index-DVB2C8Ff.js  0.93 kB 鈹?gzip: 0.56 kB
dist/assets/CheckCircleOutlined-Bxmsq8Kw.js  0.94 kB 鈹?gzip: 0.61 kB
dist/assets/droplets-NAdX3BmJ.js  0.94 kB 鈹?gzip: 0.50 kB
dist/assets/FileTextOutlined-CNX0YM61.js  0.96 kB 鈹?gzip: 0.62 kB
dist/assets/RobotOutlined-BORS_9JP.js  0.97 kB 鈹?gzip: 0.63 kB
dist/assets/BellOutlined-D5GkP0tr.js  0.97 kB 鈹?gzip: 0.65 kB
dist/assets/FormOutlined-BQt8zVxi.js  1.00 kB 鈹?gzip: 0.66 kB
dist/assets/BarChartOutlined-CuaUHpnJ.js  1.01 kB 鈹?gzip: 0.53 kB
dist/assets/BgColorsOutlined-DChci6LA.js  1.01 kB 鈹?gzip: 0.66 kB
dist/assets/CameraOutlined-BVs6tmVA.js  1.02 kB 鈹?gzip: 0.66 kB
dist/assets/AppstoreOutlined-VqZrPBsZ.js  1.06 kB 鈹?gzip: 0.52 kB
dist/assets/ExperimentOutlined-BgHosctB.js  1.06 kB 鈹?gzip: 0.72 kB
dist/assets/BranchesOutlined-DoqceiH7.js  1.07 kB 鈹?gzip: 0.65 kB
dist/assets/index-7tIGlO1L.js  1.10 kB 鈹?gzip: 0.59 kB
dist/assets/SystemSetting-C5nTGJmA.js  1.11 kB 鈹?gzip: 0.69 kB
dist/assets/ExpandOutlined-DmxD2QPe.js  1.12 kB 鈹?gzip: 0.59 kB
dist/assets/CloudOutlined-7Kp-N517.js  1.13 kB 鈹?gzip: 0.74 kB
dist/assets/UserOutlined-C4ebdDKF.js  1.22 kB 鈹?gzip: 0.76 kB
dist/assets/ZoomOutOutlined-CdPjzMfa.js  1.22 kB 鈹?gzip: 0.42 kB
dist/assets/ReloadOutlined-DdNrEJCC.js  1.23 kB 鈹?gzip: 0.77 kB
dist/assets/AlertOutlined-BEmiVn_J.js  1.24 kB 鈹?gzip: 0.73 kB
dist/assets/AimOutlined-DjiWwPPF.js  1.25 kB 鈹?gzip: 0.71 kB
dist/assets/alarmApi-BZ-m506o.js  1.25 kB 鈹?gzip: 0.35 kB
dist/assets/SyncOutlined-CLMibV2z.js  1.27 kB 鈹?gzip: 0.81 kB
dist/assets/MoneyCollectOutlined-DPX31kZt.js  1.30 kB 鈹?gzip: 0.79 kB
dist/assets/FullscreenOutlined-CKE8zdPY.js  1.34 kB 鈹?gzip: 0.77 kB
dist/assets/HistoryOutlined-Cm2yhZBV.js  1.41 kB 鈹?gzip: 0.87 kB
dist/assets/SafetyCertificateOutlined-Cv9Ug_8-.js  1.41 kB 鈹?gzip: 0.77 kB
dist/assets/SwapOutlined-C3QhvSoJ.js  1.42 kB 鈹?gzip: 0.65 kB
dist/assets/ZoomOutOutlined-BsCJgCwM.js  1.47 kB 鈹?gzip: 0.67 kB
dist/assets/satellite-DN23gGtM.js  1.47 kB 鈹?gzip: 0.54 kB
dist/assets/RocketOutlined-D32l95Ej.js  1.49 kB 鈹?gzip: 0.93 kB
dist/assets/EnvironmentOutlined-DqDsf3JY.js  1.49 kB 鈹?gzip: 0.91 kB
dist/assets/useBubbleLock-CnKa9QU1.js  1.54 kB 鈹?gzip: 0.75 kB
dist/assets/index-CVpDycq2.js  1.61 kB 鈹?gzip: 0.85 kB
dist/assets/ApiOutlined-B-23p3zJ.js  1.61 kB 鈹?gzip: 0.87 kB
dist/assets/SunOutlined-DulLtM_U.js  1.75 kB 鈹?gzip: 0.90 kB
dist/assets/DashboardOutlined-BcbkGzZH.js  1.83 kB 鈹?gzip: 0.93 kB
dist/assets/createLucideIcon-DQHekqUj.js  1.86 kB 鈹?gzip: 0.80 kB
dist/assets/CarOutlined-dQz3FjQ0.js  2.00 kB 鈹?gzip: 0.98 kB
dist/assets/index-DFuZdxqB.js  2.04 kB 鈹?gzip: 1.00 kB
dist/assets/systemSetting-Du4ydvo8.js  2.11 kB 鈹?gzip: 0.45 kB
dist/assets/PlayCircleFilled-CpNFZPGa.js  2.14 kB 鈹?gzip: 1.04 kB
dist/assets/LinkOutlined-DIT1vcCm.js  2.19 kB 鈹?gzip: 0.82 kB
dist/assets/index-DUin50PQ.js  2.19 kB 鈹?gzip: 1.14 kB
dist/assets/SettingOutlined-D_j9s6J1.js  2.35 kB 鈹?gzip: 1.21 kB
dist/assets/VideoPlayer-CIv7hT9k.js  2.39 kB 鈹?gzip: 1.33 kB
dist/assets/PictureTwoTone-BaO8R2Hf.js  2.42 kB 鈹?gzip: 1.04 kB
dist/assets/index-B5YBJdOw.js  2.42 kB 鈹?gzip: 1.16 kB
dist/assets/LoadingOutlined-EmNUEJCp.js  2.94 kB 鈹?gzip: 1.38 kB
dist/assets/SaveOutlined-CMHNvWPT.js  3.28 kB 鈹?gzip: 1.40 kB
dist/assets/device-CZX1s96N.js  3.38 kB 鈹?gzip: 0.76 kB
dist/assets/index-CBNqhCuo.js  3.61 kB 鈹?gzip: 1.27 kB
dist/assets/row-DptZftaY.js  3.74 kB 鈹?gzip: 1.64 kB
dist/assets/OnlineUser-B-LR1RQ1.js  3.74 kB 鈹?gzip: 1.77 kB
dist/assets/MonitorOutlined-8H67Im48.js  3.85 kB 鈹?gzip: 1.29 kB
dist/assets/VideoCameraAddOutlined-ujFYsK7R.js  3.86 kB 鈹?gzip: 1.42 kB
dist/assets/VideoManagement-lgnfcqOK.js  3.89 kB 鈹?gzip: 1.78 kB
dist/assets/index-s0uyhwL6.js  4.14 kB 鈹?gzip: 1.74 kB
dist/assets/MenuUnfoldOutlined-oe1UMe64.js  4.16 kB 鈹?gzip: 1.30 kB
dist/assets/ScanOutlined-B9n4kgAv.js  4.19 kB 鈹?gzip: 1.38 kB
dist/assets/index-ChYh3EL1.js  4.21 kB 鈹?gzip: 1.58 kB
dist/assets/index-DaNu9UFa.js  4.38 kB 鈹?gzip: 1.75 kB
dist/assets/Organization-CvcvXgnR.js  4.44 kB 鈹?gzip: 1.95 kB
dist/assets/index-BrtQE5cA.js  4.58 kB 鈹?gzip: 1.91 kB
dist/assets/WomanOutlined-DhEdcpR3.js  4.82 kB 鈹?gzip: 1.76 kB
dist/assets/LineChartOutlined-D9mAa485.js  5.07 kB 鈹?gzip: 1.82 kB
dist/assets/Roles-peab14ff.js  5.30 kB 鈹?gzip: 2.32 kB
dist/assets/index-UaCBHKR_.js  5.44 kB 鈹?gzip: 2.24 kB
dist/assets/index-DedMQGVM.js  5.57 kB 鈹?gzip: 2.20 kB
dist/assets/Users-CMzPBXJp.js  5.60 kB 鈹?gzip: 2.22 kB
dist/assets/index-BL30Qw1c.js  5.83 kB 鈹?gzip: 2.63 kB
dist/assets/Sider-DXAaqKLr.js  6.64 kB 鈹?gzip: 2.85 kB
dist/assets/Input-4q6jt_g5.js  6.73 kB 鈹?gzip: 2.71 kB
dist/assets/LogManagement-O6D_fZci.js  6.94 kB 鈹?gzip: 3.29 kB
dist/assets/dayjs.min-buSFCgKN.js  7.15 kB 鈹?gzip: 3.11 kB
dist/assets/Timeline-CRgNRkZL.js  7.47 kB 鈹?gzip: 2.32 kB
dist/assets/Profile-Cm2WO_xU.js  7.73 kB 鈹?gzip: 2.71 kB
dist/assets/index-6bOsNg-M.js  7.98 kB 鈹?gzip: 2.71 kB
dist/assets/GlobalOutlined-BiQGRkIB.js  8.13 kB 鈹?gzip: 2.73 kB
dist/assets/StationManage-gzXazdWA.js  9.47 kB 鈹?gzip: 3.25 kB
dist/assets/CaretUpOutlined-CJ_QY0uc.js  9.72 kB 鈹?gzip: 3.73 kB
dist/assets/index-D3Ma-lrv.js  9.90 kB 鈹?gzip: 4.14 kB
dist/assets/index-BuRUqopv.js  10.86 kB 鈹?gzip: 3.66 kB
dist/assets/index-ALYSXNVI.js  13.09 kB 鈹?gzip: 4.32 kB
dist/assets/RealTimeMonitor-BJaT8v8y.js  13.49 kB 鈹?gzip: 3.91 kB
dist/assets/Downloads-B6JNnYAG.js  14.52 kB 鈹?gzip: 5.40 kB
dist/assets/UserPage-W6MtChV2.js  14.56 kB 鈹?gzip: 5.10 kB
dist/assets/Layout-B5v7n-84.js  15.26 kB 鈹?gzip: 6.20 kB
dist/assets/mowerAllView-CIVQaOob.js  15.31 kB 鈹?gzip: 5.15 kB
dist/assets/progress-VpEMMtch.js  16.32 kB 鈹?gzip: 5.97 kB
dist/assets/Video-CfHlTupO.js  17.68 kB 鈹?gzip: 6.43 kB
dist/assets/VideoMonitorPage-B8eUkNSX.js  19.41 kB 鈹?gzip: 5.77 kB
dist/assets/Home-CsUKVxVV.js  19.63 kB 鈹?gzip: 5.88 kB
dist/assets/ReportCenter-vBJ0zNbs.js  20.12 kB 鈹?gzip: 5.58 kB
dist/assets/AIAnalysis-BXDx_pZ0.js  20.79 kB 鈹?gzip: 6.03 kB
dist/assets/index-B_4lrhUw.js  21.18 kB 鈹?gzip: 7.75 kB
dist/assets/SystemMaintain-B7rVVlmq.js  21.20 kB 鈹?gzip: 6.39 kB
dist/assets/Agri-Bb29XfiI.js  23.53 kB 鈹?gzip: 6.77 kB
dist/assets/CleanOptimization-CWLVvh62.js  23.86 kB 鈹?gzip: 6.84 kB
dist/assets/BasicSettings-CuuSvPVD.js  24.01 kB 鈹?gzip: 6.95 kB
dist/assets/Pagination-uKW-OnZm.js  24.07 kB 鈹?gzip: 7.27 kB
dist/assets/index-FQVBqmQl.js  24.29 kB 鈹?gzip: 6.84 kB
dist/assets/index-CGjCB8GI.js  24.61 kB 鈹?gzip: 8.41 kB
dist/assets/DeviceAccessPage-CVno98nd.js  27.21 kB 鈹?gzip: 7.74 kB
dist/assets/DeviceStatusPage-uF0YhWCt.js  27.91 kB 鈹?gzip: 8.86 kB
dist/assets/AntdIcon-Vj78gm8A.js  28.67 kB 鈹?gzip: 10.61 kB
dist/assets/index-BPOI5hR7.js  30.34 kB 鈹?gzip: 10.80 kB
dist/assets/index-f3imQPvT.js  31.74 kB 鈹?gzip: 11.04 kB
dist/assets/TextArea-CsLo8K_h.js  33.23 kB 鈹?gzip: 9.32 kB
dist/assets/DeviceOverviewPage-CD8FxotZ.js  35.09 kB 鈹?gzip: 9.63 kB
dist/assets/Map-DUcm8af3.js  35.52 kB 鈹?gzip: 16.50 kB
dist/assets/DeviceParamModal-DSGwPtsu.js  36.02 kB 鈹?gzip: 12.66 kB
dist/assets/Alerts-ZPDUGkfy.js  43.36 kB 鈹?gzip: 13.62 kB
dist/assets/WayLinePage-y2GT_PFs.js  47.95 kB 鈹?gzip: 12.81 kB
dist/assets/RobotTaskPage-CZh-hfge.js  50.72 kB 鈹?gzip: 15.77 kB
dist/assets/index-BTopT9-s.js  53.48 kB 鈹?gzip: 17.46 kB
dist/assets/index-0KzkHCl3.js  85.39 kB 鈹?gzip: 28.40 kB
dist/assets/index-CjPizwAt.js  104.37 kB 鈹?gzip: 33.62 kB
dist/assets/Table-CA0nEd5i.js  105.27 kB 鈹?gzip: 35.10 kB
dist/assets/index-CowEi-GC.js  118.83 kB 鈹?gzip: 40.16 kB
dist/assets/Login-DfIjGkfy.js  127.88 kB 鈹?gzip: 42.31 kB
dist/assets/useMqtt-DpGm36ma.js  372.77 kB 鈹?gzip: 112.38 kB
dist/assets/recharts-CrJS7ZSc.js  376.93 kB 鈹?gzip: 108.14 kB
dist/assets/index-Dt7gCQGU.js  651.89 kB 鈹?gzip: 215.80 kB
dist/assets/DeviceManagement-DpXxWEOS.js  722.91 kB 鈹?gzip: 158.92 kB
dist/assets/MenuList-pnm43MIv.js  857.20 kB 鈹?gzip: 165.01 kB
dist/assets/WorkOrder-DM_q3eps.js  1,507.95 kB 鈹?gzip: 445.53 kB
dist/assets/DroneVideoPlayer-DsyzzCPW.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 2m 34s

View File

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

> 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

View File

@@ -0,0 +1,268 @@
# 机器人智慧服务系统 Alpha · 国内云部署说明
> 适用:将本项目(前端 SPA + Alpha 三服务 + nodevideo 视频服务)部署到国内云服务器
> (阿里云 ECS / 腾讯云 CVM / 华为云 ECS,推荐 Ubuntu 22.04 LTS)。
> 配套代码文档见 `docs/` 下设备数据字典、命令字典、状态机、联调报告等。
---
## 一、系统组成与端口
| 组件 | 目录 / 说明 | 端口 | 数据 / 依赖 |
|------|------------|:---:|------------|
| 前端 SPA(React 19 + Vite) | 仓库根目录 `npm run build` → `dist/` | 开发 3001;生产由 Nginx 托管 80/443 | 通过 `env.js` 的 `baseURL` 指向后端 |
| 设备接入服务 Alpha | `services/device-access` | **3010** | `data/devices.json` |
| 状态服务 Alpha | `services/status` | **3020** | `data/status.json` |
| 命令服务 Alpha | `services/command` | **3030** | `data/commands.json` |
| 主后端(已有,非 Alpha) | 由 `env.js` 指向 | API **8081** / WS **9002** / MQTT **8083** / GeoServer **59018** | 需与前端在同一云环境且可达 |
| 视频服务 nodevideo | 独立项目,视频管理页直连 | **9528** | SRS 录制目录 `BASE_DIR=/hdd/data/media/record/video/srs/` |
技术栈:Node.js + Express + 本地 JSON 文件(三服务均**不使用数据库**,数据落 `data/*.json`)。
Alpha 三服务均 `app.use(cors())` 全开;nodevideo 亦 `app.use(cors())` 全开(跨域直连可行,但生产建议走反代同源)。
---
## 二、云服务器准备
1. 选购:2 vCPU / 4 GB 起(含 Cesium 三维前端,建议 4 vCPU / 8 GB);系统盘 40 GB+,数据盘按需(录像/JSON 备份)。
2. 系统:Ubuntu 22.04 LTS(或腾讯云 TencentOS Server 3/4)。
3. 安全组(推荐**仅暴露 22/80/443**,其它端口走 Nginx 反代):
- 入站:22(SSH)、80(HTTP)、443(HTTPS)。
- 出站:全通。
- 若采用「直连各端口」方案,需额外开放 3010/3020/3030/9528/8081 等,并依赖 CORS——**不推荐生产使用**。
4. 域名(可选但建议):国内云需 ICP 备案后才能用 80/443 绑定域名;备案期间可先用 IP + 自签/免费证书测试。
---
## 三、基础环境(Ubuntu)
```bash
# 1. 系统更新
sudo apt update && sudo apt -y upgrade
# 2. Node.js 20 LTS(项目用 React19+Vite6,需 Node 18+)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt -y install nodejs
node -v && npm -v # 期望 v20.x / v10.x
# 3. 进程守护 PM2(全局)
sudo npm i -g pm2
# 4. 反向代理 + HTTPS
sudo apt -y install nginx
sudo snap install --classic certbot && sudo ln -s /snap/bin/certbot /usr/bin/certbot
```
> 国内云若访问 npm 慢,可切换镜像:`npm config set registry https://registry.npmmirror.com`
---
## 四、获取代码
```bash
cd /opt
sudo git clone <你的仓库地址> robot-system
sudo chown -R $USER:$USER /opt/robot-system
cd /opt/robot-system
```
> 若不用 git,可用 `scp` / 对象存储 将本地仓库打包上传解压。
---
## 五、部署 Alpha 三服务
三个服务结构一致,分别安装依赖并启动。下面以 `device-access` 为例,其余两目录同理。
```bash
# 设备接入服务
cd /opt/robot-system/services/device-access
npm install
pm2 start server.js --name device-access --watch
# 状态服务
cd /opt/robot-system/services/status
npm install
pm2 start server.js --name status --watch
# 命令服务
cd /opt/robot-system/services/command
npm install
pm2 start server.js --name command --watch
# 保存进程列表并实现开机自启(Linux 上 pm2 startup 可用)
pm2 save
pm2 startup # 按提示执行它打印的 sudo 命令(写 systemd 单元)
```
验证:
```bash
pm2 ls # 三个服务 online
curl -s http://127.0.0.1:3010/api/devices/online/ROBOT-001 # 返回 JSON
curl -s http://127.0.0.1:3020/health ; curl -s http://127.0.0.1:3030/health
```
> 端口固定写死在 `server.js`(`PORT = 3010/3020/3030`),如需变更改文件即可。
> 数据文件 `data/*.json` 首次启动自动创建(空数组)。部署升级时**务必备份该目录**,避免覆盖业务数据。
---
## 六、部署前端(SPA)
### 6.1 配置生产后端地址
编辑仓库根目录 `env.js`,将 `PROD` 段改为云上实际地址(默认 `MODE=production` 时生效):
```js
[ENV.PROD]: {
baseURL: 'https://你的域名或http://云服务器IP:8081', // 主后端 API
WS_URL: 'wss://你的域名/ws/' 或 'ws://云服务器IP:9002/ws/',
mqtt_url: 'wss://你的域名/mqtt' 或 'ws://云服务器IP:8083/mqtt',
geoserver_url:'https://你的域名/geoserver' 或 'http://云服务器IP:59018/geoserver'
},
```
### 6.2 构建
```bash
cd /opt/robot-system
npm install
npm run build # 产出 dist/(MODE=production,使用 env.js 的 PROD 配置)
```
### 6.3 视频管理页地址(重要)
`src/components/SystemSetting/VideoManagement.tsx` 顶部硬编码了:
```ts
const NODEVIDEO_BASE = 'http://localhost:9528';
```
部署到云后,浏览器里的 `localhost:9528` 指向**用户本机**而非服务器,视频无法访问。二选一:
- **方案A(反代同源,推荐)**:改为 `const NODEVIDEO_BASE = '/nodevideo'`,并在 Nginx 配置 `location /nodevideo/ { proxy_pass http://127.0.0.1:9528/; }`。
- **方案B(跨域直连)**:改为 `const NODEVIDEO_BASE = 'http://<云服务器IP或域名>:9528'`(nodevideo 已开 CORS,可用;但需安全组开放 9528)。
改完需重新 `npm run build`。
### 6.4 Nginx 托管 + 反代
```nginx
# /etc/nginx/sites-available/robot-system
server {
listen 80; server_name 你的域名或IP;
root /opt/robot-system/dist; index index.html;
location / { try_files $uri $uri/ /index.html; } # SPA 路由回退
# ---- Alpha 三服务(按前端实际 API 前缀分流)----
location /api/devices/ { proxy_pass http://127.0.0.1:3010; }
location /api/status/ { proxy_pass http://127.0.0.1:3020; }
location /api/commands/ { proxy_pass http://127.0.0.1:3030; }
# ---- nodevideo 视频(对应方案A的 /nodevideo)----
location /nodevideo/ { proxy_pass http://127.0.0.1:9528/; }
# ---- 主后端(若与 Alpha 共用 /api 前缀需区分,建议独立子域或前缀)----
location /backend/ { proxy_pass http://127.0.0.1:8081/; }
# WebSocket / MQTT 透传(如前端直连)
location /ws/ { proxy_pass http://127.0.0.1:9002; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }
}
```
> 注意:前端 `env.js` 的 `baseURL` 必须与实际访问方式一致——若走 Nginx 同源,填 `https://你的域名`(或 `/backend` 之类前缀);若主后端与 Alpha 都用 `/api` 前缀会冲突,请为二者分配不同前缀或子域。
启用并重载:
```bash
sudo ln -s /etc/nginx/sites-available/robot-system /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```
---
## 七、部署 nodevideo 视频服务(9528)
视频管理页依赖此服务,需与前端同一云环境可达。
```bash
cd /opt/nodevideo
npm install
pm2 start server.js --name nodevideo
pm2 save
```
**关键路径说明**:`server.js` 中 `BASE_DIR = "/hdd/data/media/record/video/srs/"` 为 Linux 绝对路径(SRS 录制存储)。
- 云服务器需存在该目录并挂载好录像盘;否则 `/list` 返回 `code:500 / directories:[]`,视频管理页显示为空。
- 如路径不同,直接修改 `server.js` 的 `BASE_DIR` 后 `pm2 restart nodevideo`。
- `PORT` 在代码里写 `30003`,但实际按你指定以 **9528** 对外(用 `PORT=9528 pm2 start server.js --name nodevideo` 或反代时统一即可)。
---
## 八、域名与 HTTPS(国内云)
1. **备案**:国内云使用 80/443 + 域名必须完成 ICP 备案(阿里云/腾讯云控制台申请,约 1–2 周)。
2. **证书**:备案后用免费证书
```bash
sudo certbot --nginx -d 你的域名.cn
```
或在云控制台申请免费 DV 证书后下载,Nginx 配置 `ssl_certificate / ssl_certificate_key`。
3. 证书自动续期:`sudo certbot renew --dry-run` 验证,certbot 默认建好 timer。
---
## 九、开机自启(推荐 systemd)
PM2 在 Linux 可用 `pm2 startup`(写 systemd 单元)实现开机拉起全部 `pm2 save` 的进程,最省事。
若偏好纯 systemd,可为每个服务写一个 unit(示例 `device-access.service`):
```ini
[Unit]
Description=Device Access Service Alpha
After=network.target
[Service]
WorkingDirectory=/opt/robot-system/services/device-access
ExecStart=/usr/bin/node server.js
Restart=always
User=www-data
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl enable --now device-access
```
---
## 十、运维与备份
- **日志**:`pm2 logs <name>`;或 journald `journalctl -u device-access -f`。
- **数据备份**:三服务的 `data/*.json` 是业务数据,定时备份:
```bash
tar czf /backup/alpha-data-$(date +%F).tgz /opt/robot-system/services/*/data/
```
- **录像备份**:nodevideo 的 `/hdd/.../srs` 目录按磁盘策略归档。
- **升级流程**:拉新代码 → `npm install` → `npm run build`(前端)→ `pm2 restart <name>`;升级前先备份 `data/`。
- **回滚**:保留上一版 `dist/` 与 `services`,出问题时 `pm2 restart` 旧版本或 git checkout。
---
## 十一、快速检查清单
- [ ] 云服务器 Ubuntu 22.04,安全组仅开 22/80/443(反代方案)
- [ ] Node 20 LTS + npm + pm2 + nginx 已装
- [ ] Alpha 三服务 `pm2 ls` 均 online,端口 3010/3020/3030 监听 127.0.0.1
- [ ] 前端 `npm run build` 成功,`dist/` 由 Nginx 托管
- [ ] `env.js` 的 `PROD` 段 baseURL/WS/MQTT/GeoServer 已改为云地址
- [ ] 视频管理页 `NODEVIDEO_BASE` 已改为 `/nodevideo` 或云地址并重新 build
- [ ] nodevideo :9528 已起,`BASE_DIR` 存在且有录像;`/list` 能返回通道
- [ ] Nginx 反代规则就位,`nginx -t` 通过
- [ ] 域名已备案,HTTPS 证书已配置并自动续期
- [ ] `pm2 save` + `pm2 startup` 完成,重启服务器后服务自动拉起
- [ ] `data/*.json` 备份任务已配置

View File

@@ -17,9 +17,9 @@ import CameraManagement from './CameraManagement';
export default function DeviceManagementPage() {
const [activeTab, setActiveTab] = useState('all');
const [allOrgOptions, setAllOrgOptions] = useState([]);
const [allSiteOptions, setAllSiteOptions] = useState([]);
const [allUserOptions, setAllUserOptions] = useState([]);
const [allOrgOptions, setAllOrgOptions] = useState<any[]>([]);
const [allSiteOptions, setAllSiteOptions] = useState<any[]>([]);
const [allUserOptions, setAllUserOptions] = useState<any[]>([]);
const [dataRefreshKey, setDataRefreshKey] = useState(0);
// 初始化时获取全量数据用于列表匹配
@@ -85,13 +85,14 @@ export default function DeviceManagementPage() {
activeKey={activeTab}
onChange={setActiveTab}
style={{ marginBottom: 0 }}
>
<Tabs.TabPane key="all" tab="设备管理" />
<Tabs.TabPane key="drone" tab="无人机管理" />
<Tabs.TabPane key="smart" tab="智能装备" />
<Tabs.TabPane key="camera" tab="摄像头管理" />
<Tabs.TabPane key="serialGenerate" tab="机器人生成" />
</Tabs>
items={[
{ key: 'all', label: '设备管理' },
{ key: 'drone', label: '无人机管理' },
{ key: 'smart', label: '智能装备' },
{ key: 'camera', label: '摄像头管理' },
{ key: 'serialGenerate', label: '机器人生成' },
]}
/>
{renderTabContent()}
</div >

View File

@@ -0,0 +1,231 @@
import React, { useEffect, useRef, useState } from 'react';
import flvjs from 'flv.js';
import { Spin, Alert, Button, Space } from 'antd';
import { DownloadOutlined, ReloadOutlined } from '@ant-design/icons';
interface FlvPlayerProps {
url: string;
fileName: string;
}
const isFlv = (name: string) => name.toLowerCase().endsWith('.flv');
const FlvPlayer: React.FC<FlvPlayerProps> = ({ url, fileName }) => {
const videoRef = useRef<HTMLVideoElement>(null);
const flvPlayerRef = useRef<flvjs.Player | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [retryCount, setRetryCount] = useState(0);
const [mediaReady, setMediaReady] = useState(false);
useEffect(() => {
const video = videoRef.current;
if (!video) return;
if (flvPlayerRef.current) {
flvPlayerRef.current.destroy();
flvPlayerRef.current = null;
}
video.removeAttribute('src');
video.load();
setLoading(true);
setError(null);
setMediaReady(false);
let timeoutId: ReturnType<typeof setTimeout> | null = null;
let canPlayThroughHandler: (() => void) | null = null;
let stalledHandler: (() => void) | null = null;
let videoErrorHandler: ((e: Event) => void) | null = null;
const initTimer = setTimeout(() => {
if (isFlv(fileName)) {
if (!flvjs.isSupported()) {
setError('当前浏览器不支持 FLV MSE播放,请下载文件查看');
setLoading(false);
return;
}
try {
const player = flvjs.createPlayer({
type: 'flv',
url,
isLive: false,
cors: true,
enableWorker: true,
enableStashBuffer: true,
stashInitialSize: 4 * 1024 * 1024, // 4MB 适配4K
lazyLoad: true, // ✅关键:大文件点播不能false,否则一次性全部下载立刻关闭MSE
lazyLoadMaxDuration: 120,
retryCount: 3,
retryDelay: 1200,
});
player.attachMediaElement(video);
// ✅ canplaythrough:足够数据可以连贯播放,真正就绪
canPlayThroughHandler = () => {
console.log('✅ canplaythrough 媒体就绪');
if (timeoutId) clearTimeout(timeoutId);
setLoading(false);
setMediaReady(true);
};
stalledHandler = () => {
console.warn('⚠️ video stalled 缓冲区卡顿');
};
videoErrorHandler = (ev) => {
const v = ev.target as HTMLVideoElement;
console.error('❌ video element error', ev, v.error);
if (v.error?.code === 3) {
setError('视频编码不受浏览器MSE支持,请下载文件本地播放');
setLoading(false);
if (timeoutId) clearTimeout(timeoutId);
} else if (v.error?.code === 4) {
setError('媒体源不支持');
setLoading(false);
if (timeoutId) clearTimeout(timeoutId);
}
};
video.addEventListener('canplaythrough', canPlayThroughHandler);
video.addEventListener('stalled', stalledHandler);
video.addEventListener('error', videoErrorHandler);
player.on(flvjs.Events.METADATA_ARRIVED, (meta) => {
console.log('📦 METADATA_ARRIVED', meta);
});
player.on(flvjs.Events.MEDIA_INFO, (info) => {
console.log('📺 MEDIA_INFO', info);
});
player.on(flvjs.Events.LOADING_COMPLETE, () => {
console.log('✅ flv 文件全部加载完成');
});
player.on(flvjs.Events.ERROR, (err) => {
console.error('❌ flv error', err);
let msg = 'FLV播放异常,建议下载文件本地播放';
if (err === flvjs.ErrorTypes.NETWORK_ERROR) {
msg = '网络错误,请检查视频地址';
} else if (err === flvjs.ErrorTypes.MEDIA_ERROR) {
msg = '媒体解码错误,该FLV文件可能不兼容浏览器MSE';
}
setError(msg);
setLoading(false);
if (timeoutId) clearTimeout(timeoutId);
});
timeoutId = setTimeout(() => {
// 15s超时:还没就绪,判定异常
if (!mediaReady) {
setError('加载超时,浏览器MSE解析失败,请下载文件本地播放');
setLoading(false);
}
}, 15000);
player.load();
flvPlayerRef.current = player;
console.log('🚀 flv player created');
} catch (err) {
console.error('❌ create player exception', err);
setError('播放器初始化失败,请重试');
setLoading(false);
}
} else {
// mp4原生
video.src = url;
video.load();
canPlayThroughHandler = () => {
if (timeoutId) clearTimeout(timeoutId);
setLoading(false);
setMediaReady(true);
};
videoErrorHandler = () => {
if (timeoutId) clearTimeout(timeoutId);
setError('视频加载失败,请检查地址或下载');
setLoading(false);
};
video.addEventListener('canplaythrough', canPlayThroughHandler);
video.addEventListener('error', videoErrorHandler);
timeoutId = setTimeout(() => {
if (!mediaReady) console.warn('⏰原生视频加载慢');
}, 15000);
}
}, 200);
return () => {
clearTimeout(initTimer);
if (timeoutId) clearTimeout(timeoutId);
if (flvPlayerRef.current) {
flvPlayerRef.current.destroy();
flvPlayerRef.current = null;
}
if (video) {
if (canPlayThroughHandler) video.removeEventListener('canplaythrough', canPlayThroughHandler);
if (stalledHandler) video.removeEventListener('stalled', stalledHandler);
if (videoErrorHandler) video.removeEventListener('error', videoErrorHandler);
}
};
}, [url, fileName, retryCount, mediaReady]);
const handleRetry = () => setRetryCount(p => p + 1);
const handleManualPlay = async () => {
const v = videoRef.current;
if (!v) return;
try {
await v.play();
} catch (e) {
console.warn('手动播放失败', e);
}
};
if (error) {
return (
<div style={{ textAlign: 'center', padding: 30 }}>
<Alert message="播放失败" description={error} type="error" showIcon style={{ marginBottom: 16 }} />
<Space>
<Button icon={<ReloadOutlined />} onClick={handleRetry}>重试</Button>
<Button type="primary" icon={<DownloadOutlined />} onClick={() => window.open(url, '_blank')}>
下载视频
</Button>
</Space>
</div>
);
}
return (
<div>
{loading && (
<div style={{ textAlign: 'center', padding: 40 }}>
<Spin tip="视频加载中..." />
</div>
)}
{mediaReady && (
<Button onClick={handleManualPlay} style={{ marginBottom: 8 }}>
点击手动播放视频
</Button>
)}
<video
ref={videoRef}
controls
style={{
width: '100%',
background: '#000',
borderRadius: 8,
maxHeight: '70vh',
display: loading ? 'none' : 'block',
}}
playsInline
/>
<div style={{ marginTop: 8, color: '#888', fontSize: 12 }}>
{isFlv(fileName) && <span>💡 FLV 使用 flv.js(MSE)解码,部分特殊编码FLV浏览器无法渲染,建议下载本地播放</span>}
{!isFlv(fileName) && <span>💡 非FLV使用浏览器原生播放</span>}
</div>
</div>
);
};
export default FlvPlayer;

View File

@@ -1,42 +1,46 @@
import React, { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Card, Tag, Button, Space, Typography, Row, Col, Select, Table, Modal, message, Empty, Spin
Card, Button, Space, Typography, Row, Col, Select, Table, Modal, message, Empty, Spin
} from 'antd';
import {
VideoCameraOutlined, DownloadOutlined, PlayCircleOutlined,
FolderOutlined, ReloadOutlined, EyeOutlined, DatabaseOutlined
DownloadOutlined, PlayCircleOutlined, ReloadOutlined
} from '@ant-design/icons';
import axios from 'axios';
import flvjs from 'flv.js';
import FlvPlayer from './FlvPlayer';
const { Title, Text } = Typography;
// ======================
// 视频管理(仅 admin 可见)
// 真实数据对接 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 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 };
return res.data;
};
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 buildDownloadUrl = (p: {
deviceId: string;
year: string;
month: string;
day: string;
fileName: string;
download?: boolean;
}) => {
const params = new URLSearchParams({
deviceId: p.deviceId,
year: p.year,
month: p.month,
day: p.day,
fileName: p.fileName,
});
if (p.download) params.append('download', 'true');
return `${NODEVIDEO_BASE}/video/download?${params.toString()}`;
};
const isFlv = (name: string) => name.toLowerCase().endsWith('.flv');
const isMp4 = (name: string) => name.toLowerCase().endsWith('.mp4');
const VideoManagement = () => {
const navigate = useNavigate();
const [channels, setChannels] = useState<string[]>([]);
const [channel, setChannel] = useState<string | undefined>();
const [years, setYears] = useState<string[]>([]);
@@ -48,10 +52,12 @@ const VideoManagement = () => {
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 [playerError, setPlayerError] = useState<string | null>(null);
const videoRef = useRef<HTMLVideoElement>(null);
const flvPlayerRef = useRef<any>(null);
// 1. 加载通道列表(deviceId)
// 加载通道列表
const loadChannels = async () => {
setLoading(true);
try {
@@ -64,7 +70,7 @@ const VideoManagement = () => {
}
} catch (e) {
setChannels([]);
message.error('无法连接 nodevideo 服务(9528)');
message.error('无法连接视频服务');
} finally {
setLoading(false);
}
@@ -72,7 +78,7 @@ const VideoManagement = () => {
useEffect(() => { loadChannels(); }, []);
// 2. 选通道 → 加载年份
// 通道变更
const onChannelChange = async (val: string) => {
setChannel(val);
setYear(undefined); setYears([]);
@@ -88,7 +94,7 @@ const VideoManagement = () => {
finally { setLoading(false); }
};
// 3. 选年份 → 加载月份
// 年份变更
const onYearChange = async (val: string) => {
setYear(val);
setMonth(undefined); setMonths([]);
@@ -103,7 +109,7 @@ const VideoManagement = () => {
finally { setLoading(false); }
};
// 4. 选月份 → 加载日期
// 月份变更
const onMonthChange = async (val: string) => {
setMonth(val);
setDay(undefined); setDays([]);
@@ -117,7 +123,7 @@ const VideoManagement = () => {
finally { setLoading(false); }
};
// 5. 选日期 → 加载当天视频文件
// 日期变更
const onDayChange = async (val: string) => {
setDay(val);
setFiles([]);
@@ -131,26 +137,13 @@ const VideoManagement = () => {
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;
if (!channel || !year || !month || !day) {
message.warning('请先选择完整的时间路径');
return;
}
setPreview({
url: buildDownloadUrl({ deviceId: channel, year, month, day, fileName: file.fileName }),
fileName: file.fileName,
@@ -163,12 +156,21 @@ const VideoManagement = () => {
{
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>
<a
href={buildDownloadUrl({
deviceId: channel!,
year: year!,
month: month!,
day: day!,
fileName: rec.fileName,
download: true
})}
target="_blank"
rel="noreferrer"
>
<Button size="small" type="link" icon={<DownloadOutlined />}>下载</Button>
</a>
),
},
];
@@ -178,11 +180,11 @@ const VideoManagement = () => {
<div style={{ marginBottom: 8 }}>
<Title level={4} style={{ margin: 0, fontSize: 'clamp(14px, 1.1vw, 18px)' }}>视频管理</Title>
<Text type="secondary" style={{ fontSize: 12 }}>
录像通道与回放管理(对接 nodevideo :9528) · 共 {channels.length} 个通道
录像通道与回放管理 · 共 {channels.length} 个通道
</Text>
</div>
<Card bordered={false} style={{ borderRadius: 10, marginBottom: 8, boxShadow: '0 2px 8px rgba(0,0,0,0.04)' }} bodyStyle={{ padding: 12 }}>
<Card bordered={false} style={{ borderRadius: 10, marginBottom: 8 }} bodyStyle={{ padding: 12 }}>
<Row gutter={[8, 8]} align="middle">
<Col xs={24} sm={10} md={7}>
<Text style={{ fontSize: 12, color: '#666' }}>通道</Text>
@@ -196,29 +198,33 @@ const VideoManagement = () => {
</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 }))} />
<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 }))} />
<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>
<Select
size="small" style={{ width: '100%' }} placeholder="日"
disabled={!month} value={day} onChange={onDayChange}
options={days.map((d) => ({ value: d, label: d }))}
/>
</Col>
</Row>
</Card>
<Card bordered={false} style={{ borderRadius: 10, boxShadow: '0 2px 8px rgba(0,0,0,0.04)' }} bodyStyle={{ padding: 4 }}>
<Card bordered={false} style={{ borderRadius: 10 }} bodyStyle={{ padding: 4 }}>
{loading ? (
<div style={{ textAlign: 'center', padding: 40 }}><Spin /></div>
) : !channel ? (
@@ -236,17 +242,15 @@ const VideoManagement = () => {
</Card>
<Modal
open={!!preview} title={`预览 ${preview?.fileName || ''}`} footer={null}
onCancel={() => setPreview(null)} width={880} destroyOnClose
open={!!preview}
title={`预览: ${preview?.fileName || ''}`}
footer={null}
onCancel={() => setPreview(null)}
width={880}
destroyOnClose
key={preview?.fileName || 'preview'}
>
{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>
{preview && <FlvPlayer url={preview.url} fileName={preview.fileName} />}
</Modal>
</div>
);