feat(platform): surface vehicle quality issues

This commit is contained in:
lingniu
2026-07-03 22:04:12 +08:00
parent 2d064e3c53
commit 2fcce04c5a
8 changed files with 79 additions and 11 deletions

View File

@@ -60,6 +60,7 @@ export interface VehicleDetail {
history: Page<HistoryLocationRow>;
raw: Page<RawFrameRow>;
mileage: Page<DailyMileageRow>;
quality: Page<QualityIssueRow>;
}
export interface VehicleSourceStatus {

View File

@@ -1,4 +1,4 @@
import { Card, Col, Row, Table, Tag, Toast } from '@douyinfe/semi-ui';
import { Button, Card, Col, Form, Row, Select, Space, Table, Tag, Toast } from '@douyinfe/semi-ui';
import { useEffect, useState } from 'react';
import { api } from '../api/client';
import type { OpsHealth, QualityIssueRow } from '../api/types';
@@ -8,10 +8,17 @@ export function Quality() {
const [issues, setIssues] = useState<QualityIssueRow[]>([]);
const [health, setHealth] = useState<OpsHealth | null>(null);
useEffect(() => {
api.qualityIssues(new URLSearchParams({ limit: '20' }))
const loadIssues = (values?: Record<string, string>) => {
const params = new URLSearchParams({ limit: '20' });
if (values?.keyword) params.set('keyword', values.keyword);
if (values?.protocol) params.set('protocol', values.protocol);
api.qualityIssues(params)
.then((page) => setIssues(page.items))
.catch((error: Error) => Toast.error(error.message));
};
useEffect(() => {
loadIssues();
api.opsHealth()
.then(setHealth)
.catch((error: Error) => Toast.error(error.message));
@@ -26,6 +33,16 @@ export function Quality() {
<Col span={8}><Card bordered title="存储写入">{health?.tdengineWritable && health.mysqlWritable ? '正常' : '异常'}</Card></Col>
</Row>
<Card bordered title="质量问题" style={{ marginTop: 16 }}>
<Form layout="horizontal" onSubmit={(values) => loadIssues(values as Record<string, string>)} style={{ marginBottom: 12 }}>
<Form.Input field="keyword" label="关键词" placeholder="手机号 / 来源地址 / 车牌" style={{ width: 240 }} />
<Form.Select field="protocol" label="协议" placeholder="全部协议" style={{ width: 160 }}>
<Select.Option value="JT808">JT808</Select.Option>
</Form.Select>
<Space>
<Button htmlType="submit" theme="solid" type="primary"></Button>
<Button onClick={() => loadIssues()}></Button>
</Space>
</Form>
<Table
rowKey="vin"
dataSource={issues}

View File

@@ -2,7 +2,7 @@ import { Button, Card, Descriptions, Form, Select, Space, Table, Tabs, Tag, Toas
import { IconRefresh, IconSearch } from '@douyinfe/semi-icons';
import { useEffect, useMemo, useState } from 'react';
import { api } from '../api/client';
import type { VehicleDetail as VehicleDetailData, VehicleSourceStatus } from '../api/types';
import type { QualityIssueRow, VehicleDetail as VehicleDetailData, VehicleSourceStatus } from '../api/types';
import { PageHeader } from '../components/PageHeader';
import { StatusTag } from '../components/StatusTag';
@@ -43,6 +43,7 @@ export function VehicleDetail({ vin }: { vin: string }) {
const latest = detail?.realtime[0];
const protocols = useMemo(() => detail?.sources ?? [], [detail?.sources]);
const latestRaw = detail?.raw.items[0];
const qualityCount = detail?.quality.items.length ?? 0;
return (
<div className="vp-page">
@@ -78,7 +79,8 @@ export function VehicleDetail({ vin }: { vin: string }) {
{ key: '在线', value: <StatusTag status={identity?.online || latest ? 'ok' : 'offline'} /> },
{ key: '来源协议', value: protocols.length > 0 ? <Space>{protocols.map((item) => <Tag key={item} color="blue">{item}</Tag>)}</Space> : '-' },
{ key: '最后位置时间', value: latest?.lastSeen ?? '-' },
{ key: '最新 RAW 时间', value: latestRaw?.serverTime ?? '-' }
{ key: '最新 RAW 时间', value: latestRaw?.serverTime ?? '-' },
{ key: '质量风险', value: <Tag color={qualityCount > 0 ? 'orange' : 'green'}>{qualityCount > 0 ? `${qualityCount}` : '无'}</Tag> }
]}
/>
</div>
@@ -170,6 +172,21 @@ export function VehicleDetail({ vin }: { vin: string }) {
]}
/>
</Tabs.TabPane>
<Tabs.TabPane tab="质量风险" itemKey="quality">
<Table
loading={loading}
pagination={false}
rowKey={(row?: QualityIssueRow) => `${row?.protocol ?? ''}-${row?.issueType ?? ''}-${row?.lastSeen ?? ''}`}
dataSource={detail?.quality.items ?? []}
columns={[
{ title: '来源', dataIndex: 'protocol', width: 130 },
{ title: '问题', dataIndex: 'issueType', width: 150 },
{ title: '级别', width: 110, render: (_: unknown, row: QualityIssueRow) => <Tag color={row.severity === 'error' ? 'red' : 'orange'}>{row.severity}</Tag> },
{ title: '最后时间', dataIndex: 'lastSeen', width: 190 },
{ title: '说明', dataIndex: 'detail' }
]}
/>
</Tabs.TabPane>
</Tabs>
</Card>
</div>