diff --git a/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.tsx index 28662e24..9151bf8f 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/MonitorPage.tsx @@ -1,10 +1,10 @@ import { - IconChevronLeft, IconClose, IconFilter, IconList, IconMapPin, + IconChevronLeft, IconFilter, IconList, IconMapPin, IconQrCode, IconRefresh, IconSearch } from '@douyinfe/semi-icons'; -import { Button, Card, Empty, Input, Modal, Select, Spin, Table, Tag, TextArea } from '@douyinfe/semi-ui'; +import { Button, Card, Empty, Input, Select, Spin, Table, Tag, TextArea } from '@douyinfe/semi-ui'; import { useQuery } from '@tanstack/react-query'; -import { lazy, memo, Suspense, useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { lazy, memo, Suspense, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { api } from '../../api/client'; import type { Page, VehicleRealtimeRow } from '../../api/types'; @@ -14,6 +14,7 @@ import { SegmentedTabs } from '../shared/SegmentedTabs'; import { TablePagination } from '../shared/TablePagination'; import { ProtocolTag } from '../shared/ProtocolTag'; import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader'; +import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet'; import { formatNumber, statusLabel, vehicleStatus } from '../domain/monitor'; import { useMobileLayout } from '../hooks/useMobileLayout'; import { MAX_MONITOR_SEARCH_TERMS, MONITOR_REFRESH, monitorFilterScope, monitorQueryParams, parseMonitorSearchTerms, useMonitorData, type MonitorViewport } from '../hooks/useMonitorData'; @@ -29,27 +30,41 @@ const MONITOR_VIEW_ITEMS = [ ] as const; const VehicleDetailCard = lazy(() => import('./MonitorVehicleDetailCard')); -function BatchVehicleSearchDialog({ initialValue, onApply, onClose }: { initialValue: string; onApply: (value: string) => void; onClose: () => void }) { +function BatchVehicleSearchDialog({ initialValue, mobile, onApply, onClose }: { initialValue: string; mobile: boolean; onApply: (value: string) => void; onClose: () => void }) { const [draft, setDraft] = useState(initialValue); const terms = useMemo(() => parseMonitorSearchTerms(draft), [draft]); - return 批量搜索车辆从 Excel、文本或聊天记录中直接粘贴车牌} + ariaLabel="批量搜索车辆" + closeLabel="关闭批量搜索车辆" + dialogId="v2-monitor-batch-search" + placement={mobile ? 'bottom' : 'right'} + width={mobile ? undefined : 520} + height={mobile ? 'min(86dvh, 700px)' : undefined} + title="批量搜索车辆" + description="从 Excel、文本或聊天记录中直接粘贴车牌" + icon={} + badge={`${terms.length} 辆`} + badgeColor={terms.length ? 'blue' : 'grey'} + summaryItems={[ + { label: '已识别', value: terms.length.toLocaleString('zh-CN'), detail: '可直接应用到监控筛选', tone: terms.length ? 'primary' : 'neutral' }, + { label: '重复处理', value: '自动去重', detail: '相同车牌只保留一次', tone: 'success' }, + { label: '单次上限', value: `${MAX_MONITOR_SEARCH_TERMS} 辆`, detail: '超出部分不会进入查询' } + ]} + footerNote="支持换行、空格、逗号或分号分隔。" + secondaryActions={[{ label: '取消', onClick: onClose }]} + primaryAction={{ label: `应用搜索(${terms.length})`, ariaLabel: `应用搜索(${terms.length})`, disabled: !terms.length, icon: , onClick: () => onApply(terms.join(',')) }} onCancel={onClose} - footer={
} >