菜单可正确被加载
This commit is contained in:
parent
c0027f4c13
commit
c9c591c97c
@ -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;
|
||||
|
||||
@ -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();
|
||||
} catch (error) {
|
||||
message.error('权限更新失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
try {
|
||||
const response = await getPermissionTree();
|
||||
const { treeData, idMapping } = convertToTreeData(response);
|
||||
setTreeData(treeData);
|
||||
|
||||
const getPermissionTypeTag = (type: PermissionType) => {
|
||||
const colorMap = {
|
||||
[PermissionType.MENU]: 'blue',
|
||||
[PermissionType.BUTTON]: 'green',
|
||||
[PermissionType.API]: 'orange'
|
||||
// 将默认选中的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) {
|
||||
console.error('获取权限树失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
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>
|
||||
|
||||
@ -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) => {
|
||||
setSelectedRole(record);
|
||||
setPermissionModalVisible(true);
|
||||
// 处理表格变化
|
||||
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}
|
||||
|
||||
@ -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');
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user