菜单可正确被加载

This commit is contained in:
dengqichen 2024-12-02 17:53:55 +08:00
parent c0027f4c13
commit c9c591c97c
6 changed files with 198 additions and 144 deletions

View File

@ -38,10 +38,11 @@ src/
├── utils/ # 工具函数
├── hooks/ # 自定义 Hooks
└── types/ # TS 类型定义
│ └── pages.ts # 分页基础类
ModuleName/ # 模块结构
├── components/ # 模块私有组件
├── types/ # 类型定义
├── type.ts # 类型定义
├── service.ts # API 服务
└── index.tsx # 模块入口
```
@ -64,7 +65,7 @@ ModuleName/ # 模块结构
- 事件处理:`handle` 前缀
- 异步函数:动词开头(`fetchData`
3. 基础类型:
3. 已定义了基础类型所以直接继承即可把Response query request 的定义都模块的types.ts文件中
```typescript
interface BaseResponse {
id: number;

View File

@ -1,127 +1,153 @@
import React, { useEffect, useState } from 'react';
import { Modal, Table, message, Spin, Tag } from 'antd';
import type { Permission } from '../types';
import { PermissionType, PermissionTypeText } from '../types';
import { getRolePermissions, assignPermissions, getAllPermissions } from '../service';
import { Modal, Tree, Spin } from 'antd';
import type { DataNode } from 'antd/es/tree';
import type { Key } from 'rc-tree/lib/interface';
import { getPermissionTree } from '../service';
interface PermissionModalProps {
roleId: number;
visible: boolean;
roleId: number;
onCancel: () => void;
onSuccess: () => void;
onOk: (permissionIds: number[]) => void;
defaultCheckedKeys?: number[];
}
interface Permission {
id: number;
code: string;
name: string;
type: string;
sort: number;
}
interface MenuItem {
id: number;
name: string;
path: string;
icon: string;
type: number;
permissionChildren: MenuItem[];
permissions: Permission[];
}
const PermissionModal: React.FC<PermissionModalProps> = ({
roleId,
visible,
roleId,
onCancel,
onSuccess
onOk,
defaultCheckedKeys = []
}) => {
const [permissions, setPermissions] = useState<Permission[]>([]);
const [selectedKeys, setSelectedKeys] = useState<number[]>([]);
const [loading, setLoading] = useState(false);
const [treeData, setTreeData] = useState<DataNode[]>([]);
const [checkedKeys, setCheckedKeys] = useState<number[]>(defaultCheckedKeys);
// 获取角色已有权限和所有权限
const fetchData = async () => {
try {
setLoading(true);
const [allPermissions, rolePermissions] = await Promise.all([
getAllPermissions(),
getRolePermissions(roleId)
]);
setPermissions(allPermissions);
setSelectedKeys(rolePermissions.map(p => p.id));
} catch (error) {
message.error('获取权限数据失败');
} finally {
setLoading(false);
// 将菜单和权限数据转换为Tree组件需要的格式
const convertToTreeData = (menuList: MenuItem[]): { treeData: DataNode[], idMapping: Record<string, number> } => {
const idMapping: Record<string, number> = {};
const convertMenuToNode = (menu: MenuItem): DataNode => {
const node: DataNode = {
key: `menu-${menu.id}`,
title: menu.name,
children: []
};
// 添加功能权限节点
if (menu.permissions?.length > 0) {
const permissionNodes = menu.permissions.map(perm => {
const key = `permission-${perm.id}`;
idMapping[key] = perm.id;
return {
key,
title: perm.name,
isLeaf: true
};
});
node.children!.push(...permissionNodes);
}
// 递归处理子菜单
if (menu.permissionChildren?.length > 0) {
const childNodes = menu.permissionChildren.map(convertMenuToNode);
node.children!.push(...childNodes);
}
return node;
};
const treeData = menuList.map(convertMenuToNode);
return { treeData, idMapping };
};
useEffect(() => {
if (visible) {
fetchData();
}
}, [visible, roleId]);
const loadPermissionTree = async () => {
if (!visible) return;
const handleOk = async () => {
try {
setLoading(true);
await assignPermissions(roleId, selectedKeys);
message.success('权限更新成功');
onSuccess();
try {
const response = await getPermissionTree();
const { treeData, idMapping } = convertToTreeData(response);
setTreeData(treeData);
// 将默认选中的ID转换为Tree需要的key
const defaultKeys = defaultCheckedKeys.map(id => {
const key = Object.entries(idMapping).find(([_, value]) => value === id)?.[0];
return key;
}).filter(Boolean);
setCheckedKeys(defaultCheckedKeys);
} catch (error) {
message.error('权限更新失败');
console.error('获取权限树失败:', error);
} finally {
setLoading(false);
}
};
const getPermissionTypeTag = (type: PermissionType) => {
const colorMap = {
[PermissionType.MENU]: 'blue',
[PermissionType.BUTTON]: 'green',
[PermissionType.API]: 'orange'
};
return (
<Tag color={colorMap[type]}>
{PermissionTypeText[type]}
</Tag>
);
loadPermissionTree();
}, [visible, defaultCheckedKeys]);
const handleCheck = (checked: Key[] | { checked: Key[]; halfChecked: Key[] }) => {
const checkedKeys = Array.isArray(checked) ? checked : checked.checked;
const permissionIds: number[] = [];
checkedKeys.forEach(key => {
const keyStr = key.toString();
if (keyStr.startsWith('permission-')) {
const id = parseInt(keyStr.replace('permission-', ''));
if (!isNaN(id)) {
permissionIds.push(id);
}
}
});
setCheckedKeys(permissionIds);
};
const columns = [
{
title: '权限编码',
dataIndex: 'code',
width: 150
},
{
title: '权限名称',
dataIndex: 'name',
width: 150
},
{
title: '权限类型',
dataIndex: 'type',
width: 120,
render: (type: PermissionType) => getPermissionTypeTag(type)
},
{
title: '所属菜单',
dataIndex: 'menuName',
width: 150
},
{
title: '描述',
dataIndex: 'description',
ellipsis: true
}
];
const handleOk = () => {
onOk(checkedKeys);
};
// 将权限ID转换为Tree需要的key
const getTreeCheckedKeys = (): string[] => {
return checkedKeys.map(id => `permission-${id}`);
};
return (
<Modal
title="分配权限"
open={visible}
onOk={handleOk}
onCancel={onCancel}
width={800}
confirmLoading={loading}
destroyOnClose
onOk={handleOk}
width={600}
bodyStyle={{ maxHeight: '60vh', overflow: 'auto' }}
>
<Spin spinning={loading}>
<Table
rowSelection={{
type: 'checkbox',
selectedRowKeys: selectedKeys,
onChange: (keys) => setSelectedKeys(keys as number[])
}}
columns={columns}
dataSource={permissions}
rowKey="id"
pagination={false}
scroll={{ y: 400 }}
size="small"
<Tree
checkable
checkedKeys={getTreeCheckedKeys()}
onCheck={handleCheck}
treeData={treeData}
defaultExpandAll
/>
</Spin>
</Modal>

View File

@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react';
import { Card, Button, Table, Space, Modal, Form, Input, InputNumber, Switch, message, Tag } from 'antd';
import { Card, Button, Table, Space, Modal, Form, Input, InputNumber, message, Tag } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, KeyOutlined, TagsOutlined } from '@ant-design/icons';
import type { RoleResponse, RoleTagResponse, RoleQuery } from './types';
import { getRoleList, createRole, updateRole, deleteRole } from './service';
import { getRoleList, createRole, updateRole, deleteRole, getRolePermissions, assignPermissions } from './service';
import type { TablePaginationConfig } from 'antd/es/table';
import type { SorterResult } from 'antd/es/table/interface';
import type {FilterValue, SorterResult} from 'antd/es/table/interface';
import PermissionModal from './components/PermissionModal';
import TagModal from './components/TagModal';
@ -15,9 +15,10 @@ const RolePage: React.FC = () => {
const [confirmLoading, setConfirmLoading] = useState(false);
const [permissionModalVisible, setPermissionModalVisible] = useState(false);
const [tagModalVisible, setTagModalVisible] = useState(false);
const [selectedRole, setSelectedRole] = useState<RoleResponse | null>(null);
const [selectedRole, setSelectedRole] = useState<RoleResponse>();
const [loading, setLoading] = useState(false);
const [roles, setRoles] = useState<RoleResponse[]>([]);
const [defaultCheckedKeys, setDefaultCheckedKeys] = useState<number[]>([]);
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
@ -111,9 +112,54 @@ const RolePage: React.FC = () => {
}
};
const handleAssignPermissions = (record: RoleResponse) => {
// 处理表格变化
const handleTableChange = (
newPagination: TablePaginationConfig,
filters: Record<string, FilterValue | null>,
sorter: SorterResult<RoleResponse> | SorterResult<RoleResponse>[]
) => {
const { field, order } = Array.isArray(sorter) ? sorter[0] : sorter;
setSortField(field as string);
setSortOrder(order as 'ascend' | 'descend');
const params = {
current: newPagination.current,
pageSize: newPagination.pageSize,
sortField: field as string,
sortOrder: order === 'ascend' ? 'asc' : order === 'descend' ? 'desc' : undefined
};
fetchRoles({
pageNum: params.current,
pageSize: params.pageSize,
sortField: params.sortField,
sortOrder: params.sortOrder
});
};
// 处理分配权限
const handleAssignPermissions = async (record: RoleResponse) => {
try {
const permissions = await getRolePermissions(record.id);
setSelectedRole(record);
setPermissionModalVisible(true);
setDefaultCheckedKeys(permissions);
} catch (error) {
message.error('获取角色权限失败');
}
};
// 处理权限分配确认
const handlePermissionAssign = async (permissionIds: number[]) => {
if (!selectedRole) return;
try {
await assignPermissions(selectedRole.id, permissionIds);
message.success('权限分配成功');
setPermissionModalVisible(false);
fetchRoles();
} catch (error) {
message.error('权限分配失败');
}
};
const handleAssignTags = (record: RoleResponse) => {
@ -121,22 +167,6 @@ const RolePage: React.FC = () => {
setTagModalVisible(true);
};
const handleTableChange = (
pagination: TablePaginationConfig,
_: any,
sorter: SorterResult<RoleResponse>
) => {
const { field, order } = sorter;
setSortField(field as string);
setSortOrder(order as 'ascend' | 'descend');
fetchRoles({
pageNum: pagination.current,
pageSize: pagination.pageSize,
sortField: field as string,
sortOrder: order === 'ascend' ? 'asc' : order === 'descend' ? 'desc' : undefined
});
};
const columns = [
{
title: '角色编码',
@ -285,13 +315,11 @@ const RolePage: React.FC = () => {
{selectedRole && (
<>
<PermissionModal
roleId={selectedRole.id}
visible={permissionModalVisible}
roleId={selectedRole.id}
onCancel={() => setPermissionModalVisible(false)}
onSuccess={() => {
setPermissionModalVisible(false);
fetchRoles();
}}
onOk={handlePermissionAssign}
defaultCheckedKeys={defaultCheckedKeys}
/>
<TagModal
roleId={selectedRole.id}

View File

@ -31,10 +31,13 @@ export const updateRoleTag = (id: number, data: RoleTagRequest) => request.put(`
export const createRoleTag = (data: RoleTagRequest) => request.post<RoleTagResponse>(ROLE_TAG_BASE_URL, data);
// 获取角色的权限列表
export const getRolePermissions = (roleId: number) => request.get<Permission[]>(`${BASE_URL}/${roleId}/permissions`);
export const getRolePermissions = (roleId: number) => request.get<string[]>(`${BASE_URL}/${roleId}/permissions`);
// 分配权限
export const assignPermissions = (roleId: number, permissionIds: number[]) => request.post(`${BASE_URL}/${roleId}/permissions`, permissionIds);
export const assignPermissions = (roleId: number, permissionIds: Number[]) => request.post(`${BASE_URL}/${roleId}/permissions`, permissionIds);
// 获取所有权限列表
export const getAllPermissions = () => request.get<Permission[]>('/api/v1/permission/list');
// 获取权限树
export const getPermissionTree = () => request.get('/api/v1/menu/permission-tree');

View File

@ -1,4 +1,5 @@
import type { BaseResponse } from '@/types/base/response';
import type { BaseQuery } from '@/types/base/query';
// 权限类型枚举
export enum PermissionType {
@ -15,14 +16,14 @@ export const PermissionTypeText = {
};
// 角色查询参数
export interface RoleQuery {
export interface RoleQuery extends BaseQuery {
code?: string;
name?: string;
enabled?: boolean;
pageNum?: number;
pageSize?: number;
sortField?: string;
sortOrder?: 'asc' | 'desc';
code?: string;
name?: string;
enabled?: boolean;
}
// 角色请求参数
@ -31,6 +32,7 @@ export interface RoleRequest {
name: string;
description?: string;
sort?: number;
enabled?: boolean;
}
// 角色响应数据
@ -46,16 +48,16 @@ export interface RoleResponse extends BaseResponse {
// 角色标签请求参数
export interface RoleTagRequest {
code: string;
name: string;
color?: string;
description?: string;
color: string;
}
// 角色标签响应数据
export interface RoleTagResponse extends BaseResponse {
code: string;
name: string;
color: string;
description?: string;
}
// 权限数据
@ -63,10 +65,6 @@ export interface Permission {
id: number;
code: string;
name: string;
type: PermissionType;
menuId: number;
menuName?: string;
enabled: boolean;
type: string;
sort: number;
description?: string;
}

View File

@ -35,9 +35,7 @@ const userSlice = createSlice({
},
setMenus: (state, action: PayloadAction<MenuResponse[]>) => {
state.menus = action.payload;
console.log(action.payload)
// console.log(JSON.stringify(action.payload))
// localStorage.setItem('menus', action.payload);
localStorage.setItem('menus', JSON.stringify(action.payload));
},
logout: (state) => {
state.token = null;