权限菜单之菜单管理 SpringBoot + VUE

news2025/1/5 8:17:03

一、 数据表设计
新建表sys_menu

在这里插入图片描述
表内数据
在这里插入图片描述
添加实体类Menu
在这里插入图片描述

package com.example.demo.demos.web.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.util.List;

@Data
//可以使用 @TableName 表名注解指定当前实体类对应的表名,比如下面 Menu 实体类对应表名为 sys_menu
@TableName(value="sys_menu")
public class Menu {


    //可以使用 @TableId 注解(标注在主键上)和 @TableField 注解(标注在其他成员属性上)来指定对应的字段名
    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    private String name;
    private String path;
    private String icon;
    private String description;

    //在数据表中没有children这个字段,这个在做菜单的时候会用到,所以使用exist=false忽略
    @TableField(exist = false)
    private List<Menu> children;

    private Integer pid;
    @TableField(value="page_path")//这样处理的主要目的是java对带有下划线的字段不识别,所以改为驼峰形式
    private String pagePath;

}

二、后端增删改查接口设计

Mapper文件夹下新建 MenuMapper

package com.example.demo.demos.web.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import java.awt.*;

public interface MenuMapper extends BaseMapper<Menu> {
}

在这里插入图片描述
添加MenuService类

package com.example.demo.demos.web.demo.service;


import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.demos.web.demo.entity.Menu;
import com.example.demo.demos.web.demo.mapper.MenuMapper;
import org.springframework.stereotype.Service;

@Service
public class MenuService extends ServiceImpl<MenuMapper, Menu> {

}

在这里插入图片描述
添加MenuController类

package com.example.demo.demos.web.demo.controller;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.demos.web.demo.common.Result;
import com.example.demo.demos.web.demo.entity.Menu;
import com.example.demo.demos.web.demo.service.MenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/menu")
public class MenuControlle {

    @Autowired
    private MenuService menuService;

    //增加菜单
    @PostMapping
    public Result save(@RequestBody Menu menu){
        menuService.saveOrUpdate(menu);
        return Result.success();
    }

    //根据id删除菜单
    @DeleteMapping("/{id}")
    public Result deleteById(@PathVariable Integer id){
        menuService.removeById(id);
        return Result.success();
    }


    //批量删除菜单
    @PostMapping("/del/batch")
    public Result deleteBatch(@RequestBody List<Integer> ids){
        menuService.removeByIds(ids);
        return Result.success();
    }

    //根据id查找菜单
    @GetMapping("/{id}")
    public Result findById(@PathVariable Integer id){

        return Result.success(menuService.getById(id));
    }
    //分页查找
    @GetMapping("/page")
    public Result findPage(@RequestParam Integer pageNum,
                           @RequestParam Integer pageSize,
                           @RequestParam(defaultValue = "") String name){
        QueryWrapper<Menu> queryWrapper=new QueryWrapper<>();
        queryWrapper.like("name",name);
        queryWrapper.orderByDesc("id");
        return Result.success(menuService.page(new Page<>(pageNum,pageSize),queryWrapper));
    }
    
    




}

查找全部菜单
这里的菜单查找,不是简单的获取数据表中的所有值即可。因为菜单前端在设计的时候会设计一级菜单、二级菜单。比如:一级菜单没有页面路径以及访问路径,只有二级菜单才有。也就是说需要进行判断,如果是一级菜单就直接呈现,如果是二级菜单需要根据对应的id和pid,找出一级菜单为null的二级菜单放到Children中,这个实体类中的Children字段在数据表中是没有的。这是因为它本身不需要存在,只是在做树形数据的时候需要。后面做前端表格中必须有Children这个属性。在做前端页面的时候大家特别注意一下。

所以:MenuController中首先定义findAll接口,调用menuService.findMenus(name)方法。这里用了一个name参数,是因为前端模糊查找的搜索按钮“”复用了加载load方法,都是调用这里的findAll。如果你的程序中菜单不多,用不着查找,也可以不要这个参数。

