[Second Season] [SpringBoot+Vue] Practical Notes on Front-end and Back-end Separation Projects

Supporting video at station b: [Season 2] The simplest but practical SpringBoot+Vue front-end and back-end separation project in the whole network

The second season of SpringBoot+Vue project actual combat

1. Some optimizations

Refresh lost other tabs

  1. Cache open tabs

    tagsViewCache() {
          
          
        window.addEventListener("beforeunload", () => {
          
          
            let tabViews = this.visitedViews.map(item => {
          
          
                return {
          
          
                    fullPath: item.fullPath,
                    hash: item.hash,
                    meta: {
          
           ...item.meta },
                    name: item.name,
                    params: {
          
           ...item.params },
                    path: item.path,
                    query: {
          
           ...item.query },
                    title: item.title
                };
            });
            sessionStorage.setItem("tabViews", JSON.stringify(tabViews));
        });
        let oldViews = JSON.parse(sessionStorage.getItem("tabViews")) || [];
        if (oldViews.length > 0) {
          
          
            this.$store.state.tagsView.visitedViews = oldViews;
        }
    },
    

insert image description here

  1. remove all tagviews on logout

    // 注销时删除所有tagview
    await this.$store.dispatch('tagsView/delAllViews')
    sessionStorage.removeItem('tabViews')
    

insert image description here

Two, Swagger integration

Swagger-UI can dynamically generate online API documents based on annotations.

Common Notes

  • @Api: Used to modify the Controller class and generate Controller-related document information
  • @ApiOperation: Used to modify the methods in the Controller class and generate document information related to interface methods
  • @ApiParam: Used to modify the parameters in the interface and generate document information related to interface parameters
  • @ApiModelProperty: Used to modify the attributes of the entity class, when the entity class is a request parameter or return result, directly generate relevant document information

Integration steps:

  1. add dependencies

    <!--Swagger文档工具-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-boot-starter</artifactId>
        <version>3.0.0</version>
    </dependency>
    
  2. swagger configuration class

    @Configuration
    @EnableOpenApi
    @EnableWebMvc
    public class SwaggerConfig {
          
          
        @Bean
        public Docket api() {
          
          
            return new Docket(DocumentationType.OAS_30)
                    .apiInfo(apiInfo())
                    .select()
                    .apis(RequestHandlerSelectors.basePackage("com.lantu"))
                    .paths(PathSelectors.any())
                    .build();
        }
    
        private ApiInfo apiInfo() {
          
          
            return new ApiInfoBuilder()
                    .title("神盾局特工管理系统接口文档")
                    .description("全网最简单的SpringBoot+Vue前后端分离项目实战")
                    .version("1.0")
                    .contact(new Contact("qqcn", "http://www.qqcn.cn", "[email protected]"))
                    .build();
        }
    }
    
  3. The controller adds swagger annotations as needed

  4. Test: http://localhost:9999/swagger-ui/index.html

3. Jwt integration

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact, self-contained way to securely transmit information between parties as JSON objects. This information can be verified and trusted because it is digitally signed.

Example of jwt format:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5MjAzOThjZi1hYThiLTQzNWUtOTIxYS1iNGQ3MDNmYmZiZGQiLCJzdWIiOiJ7XCJwaG9uZVwiOlwiMTIzNDIzNFwiLFwidXNlcm5hbWVcIjpcInpoYW5nc2FuXCJ9IiwiaXNzIjoic3lzdGVtIiwiaWF0IjoxNjc3MTE4Njc2LCJleHAiOjE2NzcxMjA0NzZ9.acc7H6-6ACqcgNu5waqain7th7zJciP-41z-qgWeaSY

