From 28d1a68823b5c9c3c347aa75986d6be39de083e3 Mon Sep 17 00:00:00 2001 From: lingniu Date: Fri, 3 Jul 2026 22:27:40 +0800 Subject: [PATCH] feat(platform): paginate quality issues --- .../api/internal/platform/production_store.go | 13 ++++-- .../apps/web/src/pages/Quality.tsx | 41 +++++++++++++++---- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/vehicle-data-platform/apps/api/internal/platform/production_store.go b/vehicle-data-platform/apps/api/internal/platform/production_store.go index 491fc44e..bd60b065 100644 --- a/vehicle-data-platform/apps/api/internal/platform/production_store.go +++ b/vehicle-data-platform/apps/api/internal/platform/production_store.go @@ -313,12 +313,17 @@ func (s *ProductionStore) QualityIssues(ctx context.Context, query url.Values) ( like := "%" + keyword + "%" args = append(args, like, like, like) } + countArgs := append([]any(nil), args...) args = append(args, limit, offset) + fromSQL := `FROM jt808_registration r LEFT JOIN vehicle_identity_binding b ON b.phone = r.phone WHERE ` + strings.Join(where, " AND ") + total := 0 + if err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) `+fromSQL, countArgs...).Scan(&total); err != nil { + return Page[QualityIssueRow]{}, err + } rows, err := s.db.QueryContext(ctx, `SELECT COALESCE(b.vin, 'unknown') AS vin, COALESCE(b.plate, '') AS plate, r.phone, COALESCE(r.source_endpoint, ''), COALESCE(DATE_FORMAT(r.latest_seen_at, '%Y-%m-%d %H:%i:%s'), '') `+ - `FROM jt808_registration r LEFT JOIN vehicle_identity_binding b ON b.phone = r.phone `+ - `WHERE `+strings.Join(where, " AND ")+` ORDER BY r.latest_seen_at DESC LIMIT ? OFFSET ?`, args...) + fromSQL+` ORDER BY r.latest_seen_at DESC, r.phone ASC LIMIT ? OFFSET ?`, args...) if err != nil { - return Page[QualityIssueRow]{}, nil + return Page[QualityIssueRow]{}, err } defer rows.Close() items := make([]QualityIssueRow, 0) @@ -337,7 +342,7 @@ func (s *ProductionStore) QualityIssues(ctx context.Context, query url.Values) ( Detail: fmt.Sprintf("phone %s 未命中 binding 表,来源 %s", phone, source), }) } - return Page[QualityIssueRow]{Items: items, Total: len(items), Limit: limit, Offset: offset}, rows.Err() + return Page[QualityIssueRow]{Items: items, Total: total, Limit: limit, Offset: offset}, rows.Err() } func (s *ProductionStore) OpsHealth(ctx context.Context) (OpsHealth, error) { diff --git a/vehicle-data-platform/apps/web/src/pages/Quality.tsx b/vehicle-data-platform/apps/web/src/pages/Quality.tsx index 42859c62..237ca149 100644 --- a/vehicle-data-platform/apps/web/src/pages/Quality.tsx +++ b/vehicle-data-platform/apps/web/src/pages/Quality.tsx @@ -7,18 +7,26 @@ import { PageHeader } from '../components/PageHeader'; export function Quality() { const [issues, setIssues] = useState([]); const [health, setHealth] = useState(null); + const [loadingIssues, setLoadingIssues] = useState(true); + const [filters, setFilters] = useState>({}); + const [pagination, setPagination] = useState({ currentPage: 1, pageSize: 20, total: 0 }); - const loadIssues = (values?: Record) => { - const params = new URLSearchParams({ limit: '20' }); + const loadIssues = (values: Record = filters, page = pagination.currentPage, pageSize = pagination.pageSize) => { + setLoadingIssues(true); + const params = new URLSearchParams({ limit: String(pageSize), offset: String((page - 1) * pageSize) }); 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)); + .then((nextPage) => { + setIssues(nextPage.items); + setPagination({ currentPage: page, pageSize, total: nextPage.total }); + }) + .catch((error: Error) => Toast.error(error.message)) + .finally(() => setLoadingIssues(false)); }; useEffect(() => { - loadIssues(); + loadIssues({}, 1, pagination.pageSize); api.opsHealth() .then(setHealth) .catch((error: Error) => Toast.error(error.message)); @@ -33,20 +41,35 @@ export function Quality() { {health?.tdengineWritable && health.mysqlWritable ? '正常' : '异常'} -
loadIssues(values as Record)} style={{ marginBottom: 12 }}> + { + const nextFilters = values as Record; + setFilters(nextFilters); + loadIssues(nextFilters, 1, pagination.pageSize); + }} style={{ marginBottom: 12 }}> JT808 - + `${row?.protocol ?? ''}-${row?.issueType ?? ''}-${row?.lastSeen ?? ''}-${row?.detail ?? ''}`} dataSource={issues} - pagination={false} + 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' },