feat(platform): summarize mileage queries
This commit is contained in:
@@ -1,23 +1,51 @@
|
||||
import { Button, Card, Form, Select, Space, Table, Tag, Toast } from '@douyinfe/semi-ui';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { api } from '../api/client';
|
||||
import type { DailyMileageRow } from '../api/types';
|
||||
import type { DailyMileageRow, MileageSummary } from '../api/types';
|
||||
import { PageHeader } from '../components/PageHeader';
|
||||
|
||||
const emptySummary: MileageSummary = {
|
||||
vehicleCount: 0,
|
||||
recordCount: 0,
|
||||
sourceCount: 0,
|
||||
totalMileageKm: 0,
|
||||
averageMileagePerVin: 0
|
||||
};
|
||||
|
||||
function mileageParams(values: Record<string, string>, pageSize?: number, offset?: number) {
|
||||
const params = new URLSearchParams();
|
||||
if (pageSize != null) params.set('limit', String(pageSize));
|
||||
if (offset != null) params.set('offset', String(offset));
|
||||
if (values?.vin) params.set('vin', values.vin);
|
||||
if (values?.protocol) params.set('protocol', values.protocol);
|
||||
if (values?.dateFrom) params.set('dateFrom', values.dateFrom);
|
||||
if (values?.dateTo) params.set('dateTo', values.dateTo);
|
||||
return params;
|
||||
}
|
||||
|
||||
function formatKm(value: number) {
|
||||
return Number.isFinite(value) ? value.toLocaleString(undefined, { maximumFractionDigits: 1 }) : '0';
|
||||
}
|
||||
|
||||
export function Mileage() {
|
||||
const [rows, setRows] = useState<DailyMileageRow[]>([]);
|
||||
const [summary, setSummary] = useState<MileageSummary>(emptySummary);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [summaryLoading, setSummaryLoading] = useState(true);
|
||||
const [filters, setFilters] = useState<Record<string, string>>({});
|
||||
const [pagination, setPagination] = useState({ currentPage: 1, pageSize: 20, total: 0 });
|
||||
|
||||
const loadSummary = (values: Record<string, string> = filters) => {
|
||||
setSummaryLoading(true);
|
||||
api.mileageSummary(mileageParams(values))
|
||||
.then(setSummary)
|
||||
.catch((error: Error) => Toast.error(error.message))
|
||||
.finally(() => setSummaryLoading(false));
|
||||
};
|
||||
|
||||
const load = (values: Record<string, string> = filters, page = pagination.currentPage, pageSize = pagination.pageSize) => {
|
||||
setLoading(true);
|
||||
const params = new URLSearchParams({ limit: String(pageSize), offset: String((page - 1) * pageSize) });
|
||||
if (values?.vin) params.set('vin', values.vin);
|
||||
if (values?.protocol) params.set('protocol', values.protocol);
|
||||
if (values?.dateFrom) params.set('dateFrom', values.dateFrom);
|
||||
if (values?.dateTo) params.set('dateTo', values.dateTo);
|
||||
api.dailyMileage(params)
|
||||
api.dailyMileage(mileageParams(values, pageSize, (page - 1) * pageSize))
|
||||
.then((nextPage) => {
|
||||
setRows(nextPage.items);
|
||||
setPagination({ currentPage: page, pageSize, total: nextPage.total });
|
||||
@@ -27,6 +55,7 @@ export function Mileage() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadSummary({});
|
||||
load({}, 1, pagination.pageSize);
|
||||
}, []);
|
||||
|
||||
@@ -37,9 +66,10 @@ export function Mileage() {
|
||||
<Form layout="horizontal" onSubmit={(values) => {
|
||||
const nextFilters = values as Record<string, string>;
|
||||
setFilters(nextFilters);
|
||||
loadSummary(nextFilters);
|
||||
load(nextFilters, 1, pagination.pageSize);
|
||||
}}>
|
||||
<Form.Input field="vin" label="关键词" placeholder="VIN / 车牌" style={{ width: 260 }} />
|
||||
<Form.Input field="vin" label="关键词" placeholder="VIN / 车牌 / 手机号 / OEM" style={{ width: 260 }} />
|
||||
<Form.Select field="protocol" label="协议" placeholder="全部协议" style={{ width: 180 }}>
|
||||
<Select.Option value="GB32960">GB32960</Select.Option>
|
||||
<Select.Option value="JT808">JT808</Select.Option>
|
||||
@@ -51,11 +81,25 @@ export function Mileage() {
|
||||
<Button htmlType="submit" theme="solid" type="primary">查询</Button>
|
||||
<Button onClick={() => {
|
||||
setFilters({});
|
||||
loadSummary({});
|
||||
load({}, 1, pagination.pageSize);
|
||||
}}>重置</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
</Card>
|
||||
<div className="vp-kpi-grid" style={{ marginTop: 16 }}>
|
||||
{[
|
||||
{ label: '车辆数', value: summary.vehicleCount.toLocaleString() },
|
||||
{ label: '累计里程 km', value: formatKm(summary.totalMileageKm) },
|
||||
{ label: '单车平均 km', value: formatKm(summary.averageMileagePerVin) },
|
||||
{ label: '来源 / 记录', value: `${summary.sourceCount}/${summary.recordCount.toLocaleString()}` }
|
||||
].map((item) => (
|
||||
<Card key={item.label} bordered loading={summaryLoading}>
|
||||
<div className="vp-kpi-value">{item.value}</div>
|
||||
<div className="vp-kpi-label">{item.label}</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<Card bordered style={{ marginTop: 16 }}>
|
||||
<Table
|
||||
loading={loading}
|
||||
|
||||
Reference in New Issue
Block a user