feat: unify account directory metrics with semi ui

This commit is contained in:
lingniu
2026-07-19 12:10:36 +08:00
parent 7df015b0f0
commit 1e57fdfff1
4 changed files with 93 additions and 16 deletions

View File

@@ -51,15 +51,18 @@ test('deduplicates vehicle candidates by VIN and renders granted vehicles plate
expect(view.container.querySelector('.v2-user-discovery-shell')).toContainElement(screen.getByLabelText('账号管理操作'));
expect(screen.getByRole('heading', { name: '账号范围', level: 5 })).toBeInTheDocument();
expect(screen.getByText('按客户名称、登录账号与权限可用状态查找')).toBeInTheDocument();
expect(screen.getAllByText('1 / 1 个账号')).toHaveLength(2);
expect(screen.getByText('1 / 1 个账号')).toBeInTheDocument();
expect(view.container.querySelector('.v2-user-list.semi-card')).toBeInTheDocument();
expect(view.container.querySelector('.v2-user-filter-panel.v2-workspace-filter-panel.semi-card')).toBeInTheDocument();
expect(view.container.querySelector('.v2-customer-list.semi-list')).toBeInTheDocument();
expect(screen.getByRole('row', { name: '客户账号目录列' })).toHaveTextContent('客户账号菜单权限车辆权限最近登录访问状态');
expect(screen.getByText('权限就绪 1')).toBeInTheDocument();
expect(screen.getByText('车辆授权 1')).toBeInTheDocument();
expect(screen.getByText('权限就绪')).toBeInTheDocument();
expect(view.container.querySelector('.v2-user-directory-metrics')).not.toBeInTheDocument();
const directoryMetrics = screen.getByRole('list', { name: '客户权限目录统计' });
expect(directoryMetrics.closest('.v2-user-directory-metric-rail')).toHaveClass('v2-workspace-metric-rail', 'is-queue');
expect(directoryMetrics.querySelectorAll(':scope > [role="listitem"]')).toHaveLength(4);
expect(directoryMetrics).toHaveTextContent('当前账号1 / 1全部客户账号');
expect(directoryMetrics).toHaveTextContent('待完善0缺少菜单或车辆');
expect(directoryMetrics).toHaveTextContent('权限就绪1启用且权限完整');
expect(directoryMetrics).toHaveTextContent('车辆授权1当前范围合计');
expect(customer).toHaveClass('semi-button', 'v2-user-list-item');
expect(customer.closest('.semi-list-item')).toHaveClass('v2-user-list-row');
expect(customer).toHaveAttribute('aria-pressed', 'false');
@@ -228,6 +231,7 @@ test('keeps the customer directory visible on mobile and opens details on demand
const view = render(<QueryClientProvider client={client}><UsersPage /></QueryClientProvider>);
expect(await screen.findByText('客户权限目录')).toBeInTheDocument();
expect(screen.getByRole('list', { name: '客户权限目录统计' }).querySelectorAll(':scope > [role="listitem"].is-primary')).toHaveLength(2);
const customer = await screen.findByRole('button', { name: /选择客户 华东客户/ });
expect(view.container.querySelector('.v2-user-mobile-picker')).not.toBeInTheDocument();
expect(view.container.querySelector('.v2-customer-list.semi-list')).toBeInTheDocument();
@@ -366,9 +370,10 @@ test('filters the customer directory by status and exposes the active result sco
await waitFor(() => expect(screen.queryByRole('button', { name: /选择客户 华东客户/ })).not.toBeInTheDocument());
expect(screen.getByRole('button', { name: /选择客户 西部客户/ })).toBeInTheDocument();
expect(screen.getAllByText('1 / 2 个账号')).toHaveLength(2);
expect(screen.getByText('权限就绪 0')).toBeInTheDocument();
expect(screen.getByText('车辆授权 0')).toBeInTheDocument();
expect(screen.getByText('1 / 2 个账号')).toBeInTheDocument();
expect(screen.getByRole('list', { name: '客户权限目录统计' })).toHaveTextContent('当前账号1 / 2筛选结果 / 全部客户');
expect(screen.getByRole('list', { name: '客户权限目录统计' })).toHaveTextContent('权限就绪0启用且权限完整');
expect(screen.getByRole('list', { name: '客户权限目录统计' })).toHaveTextContent('车辆授权0当前范围合计');
fireEvent.click(screen.getByRole('button', { name: '清空账号筛选' }));
await waitFor(() => expect(screen.getByRole('button', { name: /选择客户 华东客户/ })).toBeInTheDocument());
@@ -395,7 +400,7 @@ test('filters the customer directory by status and exposes the active result sco
await waitFor(() => expect(screen.queryByRole('button', { name: /选择客户 西部客户/ })).not.toBeInTheDocument());
expect(screen.getByRole('button', { name: /选择客户 华东客户/ })).toBeInTheDocument();
expect(screen.getByText('待分配车辆')).toBeInTheDocument();
expect(screen.getByText('待完善 1')).toBeInTheDocument();
expect(screen.getByRole('list', { name: '客户权限目录统计' })).toHaveTextContent('待完善1缺少菜单或车辆');
});
test('uses a standard Semi empty state when no customer account exists', async () => {

View File

@@ -12,6 +12,7 @@ import { VehicleCandidateList } from '../shared/VehicleCandidateList';
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
import { WorkspaceConfirmDialog } from '../shared/WorkspaceDialog';
import { WorkspaceMetricRail, type WorkspaceQueueMetricRailItem } from '../shared/WorkspaceMetricRail';
import { WorkspacePanelHeader } from '../shared/WorkspacePanelHeader';
import { WorkspaceSideSheet, type WorkspaceSideSheetBadgeColor } from '../shared/WorkspaceSideSheet';
import { mergeVehicleCandidates } from '../shared/vehicleCandidates';
@@ -153,6 +154,36 @@ export default function UsersPage() {
const readyCustomers = useMemo(() => visibleCustomers.filter((user) => customerAccessState(user.status, user.menuKeys.length, user.vehicles.length).key === 'ready').length, [visibleCustomers]);
const attentionCustomers = useMemo(() => visibleCustomers.filter((user) => customerAccessState(user.status, user.menuKeys.length, user.vehicles.length).attention).length, [visibleCustomers]);
const grantedVehicles = useMemo(() => visibleCustomers.reduce((total, user) => total + user.vehicles.length, 0), [visibleCustomers]);
const directoryMetrics: WorkspaceQueueMetricRailItem[] = [
{
label: '当前账号',
value: <>{visibleCustomers.length}<i className="v2-user-directory-metric-unit"> / {customers.length}</i></>,
note: customerKeyword.trim() || customerStatus !== 'all' ? '筛选结果 / 全部客户' : '全部客户账号',
tone: 'primary',
emphasis: 'primary'
},
{
label: '待完善',
value: attentionCustomers,
note: '缺少菜单或车辆',
tone: attentionCustomers ? 'warning' : 'success',
emphasis: 'primary'
},
{
label: '权限就绪',
value: readyCustomers,
note: '启用且权限完整',
tone: 'success',
emphasis: 'secondary'
},
{
label: '车辆授权',
value: grantedVehicles,
note: '当前范围合计',
tone: 'neutral',
emphasis: 'secondary'
}
];
const editingGrant = useMemo(() => draft.vehicleGrants.find((grant) => grant.vin === editingGrantVIN), [draft.vehicleGrants, editingGrantVIN]);
const editingGrantPlate = editingGrant ? vehicleLabels[editingGrant.vin] : '';
const grantWindowInvalid = Boolean(editingGrant && (!editingGrant.validFrom || (editingGrant.validTo && editingGrant.validTo <= editingGrant.validFrom)));
@@ -469,12 +500,12 @@ export default function UsersPage() {
className="v2-user-directory-header"
title="客户权限目录"
description="当前范围内的账号、菜单与车辆授权摘要"
meta={<span className="v2-user-directory-summary">
<Tag color={visibleCustomers.length === customers.length ? 'grey' : 'blue'} type="light" size="small">{visibleCustomers.length} / {customers.length} </Tag>
<Typography.Text className="is-ready" type="tertiary"> {readyCustomers}</Typography.Text>
{attentionCustomers ? <Tag className="is-attention" color="amber" type="light" size="small"> {attentionCustomers}</Tag> : null}
<Typography.Text type="tertiary"> {grantedVehicles}</Typography.Text>
</span>}
/>
<WorkspaceMetricRail
variant="queue"
className="v2-user-directory-metric-rail"
ariaLabel="客户权限目录统计"
items={directoryMetrics}
/>
<div className="v2-user-directory-body">
{users.isPending ? <PanelLoading className="v2-user-list-loading" title="正在加载客户账号" description="账号目录和授权摘要就绪后会自动显示。" /> : customers.length === 0 ? <PanelEmpty className="v2-user-list-empty" title="还没有客户账号" description="创建客户账号后,可在这里配置菜单和车辆范围。" action={<Button theme="solid" onClick={startCreate}></Button>} /> : <>