-
{sortBy === 'today' ? (isRangeMode ? '区间' : '当日') : '累计'}总里程
+
{sortBy === 'total' ? '累计' : (isRangeMode ? '区间' : '当日')}总里程
- {pageLoading ?
: <>{Math.round(sortBy === 'today' ? stats.totalToday : stats.totalAll).toLocaleString()}
km>}
+ {pageLoading ?
: <>{Math.round(sortBy === 'total' ? stats.totalAll : stats.totalToday).toLocaleString()}
km>}
{rangeLabel}
平均单车
-
{pageLoading ?
: (stats.vehicleCount > 0 ? (sortBy === 'today' ? stats.totalToday : stats.totalAll) / stats.vehicleCount : 0).toFixed(0)}
+
{pageLoading ?
: (stats.vehicleCount > 0 ? (sortBy === 'total' ? stats.totalAll : stats.totalToday) / stats.vehicleCount : 0).toFixed(0)}
km/台
diff --git a/src/modules/mileage/api.ts b/src/modules/mileage/api.ts
index 0ccbb81..6170e04 100644
--- a/src/modules/mileage/api.ts
+++ b/src/modules/mileage/api.ts
@@ -12,8 +12,8 @@ import { fetchJson } from '../../auth/api-client';
const BASE = '/api/mileage';
export async function fetchMonitoring(params?: {
- sortBy?: string;
- sortOrder?: string;
+ sortBy?: 'today' | 'total' | 'statisticTime';
+ sortOrder?: 'asc' | 'desc';
limit?: number;
page?: number;
search?: string;
diff --git a/src/modules/mileage/xlsx-export.ts b/src/modules/mileage/xlsx-export.ts
index 7e026eb..c77ffd7 100644
--- a/src/modules/mileage/xlsx-export.ts
+++ b/src/modules/mileage/xlsx-export.ts
@@ -5,7 +5,7 @@ interface ExportContext {
date?: string;
startDate?: string;
endDate?: string;
- sortBy: 'today' | 'total';
+ sortBy: 'today' | 'total' | 'statisticTime';
}
const BASE_HEADERS = [
@@ -172,6 +172,11 @@ export function exportMileageXlsx(vehicles: MonitoringVehicle[], ctx: ExportCont
const dateTag = start && end
? `${start.replace(/-/g, '')}-${end.replace(/-/g, '')}`
: `${y}${m}${d}`;
- const filename = `里程看板_${dateTag}_${hh}${mm}_${ctx.sortBy === 'today' ? (isRange ? '区间' : '今日') : '累计'}.xlsx`;
+ const sortLabel = ctx.sortBy === 'total'
+ ? '累计'
+ : ctx.sortBy === 'statisticTime'
+ ? '统计时间'
+ : isRange ? '区间' : '今日';
+ const filename = `里程看板_${dateTag}_${hh}${mm}_${sortLabel}.xlsx`;
XLSX.writeFile(wb, filename);
}
diff --git a/src/server/routes/mileage/monitoring.ts b/src/server/routes/mileage/monitoring.ts
index f2c30f1..7165e7c 100644
--- a/src/server/routes/mileage/monitoring.ts
+++ b/src/server/routes/mileage/monitoring.ts
@@ -104,8 +104,11 @@ function normalizeRange(startQuery: string, endQuery: string): { start: string;
}
app.get('/', async (c) => {
- const sortBy = c.req.query('sortBy') || 'today';
- const sortOrder = c.req.query('sortOrder') || 'desc';
+ const requestedSortBy = c.req.query('sortBy');
+ const sortBy = requestedSortBy === 'total' || requestedSortBy === 'statisticTime'
+ ? requestedSortBy
+ : 'today';
+ const sortOrder = c.req.query('sortOrder') === 'asc' ? 'asc' : 'desc';
const limit = Number(c.req.query('limit')) || 50;
const page = Number(c.req.query('page')) || 1;
const date = c.req.query('date') || '';
@@ -189,9 +192,26 @@ app.get('/', async (c) => {
};
const sorted = [...filtered].sort((a, b) => {
+ if (sortBy === 'statisticTime') {
+ const rawTimeA = a.dataTime || a.updatedAt || a.calculatedAt;
+ const rawTimeB = b.dataTime || b.updatedAt || b.calculatedAt;
+ const parsedTimeA = rawTimeA ? Date.parse(rawTimeA) : Number.NaN;
+ const parsedTimeB = rawTimeB ? Date.parse(rawTimeB) : Number.NaN;
+ const timeA = Number.isFinite(parsedTimeA) ? parsedTimeA : null;
+ const timeB = Number.isFinite(parsedTimeB) ? parsedTimeB : null;
+
+ // Missing/invalid statistic times always stay at the end for both directions.
+ if (timeA == null && timeB == null) return a.plate.localeCompare(b.plate, 'zh-CN');
+ if (timeA == null) return 1;
+ if (timeB == null) return -1;
+ const timeDiff = sortOrder === 'desc' ? timeB - timeA : timeA - timeB;
+ return timeDiff || a.plate.localeCompare(b.plate, 'zh-CN');
+ }
+
const valA = sortBy === 'today' ? a.dailyKm : (a.totalKm || 0);
const valB = sortBy === 'today' ? b.dailyKm : (b.totalKm || 0);
- return sortOrder === 'desc' ? valB - valA : valA - valB;
+ const mileageDiff = sortOrder === 'desc' ? valB - valA : valA - valB;
+ return mileageDiff || a.plate.localeCompare(b.plate, 'zh-CN');
});
const offset = (page - 1) * limit;