当前位置: 首页 > news >正文

从0开始的中后台管理系统-5(菜单的路径绑定以及角色页面的实现)

1.菜单和路由页面双向绑定     

        实现了用户管理以及菜单管理和部门管理以及工作台这些路由组件之后,我们还没有和导航栏绑定这些路径,也就是我们需要在导航栏添加这些组件的名称以及点击后可以跳转到对应的路径。也就是说我们需要访问一个路由,可以返回给我们当前的菜单列表菜单名称以及路径。然后我们需要去定义一个数组,去通过递归的方式获取到里面的路径以及菜单名称还有图表用于跳转以及在导航里面展示。

       首先调用这个路由的时机肯定是在访问所有的路由路由之前,也就是我们需要给布局组件一个loader然后绑定一个函数去提前请求路由拿到权限列表,然后把里面包含的菜单列表传递到一个递归获取path的函数里面。

          

import api from "@/api"
import { getMenuPath } from "@/utils"
export default async function AuthLoader(){const data = await api.getPermissionList()const menuPathList = getMenuPath(data.menuList)console.log('menuPathList',menuPathList)
return {buttonList:data.buttonList,menuList:data.menuList,menuPathList:menuPathList
}
}
// 获取页面路径
export const getMenuPath = (list: Menu.MenuItem[]): string[] => {return list.reduce((result: string[], item: Menu.MenuItem) => {// 先收集当前菜单的 path(如果存在)if (item.path) {result.push(item.path);}// 递归 children(无论是否有按钮)if (Array.isArray(item.children) && item.children.length > 0) {result = result.concat(getMenuPath(item.children));}return result;}, []);
};

        这里我们获取到了路由数组,然后我们就可以去导航区域绑定。

        用useRouteLoaderData钩子可以获取到对应路由loader返回的数据通过这个拿到路径列表。以及菜单列表还有按钮权限列表。

        然后我们通过拿到的菜单列表遍历生成导航需要的菜单列表树,设置列表树每一个需要label和key以及icon和children然后递归往里面传入参数。

import React, { useEffect, useState } from 'react'
import { Menu as IMenu } from 'antd'
import type { Menu } from '@/types/api'
import {DesktopOutlined,SettingOutlined,TeamOutlined
} from '@ant-design/icons'
import styles from './index.module.less'
import { useLocation, useNavigate, useRouteLoaderData } from 'react-router-dom'
import type { MenuProps } from 'antd'
import * as Icons from '@ant-design/icons'
export default function SideMenu() {const [menuList, setMenuList] = useState<MenuItem[]>([])const data = useRouteLoaderData('layout')const [selectedKeys, setSelectKeys] = useState<string[]>([])console.log(' data', data)const { pathname } = useLocation()type MenuItem = Required<MenuProps>['items'][number]const navigite = useNavigate()// 生成每一个菜单项function getItem(label: React.ReactNode,key?: React.Key | null,icon?: React.ReactNode,children?: MenuItem[]): MenuItem {return {label,key,icon,children} as MenuItem}function createIcon(name?: string) {if (!name) return <></>const customerIcons: { [key: string]: any } = Iconsconst icon = customerIcons[name]if (!icon) return <></>return React.createElement(icon)}//递归生成菜单const getTreeMenu = (menuList: Menu.MenuItem[],treeList: MenuItem[] = []) => {menuList.forEach((item, index) => {if (item.menuType === 1 && item.menuState === 1) {if (item.button) {return treeList.push(getItem(item.menuName, item.path || index, createIcon(item.icon)))}treeList.push(getItem(item.menuName,item.path || index,createIcon(item.icon),getTreeMenu(item.children || [])))}})return treeList}useEffect(() => {const treeMenuList = getTreeMenu(data.menuList)setMenuList(treeMenuList)setSelectKeys([pathname])}, [])const handleClickLogo = () => {navigite('/welcome')}//点击跳转路由const handleClickMenu = ({ key }: { key: string }) => {setSelectKeys([key])navigite(key)}return (<div><div className={styles.logo} onClick={handleClickLogo}><img className={styles.img} src='/images/logo.png' alt='' /><span>木木货运</span></div><IMenumode='inline' //mode 模式设置的子导航打开方式 行内行外theme='dark'items={menuList}selectedKeys={selectedKeys}onClick={handleClickMenu}/></div>)
}

        然后将生成的树结构绑定给表单,然后点击事件就是跳转到对应的key。默认接收的参数就是点击事件发生的那一项。对象结构拿出key跳转即可。

        为了保留刷新之后还可以停留在当前路径,设置一个state去存储当前的pathname,然后组件挂载完之后就保存当前的pathname,然后双向绑定到菜单组件上。

2.角色页面

        角色页面和其他的页面相同。展示效果图。

        

        也是通过点击新增编辑然后展示表单框,通过useImperativeHandle方法保暴露自己的open方法,然后让父组件操作自己的展示列表的state。还有父组件用useAntdTable钩子通过接收Promise形式的请求返回值,以及默认的对象配置来生成对应的表单数据和search搜索方法。一键生成访问请求返回的数据以及search操作表单,search.submit就直接调用方法。

import React, { useRef } from 'react'
import api from '@/api/roleApi'
import { useAntdTable } from 'ahooks'
import { Button, Form, Input, Space, Table } from 'antd'
import { useForm } from 'antd/es/form/Form'
import type { Role } from '@/types/api'
import { toLocalDate } from '@/utils'
import type { IAction } from '@/types/modal'
import CreateRole from './CreateRole'
import type { ColumnsType } from 'antd/es/table'
export default function RoleList() {const roleRef = useRef<{open: (type: IAction, data?: Role.RoleItem) => void}>(null)const [form] = useForm()const getTableData = ({current,pageSize}: {current: numberpageSize: number},formData: Role.Params) => {return api.getRoleList({...formData,pageNum: current,pageSize: pageSize}).then(data => {return {total: data.page.total,list: data.list}})}const { tableProps, search } = useAntdTable(getTableData, {form,defaultPageSize: 5})const columns: ColumnsType<Role.RoleItem> = [{title: '角色名称',dataIndex: 'roleName',key: 'roleName'},{title: '备注',dataIndex: 'remark',key: 'remark'},{title: '更新时间',dataIndex: 'updateTime',key: 'updateTime',render(updataTime: string) {return toLocalDate(updataTime)}},{title: '创建时间',dataIndex: 'createTime',key: 'createTime',render(createTime: string) {return toLocalDate(createTime)}},{title: '操作',key: 'action',render(_, record) {return (<Space><ButtononClick={() => {handleEdit(record)}}>编辑</Button><Button>设置权限</Button><Button>删除</Button></Space>)}}]//创建角色const handleCreate = () => {roleRef.current?.open('create')}//编辑角色const handleEdit = (data: Role.RoleItem) => {roleRef.current?.open('edit', data)}return (<div className='role-warp'><Form form={form} className='search-form' layout='inline'><Form.Item name='roleName' label='角色名称'><Input placeholder='请输入角色名称' /></Form.Item><Form.Item><Space><Button type='primary' onClick={search.submit}>搜索</Button><Button type='default' onClick={search.reset}>重置</Button></Space></Form.Item></Form><div className='base-table'><div className='header-wrapper'><div className='title'>角色列表</div><div className='action'><Button type='primary' onClick={handleCreate}>新增</Button></div></div><TablerowKey='userId' // 保证每行有唯一 key{...tableProps}borderedcolumns={columns}/></div><CreateRole mRef={roleRef} update={search.submit} /></div>)
}

        就如代码所展示的,然后是子组件表单弹出。

import type { Role } from '@/types/api'
import type { IAction, ImodalProp } from '@/types/modal'
import { Form, Input, message, Modal } from 'antd'
import { useForm } from 'antd/es/form/Form'
import React, { useState } from 'react'
import api from '@/api/roleApi'
import { useImperativeHandle } from 'react'
export default function CreateRole(props: ImodalProp<Role.RoleItem>) {const [form] = useForm()const [visible, setVisible] = useState(false)const [action, setAction] = useState<IAction>('create')//暴露子组件open方法useImperativeHandle(props.mRef, () => {return {open}})//调用弹窗显示方法const open = (type: IAction, data?: Role.RoleItem) => {setAction(type)setVisible(true)if (data) {form.setFieldsValue(data)}}const handleOk = async () => {const valid = await form.validateFields()if (valid) {const params = form.getFieldsValue()if (action === 'create') {await api.createRole(params)} else {await api.editRole(params)}message.success('操作成功')handleCancel()props.update()}}//取消const handleCancel = () => {form.resetFields()setVisible(false)}return (<Modaltitle={action === 'create' ? '新增角色' : '编辑角色'}width={600}open={visible}okText='确定'cancelText='取消'onOk={handleOk}onCancel={handleCancel}><Form form={form} labelAlign='right' labelCol={{ span: 4 }}>{/* 隐藏域 */}<Form.Item name='_id' hidden><Input /></Form.Item><Form.Itemname='roleName'label='角色名称'rules={[{ required: true, message: '请输入角色名称' }]}><Input placeholder='请输入角色名称' /></Form.Item><Form.Item name='remark' label='备注'><Input.TextArea placeholder='请输入备注' /></Form.Item></Form></Modal>)
}

http://www.dtcms.com/a/327268.html

相关文章:

  • 分布式光伏气象站:为光伏电站的 “气象感知眼”
  • 自建知识库,向量数据库 体系建设(一)之BERT 与.NET 4.5.2 的兼容困境:技术代差下的支持壁垒
  • AWS EKS 常用命令大全:从基础管理到高级运维
  • 开发npm包【详细教程】
  • AWS KMS VS AWS Cloud HSM VS AWS Secret Manager?
  • 开源!!! htop移植到OpenHarmony
  • 自动驾驶决策算法 —— 有限状态机 FSM
  • AI项目提示-提示词-属于-mcp-cli等
  • css初学者第五天
  • 【CSS 变量】让你的 CSS “活”起来:深入理解 CSS 自定义属性与主题切换
  • 现代 CSS工具
  • web前端第二次作业
  • 【CSS 视觉】无需JS,纯 CSS 实现酷炫视觉效果(clip-path, filter, backdrop-filter)
  • 微前端面试考点与答案
  • 纯CSS+JS制作抽奖大转盘
  • 【CSS3】录音中。。。
  • aspose word for java 使用书签进行内容填充和更新
  • AppStorageV2:鸿蒙全局状态管理详解-ArkUI本地存储
  • django 如何读取项目根目录下的文件内容
  • Python常用的5种中文分词工具
  • 力扣 hot100 Day71
  • Claude Code,Gemini CLI,Trae-agent, Qwen Code 使用对比及感受
  • 【数据分享】2020-2022年我国乡镇的逐日最高气温数据(Shp/Excel格式)
  • ABAC 权限策略扩展
  • 在达梦数据库中使用group by 命令报错问题
  • MCU中的液晶显示屏LCD(Liquid Crystal Display)控制器
  • Python 正则表达式 re.findall()
  • special topic 11 (1)
  • 【Linux系统】详解Ext2,文件系统
  • 打印流水号条形码