feat: 完善 RBAC 权限体系与权限管理页面
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import { Table, Modal, Form, Input, Space, Tag, Popconfirm, Card, Checkbox, Empty } from 'antd';
|
||||
import { PlusOutlined, EditOutlined, StopOutlined } from '@ant-design/icons';
|
||||
import api from '../../api';
|
||||
import PermissionButton from '../../components/PermissionButton';
|
||||
import EditableCell from '../../components/EditableCell';
|
||||
import { message } from '../../ui/app-message';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useApiMutation } from '../../hooks/useApiMutation';
|
||||
import { validateResponse } from '../../utils/validate';
|
||||
import { permissionTreeSchema, rolesSchema } from '../../api/schemas';
|
||||
import { getErrorMessage } from '../../utils/error';
|
||||
|
||||
interface PermissionItem {
|
||||
id: number;
|
||||
@@ -23,36 +28,62 @@ interface RoleItem {
|
||||
}
|
||||
|
||||
const RolesPage: React.FC = () => {
|
||||
const [data, setData] = useState<RoleItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<RoleItem | null>(null);
|
||||
const [allPerms, setAllPerms] = useState<{ group: string; permissions: PermissionItem[] }[]>([]);
|
||||
const [form] = Form.useForm();
|
||||
const [selectedPermIds, setSelectedPermIds] = useState<number[]>([]);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [roles, permTree] = await Promise.all([
|
||||
api.get('/rbac/roles') as Promise<RoleItem[]>,
|
||||
api.get('/rbac/permissions/tree') as Promise<
|
||||
{ group: string; permissions: PermissionItem[] }[]
|
||||
>,
|
||||
]);
|
||||
setData(roles);
|
||||
setAllPerms(permTree);
|
||||
} catch (e: unknown) {
|
||||
const err = e as { message?: string };
|
||||
message.error(err?.message || '加载失败,请稍后重试');
|
||||
}
|
||||
setLoading(false);
|
||||
}, []);
|
||||
const {
|
||||
data: fetchResult = { roles: [], permTree: [] },
|
||||
isLoading,
|
||||
isFetching,
|
||||
} = useQuery<{
|
||||
roles: RoleItem[];
|
||||
permTree: { group: string; permissions: PermissionItem[] }[];
|
||||
}>({
|
||||
queryKey: ['rbac', 'roles', 'permission-tree'],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const [roles, permTree] = await Promise.all([
|
||||
api.get('/rbac/roles') as Promise<RoleItem[]>,
|
||||
api.get('/rbac/permissions/tree') as Promise<
|
||||
{ group: string; permissions: PermissionItem[] }[]
|
||||
>,
|
||||
]);
|
||||
return {
|
||||
roles: validateResponse<RoleItem[]>(rolesSchema, roles),
|
||||
permTree: validateResponse<{ group: string; permissions: PermissionItem[] }[]>(
|
||||
permissionTreeSchema,
|
||||
permTree,
|
||||
),
|
||||
};
|
||||
} catch (e: unknown) {
|
||||
message.error(getErrorMessage(e, '加载失败,请稍后重试'));
|
||||
return { roles: [], permTree: [] };
|
||||
}
|
||||
},
|
||||
});
|
||||
const data = fetchResult.roles;
|
||||
const allPerms = fetchResult.permTree;
|
||||
const loading = isLoading || isFetching;
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
const saveMutation = useApiMutation(
|
||||
async (values: { name: string; description?: string; permissionIds: number[] }) =>
|
||||
editing
|
||||
? api.put(`/rbac/roles/${editing.id}`, values)
|
||||
: api.post('/rbac/roles', values),
|
||||
{ invalidate: [['rbac', 'roles', 'permission-tree']] },
|
||||
);
|
||||
const disableMutation = useApiMutation(
|
||||
async (id: number) => api.delete(`/rbac/roles/${id}`),
|
||||
{ invalidate: [['rbac', 'roles', 'permission-tree']] },
|
||||
);
|
||||
const saveCellMutation = useApiMutation(
|
||||
async ({ record, field, value }: { record: RoleItem; field: string; value: unknown }) =>
|
||||
api.put(`/rbac/roles/${record.id}`, { [field]: value }),
|
||||
{ invalidate: [['rbac', 'roles', 'permission-tree']] },
|
||||
);
|
||||
|
||||
const handleAdd = () => {
|
||||
setEditing(null);
|
||||
@@ -72,25 +103,15 @@ const RolesPage: React.FC = () => {
|
||||
setSaving(true);
|
||||
const values = await form.validateFields();
|
||||
try {
|
||||
if (editing) {
|
||||
await api.put(`/rbac/roles/${editing.id}`, {
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
permissionIds: selectedPermIds,
|
||||
});
|
||||
message.success('角色更新成功');
|
||||
} else {
|
||||
await api.post('/rbac/roles', {
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
permissionIds: selectedPermIds,
|
||||
});
|
||||
message.success('角色创建成功');
|
||||
}
|
||||
await saveMutation.mutateAsync({
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
permissionIds: selectedPermIds,
|
||||
});
|
||||
message.success(editing ? '角色更新成功' : '角色创建成功');
|
||||
setModalOpen(false);
|
||||
fetchData();
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '操作失败');
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
@@ -98,11 +119,10 @@ const RolesPage: React.FC = () => {
|
||||
|
||||
const handleDisable = async (id: number) => {
|
||||
try {
|
||||
await api.delete(`/rbac/roles/${id}`);
|
||||
await disableMutation.mutateAsync(id);
|
||||
message.success('角色已停用');
|
||||
fetchData();
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '停用失败');
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
}
|
||||
};
|
||||
|
||||
@@ -144,11 +164,14 @@ const RolesPage: React.FC = () => {
|
||||
);
|
||||
const saveCell = useCallback(
|
||||
async (record: RoleItem, field: string, value: unknown) => {
|
||||
await api.put(`/rbac/roles/${record.id}`, { [field]: value });
|
||||
message.success('已保存');
|
||||
await fetchData();
|
||||
try {
|
||||
await saveCellMutation.mutateAsync({ record, field, value });
|
||||
message.success('已保存');
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
}
|
||||
},
|
||||
[fetchData],
|
||||
[saveCellMutation],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
|
||||
Reference in New Issue
Block a user