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}
|
||||
|
||||
@@ -189,7 +189,8 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources.AlertsPage).toContain('<Card className={`v2-alert-inspector');
|
||||
expect(corePageSources.AlertsPage).toContain('<WorkspaceCommandBar');
|
||||
expect(corePageSources.AlertsPage).toContain('className="v2-alert-navigation v2-alert-command-bar"');
|
||||
expect(corePageSources.AlertsPage).toContain('<SideSheet\n className="v2-alert-filter-sidesheet"');
|
||||
expect(corePageSources.AlertsPage).toContain('<WorkspaceSideSheet\n className="v2-alert-filter-sidesheet"');
|
||||
expect(corePageSources.AlertsPage).toContain('variant="filter"');
|
||||
expect(corePageSources.AlertsPage).toContain('<WorkspaceDetailSideSheet\n className="v2-alert-detail-sidesheet"');
|
||||
expect(corePageSources.AlertsPage).toContain('<WorkspaceSideSheet\n className="v2-alert-rule-editor-sidesheet"');
|
||||
expect(corePageSources.AlertsPage).toContain('variant="editor"');
|
||||
@@ -339,7 +340,9 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources.OperationsPage).toContain('<Card className="v2-source-audit"');
|
||||
expect(corePageSources.OperationsPage).toContain('className="v2-source-table"');
|
||||
expect(corePageSources.OperationsPage).toContain('<Table');
|
||||
expect(corePageSources.OperationsPage).toContain('<SideSheet');
|
||||
expect(corePageSources.OperationsPage).toContain('<MobileFilterSheet');
|
||||
expect(corePageSources.OperationsPage).not.toContain('<SideSheet');
|
||||
expect(corePageSources.OperationsPage).toContain('className="v2-source-mobile-filter-empty"');
|
||||
expect(corePageSources.OperationsPage).toContain('<WorkspaceSideSheet');
|
||||
expect(corePageSources.OperationsPage).toContain('v2-source-policy-sidesheet');
|
||||
expect(corePageSources.OperationsPage).toContain('className={`v2-source-mobile-card');
|
||||
@@ -412,7 +415,10 @@ describe('V2 production entry', () => {
|
||||
expect(corePageSources.StatisticsPage).toContain('<MobileFilterToggle');
|
||||
expect(corePageSources.StatisticsPage).toContain('<MobileFilterSheet');
|
||||
expect(mobileFilterSheetSource).toContain('placement="bottom"');
|
||||
for (const name of ['AccessPage', 'AlertsPage', 'HistoryPage', 'StatisticsPage', 'UsersPage']) {
|
||||
expect(mobileFilterSheetSource).toContain('<WorkspaceSideSheet');
|
||||
expect(mobileFilterSheetSource).toContain('variant="filter"');
|
||||
expect(mobileFilterSheetSource).not.toContain('<SideSheet');
|
||||
for (const name of ['AccessPage', 'AlertsPage', 'HistoryPage', 'OperationsPage', 'StatisticsPage', 'UsersPage']) {
|
||||
expect(corePageSources[name]).toContain("from '../shared/MobileFilterSheet'");
|
||||
expect(corePageSources[name]).toContain('<MobileFilterSheet');
|
||||
}
|
||||
@@ -424,7 +430,8 @@ describe('V2 production entry', () => {
|
||||
expect(workspaceSideSheetSource).toContain("from '@douyinfe/semi-ui'");
|
||||
expect(workspaceSideSheetSource).toContain('<SideSheet');
|
||||
expect(workspaceSideSheetSource).toContain('className={sheetClassName}');
|
||||
expect(workspaceStyles).toContain('.v2-workspace-config-sidesheet .semi-sidesheet-inner {');
|
||||
expect(workspaceStyles).toContain(':is(.v2-workspace-config-sidesheet, .v2-workspace-filter-sidesheet) .semi-sidesheet-inner {');
|
||||
expect(workspaceStyles).toContain('.v2-workspace-filter-sidesheet .v2-workspace-config-title-icon {');
|
||||
expect(workspaceStyles).toContain('.v2-workspace-config-summary {');
|
||||
expect(workspaceStyles).toContain('.v2-workspace-config-footer {');
|
||||
expect(vehicleSearchSource).toContain('<MobileFilterSheet');
|
||||
|
||||
@@ -12,6 +12,8 @@ test('renders one shared Semi filter sheet with consistent title, section and ac
|
||||
className="test-filter-sheet"
|
||||
visible
|
||||
ariaLabel="测试筛选"
|
||||
dialogId="test-filter-dialog"
|
||||
closeLabel="关闭测试筛选"
|
||||
title="筛选车辆"
|
||||
description="按车辆和状态缩小范围"
|
||||
onCancel={onCancel}
|
||||
@@ -24,14 +26,17 @@ test('renders one shared Semi filter sheet with consistent title, section and ac
|
||||
</MobileFilterSheet>);
|
||||
|
||||
const dialog = screen.getByRole('dialog', { name: '测试筛选' });
|
||||
expect(dialog.closest('.semi-sidesheet')).toHaveClass('v2-mobile-filter-sheet', 'test-filter-sheet');
|
||||
expect(dialog).toHaveAttribute('id', 'test-filter-dialog');
|
||||
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.getByRole('region', { name: '车辆条件' })).toHaveTextContent('支持车牌或 VIN');
|
||||
fireEvent.click(screen.getByRole('button', { name: '重置条件' }));
|
||||
expect(screen.getByRole('dialog', { name: '测试筛选' })).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '应用并查询' }));
|
||||
expect(onReset).toHaveBeenCalledTimes(1);
|
||||
expect(onApply).toHaveBeenCalledTimes(1);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'close' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭测试筛选' }));
|
||||
expect(onCancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button, SideSheet } from '@douyinfe/semi-ui';
|
||||
import { useEffect, useId, type ReactNode } from 'react';
|
||||
import { IconFilter } from '@douyinfe/semi-icons';
|
||||
import { type ReactNode } from 'react';
|
||||
import { WorkspaceSideSheet } from './WorkspaceSideSheet';
|
||||
|
||||
type MobileFilterSheetAction = {
|
||||
label: string;
|
||||
@@ -13,6 +14,8 @@ type MobileFilterSheetProps = {
|
||||
visible: boolean;
|
||||
height?: string | number;
|
||||
ariaLabel: string;
|
||||
dialogId?: string;
|
||||
closeLabel?: string;
|
||||
title: string;
|
||||
description: string;
|
||||
onCancel: () => void;
|
||||
@@ -26,6 +29,8 @@ export function MobileFilterSheet({
|
||||
visible,
|
||||
height = 'min(82dvh, 700px)',
|
||||
ariaLabel,
|
||||
dialogId,
|
||||
closeLabel,
|
||||
title,
|
||||
description,
|
||||
onCancel,
|
||||
@@ -34,40 +39,27 @@ export function MobileFilterSheet({
|
||||
children
|
||||
}: MobileFilterSheetProps) {
|
||||
const sheetClassName = ['v2-mobile-filter-sheet', className].filter(Boolean).join(' ');
|
||||
const sheetId = useId();
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) return;
|
||||
const dialog = document.querySelector(`[data-mobile-filter-sheet-id="${sheetId}"] .semi-sidesheet-inner`);
|
||||
dialog?.setAttribute('aria-label', ariaLabel);
|
||||
}, [ariaLabel, sheetId, visible]);
|
||||
|
||||
return <SideSheet
|
||||
return <WorkspaceSideSheet
|
||||
className={sheetClassName}
|
||||
data-mobile-filter-sheet-id={sheetId}
|
||||
variant="filter"
|
||||
visible={visible}
|
||||
ariaLabel={ariaLabel}
|
||||
dialogId={dialogId}
|
||||
closeLabel={closeLabel}
|
||||
placement="bottom"
|
||||
height={height}
|
||||
aria-label={ariaLabel}
|
||||
title={<div className="v2-mobile-filter-sheet-title"><strong>{title}</strong><span>{description}</span></div>}
|
||||
title={title}
|
||||
description={description}
|
||||
icon={<IconFilter />}
|
||||
badge="筛选范围"
|
||||
onCancel={onCancel}
|
||||
footer={<div className="v2-mobile-filter-sheet-footer">
|
||||
{secondaryAction ? <Button
|
||||
theme="light"
|
||||
disabled={secondaryAction.disabled}
|
||||
loading={secondaryAction.loading}
|
||||
onClick={secondaryAction.onClick}
|
||||
>{secondaryAction.label}</Button> : <span aria-hidden="true" />}
|
||||
<Button
|
||||
theme="solid"
|
||||
disabled={primaryAction.disabled}
|
||||
loading={primaryAction.loading}
|
||||
onClick={primaryAction.onClick}
|
||||
>{primaryAction.label}</Button>
|
||||
</div>}
|
||||
footerNote="筛选条件仅在应用后更新当前结果"
|
||||
secondaryActions={secondaryAction ? [secondaryAction] : []}
|
||||
primaryAction={primaryAction}
|
||||
>
|
||||
{children}
|
||||
</SideSheet>;
|
||||
</WorkspaceSideSheet>;
|
||||
}
|
||||
|
||||
type MobileFilterSheetSectionProps = {
|
||||
|
||||
@@ -5,15 +5,16 @@ import { MobileFilterToggle } from './MobileFilterToggle';
|
||||
describe('MobileFilterToggle', () => {
|
||||
test('exposes the current filter summary and expanded state through a Semi button', () => {
|
||||
const onToggle = vi.fn();
|
||||
const { rerender } = render(<MobileFilterToggle summary="全部车辆 · 7 天" expanded={false} onToggle={onToggle} collapsedLabel="修改" />);
|
||||
const { rerender } = render(<MobileFilterToggle summary="全部车辆 · 7 天" expanded={false} controls="vehicle-filter-dialog" onToggle={onToggle} collapsedLabel="修改" />);
|
||||
|
||||
const collapsed = screen.getByRole('button', { name: '修改筛选条件:全部车辆 · 7 天' });
|
||||
expect(collapsed).toHaveClass('semi-button');
|
||||
expect(collapsed).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(collapsed).toHaveAttribute('aria-controls', 'vehicle-filter-dialog');
|
||||
fireEvent.click(collapsed);
|
||||
expect(onToggle).toHaveBeenCalledTimes(1);
|
||||
|
||||
rerender(<MobileFilterToggle summary="已选 2 辆 · 7 天" expanded onToggle={onToggle} collapsedLabel="修改" />);
|
||||
rerender(<MobileFilterToggle summary="已选 2 辆 · 7 天" expanded controls="vehicle-filter-dialog" onToggle={onToggle} collapsedLabel="修改" />);
|
||||
expect(screen.getByRole('button', { name: '收起筛选条件:已选 2 辆 · 7 天' })).toHaveAttribute('aria-expanded', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ type MobileFilterToggleProps = {
|
||||
summary: string;
|
||||
expanded: boolean;
|
||||
onToggle: () => void;
|
||||
controls?: string;
|
||||
expandedLabel?: string;
|
||||
collapsedLabel?: string;
|
||||
};
|
||||
@@ -15,6 +16,7 @@ export function MobileFilterToggle({
|
||||
summary,
|
||||
expanded,
|
||||
onToggle,
|
||||
controls,
|
||||
expandedLabel = '收起',
|
||||
collapsedLabel = '展开'
|
||||
}: MobileFilterToggleProps) {
|
||||
@@ -26,6 +28,7 @@ export function MobileFilterToggle({
|
||||
block
|
||||
htmlType="button"
|
||||
aria-expanded={expanded}
|
||||
aria-controls={controls}
|
||||
aria-label={`${actionLabel}${title}:${summary}`}
|
||||
onClick={onToggle}
|
||||
>
|
||||
|
||||
@@ -41,7 +41,7 @@ test('renders one accessible Semi configuration sheet with shared summary and ac
|
||||
expect(onCancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('exposes task, editor, help and action variants without changing the accessible dialog contract', () => {
|
||||
test('exposes task, editor, help, action and filter variants without changing the accessible dialog contract', () => {
|
||||
const view = render(<>
|
||||
<WorkspaceSideSheet
|
||||
visible
|
||||
@@ -84,16 +84,28 @@ test('exposes task, editor, help and action variants without changing the access
|
||||
>
|
||||
<span>操作内容</span>
|
||||
</WorkspaceSideSheet>
|
||||
<WorkspaceSideSheet
|
||||
visible
|
||||
variant="filter"
|
||||
ariaLabel="范围筛选"
|
||||
title="筛选"
|
||||
description="应用后更新"
|
||||
onCancel={() => undefined}
|
||||
>
|
||||
<span>筛选内容</span>
|
||||
</WorkspaceSideSheet>
|
||||
</>);
|
||||
|
||||
expect(screen.getByRole('dialog', { name: '后台任务' }).closest('.semi-sidesheet')).toHaveClass('v2-workspace-task-sidesheet');
|
||||
expect(screen.getByRole('dialog', { name: '策略编辑' }).closest('.semi-sidesheet')).toHaveClass('v2-workspace-editor-sidesheet');
|
||||
expect(screen.getByRole('dialog', { name: '页面帮助' }).closest('.semi-sidesheet')).toHaveClass('v2-workspace-help-sidesheet');
|
||||
expect(screen.getByRole('dialog', { name: '快捷操作' }).closest('.semi-sidesheet')).toHaveClass('v2-workspace-action-sidesheet');
|
||||
expect(screen.getByRole('dialog', { name: '范围筛选' }).closest('.semi-sidesheet')).toHaveClass('v2-workspace-filter-sidesheet');
|
||||
expect(screen.getByText('任务内容')).toBeInTheDocument();
|
||||
expect(screen.getByText('编辑内容')).toBeInTheDocument();
|
||||
expect(screen.getByText('帮助内容')).toBeInTheDocument();
|
||||
expect(screen.getByText('操作内容')).toBeInTheDocument();
|
||||
expect(screen.getByText('筛选内容')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('help-icon').closest('.v2-workspace-config-title-icon')).toHaveAttribute('aria-hidden', 'true');
|
||||
expect(view.baseElement).toHaveTextContent('查看生成进度');
|
||||
expect(view.baseElement).toHaveTextContent('保存后生效');
|
||||
|
||||
@@ -21,7 +21,7 @@ export type WorkspaceSideSheetBadgeColor = 'blue' | 'green' | 'orange' | 'red' |
|
||||
|
||||
export type WorkspaceSideSheetProps = {
|
||||
className?: string;
|
||||
variant?: 'config' | 'detail' | 'task' | 'editor' | 'help' | 'action';
|
||||
variant?: 'config' | 'detail' | 'task' | 'editor' | 'help' | 'action' | 'filter';
|
||||
visible: boolean;
|
||||
ariaLabel: string;
|
||||
closeLabel?: string;
|
||||
|
||||
@@ -20599,14 +20599,14 @@
|
||||
* Shared Semi UI configuration SideSheet: one hierarchy for strategy,
|
||||
* column and source-policy work across desktop and mobile.
|
||||
*/
|
||||
.v2-workspace-config-sidesheet .semi-sidesheet-inner {
|
||||
:is(.v2-workspace-config-sidesheet, .v2-workspace-filter-sidesheet) .semi-sidesheet-inner {
|
||||
overflow: hidden;
|
||||
border-left: 1px solid #d9e3ef;
|
||||
background: #f5f7fa;
|
||||
box-shadow: -22px 0 64px rgba(24, 43, 68, .16);
|
||||
}
|
||||
|
||||
.v2-workspace-config-sidesheet .semi-sidesheet-header {
|
||||
:is(.v2-workspace-config-sidesheet, .v2-workspace-filter-sidesheet) .semi-sidesheet-header {
|
||||
min-height: 78px;
|
||||
border-bottom: 1px solid #e1e8f1;
|
||||
background:
|
||||
@@ -20615,7 +20615,7 @@
|
||||
padding: 14px 18px;
|
||||
}
|
||||
|
||||
.v2-workspace-config-sidesheet .semi-sidesheet-title {
|
||||
:is(.v2-workspace-config-sidesheet, .v2-workspace-filter-sidesheet) .semi-sidesheet-title {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@@ -20667,7 +20667,7 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.v2-workspace-config-sidesheet .semi-sidesheet-body {
|
||||
:is(.v2-workspace-config-sidesheet, .v2-workspace-filter-sidesheet) .semi-sidesheet-body {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: #f5f7fa;
|
||||
@@ -20760,7 +20760,7 @@
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.v2-workspace-config-sidesheet .semi-sidesheet-footer {
|
||||
:is(.v2-workspace-config-sidesheet, .v2-workspace-filter-sidesheet) .semi-sidesheet-footer {
|
||||
min-height: 64px;
|
||||
border-top: 1px solid #e1e8f1;
|
||||
background: rgba(255, 255, 255, .97);
|
||||
@@ -20981,14 +20981,14 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.v2-workspace-config-sidesheet.semi-sidesheet-bottom .semi-sidesheet-inner {
|
||||
:is(.v2-workspace-config-sidesheet, .v2-workspace-filter-sidesheet).semi-sidesheet-bottom .semi-sidesheet-inner {
|
||||
border: 1px solid #d8e2ed;
|
||||
border-bottom: 0;
|
||||
border-radius: 18px 18px 0 0;
|
||||
box-shadow: 0 -18px 48px rgba(23, 43, 68, .17);
|
||||
}
|
||||
|
||||
.v2-workspace-config-sidesheet.semi-sidesheet-bottom .semi-sidesheet-header {
|
||||
:is(.v2-workspace-config-sidesheet, .v2-workspace-filter-sidesheet).semi-sidesheet-bottom .semi-sidesheet-header {
|
||||
min-height: 68px;
|
||||
padding: 10px 12px 10px 14px;
|
||||
}
|
||||
@@ -21032,7 +21032,7 @@
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.v2-workspace-config-sidesheet.semi-sidesheet-bottom .semi-sidesheet-footer {
|
||||
:is(.v2-workspace-config-sidesheet, .v2-workspace-filter-sidesheet).semi-sidesheet-bottom .semi-sidesheet-footer {
|
||||
min-height: 58px;
|
||||
padding: 7px 9px calc(7px + env(safe-area-inset-bottom));
|
||||
}
|
||||
@@ -21971,3 +21971,159 @@
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Unified Semi UI filter workbench.
|
||||
* Desktop advanced ranges and mobile discovery sheets now share one identity,
|
||||
* one scroll owner and one explicit draft-to-apply action boundary.
|
||||
*/
|
||||
.v2-workspace-filter-sidesheet .semi-sidesheet-inner {
|
||||
background: #f4f7fb;
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .semi-sidesheet-header {
|
||||
background:
|
||||
radial-gradient(circle at 10% 0%, rgba(18, 104, 243, .1), transparent 38%),
|
||||
linear-gradient(180deg, #fff 0%, #fbfcfe 100%);
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-workspace-config-title-icon {
|
||||
border-color: #cfe0f7;
|
||||
background: linear-gradient(145deg, #f4f8ff 0%, #e7f0ff 100%);
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-workspace-config-summary {
|
||||
min-height: 74px;
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-workspace-config-summary strong {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-workspace-config-content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-alert-advanced-filter {
|
||||
gap: 13px;
|
||||
border: 1px solid #dce5ef;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
padding: 14px;
|
||||
box-shadow: 0 8px 24px rgba(31, 53, 80, .05);
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-alert-advanced-filter label {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.v2-workspace-filter-sidesheet.semi-sidesheet-bottom .semi-sidesheet-inner {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-workspace-config-title {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-workspace-config-title-icon {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-workspace-config-title strong {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-workspace-config-title small {
|
||||
max-width: min(48vw, 210px);
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-workspace-config-content {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-mobile-filter-sheet-section {
|
||||
border-color: #dce5ef;
|
||||
border-radius: 11px;
|
||||
box-shadow: 0 7px 22px rgba(31, 53, 80, .045);
|
||||
}
|
||||
|
||||
.v2-source-mobile-filter-sidesheet .v2-source-candidates.v2-vehicle-candidate-list {
|
||||
position: static;
|
||||
max-height: min(390px, 43dvh);
|
||||
}
|
||||
|
||||
.v2-source-mobile-filter-empty {
|
||||
display: grid;
|
||||
min-height: 190px;
|
||||
place-content: center;
|
||||
justify-items: center;
|
||||
gap: 7px;
|
||||
border: 1px dashed #cad8e9;
|
||||
border-radius: 12px;
|
||||
background:
|
||||
radial-gradient(circle at 50% 0%, rgba(18, 104, 243, .07), transparent 48%),
|
||||
rgba(255, 255, 255, .72);
|
||||
color: #7b8b9f;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.v2-source-mobile-filter-empty > i {
|
||||
display: grid;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
place-items: center;
|
||||
border: 1px solid #d4e2f5;
|
||||
border-radius: 12px;
|
||||
background: #eef5ff;
|
||||
color: #1268f3;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.v2-source-mobile-filter-empty > strong {
|
||||
color: #2b4058;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.v2-source-mobile-filter-empty > small {
|
||||
max-width: 250px;
|
||||
color: #7c8b9e;
|
||||
font-size: 9px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) and (max-height: 480px) and (orientation: landscape) {
|
||||
.v2-workspace-filter-sidesheet.semi-sidesheet-bottom .semi-sidesheet-inner {
|
||||
height: 100dvh !important;
|
||||
max-height: 100dvh;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet.semi-sidesheet-bottom .semi-sidesheet-header {
|
||||
min-height: 54px;
|
||||
padding: 7px 10px 7px 12px;
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-workspace-config-title-icon,
|
||||
.v2-workspace-filter-sidesheet .v2-workspace-config-title small,
|
||||
.v2-workspace-filter-sidesheet .v2-workspace-config-title > .semi-tag,
|
||||
.v2-workspace-filter-sidesheet .v2-mobile-filter-sheet-section > header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v2-workspace-filter-sidesheet .v2-workspace-config-content {
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
.v2-source-mobile-filter-empty {
|
||||
min-height: 88px;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user