springboot + Vue前后端项目(第十五记)

项目实战第十五记

  • 写在前面
  • 1.后端接口实现
    • [1.1 用户表添加角色字段](#1.1 用户表添加角色字段)
    • [1.2 角色表增加唯一标识字段](#1.2 角色表增加唯一标识字段)
    • [1.3 UserDTO](#1.3 UserDTO)
    • [1.4 UserServiceImpl](#1.4 UserServiceImpl)
    • [1.5 MenuServiceImpl](#1.5 MenuServiceImpl)
  • [2. 前端实现](#2. 前端实现)
    • [2.1 User.vue](#2.1 User.vue)
    • [2.2 动态菜单设计](#2.2 动态菜单设计)
      • [2.2.1 Login.vue](#2.2.1 Login.vue)
      • [2.2.2 Aside.vue](#2.2.2 Aside.vue)
    • [2.3 动态路由设计](#2.3 动态路由设计)
      • [2.3.1 菜单表新增字段page_path](#2.3.1 菜单表新增字段page_path)
      • [2.3.2 路由设计](#2.3.2 路由设计)
      • [2.3.3 登录界面Login.vue设置路由](#2.3.3 登录界面Login.vue设置路由)
  • 总结
  • 写在最后

写在前面

  1. 动态菜单设计
  2. 动态路由设计

1.后端接口实现

1.1 用户表添加角色字段

java 复制代码
CREATE TABLE `sys_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `username` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户名',
  `password` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码',
  `nickname` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '昵称',
  `email` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱',
  `phone` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '电话',
  `address` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '地址',
  `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `avatar_url` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT 'https://himg.bdimg.com/sys/portraitn/item/public.1.23bd3c8c.ANoeKxl_gef9fnrikdXOYA' COMMENT '头像',
  `role` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '角色',
  `deleted` tinyint(4) DEFAULT '0' COMMENT '逻辑删除0(未删除),1(删除)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC

1.2 角色表增加唯一标识字段

java 复制代码
CREATE TABLE `sys_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `role_key` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '唯一标识',
  `name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',
  `description` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '描述',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC

1.3 UserDTO

java 复制代码
@Data
public class UserDTO {
    private String username;
    private String password;
    private String nickname;
    private String avatarUrl;
    private String token;

    private String role;
    private List<Menu> menus;
}

1.4 UserServiceImpl

复制代码
package com.ppj.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ppj.constants.Constants;
import com.ppj.entity.Menu;
import com.ppj.entity.Role;
import com.ppj.entity.RoleMenu;
import com.ppj.entity.User;
import com.ppj.entity.dto.UserDTO;
import com.ppj.exception.ServiceException;
import com.ppj.mapper.MenuMapper;
import com.ppj.mapper.RoleMapper;
import com.ppj.mapper.RoleMenuMapper;
import com.ppj.mapper.UserMapper;
import com.ppj.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ppj.utils.TokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.sql.rowset.serial.SerialException;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author ppj
 * @since 2024-04-20
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    private static final Log LOG = Log.get();

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private RoleMenuMapper roleMenuMapper;

    @Autowired
    private MenuServiceImpl menuService;

    @Override
    public UserDTO login(UserDTO userDTO) {
        if(StrUtil.isBlank(userDTO.getUsername()) || StrUtil.isBlank(userDTO.getPassword())){
            return null;
        }
        User user = getUserInfo(userDTO);
        if(user != null){
            String token = TokenUtils.genToken(user.getId().toString(), userDTO.getPassword());
            userDTO.setToken(token);
            // 把user相应的值传递给userDTO
            BeanUtil.copyProperties(user,userDTO,true);
            List<Menu> roleMenus = getRoleMenus(user.getRole());
            userDTO.setMenus(roleMenus);
            return userDTO;
        }else{  // 数据库查不到
            throw new ServiceException(Constants.CODE_600,"用户名或密码错误");
        }
    }

    @Override
    public Boolean register(UserDTO userDTO) {
        if(StrUtil.isBlank(userDTO.getUsername()) || StrUtil.isBlank(userDTO.getPassword())){
            return false;
        }
        User user = getUserInfo(userDTO);
        if(user == null){
            User newUser = new User();
            // 值传递
            BeanUtil.copyProperties(userDTO,newUser,true);
            // 保存至数据库
            save(newUser);
        }else{
            throw new ServiceException(Constants.CODE_600,"用户已存在");
        }
        return true;
    }

    public User getUserInfo(UserDTO userDTO){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username",userDTO.getUsername())
                .eq("password",userDTO.getPassword());
        User user;
        try {
            // 可能查到多条记录,后台报异常,写个异常类(主动捕获异常)
            user = getOne(queryWrapper);
        }catch (Exception e){  // 可能查出多个符合条件的记录
            LOG.error(e);
            throw new ServiceException(Constants.CODE_500,"系统错误");
        }
        return user;
    }

    /**
     * 根据角色名称获取菜单列表
     * @param roleName
     * @return
     */
    public List<Menu> getRoleMenus(String roleName){
        Integer roleId = roleMapper.getRoleIdByName(roleName);
        List<Integer> menuIds = roleMenuMapper.getMenuIdsByRoleId(roleId);

        // 查询系统中所有菜单,树结构
        List<Menu> menus = menuService.findMenus("");
        // new一个最后筛选完成之后的list
        ArrayList<Menu> roleMenus = new ArrayList<>();
        for (Menu menu : menus) {
            if(menuIds.contains(menu.getId())){
                roleMenus.add(menu);
            }
            List<Menu> children = menu.getChildren();
            // 移除children中不在menuIds集合中的元素
            if (children != null) {
                children.removeIf(child -> !menuIds.contains(child.getId()));
            }
        }
        return roleMenus;
    }

}

将写在MenuController中的方法提取到service中(更符合现实中的开发)

如下图所示:controller不含业务代码

java 复制代码
package com.ppj.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ppj.entity.Menu;
import com.ppj.mapper.MenuMapper;
import com.ppj.service.IMenuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

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

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author ppj
 * @since 2024-05-29
 */
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements IMenuService {

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

}

2. 前端实现

2.1 User.vue

主要是表格多添加role字段,添加框增加角色选择框

javascript 复制代码
<template>

  <div>
    <div style="margin: 10px 0">
      <el-input style="width: 200px" placeholder="请输入名称" suffix-icon="el-icon-search" v-model="username"></el-input>
      <el-input style="width: 200px" placeholder="请输入地址" suffix-icon="el-icon-position" class="ml-5" v-model="address"></el-input>
      <el-button class="ml-5" type="primary" @click="getList">搜索</el-button>
      <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
    </div>

    <div style="margin: 10px 0">
      <el-button type="primary" @click="handleAdd">新增 <i class="el-icon-circle-plus-outline"></i></el-button>
      <el-button type="warning" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate">修改</el-button>
      <el-button type="danger" :disabled="multiple" @click="handleDelete">删除 <i class="el-icon-remove-outline"></i></el-button>

<!--      <el-upload action="http://localhost:9000/user/import" :show-file-list="false" accept="xlsx" :on-success="handleImport" style="display: inline-block">-->
<!--        <el-button type="primary" class="ml-5">导入 <i class="el-icon-bottom"></i></el-button>-->
<!--      </el-upload>-->
            <el-button type="success" @click="handleImport" class="ml-5">导入 <i class="el-icon-bottom"></i></el-button>
      <el-button type="warning" @click="handleExport" class="ml-5">导出 <i class="el-icon-top"></i></el-button>
    </div>

    <el-table v-loading="loading" :data="tableData" border stripe :header-cell-class-name="headerBg" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55"></el-table-column>
      <el-table-column prop="id" label="序号" width="140"></el-table-column>
      <el-table-column prop="username" label="用户名" width="140"></el-table-column>
      <el-table-column prop="nickname" label="昵称" width="140"></el-table-column>
      <el-table-column prop="role" label="角色" width="140">
        <template v-slot="scope">
          <el-tag>{{ scope.row.role}}</el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="email" label="邮箱" width="200"></el-table-column>
      <el-table-column prop="address" label="地址" width="140"></el-table-column>
      <el-table-column prop="createTime" label="创建时间" width="140"></el-table-column>
      <el-table-column label="操作"  align="center">
        <template v-slot="scope">
          <el-button type="success" @click="handleUpdate(scope.row)">编辑 <i class="el-icon-edit"></i></el-button>
          <el-button type="danger" @click="handleDelete(scope.row)">删除 <i class="el-icon-remove-outline"></i></el-button>
        </template>
      </el-table-column>
    </el-table>
    <div style="padding: 10px 0">
      <el-pagination
          class="page"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
          :page-sizes="[5, 10]"
          :page-size="pageSize"
          layout="total, sizes, prev, pager, next, jumper"
          :total="total">
      </el-pagination>
    </div>

    <!--  用户信息       -->
    <el-dialog title="用户信息" :visible.sync="dialogFormVisible" width="30%" >
      <el-form label-width="80px" size="small">
        <el-form-item label="用户名">
          <el-input v-model="form.username" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="昵称">
          <el-input v-model="form.nickname" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="角色" >
          <el-select v-model="form.role" placeholder="请选择" style="width: 100%">
            <el-option
                v-for="item in roles"
                :key="item.name"
                :label="item.name"
                :value="item.roleKey">
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="邮箱">
          <el-input v-model="form.email" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="电话">
          <el-input v-model="form.phone" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="地址">
          <el-input v-model="form.address" 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>

    <!-- 用户导入对话框 -->
    <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px">
      <el-upload
          ref="upload"
          :limit="1"
          accept=".xlsx, .xls"
          :action="upload.url"
          :disabled="upload.isUploading"
          :on-progress="handleFileUploadProgress"
          :on-success="handleFileSuccess"
          :auto-upload="false"
          drag
      >
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">
          将文件拖到此处,或
          <em>点击上传</em>
        </div>
        <div class="el-upload__tip" slot="tip">
          <el-link type="info" style="font-size: 16px;color:green" @click="importTemplate">下载模板</el-link>
        </div>
        <div class="el-upload__tip" style="color: red" slot="tip">
          提示:仅允许导入"xls"或"xlsx"格式文件!
        </div>
      </el-upload>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitFileForm">确 定</el-button>
        <el-button @click="upload.open = false">取 消</el-button>
      </div>
    </el-dialog>

  </div>

</template>

<script>
export default {
  name: "User",
  data(){
    return {
      tableData: [],
      pageSize: 5,
      total: 0,
      pageNum: 1,
      username: '',
      address: '',
      collapseBtnClass: 'el-icon-s-fold',
      isCollapse: false,
      sideWidth: 200,
      logoTextShow: true,
      headerBg: 'headerBg',
      dialogFormVisible: false,
      form: {},
      // 遮罩层
      loading: true,
      // 选中数组
      ids: [],
      // 非单个禁用
      single: true,
      // 非多个禁用
      multiple: true,
      //用户导入参数
      upload: {
        //是否显示弹出层(用户导入)
        open: false,
        //弹出层标题(用户导入)
        title: "",
        //是否禁用上传
        // isUploading: false,
        //是否更新已经存在的用户数据
        //updateSupport: 0,
        //设置上传的请求头部
        //headers: "",
        //上传的地址
        url: "http://localhost:9000/user/import",
      },
      roles: [],
    }
  },
  created() {
    this.getList();
  },
  methods: {
    getList(){
      this.loading = true;
      this.request.get('/user/page',
          {
            params: {
              pageNum: this.pageNum,
              pageSize: this.pageSize,
              username: this.username,
              address: this.address
            }
          }
      ).then(res => {
        if(res.code === '200'){
          this.tableData = res.data.records;
          this.total = res.data.total;
          this.loading = false;
        }else{
          this.$message.error(res.msg)
        }

      })

      this.request.get('/role').then(res => {
        if(res.code === '200'){
          this.roles = res.data;
        }else{
          this.$message.error(res.msg)
        }
      })
    },
    handleSizeChange(val) {
      this.pageSize = val;
    },
    handleCurrentChange(val) {
      this.pageNum = val;
      this.getList();
    },
    // 多选框选中数据
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.id);
      this.single = selection.length != 1;
      this.multiple = !selection.length;
    },
    // 重置按钮
    resetQuery(){
      this.username = undefined;
      this.address = undefined;
      this.getList();
    },
    // 新增
    handleAdd(){
      this.dialogFormVisible = true;
      this.form = {};
    },
    save(){
      this.request.post("/user",this.form).then(res => {
        if(res.code === "200" || res.code === 200){
          this.$message.success("操作成功")
        }else {
          this.$message.error("操作失败")
        }
        this.dialogFormVisible = false;
        this.getList();
      })
    },
    // 修改
    handleUpdate(row){
      // 表单置空
      this.reset();
      // 重新查询数据
      const userId = row.id || this.ids;
      this.request.get('/user/'+userId).then(response => {
        this.form = response.data;
        this.dialogFormVisible = true;
      });
    },
    reset(){
      this.form.username = undefined;
      this.form.nickname = undefined;
      this.form.email = undefined;
      this.form.phone = undefined;
      this.form.address = undefined;
    },
    // 删除
    handleDelete(row){
      let _this = this;
      const userIds = row.id || this.ids;
      this.$confirm('是否确认删除用户编号为"' + userIds + '"的数据项?', '删除用户', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        _this.request.delete("/user/"+userIds).then(res=>{
          if(res.code === "200" || res.code === 200){
            _this.$message.success("删除成功")
          }else {
            _this.$message.error("删除失败")
          }
          this.getList();
        })
      }).catch(() => {
      });
    },
    // 导出
    handleExport(){
      window.open('http://localhost:9000/user/export');
      this.$message.success("导出成功");
    },
    // handleImport(){
    //   this.$message.success('导入成功')
    //   this.getList()
    // },
    handleImport(){
      this.upload.title = '用户导入'
      this.upload.open = true
    },
    importTemplate(){
      this.$message.success("正在下载模版");
      window.open('http://localhost:9000/user/download')
    },
    //文件上传处理
    handleFileUploadProgress(event,file,fileList){
      //this.upload.isUploading = true;
      this.loading = true;
    },
    //文件上传成功处理
    handleFileSuccess(response,file,fileList){
      this.loading = false;
      this.upload.open = false;
      // this.upload.isUploading = false;
      this.$refs.upload.clearFiles();
      this.$message.success("导入成功");
      this.getList()
    },
    //提交上传文件
    submitFileForm(){
      this.$refs.upload.submit();
    }
  }
}
</script>

<style scoped>

</style>

2.2 动态菜单设计

2.2.1 Login.vue

在登录成功的时候将菜单存到浏览器中

javascript 复制代码
<template>
  <div class="wrapper">
    <div style="margin: 200px auto; background-color: #fff; width: 350px; height: 300px; padding: 20px; border-radius: 10px">
      <div style="margin: 20px 0; text-align: center; font-size: 24px"><b>登 录</b></div>
      <el-form :model="user" :rules="rules" ref="userForm">
        <el-form-item prop="username">
          <el-input size="medium" style="margin: 10px 0" prefix-icon="el-icon-user" v-model="user.username"></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input size="medium" style="margin: 10px 0" prefix-icon="el-icon-lock" show-password v-model="user.password"></el-input>
        </el-form-item>
        <el-form-item style="margin: 10px 0; text-align: right">
          <el-button type="primary" size="small"  autocomplete="off" @click="login">登录</el-button>
          <el-button type="warning" size="small"  autocomplete="off" @click="$router.push('/register')">注册</el-button>
        </el-form-item>
      </el-form>
    </div>
  </div>
</template>

<script>
export default {
  name: "Login",
  data() {
    return {
      user: {},
      rules: {
        username: [
          { required: true, message: '请输入用户名', trigger: 'blur' },
          { min: 3, max: 10, message: '长度在 3 到 5 个字符', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '请输入密码', trigger: 'blur' },
          { min: 1, max: 20, message: '长度在 1 到 20 个字符', trigger: 'blur' }
        ],
      }
    }
  },
  methods: {
    login() {
      this.$refs['userForm'].validate((valid) => {
        if (valid) {  // 表单校验合法
          this.request.post("/user/login", this.user).then(res => {
            if(res.code === 200 || res.code === '200') {
              localStorage.setItem('loginUser',JSON.stringify(res.data));
              localStorage.setItem("menus",JSON.stringify(res.data.menus));
              this.$router.push("/")
              this.$message.success("登录成功")
            } else {
              this.$message.error(res.msg)
            }
          })
        } else {
          return false;
        }
      });
    }
  }
}
</script>

<style>
.wrapper {
  height: 100vh;
  background-image: linear-gradient(to bottom right, #FC466B , #3F5EFB);
  overflow: hidden;
}
</style>

2.2.2 Aside.vue

侧边栏动态显示菜单

javascript 复制代码
<template>
  <el-menu :default-openeds="['1', '3']" style="min-height: 100%; overflow-x: hidden"
           background-color="rgb(48, 65, 86)"
           text-color="#fff"
           active-text-color="#ffd04b"
           :collapse-transition="false"
           :collapse="isCollapse"
           router
  >
    <div style="height: 60px; line-height: 60px; text-align: center">
      <img src="../assets/logo.png" alt="" style="width: 20px; position: relative; top: 5px; right: 5px">
      <b style="color: white" v-show="logoTextShow">后台管理系统</b>
    </div>

    <div v-for="item in menus" :key="item.id">
      <!-- 一级菜单 -->
      <div v-if="item.path">
        <el-menu-item :index="item.path">
          <template slot="title">
            <i :class="item.icon"></i>
            <span slot="title">{{ item.name }}</span>
          </template>
        </el-menu-item>
      </div>
      <!-- 二级菜单 -->
      <div v-else>
        <el-submenu :index="item.id+''">
          <template slot="title">
            <i :class="item.icon"></i>
            <span slot="title">{{ item.name }}</span>
          </template>
          <div v-for="subItem in item.children" :key="subItem.id">
            <el-menu-item :index="subItem.path">
              <template slot="title">
                <i :class="subItem.icon"></i>
                <span slot="title">{{ subItem.name }}</span>
              </template>
            </el-menu-item>
          </div>
        </el-submenu>
      </div>
    </div>

  </el-menu>
</template>

<script>
export default {
  name: "Aside",
  props: {
    isCollapse: Boolean,
    logoTextShow: Boolean
  },
  data() {
    return {
      menus: localStorage.getItem('menus') ? JSON.parse(localStorage.getItem('menus')) : []
    }
  },

}
</script>

<style scoped>

</style>

2.3 动态路由设计

为什么设置动态路由,这是因为没有其他页面权限的用户也是可以访问其他页面

如下图所示:

安其拉是普通用户,只有主页的菜单权限(自己设置的角色所拥有的菜单)

2.3.1 菜单表新增字段page_path

对应每个页面组件名称

sql 复制代码
CREATE TABLE `sys_menu` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',
  `path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '路径',
  `page_path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '页面路径',
  `pid` int(11) DEFAULT NULL COMMENT '父级id',
  `icon` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '图标',
  `description` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '描述',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC

2.3.2 路由设计

javascript 复制代码
import Vue from 'vue'
import VueRouter from 'vue-router'
import Manage from '../views/Manage.vue'
import store from "@/store";

Vue.use(VueRouter)
//定义一个路由对象数组
const routes = [
  {
    path: '/login',
    name: '登录',
    component: () => import('../views/Login.vue')
  },
  {
    path: '/register',
    name: '注册',
    component: () => import('../views/Register.vue')
  }

]

//使用路由对象数组创建路由实例,供main.js引用
const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

// 注意:刷新页面会导致页面路由重置
export const setRoutes = () => {
  const storeMenus = localStorage.getItem("menus");
  if (storeMenus) {

    // 获取当前的路由对象名称数组
    const currentRouteNames = router.getRoutes().map(v => v.name)
    if (!currentRouteNames.includes('Manage')) {
      // 拼装动态路由
      const manageRoute = { path: '/', name: 'Manage', component: () => import('../views/Manage.vue'), redirect: "/home", children: [
          { path: 'person', name: '个人信息', component: () => import('../views/Person.vue')},
          // { path: 'password', name: '修改密码', component: () => import('../views/Password.vue')}
        ] }
      const menus = JSON.parse(storeMenus)
      menus.forEach(item => {
        if (item.path) {  // 当且仅当path不为空的时候才去设置路由
          let itemMenu = { path: item.path.replace("/", ""), name: item.name, component: () => import('../views/' + item.pagePath + '.vue'),meta: { title: item.name }}
          manageRoute.children.push(itemMenu)
        } else if(item.children.length) {
          item.children.forEach(item => {
            if (item.path) {
              let itemMenu = { path: item.path.replace("/", ""), name: item.name, component: () => import('../views/' + item.pagePath + '.vue'),meta: { title: item.name }}
              manageRoute.children.push(itemMenu)
            }
          })
        }
      })
      // 动态添加到现在的路由对象中去
      router.addRoute(manageRoute)
    }

  }
}

// 重置我就再set一次路由
setRoutes()


// 路由守卫
// router.beforeEach((to, from, next) => {
//   localStorage.setItem('currentPathName',to.name);   // 设置当前的路由名称,为了在Header组件中去使用
//   store.commit('setPath')    // 触发store的数据更新
//   next()   // 放行路由
// })

export default router

2.3.3 登录界面Login.vue设置路由

改动登录页面在成功登录时,设置路由

js 复制代码
// 导入
import {setRoutes} from "@/router";

// 当登录成功时,同时设置路由
localStorage.setItem("user",JSON.stringify(res.data));  //存储用户信息到浏览器
localStorage.setItem("menus",JSON.stringify(res.data.menus));  //存储菜单到浏览器
setRoutes();
this.$router.push("/");
this.$message.success("登录成功");

总结

  • 本篇主要讲解动态菜单和动态路由的设计与实现

写在最后

如果此文对您有所帮助,请帅戈靓女们 务必不要吝啬你们的Zan ,感谢!!不懂的可以在评论区评论 ,有空会及时回复。
文章会一直更新

相关推荐
Sheldon一蓑烟雨任平生14 小时前
Vue3 插件(可选独立模块复用)
vue.js·vue3·插件·vue3 插件·可选独立模块·插件使用方式·插件中的依赖注入
鱼与宇16 小时前
苍穹外卖-VUE
前端·javascript·vue.js
摇滚侠16 小时前
Spring Boot3零基础教程,Spring Boot 应用打包成 exe 可执行文件,笔记91 笔记92 笔记93
linux·spring boot·笔记
裴嘉靖16 小时前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw28242616 小时前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
时间的情敌17 小时前
Vite 大型项目优化方案
vue.js
西洼工作室18 小时前
高效管理搜索历史:Vue持久化实践
前端·javascript·vue.js
lang2015092818 小时前
Spring Boot日志配置完全指南
java·spring boot·单元测试
故事不长丨18 小时前
【Java SpringBoot+Vue 实现视频文件上传与存储】
java·javascript·spring boot·vscode·后端·vue·intellij-idea
Jeffrey__Lin20 小时前
解决Grid布局下el-table自适应缩小失败的问题
vue.js·elementui·html