From 8a2edc8bc9667f597efe087bae431ffbe39d8285 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 18 Jul 2026 15:27:47 +0800 Subject: [PATCH] refine Semi UI access workspace --- .../apps/web/src/v2/pages/AccessPage.test.tsx | 10 +++++ .../apps/web/src/v2/pages/AccessPage.tsx | 33 +++++++++++++--- .../apps/web/src/v2/pages/HistoryPage.tsx | 2 +- .../apps/web/src/v2/styles/workspace.css | 39 ++++++++++++++++--- 4 files changed, 72 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 265df8a5..30fb0513 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 @@ -170,6 +170,16 @@ test('renders mobile access vehicles as selectable Semi cards', async () => { expect(within(action).getByText('808')).toBeInTheDocument(); expect(within(action).getByText('宇通')).toBeInTheDocument(); expect(screen.queryByLabelText('每页车辆数')).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: '打开接入治理' })).toHaveTextContent('治理'); + const toolsButton = screen.getByRole('button', { name: '打开接入管理工具' }); + expect(toolsButton).toHaveTextContent('工具'); + expect(screen.queryByRole('menuitem', { name: /导出当前页/ })).not.toBeInTheDocument(); + fireEvent.click(toolsButton); + await waitFor(() => expect(toolsButton).toHaveAttribute('aria-expanded', 'true')); + expect(screen.getByRole('menuitem', { name: /导出当前页/ })).toBeInTheDocument(); + fireEvent.click(screen.getByRole('menuitem', { name: /刷新数据/ })); + await waitFor(() => expect(mocks.accessSummary).toHaveBeenCalledTimes(2)); + expect(toolsButton).toHaveAttribute('aria-expanded', 'false'); expect(action).toHaveAttribute('aria-pressed', 'false'); expect(action).toHaveAttribute('aria-expanded', 'false'); fireEvent.click(action); 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 13620c5d..c1dcf243 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/AccessPage.tsx @@ -1,5 +1,5 @@ -import { IconChevronRight, IconClose, IconDownload, IconRefresh, IconSave, IconSearch, IconSetting } from '@douyinfe/semi-icons'; -import { Button, Card, CardGroup, Collapse, Descriptions, Empty, Input, Select, SideSheet, Spin, Table, Tag, Typography } from '@douyinfe/semi-ui'; +import { IconChevronRight, IconClose, IconDownload, IconMore, IconRefresh, IconSave, IconSearch, IconSetting } from '@douyinfe/semi-icons'; +import { Button, Card, CardGroup, Collapse, Descriptions, Dropdown, Empty, Input, Select, SideSheet, Spin, Table, Tag, Typography } from '@douyinfe/semi-ui'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { FormEvent, KeyboardEvent, memo, useEffect, useMemo, useState } from 'react'; import { Link, useSearchParams } from 'react-router-dom'; @@ -219,6 +219,7 @@ export default function AccessPage() { const mobileLayout = useMobileLayout(); const [offset, setOffset] = useState(0); const [limit, setLimit] = useState(() => mobileLayout ? 20 : 50); const [selectedVIN, setSelectedVIN] = useState(''); const [governanceOpen, setGovernanceOpen] = useState(false); + const [mobileToolsOpen, setMobileToolsOpen] = useState(false); const [thresholdDraft, setThresholdDraft] = useState(); const queryClient = useQueryClient(); const baseQuery = useMemo(() => Object.fromEntries(Object.entries(criteria).filter(([, value]) => value)) as AccessQuery, [criteria]); const vehicleScope = useMemo(() => queryScopeKey(baseQuery), [baseQuery]); @@ -229,7 +230,10 @@ export default function AccessPage() { useEffect(() => { if (thresholdQuery.data && !thresholdDraft) setThresholdDraft({ version: thresholdQuery.data.version, defaultThresholdSec: thresholdQuery.data.defaultThresholdSec, delayThresholdSec: thresholdQuery.data.delayThresholdSec, longOfflineSec: thresholdQuery.data.longOfflineSec, protocols: thresholdQuery.data.protocols }); }, [thresholdDraft, thresholdQuery.data]); const updateThreshold = useMutation({ mutationFn: api.updateAccessThresholds, onSuccess: async (config) => { setThresholdDraft({ version: config.version, defaultThresholdSec: config.defaultThresholdSec, delayThresholdSec: config.delayThresholdSec, longOfflineSec: config.longOfflineSec, protocols: config.protocols }); await Promise.all([queryClient.invalidateQueries({ queryKey: ['access-summary'] }), queryClient.invalidateQueries({ queryKey: ['access-vehicles'] })]); } }); useEffect(() => { - if (!mobileLayout) return; + if (!mobileLayout) { + setMobileToolsOpen(false); + return; + } setLimit(20); setOffset(0); }, [mobileLayout]); @@ -257,10 +261,27 @@ export default function AccessPage() { className="v2-access-command-bar" ariaLabel="接入管理操作" title="真实来源与接入健康" - description="真实来源、在线健康与资料差异一处核对" + description={mobileLayout + ? summary ? `截至 ${compactTime(summary.asOf)} · ${summary.totalVehicles.toLocaleString('zh-CN')} 辆主车辆` : '正在读取最新接入状态' + : '真实来源、在线健康与资料差异一处核对'} status={summary ? `${summary.totalVehicles.toLocaleString('zh-CN')} 辆主车辆` : '正在读取车辆'} meta={数据时间 } - actions={<>{editable ? : null}} + actions={mobileLayout ? <> + {editable ? : null} + + } onClick={() => { setMobileToolsOpen(false); downloadRows(rows); }}>导出当前页 + } onClick={() => { setMobileToolsOpen(false); void refresh(); }}>刷新数据 + } + > + + + : <>{editable ? : null}} /> } + actions={mobileLayout ? null : <>} />