From 06a2edc47015ba5f3f5a6180299ba781404f4562 Mon Sep 17 00:00:00 2001 From: kkfluous Date: Thu, 2 Apr 2026 10:01:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A7=9F=E8=B5=81=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=B8=8E=E9=83=A8=E9=97=A8=E5=88=86=E5=88=97=E7=AD=9B=E9=80=89?= =?UTF-8?q?=EF=BC=8C=E6=9C=AA=E5=90=8C=E6=AD=A5=E8=BD=A6=E8=BE=86=E6=98=BE?= =?UTF-8?q?=E7=A4=BA-=EF=BC=8C=E5=8D=A1=E7=89=87=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=BB=8A/=E6=80=BB=E6=A0=87=E7=AD=BE=EF=BC=8C=E5=85=A8?= =?UTF-8?q?=E5=B1=8F=E7=9B=91=E6=8E=A7=E5=8E=8B=E7=BC=A9=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- src/modules/mileage/MonitoringView.tsx | 197 +++++++++++++------------ src/modules/mileage/api.ts | 2 + src/modules/mileage/types.ts | 1 + src/server/routes/mileage.ts | 10 +- 4 files changed, 110 insertions(+), 100 deletions(-) diff --git a/src/modules/mileage/MonitoringView.tsx b/src/modules/mileage/MonitoringView.tsx index 0638ab8..e5bb6a2 100644 --- a/src/modules/mileage/MonitoringView.tsx +++ b/src/modules/mileage/MonitoringView.tsx @@ -101,6 +101,7 @@ export default function MonitoringView() { const [filterCustomer, setFilterCustomer] = useState('All'); const [filterProject, setFilterProject] = useState('All'); const [filterEntity, setFilterEntity] = useState('All'); + const [filterRentStatus, setFilterRentStatus] = useState('All'); const [filterRegionCode, setFilterRegionCode] = useState('All'); const [filterMileageRange, setFilterMileageRange] = useState({ min: '', max: '' }); const [appliedMileageRange, setAppliedMileageRange] = useState({ min: '', max: '' }); @@ -112,7 +113,7 @@ export default function MonitoringView() { const [vehicles, setVehicles] = useState([]); const [stats, setStats] = useState({ totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 }); - const [filterOptions, setFilterOptions] = useState({ departments: [], customers: [], plates: [], projects: [], entities: [] }); + const [filterOptions, setFilterOptions] = useState({ departments: [], customers: [], plates: [], projects: [], entities: [], rentStatuses: [] }); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); @@ -135,6 +136,7 @@ export default function MonitoringView() { customer: filterCustomer !== 'All' ? filterCustomer : undefined, project: filterProject !== 'All' ? filterProject : undefined, entity: filterEntity !== 'All' ? filterEntity : undefined, + rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined, plate: filterPlate !== 'All' ? filterPlate : undefined, mileageMin: appliedMileageRange.min || undefined, mileageMax: appliedMileageRange.max || undefined, @@ -147,7 +149,7 @@ export default function MonitoringView() { setPage(1); setHasMore(d.page < d.totalPages); }).catch(() => {}); - }, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterPlate, appliedMileageRange, filterDate]); + }, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlate, appliedMileageRange, filterDate]); // 加载更多 const loadMore = useCallback(() => { @@ -164,6 +166,7 @@ export default function MonitoringView() { customer: filterCustomer !== 'All' ? filterCustomer : undefined, project: filterProject !== 'All' ? filterProject : undefined, entity: filterEntity !== 'All' ? filterEntity : undefined, + rentStatus: filterRentStatus !== 'All' ? filterRentStatus : undefined, plate: filterPlate !== 'All' ? filterPlate : undefined, mileageMin: appliedMileageRange.min || undefined, mileageMax: appliedMileageRange.max || undefined, @@ -173,7 +176,7 @@ export default function MonitoringView() { setPage(nextPage); setHasMore(nextPage < d.totalPages); }).catch(() => {}).finally(() => setLoadingMore(false)); - }, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterPlate, appliedMileageRange, filterDate, page, loadingMore, hasMore]); + }, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlate, appliedMileageRange, filterDate, page, loadingMore, hasMore]); // 筛选/排序变化时重新加载 useEffect(() => { @@ -247,76 +250,66 @@ export default function MonitoringView() { exit={{ opacity: 0 }} className="fixed inset-0 z-[100] bg-slate-950 flex flex-col overflow-hidden" > - {/* Top bar: title + KPI row + close */} -
-
-
-
-

全屏监控

-
+ {/* Top bar: compact inline KPI */} +
+
- - +
+

全屏监控

