refine Semi UI access workspace
This commit is contained in:
@@ -170,6 +170,16 @@ test('renders mobile access vehicles as selectable Semi cards', async () => {
|
|||||||
expect(within(action).getByText('808')).toBeInTheDocument();
|
expect(within(action).getByText('808')).toBeInTheDocument();
|
||||||
expect(within(action).getByText('宇通')).toBeInTheDocument();
|
expect(within(action).getByText('宇通')).toBeInTheDocument();
|
||||||
expect(screen.queryByLabelText('每页车辆数')).not.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-pressed', 'false');
|
||||||
expect(action).toHaveAttribute('aria-expanded', 'false');
|
expect(action).toHaveAttribute('aria-expanded', 'false');
|
||||||
fireEvent.click(action);
|
fireEvent.click(action);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IconChevronRight, IconClose, IconDownload, IconRefresh, IconSave, IconSearch, IconSetting } from '@douyinfe/semi-icons';
|
import { IconChevronRight, IconClose, IconDownload, IconMore, 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 { 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 { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { FormEvent, KeyboardEvent, memo, useEffect, useMemo, useState } from 'react';
|
import { FormEvent, KeyboardEvent, memo, useEffect, useMemo, useState } from 'react';
|
||||||
import { Link, useSearchParams } from 'react-router-dom';
|
import { Link, useSearchParams } from 'react-router-dom';
|
||||||
@@ -219,6 +219,7 @@ export default function AccessPage() {
|
|||||||
const mobileLayout = useMobileLayout();
|
const mobileLayout = useMobileLayout();
|
||||||
const [offset, setOffset] = useState(0); const [limit, setLimit] = useState(() => mobileLayout ? 20 : 50); const [selectedVIN, setSelectedVIN] = useState('');
|
const [offset, setOffset] = useState(0); const [limit, setLimit] = useState(() => mobileLayout ? 20 : 50); const [selectedVIN, setSelectedVIN] = useState('');
|
||||||
const [governanceOpen, setGovernanceOpen] = useState(false);
|
const [governanceOpen, setGovernanceOpen] = useState(false);
|
||||||
|
const [mobileToolsOpen, setMobileToolsOpen] = useState(false);
|
||||||
const [thresholdDraft, setThresholdDraft] = useState<AccessThresholdUpdate>(); const queryClient = useQueryClient();
|
const [thresholdDraft, setThresholdDraft] = useState<AccessThresholdUpdate>(); const queryClient = useQueryClient();
|
||||||
const baseQuery = useMemo(() => Object.fromEntries(Object.entries(criteria).filter(([, value]) => value)) as AccessQuery, [criteria]);
|
const baseQuery = useMemo(() => Object.fromEntries(Object.entries(criteria).filter(([, value]) => value)) as AccessQuery, [criteria]);
|
||||||
const vehicleScope = useMemo(() => queryScopeKey(baseQuery), [baseQuery]);
|
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]);
|
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'] })]); } });
|
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(() => {
|
useEffect(() => {
|
||||||
if (!mobileLayout) return;
|
if (!mobileLayout) {
|
||||||
|
setMobileToolsOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setLimit(20);
|
setLimit(20);
|
||||||
setOffset(0);
|
setOffset(0);
|
||||||
}, [mobileLayout]);
|
}, [mobileLayout]);
|
||||||
@@ -257,10 +261,27 @@ export default function AccessPage() {
|
|||||||
className="v2-access-command-bar"
|
className="v2-access-command-bar"
|
||||||
ariaLabel="接入管理操作"
|
ariaLabel="接入管理操作"
|
||||||
title="真实来源与接入健康"
|
title="真实来源与接入健康"
|
||||||
description="真实来源、在线健康与资料差异一处核对"
|
description={mobileLayout
|
||||||
|
? summary ? `截至 ${compactTime(summary.asOf)} · ${summary.totalVehicles.toLocaleString('zh-CN')} 辆主车辆` : '正在读取最新接入状态'
|
||||||
|
: '真实来源、在线健康与资料差异一处核对'}
|
||||||
status={summary ? `${summary.totalVehicles.toLocaleString('zh-CN')} 辆主车辆` : '正在读取车辆'}
|
status={summary ? `${summary.totalVehicles.toLocaleString('zh-CN')} 辆主车辆` : '正在读取车辆'}
|
||||||
meta={<Typography.Text type="tertiary">数据时间 <AccessTime value={summary?.asOf} /></Typography.Text>}
|
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
|
<WorkspaceFilterPanel
|
||||||
className="v2-access-filter-card-v3"
|
className="v2-access-filter-card-v3"
|
||||||
@@ -292,7 +313,7 @@ export default function AccessPage() {
|
|||||||
title={scopeLabels[criteria.connectionState] || '车辆接入清单'}
|
title={scopeLabels[criteria.connectionState] || '车辆接入清单'}
|
||||||
description="缺席协议仅表示当前未发现;优先核对异常、离线与资料差异"
|
description="缺席协议仅表示当前未发现;优先核对异常、离线与资料差异"
|
||||||
actionsClassName="v2-access-table-actions"
|
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="接入差异筛选">{[
|
<nav className="v2-access-status-tabs" aria-label="接入差异筛选">{[
|
||||||
{ label: '主车辆', value: summary?.totalVehicles ?? 0, tone: 'all', connectionState: '', hint: '全部' },
|
{ label: '主车辆', value: summary?.totalVehicles ?? 0, tone: 'all', connectionState: '', hint: '全部' },
|
||||||
|
|||||||
@@ -436,7 +436,7 @@ export default function HistoryPage() {
|
|||||||
position="bottomRight"
|
position="bottomRight"
|
||||||
visible={mobileToolsOpen}
|
visible={mobileToolsOpen}
|
||||||
onVisibleChange={setMobileToolsOpen}
|
onVisibleChange={setMobileToolsOpen}
|
||||||
contentClassName="v2-history-mobile-tools-menu"
|
contentClassName="v2-workspace-mobile-tools-menu"
|
||||||
render={<Dropdown.Menu>
|
render={<Dropdown.Menu>
|
||||||
<Dropdown.Item icon={<IconSetting />} onClick={() => { setMobileToolsOpen(false); setColumnSettingsOpen(true); }}>字段设置</Dropdown.Item>
|
<Dropdown.Item icon={<IconSetting />} onClick={() => { setMobileToolsOpen(false); setColumnSettingsOpen(true); }}>字段设置</Dropdown.Item>
|
||||||
{exportAllowed ? <Dropdown.Item icon={<IconDownload />} onClick={() => { setMobileToolsOpen(false); setExportJobsOpen(true); }}>导出任务</Dropdown.Item> : null}
|
{exportAllowed ? <Dropdown.Item icon={<IconDownload />} onClick={() => { setMobileToolsOpen(false); setExportJobsOpen(true); }}>导出任务</Dropdown.Item> : null}
|
||||||
|
|||||||
@@ -7008,7 +7008,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-history-mobile-tools-menu {
|
.v2-workspace-mobile-tools-menu {
|
||||||
width: 168px;
|
width: 168px;
|
||||||
min-width: 168px;
|
min-width: 168px;
|
||||||
border: 1px solid #dbe4ef;
|
border: 1px solid #dbe4ef;
|
||||||
@@ -7017,7 +7017,7 @@
|
|||||||
box-shadow: 0 18px 44px rgba(27, 45, 72, .16);
|
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;
|
min-height: 42px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding-inline: 11px;
|
padding-inline: 11px;
|
||||||
@@ -7026,8 +7026,8 @@
|
|||||||
font-weight: 650;
|
font-weight: 650;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v2-history-mobile-tools-menu .semi-dropdown-item:hover,
|
.v2-workspace-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:focus-visible {
|
||||||
background: #edf5ff;
|
background: #edf5ff;
|
||||||
color: #1d64c5;
|
color: #1d64c5;
|
||||||
}
|
}
|
||||||
@@ -9328,12 +9328,41 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.v2-access-discovery-shell > .v2-workspace-command-bar {
|
.v2-access-discovery-shell > .v2-workspace-command-bar {
|
||||||
min-height: 48px;
|
min-height: 56px;
|
||||||
border: 1px solid #dce6f2;
|
border: 1px solid #dce6f2;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: linear-gradient(105deg, #fff 0%, #f8fbff 100%);
|
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 {
|
.v2-access-discovery-shell > .v2-access-filter-card-v3.semi-card {
|
||||||
border: 1px solid #dce6f2;
|
border: 1px solid #dce6f2;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
|||||||
Reference in New Issue
Block a user