From 9a49d015cf73327be7f5879e73f482bf3f88844c Mon Sep 17 00:00:00 2001 From: lingniu Date: Sat, 18 Jul 2026 08:28:23 +0800 Subject: [PATCH] refine Semi UI account permission workspace --- .../apps/web/src/v2/pages/UsersPage.test.tsx | 36 +++++++++ .../apps/web/src/v2/pages/UsersPage.tsx | 56 ++++++++++---- .../apps/web/src/v2/productionEntry.test.ts | 3 +- .../apps/web/src/v2/styles/workspace.css | 73 +++++++++++++++++++ 4 files changed, 153 insertions(+), 15 deletions(-) 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 c727ebf0..89d215c7 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 @@ -169,6 +169,42 @@ test('keeps the customer directory visible on mobile and opens details on demand expect(screen.getAllByRole('button', { name: '新建客户账号' })).toHaveLength(1); }); +test('keeps granted vehicles primary and reveals mobile assignment tools on demand', async () => { + layout.mobile = true; + const vehicles = Array.from({ length: 5 }, (_, index) => ({ + vin: `VIN${String(index + 1).padStart(3, '0')}`, + plate: `粤A${String(index + 1).padStart(5, '0')}`, + validFrom: '2026-06-05T00:00:00+08:00', + sourceSystem: 'manual', + grantedBy: '平台管理员' + })); + mocks.adminUsers.mockResolvedValue([{ + id: 7, username: 'customer-east', displayName: '华东客户', userType: 'customer', status: 'enabled', + customerRef: '', tenantRef: '', authProvider: 'local', menuKeys: ['monitor', 'vehicles', 'tracks', 'statistics'], + vehicleVins: vehicles.map((vehicle) => vehicle.vin), vehicles, grantHistory: [], + createdAt: '2026-07-16T00:00:00Z', updatedAt: '2026-07-16T00:00:00Z' + }]); + const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + render(); + + fireEvent.click(await screen.findByRole('button', { name: /选择客户 华东客户/ })); + + const toggle = await screen.findByRole('button', { name: '添加授权车辆' }); + expect(toggle).toHaveAttribute('aria-expanded', 'false'); + expect(document.querySelector('.v2-user-vehicle-section')).toHaveClass('is-composer-collapsed'); + expect(screen.queryByRole('textbox', { name: '按车牌或 VIN 搜索' })).not.toBeInTheDocument(); + expect(screen.getAllByRole('button', { name: /^移除 / })).toHaveLength(4); + expect(screen.getByText('第 1–4 条,共 5 辆')).toBeInTheDocument(); + expect(document.querySelector('.v2-user-editor-last-login')).toHaveTextContent('最近登录'); + + fireEvent.click(toggle); + + expect(screen.getByRole('button', { name: '收起添加车辆' })).toHaveAttribute('aria-expanded', 'true'); + expect(document.querySelector('.v2-user-vehicle-section')).not.toHaveClass('is-composer-collapsed'); + expect(screen.getByRole('textbox', { name: '按车牌或 VIN 搜索' })).toBeInTheDocument(); + expect(document.querySelector('#v2-user-vehicle-composer')).toBeInTheDocument(); +}); + test('filters the customer directory by status and exposes the active result scope', async () => { mocks.adminUsers.mockResolvedValue([ { 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 742abdda..21dcc6ff 100644 --- a/vehicle-data-platform/apps/web/src/v2/pages/UsersPage.tsx +++ b/vehicle-data-platform/apps/web/src/v2/pages/UsersPage.tsx @@ -94,6 +94,7 @@ export default function UsersPage() { const [assignedKeyword, setAssignedKeyword] = useState(''); const [assignedPage, setAssignedPage] = useState(1); const [bulkVINs, setBulkVINs] = useState(''); + const [vehicleComposerOpen, setVehicleComposerOpen] = useState(false); const [vehicleLabels, setVehicleLabels] = useState>({}); const [feedback, setFeedback] = useState(''); const [editingGrantVIN, setEditingGrantVIN] = useState(''); @@ -103,6 +104,7 @@ export default function UsersPage() { const editingGrantPlate = editingGrant ? vehicleLabels[editingGrant.vin] : ''; const grantWindowInvalid = Boolean(editingGrant && (!editingGrant.validFrom || (editingGrant.validTo && editingGrant.validTo <= editingGrant.validFrom))); const editorVisible = creating || Boolean(selected); + const vehicleComposerVisible = !mobileLayout || creating || draft.vehicleGrants.length === 0 || vehicleComposerOpen; useSideSheetA11y(editorVisible, '.v2-user-editor-sidesheet', 'v2-user-editor-sheet', '客户账号详情', '关闭账号详情'); useSideSheetA11y(Boolean(editingGrant), '.v2-user-grant-sidesheet', 'v2-user-grant-window', '车辆授权有效期', '关闭车辆授权有效期'); @@ -173,6 +175,7 @@ export default function UsersPage() { setAssignedKeyword(''); setAssignedPage(1); setBulkVINs(''); + setVehicleComposerOpen(true); setVehicleLabels({}); setFeedback(''); setEditingGrantVIN(''); @@ -185,6 +188,7 @@ export default function UsersPage() { setAssignedKeyword(''); setAssignedPage(1); setBulkVINs(''); + setVehicleComposerOpen(false); setVehicleLabels({}); setFeedback(''); setEditingGrantVIN(''); @@ -198,6 +202,7 @@ export default function UsersPage() { setAssignedKeyword(''); setAssignedPage(1); setBulkVINs(''); + setVehicleComposerOpen(user.vehicles.length === 0); setFeedback(''); setEditingGrantVIN(''); }; @@ -355,7 +360,7 @@ export default function UsersPage() { width={mobileLayout ? '100%' : 'min(840px, 100vw)'} aria-label="客户账号详情" title={
- {creating ? '创建客户账号' : selected?.displayName}{creating ? '设置登录身份和最小必要权限' : `@${selected?.username} · ${draft.menuKeys.length} 个菜单 · ${draft.vehicleGrants.length} 辆车 · 最近登录 ${formatTime(selected?.lastLoginAt)}`} + {creating ? '创建客户账号' : selected?.displayName}{creating ? '设置登录身份和最小必要权限' : <>@{selected?.username} · {draft.menuKeys.length} 个菜单 · {draft.vehicleGrants.length} 辆车 · 最近登录 {formatTime(selected?.lastLoginAt)}}
} onCancel={closeEditor} @@ -382,19 +387,42 @@ export default function UsersPage() { 车辆权限{draft.vehicleGrants.length}} itemKey="vehicles"> - 车辆权限与有效期 {draft.vehicleGrants.length} 辆} headerLine headerExtraContent={draft.vehicleGrants.length ? : null}> -
- {deferredVehicleKeyword ? candidates.refetch()} - selectedVins={assigned} - selectedLabel="已分配" - onSelect={(vehicle) => toggleVIN(vehicle.vin)} - /> : null} + 车辆权限与有效期 {draft.vehicleGrants.length} 辆} + headerLine + headerExtraContent={draft.vehicleGrants.length ?
+ {mobileLayout && draft.vehicleGrants.length ? : null} + {draft.vehicleGrants.length ? : null} +
: null} + > + {vehicleComposerVisible ?
+
+ {deferredVehicleKeyword ? candidates.refetch()} + selectedVins={assigned} + selectedLabel="已分配" + onSelect={(vehicle) => toggleVIN(vehicle.vin)} + /> : null} +
: null} {draft.vehicleGrants.length ? <>
已授权车辆{assignedKeyword ? `${filteredAssignedGrants.length} 条匹配结果` : `${draft.vehicleGrants.length} 辆车拥有当前访问权限`} 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 b67f3eb6..e2cead77 100644 --- a/vehicle-data-platform/apps/web/src/v2/productionEntry.test.ts +++ b/vehicle-data-platform/apps/web/src/v2/productionEntry.test.ts @@ -237,7 +237,8 @@ describe('V2 production entry', () => { expect(corePageSources.UsersPage).toContain("'关闭账号详情'"); expect(corePageSources.UsersPage).toContain(' span { + min-width: 0; +} + +.v2-user-editor-last-login { + flex: 0 0 auto; +} + .v2-user-editor-sheet-title .v2-user-status { display: flex; min-width: 92px; @@ -9248,6 +9258,17 @@ box-shadow: 0 6px 22px rgba(31, 53, 80, .05); } +.v2-user-section-actions { + display: flex; + align-items: center; + gap: 4px; +} + +.v2-user-vehicle-composer { + display: grid; + gap: 10px; +} + .v2-user-editor-sidesheet .semi-sidesheet-footer { min-height: 64px; border-top: 1px solid #e2e9f1; @@ -9325,6 +9346,10 @@ font-size: 9px; } + .v2-user-editor-last-login { + display: none; + } + .v2-user-editor-sheet-title .v2-user-status { min-width: 34px; } @@ -9358,6 +9383,54 @@ -webkit-overflow-scrolling: touch; } + .v2-user-section-actions > .semi-button { + min-width: 0; + min-height: 32px; + border-radius: 7px; + padding-inline: 8px; + font-size: 9px; + } + + .v2-user-vehicle-tools-toggle.semi-button { + min-width: 58px; + } + + .v2-user-vehicle-composer { + gap: 8px; + } + + .v2-user-editor-sidesheet .v2-vehicle-permission-tools { + gap: 8px; + } + + .v2-user-vehicle-section.is-composer-collapsed .v2-assigned-toolbar { + margin-top: 0; + border-top: 0; + padding-top: 1px; + } + + .v2-user-editor-sidesheet .v2-assigned-toolbar { + grid-template-columns: 104px minmax(0, 1fr); + align-items: end; + gap: 8px; + } + + .v2-user-editor-sidesheet .v2-assigned-toolbar > .semi-input-wrapper { + min-height: 40px; + } + + .v2-user-editor-sidesheet .v2-assigned-vins > .v2-assigned-vehicle-card.semi-card { + min-height: 90px; + padding: 9px 10px; + contain-intrinsic-size: auto 90px; + } + + .v2-user-editor-sidesheet .v2-assigned-vehicle-card > .semi-card-body > .v2-assigned-vehicle-interval { + gap: 6px; + margin-top: 7px; + padding-top: 6px; + } + .v2-user-editor-sidesheet .v2-user-editor-tabs > .semi-tabs-content::-webkit-scrollbar { width: 11px; }