refine Semi UI access workspace

This commit is contained in:
lingniu
2026-07-18 15:27:47 +08:00
parent b764ba1be8
commit 8a2edc8bc9
4 changed files with 72 additions and 12 deletions

View File

@@ -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);

View File

@@ -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<AccessThresholdUpdate>(); 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={<Typography.Text type="tertiary"> <AccessTime value={summary?.asOf} /></Typography.Text>}
actions={<>{editable ? <Button theme="light" icon={<IconSetting />} aria-haspopup="dialog" aria-controls="v2-access-governance" aria-expanded={governanceOpen} onClick={() => setGovernanceOpen(true)}>{unresolvedQuery.data?.total ? ` · ${unresolvedQuery.data.total}` : ''}</Button> : null}<Button theme="light" icon={<IconRefresh />} onClick={() => void refresh()}></Button></>}
actions={mobileLayout ? <>
{editable ? <Button className="v2-access-mobile-tool-button" theme="light" icon={<IconSetting />} aria-label="打开接入治理" aria-haspopup="dialog" aria-controls="v2-access-governance" aria-expanded={governanceOpen} onClick={() => setGovernanceOpen(true)}>{unresolvedQuery.data?.total ? ` · ${unresolvedQuery.data.total}` : ''}</Button> : null}
<Dropdown
trigger="click"
position="bottomRight"
visible={mobileToolsOpen}
onVisibleChange={setMobileToolsOpen}
contentClassName="v2-workspace-mobile-tools-menu"
render={<Dropdown.Menu>
<Dropdown.Item disabled={!rows.length} icon={<IconDownload />} onClick={() => { setMobileToolsOpen(false); downloadRows(rows); }}></Dropdown.Item>
<Dropdown.Item icon={<IconRefresh />} onClick={() => { setMobileToolsOpen(false); void refresh(); }}></Dropdown.Item>
</Dropdown.Menu>}
>
<Button className="v2-access-mobile-tool-button" theme="light" aria-label="打开接入管理工具" aria-haspopup="menu" aria-expanded={mobileToolsOpen} icon={<IconMore />}></Button>
</Dropdown>
</> : <>{editable ? <Button theme="light" icon={<IconSetting />} aria-haspopup="dialog" aria-controls="v2-access-governance" aria-expanded={governanceOpen} onClick={() => setGovernanceOpen(true)}>{unresolvedQuery.data?.total ? ` · ${unresolvedQuery.data.total}` : ''}</Button> : null}<Button theme="light" icon={<IconRefresh />} onClick={() => void refresh()}></Button></>}
/>
<WorkspaceFilterPanel
className="v2-access-filter-card-v3"
@@ -292,7 +313,7 @@ export default function AccessPage() {
title={scopeLabels[criteria.connectionState] || '车辆接入清单'}
description="缺席协议仅表示当前未发现;优先核对异常、离线与资料差异"
actionsClassName="v2-access-table-actions"
actions={<><ProtocolCoverage summary={summary} /><Button theme="light" icon={<IconDownload />} onClick={() => downloadRows(rows)} disabled={!rows.length}></Button></>}
actions={mobileLayout ? null : <><ProtocolCoverage summary={summary} /><Button theme="light" icon={<IconDownload />} onClick={() => downloadRows(rows)} disabled={!rows.length}></Button></>}
/>
<nav className="v2-access-status-tabs" aria-label="接入差异筛选">{[
{ label: '主车辆', value: summary?.totalVehicles ?? 0, tone: 'all', connectionState: '', hint: '全部' },

View File

@@ -436,7 +436,7 @@ export default function HistoryPage() {
position="bottomRight"
visible={mobileToolsOpen}
onVisibleChange={setMobileToolsOpen}
contentClassName="v2-history-mobile-tools-menu"
contentClassName="v2-workspace-mobile-tools-menu"
render={<Dropdown.Menu>
<Dropdown.Item icon={<IconSetting />} onClick={() => { setMobileToolsOpen(false); setColumnSettingsOpen(true); }}></Dropdown.Item>
{exportAllowed ? <Dropdown.Item icon={<IconDownload />} onClick={() => { setMobileToolsOpen(false); setExportJobsOpen(true); }}></Dropdown.Item> : null}

View File

@@ -7008,7 +7008,7 @@
}
}
.v2-history-mobile-tools-menu {
.v2-workspace-mobile-tools-menu {
width: 168px;
min-width: 168px;
border: 1px solid #dbe4ef;
@@ -7017,7 +7017,7 @@
box-shadow: 0 18px 44px rgba(27, 45, 72, .16);
}
.v2-history-mobile-tools-menu .semi-dropdown-item {
.v2-workspace-mobile-tools-menu .semi-dropdown-item {
min-height: 42px;
border-radius: 8px;
padding-inline: 11px;
@@ -7026,8 +7026,8 @@
font-weight: 650;
}
.v2-history-mobile-tools-menu .semi-dropdown-item:hover,
.v2-history-mobile-tools-menu .semi-dropdown-item:focus-visible {
.v2-workspace-mobile-tools-menu .semi-dropdown-item:hover,
.v2-workspace-mobile-tools-menu .semi-dropdown-item:focus-visible {
background: #edf5ff;
color: #1d64c5;
}
@@ -9328,12 +9328,41 @@
}
.v2-access-discovery-shell > .v2-workspace-command-bar {
min-height: 48px;
min-height: 56px;
border: 1px solid #dce6f2;
border-radius: 10px;
background: linear-gradient(105deg, #fff 0%, #f8fbff 100%);
}
.v2-access-command-bar .v2-workspace-command-copy > .semi-typography:last-child {
display: block;
max-width: 160px;
color: #8492a5;
font-size: 9px;
}
.v2-access-command-bar .v2-workspace-command-actions > .v2-access-mobile-tool-button.semi-button {
width: auto;
min-width: 58px;
min-height: 38px;
border: 1px solid #dbe5f0;
border-radius: 8px;
background: #f7f9fc;
padding: 0 9px;
color: #536980;
}
.v2-access-command-bar .v2-access-mobile-tool-button .semi-button-content-right {
margin-left: 4px;
font-size: 10px;
font-weight: 700;
}
.v2-access-command-bar .v2-access-mobile-tool-button .semi-icon {
color: #5f7188;
font-size: 14px;
}
.v2-access-discovery-shell > .v2-access-filter-card-v3.semi-card {
border: 1px solid #dce6f2;
border-radius: 10px;