⑴ Integration steps

  1. pom

    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    
  2. Tools

    @Component
    public class JwtUtil {
          
          
        // 有效期
        private static final long JWT_EXPIRE = 30*60*1000L;  //半小时
        // 令牌秘钥
        private static final String JWT_KEY = "123456";
    
        public  String createToken(Object data){
          
          
            // 当前时间
            long currentTime = System.currentTimeMillis();
            // 过期时间
            long expTime = currentTime+JWT_EXPIRE;
            // 构建jwt
            JwtBuilder builder = Jwts.builder()
                    .setId(UUID.randomUUID()+"")
                    .setSubject(JSON.toJSONString(data))
                    .setIssuer("system")
                    .setIssuedAt(new Date(currentTime))
                    .signWith(SignatureAlgorithm.HS256, encodeSecret(JWT_KEY))
                    .setExpiration(new Date(expTime));
            return builder.compact();
        }
    
        private  SecretKey encodeSecret(String key){
          
          
            byte[] encode = Base64.getEncoder().encode(key.getBytes());
            SecretKeySpec aes = new SecretKeySpec(encode, 0, encode.length, "AES");
            return  aes;
        }
    
        public  Claims parseToken(String token){
          
          
            Claims body = Jwts.parser()
                    .setSigningKey(encodeSecret(JWT_KEY))
                    .parseClaimsJws(token)
                    .getBody();
            return body;
        }
    
        public <T> T parseToken(String token,Class<T> clazz){
          
          
            Claims body = Jwts.parser()
                    .setSigningKey(encodeSecret(JWT_KEY))
                    .parseClaimsJws(token)
                    .getBody();
            return JSON.parseObject(body.getSubject(),clazz);
        }
    
    }
    
  3. Test tools

  4. Modify login logic
    insert image description here
    insert image description here

  5. test login

Questions to think about:

How does the login subsequent request validate the jwt?

insert image description here

⑵ JWT verification interceptor

define interceptor

@Component
@Slf4j
public class JwtValidateInterceptor implements HandlerInterceptor {
    
    
    @Autowired
    private JwtUtil jwtUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        String token = request.getHeader("X-Token");
        System.out.println(request.getRequestURI() +" 待验证:"+token);
        if(token != null){
    
    
            try {
    
    
                jwtUtil.parseToken(token);
                log.debug(request.getRequestURI() + " 放行...");
                return true;
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
        log.debug(request.getRequestURI() + " 禁止访问...");
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(JSON.toJSONString(Result.fail(20003,"jwt令牌无效,请重新登录")));
        return false;
    }
}

register interceptor

@Configuration
public class MyWebConfig implements WebMvcConfigurer {
    
    
    @Autowired
    private JwtValidateInterceptor jwtValidateInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        InterceptorRegistration registration = registry.addInterceptor(jwtValidateInterceptor);
        registration.addPathPatterns("/**")
                .excludePathPatterns(
                        "/user/login",
                        "/user/info",
                        "/user/logout",
                        "/error",
                        "/swagger-ui/**",
                        "/swagger-resources/**",
                        "/v3/**");
    }
}

⑶ Swagger authorization configuration

@Configuration
@EnableOpenApi
@EnableWebMvc
public class SwaggerConfig {
    
    
    @Bean
    public Docket api() {
    
    
        return new Docket(DocumentationType.OAS_30)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.lantu"))
                .paths(PathSelectors.any())
                .build()
                .securitySchemes(Collections.singletonList(securityScheme()))
                .securityContexts(Collections.singletonList(securityContext()));
    }

    private SecurityScheme securityScheme() {
    
    
        //return new ApiKey("Authorization", "Authorization", "header");
        return new ApiKey("X-Token", "X-Token", "header");
    }

    private SecurityContext securityContext() {
    
    
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
                .forPaths(PathSelectors.regex("^(?!auth).*$"))
                .build();
    }

    private List<SecurityReference> defaultAuth() {
    
    
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        return Collections.singletonList(
                new SecurityReference("X-Token", authorizationScopes));
    }

    private ApiInfo apiInfo() {
    
    
        return new ApiInfoBuilder()
                .title("神盾局特工管理系统接口文档")
                .description("全网最简单的SpringBoot+Vue前后端分离项目实战")
                .version("1.0")
                .contact(new Contact("老蔡", "https://space.bilibili.com/431588578", "[email protected]"))
                .build();
    }
}

4. Role Management

1. Basic functions

⑴ Preview effect

insert image description here

⑵ front end

role.vue

