feat(auth): 角色管理CRUD与角色权限绑定功能
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (java-kotlin) (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled

新增角色创建/编辑/删除接口和角色-权限绑定接口,支持管理员自定义角色并灵活配置权限。
前端新增角色CRUD弹窗、按模块分组的权限配置面板,内置角色禁止删除但允许编辑和配置权限。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-10 00:09:48 +08:00
parent ea7ca5474e
commit 06a7cd9abd
12 changed files with 583 additions and 6 deletions

View File

@@ -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>
);
}

View File

@@ -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 });
}