一、 数据表设计
新建表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;
}
二、后端增删改查接口设计
Mappe
r文件夹下新建 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)