<template>
  <div>
    <!-- 搜索栏 -->
    <el-card id="search">
      <el-row>
        <el-col :span="18">
          <el-input placeholder="角色名" v-model="searchModel.roleName" clearable> </el-input>
          <el-button @click="getRoleList" type="primary" icon="el-icon-search" round>查询</el-button>
        </el-col>
        <el-col :span="6" align="right">
          <el-button @click="openEditUI(null)" type="primary" icon="el-icon-plus" circle></el-button>
        </el-col>
      </el-row>
    </el-card>

    <!-- 结果列表 -->
    <el-card>
 
        <el-table :data="roleList" stripe style="width: 100%">
          <el-table-column label="#" width="80">
            <template slot-scope="scope">
              {
   
   {(searchModel.pageNo-1) * searchModel.pageSize + scope.$index + 1}}
            </template>
          </el-table-column>
          <el-table-column prop="roleId" label="角色编号" width="180">
          </el-table-column>
          <el-table-column prop="roleName" label="角色名称" width="180">
          </el-table-column>
          <el-table-column prop="roleDesc" label="角色描述" >
          </el-table-column>
          <el-table-column   label="操作" width="180">
            <template slot-scope="scope">
              <el-button @click="openEditUI(scope.row.roleId)" type="primary" icon="el-icon-edit" circle size="mini"></el-button>
              <el-button @click="deleteRole(scope.row)" type="danger" icon="el-icon-delete" circle size="mini"></el-button>
            </template>
          </el-table-column>
        </el-table> 
 
    </el-card>
    <el-pagination
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      :current-page="searchModel.pageNo"
      :page-sizes="[5, 10, 20, 50]"
      :page-size="searchModel.pageSize"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total">
    </el-pagination>

    <!-- 对话框 -->
    <el-dialog @close="clearForm" :title="title" :visible.sync="dialogFormVisible" :close-on-click-modal="false">
      <el-form :model="roleForm" ref="roleFormRef" :rules="rules">
        <el-form-item prop="roleName" label="角色名称" :label-width="formLabelWidth">
          <el-input v-model="roleForm.roleName" autocomplete="off"></el-input>
        </el-form-item>
        
        <el-form-item prop="roleDesc" label="角色描述" :label-width="formLabelWidth">
          <el-input v-model="roleForm.roleDesc" 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="saveRole">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>


<script>
import roleApi from '@/api/roleManage'
export default {
  data(){
    
    return{
      formLabelWidth: '130px',
      roleForm: {},
      dialogFormVisible: false,
      title: '',
      searchModel: {
        pageNo: 1,
        pageSize: 10
      },
      roleList: [],
      total: 0,
      rules:{
        roleName: [
          { required: true, message: '请输入角色名称', trigger: 'blur' },
          { min: 3, max: 50, message: '长度在 3 到 50 个字符', trigger: 'blur' }
        ]
      }
    }
  },
  methods:{
    deleteRole(role){
      this.$confirm(`您确定删除角色 ${role.roleName} ?`, '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
      }).then(() => {
        roleApi.deleteRoleById(role.roleId).then(response => {
          this.$message({
            type: 'success',
            message: response.message
          });
          this.dialogFormVisible = false;
          this.getRoleList();
        });
        
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        });          
      });
    },
    saveRole(){
      // 触发表单验证
      this.$refs.roleFormRef.validate((valid) => {
        if (valid) {
          // 提交保存请求
          roleApi.saveRole(this.roleForm).then(response => {
            // 成功提示
            this.$message({
              message: response.message,
              type: 'success'
            });
            // 关闭对话框
            this.dialogFormVisible = false;
            // 刷新表格数据
            this.getRoleList();
          });
          
        } else {
          console.log('error submit!!');
          return false;
        }
      });
      
    },
    clearForm(){
      this.roleForm = {};
      this.$refs.roleFormRef.clearValidate();
    },
    openEditUI(id){
      if(id == null){
        this.title = '新增角色';
      }else{
        this.title = '修改角色';
        roleApi.getRoleById(id).then(response => {
          this.roleForm = response.data;
        });
      }
      this.dialogFormVisible = true;
    },
    handleSizeChange(pageSize){
      this.searchModel.pageSize = pageSize;
      this.getRoleList();
    },
    handleCurrentChange(pageNo){
      this.searchModel.pageNo = pageNo;
      this.getRoleList();
    },
    getRoleList(){
      roleApi.getRoleList(this.searchModel).then(response => {
        this.roleList = response.data.rows;
        this.total = response.data.total;
      });
    }
  },
  created(){
    this.getRoleList();
  }
};
</script>