service 中添加

    public List<Menu> findMenus(String name) {
        QueryWrapper<Menu> queryWrapper=new QueryWrapper<>();
        if(StrUtil.isNotBlank(name)){
            queryWrapper.like("name",name);
        }
        List<Menu> list = list(queryWrapper);
        // 找出pid为null的一级菜单
        List<Menu> parentNodes=list.stream().filter(menu -> menu.getPid()==null).collect(Collectors.toList());
        //找出一级菜单为null的二级菜单放到Children中
        for(Menu menu:parentNodes){
            menu.setChildren(list.stream().filter(m->menu.getId().equals(m.getPid())).collect(Collectors.toList()));
        }
        return parentNodes;
    }

controllert添加

    //查询全部菜单
    @GetMapping
    public Result findAll( @RequestParam(defaultValue = "") String name){

        return Result.success(menuService.findMenus(name));
    }

在这里插入图片描述
前端Menu.vue页面设计

重点注意
(1)是否出现“新增子菜单”按钮,取决于是否是一级菜单,做了一个 v-if="!scope.row.pid && !scope.row.path"的判断,也就是说如果他的父级菜单和访问地址路径都为空,说明是一个一级菜单,就出现“新增子菜单”按钮。
(2)根据ElementUI官网解释,支持树类型数据的显示,row中包含children字段时,被视为树形数据。渲染树形数据时,必须要指定 row-key
所以:一方面后端查找菜单的时候要进行判断,如果是一级菜单就直接呈现,如果是二级菜单需要根据对应的id和pid,找出一级菜单为null的二级菜单放到Children中,实体类中的Children字段在数据表中是没有的。这是因为它本身不需要存在,只是在做树形数据的时候需要。另一方面,注意观察表格代码中的row-key一定要写上。
在这里插入图片描述

<template>
    <div>
      <div style="padding:10px">
          <el-input style="width:250px" suffix-icon="el-icon-search" placeholder="请输入名称搜索" v-model="name"></el-input>
          <el-button style="margin-left:5px" type="primary" @click="load">搜索</el-button>
          <el-button style="margin-left:5px" type="warning" @click="reset">重置</el-button>
        </div>
        <div style="margin:10px">
           <el-button type="primary" @click="handleAdd">新增<i class="el-icon-circle-plus"></i></el-button>
           <el-button type="danger" @click="delBatch">批量删除<i class="el-icon-remove"></i></el-button>
        </div>
          <el-table :data="tableData"  row-key="id"
                      border default-expand-all @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="55"></el-table-column>
            <el-table-column prop="id" label="ID " width="80">
            </el-table-column>
            <el-table-column prop="name" label="名称 " >
            </el-table-column>
            <el-table-column prop="path" label="路径 " >
            </el-table-column>
            <el-table-column prop="pagePath" label="页面路径 " >
            </el-table-column>
            <el-table-column  label="图标 " align="center">
              <template slot-scope="scope">
                <i style="font-size:18px"  :class="scope.row.icon"/>
              </template>
            </el-table-column>
            <el-table-column prop="description" label="描述" >
            </el-table-column>          
            <el-table-column fixed="right" width="300px" label="操作" >                         
              <template slot-scope="scope">
                 <el-button type="primary" size="small" icon="el-icon-plus" @click="childmenuAdd(scope.row.id)" v-if="!scope.row.pid && !scope.row.path">新增子菜单</el-button>
                <el-button type="success" size="small" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
                <el-popconfirm style="margin-left:5px"
                  confirm-button-text='确定'
                  cancel-button-text='再想想'
                  icon="el-icon-info"
                  icon-color="red"
                  title="您确定删除吗?"
                  @confirm="handleDelete(scope.row.id)"
                >
                <el-button type="danger" size="small" slot="reference" icon="el-icon-delete" >删除</el-button>
                </el-popconfirm>
              </template>
            </el-table-column>         
          </el-table>
          <el-dialog title="菜单信息" :visible.sync="dialogFormVisible" width="30%">
            <el-form label-width="80px" size="small">
              <el-form-item label="名称">
                <el-input v-model="form.name" autocomplete="off"></el-input>
              </el-form-item>
              <el-form-item label="路径">
                <el-input v-model="form.path" autocomplete="off"></el-input>
              </el-form-item>
              <el-form-item label="页面路径">
                <el-input v-model="form.pagePath" autocomplete="off"></el-input>
              </el-form-item>   
              <el-form-item label="图标">
               <el-select clearable v-model="form.icon" placeholder="请选择" style="width:100%;">     
                <el-option v-for="item in options" :key="item.name" :label="item.name" :value="item.value">
                  <i :class="item.value"/>{{item.name}}
                </el-option>
              </el-select>
              </el-form-item>            
              <el-form-item label="描述">
                <el-input v-model="form.description" autocomplete="off"></el-input>
              </el-form-item>                 
            </el-form>
            <div slot="footer" class="dialog-footer">
              <el-button @click="dialogFormVisible = false">取 消</el-button>
              <el-button type="primary" @click="save">确 定</el-button>
            </div>
          </el-dialog>
    </div>
  </template>
  <script>
  export default {
      name:"Menu",
      data(){
          return{
              tableData:[],       
              name:"",
              dialogFormVisible:false,
              form:{},
              multipleSelection:[],
              options:[]
          }
      },
      created(){
       this.load();
      },
      methods:{
        //搜索重置
        reset(){
          this.name="";        
          this.load();
        },
        //打开新增菜单对话框,同时获取图标数据
         handleAdd(){
          this.dialogFormVisible = true;
          this.form={};//如果之前有填过值,可以置空 
          this.request.get("http://localhost:8080/menu/icons").then(res=>{ //不管是新增还是编辑都直接获取图标数据即可,不需要与当前id对应    
            console.log(res);     
            this.options=res.data;       
            })       
        },
        //实现新增菜单
        save(){
          this.request.post("http://localhost:8080/menu",this.form).then(res=>{
            if(res.code=='200'){
              this.$message.success("保存成功");
              this.dialogFormVisible=false;
              this.load();
            }else{
              this.$message.error("保存失败");
            }
          })
        },      
        //编辑菜单
        handleEdit(row){  //编辑
          this.form=row;//把当前行的数据赋值给form
          this.dialogFormVisible=true; 
          this.request.get("http://localhost:8080/menu/icons").then(res=>{ //不管是新增还是编辑都直接获取图标数据即可,不需要与当前id对应
            console.log(res);     
            this.options=res.data;        
            })               
        },
        //多行选择
        handleSelectionChange(val){
          console.log(val);
          this.multipleSelection =val;
        },
        //批量删除
        delBatch(){ 
           let ids=this.multipleSelection.map(v=>v.id);//map这个方法可以实现将multipleSelection中的对象扁平化处理。
           this.request.post("http://localhost:8080/menu/del/batch",ids).then(res=>{
           if(res.code=='200'){
              this.$message.success("批量删除成功");
              this.load();
            }else{
              this.$message.error("批量删除失败");
            } 
          })
        },
        //根据选择行的id删除
        handleDelete(id){
           this.request.delete("http://localhost:8080/menu/"+id+"").then(res=>{
           if(res.code=='200'){
              this.$message.success("删除成功");
              this.load();
            }else{
              this.$message.error("删除失败");
            } 
          })
        },      
        //将请求数据封装为一个方法
        load() {          
            //使用axios封装的request,获取所有菜单数据        
            this.request.get("http://localhost:8080/menu",{
              params:{           
              name:this.name           
              }
            }).then(res=>{
            console.log(res);
            this.tableData=res.data;      
            })
        },
        //新增子菜单,打开新增菜单对话框,把父菜单的id传递到form中
        childmenuAdd(pid){
          this.dialogFormVisible = true;
          this.form={};//如果之前有填过值,可以置空
          if(pid){
            this.form.pid=pid;//传递父菜单的id
          }
          this.request.get("http://localhost:8080/menu/icons").then(res=>{ //不管是新增还是编辑都直接获取图标数据即可,不需要与当前id对应    
            console.log(res);     
            this.options=res.data;       
            })       
        }
        
      }    
  }
  </script>
  <style scoped>
  </style>

路由修改

在这里插入图片描述


import Vue from 'vue'
import VueRouter from 'vue-router'
import Manage from '../views/Manage.vue'
import Aside from '@/components/Aside.vue'
import User from '@/views/user.vue'
import Login  from '../views/Login.vue'
import RailwayIntroduction from '@/views/railwayIntroduction.vue'
import Menu from '@/views/Menu.vue'
Vue.use(VueRouter)
const routes = [
  {
    path: '/',
    name: 'Manage',
    redirect: '/login',
    component: Manage,
    children:[
      {
        path: 'user',
        name: 'User',
        component: User
      },
      {
        path:'menu',
        name:'Menu',
        component:Menu
    },
      {
        path: 'railwayIntroduction',
        name: 'RailwayIntroduction',
        component: RailwayIntroduction
      },
      {
        path: 'home',
        name: 'Home',
        // route level code-splitting
        // this generates a separate chunk (about.[hash].js) for this route
        // which is lazy-loaded when the route is visited.
        component: () => import(/* webpackChunkName: "about" */ '../views/Home.vue')
      },
      
    ]
  },
  {
    path: '/login',
    name: 'Login',
    component: Login
  }
]



const router = new VueRouter({
   mode: 'hash',
  base: process.env.BASE_URL,
  routes
})

export default router

侧边组件修改

在这里插入图片描述

<template>
  <el-menu :default-openeds="[]" style="min-height:100%; overflow-x:hidden"
     background-color=rgb(48,65,86)
     text-color=#ccc
     active-text-color=red
     router=""
     >
     <div style="height:60px; line-height:60px; text-align:center">
     <img src="../assets/logo.png" style="width:20px;position:relative;top:5px;margin-right:5px"/>
     <b style="color:white">后台管理系统</b>
     </div>
      <el-menu-item index="/home">
         <template slot="title">
             <i class="el-icon-house"></i>
             <span slot="title">主页</span>
         </template>
      </el-menu-item>
     <el-submenu index="2">
         <template slot="title">
             <i class="el-icon-menu"></i>
             <span slot="title">系统管理</span>
         </template>
         <el-menu-item index="/user">
             <i class="el-icon-s-custom"></i>
             <span slot="title">用户管理</span>
         </el-menu-item>     
         
         <el-menu-item index="/menu">
                 <template slot="title">
                    <i class="el-icon-s-custom"></i>
                    <span >菜单管理</span>  
                 </template>      
                </el-menu-item>


         <el-menu-item index="/railwayIntroduction">
             <i class="el-icon-s-custom"></i>
             <span slot="title">铁路首页</span>
         </el-menu-item>     

     </el-submenu>    
   </el-menu>
 </template>
 
 <script>
 export default {
   //输出组件
   name: "Aside"
 }
 </script>
 
 <style scoped>
 </style>

管理页面vue
在这里插入图片描述
不懂请看前面博客

整体设计完如图所示

在这里插入图片描述

出现问题:

