diff --git a/vehicle-data-platform/apps/web/src/v2/pages/UsersPage.test.tsx b/vehicle-data-platform/apps/web/src/v2/pages/UsersPage.test.tsx index 814fac5c..a7171aaf 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/UsersPage.test.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/UsersPage.test.tsx @@ -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(); 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 () => { diff --git a/vehicle-data-platform/apps/web/src/v2/pages/UsersPage.tsx b/vehicle-data-platform/apps/web/src/v2/pages/UsersPage.tsx index 74c61155..cabaaae0 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/UsersPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/UsersPage.tsx @@ -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} / {customers.length}, + 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={ - {visibleCustomers.length} / {customers.length} 个账号 - 权限就绪 {readyCustomers} - {attentionCustomers ? 待完善 {attentionCustomers} : null} - 车辆授权 {grantedVehicles} - } + /> +
{users.isPending ? : customers.length === 0 ? 创建第一个客户账号} /> : <> diff --git a/vehicle-data-platform/apps/web/src/v2/productionEntry.test.ts b/vehicle-data-platform/apps/web/src/v2/productionEntry.test.ts index 304b8f5e..c9edf69c 100644 --- a/vehicle-data-platform/apps/web/src/v2/productionEntry.test.ts +++ b/vehicle-data-platform/apps/web/src/v2/productionEntry.test.ts @@ -304,7 +304,10 @@ describe('V2 production entry', () => { expect(corePageSources.UsersPage).toContain('className="v2-user-command-bar"'); expect(corePageSources.UsersPage).toContain('className="v2-user-discovery-shell"'); expect(corePageSources.UsersPage).toContain('className="v2-user-directory-columns"'); - expect(corePageSources.UsersPage).not.toContain('v2-user-directory-metrics'); + expect(corePageSources.UsersPage).toContain("from '../shared/WorkspaceMetricRail'"); + expect(corePageSources.UsersPage).toContain('className="v2-user-directory-metric-rail"'); + expect(corePageSources.UsersPage).toContain('ariaLabel="客户权限目录统计"'); + expect(corePageSources.UsersPage).not.toContain('v2-user-directory-summary'); expect(corePageSources.UsersPage).toContain('aria-expanded={selectedID === user.id && !creating}'); expect(corePageSources.UsersPage).toContain(' span.is-primary.is-warning { + background: linear-gradient(145deg, #fff7ea 0%, #fffdf8 100%); + box-shadow: inset 0 -3px #e59a2f; +} + +.v2-user-directory-metric-rail .v2-workspace-metric-list > span.is-primary.is-success { + background: linear-gradient(145deg, #eefaf5 0%, #fbfefd 100%); + box-shadow: inset 0 -3px #20a678; +} + +@media (max-width: 680px) { + .v2-user-directory-metric-rail .v2-workspace-metric-list { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +}