feat: unify filter workbenches
This commit is contained in:
@@ -22,7 +22,6 @@ import { canAdminister } from '../auth/session';
|
||||
import { QUERY_MEMORY, queryScopeKey, retainPreviousPageWithinScope } from '../queryPolicy';
|
||||
import { downloadBlob } from '../domain/download';
|
||||
import { useMobileLayout } from '../hooks/useMobileLayout';
|
||||
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
|
||||
|
||||
const PROTOCOLS = ['GB32960', 'JT808', 'YUTONG_MQTT'] as const;
|
||||
const protocolThresholdMeta: Record<(typeof PROTOCOLS)[number], { label: string; description: string }> = {
|
||||
@@ -357,7 +356,6 @@ export default function AccessPage() {
|
||||
}, [mobileLayout]);
|
||||
const rows = vehiclesQuery.data?.items ?? []; const selected = rows.find((row) => row.vin === selectedVIN);
|
||||
const mobileFiltersOpen = mobileLayout && !filtersCollapsed;
|
||||
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 applyDraft = () => { apply(draft); setFiltersCollapsed(true); };
|
||||
@@ -416,6 +414,7 @@ export default function AccessPage() {
|
||||
title="接入筛选"
|
||||
summary={activeFilterCount ? `已启用 ${activeFilterCount} 项` : '全部主车辆'}
|
||||
expanded={mobileFiltersOpen}
|
||||
controls="v2-access-mobile-filters"
|
||||
expandedLabel="关闭"
|
||||
collapsedLabel="修改"
|
||||
onToggle={() => setFiltersCollapsed((value) => !value)}
|
||||
@@ -425,6 +424,8 @@ export default function AccessPage() {
|
||||
visible={mobileFiltersOpen}
|
||||
height="min(76dvh, 640px)"
|
||||
ariaLabel="接入车辆筛选"
|
||||
dialogId="v2-access-mobile-filters"
|
||||
closeLabel="关闭接入车辆筛选"
|
||||
title="筛选接入车辆"
|
||||
description="车辆、接入状态、真实协议与品牌"
|
||||
onCancel={() => setFiltersCollapsed(true)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IconAlarm, IconBell, IconChevronRight, IconClose, IconFilter, IconPlus, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
|
||||
import { Button, Card, CardGroup, Collapse, Descriptions, Empty, Input, Radio, Select, SideSheet, Spin, Table, Tag, TextArea, Timeline, Typography } from '@douyinfe/semi-ui';
|
||||
import { Button, Card, CardGroup, Collapse, Descriptions, Empty, Input, Radio, Select, Spin, Table, Tag, TextArea, Timeline, Typography } from '@douyinfe/semi-ui';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { FormEvent, memo, useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';
|
||||
import { Link, useSearchParams } from 'react-router-dom';
|
||||
@@ -23,7 +23,6 @@ import { usePlatformSession } from '../auth/AuthGate';
|
||||
import { canAdminister, canOperate } from '../auth/session';
|
||||
import { QUERY_MEMORY, queryScopeKey, retainPreviousPageWithinScope } from '../queryPolicy';
|
||||
import { useMobileLayout } from '../hooks/useMobileLayout';
|
||||
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
|
||||
|
||||
type Tab = 'events' | 'rules' | 'notifications';
|
||||
type Filters = { keyword: string; severity: string; status: string; ruleId: string; protocol: string; dateFrom: string; dateTo: string };
|
||||
@@ -196,8 +195,6 @@ function EventWorkspace({ filters, draft, setDraft, setFilters, rules, unread, e
|
||||
const selectEvent = useCallback((id: string) => setSelection({ scope: eventScope, id }), [eventScope]);
|
||||
const selectedEvent = detail.data ?? rows.find((row) => row.id === selectedID);
|
||||
const mobileFiltersOpen = mobileLayout && !filtersCollapsed;
|
||||
useSideSheetA11y(!mobileLayout && advancedOpen, '.v2-alert-filter-sidesheet', 'v2-alert-advanced-filters', '告警高级筛选', '关闭告警高级筛选');
|
||||
useSideSheetA11y(mobileFiltersOpen, '.v2-alert-mobile-filter-sidesheet', 'v2-alert-mobile-filters', '告警事件筛选', '关闭告警事件筛选');
|
||||
const applyDraft = () => {
|
||||
setFilters(draft);
|
||||
setOffset(0);
|
||||
@@ -218,6 +215,7 @@ function EventWorkspace({ filters, draft, setDraft, setFilters, rules, unread, e
|
||||
const sums = summary.data;
|
||||
const activeFilterCount = Object.values(filters).filter(Boolean).length;
|
||||
const advancedFilterCount = [filters.ruleId, filters.protocol, filters.dateFrom, filters.dateTo].filter(Boolean).length;
|
||||
const draftAdvancedFilterCount = [draft.ruleId, draft.protocol, draft.dateFrom, draft.dateTo].filter(Boolean).length;
|
||||
const allEventCount = Number(sums?.active ?? 0) + Number(sums?.recovered ?? 0) + Number(sums?.closed ?? 0) + Number(sums?.ignored ?? 0);
|
||||
const inspector = <EventInspector event={selectedEvent} note={note} acting={action.isPending} actionError={action.error instanceof Error ? action.error.message : undefined} editable={editable} onNote={setNote} onAction={(name) => { const event = detail.data; if (event) action.mutate({ name, event }); }} onClose={closeDetail} />;
|
||||
const coreFilterFields = <>
|
||||
@@ -249,6 +247,7 @@ function EventWorkspace({ filters, draft, setDraft, setFilters, rules, unread, e
|
||||
title="事件筛选"
|
||||
summary={activeFilterCount ? `已启用 ${activeFilterCount} 项` : '全部告警'}
|
||||
expanded={mobileFiltersOpen}
|
||||
controls="v2-alert-mobile-filters"
|
||||
expandedLabel="关闭"
|
||||
collapsedLabel="修改"
|
||||
onToggle={() => setFiltersCollapsed((value) => !value)}
|
||||
@@ -258,6 +257,8 @@ function EventWorkspace({ filters, draft, setDraft, setFilters, rules, unread, e
|
||||
visible={mobileFiltersOpen}
|
||||
height="min(82dvh, 690px)"
|
||||
ariaLabel="告警事件筛选"
|
||||
dialogId="v2-alert-mobile-filters"
|
||||
closeLabel="关闭告警事件筛选"
|
||||
title="筛选告警事件"
|
||||
description="车辆、状态、规则与时间范围"
|
||||
onCancel={() => setFiltersCollapsed(true)}
|
||||
@@ -328,18 +329,31 @@ function EventWorkspace({ filters, draft, setDraft, setFilters, rules, unread, e
|
||||
</Card>
|
||||
{!mobileLayout && selectedID ? inspector : null}
|
||||
</div>
|
||||
{!mobileLayout ? <SideSheet
|
||||
{!mobileLayout ? <WorkspaceSideSheet
|
||||
className="v2-alert-filter-sidesheet"
|
||||
variant="filter"
|
||||
visible={advancedOpen}
|
||||
aria-label="告警高级筛选"
|
||||
placement="right"
|
||||
ariaLabel="告警高级筛选"
|
||||
dialogId="v2-alert-advanced-filters"
|
||||
closeLabel="关闭告警高级筛选"
|
||||
width={430}
|
||||
title={<div className="v2-alert-sheet-title"><strong>更多筛选</strong><span>按规则、协议和触发时间收窄告警范围</span></div>}
|
||||
title="更多筛选"
|
||||
description="按规则、协议和触发时间收窄告警范围"
|
||||
icon={<IconFilter />}
|
||||
badge={draftAdvancedFilterCount ? `${draftAdvancedFilterCount} 项待应用` : '全部范围'}
|
||||
badgeColor={draftAdvancedFilterCount ? 'blue' : 'grey'}
|
||||
summaryItems={[
|
||||
{ label: '规则', value: rules.find((rule) => rule.id === draft.ruleId)?.name || '全部规则', tone: draft.ruleId ? 'primary' : 'neutral' },
|
||||
{ label: '协议', value: draft.protocol || '全部协议', tone: draft.protocol ? 'primary' : 'neutral' },
|
||||
{ label: '触发时间', value: draft.dateFrom || draft.dateTo ? '已限定' : '不限时间', detail: draft.dateFrom || draft.dateTo ? '按本地时间查询' : undefined, tone: draft.dateFrom || draft.dateTo ? 'primary' : 'neutral' }
|
||||
]}
|
||||
footerNote="高级条件仅在应用后更新事件列表"
|
||||
secondaryActions={[{ label: '清空高级条件', onClick: () => setDraft({ ...draft, ruleId: '', protocol: '', dateFrom: '', dateTo: '' }) }]}
|
||||
primaryAction={{ label: '应用筛选', onClick: () => { applyDraft(); setAdvancedOpen(false); } }}
|
||||
onCancel={() => setAdvancedOpen(false)}
|
||||
footer={<div className="v2-alert-sheet-footer"><Button theme="light" onClick={() => setDraft({ ...draft, ruleId: '', protocol: '', dateFrom: '', dateTo: '' })}>清空高级条件</Button><Button theme="solid" onClick={() => { applyDraft(); setAdvancedOpen(false); }}>应用筛选</Button></div>}
|
||||
>
|
||||
<div className="v2-alert-advanced-filter">{advancedFilterFields}</div>
|
||||
</SideSheet>
|
||||
</WorkspaceSideSheet>
|
||||
: null}
|
||||
<WorkspaceDetailSideSheet
|
||||
className="v2-alert-detail-sidesheet"
|
||||
|
||||
@@ -369,7 +369,6 @@ export default function HistoryPage() {
|
||||
}, [dataScope]);
|
||||
const mobileFiltersOpen = mobileLayout && !filtersCollapsed;
|
||||
useSideSheetA11y(exportAllowed && exportJobsOpen, '.v2-history-export-sidesheet', 'v2-history-export-jobs', '历史数据导出任务', '关闭历史数据导出任务');
|
||||
useSideSheetA11y(mobileFiltersOpen, '.v2-history-filter-sidesheet', 'v2-history-filter-sheet', '历史查询范围', '关闭历史查询范围');
|
||||
|
||||
const applyDraft = () => {
|
||||
const parsed = parseHistoryKeywords(draft.keywords);
|
||||
@@ -453,6 +452,7 @@ export default function HistoryPage() {
|
||||
title="查询范围"
|
||||
summary={keywords.length ? `${keywords.length} 辆 · ${criteria.category === 'location' ? '位置数据' : criteria.category === 'raw' ? '原始报文' : '日里程'}` : '请选择车辆'}
|
||||
expanded={mobileFiltersOpen}
|
||||
controls="v2-history-filter-sheet"
|
||||
expandedLabel="关闭"
|
||||
collapsedLabel="修改"
|
||||
onToggle={() => setFiltersCollapsed((value) => !value)}
|
||||
@@ -462,6 +462,8 @@ export default function HistoryPage() {
|
||||
visible={mobileFiltersOpen}
|
||||
height="min(82dvh, 700px)"
|
||||
ariaLabel="历史查询范围"
|
||||
dialogId="v2-history-filter-sheet"
|
||||
closeLabel="关闭历史查询范围"
|
||||
title="筛选历史数据"
|
||||
description="车辆、时间、数据类型与协议来源"
|
||||
onCancel={() => setFiltersCollapsed(true)}
|
||||
|
||||
@@ -423,6 +423,7 @@ test('allows provider maintenance but keeps canonical source policy read only',
|
||||
const filterDialog = await screen.findByRole('dialog', { name: '诊断车辆筛选' });
|
||||
expect(document.querySelector('.v2-source-mobile-filter-sidesheet')).toHaveClass('semi-sidesheet-bottom');
|
||||
expect(document.querySelector('.v2-source-mobile-filter-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(78dvh, 660px)' });
|
||||
expect(within(filterDialog).getByText('输入车牌或 VIN 开始查找')).toBeInTheDocument();
|
||||
fireEvent.change(within(filterDialog).getByLabelText('按车牌或 VIN 搜索诊断车辆'), { target: { value: '沪A' } });
|
||||
const candidateButton = await waitFor(() => {
|
||||
const button = filterDialog.querySelector<HTMLButtonElement>('.v2-source-candidates button');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IconRefresh, IconSearch, IconTickCircle } from '@douyinfe/semi-icons';
|
||||
import { Button, Card, CardGroup, Checkbox, Descriptions, Empty, Input, Progress, SideSheet, Spin, Table, Tag, Typography } from '@douyinfe/semi-ui';
|
||||
import { Button, Card, CardGroup, Checkbox, Descriptions, Empty, Input, Progress, Spin, Table, Tag, Typography } from '@douyinfe/semi-ui';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { FormEvent, useDeferredValue, useEffect, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
@@ -7,6 +7,7 @@ import { api } from '../../api/client';
|
||||
import type { VehicleCoverageRow, VehicleLocationSourceEvidence, VehicleSourceDiagnostic } from '../../api/types';
|
||||
import { InlineError } from '../shared/AsyncState';
|
||||
import { MobileFilterToggle } from '../shared/MobileFilterToggle';
|
||||
import { MobileFilterSheet, MobileFilterSheetSection } from '../shared/MobileFilterSheet';
|
||||
import { PlatformTime } from '../shared/PlatformTime';
|
||||
import { SegmentedTabs } from '../shared/SegmentedTabs';
|
||||
import { VehicleCandidateList } from '../shared/VehicleCandidateList';
|
||||
@@ -16,7 +17,6 @@ import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
|
||||
import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet';
|
||||
import { LIVE_QUERY_POLICY, QUERY_MEMORY } from '../queryPolicy';
|
||||
import { useMobileLayout } from '../hooks/useMobileLayout';
|
||||
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
|
||||
import ReconciliationCenter from './ReconciliationCenter';
|
||||
|
||||
type OperationsWorkspace = 'reconciliation' | 'diagnostic' | 'health';
|
||||
@@ -296,7 +296,6 @@ function SourceDiagnosticWorkspace() {
|
||||
if (selectedLabel) setKeyword(selectedLabel);
|
||||
setFiltersCollapsed(true);
|
||||
};
|
||||
useSideSheetA11y(mobileFiltersOpen, '.v2-source-mobile-filter-sidesheet', 'v2-source-mobile-filter-sheet', '诊断车辆筛选', '关闭诊断车辆筛选');
|
||||
const filterStatus = selectedLabel
|
||||
? `已选 ${selectedLabel}`
|
||||
: deferredKeyword
|
||||
@@ -334,28 +333,35 @@ function SourceDiagnosticWorkspace() {
|
||||
title="诊断车辆"
|
||||
summary={selectedLabel ? `已选 ${selectedLabel}` : '尚未选择车辆'}
|
||||
expanded={mobileFiltersOpen}
|
||||
controls="v2-source-mobile-filter-sheet"
|
||||
expandedLabel="关闭"
|
||||
collapsedLabel="修改"
|
||||
onToggle={() => mobileFiltersOpen ? closeMobileFilters() : setFiltersCollapsed(false)}
|
||||
/>
|
||||
<SideSheet
|
||||
<MobileFilterSheet
|
||||
className="v2-source-mobile-filter-sidesheet"
|
||||
visible={mobileFiltersOpen}
|
||||
placement="bottom"
|
||||
height="min(78dvh, 660px)"
|
||||
aria-label="诊断车辆筛选"
|
||||
title={<div className="v2-source-mobile-filter-title"><strong>选择诊断车辆</strong><span>按车牌或 VIN 搜索已绑定车辆</span></div>}
|
||||
ariaLabel="诊断车辆筛选"
|
||||
dialogId="v2-source-mobile-filter-sheet"
|
||||
closeLabel="关闭诊断车辆筛选"
|
||||
title="选择诊断车辆"
|
||||
description="按车牌或 VIN 搜索已绑定车辆"
|
||||
onCancel={closeMobileFilters}
|
||||
footer={<div className="v2-source-mobile-filter-footer"><Button theme="light" disabled={!selected && !keyword} onClick={clearSelection}>清除选择</Button><Button theme="solid" disabled={!browsingCandidates || !candidates.data?.items.length} onClick={chooseFirst}>诊断首个结果</Button></div>}
|
||||
secondaryAction={{ label: '清除选择', disabled: !selected && !keyword, onClick: clearSelection }}
|
||||
primaryAction={{ label: '诊断首个结果', disabled: !browsingCandidates || !candidates.data?.items.length, onClick: chooseFirst }}
|
||||
>
|
||||
<form className="v2-source-mobile-filter-form" onSubmit={submit}>
|
||||
<section className="v2-source-mobile-filter-section" aria-label="诊断车辆搜索">
|
||||
<header><strong>车辆范围</strong><span>{selectedLabel ? `当前诊断 ${selectedLabel},选择其他车辆后再切换` : '每次只读取一辆车的来源证据,不扫描整车队'}</span></header>
|
||||
<MobileFilterSheetSection className="v2-source-mobile-filter-section" ariaLabel="诊断车辆搜索" title="车辆范围" description={selectedLabel ? `当前诊断 ${selectedLabel},选择其他车辆后再切换` : '每次只读取一辆车的来源证据,不扫描整车队'}>
|
||||
{sourceSearchInput}
|
||||
</section>
|
||||
{sourceCandidateList}
|
||||
</MobileFilterSheetSection>
|
||||
{sourceCandidateList ?? <div className="v2-source-mobile-filter-empty" role="status">
|
||||
<i aria-hidden="true"><IconSearch /></i>
|
||||
<strong>{selectedLabel ? '输入新关键词切换车辆' : '输入车牌或 VIN 开始查找'}</strong>
|
||||
<small>{selectedLabel ? `当前保留 ${selectedLabel},关闭筛选不会改变诊断结果` : '候选结果会同时展示车牌、VIN 与已接协议'}</small>
|
||||
</div>}
|
||||
</form>
|
||||
</SideSheet>
|
||||
</MobileFilterSheet>
|
||||
</div> : <WorkspaceFilterPanel
|
||||
className="v2-source-filter-panel"
|
||||
title="诊断车辆"
|
||||
|
||||
@@ -22,7 +22,6 @@ import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet';
|
||||
import { QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy';
|
||||
import { monitorReturnFromParams, preserveMonitorReturn } from '../routing/monitorContext';
|
||||
import { useMobileLayout } from '../hooks/useMobileLayout';
|
||||
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
|
||||
|
||||
const DAY = 86_400_000;
|
||||
const DETAIL_LIMIT = 10_000;
|
||||
@@ -386,7 +385,6 @@ export default function StatisticsPage() {
|
||||
const pageRef = useRef<HTMLDivElement>(null);
|
||||
const tableScrollRef = useRef<HTMLDivElement>(null);
|
||||
const mountedRef = useRef(true);
|
||||
useSideSheetA11y(mobileFiltersOpen, '.v2-mileage-filter-sidesheet', 'v2-mileage-filter-panel', '里程范围筛选', '关闭里程范围筛选');
|
||||
useEffect(() => {
|
||||
mountedRef.current = true;
|
||||
return () => {
|
||||
@@ -604,6 +602,7 @@ export default function StatisticsPage() {
|
||||
title="里程范围"
|
||||
summary={rangeSummary}
|
||||
expanded={mobileFiltersOpen}
|
||||
controls="v2-mileage-filter-panel"
|
||||
expandedLabel="关闭"
|
||||
collapsedLabel="修改"
|
||||
onToggle={toggleFilters}
|
||||
@@ -614,6 +613,8 @@ export default function StatisticsPage() {
|
||||
visible={mobileFiltersOpen}
|
||||
height="min(82dvh, 690px)"
|
||||
ariaLabel="里程范围筛选"
|
||||
dialogId="v2-mileage-filter-panel"
|
||||
closeLabel="关闭里程范围筛选"
|
||||
title="里程范围"
|
||||
description="选择车辆、自然日与数据源"
|
||||
onCancel={closeMobileFilters}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { FormEvent, useDeferredValue, useEffect, useMemo, useState } from 'react
|
||||
import { api } from '../../api/client';
|
||||
import type { AdminUser, CustomerUserInput, CustomerVehicleGrantInput } from '../../api/types';
|
||||
import { useMobileLayout } from '../hooks/useMobileLayout';
|
||||
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
|
||||
import { MobileFilterToggle } from '../shared/MobileFilterToggle';
|
||||
import { MobileFilterSheet, MobileFilterSheetSection } from '../shared/MobileFilterSheet';
|
||||
import { TablePagination } from '../shared/TablePagination';
|
||||
@@ -160,7 +159,6 @@ export default function UsersPage() {
|
||||
const vehicleComposerVisible = !mobileLayout || creating || draft.vehicleGrants.length === 0 || vehicleComposerOpen;
|
||||
const hasUnsavedChanges = useMemo(() => draftSignature(draft) !== draftSignature(baselineDraft), [baselineDraft, draft]);
|
||||
const draftAccessState = customerAccessState(draft.status, draft.menuKeys.length, draft.vehicleGrants.length);
|
||||
useSideSheetA11y(mobileFiltersOpen, '.v2-user-mobile-filter-sidesheet', 'v2-user-mobile-filters', '客户账号筛选', '关闭客户账号筛选');
|
||||
|
||||
useEffect(() => {
|
||||
if (!creating && selected) {
|
||||
@@ -387,6 +385,7 @@ export default function UsersPage() {
|
||||
title="账号范围"
|
||||
summary={`${customerScopeLabel(customerStatus)}${customerKeyword.trim() ? ` · ${customerKeyword.trim()}` : ''}`}
|
||||
expanded={mobileFiltersOpen}
|
||||
controls="v2-user-mobile-filters"
|
||||
expandedLabel="关闭"
|
||||
collapsedLabel="修改"
|
||||
onToggle={mobileFiltersOpen ? closeMobileFilters : openMobileFilters}
|
||||
@@ -396,6 +395,8 @@ export default function UsersPage() {
|
||||
visible={mobileFiltersOpen}
|
||||
height="min(62dvh, 480px)"
|
||||
ariaLabel="客户账号筛选"
|
||||
dialogId="v2-user-mobile-filters"
|
||||
closeLabel="关闭客户账号筛选"
|
||||
title="筛选客户账号"
|
||||
description="按账号信息与权限状态缩小客户范围"
|
||||
onCancel={closeMobileFilters}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { usePlatformSession } from '../auth/AuthGate';
|
||||
import { canAdminister } from '../auth/session';
|
||||
import { formatZhNumber } from '../domain/formatters';
|
||||
import { useMobileLayout } from '../hooks/useMobileLayout';
|
||||
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
|
||||
import { QUERY_MEMORY, queryScopeKey, retainPreviousPageWithinScope } from '../queryPolicy';
|
||||
import { MobileFilterToggle } from '../shared/MobileFilterToggle';
|
||||
import { MobileFilterSheet, MobileFilterSheetSection } from '../shared/MobileFilterSheet';
|
||||
@@ -99,7 +98,6 @@ export default function VehicleSearchWorkspace() {
|
||||
setFiltersCollapsed(mobileLayout);
|
||||
setCandidatesOpen(false);
|
||||
}, [mobileLayout]);
|
||||
useSideSheetA11y(mobileFiltersOpen, '.v2-vehicle-mobile-filter-sidesheet', 'v2-vehicle-mobile-filter-dialog', '车辆搜索', '关闭车辆搜索');
|
||||
const openCandidates = () => {
|
||||
window.clearTimeout(closeTimerRef.current);
|
||||
if (!mobileLayout) setCandidatesOpen(Boolean(keyword.trim()));
|
||||
@@ -168,6 +166,7 @@ export default function VehicleSearchWorkspace() {
|
||||
title="车辆范围"
|
||||
summary={mobileFilterSummary}
|
||||
expanded={mobileFiltersOpen}
|
||||
controls="v2-vehicle-mobile-filter-dialog"
|
||||
expandedLabel="关闭"
|
||||
collapsedLabel="修改"
|
||||
onToggle={mobileFiltersOpen ? closeMobileFilters : openMobileFilters}
|
||||
@@ -177,6 +176,8 @@ export default function VehicleSearchWorkspace() {
|
||||
visible={mobileFiltersOpen}
|
||||
height="min(68dvh, 560px)"
|
||||
ariaLabel="车辆搜索"
|
||||
dialogId="v2-vehicle-mobile-filter-dialog"
|
||||
closeLabel="关闭车辆搜索"
|
||||
title="查找车辆"
|
||||
description="车牌优先,支持 VIN 与终端手机号"
|
||||
onCancel={closeMobileFilters}
|
||||
|
||||
Reference in New Issue
Block a user