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 ,感谢!!不懂的可以在评论区评论 ,有空会及时回复。
文章会一直更新

相关推荐
独断万古他化6 分钟前
【SSM开发实战:博客系统】(三)核心业务功能开发与安全加密实现
spring boot·spring·mybatis·博客系统·加密
xjt_090121 分钟前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
rannn_11123 分钟前
【苍穹外卖|Day4】套餐页面开发(新增套餐、分页查询、删除套餐、修改套餐、起售停售)
java·spring boot·后端·学习
qq_124987075327 分钟前
基于JavaWeb的大学生房屋租赁系统(源码+论文+部署+安装)
java·数据库·人工智能·spring boot·计算机视觉·毕业设计·计算机毕业设计
我是伪码农32 分钟前
Vue 2.3
前端·javascript·vue.js
倒流时光三十年1 小时前
SpringBoot 数据库同步 Elasticsearch 性能优化
数据库·spring boot·elasticsearch
码农小卡拉1 小时前
深入解析Spring Boot文件加载顺序与加载方式
java·数据库·spring boot
Dragon Wu2 小时前
Spring Security Oauth2.1 授权码模式实现前后端分离的方案
java·spring boot·后端·spring cloud·springboot·springcloud
跳动的梦想家h2 小时前
环境配置 + AI 提效双管齐下
java·vue.js·spring
Mr Xu_2 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js