diff --git a/src/modules/mileage/MonitoringView.tsx b/src/modules/mileage/MonitoringView.tsx
index 0104bfe..3838225 100644
--- a/src/modules/mileage/MonitoringView.tsx
+++ b/src/modules/mileage/MonitoringView.tsx
@@ -1,11 +1,11 @@
-import { useState, useEffect, useMemo } from 'react';
+import { useState, useEffect, useCallback, useMemo } from 'react';
import { motion, AnimatePresence } from 'motion/react';
import {
Truck, Search, Filter, ChevronDown,
Maximize2, Minimize2, RotateCcw,
- ArrowUp, ArrowDown,
+ ArrowUp, ArrowDown, ChevronLeft, ChevronRight,
} from 'lucide-react';
-import type { MonitoringVehicle } from './types';
+import type { MonitoringVehicle, MonitoringStats, MonitoringFilters } from './types';
import { fetchMonitoring } from './api';
const SearchableSelect = ({
@@ -63,7 +63,7 @@ const SearchableSelect = ({
>
无限制
- {filtered.map(opt => (
+ {filtered.map((opt: string) => (
([]);
- const [totalCount, setTotalCount] = useState(0);
- const [departments, setDepartments] = useState
([]);
- const [plateNumbers, setPlateNumbers] = useState([]);
- const [projects, setProjects] = useState([]);
+ const [vehicles, setVehicles] = useState([]);
+ const [stats, setStats] = useState({ totalToday: 0, totalAll: 0, onlineCount: 0, vehicleCount: 0 });
+ const [filterOptions, setFilterOptions] = useState({ departments: [], customers: [], plates: [] });
+ const [total, setTotal] = useState(0);
+ const [page, setPage] = useState(1);
+ const [totalPages, setTotalPages] = useState(1);
+ const [updatedAt, setUpdatedAt] = useState('');
+ const PAGE_SIZE = 50;
- // 加载筛选选项(仅首次,用轻量请求)
- useEffect(() => {
- fetchMonitoring({ limit: 2000 }).then(d => {
- const depts = Array.from(new Set(d.vehicles.map(v => v.department).filter(Boolean))) as string[];
- const plates = Array.from(new Set(d.vehicles.map(v => v.plate).filter(Boolean)));
- const custs = Array.from(new Set(d.vehicles.map(v => v.customer).filter(Boolean))) as string[];
- setDepartments(depts);
- setPlateNumbers(plates);
- setProjects(custs);
+ const departments = filterOptions.departments;
+ const plateNumbers = filterOptions.plates;
+ const projects = filterOptions.customers;
+
+ const loadData = useCallback((p = page) => {
+ fetchMonitoring({
+ sortBy,
+ sortOrder,
+ limit: PAGE_SIZE,
+ page: p,
+ search: searchTerm || undefined,
+ dept: filterDept !== 'All' ? filterDept : undefined,
+ customer: filterProject !== 'All' ? filterProject : undefined,
+ }).then(d => {
+ setVehicles(d.vehicles);
+ setStats(d.stats);
+ setFilterOptions(d.filters);
+ setTotal(d.total);
+ setPage(d.page);
+ setTotalPages(d.totalPages);
+ setUpdatedAt(d.updatedAt);
}).catch(() => {});
- }, []);
+ }, [sortBy, sortOrder, searchTerm, filterDept, filterProject, page]);
- // 加载数据(带服务端筛选/排序/分页)
+ // 筛选/排序变化时重置到第1页
useEffect(() => {
- const load = () => {
- fetchMonitoring({
- sortBy,
- sortOrder,
- limit: 100,
- search: searchTerm || undefined,
- dept: filterDept !== 'All' ? filterDept : undefined,
- customer: filterProject !== 'All' ? filterProject : undefined,
- }).then(d => {
- setFilteredVehicles(d.vehicles);
- setTotalCount(d.total);
- }).catch(() => {});
- };
- load();
- const interval = setInterval(load, 60000);
- return () => clearInterval(interval);
+ setPage(1);
+ loadData(1);
}, [sortBy, sortOrder, searchTerm, filterDept, filterProject]);
+ // 翻页时加载
+ useEffect(() => {
+ loadData(page);
+ }, [page]);
+
+ const filteredVehicles = vehicles;
+
const toggleFullscreen = () => {
if (!isFullscreen) {
const elem = document.documentElement;
@@ -158,16 +166,6 @@ export default function MonitoringView() {
setIsFullscreen(!isFullscreen);
};
- const stats = useMemo(() => {
- const totalToday = filteredVehicles.reduce((sum, v) => sum + (v.dailyKm || 0), 0);
- const totalAll = filteredVehicles.reduce((sum, v) => sum + (v.totalKm || 0), 0);
-
- const activeTotal = sortBy === 'today' ? totalToday : totalAll;
- const activeAvg = filteredVehicles.length > 0 ? activeTotal / filteredVehicles.length : 0;
-
- return { totalToday, totalAll, activeTotal, activeAvg, count: totalCount };
- }, [filteredVehicles, sortBy, totalCount]);
-
return (
<>
{/* Fullscreen Landscape View Overlay */}
@@ -227,7 +225,7 @@ export default function MonitoringView() {
监控台数
- {stats.count}
+ {stats.vehicleCount}
台
@@ -236,7 +234,7 @@ export default function MonitoringView() {
平均单车 ({sortBy === 'today' ? '今日' : '累计'})
- {stats.activeAvg.toFixed(0)}
+ {(stats.vehicleCount > 0 ? (sortBy === 'today' ? stats.totalToday : stats.totalAll) / stats.vehicleCount : 0).toFixed(0)}
KM
@@ -687,19 +685,19 @@ export default function MonitoringView() {
{sortBy === 'today' ? '今日' : '累计'}总里程 (KM)
- {stats.activeTotal.toLocaleString()}
+ {(sortBy === 'today' ? stats.totalToday : stats.totalAll).toLocaleString()}
{sortBy === 'today' && {'\u2191'}12%}
平均单车
-
{stats.activeAvg.toFixed(0)}
+
{(stats.vehicleCount > 0 ? (sortBy === 'today' ? stats.totalToday : stats.totalAll) / stats.vehicleCount : 0).toFixed(0)}
KM/台
监控台数
-
{stats.count}
+
{stats.vehicleCount}
台
@@ -769,6 +767,29 @@ export default function MonitoringView() {
未找到匹配数据
)}
+
+ {/* 分页控件 */}
+ {totalPages > 1 && (
+
+
+
+ {page} / {totalPages}
+
+
+
+ )}
>
);
diff --git a/src/modules/mileage/api.ts b/src/modules/mileage/api.ts
index 934121d..558331b 100644
--- a/src/modules/mileage/api.ts
+++ b/src/modules/mileage/api.ts
@@ -12,7 +12,7 @@ export async function fetchMonitoring(params?: {
sortBy?: string;
sortOrder?: string;
limit?: number;
- offset?: number;
+ page?: number;
search?: string;
dept?: string;
customer?: string;
@@ -21,7 +21,7 @@ export async function fetchMonitoring(params?: {
if (params?.sortBy) query.set('sortBy', params.sortBy);
if (params?.sortOrder) query.set('sortOrder', params.sortOrder);
if (params?.limit) query.set('limit', String(params.limit));
- if (params?.offset) query.set('offset', String(params.offset));
+ if (params?.page) query.set('page', String(params.page));
if (params?.search) query.set('search', params.search);
if (params?.dept) query.set('dept', params.dept);
if (params?.customer) query.set('customer', params.customer);
diff --git a/src/modules/mileage/types.ts b/src/modules/mileage/types.ts
index ed6887c..00a7ff1 100644
--- a/src/modules/mileage/types.ts
+++ b/src/modules/mileage/types.ts
@@ -11,9 +11,26 @@ export interface MonitoringVehicle {
manager: string | null;
}
+export interface MonitoringStats {
+ totalToday: number;
+ totalAll: number;
+ onlineCount: number;
+ vehicleCount: number;
+}
+
+export interface MonitoringFilters {
+ departments: string[];
+ customers: string[];
+ plates: string[];
+}
+
export interface MonitoringData {
vehicles: MonitoringVehicle[];
+ stats: MonitoringStats;
+ filters: MonitoringFilters;
total: number;
+ page: number;
+ totalPages: number;
updatedAt: string;
}
diff --git a/src/server/routes/mileage.ts b/src/server/routes/mileage.ts
index 3fbeee1..6252869 100644
--- a/src/server/routes/mileage.ts
+++ b/src/server/routes/mileage.ts
@@ -18,37 +18,36 @@ LEFT JOIN tab_user u ON u.id = c.bd AND u.is_deleted = 0
LEFT JOIN tab_department dep ON dep.id = u.dep_id AND dep.is_deleted = 0
WHERE truck.is_deleted = 0 AND truck.is_operation = 1`;
-// 车辆关联信息缓存(5分钟过期)
-let vehicleInfoCache: { data: Map; expiry: number } | null = null;
-const CACHE_TTL = 5 * 60 * 1000;
-
-async function getVehicleInfoMap(): Promise