<style>
#search .el-input {
  width: 200px;
  margin-right: 10px;
}
.el-dialog .el-input{
  width: 85%;
}
</style>

roleManage.js

import request from '@/utils/request'

export default{
    
    
  // 分页查询角色列表
  getRoleList(searchModel){
    
    
    return request({
    
    
      url: '/role/list',
      method: 'get',
      params: {
    
    
        roleName: searchModel.roleName,
        pageNo: searchModel.pageNo,
        pageSize: searchModel.pageSize
      }
    });
  },
  // 新增
  addRole(role){
    
    
    return request({
    
    
      url: '/role',
      method: 'post',
      data: role
    });
  },
  // 修改
  updateRole(role){
    
    
    return request({
    
    
      url: '/role',
      method: 'put',
      data: role
    });
  },
  // 保存角色数据
  saveRole(role){
    
    
    if(role.roleId == null || role.roleId == undefined){
    
    
      return this.addRole(role);
    }
    return this.updateRole(role);
  },
  // 根据id查询
  getRoleById(id){
    
    
    return request({
    
    
      url: `/role/${
      
      id}`,
      method: 'get'
    });
  },
  // 根据id删除
  deleteRoleById(id){
    
    
    return request({
    
    
      url: `/role/${
      
      id}`,
      method: 'delete'
    });
  },

}

⑶ Backend

RoleController

@RestController
@RequestMapping("/role")
public class RoleController {
    
    

    @Autowired
    private IRoleService roleService;

    @GetMapping("/list")
    public Result<Map<String,Object>> getUserList(@RequestParam(value = "roleName",required = false) String roleName,
                                                  @RequestParam(value = "pageNo") Long pageNo,
                                                  @RequestParam(value = "pageSize") Long pageSize){
    
    
        LambdaQueryWrapper<Role> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StringUtils.hasLength(roleName),Role::getRoleName,roleName);
        wrapper.orderByDesc(Role::getRoleId);

        Page<Role> page = new Page<>(pageNo,pageSize);
        roleService.page(page, wrapper);

        Map<String,Object> data = new HashMap<>();
        data.put("total",page.getTotal());
        data.put("rows",page.getRecords());

        return Result.success(data);

    }

    @PostMapping
    public Result<?> addRole(@RequestBody Role role){
    
    
        roleService.save(role);
        return Result.success("新增角色成功");
    }

    @PutMapping
    public Result<?> updateRole(@RequestBody Role role){
    
    
        roleService.updateById(role);
        return Result.success("修改角色成功");
    }

    @GetMapping("/{id}")
    public Result<Role> getRoleById(@PathVariable("id") Integer id){
    
    
        Role role = roleService.getById(id);
        return Result.success(role);
    }

    @DeleteMapping("/{id}")
    public Result<Role> deleteRoleById(@PathVariable("id") Integer id){
    
    
        roleService.removeById(id);
        return Result.success("删除角色成功");
    }

}

2. Role permission setting display

insert image description here

⑴ front end

menuManage.js

import request from '@/utils/request'

export default{
    
    
  // 查询所有菜单数据
  getAllMenu(){
    
    
    return request({
    
    
      url: '/menu',
      method: 'get',
    });
  },
}

role.vue

<el-form-item
              prop="roleDesc"
              label="权限设置"
              :label-width="formLabelWidth"
              >
    <el-tree
             :data="menuList"
             :props="menuProps"
             node-key="menuId"
             show-checkbox
             style="width:85%"
             default-expand-all
             ></el-tree>
</el-form-item>

insert image description here
insert image description here

⑵ database

New data in x_menu table

