You've already forked DataMate
feat(auth): 角色管理CRUD与角色权限绑定功能
Some checks failed
Some checks failed
新增角色创建/编辑/删除接口和角色-权限绑定接口,支持管理员自定义角色并灵活配置权限。 前端新增角色CRUD弹窗、按模块分组的权限配置面板,内置角色禁止删除但允许编辑和配置权限。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,11 +2,18 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
Col,
|
||||
Empty,
|
||||
Form,
|
||||
Input,
|
||||
message,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Table,
|
||||
Tag,
|
||||
Typography,
|
||||
@@ -14,14 +21,20 @@ import {
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import {
|
||||
assignUserRolesUsingPut,
|
||||
bindRolePermissionsUsingPut,
|
||||
createRoleUsingPost,
|
||||
deleteRoleUsingDelete,
|
||||
getRolePermissionsUsingGet,
|
||||
listAuthPermissionsUsingGet,
|
||||
listAuthRolesUsingGet,
|
||||
listAuthUsersUsingGet,
|
||||
updateRoleUsingPut,
|
||||
} from "./settings.apis";
|
||||
import type {
|
||||
AuthPermissionInfo,
|
||||
AuthRoleInfo,
|
||||
AuthUserWithRoles,
|
||||
RoleWithPermissionsResponse,
|
||||
} from "./settings.apis";
|
||||
|
||||
interface ApiResponse<T> {
|
||||
@@ -49,6 +62,17 @@ export default function UserPermissionManagement({
|
||||
const [selectedRoleCodes, setSelectedRoleCodes] = useState<string[]>([]);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
// Role CRUD state
|
||||
const [createRoleOpen, setCreateRoleOpen] = useState(false);
|
||||
const [editingRole, setEditingRole] = useState<AuthRoleInfo | null>(null);
|
||||
const [createRoleForm] = Form.useForm();
|
||||
const [editRoleForm] = Form.useForm();
|
||||
|
||||
// Permission config state
|
||||
const [permConfigRole, setPermConfigRole] = useState<AuthRoleInfo | null>(null);
|
||||
const [selectedPermissionIds, setSelectedPermissionIds] = useState<string[]>([]);
|
||||
const [permConfigLoading, setPermConfigLoading] = useState(false);
|
||||
|
||||
const canShowAnything = canManageUsers || canViewRoles || canViewPermissions;
|
||||
const canAssignRoles = canManageUsers && roles.length > 0;
|
||||
|
||||
@@ -61,6 +85,17 @@ export default function UserPermissionManagement({
|
||||
[roles]
|
||||
);
|
||||
|
||||
const permissionsByModule = useMemo(() => {
|
||||
const map = new Map<string, AuthPermissionInfo[]>();
|
||||
for (const perm of permissions) {
|
||||
const module = perm.module || "其他";
|
||||
const list = map.get(module) ?? [];
|
||||
list.push(perm);
|
||||
map.set(module, list);
|
||||
}
|
||||
return map;
|
||||
}, [permissions]);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -169,6 +204,14 @@ export default function UserPermissionManagement({
|
||||
const roleColumns: ColumnsType<AuthRoleInfo> = [
|
||||
{ title: "角色编码", dataIndex: "roleCode", key: "roleCode", width: 220 },
|
||||
{ title: "角色名称", dataIndex: "roleName", key: "roleName", width: 180 },
|
||||
{
|
||||
title: "类型",
|
||||
dataIndex: "isBuiltIn",
|
||||
key: "isBuiltIn",
|
||||
width: 100,
|
||||
render: (isBuiltIn?: boolean) =>
|
||||
isBuiltIn ? <Tag color="blue">内置</Tag> : <Tag color="orange">自定义</Tag>,
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
dataIndex: "enabled",
|
||||
@@ -183,6 +226,49 @@ export default function UserPermissionManagement({
|
||||
key: "description",
|
||||
render: (value?: string) => value || "-",
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "actions",
|
||||
width: 220,
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setEditingRole(record);
|
||||
editRoleForm.setFieldsValue({
|
||||
roleName: record.roleName,
|
||||
description: record.description ?? "",
|
||||
enabled: record.enabled ?? true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => void openPermissionConfig(record)}
|
||||
>
|
||||
权限配置
|
||||
</Button>
|
||||
{!record.isBuiltIn && (
|
||||
<Popconfirm
|
||||
title="确定要删除该角色吗?"
|
||||
description="删除后关联的用户绑定将同步移除"
|
||||
onConfirm={() => void handleDeleteRole(record.id)}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Button type="link" size="small" danger>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const permissionColumns: ColumnsType<AuthPermissionInfo> = [
|
||||
@@ -250,6 +336,97 @@ export default function UserPermissionManagement({
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateRole = async () => {
|
||||
try {
|
||||
const values = await createRoleForm.validateFields();
|
||||
setSubmitting(true);
|
||||
await createRoleUsingPost({
|
||||
roleCode: values.roleCode,
|
||||
roleName: values.roleName,
|
||||
description: values.description,
|
||||
});
|
||||
message.success("角色创建成功");
|
||||
setCreateRoleOpen(false);
|
||||
createRoleForm.resetFields();
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
if (error && typeof error === "object" && "errorFields" in error) {
|
||||
return;
|
||||
}
|
||||
message.error("角色创建失败");
|
||||
console.error("角色创建失败:", error);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateRole = async () => {
|
||||
if (!editingRole) return;
|
||||
try {
|
||||
const values = await editRoleForm.validateFields();
|
||||
setSubmitting(true);
|
||||
await updateRoleUsingPut(editingRole.id, {
|
||||
roleName: values.roleName,
|
||||
description: values.description,
|
||||
enabled: values.enabled,
|
||||
});
|
||||
message.success("角色更新成功");
|
||||
setEditingRole(null);
|
||||
editRoleForm.resetFields();
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
if (error && typeof error === "object" && "errorFields" in error) {
|
||||
return;
|
||||
}
|
||||
message.error("角色更新失败");
|
||||
console.error("角色更新失败:", error);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteRole = async (roleId: string) => {
|
||||
try {
|
||||
await deleteRoleUsingDelete(roleId);
|
||||
message.success("角色删除成功");
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
message.error("角色删除失败");
|
||||
console.error("角色删除失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const openPermissionConfig = async (role: AuthRoleInfo) => {
|
||||
setPermConfigRole(role);
|
||||
setPermConfigLoading(true);
|
||||
try {
|
||||
const response = (await getRolePermissionsUsingGet(role.id)) as ApiResponse<RoleWithPermissionsResponse>;
|
||||
setSelectedPermissionIds(response?.data?.permissionIds ?? []);
|
||||
} catch (error) {
|
||||
message.error("加载角色权限失败");
|
||||
console.error("加载角色权限失败:", error);
|
||||
setPermConfigRole(null);
|
||||
} finally {
|
||||
setPermConfigLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBindPermissions = async () => {
|
||||
if (!permConfigRole) return;
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await bindRolePermissionsUsingPut(permConfigRole.id, selectedPermissionIds);
|
||||
message.success("权限配置成功");
|
||||
setPermConfigRole(null);
|
||||
setSelectedPermissionIds([]);
|
||||
} catch (error) {
|
||||
message.error("权限配置失败");
|
||||
console.error("权限配置失败:", error);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!canShowAnything) {
|
||||
return <Empty description="当前账号无用户与权限管理权限" />;
|
||||
}
|
||||
@@ -266,7 +443,14 @@ export default function UserPermissionManagement({
|
||||
/>
|
||||
</Card>
|
||||
{canViewRoles && (
|
||||
<Card title="角色列表">
|
||||
<Card
|
||||
title="角色列表"
|
||||
extra={
|
||||
<Button type="primary" onClick={() => setCreateRoleOpen(true)}>
|
||||
创建角色
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
loading={loading}
|
||||
rowKey="id"
|
||||
@@ -287,6 +471,8 @@ export default function UserPermissionManagement({
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 分配角色 Modal */}
|
||||
<Modal
|
||||
title={`分配角色 - ${editingUser?.username || ""}`}
|
||||
open={Boolean(editingUser)}
|
||||
@@ -315,7 +501,122 @@ export default function UserPermissionManagement({
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* 创建角色 Modal */}
|
||||
<Modal
|
||||
title="创建角色"
|
||||
open={createRoleOpen}
|
||||
confirmLoading={submitting}
|
||||
onOk={() => {
|
||||
void handleCreateRole();
|
||||
}}
|
||||
onCancel={() => {
|
||||
setCreateRoleOpen(false);
|
||||
createRoleForm.resetFields();
|
||||
}}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={createRoleForm} layout="vertical" style={{ marginTop: 16 }}>
|
||||
<Form.Item
|
||||
name="roleCode"
|
||||
label="角色编码"
|
||||
rules={[{ required: true, message: "请输入角色编码" }]}
|
||||
>
|
||||
<Input placeholder="如:data_analyst" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="roleName"
|
||||
label="角色名称"
|
||||
rules={[{ required: true, message: "请输入角色名称" }]}
|
||||
>
|
||||
<Input placeholder="如:数据分析师" />
|
||||
</Form.Item>
|
||||
<Form.Item name="description" label="描述">
|
||||
<Input.TextArea rows={3} placeholder="角色描述(可选)" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 编辑角色 Modal */}
|
||||
<Modal
|
||||
title="编辑角色"
|
||||
open={Boolean(editingRole)}
|
||||
confirmLoading={submitting}
|
||||
onOk={() => {
|
||||
void handleUpdateRole();
|
||||
}}
|
||||
onCancel={() => {
|
||||
setEditingRole(null);
|
||||
editRoleForm.resetFields();
|
||||
}}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={editRoleForm} layout="vertical" style={{ marginTop: 16 }}>
|
||||
<Form.Item label="角色编码">
|
||||
<Input value={editingRole?.roleCode} disabled />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="roleName"
|
||||
label="角色名称"
|
||||
rules={[{ required: true, message: "请输入角色名称" }]}
|
||||
>
|
||||
<Input placeholder="角色名称" />
|
||||
</Form.Item>
|
||||
<Form.Item name="description" label="描述">
|
||||
<Input.TextArea rows={3} placeholder="角色描述(可选)" />
|
||||
</Form.Item>
|
||||
<Form.Item name="enabled" label="启用状态" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 权限配置 Modal */}
|
||||
<Modal
|
||||
title={`权限配置 - ${permConfigRole?.roleName || ""}`}
|
||||
open={Boolean(permConfigRole)}
|
||||
confirmLoading={submitting}
|
||||
onOk={() => {
|
||||
void handleBindPermissions();
|
||||
}}
|
||||
onCancel={() => {
|
||||
setPermConfigRole(null);
|
||||
setSelectedPermissionIds([]);
|
||||
}}
|
||||
width={640}
|
||||
destroyOnClose
|
||||
>
|
||||
{permConfigLoading ? (
|
||||
<Typography.Text type="secondary">加载中...</Typography.Text>
|
||||
) : permissions.length === 0 ? (
|
||||
<Typography.Text type="secondary">暂无可配置权限</Typography.Text>
|
||||
) : (
|
||||
<div style={{ maxHeight: 400, overflow: "auto" }}>
|
||||
<Checkbox.Group
|
||||
value={selectedPermissionIds}
|
||||
onChange={(values) => setSelectedPermissionIds(values as string[])}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{Array.from(permissionsByModule.entries()).map(([module, perms]) => (
|
||||
<div key={module} style={{ marginBottom: 16 }}>
|
||||
<Typography.Text strong style={{ display: "block", marginBottom: 8 }}>
|
||||
{module}
|
||||
</Typography.Text>
|
||||
<Row>
|
||||
{perms.map((perm) => (
|
||||
<Col span={12} key={perm.id}>
|
||||
<Checkbox value={perm.id}>
|
||||
{perm.permissionName}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
))}
|
||||
</Checkbox.Group>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ export interface AuthRoleInfo {
|
||||
roleName: string;
|
||||
description?: string;
|
||||
enabled?: boolean;
|
||||
isBuiltIn?: boolean;
|
||||
}
|
||||
|
||||
export interface AuthPermissionInfo {
|
||||
@@ -85,3 +86,38 @@ export function listAuthPermissionsUsingGet() {
|
||||
export function assignUserRolesUsingPut(userId: number, roleIds: string[]) {
|
||||
return put(`/api/auth/users/${userId}/roles`, { roleIds });
|
||||
}
|
||||
|
||||
export interface RoleWithPermissionsResponse {
|
||||
role: AuthRoleInfo;
|
||||
permissionIds: string[];
|
||||
}
|
||||
|
||||
export function createRoleUsingPost(data: {
|
||||
roleCode: string;
|
||||
roleName: string;
|
||||
description?: string;
|
||||
}) {
|
||||
return post("/api/auth/roles", data);
|
||||
}
|
||||
|
||||
export function updateRoleUsingPut(
|
||||
roleId: string,
|
||||
data: { roleName: string; description?: string; enabled?: boolean }
|
||||
) {
|
||||
return put(`/api/auth/roles/${roleId}`, data);
|
||||
}
|
||||
|
||||
export function deleteRoleUsingDelete(roleId: string) {
|
||||
return del(`/api/auth/roles/${roleId}`);
|
||||
}
|
||||
|
||||
export function getRolePermissionsUsingGet(roleId: string) {
|
||||
return get(`/api/auth/roles/${roleId}/permissions`);
|
||||
}
|
||||
|
||||
export function bindRolePermissionsUsingPut(
|
||||
roleId: string,
|
||||
permissionIds: string[]
|
||||
) {
|
||||
return put(`/api/auth/roles/${roleId}/permissions`, { permissionIds });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user