×
ERROR
Request failed with status code 400
AxiosError: Request failed with status code 400
    at settle (webpack-internal:///./node_modules/axios/lib/core/settle.js:24:12)
    at XMLHttpRequest.onloadend (webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:67:66)
    at Axios.request (webpack-internal:///./node_modules/axios/lib/core/Axios.js:58:41)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2269839.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【AIGC篇】AIGC 引擎:点燃创作自动化的未来之火

&#xff1a;羑悻的小杀马特.-CSDN博客 未来都是惊喜。你生来本应为高山。并非草芥。 引言&#xff1a; 在当今数字化的时代&#xff0c;人工智能生成内容&#xff08;AIGC&#xff09;正以一种前所未有的力量改变着我们的创作领域。它就像一个神秘而强大的魔法师&#xff0c;…

UnityRenderStreaming使用记录(三)

测试UnityRenderStreaming在Ubuntu24.04.1LTS上的表现 先放上运行图操作系统 Ubuntu24.04.1LTSUnity测试工程环境相关修改遇到的问题 先放上运行图 操作系统 Ubuntu24.04.1LTS 系统下载地址 https://cn.ubuntu.com/download/desktop安装UnityHub https://blog.csdn.net/AWNUXC…

从0开始的docker镜像制作-ubuntu22.04

从0开始的docker镜像制作-ubuntu22.04 一、拉取基础ubuntu22.04镜像二、进入拉取的docker镜像中&#xff0c;下载自己需要的安装包三、安装需要的系统软件四、打包现有镜像为一个新的镜像五、推送打包的镜像到私有docker服务器1.编辑docker文件&#xff0c;使其允许http传输和对…

多模态论文笔记——CogVLM和CogVLM2(副)

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍多模态模型的LoRA版本——CogVLM和CogVLM2。在SD 3中使用其作为captioner基准模型的原因和优势。 文章目录 CogVLM论文背景VLMs 的任务与挑战现有方法及…

gitlab-runner的卸载与安装

如果你使用rpm方式安装gitlab-runner&#xff0c;则可以参考本教程。 卸载 停止和卸载gitlab-runner 停止 gitlab-runner stopchkconfig gitlab-runner off卸载 gitlab-runner uninstall删除rpm包 查询出rpm包名&#xff0c;根据包名删除rpm。 [rootEuler02 ~]# rpm -qa …

Nacos配置中心总结

Nacos配置中心总结 Nacos配置文件的加载顺序和优先级 加载顺序 nacos作为配置中心时&#xff0c;需要在bootstrap.yml文件中添加nacos config相关的配置&#xff0c;这样系统启动时就能先去拉取nacos server上的配置了。拉取过来后会和本地配置文件进行合并。 bootstrap.ym…

赛博周刊·2024年度工具精选(图片资源类)

1、EmojiSpark emoji表情包查找工具。 2、fluentui-emoji 微软开源的Fluent Emoji表情包。 3、开源Emoji库 一个开源的emoji库&#xff0c;目前拥有4000个emoji表情。 4、中国表情包大合集博物馆 一个专门收集中国表情包的项目&#xff0c;已收录5712张表情包&#xff0c;并…

Goland:专为Go语言设计的高效IDE

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;Goland是JetBrains公司开发的集成开发环境&#xff08;IDE&#xff09;&#xff0c;专为Go语言设计&#xff0c;提供了高效的代码编辑、强大的调试工具和丰富的项目管理功能。其智能代码补全、强大的调试与测试支…

小程序发版后,用户使用时,强制更新为最新版本

为什么要强制更新为最新版本&#xff1f; 在小程序的开发和运营过程中&#xff0c;强制用户更新到最新版本是一项重要的策略&#xff0c;能够有效提升用户体验并保障系统的稳定性与安全性。以下是一些主要原因&#xff1a; 1. 功能兼容 新功能或服务通常需要最新版本的支持&…

EasyExcel(环境搭建以及常用写入操作)

文章目录 EasyExcel环境搭建1.创建模块 easyexcel-demo2.引入依赖3.启动类创建 EasyExcel写1.最简单的写入1.模板2.方法3.结果 Write01.xlsx 2.指定字段不写入Excel1.模板2.方法3.结果 Write02.xlsx 3.指定字段写入excel1.模板2.方法3.结果 Write03.xlsx 4.按照index顺序写入ex…

典型常见的基于知识蒸馏的目标检测方法总结三

来源&#xff1a;Google学术2023-2024的顶会顶刊论文 NeurIPS 2022&#xff1a;Towards Efficient 3D Object Detection with Knowledge Distillation 为3D目标检测提出了一种知识蒸馏的Benchmark范式&#xff0c;包含feature的KD&#xff0c;Logit的cls和reg的KD&#xff0c…

通过Dockerfile来实现项目可以指定读取不同环境的yml包

通过Dockerfile来实现项目可以指定读取不同环境的yml包 1. 挂载目录2. DockerFile3. 运行脚本deploy.sh4. 运行查看日志进入容器 5. 接口测试修改application-dev.yml 6. 优化Dockerfile7. 部分参数解释8. 优化不同环境下的日志也不同调整 Dockerfile修改部署脚本 deploy.sh重新…

开源的go语言统一配置中心 - nacos + nacos go sdk

配置文件实时更新机制的场景需求 配置文件热更新主要应用于需要在不停机的情况下动态调整系统行为的场景&#xff0c;例如修改服务参数、切换数据源等。其原理在于通过一个中心化的管理平台来存储和分发最新的配置信息。当配置文件发生变化时&#xff0c;该平台会主动或被动地…

对45家“AI+安全”产品/方案的分析

一. 关键洞察 “AI+安全”创新非常活跃,一片百家争鸣之势,赛道选择上,以事件分诊Incident Triage、 安全辅助Security Copilots、自动化Automation三者为主为主,这充分反映了当前安全运营的主要需求,在产品理念选择上以 AI 和 自动化为主,这确实又切合上了在关键…

GESP202412 三级【数字替换】题解(AC)

》》》点我查看「视频」详解》》》 [GESP202412 三级] 数字替换 题目描述 小杨有一个包含 n n n 个数字的序列 A A A&#xff0c;即 A [ a 1 , a 2 , … , a n ] A[a_1,a_2,\ldots,a_n] A[a1​,a2​,…,an​]&#xff0c;他想将其中大于 k k k 的数字都替换为序列的最大…

springboot集成websokcet+H5开发聊天原型(二)

本文没有写完~~~~ 聊天相关数据结构&#xff1a; 我们初步设计了如下几个数据结构。 //存放 sessionId 与 userId 的map private Map<String,String> sessionId_userId new HashMap<>(); // 用于存储用户与群组的关联关系&#xff0c;键为用户ID&#xff0c;值…

List接口(源码阅读)

文章目录 1.List接口常用方法1.代码2.结果 2.ArrayList底层机制1.结论2.ArrayList底层源码1.代码2.debug添加第一个元素1.进入2.elementData数组存储ArrayList的数据3.初始化为空数组4.首先确保使用size1来计算最小容量5.如果elementData为空&#xff0c;最小容量就是106.modCo…

Python爬虫(一)- Requests 安装与基本使用教程

文章目录 前言一、简介及安装1. 简介2. 安装 Requests2.1 安装2.2 检查安装是否成功 二、使用 Requests 发送 HTTP 请求1. 发送 GET 请求2. 发送 POST 请求3. 发送 PUT 请求4. 发送 DELETE 请求5. 发送 HEAD 请求6. 发送 OPTIONS 请求 三、传递参数1. GET 请求传递 URL 参数1.1…

风力涡轮机缺陷检测数据集,86.6%准确识别率,11921张图片,支持yolo,PASICAL VOC XML,COCO JSON格式的标注

风力涡轮机缺陷检测数据集&#xff0c;86.6&#xff05;准确识别率&#xff0c;11921张图片&#xff0c;支持yolo&#xff0c;PASICAL VOC XML&#xff0c;COCO JSON格式的标注 数据集下载 yolov11&#xff1a; https://download.csdn.net/download/pbymw8iwm/90206849 yolov…

简易屏幕共享工具-基于WebSocket

前面写了两个简单的屏幕共享工具&#xff0c;不过那只是为了验证通过截屏的方式是否可行&#xff0c;因为通常手动截屏的频率很低&#xff0c;而对于视频来说它的帧率要求就很高了&#xff0c;至少要一秒30帧率左右。所以&#xff0c;经过实际的截屏工具验证&#xff0c;我了解…