delete from x_menu;
insert into `x_menu` (`menu_id`, `component`, `path`, `redirect`, `name`, `title`, `icon`, `parent_id`, `is_leaf`, `hidden`) values('1','Layout','/sys','/sys/user','sysManage','系统管理','userManage','0','N','0');
insert into `x_menu` (`menu_id`, `component`, `path`, `redirect`, `name`, `title`, `icon`, `parent_id`, `is_leaf`, `hidden`) values('2','sys/user','user',NULL,'userList','用户列表','user','1','Y','0');
insert into `x_menu` (`menu_id`, `component`, `path`, `redirect`, `name`, `title`, `icon`, `parent_id`, `is_leaf`, `hidden`) values('3','sys/role','role',NULL,'roleList','角色列表','roleManage','1','Y','0');
insert into `x_menu` (`menu_id`, `component`, `path`, `redirect`, `name`, `title`, `icon`, `parent_id`, `is_leaf`, `hidden`) values('4','Layout','/test','/test/test1','test','功能测试','form','0','N','0');
insert into `x_menu` (`menu_id`, `component`, `path`, `redirect`, `name`, `title`, `icon`, `parent_id`, `is_leaf`, `hidden`) values('5','test/test1','test1','','test1','测试点一','form','4','Y','0');
insert into `x_menu` (`menu_id`, `component`, `path`, `redirect`, `name`, `title`, `icon`, `parent_id`, `is_leaf`, `hidden`) values('6','test/test2','test2','','test2','测试点二','form','4','Y','0');
insert into `x_menu` (`menu_id`, `component`, `path`, `redirect`, `name`, `title`, `icon`, `parent_id`, `is_leaf`, `hidden`) values('7','test/test3','test3','','test3','测试点三','form','4','Y','0');

⑶ Backend

Added in the Menu class

@TableField(exist = false)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<Menu> children;

@TableField(exist = false)
private Map<String,Object> meta = new HashMap<>();
public Map<String,Object> getMeta(){
    
    
    meta.put("title",this.title);
    meta.put("icon",this.icon);
    return this.meta;
}

MenuController

@RestController
@RequestMapping("/menu")
public class MenuController {
    
    
    @Autowired
    private IMenuService menuService;

    @GetMapping
    public Result<?> getAllMenu(){
    
    
        List<Menu> menuList =  menuService.getAllMenu();
        return Result.success(menuList);
    }

}

MenuSeviceImpl

@Override
public List<Menu> getAllMenu() {
    
    
    // 一级菜单
    LambdaQueryWrapper<Menu> wrapper = new LambdaQueryWrapper();
    wrapper.eq(Menu::getParentId,0);
    List<Menu> menuList = this.list(wrapper);
    // 子菜单
    setMenuChildren(menuList);
    return menuList;
}

private void setMenuChildren(List<Menu> menuList) {
    
    
    if(menuList != null) {
    
    
        for (Menu menu:menuList) {
    
    
            LambdaQueryWrapper<Menu> subWrapper = new LambdaQueryWrapper();
            subWrapper.eq(Menu::getParentId, menu.getMenuId());
            List<Menu> subMenuList = this.list(subWrapper);
            menu.setChildren(subMenuList);
            // 递归
            setMenuChildren(subMenuList);
        }
    }
}

3. Submission of new role permissions

⑴ front end

insert image description here

⑵ Backend

insert image description here

4. Role permission echo

⑴ front end

insert image description here

⑵ Backend

insert image description here

RoleMenuMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lantu.sys.mapper.RoleMenuMapper">
    <select id="getMenuIdListByRoleId" parameterType="Integer" resultType="Integer">
        select
            a.`menu_id`
        from x_role_menu a, x_menu b
        where a.`menu_id` = b.`menu_id`
          and b.`is_leaf` = 'Y'
          and a.`role_id` = #{roleId}
    </select>
</mapper>

5. Role permission modification submission

⑴ Backend

Added RoleServiceImpl

@Override
@Transactional
public void updateRole(Role role) {
    
    
    // 更新role表
    this.updateById(role);
    // 清除原有权限
    LambdaQueryWrapper<RoleMenu> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(RoleMenu::getRoleId,role.getRoleId());
    roleMenuMapper.delete(wrapper);
    //新增权限
    for (Integer menuId : role.getMenuIdList()) {
    
    
        roleMenuMapper.insert(new RoleMenu(null,role.getRoleId(),menuId));
    }
}