+
+
+ 今日 {Math.round(stats.totalToday).toLocaleString()} km + | + 累计 {Math.round(stats.totalAll).toLocaleString()} km + | + 车辆 {stats.vehicleCount} + | + {(stats.vehicleCount > 0 ? (sortBy === 'today' ? stats.totalToday : stats.totalAll) / stats.vehicleCount : 0).toFixed(0)} km
- {/* KPI — single row */} -
-
-
今日总里程
-
{Math.round(stats.totalToday).toLocaleString()} km
-
-
-
累计总里程
-
{Math.round(stats.totalAll).toLocaleString()} km
-
-
-
监控台数
-
{stats.vehicleCount}
-
-
-
平均单车
-
{(stats.vehicleCount > 0 ? (sortBy === 'today' ? stats.totalToday : stats.totalAll) / stats.vehicleCount : 0).toFixed(0)} km
-
+
+ +
{/* Table Area */}
-
- 车辆实时明细数据 +
-
+
在线
-
+
离线
-
- 未对接车机 +
+ 未对接
- 最后更新: {new Date().toLocaleTimeString()} + {new Date().toLocaleTimeString()}
- - + + - - - + - - + {filteredVehicles.map((v) => ( - - - + - - - + + + + - - @@ -514,7 +499,7 @@ export default function MonitoringView() { @@ -569,6 +554,21 @@ export default function MonitoringView() { + {/* Rent Status */} +
+ + +
+ + +
{/* Entity */}
@@ -657,6 +657,7 @@ export default function MonitoringView() { {/* Active Filter Tags */} {(() => { const tags: { label: string; onClear: () => void }[] = []; + if (filterRentStatus !== 'All') tags.push({ label: `状态: ${filterRentStatus}`, onClear: () => setFilterRentStatus('All') }); if (filterDept !== 'All') tags.push({ label: `部门: ${filterDept}`, onClear: () => setFilterDept('All') }); if (filterCustomer !== 'All') tags.push({ label: `客户: ${filterCustomer}`, onClear: () => setFilterCustomer('All') }); if (filterProject !== 'All') tags.push({ label: `项目: ${filterProject}`, onClear: () => setFilterProject('All') }); @@ -750,7 +751,7 @@ export default function MonitoringView() {
- {v.department ? v.department.replace('业务', '') : v.rentStatus || ''} + {v.rentStatus || ''}{v.department ? ` · ${v.department.replace('业务', '')}` : ''} {v.customer || '-'}
@@ -760,13 +761,15 @@ export default function MonitoringView() { {!v.isDataSynced && (
)} +
- {v.dailyKm?.toLocaleString()} km + {v.isDataSynced ? <>{v.dailyKm?.toLocaleString()} km : '-'}
+ - {v.totalKm?.toLocaleString()} km + {v.isDataSynced && v.totalKm != null ? `${v.totalKm.toLocaleString()} km` : '-'} {!v.isDataSynced && ( 未同步 diff --git a/src/modules/mileage/api.ts b/src/modules/mileage/api.ts index d189201..634b4e5 100644 --- a/src/modules/mileage/api.ts +++ b/src/modules/mileage/api.ts @@ -18,6 +18,7 @@ export async function fetchMonitoring(params?: { customer?: string; project?: string; entity?: string; + rentStatus?: string; plate?: string; mileageMin?: string; mileageMax?: string; @@ -33,6 +34,7 @@ export async function fetchMonitoring(params?: { if (params?.customer) query.set('customer', params.customer); if (params?.project) query.set('project', params.project); if (params?.entity) query.set('entity', params.entity); + if (params?.rentStatus) query.set('rentStatus', params.rentStatus); if (params?.plate) query.set('plate', params.plate); if (params?.mileageMin) query.set('mileageMin', params.mileageMin); if (params?.mileageMax) query.set('mileageMax', params.mileageMax); diff --git a/src/modules/mileage/types.ts b/src/modules/mileage/types.ts index b7f44f5..7daea1e 100644 --- a/src/modules/mileage/types.ts +++ b/src/modules/mileage/types.ts @@ -27,6 +27,7 @@ export interface MonitoringFilters { plates: string[]; projects: string[]; entities: string[]; + rentStatuses: string[]; } export interface MonitoringData { diff --git a/src/server/routes/mileage.ts b/src/server/routes/mileage.ts index 0a8472d..3bec11e 100644 --- a/src/server/routes/mileage.ts +++ b/src/server/routes/mileage.ts @@ -45,7 +45,7 @@ interface CachedVehicle { interface MonitoringCache { vehicles: CachedVehicle[]; stats: { totalToday: number; totalAll: number; vehicleCount: number }; - filters: { departments: string[]; customers: string[]; plates: string[]; projects: string[]; entities: string[] }; + filters: { departments: string[]; customers: string[]; plates: string[]; projects: string[]; entities: string[]; rentStatuses: string[] }; updatedAt: string; } @@ -142,11 +142,12 @@ async function refreshMonitoringCache() { const plates = vehicles.map(v => v.plate); const projects = Array.from(new Set(vehicles.map(v => v.project).filter(Boolean))) as string[]; const entities = Array.from(new Set(vehicles.map(v => v.entity).filter(Boolean))) as string[]; + const rentStatuses = Array.from(new Set(vehicles.map(v => v.rentStatus).filter(Boolean))) as string[]; monitoringCache = { vehicles, stats: { totalToday, totalAll, vehicleCount: vehicles.length }, - filters: { departments, customers, plates, projects, entities }, + filters: { departments, customers, plates, projects, entities, rentStatuses }, updatedAt: new Date().toISOString(), }; @@ -199,7 +200,7 @@ async function queryDateMileage(dateStr: string): Promise<{ vehicles: CachedVehi // GET /monitoring — 从缓存取数据(或指定日期实时查询),支持筛选/排序/分页 app.get('/monitoring', async (c) => { - const emptyResponse = { vehicles: [], stats: { totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 }, filters: { departments: [], customers: [], plates: [], projects: [], entities: [] }, total: 0, page: 1, totalPages: 1, updatedAt: new Date().toISOString() }; + const emptyResponse = { vehicles: [], stats: { totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 }, filters: { departments: [], customers: [], plates: [], projects: [], entities: [], rentStatuses: [] }, total: 0, page: 1, totalPages: 1, updatedAt: new Date().toISOString() }; const sortBy = c.req.query('sortBy') || 'today'; const sortOrder = c.req.query('sortOrder') || 'desc'; @@ -213,6 +214,7 @@ app.get('/monitoring', async (c) => { const mileageMin = c.req.query('mileageMin') || ''; const mileageMax = c.req.query('mileageMax') || ''; const plate = c.req.query('plate') || ''; + const rentStatus = c.req.query('rentStatus') || ''; const date = c.req.query('date') || ''; let allVehicles: CachedVehicle[]; @@ -233,6 +235,7 @@ app.get('/monitoring', async (c) => { plates: allVehicles.map(v => v.plate), projects: Array.from(new Set(allVehicles.map(v => v.project).filter(Boolean))) as string[], entities: Array.from(new Set(allVehicles.map(v => v.entity).filter(Boolean))) as string[], + rentStatuses: Array.from(new Set(allVehicles.map(v => v.rentStatus).filter(Boolean))) as string[], }; } catch (e) { console.error('monitoring date query error:', e); @@ -259,6 +262,7 @@ app.get('/monitoring', async (c) => { if (customer) vehicles = vehicles.filter(v => v.customer === customer); if (project) vehicles = vehicles.filter(v => v.project === project); if (entity) vehicles = vehicles.filter(v => v.entity === entity); + if (rentStatus) vehicles = vehicles.filter(v => v.rentStatus === rentStatus); if (plate) vehicles = vehicles.filter(v => v.plate === plate); if (mileageMin) vehicles = vehicles.filter(v => v.dailyKm >= Number(mileageMin)); if (mileageMax) vehicles = vehicles.filter(v => v.dailyKm <= Number(mileageMax));
-
+
状态 +
车牌号
在线状态 -
+
+
客户
-
- 业务部门 +
+
+ 租赁状态 +
+
+
+ 部门 +
{ if (sortBy === 'today') { setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc'); @@ -371,7 +376,7 @@ export default function MonitoringView() { { if (sortBy === 'total') { setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc'); @@ -388,46 +393,26 @@ export default function MonitoringView() { )} 状态
{v.plate} -
-
- - {v.isOnline ? '在线' : '离线'} - -
+
+
{v.customer}{v.department || v.rentStatus || ''} -
-
- {!v.isDataSynced && ( -
- )} - - {v.dailyKm?.toLocaleString()} km - -
- {!v.isDataSynced && 未对接} -
+
{v.plate}{v.customer || '-'}{v.rentStatus || '-'}{v.department || '-'} + + {v.isDataSynced ? <>{v.dailyKm?.toLocaleString()} km : '-'} + -
- - {v.totalKm?.toLocaleString()} km - -
-
- - {v.isOnline ? '运行中' : '静止/离线'} + + + {v.isDataSynced && v.totalKm != null ? <>{v.totalKm.toLocaleString()} km : '-'}