From c358faf17d78059099b935d5ea4d9a2ab45b35b7 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 18 Jul 2026 23:07:12 +0800 Subject: [PATCH] feat: optimize mobile access filters --- .../apps/web/src/v2/pages/AccessPage.test.tsx | 21 ++- .../apps/web/src/v2/pages/AccessPage.tsx | 55 ++++++-- .../apps/web/src/v2/styles/workspace.css | 123 ++++++++++++++++++ 3 files changed, 187 insertions(+), 12 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.test.tsx b/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.test.tsx index e5388479..ccc695ea 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.test.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.test.tsx @@ -191,7 +191,7 @@ test('renders mobile access vehicles as selectable Semi cards', async () => { const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); const view = render(); - const action = await screen.findByRole('button', { name: '查看 粤A00001 接入详情' }); + let action = await screen.findByRole('button', { name: '查看 粤A00001 接入详情' }); expect(action).toHaveClass('semi-button', 'v2-access-mobile-action'); expect(action.closest('.semi-card')).toHaveClass('v2-access-mobile-card'); expect(view.container.querySelector('.v2-access-table-scroll-v3.is-mobile-scroll')).toHaveAttribute('tabindex', '0'); @@ -202,6 +202,25 @@ test('renders mobile access vehicles as selectable Semi cards', async () => { expect(screen.queryByLabelText('每页车辆数')).not.toBeInTheDocument(); expect(screen.getByRole('button', { name: '打开接入治理' })).toHaveTextContent('治理'); expect(screen.getByRole('button', { name: '打开接入治理' })).toHaveClass('v2-workspace-mobile-tool-button', 'is-muted'); + const filterTrigger = screen.getByRole('button', { name: /修改接入筛选/ }); + expect(filterTrigger).toHaveAttribute('aria-expanded', 'false'); + fireEvent.click(filterTrigger); + const filterDialog = await screen.findByRole('dialog', { name: '接入车辆筛选' }); + expect(document.querySelector('.v2-access-mobile-filter-sidesheet')).toHaveClass('semi-sidesheet-bottom'); + expect(document.querySelector('.v2-access-mobile-filter-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(76dvh, 640px)' }); + expect(within(filterDialog).getByRole('textbox', { name: '车辆' })).toBeInTheDocument(); + expect(within(filterDialog).getByRole('combobox', { name: '接入状态' })).toBeInTheDocument(); + expect(within(filterDialog).getByRole('combobox', { name: '真实协议' })).toBeInTheDocument(); + expect(within(filterDialog).getByRole('combobox', { name: '车辆品牌' })).toBeInTheDocument(); + fireEvent.click(within(filterDialog).getByRole('combobox', { name: '接入状态' })); + const attentionOption = (await screen.findAllByText('需关注')).find((element) => element.classList.contains('semi-select-option-text')); + expect(attentionOption).toBeInTheDocument(); + fireEvent.click(attentionOption!); + fireEvent.click(within(filterDialog).getByRole('button', { name: '应用并查询' })); + await waitFor(() => expect(mocks.accessVehicles).toHaveBeenLastCalledWith(expect.objectContaining({ connectionState: 'attention' }), expect.anything())); + expect(screen.getByRole('button', { name: /修改接入筛选:已启用 1 项/ })).toHaveAttribute('aria-expanded', 'false'); + expect(screen.getByRole('dialog', { name: '接入车辆筛选' })).toHaveClass('semi-sidesheet-animation-content_hide_bottom'); + action = await screen.findByRole('button', { name: '查看 粤A00001 接入详情' }); const toolsButton = screen.getByRole('button', { name: '打开接入管理工具' }); expect(toolsButton).toHaveTextContent('工具'); expect(toolsButton).toHaveClass('v2-workspace-mobile-tool-button', 'is-muted'); diff --git a/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx index 96df85cc..ab143291 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx @@ -8,6 +8,7 @@ import type { AccessProtocolStatus, AccessQuery, AccessSummary, AccessThresholdC import { accessIssueSummary, accessRowsToCSV, formatAccessTime, formatSeconds, updateProtocolThreshold } from '../domain/access'; import { InlineError, PanelEmpty, PanelLoading } from '../shared/AsyncState'; import { MetricActionButton } from '../shared/MetricActionButton'; +import { MobileFilterToggle } from '../shared/MobileFilterToggle'; import { TablePagination } from '../shared/TablePagination'; import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar'; import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel'; @@ -349,12 +350,17 @@ export default function AccessPage() { setOffset(0); }, [mobileLayout]); const rows = vehiclesQuery.data?.items ?? []; const selected = rows.find((row) => row.vin === selectedVIN); + const mobileFiltersOpen = mobileLayout && !filtersCollapsed; useSideSheetA11y(Boolean(selected), '.v2-access-detail-sidesheet', 'v2-access-detail', '车辆接入详情', '关闭车辆接入详情'); useSideSheetA11y(editable && governanceOpen, '.v2-access-governance-sidesheet', 'v2-access-governance', '接入治理配置', '关闭接入治理配置'); + useSideSheetA11y(mobileFiltersOpen, '.v2-access-mobile-filter-sidesheet', 'v2-access-mobile-filters', '接入车辆筛选', '关闭接入车辆筛选'); const syncURL = (filters: Filters) => { const next = new URLSearchParams(); Object.entries(filters).forEach(([key, value]) => { if (value) next.set(key, value); }); setSearchParams(next, { replace: true }); }; const apply = (next: Filters) => { setDraft(next); setCriteria(next); setOffset(0); setSelectedVIN(''); syncURL(next); }; - const submit = (event: FormEvent) => { event.preventDefault(); apply(draft); setFiltersCollapsed(true); }; + const applyDraft = () => { apply(draft); setFiltersCollapsed(true); }; + const resetFilters = () => { apply(EMPTY_FILTERS); setFiltersCollapsed(true); }; + const submit = (event: FormEvent) => { event.preventDefault(); applyDraft(); }; const page = Math.floor(offset / limit) + 1; const totalPages = Math.max(1, Math.ceil((vehiclesQuery.data?.total ?? 0) / limit)); const summary = summaryQuery.data; + const activeFilterCount = Object.values(criteria).filter(Boolean).length; const refresh = () => Promise.all([summaryQuery.refetch(), vehiclesQuery.refetch(), ...(editable ? [unresolvedQuery.refetch(), thresholdQuery.refetch()] : [])]); const scrollAccessTable = (event: KeyboardEvent) => { if (mobileLayout || event.currentTarget !== event.target || (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')) return; @@ -365,6 +371,12 @@ export default function AccessPage() { event.preventDefault(); tableScroller.scrollBy({ left: event.key === 'ArrowRight' ? 260 : -260, behavior: 'smooth' }); }; + const filterFields = <> + + + - -