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, QualitySummary, QualityIssueRow } from '../api/types'; import { PageHeader } from '../components/PageHeader'; const statusColor: Record = { ok: 'green', warning: 'orange', error: 'red' }; function formatLag(value?: number | null) { return value == null ? '未接入' : value.toLocaleString(); } function storageReadStatus(health: OpsHealth | null) { return health?.tdengineWritable && health.mysqlWritable ? 'ok' : 'error'; } const emptySummary: QualitySummary = { issueVehicleCount: 0, issueRecordCount: 0, errorCount: 0, warningCount: 0, protocols: [], issueTypes: [] }; function qualityParams(values: Record) { const params = new URLSearchParams(); if (values?.keyword) params.set('keyword', values.keyword); if (values?.protocol) params.set('protocol', values.protocol); return params; } export function Quality({ onHealthLoaded }: { onHealthLoaded?: (health: OpsHealth) => void }) { const [issues, setIssues] = useState([]); const [summary, setSummary] = useState(emptySummary); const [health, setHealth] = useState(null); const [loadingIssues, setLoadingIssues] = useState(true); const [loadingSummary, setLoadingSummary] = useState(true); const [loadingHealth, setLoadingHealth] = useState(true); const [filters, setFilters] = useState>({}); const [pagination, setPagination] = useState({ currentPage: 1, pageSize: 20, total: 0 }); const loadIssues = (values: Record = filters, page = pagination.currentPage, pageSize = pagination.pageSize) => { setLoadingIssues(true); const params = qualityParams(values); params.set('limit', String(pageSize)); params.set('offset', String((page - 1) * pageSize)); api.qualityIssues(params) .then((nextPage) => { setIssues(nextPage.items); setPagination({ currentPage: page, pageSize, total: nextPage.total }); }) .catch((error: Error) => Toast.error(error.message)) .finally(() => setLoadingIssues(false)); }; const loadSummary = (values: Record = filters) => { setLoadingSummary(true); api.qualitySummary(qualityParams(values)) .then(setSummary) .catch((error: Error) => Toast.error(error.message)) .finally(() => setLoadingSummary(false)); }; const loadHealth = () => { setLoadingHealth(true); api.opsHealth() .then((nextHealth) => { setHealth(nextHealth); onHealthLoaded?.(nextHealth); }) .catch((error: Error) => Toast.error(error.message)) .finally(() => setLoadingHealth(false)); }; useEffect(() => { loadSummary({}); loadIssues({}, 1, pagination.pageSize); loadHealth(); }, []); return (
{[ { label: '问题车辆', value: summary.issueVehicleCount.toLocaleString() }, { label: '问题记录', value: summary.issueRecordCount.toLocaleString() }, { label: '错误 / 警告', value: `${summary.errorCount}/${summary.warningCount}` }, { label: '主要问题', value: summary.issueTypes.length > 0 ? `${summary.issueTypes[0].name} ${summary.issueTypes[0].count}` : '-' } ].map((item) => (
{item.value}
{item.label}
))}
{formatLag(health?.kafkaLag)} {formatLag(health?.redisOnlineKeys)} {storageReadStatus(health) === 'ok' ? '正常' : '异常'}
{ const nextFilters = values as Record; setFilters(nextFilters); loadSummary(nextFilters); loadIssues(nextFilters, 1, pagination.pageSize); }} style={{ marginBottom: 12 }}> JT808 `${row?.protocol ?? ''}-${row?.issueType ?? ''}-${row?.lastSeen ?? ''}-${row?.detail ?? ''}`} dataSource={issues} pagination={{ currentPage: pagination.currentPage, pageSize: pagination.pageSize, total: pagination.total, showSizeChanger: true, onPageChange: (page) => loadIssues(filters, page, pagination.pageSize), onPageSizeChange: (pageSize) => loadIssues(filters, 1, pageSize) }} columns={[ { title: 'VIN', dataIndex: 'vin' }, { title: '车牌', dataIndex: 'plate' }, { title: '手机号', dataIndex: 'phone' }, { title: '来源地址', dataIndex: 'sourceEndpoint' }, { title: '协议', dataIndex: 'protocol' }, { title: '问题', dataIndex: 'issueType' }, { title: '级别', render: (_: unknown, row: QualityIssueRow) => {row.severity} }, { title: '最后时间', dataIndex: 'lastSeen' }, { title: '说明', dataIndex: 'detail' } ]} /> 链路健康} style={{ marginTop: 16 }} >
( {row.status} ) }, { title: '说明', dataIndex: 'detail' } ]} /> ); }