refine Semi UI history evidence workspace

This commit is contained in:
lingniu
2026-07-18 13:00:33 +08:00
parent 8aa273e579
commit ad81d108a7
3 changed files with 157 additions and 10 deletions

View File

@@ -1,7 +1,7 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { IconChevronRight, IconClose, IconDownload, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
import { Button, Card, CardGroup, Checkbox, Descriptions, Empty, Input, Progress, Select, SideSheet, Spin, Table, Tag, Typography } from '@douyinfe/semi-ui';
import { FormEvent, useCallback, useEffect, useMemo, useState } from 'react';
import { FormEvent, KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { api } from '../../api/client';
import type { HistoryDataResponse, HistoryDataRow, HistoryExportRequest, HistoryMetricDefinition, HistorySeriesResponse } from '../../api/types';
@@ -161,6 +161,7 @@ function ColumnVisibilityPanel({ metrics, visible, visibleKeys, onToggle, onShow
onReset: () => void;
onClose: () => void;
}) {
const mobileLayout = useMobileLayout();
const [search, setSearch] = useState('');
const filteredMetrics = useMemo(() => {
const keyword = search.trim().toLowerCase();
@@ -173,7 +174,9 @@ function ColumnVisibilityPanel({ metrics, visible, visibleKeys, onToggle, onShow
className="v2-history-column-sidesheet"
visible={visible}
aria-label="列显示设置"
width={460}
placement={mobileLayout ? 'bottom' : 'right'}
width={mobileLayout ? undefined : 460}
height={mobileLayout ? 'min(68dvh, 580px)' : undefined}
title={<div className="v2-history-column-title"><strong></strong><span></span></div>}
onCancel={onClose}
footer={<div className="v2-history-column-footer"><span> {visibleKeys.length} / {metrics.length}</span><Button theme="light" onClick={onShowAll} disabled={!metrics.length}></Button><Button theme="light" onClick={onReset} disabled={!metrics.length}></Button></div>}
@@ -188,12 +191,12 @@ function ColumnVisibilityPanel({ metrics, visible, visibleKeys, onToggle, onShow
function HistoryDataTable({ rows, metrics, selectedRowID, onSelect }: { rows: HistoryDataRow[]; metrics: HistoryMetricDefinition[]; selectedRowID?: string; onSelect: (row: HistoryDataRow) => void }) {
const columns = useMemo(() => [
{
title: '', dataIndex: 'selection', width: 42,
title: '', dataIndex: 'selection', width: 42, fixed: 'left' as const, className: 'v2-history-selection-column',
render: (_: unknown, row: HistoryDataRow) => <Checkbox checked={selectedRowID === row.id} onChange={() => onSelect(row)} aria-label={`选择 ${row.plate || row.vin} ${row.deviceTime}`} />
},
{ title: '设备时间', dataIndex: 'deviceTime', width: 150, render: (value: string) => <PlatformTime className="v2-history-time" value={value} /> },
{ title: '设备时间', dataIndex: 'deviceTime', width: 150, fixed: 'left' as const, className: 'v2-history-device-time-column', render: (value: string) => <PlatformTime className="v2-history-time" value={value} /> },
{ title: '车牌', dataIndex: 'plate', width: 110, fixed: 'left' as const, className: 'v2-history-plate-column', render: (value: string) => value || '—' },
{ title: '服务时间', dataIndex: 'serverTime', width: 150, render: (value: string) => <PlatformTime className="v2-history-time" value={value} /> },
{ title: '车牌', dataIndex: 'plate', width: 110, render: (value: string) => value || '—' },
{ title: 'VIN', dataIndex: 'vin', width: 160, render: (value: string) => <span className="v2-history-vin" title={value}>{value}</span> },
{ title: '协议', dataIndex: 'protocol', width: 100 },
...metrics.map((metric) => ({
@@ -220,7 +223,8 @@ function HistoryDataTable({ rows, metrics, selectedRowID, onSelect }: { rows: Hi
return <Table
className="v2-history-data-table"
style={{ minWidth }}
style={{ width: '100%' }}
scroll={{ x: minWidth }}
columns={columns}
dataSource={rows}
rowKey="id"
@@ -333,6 +337,13 @@ export default function HistoryPage() {
return { ...current, [criteria.category]: next };
});
const setVisibleMetrics = (keys: string[]) => setVisibleByCategory((current) => ({ ...current, [criteria.category]: keys }));
const scrollHistoryTable = (event: KeyboardEvent<HTMLDivElement>) => {
if (mobileLayout || event.currentTarget !== event.target || (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')) return;
const tableScroller = event.currentTarget.querySelector<HTMLElement>('.semi-table-body');
if (!tableScroller) return;
event.preventDefault();
tableScroller.scrollBy({ left: event.key === 'ArrowRight' ? 260 : -260, behavior: 'smooth' });
};
const totalPages = Math.max(1, Math.ceil((result?.total ?? 0) / limit));
const page = Math.floor(offset / limit) + 1;
const summarySources = resultSummary?.sources?.join('、') || '—';
@@ -424,8 +435,9 @@ export default function HistoryPage() {
<ColumnVisibilityPanel visible={columnSettingsOpen} metrics={allMetrics} visibleKeys={visibleKeys} onToggle={toggleMetric} onShowAll={() => setVisibleMetrics(allMetrics.map((metric) => metric.key))} onReset={() => setVisibleMetrics(allMetrics.filter((metric) => metric.defaultVisible).map((metric) => metric.key))} onClose={() => setColumnSettingsOpen(false)} />
<div
className={`v2-history-table-scroll${mobileLayout ? ' is-mobile-scroll' : ''}`}
tabIndex={mobileLayout ? 0 : undefined}
aria-label={mobileLayout ? '历史明细列表,可上下滚动' : undefined}
tabIndex={0}
aria-label={mobileLayout ? '历史明细列表,可上下滚动' : '历史明细表格,可使用左右方向键浏览业务字段'}
onKeyDown={scrollHistoryTable}
>
{mobileLayout
? <div className="v2-history-mobile-list">{resultRows.map((row) => <Card key={row.id} className={`v2-history-mobile-card${selectedRow?.id === row.id ? ' is-selected' : ''}`} bodyStyle={{ padding: 0 }}><Button theme="borderless" type="tertiary" className="v2-history-mobile-action" aria-pressed={selectedRow?.id === row.id} aria-expanded={selectedRow?.id === row.id} aria-label={`查看 ${row.plate || row.vin} ${row.deviceTime} 数据详情`} onClick={() => selectRow(row)}><span className="v2-history-mobile-card-content"><header><span><strong>{row.plate || '未绑定车牌'}</strong><small>{row.vin}</small></span><span className={`v2-quality is-${row.quality}`}><i />{historyQualityLabel(row.quality)}</span></header><p><PlatformTime className="v2-history-time" value={row.deviceTime} /><b>{row.protocol}</b></p><small className="v2-history-mobile-quality">{row.qualityReason || '平台未返回质量说明'}</small><dl>{visibleMetrics.slice(0, 4).map((metric) => <div key={metric.key}><dt>{metric.label}</dt><dd>{formatHistoryValue(row.values[metric.key], metric)}</dd></div>)}</dl><footer><IconChevronRight /></footer></span></Button></Card>)}</div>
@@ -445,7 +457,8 @@ export default function HistoryPage() {
className="v2-history-detail-sidesheet"
visible={mobileLayout && Boolean(selectedRow)}
aria-label="历史数据详情"
width="100%"
placement="bottom"
height="min(82dvh, 720px)"
title={<div className="v2-history-mobile-sheet-title"><strong></strong><span>{selectedRow ? `${selectedRow.plate || '未绑定车牌'} · ${selectedRow.protocol}` : '来源、质量与原始证据'}</span></div>}
onCancel={closeSelectedRow}
>
@@ -455,7 +468,9 @@ export default function HistoryPage() {
className="v2-history-export-sidesheet"
visible={exportAllowed && exportJobsOpen}
aria-label="历史数据导出任务"
width={mobileLayout ? '100%' : 520}
placement={mobileLayout ? 'bottom' : 'right'}
width={mobileLayout ? undefined : 520}
height={mobileLayout ? 'min(76dvh, 680px)' : undefined}
title={<div className="v2-history-mobile-sheet-title"><strong></strong><span></span></div>}
onCancel={() => setExportJobsOpen(false)}
>