IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 关于左侧菜单的开发和动态路由注册 -> 正文阅读

[JavaScript知识库]关于左侧菜单的开发和动态路由注册

关于左侧菜单的开发和动态路由注册

el-menu的菜单的基本认识和理解

  • el-menu
    • el-sub-menu
      • `<template #title>item four
      • el-menu-item
        • <template #title><span>item four</span></template>
      • el-menu-item
        • <template #title><span>item four</span></template>
    • el-menu-item
      • <template #title><span>item four</span></template>

还原web格式

  • 控制面板
    • 后台首页
    • 后台设置
  • 优惠券管理

在后端的菜单

存在两种情况,一种有子元素,一种是没子元素,所以我们就必须进行区分。区分的界限其实就通过一个menu.children.length > 0 说明存在子元素。反之,就没子元素。就直接显示,具体代码如下:

<el-menu :default-active="1" class="border-0" :unique-opened="true" :collapse-transition="false">

    <el-sub-menu index="1-1">
        <template #title>
            <el-icon><Location/></el-icon>
            <span>控制面板</span>
        </template>
        <el-menu-item index="1-1-1">
            <el-icon><Location/></el-icon>
            <span>后台首页</span>
        </el-menu-item>
        <el-menu-item index="1-1-2">
            <el-icon><User/></el-icon>
            <span>后台设置</span>
        </el-menu-item>
    </el-sub-menu>

    <el-menu-item index="2">
        <el-icon><Share/></el-icon>
        <span>优惠券管理</span>
    </el-menu-item>

</el-menu>

菜单接口的设计和递归 - 服务端

1: 数据库表
在这里插入图片描述

CREATE TABLE `kss_admin_menu` (
  `id` bigint(20) NOT NULL COMMENT '主键',
  `name` varchar(128) DEFAULT '0' COMMENT '菜单名词',
  `sorted` int(11) DEFAULT NULL COMMENT '菜单排序',
  `path` varchar(400) DEFAULT NULL COMMENT '菜单链接',
  `icon` varchar(128) DEFAULT NULL COMMENT '菜单图标',
  `status` int(11) DEFAULT NULL COMMENT '菜单发布',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `pid` bigint(20) DEFAULT NULL COMMENT '菜单名称',
  `componentname` varchar(200) DEFAULT NULL COMMENT '组件名称',
  `pathname` varchar(100) DEFAULT NULL COMMENT '路径名称',
  `layout` varchar(50) DEFAULT NULL COMMENT '父组件',
  `indexon` int(11) DEFAULT NULL COMMENT '排序',
  `showflag` int(1) DEFAULT NULL COMMENT '是否展示',
  `isdelete` int(1) DEFAULT NULL COMMENT '删除状态 0未删除 1删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='菜单管理 '

2: 创建实体

package com.pug.zixun.pojo;

import java.util.Date;
import java.util.List;

import lombok.*;
import com.baomidou.mybatisplus.annotation.*;

import org.pug.generator.anno.PugDoc;
/**
 * AdminMenu实体
 *
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName("kss_admin_menu")
public class AdminMenu  implements java.io.Serializable {

    @PugDoc(name="主键")
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    @PugDoc(name="菜单名词")
    private String name;
    @PugDoc(name="菜单链接")
    private String path;
    @PugDoc(name="路径名称")
    private String pathname;
    @PugDoc(name="菜单图标")
    private String icon;
    @PugDoc(name="菜单排序")
    private Integer sorted;
    @PugDoc(name="菜单发布")
    private Integer status;
    @PugDoc(name="创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @PugDoc(name="更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    @PugDoc(name="菜单名称")
    private Long pid;
    @PugDoc(name="删除状态 0未删除 1删除")
    private Integer isdelete;
    // 子集
    @TableField(exist = false)
    private List<AdminMenu> children;
}

3: AdminMenuMapper

package com.pug.zixun.mapper;

import com.pug.zixun.pojo.AdminMenu;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
 * AdminMenuMapper
 *
*/
public interface AdminMenuMapper extends BaseMapper<AdminMenu>{
}

4: AdminMenuService.java

package com.pug.zixun.service.adminmenu;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.pug.zixun.pojo.AdminMenu;
import com.pug.zixun.vo.AdminMenuVo;
import com.pug.zixun.bo.AdminMenuBo;
import com.pug.zixun.service.BaseService;
import java.util.List;

/**
 * IAdminMenuService接口
 *
*/
public interface IAdminMenuService extends IService<AdminMenu>,BaseService{

    /**
     * 查询菜单
     * @return
     */
    List<AdminMenu> findAdminMenuTree();
}

5: 实现类

package com.pug.zixun.service.adminmenu;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pug.zixun.mapper.AdminMenuMapper;
import com.pug.zixun.pojo.AdminMenu;
import com.pug.zixun.vo.AdminMenuVo;
import com.pug.zixun.bo.AdminMenuBo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import com.pug.zixun.commons.enums.ResultStatusEnum;
import com.pug.zixun.commons.ex.PugValidatorException;
import com.pug.zixun.commons.utils.fn.asserts.Vsserts;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;



/**
 * AdminMenuServiceImpl实现类
 *
*/
@Service
@Slf4j
public class AdminMenuServiceImpl extends ServiceImpl<AdminMenuMapper,AdminMenu> implements IAdminMenuService  {

  
    
    @Override
    public List<AdminMenu> findAdminMenuTree(){
        // 1 :查询表中所有的数据
        LambdaQueryWrapper<AdminMenu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(AdminMenu::getStatus,1);
        List<AdminMenu> allList = this.list(lambdaQueryWrapper); // 思考空间,为什么查询的是所有
        // 2: 找到所有的根节点 pid = 0
        List<AdminMenu> rootList = allList.stream().filter(category -> category.getPid().equals(0L))
                .sorted((a, b) -> a.getSorted() - b.getSorted()).collect(Collectors.toList());
        // 3 : 查询所有的非根节点
        List<AdminMenu> subList = allList.stream().filter(category -> !category.getPid().equals(0L)).collect(Collectors.toList());
        // 4 : 循环根节点去subList去找对应的子节点
        rootList.forEach(root -> buckForback(root, subList));

        return rootList;
    }

    private void buckForback(AdminMenu root, List<AdminMenu> subList) {
        // 通过根节点去id和子节点的pid是否相等,如果相等的话,代表是当前根的子集
        List<AdminMenu> childrenList = subList.stream().filter(category -> category.getPid().equals(root.getId()))
                .sorted((a, b) -> a.getSorted() - b.getSorted())
                .collect(Collectors.toList());
        // 如果你当前没一个子集,初始化一个空数组
        if (!CollectionUtils.isEmpty(childrenList)) {
            // 查询以后放回去
            root.setChildren(childrenList);
            // 再次递归构建即可
            childrenList.forEach(category -> buckForback(category, subList));
        } else {
            root.setChildren(new ArrayList<>());
        }
    }

}

6:定义菜单controller

package com.pug.zixun.controller.adminmenu;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.pug.zixun.service.adminmenu.IAdminMenuService;
import com.pug.zixun.pojo.AdminMenu;
import com.pug.zixun.vo.AdminMenuVo;
import com.pug.zixun.bo.AdminMenuBo;
import com.pug.zixun.commons.enums.ResultStatusEnum;
import com.pug.zixun.commons.ex.PugValidatorException;
import com.pug.zixun.commons.utils.fn.asserts.Vsserts;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import com.pug.zixun.controller.BaseController;
import java.util.List;
import org.pug.generator.anno.PugDoc;
/**
 * AdminMenuController
 *
*/

@RestController
@RequiredArgsConstructor
@Slf4j
@PugDoc(name="后台菜单",tabname="kss_admin_menu")
public class AdminMenuController extends BaseController{

    private final IAdminMenuService adminmenuService;

    /**
     * 查询分类的接口信息-tree
     *
     * @return
     */
    @PostMapping("/menu/tree")
    @PugDoc(name="查询后台菜单信息")
    public List<AdminMenu> tree() {
        return adminmenuService.findAdminMenuTree();
    }
}

7:分析原理

如何做到无限极菜单。通过表自映射过程

  • 控制面板 id=1 pid = 0
    • 后台首页 id=2 pid=1
    • 后台设置 id=3 pid=1
  • 优惠券管理 id=4 pid = 0
  • 用户管理 id=5 pid=0
    • 用户管理id=6 pid = 5
      • 用户设置id=7 pid = 6
        • 用户密码设置 id=8 pid = 7
      • 用户头像id =9 pid = 6
      • 用户审核 id=10 pid = 6
    • 用户添加id = 11 pid = 5
  • 菜单管理 id = 12 pid =0
  • 角色管理 id = 13 pid =0
  • 权限管理 id = 14 pid =0
    • 权限添加 id = 15 pid =14
    • 权限列表 id = 16 pid =14

上面的菜单或者未来的百度网盘的目录结构的设计,其实都是一张数据库表。来完成的。
原理就是通过 id 和 pid来形成自映射的过程.

数据递归方式由如下几种

  • 全查,不考虑父子关系,全部在java代码来完成

    • 这种数据量比较小的情况,可以考虑
    • 场景:菜单查询,分类查询
  • 查询数据库的方式

    • 根据pid=0查询所有的菜单根元素

    • 循环遍历,然后再根据id去查询表中pid=id子元素。

    • 场景:评论,百度目录设置 ,子元素查询和分页更注重异步去查询

      - 今天心情不不错,发生大事情 pid = 0 id=1
        - 是的  id=2 pid = 1
        - 棒棒的 id=3 pid = 1
        - 美美的 id=4 pid = 1
        查看更多(120)
        
      - 今天心情不不错,发生大事情 pid = 0 id=2
        - 是的  id=12 pid = 2
        - 帮帮的 id=13 pid = 2
        - 美美 id=14 pid = 2
        查看更多(12)  
      

因为菜单的数据的是非常小的。所有考虑第一种方案,全查。

查询所有菜单数据

// 查询表中所有的数据
LambdaQueryWrapper<AdminMenu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
// 把status=1和isdelete=0 查询出来
lambdaQueryWrapper.eq(AdminMenu::getStatus,1);
lambdaQueryWrapper.eq(AdminMenu::getIsdelete,0);
// 查询所有菜单
List<AdminMenu> allList = this.list(lambdaQueryWrapper);

得到代码如下:

在这里插入图片描述

过滤所有的根(父)元素

也就是pid=0的元素,如下

   // 过滤所有的根(父)元素 。条件是pid = 0 ,
        // 注意我这里的PID和ID数据类型是Long。在判断的时候记得一定要加一个L
        List<AdminMenu> rootMenuList = allList.stream().filter(menu -> menu.getPid().equals(0L)).collect(Collectors.toList());

结果如下:

  • 控制面板 id=1 pid = 0
  • 优惠券管理 id=4 pid = 0
  • 用户管理 id=5 pid=0
  • 菜单管理 id = 12 pid =0
  • 角色管理 id = 13 pid =0
  • 权限管理 id = 14 pid =0

遍历父元素开始找子元素

  • 条件:把子元素的pid = 父元素id找出来即可

  • 如何存放?子元素找出来多个还是单个?

    • 肯定也是List,肯定是多个。
    // 子集
    @TableField(exist = false)
    private List<AdminMenu> children;
    

代码如下:

// 遍历父元素开始找子元素
rootMenuList = rootMenuList.stream().map(rootMenu -> {
    List<AdminMenu> childrenMenuList = allList.stream()
        .filter(menu -> menu.getPid().equals(rootMenu.getId())).collect(Collectors.toList());
    // 这个判断的主要目的:是为了防止空集合,创建要给空集合对象,让json转换的时候由[].方便后续的判断长度==0
    if (CollectionUtils.isEmpty(childrenMenuList)) {
        childrenMenuList = new ArrayList<>();
    }

    // 记得把找的子元素放入到children集合中
    rootMenu.setChildren(childrenMenuList);
    return rootMenu;
}).collect(Collectors.toList());

结构入下:

  • 控制面板 id=1 pid = 0
    • 后台首页 id=2 pid=1
    • 后台设置 id=3 pid=1
  • 优惠券管理 id=4 pid = 0
  • 用户管理 id=5 pid=0
    • 用户管理id=6 pid = 5
    • 用户添加id = 11 pid = 5
  • 菜单管理 id = 12 pid =0
  • 角色管理 id = 13 pid =0
  • 权限管理 id = 14 pid =0
    • 权限添加 id = 15 pid =14
    • 权限列表 id = 16 pid =14

最终递归代码

 /**
     * 查询菜单
     * @return
     */
    List<AdminMenu> findAdminMenuTree() {
        // 查询表中所有的数据
        LambdaQueryWrapper<AdminMenu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 把status=1和isdelete=0 查询出来
        lambdaQueryWrapper.eq(AdminMenu::getStatus, 1);
        lambdaQueryWrapper.eq(AdminMenu::getIsdelete, 0);
        // 查询所有菜单
        List<AdminMenu> allList = this.list(lambdaQueryWrapper);
        // 过滤所有的根(父)元素 。条件是pid = 0 ,
        // 注意我这里的PID和ID数据类型是Long。在判断的时候记得一定要加一个L
        List<AdminMenu> rootMenuList = allList.stream().filter(menu -> menu.getPid().equals(0L)).collect(Collectors.toList());
        // 递归调用菜单
        rootMenuList.forEach(rootMenu ->  bucketList(rootMenu,allList));
        // 返回
        return rootMenuList;

    }


    public void bucketList(AdminMenu rootMenu , List<AdminMenu> allList){
        // 遍历父元素开始找子元素
        List<AdminMenu> childrenMenuList = allList.stream()
                .filter(menu -> menu.getPid().equals(rootMenu.getId())).collect(Collectors.toList());
        // 这个判断的主要目的:是为了防止空集合,创建要给空集合对象,让json转换的时候由[].方便后续的判断长度==0
        if (CollectionUtils.isEmpty(childrenMenuList)) {
            // 记得把找的子元素放入到children集合中
            rootMenu.setChildren(new ArrayList<>());
        }else{
            // 记得把找的子元素放入到children集合中
            rootMenu.setChildren(childrenMenuList);
            // 开始继续遍历
            childrenMenuList.forEach((childrenMenu)->bucketList(childrenMenu,allList));
        }

    }
  • 控制面板 id=1 pid = 0
    • 后台首页 id=2 pid=1
    • 后台设置 id=3 pid=1
  • 优惠券管理 id=4 pid = 0
  • 用户管理 id=5 pid=0
    • 用户管理id=6 pid = 5
      • 用户设置id=7 pid = 6
        • 用户密码设置 id=8 pid = 7
      • 用户头像id =9 pid = 6
      • 用户审核 id=10 pid = 6
    • 用户添加id = 11 pid = 5
  • 菜单管理 id = 12 pid =0
  • 角色管理 id = 13 pid =0
  • 权限管理 id = 14 pid =0
    • 权限添加 id = 15 pid =14
    • 权限列表 id = 16 pid =14

测试菜单的接口

http://127.0.0.1:8877/admin/menu/tree

前台对接菜单管理

1: 定义异步请求接口调用

在services/navmenu/AdminMenuService.js 如下:

import request from '@/utils/request'

export default {

    /**
     * 创建验证码
     */
    loadNavMenu() {
        return request.post("/menu/tree");
    }

}

2:菜单渲染

找到layouts下面的PugMenu.vue进行异步调用接口,如下:

js部分

import adminMenuService from '@/services/navmenu/AdminMenuService.js'
// 定义响应式数据
const  menuList = ref([]);
onMounted(async () => {
    const severResponse = await adminMenuService.loadNavMenu();
    // 渲染赋值
    menuList.value = severResponse.data;
})

vue部分

<el-menu 
            :default-active="1" 
            class="border-0" 
            :unique-opened="true" 
            :collapse-transition="false"
            
            >
                <template  v-for="(menu,index) in menuList"  :key="menu.id" >
                     <!--有子元素的菜单-->
                    <el-sub-menu :index="menu.name" v-if="menu.children && menu.children.length > 0">
                        <template #title>
                            <el-icon><Location/></el-icon>
                            <span>{{menu.name}}</span>
                        </template>
                        <el-menu-item :index="cmenu.path" v-for="(cmenu,cindex) in menu.children" :key="cmenu.id">
                            <el-icon><Location/></el-icon>
                            <span>{{cmenu.name}}</span>
                        </el-menu-item>
                    </el-sub-menu>
                    <!--无子元素的菜单-->
                    <el-menu-item :index="menu.path" v-else>
                        <el-icon><Share/></el-icon>
                        <span>{{menu.name}}</span>
                    </el-menu-item>
                </template>
        </el-menu>

在这里插入图片描述

关于路由转发

<el-sub-menu :index="menu.name" v-if="menu.children && menu.children.length > 0">
<el-menu-item :index="cmenu.path" v-for="(cmenu,cindex) in menu.children" :key="cmenu.id">

为什么上面的:index=menu.name 有的是 cmenu.path呢?存在的父级元素不参与路由转发。只有子元素才参与,所以子元素:index=“cmenu.path”

转发的过程如下:

  • 通过点击子菜单或者没有子元素的菜单,获取菜单的index。(path)
  • 然后通过router.push(index) 跳转即可

如下:

// 菜单栏的数据
//const menuList = computed(() => store.state.menu.menuList)
// 点击菜单进行导航
const handleSelectMenu = (index) => {
    //store.commit("menu/addPath", index);
    router.push(index);
}

注意记得先把所有的菜单路由和spa页面先进行定义。并且绑定关系哦才可以生效。否则全部跳入404页面

关于菜单图标的问题

使用动态组件实现

图标集合:https://element-plus.gitee.io/zh-CN/component/icon.html#%E5%9B%BE%E6%A0%87%E9%9B%86%E5%90%88

代码

<el-icon>
    <component :is="menu.icon"></component>
</el-icon>

注意:

  • 名字定义到数据库的时候,要么是和官方一模一样 ,比如:AddLocation
  • 要么就是遵循驼峰小写定义,比如:add-location
    在这里插入图片描述
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-08-19 18:55:10  更:2022-08-19 18:56:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年9日历 -2024/9/28 8:24:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码