refine Semi UI mileage workspace
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { IconArrowDown, IconArrowUp, IconClose, IconDownload, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
|
||||
import { Button, Card, CardGroup, Empty, Input, SideSheet, Spin, Switch, Table, Tag } from '@douyinfe/semi-ui';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { FormEvent, type RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { type CSSProperties, FormEvent, memo, type RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { api } from '../../api/client';
|
||||
import type { DailyMileageRow, MileageStatistics, Page, VehicleRow } from '../../api/types';
|
||||
@@ -233,22 +233,25 @@ function VehicleMultiSelect({ value, onChange }: { value: VehicleOption[]; onCha
|
||||
function SummaryRail({ data, criteria, fleetTotal, loading }: { data?: MileageStatistics; criteria: Criteria; fleetTotal?: number; loading: boolean }) {
|
||||
const days = inclusiveDays(criteria.dateFrom, criteria.dateTo);
|
||||
const vehicleCount = criteria.vehicles.length ? data?.vehicleCount ?? 0 : fleetTotal ?? 0;
|
||||
const summaryRange = criteria.dateFrom.slice(0, 4) === criteria.dateTo.slice(0, 4)
|
||||
? `${criteria.dateFrom.replace(/-/g, '/')} — ${criteria.dateTo.slice(5).replace('-', '/')}`
|
||||
: `${criteria.dateFrom.replace(/-/g, '/')} — ${criteria.dateTo.replace(/-/g, '/')}`;
|
||||
const items = loading ? [
|
||||
['已绑定主车辆', '—', '正在按新筛选范围查询'],
|
||||
['统计天数', `${days} 天`, `${criteria.dateFrom} 至 ${criteria.dateTo}`],
|
||||
['统计天数', `${days} 天`, summaryRange],
|
||||
['区间总里程', '—', '正在计算区间汇总'],
|
||||
['日均里程', '—', '正在计算有效车辆日']
|
||||
] : [
|
||||
['已绑定主车辆', `${vehicleCount} 辆`, criteria.vehicles.length ? `已选择 ${criteria.vehicles.length} 辆` : `档案口径 · ${data?.vehicleCount ?? 0} 辆有里程`],
|
||||
['统计天数', `${days} 天`, `${criteria.dateFrom} 至 ${criteria.dateTo}`],
|
||||
['统计天数', `${days} 天`, summaryRange],
|
||||
['区间总里程', `${formatKm(data?.periodMileageKm)} km`, `${data?.recordCount ?? 0} 条车辆日记录`],
|
||||
['日均里程', `${formatKm(data?.averageDailyMileageKm)} km`, '按有效车辆日平均']
|
||||
];
|
||||
const primary = items[2];
|
||||
const secondary = [items[0], items[1], items[3]];
|
||||
return <Card className="v2-mileage-summary" aria-label="里程查询统计信息" bodyStyle={{ padding: 0 }}>
|
||||
<Card className="v2-mileage-summary-card is-primary" bodyStyle={{ padding: 0 }} aria-label={`${primary[0]}:${primary[1]};${primary[2]}`}><small>{primary[0]}</small><strong className="v2-mileage-summary-value">{primary[1]}</strong><span>{primary[2]}</span></Card>
|
||||
<CardGroup className="v2-mileage-summary-secondary" type="grid" spacing={0}>{secondary.map(([label, value, note]) => <Card className="v2-mileage-summary-card" bodyStyle={{ padding: 0 }} aria-label={`${label}:${value};${note}`} key={label}><small>{label}</small><strong>{value}</strong><span>{note}</span></Card>)}</CardGroup>
|
||||
<Card className="v2-mileage-summary-card is-primary" bodyStyle={{ padding: 0 }} aria-label={`${primary[0]}:${primary[1]};${primary[2]}`}><small>{primary[0]}</small><strong className="v2-mileage-summary-value">{primary[1]}</strong><span title={primary[2]}>{primary[2]}</span></Card>
|
||||
<CardGroup className="v2-mileage-summary-secondary" type="grid" spacing={0}>{secondary.map(([label, value, note], index) => <Card className={`v2-mileage-summary-card is-${['fleet', 'range', 'average'][index]}`} bodyStyle={{ padding: 0 }} aria-label={`${label}:${value};${note}`} key={label}><small>{label}</small><strong>{value}</strong><span title={note}>{note}</span></Card>)}</CardGroup>
|
||||
</Card>;
|
||||
}
|
||||
|
||||
@@ -270,37 +273,41 @@ function dateLabel(date: string) {
|
||||
return `${Number(month)}/${Number(day)}`;
|
||||
}
|
||||
|
||||
function MileageTable({ rows, dates, scrollRef }: { rows: VehicleMileageMatrix[]; dates: string[]; scrollRef: RefObject<HTMLDivElement> }) {
|
||||
let maxDailyMileage = 1;
|
||||
for (const row of rows) {
|
||||
for (const mileage of row.days.values()) maxDailyMileage = Math.max(maxDailyMileage, mileage);
|
||||
}
|
||||
const columns = [
|
||||
{ title: '车牌', dataIndex: 'plate', className: 'is-plate', width: 120, render: (_value: string, row: VehicleMileageMatrix) => <strong>{row.plate || '未绑定'}</strong> },
|
||||
...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` }
|
||||
];
|
||||
const tableWidth = 120 + dates.length * 96 + 128;
|
||||
return <div className="v2-mileage-table-wrap" ref={scrollRef}>
|
||||
const MileageTable = memo(function MileageTable({ rows, dates, scrollRef }: { rows: VehicleMileageMatrix[]; dates: string[]; scrollRef: RefObject<HTMLDivElement> }) {
|
||||
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) => <strong>{row.plate || '未绑定'}</strong> },
|
||||
...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 <div className="v2-mileage-table-wrap" ref={scrollRef} style={{ '--v2-mileage-mobile-table-width': `${96 + dates.length * 78 + 104}px` } as CSSProperties}>
|
||||
<Table className="v2-mileage-table" columns={columns} dataSource={rows} rowKey="vin" pagination={false} scroll={{ x: Math.max(556, tableWidth) }} />
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
export default function StatisticsPage() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
Reference in New Issue
Block a user