6. Delete related permissions when role is deleted

⑴ Backend

insert image description here

5. User role setting

1. Role display

⑴ front end

roleManage.js

// 查询所有角色列表
getAllRole(){
    
    
    return request({
    
    
        url: '/role/all',
        method: 'get'
    });
},

user.vue
insert image description here
insert image description here

⑵ Backend

RoleController

insert image description here

2. Submit roles when adding new users

⑴ Backend

insert image description here

UserServiceImpl
insert image description here

3. Character echo

⑴ Backend

UserServiceImpl
insert image description here

4. Submit roles when modifying users

⑴ Backend

UserServiceImpl
insert image description here

5. Delete related roles when users are deleted

⑴ Backend

UserServiceImpl
insert image description here

Six, dynamic routing

1. Query the menu according to the user

⑴ Backend

MenuMapper.xml

<select id="getMenuListByUserId" resultType="Menu">
    SELECT *
    FROM x_menu a,
    x_role_menu b,
    x_user_role c
    WHERE a.`menu_id` = b.`menu_id`
    AND b.`role_id` = c.`role_id`
    AND a.`parent_id` = #{pid}
    AND c.`user_id` = #{userId}
</select>

yml

type-aliases-package: com.lantu.*.entity

MenuMapper.java

public interface MenuMapper extends BaseMapper<Menu> {
    
    
    public List<Menu> getMenuListByUserId(@Param("userId") Integer userId,
                                          @Param("pid") Integer pid);
}

MenuServiceImpl

@Override
    public List<Menu> getMenuListByUserId(Integer userId) {
    
    
        // 一级菜单
        List<Menu> menuList = this.getBaseMapper().getMenuListByUserId(userId, 0);
        // 子菜单
        setMenuChildrenByUserId(userId, menuList);
        return menuList;
    }

    private void setMenuChildrenByUserId(Integer userId, List<Menu> menuList) {
    
    
        if (menuList != null) {
    
    
            for (Menu menu : menuList) {
    
    
                List<Menu> subMenuList = this.getBaseMapper().getMenuListByUserId(userId, menu.getMenuId());
                menu.setChildren(subMenuList);
                // 递归
                setMenuChildrenByUserId(userId,subMenuList);
            }
        }
    }

Return data through the user/info interface

UserServiceImpl

insert image description here

2. Front-end dynamic routing processing

⑴ Modify the original routing configuration

src\router\index.js, keep the basic routing, delete or comment other

export const constantRoutes = [
  {
    
    
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    
    
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },
  {
    
    
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
    
    
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: {
    
     title: '首页', icon: 'dashboard', affix:true ,noCache: false}
    }]
  },  
]

⑵ Get menu data and save it to Vuex

src\store\modules\user.js

insert image description here

src\store\getters.js

insert image description here

⑶ Routing conversion

Modify permission.js in the src directory

insert image description here
insert image description here

import Layout from '@/layout'
// 路由转换
let myRoutes = myFilterAsyncRoutes(store.getters.menuList);
// 404
myRoutes.push({
    
    
    path: '*',
    redirect: '/404',
    hidden: true
});
// 动态添加路由
router.addRoutes(myRoutes);
// 存至全局变量
global.myRoutes = myRoutes;

next({
    
    ...to,replace:true})  // 防止刷新后页面空白

function myFilterAsyncRoutes(menuList) {
    
    
  menuList.filter(menu => {
    
    
    if (menu.component === 'Layout') {
    
    
      menu.component = Layout
      console.log(menu.component);
    } else {
    
    
      menu.component = require(`@/views/${
      
      menu.component}.vue`).default
    }
    // 递归处理子菜单
    if (menu.children && menu.children.length) {
    
    
      menu.children = myFilterAsyncRoutes(menu.children)
    }
    return true
  })
  return menuList;
}

⑷ route merge

src\layout\components\Sidebar\index.vue

insert image description here

Test the expected results. Users with different roles will display different menu lists after logging in.

So far, although the dynamic menu function has been realized, the security problem has not been solved. You can think about what problems exist?

Guess you like

Origin blog.csdn.net/m0_37613503/article/details/129349710