refactor(ui): unify semi mobile query workspaces
This commit is contained in:
@@ -240,6 +240,11 @@ test('uses one focused Semi bottom SideSheet for the complete mobile history que
|
|||||||
const dialog = await screen.findByRole('dialog', { name: '历史查询范围' });
|
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')).toHaveClass('semi-sidesheet-bottom');
|
||||||
expect(document.querySelector('.v2-history-filter-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(82dvh, 700px)' });
|
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('车牌 / VIN,多台用逗号分隔')).toBeInTheDocument();
|
||||||
expect(within(dialog).getByPlaceholderText('开始时间')).toBeInTheDocument();
|
expect(within(dialog).getByPlaceholderText('开始时间')).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: '重置条件' }));
|
fireEvent.click(within(dialog).getByRole('button', { name: '重置条件' }));
|
||||||
expect(screen.getByRole('dialog', { name: '历史查询范围' })).toBeInTheDocument();
|
expect(screen.getByRole('dialog', { name: '历史查询范围' })).toBeInTheDocument();
|
||||||
fireEvent.change(within(dialog).getByPlaceholderText('车牌 / VIN,多台用逗号分隔'), { target: { value: 'MOBILEVIN' } });
|
fireEvent.change(within(dialog).getByPlaceholderText('车牌 / VIN,多台用逗号分隔'), { target: { value: 'MOBILEVIN' } });
|
||||||
|
expect(querySummary).toHaveTextContent('车辆范围1 辆最多 5 台车辆');
|
||||||
fireEvent.click(within(dialog).getByRole('button', { name: '应用并查询' }));
|
fireEvent.click(within(dialog).getByRole('button', { name: '应用并查询' }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
|||||||
@@ -427,6 +427,11 @@ export default function HistoryPage() {
|
|||||||
setSelectedRowRef({ scope: dataScope, id: '' });
|
setSelectedRowRef({ scope: dataScope, id: '' });
|
||||||
}, [dataScope]);
|
}, [dataScope]);
|
||||||
const mobileFiltersOpen = mobileLayout && !filtersCollapsed;
|
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(() => [
|
const historyRangePresets = useMemo(() => [
|
||||||
{ key: 'today' as const, label: '今天', range: historyPresetWindow('today') },
|
{ key: 'today' as const, label: '今天', range: historyPresetWindow('today') },
|
||||||
{ key: 'yesterday' as const, label: '昨天', range: historyPresetWindow('yesterday') },
|
{ key: 'yesterday' as const, label: '昨天', range: historyPresetWindow('yesterday') },
|
||||||
@@ -537,6 +542,25 @@ export default function HistoryPage() {
|
|||||||
closeLabel="关闭历史查询范围"
|
closeLabel="关闭历史查询范围"
|
||||||
title="筛选历史数据"
|
title="筛选历史数据"
|
||||||
description="车辆、时间、数据类型与协议来源"
|
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)}
|
onCancel={() => setFiltersCollapsed(true)}
|
||||||
secondaryAction={{ label: '重置条件', onClick: resetDraft }}
|
secondaryAction={{ label: '重置条件', onClick: resetDraft }}
|
||||||
primaryAction={{ label: '应用并查询', disabled: !parseHistoryKeywords(draft.keywords).length, onClick: applyDraft }}
|
primaryAction={{ label: '应用并查询', disabled: !parseHistoryKeywords(draft.keywords).length, onClick: applyDraft }}
|
||||||
|
|||||||
@@ -284,9 +284,11 @@ test('keeps the mobile vehicle directory primary and applies search from a Semi
|
|||||||
const input = screen.getByRole('textbox', { name: '搜索车辆' });
|
const input = screen.getByRole('textbox', { name: '搜索车辆' });
|
||||||
expect(input).toBeVisible();
|
expect(input).toBeVisible();
|
||||||
expect(document.querySelector('.v2-vehicle-mobile-filter-sidesheet')).toBeInTheDocument();
|
expect(document.querySelector('.v2-vehicle-mobile-filter-sidesheet')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('list', { name: '车辆搜索摘要' })).toHaveTextContent('授权范围10 辆当前账号可见当前条件全部车辆浏览完整目录快速结果待输入最多展示 6 辆');
|
||||||
expect(screen.getAllByRole('button', { name: /打开 .* 车辆档案/ })).toHaveLength(8);
|
expect(screen.getAllByRole('button', { name: /打开 .* 车辆档案/ })).toHaveLength(8);
|
||||||
fireEvent.change(input, { target: { value: items[0].plate } });
|
fireEvent.change(input, { target: { value: items[0].plate } });
|
||||||
expect(await screen.findByRole('listbox')).toHaveAttribute('id', 'v2-vehicle-mobile-search-options');
|
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);
|
expect(screen.getAllByRole('button', { name: /打开 .* 车辆档案/ })).toHaveLength(8);
|
||||||
fireEvent.click(screen.getByRole('button', { name: '应用搜索' }));
|
fireEvent.click(screen.getByRole('button', { name: '应用搜索' }));
|
||||||
await waitFor(() => expect(screen.getByRole('button', { name: `修改车辆范围:${items[0].plate} · 10 辆匹配` })).toHaveAttribute('aria-expanded', 'false'));
|
await waitFor(() => expect(screen.getByRole('button', { name: `修改车辆范围:${items[0].plate} · 10 辆匹配` })).toHaveAttribute('aria-expanded', 'false'));
|
||||||
|
|||||||
@@ -201,12 +201,31 @@ export default function VehicleSearchWorkspace() {
|
|||||||
<MobileFilterSheet
|
<MobileFilterSheet
|
||||||
className="v2-vehicle-mobile-filter-sidesheet"
|
className="v2-vehicle-mobile-filter-sidesheet"
|
||||||
visible={mobileFiltersOpen}
|
visible={mobileFiltersOpen}
|
||||||
height="min(68dvh, 560px)"
|
height="min(72dvh, 610px)"
|
||||||
ariaLabel="车辆搜索"
|
ariaLabel="车辆搜索"
|
||||||
dialogId="v2-vehicle-mobile-filter-dialog"
|
dialogId="v2-vehicle-mobile-filter-dialog"
|
||||||
closeLabel="关闭车辆搜索"
|
closeLabel="关闭车辆搜索"
|
||||||
title="查找车辆"
|
title="查找车辆"
|
||||||
description="车牌优先,支持 VIN 与终端手机号"
|
description="车牌优先,支持 VIN 与终端手机号"
|
||||||
|
summaryItems={[
|
||||||
|
{
|
||||||
|
label: '授权范围',
|
||||||
|
value: candidates.data ? `${candidates.data.total.toLocaleString('zh-CN')} 辆` : '读取中',
|
||||||
|
detail: '当前账号可见',
|
||||||
|
tone: 'primary'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '当前条件',
|
||||||
|
value: draftKeyword.trim() || '全部车辆',
|
||||||
|
detail: draftKeyword.trim() ? '车牌 / VIN / 终端' : '浏览完整目录'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '快速结果',
|
||||||
|
value: deferredDraftKeyword ? mobileCandidates.isFetching ? '查找中' : `${mobileCandidates.data?.total ?? 0} 辆` : '待输入',
|
||||||
|
detail: '最多展示 6 辆',
|
||||||
|
tone: deferredDraftKeyword ? 'success' : 'neutral'
|
||||||
|
}
|
||||||
|
]}
|
||||||
onCancel={closeMobileFilters}
|
onCancel={closeMobileFilters}
|
||||||
secondaryAction={{ label: '重置范围', onClick: resetMobileDraft }}
|
secondaryAction={{ label: '重置范围', onClick: resetMobileDraft }}
|
||||||
primaryAction={{ label: '应用搜索', onClick: applyMobileFilters }}
|
primaryAction={{ label: '应用搜索', onClick: applyMobileFilters }}
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ test('renders one shared Semi filter sheet with consistent title, section and ac
|
|||||||
closeLabel="关闭测试筛选"
|
closeLabel="关闭测试筛选"
|
||||||
title="筛选车辆"
|
title="筛选车辆"
|
||||||
description="按车辆和状态缩小范围"
|
description="按车辆和状态缩小范围"
|
||||||
|
summaryItems={[
|
||||||
|
{ label: '车辆范围', value: '3 辆', detail: '当前已选', tone: 'primary' },
|
||||||
|
{ label: '数据来源', value: '全部', detail: '3 路协议' }
|
||||||
|
]}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
secondaryAction={{ label: '重置条件', onClick: onReset }}
|
secondaryAction={{ label: '重置条件', onClick: onReset }}
|
||||||
primaryAction={{ label: '应用并查询', onClick: onApply }}
|
primaryAction={{ label: '应用并查询', onClick: onApply }}
|
||||||
@@ -30,6 +34,7 @@ test('renders one shared Semi filter sheet with consistent title, section and ac
|
|||||||
expect(dialog.closest('.semi-sidesheet')).toHaveClass('v2-workspace-filter-sidesheet', 'v2-mobile-filter-sheet', 'test-filter-sheet');
|
expect(dialog.closest('.semi-sidesheet')).toHaveClass('v2-workspace-filter-sidesheet', 'v2-mobile-filter-sheet', 'test-filter-sheet');
|
||||||
expect(screen.getByText('按车辆和状态缩小范围')).toBeInTheDocument();
|
expect(screen.getByText('按车辆和状态缩小范围')).toBeInTheDocument();
|
||||||
expect(screen.getByText('筛选范围')).toBeInTheDocument();
|
expect(screen.getByText('筛选范围')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('list', { name: '测试筛选摘要' })).toHaveTextContent('车辆范围3 辆当前已选数据来源全部3 路协议');
|
||||||
expect(screen.getByText('筛选条件仅在应用后更新当前结果')).toBeInTheDocument();
|
expect(screen.getByText('筛选条件仅在应用后更新当前结果')).toBeInTheDocument();
|
||||||
expect(screen.getByRole('region', { name: '车辆条件' })).toHaveTextContent('支持车牌或 VIN');
|
expect(screen.getByRole('region', { name: '车辆条件' })).toHaveTextContent('支持车牌或 VIN');
|
||||||
fireEvent.click(screen.getByRole('button', { name: '重置条件' }));
|
fireEvent.click(screen.getByRole('button', { name: '重置条件' }));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IconFilter } from '@douyinfe/semi-icons';
|
import { IconFilter } from '@douyinfe/semi-icons';
|
||||||
import { type ReactNode } from 'react';
|
import { type ReactNode } from 'react';
|
||||||
import { WorkspaceSideSheet } from './WorkspaceSideSheet';
|
import { WorkspaceSideSheet, type WorkspaceSideSheetSummaryItem } from './WorkspaceSideSheet';
|
||||||
|
|
||||||
type MobileFilterSheetAction = {
|
type MobileFilterSheetAction = {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -18,6 +18,7 @@ type MobileFilterSheetProps = {
|
|||||||
closeLabel?: string;
|
closeLabel?: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
summaryItems?: WorkspaceSideSheetSummaryItem[];
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
secondaryAction?: MobileFilterSheetAction;
|
secondaryAction?: MobileFilterSheetAction;
|
||||||
primaryAction: MobileFilterSheetAction;
|
primaryAction: MobileFilterSheetAction;
|
||||||
@@ -33,6 +34,7 @@ export function MobileFilterSheet({
|
|||||||
closeLabel,
|
closeLabel,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
|
summaryItems = [],
|
||||||
onCancel,
|
onCancel,
|
||||||
secondaryAction,
|
secondaryAction,
|
||||||
primaryAction,
|
primaryAction,
|
||||||
@@ -53,6 +55,7 @@ export function MobileFilterSheet({
|
|||||||
description={description}
|
description={description}
|
||||||
icon={<IconFilter />}
|
icon={<IconFilter />}
|
||||||
badge="筛选范围"
|
badge="筛选范围"
|
||||||
|
summaryItems={summaryItems}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
footerNote="筛选条件仅在应用后更新当前结果"
|
footerNote="筛选条件仅在应用后更新当前结果"
|
||||||
secondaryActions={secondaryAction ? [secondaryAction] : []}
|
secondaryActions={secondaryAction ? [secondaryAction] : []}
|
||||||
|
|||||||
@@ -96,4 +96,59 @@
|
|||||||
- 首次视觉复核发现当前模块仍继承底栏的底部装饰线;已在更多功能作用域内移除,保留单一高亮语义。
|
- 首次视觉复核发现当前模块仍继承底栏的底部装饰线;已在更多功能作用域内移除,保留单一高亮语义。
|
||||||
- 同尺寸并排对比确认五个入口均可见,层级更清晰,未发现 P0、P1 或 P2 视觉与交互问题。
|
- 同尺寸并排对比确认五个入口均可见,层级更清晰,未发现 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
|
final result: passed
|
||||||
|
|||||||
Reference in New Issue
Block a user