feat(web): unify Semi UI workspace states
This commit is contained in:
@@ -7,7 +7,7 @@ import { api } from '../../api/client';
|
||||
import type { HistoryDataResponse, HistoryDataRow, HistoryExportRequest, HistoryMetricDefinition, HistorySeriesResponse } from '../../api/types';
|
||||
import { buildHistorySeriesPanels, formatExportFileSize, formatHistoryValue, formatSeriesGrain, historyExportPollInterval, parseHistoryKeywords } from '../domain/history';
|
||||
import { downloadBlob } from '../domain/download';
|
||||
import { InlineError } from '../shared/AsyncState';
|
||||
import { InlineError, PanelEmpty, PanelLoading } from '../shared/AsyncState';
|
||||
import { MonitorReturnBar } from '../shared/MonitorReturnBar';
|
||||
import { MobileFilterToggle } from '../shared/MobileFilterToggle';
|
||||
import { PageHeader } from '../shared/PageHeader';
|
||||
@@ -275,6 +275,8 @@ export default function HistoryPage() {
|
||||
gcTime: QUERY_MEMORY.highVolumeGcTime
|
||||
});
|
||||
const result = dataQuery.data;
|
||||
const resultRows = result?.rows ?? [];
|
||||
const resultSummary = result?.summary;
|
||||
const allMetrics = result?.columns ?? catalogQuery.data?.metrics.filter((metric) => metric.category === criteria.category) ?? [];
|
||||
const visibleKeys = visibleByCategory[criteria.category] ?? allMetrics.filter((metric) => metric.defaultVisible).map((metric) => metric.key);
|
||||
const visibleMetrics = allMetrics.filter((metric) => visibleKeys.includes(metric.key));
|
||||
@@ -292,7 +294,7 @@ export default function HistoryPage() {
|
||||
const selectedRow = scopedSelectedRowRef?.id === ''
|
||||
? undefined
|
||||
: scopedSelectedRowRef
|
||||
? result?.rows.find((row) => row.id === scopedSelectedRowRef.id)
|
||||
? resultRows.find((row) => row.id === scopedSelectedRowRef.id)
|
||||
: undefined;
|
||||
const selectRow = useCallback((row: HistoryDataRow) => {
|
||||
setSelectedRowRef({ scope: dataScope, id: row.id });
|
||||
@@ -325,7 +327,7 @@ export default function HistoryPage() {
|
||||
const setVisibleMetrics = (keys: string[]) => setVisibleByCategory((current) => ({ ...current, [criteria.category]: keys }));
|
||||
const totalPages = Math.max(1, Math.ceil((result?.total ?? 0) / limit));
|
||||
const page = Math.floor(offset / limit) + 1;
|
||||
const summarySources = result?.summary.sources.join('、') || '—';
|
||||
const summarySources = resultSummary?.sources?.join('、') || '—';
|
||||
|
||||
return <div className="v2-history-page">
|
||||
<MonitorReturnBar />
|
||||
@@ -342,7 +344,7 @@ export default function HistoryPage() {
|
||||
<fieldset className="v2-history-range"><legend>时间范围</legend><div><Input aria-label="开始时间" type="datetime-local" value={draft.dateFrom} onChange={(value) => setDraft((current) => ({ ...current, dateFrom: value }))} /><span>至</span><Input aria-label="结束时间" type="datetime-local" value={draft.dateTo} onChange={(value) => setDraft((current) => ({ ...current, dateTo: value }))} /></div></fieldset>
|
||||
<label><span id="history-category-label">数据类型</span><Select aria-labelledby="history-category-label" value={draft.category} onChange={(value) => setDraft((current) => ({ ...current, category: String(value) }))} optionList={(catalogQuery.data?.categories ?? [{ key: 'location', label: '位置数据' }, { key: 'raw', label: '原始报文' }, { key: 'mileage', label: '日里程' }]).map((item) => ({ value: item.key, label: item.label }))} /></label>
|
||||
<label><span id="history-protocol-label">数据来源</span><Select aria-labelledby="history-protocol-label" value={draft.protocol} onChange={(value) => setDraft((current) => ({ ...current, protocol: String(value) }))} optionList={[{ value: '', label: '全部来源' }, { value: 'GB32960', label: 'GB32960' }, { value: 'JT808', label: 'JT808' }, { value: 'YUTONG_MQTT', label: 'YUTONG_MQTT' }]} /></label>
|
||||
<div className="v2-history-toolbar-actions"><Button className="v2-primary-button" theme="solid" htmlType="submit" disabled={!parseHistoryKeywords(draft.keywords).length}>查询</Button><Button className="v2-secondary-button" theme="light" onClick={reset}>重置</Button>{exportAllowed ? <CreateExportButton disabled={!result?.rows.length} request={{ keywords, category: criteria.category, protocol: criteria.protocol || undefined, dateFrom: criteria.dateFrom, dateTo: criteria.dateTo, metrics: visibleKeys, format: 'csv' }} /> : <Tag className="v2-history-permission-tag" color="grey" type="light" size="large">只读</Tag>}</div>
|
||||
<div className="v2-history-toolbar-actions"><Button className="v2-primary-button" theme="solid" htmlType="submit" disabled={!parseHistoryKeywords(draft.keywords).length}>查询</Button><Button className="v2-secondary-button" theme="light" onClick={reset}>重置</Button>{exportAllowed ? <CreateExportButton disabled={!resultRows.length} request={{ keywords, category: criteria.category, protocol: criteria.protocol || undefined, dateFrom: criteria.dateFrom, dateTo: criteria.dateTo, metrics: visibleKeys, format: 'csv' }} /> : <Tag className="v2-history-permission-tag" color="grey" type="light" size="large">只读</Tag>}</div>
|
||||
</form>
|
||||
</Card>
|
||||
<Card className="v2-history-metrics-card" bodyStyle={{ padding: 0 }}>
|
||||
@@ -358,16 +360,20 @@ export default function HistoryPage() {
|
||||
{dataQuery.isError ? <InlineError message={dataQuery.error instanceof Error ? dataQuery.error.message : '历史查询失败'} onRetry={() => dataQuery.refetch()} /> : null}
|
||||
<div className={`v2-history-workspace${selectedRow && !mobileLayout ? ' is-inspector-open' : ''}`}>
|
||||
<div className={`v2-history-main${trendExpanded ? ' has-expanded-trend' : ''}`}>
|
||||
<Card className="v2-history-summary" bodyStyle={{ padding: 0 }}><div><small>结果行数</small><strong>{result?.total.toLocaleString('zh-CN') ?? '—'}</strong></div><div><small>车辆数</small><strong>{result?.summary.vehicleCount ?? '—'}</strong></div><div><small>数据源</small><strong className="is-source" title={summarySources}>{summarySources}</strong></div><div><small>查询耗时</small><strong>{result ? `${result.summary.queryDurationMs} ms` : '—'}</strong></div></Card>
|
||||
<Card className="v2-history-summary" bodyStyle={{ padding: 0 }}><div><small>结果行数</small><strong>{result?.total?.toLocaleString('zh-CN') ?? '—'}</strong></div><div><small>车辆数</small><strong>{resultSummary?.vehicleCount ?? '—'}</strong></div><div><small>数据源</small><strong className="is-source" title={summarySources}>{summarySources}</strong></div><div><small>查询耗时</small><strong>{resultSummary ? `${resultSummary.queryDurationMs} ms` : '—'}</strong></div></Card>
|
||||
<HistoryTrend response={seriesQuery.data} category={criteria.category} loading={seriesQuery.isFetching} error={seriesQuery.isError ? (seriesQuery.error instanceof Error ? seriesQuery.error.message : '未知错误') : undefined} hasMetrics={Boolean(seriesMetricKey)} expanded={trendExpanded} onToggle={() => setTrendExpanded((value) => !value)} />
|
||||
<Card className={`v2-history-table-card is-${density}`} bodyStyle={{ padding: 0 }}>
|
||||
<WorkspacePanelHeader title="数据明细" actions={<><Button theme="borderless" aria-label="列设置" aria-haspopup="dialog" aria-expanded={columnSettingsOpen} aria-controls="v2-history-column-settings" icon={<IconSetting />} onClick={() => setColumnSettingsOpen((value) => !value)}>列设置</Button>{exportAllowed ? <Button theme="borderless" aria-label="查看导出任务" aria-haspopup="dialog" aria-expanded={exportJobsOpen} aria-controls="v2-history-export-jobs" icon={<IconDownload />} onClick={() => setExportJobsOpen(true)}>导出任务</Button> : null}{!mobileLayout ? <Select aria-label="表格密度" value={density} onChange={(value) => setDensity(String(value) as typeof density)} optionList={[{ value: 'compact', label: '紧凑' }, { value: 'comfortable', label: '舒适' }]} /> : null}<Button theme="borderless" onClick={() => dataQuery.refetch()} aria-label="刷新历史数据" icon={<IconRefresh />} /></>} />
|
||||
<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
|
||||
? <div className="v2-history-mobile-list">{result?.rows.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><time>{row.deviceTime}</time><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>
|
||||
: <HistoryDataTable rows={result?.rows ?? []} metrics={visibleMetrics} selectedRowID={selectedRow?.id} onSelect={selectRow} />}
|
||||
{dataQuery.isPending && keywords.length ? <div className="v2-history-loading" role="status"><Spin size="middle" tip="正在加载当前筛选范围的历史数据…" /></div> : !result?.rows.length ? <Empty className="v2-history-empty" title={keywords.length ? '当前条件没有历史记录' : '等待查询历史数据'} description={keywords.length ? '调整车辆、时间或数据来源后重试。' : '输入车牌或 VIN,最多可同时查询 5 台车辆。'} /> : null}
|
||||
? <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><time>{row.deviceTime}</time><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>
|
||||
: <HistoryDataTable rows={resultRows} metrics={visibleMetrics} selectedRowID={selectedRow?.id} onSelect={selectRow} />}
|
||||
{dataQuery.isPending && keywords.length
|
||||
? <PanelLoading className="v2-history-loading" title="正在加载当前筛选范围的历史数据" description="按车辆、时间和协议整理可追溯证据。" />
|
||||
: !resultRows.length
|
||||
? <PanelEmpty className="v2-history-empty" title={keywords.length ? '当前条件没有历史记录' : '等待查询历史数据'} description={keywords.length ? '调整车辆、时间或数据来源后重试。' : '输入车牌或 VIN,最多可同时查询 5 台车辆。'} />
|
||||
: null}
|
||||
</div>
|
||||
<footer><TablePagination page={page} totalPages={totalPages} info={`共 ${(result?.total ?? 0).toLocaleString('zh-CN')} 条`} onPageChange={(next) => setOffset((next - 1) * limit)} pageSize={mobileLayout ? undefined : limit} onPageSizeChange={(next) => { setLimit(next); setOffset(0); }} pageSizeOptions={mobileLayout ? undefined : [{ value: 20, label: '20 条/页' }, { value: 50, label: '50 条/页' }, { value: 100, label: '100 条/页' }]} /></footer>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user