refine Semi UI account permission workspace

This commit is contained in:
lingniu
2026-07-18 08:28:23 +08:00
parent aca0edf130
commit 9a49d015cf
4 changed files with 153 additions and 15 deletions

View File

@@ -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<Record<string, string>>({});
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={<div className="v2-user-editor-sheet-title">
<span><strong>{creating ? '创建客户账号' : selected?.displayName}</strong><small>{creating ? '设置登录身份和最小必要权限' : `@${selected?.username} · ${draft.menuKeys.length} 个菜单 · ${draft.vehicleGrants.length} 辆车 · 最近登录 ${formatTime(selected?.lastLoginAt)}`}</small></span>
<span><strong>{creating ? '创建客户账号' : selected?.displayName}</strong><small>{creating ? '设置登录身份和最小必要权限' : <><span>@{selected?.username} · {draft.menuKeys.length} · {draft.vehicleGrants.length} </span><span className="v2-user-editor-last-login"> · {formatTime(selected?.lastLoginAt)}</span></>}</small></span>
<label className="v2-user-status"><Switch aria-label={draft.status === 'enabled' ? '账号启用' : '账号停用'} checked={draft.status === 'enabled'} onChange={(checked) => setDraft((value) => ({ ...value, status: checked ? 'enabled' : 'disabled' }))} /><span>{draft.status === 'enabled' ? '账号启用' : '账号停用'}</span></label>
</div>}
onCancel={closeEditor}
@@ -382,19 +387,42 @@ export default function UsersPage() {
</Card>
</Tabs.TabPane>
<Tabs.TabPane tab={<span className="v2-user-tab-label"><Tag color={draft.vehicleGrants.length ? 'blue' : 'grey'} type="light" size="small">{draft.vehicleGrants.length}</Tag></span>} itemKey="vehicles">
<Card className="v2-user-editor-section v2-user-vehicle-section" title={<span className="v2-user-section-title"> <Tag color={draft.vehicleGrants.length ? 'blue' : 'grey'} type="light" size="small">{draft.vehicleGrants.length} </Tag></span>} headerLine headerExtraContent={draft.vehicleGrants.length ? <Button theme="borderless" type="tertiary" size="small" onClick={() => setDraft((value) => ({ ...value, vehicleGrants: [] }))}></Button> : null}>
<div className="v2-vehicle-permission-tools"><label><span> VIN </span><Input aria-label="按车牌或 VIN 搜索" value={vehicleKeyword} onChange={setVehicleKeyword} placeholder="输入后显示候选车辆" /></label><label><span> VIN</span><span className="v2-bulk-vin"><Input value={bulkVINs} onChange={setBulkVINs} placeholder="空格、逗号或换行分隔" /><Button theme="light" disabled={!bulkVINs.trim()} onClick={addBulkVINs}></Button></span></label></div>
{deferredVehicleKeyword ? <VehicleCandidateList
className="v2-vehicle-candidates"
layout="grid"
items={candidateVehicles}
loading={candidates.isPending}
error={candidates.isError ? (candidates.error instanceof Error ? candidates.error.message : '车辆候选加载失败') : undefined}
onRetry={() => candidates.refetch()}
selectedVins={assigned}
selectedLabel="已分配"
onSelect={(vehicle) => toggleVIN(vehicle.vin)}
/> : null}
<Card
className={`v2-user-editor-section v2-user-vehicle-section${mobileLayout && !vehicleComposerVisible ? ' is-composer-collapsed' : ''}`}
title={<span className="v2-user-section-title"> <Tag color={draft.vehicleGrants.length ? 'blue' : 'grey'} type="light" size="small">{draft.vehicleGrants.length} </Tag></span>}
headerLine
headerExtraContent={draft.vehicleGrants.length ? <div className="v2-user-section-actions">
{mobileLayout && draft.vehicleGrants.length ? <Button
className="v2-user-vehicle-tools-toggle"
theme="light"
type="primary"
size="small"
icon={!vehicleComposerOpen ? <IconPlus /> : undefined}
aria-controls="v2-user-vehicle-composer"
aria-expanded={vehicleComposerOpen}
aria-label={vehicleComposerOpen ? '收起添加车辆' : '添加授权车辆'}
onClick={() => setVehicleComposerOpen((value) => !value)}
>{vehicleComposerOpen ? '收起' : '添加'}</Button> : null}
{draft.vehicleGrants.length ? <Button theme="borderless" type="tertiary" size="small" onClick={() => {
setDraft((value) => ({ ...value, vehicleGrants: [] }));
setVehicleComposerOpen(true);
}}></Button> : null}
</div> : null}
>
{vehicleComposerVisible ? <div id="v2-user-vehicle-composer" className="v2-user-vehicle-composer">
<div className="v2-vehicle-permission-tools"><label><span> VIN </span><Input aria-label="按车牌或 VIN 搜索" value={vehicleKeyword} onChange={setVehicleKeyword} placeholder="输入后显示候选车辆" /></label><label><span> VIN</span><span className="v2-bulk-vin"><Input value={bulkVINs} onChange={setBulkVINs} placeholder="空格、逗号或换行分隔" /><Button theme="light" disabled={!bulkVINs.trim()} onClick={addBulkVINs}></Button></span></label></div>
{deferredVehicleKeyword ? <VehicleCandidateList
className="v2-vehicle-candidates"
layout="grid"
items={candidateVehicles}
loading={candidates.isPending}
error={candidates.isError ? (candidates.error instanceof Error ? candidates.error.message : '车辆候选加载失败') : undefined}
onRetry={() => candidates.refetch()}
selectedVins={assigned}
selectedLabel="已分配"
onSelect={(vehicle) => toggleVIN(vehicle.vin)}
/> : null}
</div> : null}
{draft.vehicleGrants.length ? <>
<div className="v2-assigned-toolbar">
<span><strong></strong><small>{assignedKeyword ? `${filteredAssignedGrants.length} 条匹配结果` : `${draft.vehicleGrants.length} 辆车拥有当前访问权限`}</small></span>