From 2f195e725a5f8b3582185fd50c9b474e7bd66c69 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 19 Jul 2026 21:51:03 +0800 Subject: [PATCH] refactor(ui): unify semi mobile query workspaces --- .../web/src/v2/pages/HistoryPage.test.tsx | 6 ++ .../apps/web/src/v2/pages/HistoryPage.tsx | 24 ++++++++ .../web/src/v2/pages/VehiclePage.test.tsx | 2 + .../src/v2/pages/VehicleSearchWorkspace.tsx | 21 ++++++- .../src/v2/shared/MobileFilterSheet.test.tsx | 5 ++ .../web/src/v2/shared/MobileFilterSheet.tsx | 5 +- vehicle-data-platform/design-qa.md | 55 +++++++++++++++++++ 7 files changed, 116 insertions(+), 2 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.test.tsx b/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.test.tsx index 36acc507..54c43719 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.test.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.test.tsx @@ -240,6 +240,11 @@ test('uses one focused Semi bottom SideSheet for the complete mobile history que const dialog = await screen.findByRole('dialog', { name: '历史查询范围' }); expect(document.querySelector('.v2-history-filter-sidesheet')).toHaveClass('semi-sidesheet-bottom'); expect(document.querySelector('.v2-history-filter-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(82dvh, 700px)' }); + const querySummary = within(dialog).getByRole('list', { name: '历史查询范围摘要' }); + expect(querySummary).toHaveTextContent('车辆范围待选择最多 5 台车辆'); + expect(querySummary).toHaveTextContent('时间范围'); + expect(querySummary).toHaveTextContent('上海时区'); + expect(querySummary).toHaveTextContent('数据范围位置数据全部来源'); expect(within(dialog).getByPlaceholderText('车牌 / VIN,多台用逗号分隔')).toBeInTheDocument(); expect(within(dialog).getByPlaceholderText('开始时间')).toBeInTheDocument(); expect(within(dialog).getByPlaceholderText('结束时间')).toBeInTheDocument(); @@ -249,6 +254,7 @@ test('uses one focused Semi bottom SideSheet for the complete mobile history que fireEvent.click(within(dialog).getByRole('button', { name: '重置条件' })); expect(screen.getByRole('dialog', { name: '历史查询范围' })).toBeInTheDocument(); fireEvent.change(within(dialog).getByPlaceholderText('车牌 / VIN,多台用逗号分隔'), { target: { value: 'MOBILEVIN' } }); + expect(querySummary).toHaveTextContent('车辆范围1 辆最多 5 台车辆'); fireEvent.click(within(dialog).getByRole('button', { name: '应用并查询' })); await waitFor(() => { diff --git a/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx index 1c12eddd..31935d36 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/HistoryPage.tsx @@ -427,6 +427,11 @@ export default function HistoryPage() { setSelectedRowRef({ scope: dataScope, id: '' }); }, [dataScope]); const mobileFiltersOpen = mobileLayout && !filtersCollapsed; + const draftVehicleCount = parseHistoryKeywords(draft.keywords).length; + const draftCategoryLabel = catalogQuery.data?.categories.find((item) => item.key === draft.category)?.label + ?? (draft.category === 'raw' ? '原始报文' : draft.category === 'mileage' ? '日里程' : '位置数据'); + const draftSourceLabel = draft.protocol || '全部来源'; + const draftWindowLabel = `${draft.dateFrom.slice(5, 10)}–${draft.dateTo.slice(5, 10)}`; const historyRangePresets = useMemo(() => [ { key: 'today' as const, label: '今天', range: historyPresetWindow('today') }, { key: 'yesterday' as const, label: '昨天', range: historyPresetWindow('yesterday') }, @@ -537,6 +542,25 @@ export default function HistoryPage() { closeLabel="关闭历史查询范围" title="筛选历史数据" description="车辆、时间、数据类型与协议来源" + summaryItems={[ + { + label: '车辆范围', + value: draftVehicleCount ? `${draftVehicleCount} 辆` : '待选择', + detail: '最多 5 台车辆', + tone: 'primary' + }, + { + label: '时间范围', + value: draftWindowLabel, + detail: '上海时区' + }, + { + label: '数据范围', + value: draftCategoryLabel, + detail: draftSourceLabel, + tone: draft.protocol ? 'success' : 'neutral' + } + ]} onCancel={() => setFiltersCollapsed(true)} secondaryAction={{ label: '重置条件', onClick: resetDraft }} primaryAction={{ label: '应用并查询', disabled: !parseHistoryKeywords(draft.keywords).length, onClick: applyDraft }} diff --git a/vehicle-data-platform/apps/web/src/v2/pages/VehiclePage.test.tsx b/vehicle-data-platform/apps/web/src/v2/pages/VehiclePage.test.tsx index b050b6e0..66682ad4 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/VehiclePage.test.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/VehiclePage.test.tsx @@ -284,9 +284,11 @@ test('keeps the mobile vehicle directory primary and applies search from a Semi const input = screen.getByRole('textbox', { name: '搜索车辆' }); expect(input).toBeVisible(); expect(document.querySelector('.v2-vehicle-mobile-filter-sidesheet')).toBeInTheDocument(); + expect(screen.getByRole('list', { name: '车辆搜索摘要' })).toHaveTextContent('授权范围10 辆当前账号可见当前条件全部车辆浏览完整目录快速结果待输入最多展示 6 辆'); expect(screen.getAllByRole('button', { name: /打开 .* 车辆档案/ })).toHaveLength(8); fireEvent.change(input, { target: { value: items[0].plate } }); expect(await screen.findByRole('listbox')).toHaveAttribute('id', 'v2-vehicle-mobile-search-options'); + expect(screen.getByRole('list', { name: '车辆搜索摘要' })).toHaveTextContent(`当前条件${items[0].plate}车牌 / VIN / 终端`); expect(screen.getAllByRole('button', { name: /打开 .* 车辆档案/ })).toHaveLength(8); fireEvent.click(screen.getByRole('button', { name: '应用搜索' })); await waitFor(() => expect(screen.getByRole('button', { name: `修改车辆范围:${items[0].plate} · 10 辆匹配` })).toHaveAttribute('aria-expanded', 'false')); diff --git a/vehicle-data-platform/apps/web/src/v2/pages/VehicleSearchWorkspace.tsx b/vehicle-data-platform/apps/web/src/v2/pages/VehicleSearchWorkspace.tsx index 9554356c..90e4232b 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/VehicleSearchWorkspace.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/VehicleSearchWorkspace.tsx @@ -201,12 +201,31 @@ export default function VehicleSearchWorkspace() { void; secondaryAction?: MobileFilterSheetAction; primaryAction: MobileFilterSheetAction; @@ -33,6 +34,7 @@ export function MobileFilterSheet({ closeLabel, title, description, + summaryItems = [], onCancel, secondaryAction, primaryAction, @@ -53,6 +55,7 @@ export function MobileFilterSheet({ description={description} icon={} badge="筛选范围" + summaryItems={summaryItems} onCancel={onCancel} footerNote="筛选条件仅在应用后更新当前结果" secondaryActions={secondaryAction ? [secondaryAction] : []} diff --git a/vehicle-data-platform/design-qa.md b/vehicle-data-platform/design-qa.md index b3d8aae8..4ad0c6c2 100644 --- a/vehicle-data-platform/design-qa.md +++ b/vehicle-data-platform/design-qa.md @@ -96,4 +96,59 @@ - 首次视觉复核发现当前模块仍继承底栏的底部装饰线;已在更多功能作用域内移除,保留单一高亮语义。 - 同尺寸并排对比确认五个入口均可见,层级更清晰,未发现 P0、P1 或 P2 视觉与交互问题。 +## 查询工作台 Semi UI Design QA + +- source visual truth: ECS 生产端车辆查询、轨迹回放、历史数据 +- source screenshots: + - vehicle page: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-query-workspaces/before-vehicles-mobile.png` + - vehicle filter: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-query-workspaces/before-vehicle-filter-mobile.png` + - track page: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-query-workspaces/before-tracks-mobile.png` + - track workbench: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-query-workspaces/before-track-filter-mobile.png` + - history page: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-query-workspaces/before-history-mobile.png` + - history filter: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-query-workspaces/before-history-filter-mobile.png` +- implementation screenshots: + - vehicle filter: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-query-workspaces/after-vehicle-filter-mobile-settled.png` + - history filter: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-query-workspaces/after-history-filter-mobile.png` + - history draft state: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-query-workspaces/after-history-filter-draft-mobile.png` + - desktop history: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-query-workspaces/after-history-desktop.png` +- comparison evidence: + - vehicle mobile: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-query-workspaces/compare-vehicle-filter-mobile.png` + - history mobile: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-query-workspaces/compare-history-filter-mobile.png` + - history desktop: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-query-workspaces/compare-history-desktop.png` +- viewport and state: + - mobile `390 × 844` + - desktop `1440 × 900` + - 管理员权限、未提交查询的初始状态 + +### Fidelity Ledger + +| Surface | Required fidelity | Implementation evidence | Result | +| --- | --- | --- | --- | +| Shared filter anatomy | 车辆与历史筛选应与轨迹查询使用同一标题、摘要、内容和确认边界。 | `MobileFilterSheet` 新增共享 `summaryItems`,继续复用 `WorkspaceSideSheet`。 | Improved. | +| Draft visibility | 用户修改条件时必须知道尚未应用的车辆、时间和数据范围。 | 摘要直接读取草稿状态;输入 `沪A01559F` 后车辆范围立即由“待选择”更新为“1 辆”。 | Passed. | +| Vehicle discovery | 查车面板必须同时说明账号授权范围、当前关键词和快速结果状态。 | 生产数据下显示 `1,024 辆`、`全部车辆`、`待输入`,输入后同步更新当前条件和匹配状态。 | Passed. | +| History scope | 历史查询必须同时呈现车辆上限、日期区间、数据类型和协议来源。 | 三段摘要对应车辆范围、时间范围和数据范围,并保留上海时区说明。 | Passed. | +| Track consistency | 轨迹查询现有行程摘要和查询状态不能被新共享组件破坏。 | 轨迹仍使用同一个 `WorkspaceSideSheet` 摘要模型,页面与查询明细交互未修改。 | Preserved. | +| Desktop stability | 移动端工作台迁移不得改变桌面查询布局和密度。 | `1440 × 900` 同状态前后截图一致,桌面继续使用 `WorkspaceFilterPanel`。 | Preserved. | +| Action boundary | 草稿不应在关闭弹层时意外应用。 | 关闭、重置、应用三类动作保持独立,原测试继续覆盖草稿回滚和显式提交。 | Preserved. | + +### Interaction And Accessibility Checks + +- 摘要区域使用命名列表,并继承“车辆搜索摘要”“历史查询范围摘要”等可访问名称; +- 车辆筛选输入后,摘要与候选列表同时更新; +- 历史筛选输入后,车辆数量实时更新,应用按钮恢复可用; +- 筛选弹层关闭、重置与应用按钮保持完整可见; +- `390 × 844` 下内容由共享滚动区承载,无横向溢出; +- `1440 × 900` 桌面历史查询前后布局一致; +- 轨迹回放原有摘要、地图、播放轴与查询明细未发生行为回退。 + +### Comparison History + +#### Pass 1 — fixed + +- 原车辆和历史筛选只有标题与表单,用户需要进入字段后才能理解当前授权范围和草稿状态;轨迹查询已经具备摘要层,三页层级不统一。 +- 当前将摘要能力下沉到 `MobileFilterSheet`,车辆和历史页通过真实查询状态填充,不复制新的弹层组件。 +- 首次截图在 SideSheet 进入动画期间截到不完整底栏;等待动画稳定后复核,重置与应用按钮完整可见。 +- 同视口对比确认新增摘要没有压缩关键输入、日期范围和来源选择,未发现 P0、P1 或 P2 问题。 + final result: passed