管理后台左侧列表调整
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user