菜单可正确被加载

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

View File

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

View File

@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react'; 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 { PlusOutlined, EditOutlined, DeleteOutlined, KeyOutlined, TagsOutlined } from '@ant-design/icons';
import type { RoleResponse, RoleTagResponse, RoleQuery } from './types'; 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 { 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 PermissionModal from './components/PermissionModal';
import TagModal from './components/TagModal'; import TagModal from './components/TagModal';
@ -15,9 +15,10 @@ const RolePage: React.FC = () => {
const [confirmLoading, setConfirmLoading] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false);
const [permissionModalVisible, setPermissionModalVisible] = useState(false); const [permissionModalVisible, setPermissionModalVisible] = useState(false);
const [tagModalVisible, setTagModalVisible] = 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 [loading, setLoading] = useState(false);
const [roles, setRoles] = useState<RoleResponse[]>([]); const [roles, setRoles] = useState<RoleResponse[]>([]);
const [defaultCheckedKeys, setDefaultCheckedKeys] = useState<number[]>([]);
const [pagination, setPagination] = useState<TablePaginationConfig>({ const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1, current: 1,
pageSize: 10, pageSize: 10,
@ -111,9 +112,54 @@ const RolePage: React.FC = () => {
} }
}; };
const handleAssignPermissions = (record: RoleResponse) => { // 处理表格变化
setSelectedRole(record); const handleTableChange = (
setPermissionModalVisible(true); 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) => { const handleAssignTags = (record: RoleResponse) => {
@ -121,22 +167,6 @@ const RolePage: React.FC = () => {
setTagModalVisible(true); 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 = [ const columns = [
{ {
title: '角色编码', title: '角色编码',
@ -285,13 +315,11 @@ const RolePage: React.FC = () => {
{selectedRole && ( {selectedRole && (
<> <>
<PermissionModal <PermissionModal
roleId={selectedRole.id}
visible={permissionModalVisible} visible={permissionModalVisible}
roleId={selectedRole.id}
onCancel={() => setPermissionModalVisible(false)} onCancel={() => setPermissionModalVisible(false)}
onSuccess={() => { onOk={handlePermissionAssign}
setPermissionModalVisible(false); defaultCheckedKeys={defaultCheckedKeys}
fetchRoles();
}}
/> />
<TagModal <TagModal
roleId={selectedRole.id} 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 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 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 { BaseResponse } from '@/types/base/response';
import type { BaseQuery } from '@/types/base/query';
// 权限类型枚举 // 权限类型枚举
export enum PermissionType { 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; pageNum?: number;
pageSize?: number; pageSize?: number;
sortField?: string; sortField?: string;
sortOrder?: 'asc' | 'desc'; sortOrder?: 'asc' | 'desc';
code?: string;
name?: string;
enabled?: boolean;
} }
// 角色请求参数 // 角色请求参数
@ -31,6 +32,7 @@ export interface RoleRequest {
name: string; name: string;
description?: string; description?: string;
sort?: number; sort?: number;
enabled?: boolean;
} }
// 角色响应数据 // 角色响应数据
@ -46,16 +48,16 @@ export interface RoleResponse extends BaseResponse {
// 角色标签请求参数 // 角色标签请求参数
export interface RoleTagRequest { export interface RoleTagRequest {
code: string;
name: string; name: string;
color?: string; color: string;
description?: string;
} }
// 角色标签响应数据 // 角色标签响应数据
export interface RoleTagResponse extends BaseResponse { export interface RoleTagResponse extends BaseResponse {
code: string;
name: string; name: string;
color: string; color: string;
description?: string;
} }
// 权限数据 // 权限数据
@ -63,10 +65,6 @@ export interface Permission {
id: number; id: number;
code: string; code: string;
name: string; name: string;
type: PermissionType; type: string;
menuId: number;
menuName?: string;
enabled: boolean;
sort: number; sort: number;
description?: string;
} }

View File

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