管理后台左侧列表调整

This commit is contained in:
2026-07-07 18:49:47 +08:00
parent 98e9652865
commit 1ac72e9a69
14 changed files with 780 additions and 40 deletions
@@ -0,0 +1,264 @@
import { useEffect, useMemo, useState } from 'react';
import {
Alert,
Button,
Card,
Checkbox,
Col,
Form,
Row,
Select,
Space,
Tabs,
Tag,
Typography,
message,
} from 'antd';
import {
HQ_ADMIN_ROLES,
HQ_PERMISSION_CATALOG,
type HqPermissionKey,
} from '@dukang/shared-types';
import { request, type HqProfile } from '../lib/api';
type RolePermRes = { role: string; permissionKeys: string[] };
type AccountOption = { id: string; name: string; phone: string; loginName: string | null; adminRole: string };
type AccountPermRes = {
account: AccountOption;
permissionKeys: string[];
rolePermissionKeys: string[];
userPermissionKeys: string[];
effectivePermissionKeys: string[];
};
const ROLE_LABELS = Object.fromEntries(HQ_ADMIN_ROLES.map((r) => [r.value, r.label]));
function PermissionChecklist({
value,
onChange,
disabled,
}: {
value: string[];
onChange: (keys: string[]) => void;
disabled?: boolean;
}) {
return (
<Checkbox.Group
style={{ width: '100%' }}
value={value}
disabled={disabled}
onChange={(checked) => onChange(checked as string[])}
>
<Row gutter={[8, 8]}>
{HQ_PERMISSION_CATALOG.map((item) => (
<Col key={item.key} span={8}>
<Checkbox value={item.key}>{item.label}</Checkbox>
</Col>
))}
</Row>
</Checkbox.Group>
);
}
export default function HqPermissionsPage() {
const [profile, setProfile] = useState<HqProfile | null>(null);
const [role, setRole] = useState<string>('OPS');
const [roleKeys, setRoleKeys] = useState<string[]>([]);
const [roleLoading, setRoleLoading] = useState(false);
const [roleSaving, setRoleSaving] = useState(false);
const [accounts, setAccounts] = useState<AccountOption[]>([]);
const [accountId, setAccountId] = useState<string>();
const [accountKeys, setAccountKeys] = useState<string[]>([]);
const [roleInheritedKeys, setRoleInheritedKeys] = useState<string[]>([]);
const [accountLoading, setAccountLoading] = useState(false);
const [accountSaving, setAccountSaving] = useState(false);
const previewEffectiveKeys = useMemo(
() => [...new Set([...roleInheritedKeys, ...accountKeys])],
[roleInheritedKeys, accountKeys],
);
const isSuperAdmin = profile?.adminRole === 'SUPER_ADMIN';
useEffect(() => {
request<HqProfile>('/admin/auth/me').then(setProfile).catch(() => {});
}, []);
useEffect(() => {
if (!isSuperAdmin) return;
request<{ items: AccountOption[] }>('/admin/hq-accounts?page=1&pageSize=100')
.then((res) => setAccounts(res.items))
.catch(() => {});
}, [isSuperAdmin]);
useEffect(() => {
if (!isSuperAdmin || !role) return;
setRoleLoading(true);
request<RolePermRes>(`/admin/hq-permissions/roles/${role}`)
.then((res) => setRoleKeys(res.permissionKeys))
.finally(() => setRoleLoading(false));
}, [isSuperAdmin, role]);
useEffect(() => {
if (!isSuperAdmin || !accountId) return;
setAccountLoading(true);
request<AccountPermRes>(`/admin/hq-permissions/accounts/${accountId}`)
.then((res) => {
setAccountKeys(res.userPermissionKeys);
setRoleInheritedKeys(res.rolePermissionKeys);
})
.finally(() => setAccountLoading(false));
}, [isSuperAdmin, accountId]);
async function saveRolePermissions() {
setRoleSaving(true);
try {
const res = await request<RolePermRes>(`/admin/hq-permissions/roles/${role}`, {
method: 'PUT',
body: JSON.stringify({ permissionKeys: roleKeys }),
});
setRoleKeys(res.permissionKeys);
message.success('角色权限已保存');
} catch (e) {
message.error(e instanceof Error ? e.message : '保存失败');
} finally {
setRoleSaving(false);
}
}
async function saveAccountPermissions() {
if (!accountId) return;
setAccountSaving(true);
try {
const res = await request<AccountPermRes>(`/admin/hq-permissions/accounts/${accountId}`, {
method: 'PUT',
body: JSON.stringify({ permissionKeys: accountKeys }),
});
setAccountKeys(res.userPermissionKeys);
setRoleInheritedKeys(res.rolePermissionKeys);
message.success('用户权限已保存');
} catch (e) {
message.error(e instanceof Error ? e.message : '保存失败');
} finally {
setAccountSaving(false);
}
}
if (!isSuperAdmin) {
return (
<div>
<Typography.Title level={4}></Typography.Title>
<Alert type="warning" showIcon message="仅超级管理员可配置权限" />
</div>
);
}
return (
<div>
<Typography.Title level={4}></Typography.Title>
<Typography.Paragraph type="secondary">
=
</Typography.Paragraph>
<Tabs
items={[
{
key: 'role',
label: '按角色分配',
children: (
<Card loading={roleLoading}>
<Form layout="inline" style={{ marginBottom: 16 }}>
<Form.Item label="角色">
<Select
style={{ width: 180 }}
value={role}
onChange={setRole}
options={HQ_ADMIN_ROLES.map((r) => ({
value: r.value,
label: r.label,
disabled: r.value === 'SUPER_ADMIN',
}))}
/>
</Form.Item>
</Form>
{role === 'SUPER_ADMIN' ? (
<Alert type="info" showIcon message="超级管理员拥有全部权限,无需配置" />
) : (
<>
<PermissionChecklist value={roleKeys} onChange={setRoleKeys} />
<div style={{ marginTop: 16 }}>
<Button type="primary" loading={roleSaving} onClick={() => void saveRolePermissions()}>
</Button>
</div>
</>
)}
</Card>
),
},
{
key: 'user',
label: '按用户分配',
children: (
<Card loading={accountLoading}>
<Form layout="inline" style={{ marginBottom: 16 }}>
<Form.Item label="HQ 账户">
<Select
showSearch
allowClear
placeholder="选择账户"
style={{ width: 320 }}
value={accountId}
onChange={setAccountId}
optionFilterProp="label"
options={accounts
.filter((a) => a.adminRole !== 'SUPER_ADMIN')
.map((a) => ({
value: a.id,
label: `${a.name} · ${a.loginName || a.phone} · ${ROLE_LABELS[a.adminRole] || a.adminRole}`,
}))}
/>
</Form.Item>
</Form>
{!accountId ? (
<Alert type="info" showIcon message="请先选择要配置的 HQ 账户" />
) : (
<>
<Space wrap style={{ marginBottom: 12 }}>
<span></span>
{roleInheritedKeys.map((key) => {
const item = HQ_PERMISSION_CATALOG.find((p) => p.key === key);
return (
<Tag key={key} color="blue">
{item?.label || key}
</Tag>
);
})}
</Space>
<Typography.Paragraph type="secondary">
</Typography.Paragraph>
<PermissionChecklist value={accountKeys} onChange={setAccountKeys} />
<Space wrap style={{ marginTop: 12 }}>
<span></span>
{previewEffectiveKeys.map((key) => {
const item = HQ_PERMISSION_CATALOG.find((p) => p.key === (key as HqPermissionKey));
return <Tag key={key}>{item?.label || key}</Tag>;
})}
</Space>
<div style={{ marginTop: 16 }}>
<Button type="primary" loading={accountSaving} onClick={() => void saveAccountPermissions()}>
</Button>
</div>
</>
)}
</Card>
),
},
]}
/>
</div>
);
}