+const MileageTable = memo(function MileageTable({ rows, dates, scrollRef }: { rows: VehicleMileageMatrix[]; dates: string[]; scrollRef: RefObject
}) {
+ const { columns, tableWidth } = useMemo(() => {
+ let maxDailyMileage = 1;
+ for (const row of rows) {
+ for (const mileage of row.days.values()) maxDailyMileage = Math.max(maxDailyMileage, mileage);
+ }
+ return {
+ columns: [
+ { title: '车牌', dataIndex: 'plate', className: 'is-plate', width: 120, render: (_value: string, row: VehicleMileageMatrix) => {row.plate || '未绑定'} },
+ ...dates.map((date) => ({
+ title: dateLabel(date), dataIndex: date, className: 'is-number is-date', width: 96,
+ onHeaderCell: () => ({ title: date }),
+ onCell: (row?: VehicleMileageMatrix) => {
+ const mileage = row?.days.get(date);
+ const intensity = mileage && mileage > 0 ? .035 + mileage / maxDailyMileage * .13 : 0;
+ return {
+ className: `is-number is-date${mileage != null ? ' is-daily' : ' is-empty'}`,
+ title: mileage != null ? `来源:${row?.sources.get(date) || '—'}` : undefined,
+ style: intensity ? { backgroundColor: `rgba(37, 99, 235, ${intensity.toFixed(3)})` } : undefined
+ };
+ },
+ render: (_value: unknown, row: VehicleMileageMatrix) => {
+ const mileage = row.days.get(date);
+ return mileage != null ? `${formatKm(mileage)} km` : '—';
+ }
+ })),
+ { title: '区间总里程', dataIndex: 'totalMileageKm', className: 'is-number is-total', width: 128, onCell: () => ({ className: 'is-number is-period is-total' }), render: (value: number) => `${formatKm(value)} km` }
+ ],
+ tableWidth: 120 + dates.length * 96 + 128
+ };
+ }, [dates, rows]);
+ return ;
-}
+});
export default function StatisticsPage() {
const [searchParams, setSearchParams] = useSearchParams();
diff --git a/vehicle-data-platform/apps/web/src/v2/styles/workspace.css b/vehicle-data-platform/apps/web/src/v2/styles/workspace.css
index e299bf1f..52746e3c 100644
--- a/vehicle-data-platform/apps/web/src/v2/styles/workspace.css
+++ b/vehicle-data-platform/apps/web/src/v2/styles/workspace.css
@@ -10208,3 +10208,453 @@
min-height: 94px;
}
}
+
+/*
+ * Semi UI mileage workspace convergence.
+ * Keep a single query/summary surface, one table scroll owner and the same
+ * restrained control, radius and evidence language used by the other
+ * operations workspaces.
+ */
+.v2-mileage-page {
+ gap: 10px;
+ padding: 14px 18px 16px;
+}
+
+.v2-mileage-query-panel.v2-workspace-filter-panel.semi-card {
+ overflow: visible;
+ border-color: #d8e3ef;
+ border-radius: 12px;
+ box-shadow: 0 8px 26px rgba(31, 53, 80, .055);
+}
+
+.v2-mileage-query-panel .v2-workspace-filter-heading {
+ min-height: 44px;
+ border-bottom-color: #e3eaf2;
+ background: linear-gradient(100deg, #f7faff 0%, #f9fbfd 58%, #fff 100%);
+ padding-inline: 13px;
+}
+
+.v2-mileage-query-panel .v2-mileage-filter {
+ gap: 10px;
+ background: #fff;
+ padding: 11px 12px 12px;
+}
+
+.v2-mileage-filter > label {
+ gap: 5px;
+ color: #607187;
+ font-size: 11px;
+ font-weight: 650;
+}
+
+.v2-mileage-multiselect,
+.v2-mileage-filter > label > .semi-input-wrapper {
+ min-height: 38px;
+ border-color: #d8e2ed;
+ border-radius: 8px;
+ background: #fbfcfe;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .72);
+}
+
+.v2-mileage-multiselect:hover,
+.v2-mileage-filter > label > .semi-input-wrapper:hover {
+ border-color: #b9c9dc;
+ background: #fff;
+}
+
+.v2-mileage-multiselect:focus-within,
+.v2-mileage-filter > label > .semi-input-wrapper-focus {
+ border-color: #75a5ed;
+ background: #fff;
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, .09);
+}
+
+.v2-mileage-selection > .semi-input-wrapper .semi-input,
+.v2-mileage-filter > label > .semi-input-wrapper .semi-input {
+ color: #2d4058;
+ font-size: 12px;
+}
+
+.v2-mileage-filter > .v2-primary-button.semi-button {
+ min-height: 38px;
+ border-radius: 8px;
+ padding-inline: 19px;
+ font-size: 12px;
+ font-weight: 680;
+ box-shadow: 0 4px 12px rgba(18, 104, 223, .16);
+}
+
+.v2-mileage-source-trigger.semi-button {
+ min-height: 38px;
+ border-color: #d8e3ef;
+ border-radius: 8px;
+ background: #f8fbff;
+ color: #49647f;
+ font-size: 11px;
+}
+
+.v2-mileage-source-trigger.semi-button:hover,
+.v2-mileage-source-trigger.semi-button[aria-expanded="true"] {
+ border-color: #aec6e4;
+ background: #f2f7ff;
+ color: #245da8;
+}
+
+.v2-mileage-source-trigger em {
+ color: #3d6594;
+ font-size: 9px;
+}
+
+.v2-mileage-ranges {
+ min-height: 38px;
+ gap: 2px;
+ border: 1px solid #e2e8f0;
+ border-radius: 9px;
+ background: #f5f7fa;
+ padding: 3px;
+}
+
+.v2-mileage-ranges .semi-button {
+ min-height: 30px;
+ border-radius: 6px;
+ font-size: 11px;
+ font-weight: 650;
+}
+
+.v2-mileage-ranges .semi-button:not(.semi-button-primary):hover {
+ background: #fff;
+ color: #1268df;
+}
+
+.v2-mileage-query-panel .v2-mileage-summary.semi-card {
+ border: 0;
+ border-top: 1px solid #e2e9f2;
+ border-radius: 0 0 11px 11px;
+ background: #fff;
+ box-shadow: none;
+}
+
+.v2-mileage-summary > .semi-card-body > .v2-mileage-summary-card.is-primary > .semi-card-body {
+ background: linear-gradient(128deg, #0c61e7 0%, #1c73ee 68%, #2c82ef 100%);
+}
+
+.v2-mileage-summary > .semi-card-body > .is-primary .v2-mileage-summary-value {
+ letter-spacing: -.035em;
+}
+
+.v2-mileage-summary-secondary.semi-card-group > .v2-mileage-summary-card > .semi-card-body {
+ justify-content: center;
+ padding: 11px 15px !important;
+}
+
+.v2-mileage-summary-secondary .v2-mileage-summary-card small {
+ color: #78879a;
+ font-size: 10px;
+ font-weight: 560;
+}
+
+.v2-mileage-summary-secondary .v2-mileage-summary-card strong {
+ margin: 5px 0 4px;
+ color: #22354d;
+ font-size: clamp(19px, 1.55vw, 25px);
+ font-weight: 760;
+ line-height: 1.08;
+}
+
+.v2-mileage-summary-secondary .v2-mileage-summary-card span {
+ display: -webkit-box;
+ min-height: 2.6em;
+ overflow: hidden;
+ color: #8492a4;
+ font-size: 9px;
+ line-height: 1.3;
+ text-overflow: ellipsis;
+ white-space: normal;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+}
+
+.v2-mileage-results.semi-card {
+ border-color: #d8e3ef;
+ border-radius: 12px;
+ box-shadow: 0 8px 26px rgba(31, 53, 80, .05);
+}
+
+.v2-mileage-results > .semi-card-body > .v2-workspace-panel-header {
+ min-height: 50px;
+ border-bottom-color: #e2e9f1;
+ background: linear-gradient(90deg, #fff 0%, #fbfcfe 100%);
+ padding-inline: 14px;
+}
+
+.v2-mileage-results .v2-workspace-panel-copy > h5.semi-typography {
+ color: #23384f;
+ font-size: 15px;
+}
+
+.v2-mileage-results .v2-workspace-panel-copy > .semi-typography,
+.v2-mileage-results .v2-workspace-result-meta {
+ color: #748499;
+ font-size: 10px;
+}
+
+.v2-mileage-result-actions > .semi-button {
+ min-height: 32px;
+ border-radius: 8px;
+ font-size: 11px;
+ font-weight: 650;
+}
+
+.v2-mileage-table-wrap {
+ scrollbar-color: #9eb0c4 #f2f5f9;
+ scrollbar-gutter: stable;
+ scrollbar-width: auto;
+}
+
+.v2-mileage-table-wrap::-webkit-scrollbar {
+ width: 14px;
+ height: 14px;
+}
+
+.v2-mileage-table-wrap::-webkit-scrollbar-track {
+ border-radius: 999px;
+ background: #f2f5f9;
+}
+
+.v2-mileage-table-wrap::-webkit-scrollbar-thumb {
+ min-width: 56px;
+ min-height: 56px;
+ border: 3px solid #f2f5f9;
+ border-radius: 999px;
+ background: #9eb0c4;
+}
+
+.v2-mileage-table-wrap::-webkit-scrollbar-thumb:hover {
+ background: #7f95ae;
+}
+
+.v2-mileage-table-wrap::-webkit-scrollbar-corner {
+ background: #f2f5f9;
+}
+
+.v2-mileage-table .semi-table-thead > .semi-table-row > .semi-table-row-head {
+ height: 42px;
+ border-bottom-color: #dde6f0;
+ background: #f6f8fb;
+ color: #5a6d84;
+ font-size: 11px;
+}
+
+.v2-mileage-table .semi-table-tbody > .semi-table-row > .semi-table-row-cell {
+ height: 52px;
+ border-bottom-color: #e9eef4;
+ color: #43566d;
+ font-size: 12px;
+}
+
+.v2-mileage-table .semi-table-tbody > .semi-table-row:hover {
+ background: #f6faff;
+}
+
+.v2-mileage-table .semi-table-row-cell.is-plate strong {
+ color: #243950;
+ font-size: 12px;
+ font-weight: 720;
+}
+
+.v2-mileage-table .semi-table-row-head.is-total {
+ background: #edf5ff;
+ color: #175fbc;
+}
+
+.v2-mileage-table.semi-table-wrapper :is(.semi-table-row-head,.semi-table-row-cell).is-total,
+.v2-mileage-table :is(.semi-table-row-head,.semi-table-row-cell).semi-table-cell-fixed-right {
+ border-left-color: #d5e2f1;
+ box-shadow: -6px 0 14px rgba(38, 71, 111, .045);
+}
+
+.v2-mileage-table .semi-table-row-cell.is-period {
+ color: #0e62cf;
+ font-size: 12px;
+ font-weight: 780;
+}
+
+.v2-mileage-results > .semi-card-body > footer {
+ min-height: 40px;
+ border-top-color: #e3eaf2;
+ background: #fbfcfe;
+}
+
+.v2-mileage-evidence {
+ min-height: 20px;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 0;
+ color: #8290a2;
+ font-size: 9px;
+}
+
+.v2-mileage-evidence > span {
+ position: relative;
+ overflow: hidden;
+ padding-inline: 10px;
+ text-overflow: ellipsis;
+}
+
+.v2-mileage-evidence > span:first-child {
+ padding-left: 2px;
+}
+
+.v2-mileage-evidence > span + span::before {
+ position: absolute;
+ top: 50%;
+ left: 0;
+ width: 3px;
+ height: 3px;
+ border-radius: 50%;
+ background: #b4c0ce;
+ content: "";
+ transform: translateY(-50%);
+}
+
+@media (min-width: 681px) and (max-width: 1050px) {
+ .v2-mileage-query-panel .v2-mileage-filter {
+ row-gap: 10px;
+ }
+
+ .v2-mileage-summary > .semi-card-body {
+ grid-template-columns: minmax(285px, 1fr) minmax(0, 2fr);
+ }
+}
+
+@media (max-width: 680px) {
+ .v2-mileage-page {
+ gap: 7px;
+ padding: 8px;
+ }
+
+ .v2-mileage-query-panel.v2-workspace-filter-panel.semi-card {
+ border-radius: 11px;
+ box-shadow: 0 5px 18px rgba(31, 53, 80, .05);
+ }
+
+ .v2-mileage-query-panel .v2-mileage-filter {
+ gap: 9px;
+ padding: 10px;
+ }
+
+ .v2-mileage-filter > label > .semi-input-wrapper,
+ .v2-mileage-multiselect,
+ .v2-mileage-source-trigger.semi-button,
+ .v2-mileage-filter > .v2-primary-button.semi-button {
+ min-height: 42px;
+ }
+
+ .v2-mileage-query-panel .v2-mileage-summary.semi-card {
+ border-top: 0;
+ border-radius: 10px;
+ }
+
+ .v2-mileage-summary-secondary .v2-mileage-summary-card strong {
+ font-size: 16px;
+ }
+
+ .v2-mileage-summary-secondary .v2-mileage-summary-card span {
+ display: block;
+ min-height: 0;
+ overflow: hidden;
+ font-size: 8px;
+ line-height: 1.35;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .v2-mileage-results > .semi-card-body > .v2-workspace-panel-header {
+ display: grid;
+ min-height: 54px;
+ height: 54px;
+ grid-template-columns: 82px minmax(0, 1fr);
+ gap: 6px;
+ padding: 7px 9px;
+ }
+
+ .v2-mileage-results .v2-workspace-panel-copy {
+ width: 82px;
+ min-width: 82px;
+ max-width: 82px;
+ flex: none;
+ }
+
+ .v2-mileage-results .v2-workspace-panel-copy > h5.semi-typography {
+ width: auto;
+ overflow: visible;
+ font-size: 13px;
+ text-overflow: clip;
+ }
+
+ .v2-mileage-results .v2-workspace-panel-copy > .semi-typography:last-child,
+ .v2-mileage-results .v2-workspace-result-meta > span:last-child {
+ display: none;
+ }
+
+ .v2-mileage-results > .semi-card-body > .v2-workspace-panel-header > .v2-mileage-result-actions {
+ display: flex;
+ width: 100%;
+ min-width: 0;
+ max-width: none;
+ height: auto;
+ flex: none;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 4px;
+ }
+
+ .v2-mileage-results .v2-mileage-result-actions > .v2-workspace-panel-meta {
+ flex: 0 0 auto;
+ font-size: 8px;
+ }
+
+ .v2-mileage-result-actions > .semi-button {
+ width: auto;
+ min-width: 0;
+ height: 32px;
+ flex: 0 0 auto;
+ padding-inline: 7px;
+ font-size: 9px;
+ }
+
+ .v2-mileage-table table {
+ width: max(100%, var(--v2-mileage-mobile-table-width)) !important;
+ }
+
+ .v2-mileage-table col.is-plate,
+ .v2-mileage-table :is(.semi-table-row-head,.semi-table-row-cell).is-plate {
+ width: 96px !important;
+ min-width: 96px !important;
+ max-width: 96px !important;
+ }
+
+ .v2-mileage-table col.is-date,
+ .v2-mileage-table :is(.semi-table-row-head,.semi-table-row-cell).is-date {
+ width: 78px !important;
+ min-width: 78px !important;
+ max-width: 78px !important;
+ }
+
+ .v2-mileage-table col.is-total,
+ .v2-mileage-table :is(.semi-table-row-head,.semi-table-row-cell).is-total {
+ width: 104px !important;
+ min-width: 104px !important;
+ max-width: 104px !important;
+ }
+
+ .v2-mileage-table .semi-table-thead > .semi-table-row > .semi-table-row-head {
+ height: 38px;
+ font-size: 10px;
+ }
+
+ .v2-mileage-table .semi-table-tbody > .semi-table-row > .semi-table-row-cell {
+ height: 44px;
+ font-size: 11px;
+ }
+}