@@ -17,6 +17,7 @@ import {
|
||||
import {
|
||||
HQ_ADMIN_ROLES,
|
||||
HQ_PERMISSION_CATALOG,
|
||||
computeHqEffectivePermissionKeys,
|
||||
type HqPermissionKey,
|
||||
} from '@dukang/shared-types';
|
||||
import { request, type HqProfile } from '../lib/api';
|
||||
@@ -27,6 +28,8 @@ type AccountPermRes = {
|
||||
account: AccountOption;
|
||||
permissionKeys: string[];
|
||||
rolePermissionKeys: string[];
|
||||
grantKeys?: string[];
|
||||
denyKeys?: string[];
|
||||
userPermissionKeys: string[];
|
||||
effectivePermissionKeys: string[];
|
||||
};
|
||||
@@ -46,11 +49,17 @@ function PermissionChecklist({
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
roleKeys,
|
||||
grantKeys,
|
||||
}: {
|
||||
value: string[];
|
||||
onChange: (keys: string[]) => void;
|
||||
disabled?: boolean;
|
||||
roleKeys?: string[];
|
||||
grantKeys?: string[];
|
||||
}) {
|
||||
const roleSet = new Set(roleKeys ?? []);
|
||||
const grantSet = new Set(grantKeys ?? []);
|
||||
return (
|
||||
<Checkbox.Group
|
||||
style={{ width: '100%' }}
|
||||
@@ -81,6 +90,13 @@ function PermissionChecklist({
|
||||
{items.map((item) => (
|
||||
<Checkbox key={item.key} value={item.key}>
|
||||
{item.label}
|
||||
{roleSet.has(item.key) && !value.includes(item.key) ? (
|
||||
<Tag style={{ marginLeft: 6 }} color="red">已撤销</Tag>
|
||||
) : grantSet.has(item.key) ? (
|
||||
<Tag style={{ marginLeft: 6 }} color="orange">追加</Tag>
|
||||
) : roleSet.has(item.key) ? (
|
||||
<Tag style={{ marginLeft: 6 }} color="blue">角色</Tag>
|
||||
) : null}
|
||||
</Checkbox>
|
||||
))}
|
||||
</Space>
|
||||
@@ -88,7 +104,16 @@ function PermissionChecklist({
|
||||
<Row gutter={[12, 10]}>
|
||||
{items.map((item) => (
|
||||
<Col key={item.key} xs={24} sm={12} md={span}>
|
||||
<Checkbox value={item.key}>{item.label}</Checkbox>
|
||||
<Checkbox value={item.key}>
|
||||
{item.label}
|
||||
{roleSet.has(item.key) && !value.includes(item.key) ? (
|
||||
<Tag style={{ marginLeft: 6 }} color="red">已撤销</Tag>
|
||||
) : grantSet.has(item.key) ? (
|
||||
<Tag style={{ marginLeft: 6 }} color="orange">追加</Tag>
|
||||
) : roleSet.has(item.key) ? (
|
||||
<Tag style={{ marginLeft: 6 }} color="blue">角色</Tag>
|
||||
) : null}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
@@ -100,6 +125,14 @@ function PermissionChecklist({
|
||||
);
|
||||
}
|
||||
|
||||
function splitAccountOverrides(roleKeys: string[], effectiveKeys: string[]) {
|
||||
const roleSet = new Set(roleKeys);
|
||||
const effectiveSet = new Set(effectiveKeys);
|
||||
const grantKeys = effectiveKeys.filter((k) => !roleSet.has(k));
|
||||
const denyKeys = roleKeys.filter((k) => !effectiveSet.has(k));
|
||||
return { grantKeys, denyKeys };
|
||||
}
|
||||
|
||||
export default function HqPermissionsPage() {
|
||||
const [profile, setProfile] = useState<HqProfile | null>(null);
|
||||
const [role, setRole] = useState<string>('OPS');
|
||||
@@ -111,6 +144,8 @@ export default function HqPermissionsPage() {
|
||||
const [accountId, setAccountId] = useState<string>();
|
||||
const [accountKeys, setAccountKeys] = useState<string[]>([]);
|
||||
const [roleInheritedKeys, setRoleInheritedKeys] = useState<string[]>([]);
|
||||
const [grantKeys, setGrantKeys] = useState<string[]>([]);
|
||||
const [denyKeys, setDenyKeys] = useState<string[]>([]);
|
||||
const [accountLoading, setAccountLoading] = useState(false);
|
||||
const [accountSaving, setAccountSaving] = useState(false);
|
||||
|
||||
@@ -121,8 +156,8 @@ export default function HqPermissionsPage() {
|
||||
const selectedIsSuperAdmin = selectedAccount?.adminRole === 'SUPER_ADMIN';
|
||||
|
||||
const previewEffectiveKeys = useMemo(
|
||||
() => [...new Set([...roleInheritedKeys, ...accountKeys])],
|
||||
[roleInheritedKeys, accountKeys],
|
||||
() => computeHqEffectivePermissionKeys(roleInheritedKeys, grantKeys, denyKeys),
|
||||
[roleInheritedKeys, grantKeys, denyKeys],
|
||||
);
|
||||
|
||||
const isSuperAdmin = profile?.adminRole === 'SUPER_ADMIN';
|
||||
@@ -151,8 +186,10 @@ export default function HqPermissionsPage() {
|
||||
setAccountLoading(true);
|
||||
request<AccountPermRes>(`/admin/hq-permissions/accounts/${accountId}`)
|
||||
.then((res) => {
|
||||
setAccountKeys(res.userPermissionKeys);
|
||||
setGrantKeys(res.grantKeys ?? res.userPermissionKeys ?? []);
|
||||
setDenyKeys(res.denyKeys ?? []);
|
||||
setRoleInheritedKeys(res.rolePermissionKeys);
|
||||
setAccountKeys(res.effectivePermissionKeys);
|
||||
})
|
||||
.finally(() => setAccountLoading(false));
|
||||
}, [isSuperAdmin, accountId]);
|
||||
@@ -179,10 +216,12 @@ export default function HqPermissionsPage() {
|
||||
try {
|
||||
const res = await request<AccountPermRes>(`/admin/hq-permissions/accounts/${accountId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ permissionKeys: accountKeys }),
|
||||
body: JSON.stringify({ grantKeys, denyKeys }),
|
||||
});
|
||||
setAccountKeys(res.userPermissionKeys);
|
||||
setGrantKeys(res.grantKeys ?? res.userPermissionKeys ?? []);
|
||||
setDenyKeys(res.denyKeys ?? []);
|
||||
setRoleInheritedKeys(res.rolePermissionKeys);
|
||||
setAccountKeys(res.effectivePermissionKeys);
|
||||
message.success('用户权限已保存');
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '保存失败');
|
||||
@@ -204,8 +243,9 @@ export default function HqPermissionsPage() {
|
||||
<div>
|
||||
<Typography.Title level={4}>权限分配</Typography.Title>
|
||||
<Typography.Paragraph type="secondary">
|
||||
按角色配置基础权限;按用户可追加专属权限。最终生效权限 = 角色权限 ∪ 用户权限。
|
||||
按角色配置基础权限;按用户可追加或撤销。最终生效权限 =(角色权限 ∪ 追加)− 撤销。
|
||||
超级管理员默认拥有除「危险操作」外的全部权限;删除用户/订单/城市需在「按用户分配」中单独勾选(默认均无)。
|
||||
运营/财务默认可删除门店分类;城市门店服务可新增分类,不可删除。
|
||||
「系统设置」已拆分为各配置分组;「财务」对应门店/合伙人/酒厂账单。
|
||||
</Typography.Paragraph>
|
||||
|
||||
@@ -275,59 +315,51 @@ export default function HqPermissionsPage() {
|
||||
<Alert type="info" showIcon message="请先选择要配置的 HQ 账户" />
|
||||
) : (
|
||||
<>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<span style={{ marginRight: 8 }}>角色继承:</span>
|
||||
{selectedIsSuperAdmin ? (
|
||||
<Tag color="blue">超级管理员基础权限(不含危险操作)</Tag>
|
||||
) : (
|
||||
<Space wrap size={[4, 4]}>
|
||||
{roleInheritedKeys.map((key) => {
|
||||
const item = HQ_PERMISSION_CATALOG.find((p) => p.key === key);
|
||||
return (
|
||||
<Tag key={key} color="blue">
|
||||
{item?.label || key}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
{!roleInheritedKeys.length ? (
|
||||
<Typography.Text type="secondary">无</Typography.Text>
|
||||
) : null}
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
<Typography.Paragraph type="secondary">
|
||||
下方勾选为用户专属追加权限(保存后与角色权限合并生效)。危险操作(删除用户/订单/城市)默认不授予,需在此勾选。
|
||||
勾选表示该账号最终拥有该权限。取消角色已有项即为撤销;勾选角色没有的项即为追加。
|
||||
切换角色会清空账号级追加/撤销。
|
||||
</Typography.Paragraph>
|
||||
<PermissionChecklist value={accountKeys} onChange={setAccountKeys} />
|
||||
<PermissionChecklist
|
||||
value={accountKeys}
|
||||
roleKeys={roleInheritedKeys}
|
||||
grantKeys={grantKeys}
|
||||
disabled={selectedIsSuperAdmin}
|
||||
onChange={(nextEffective) => {
|
||||
if (selectedIsSuperAdmin) return;
|
||||
const next = splitAccountOverrides(roleInheritedKeys, nextEffective);
|
||||
setGrantKeys(next.grantKeys);
|
||||
setDenyKeys(next.denyKeys);
|
||||
setAccountKeys(nextEffective);
|
||||
}}
|
||||
/>
|
||||
{selectedIsSuperAdmin ? (
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginTop: 12 }}
|
||||
message="超级管理员基础权限不可撤销。危险操作请到下方以外的方式:当前勾选仅展示生效权限。"
|
||||
/>
|
||||
) : null}
|
||||
<div style={{ marginTop: 12 }}>
|
||||
<span style={{ marginRight: 8 }}>合并生效:</span>
|
||||
{selectedIsSuperAdmin ? (
|
||||
<Space wrap size={[4, 4]}>
|
||||
<Tag>基础权限(全部)</Tag>
|
||||
{accountKeys.map((key) => {
|
||||
const item = HQ_PERMISSION_CATALOG.find((p) => p.key === (key as HqPermissionKey));
|
||||
return (
|
||||
<Tag key={key} color="orange">
|
||||
{item?.label || key}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
) : (
|
||||
<Space wrap size={[4, 4]}>
|
||||
{previewEffectiveKeys.map((key) => {
|
||||
const item = HQ_PERMISSION_CATALOG.find((p) => p.key === (key as HqPermissionKey));
|
||||
return (
|
||||
<Tag key={key}>
|
||||
{item?.label || key}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
)}
|
||||
<Space wrap size={[4, 4]}>
|
||||
{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>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Button type="primary" loading={accountSaving} onClick={() => void saveAccountPermissions()}>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={accountSaving}
|
||||
disabled={selectedIsSuperAdmin}
|
||||
onClick={() => void saveAccountPermissions()}
|
||||
>
|
||||
保存用户权限
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user