vue-element-admin动态菜单改造,含数据库以及前端目录结构参考

*小tip:每次重新启动前端项目加载困难可以crlt+shift+delete清除浏览器缓存后再重新启动

1.前端改造

首先我们拿到项目,得了解一下目录结构

他是通过src->router->index.js,根据路由来进行页面的跳转

constantRoutes:通用页面路由

asyncRoutes:动态路由

1.1参数讲解

各个参数的意思

有子目录的目录,以最常见的table举例,table在这里,ctrl+左键点进去就行

参数与目录的对照

1.2动态菜单思路

我们要做到的是,根据后端返回的json对象,动态的显示目录

而vue-element-admin,是写死了的菜单,所以我们调用后端接口,实现目录的拼接,最终达到实现动态菜单的目的

那么我们就要仿造router目录下index.js文件,动态的生成相似的json对象

1.3前端代码

修改router目录下的index.js中的asyncRoutes方法,使其为空,要我们动态的加入菜单

export const asyncRoutes = [
]

首先在src->api>user.js,加入一个接口方法

//这里加一个,根据data(token)的不同,后台会返回不同的字符串结果,动态菜单完成
export function authMenu(data) {
  const jsonData = JSON.stringify(data); // 手动转换为 JSON 字符串

  console.log("data数据是", jsonData); // 在这里添加打印语句

  return request({
    url: '/user/selectMenu',
    method: 'post',
    data: jsonData, // 使用转换后的 JSON 字符串
    headers: {
      'Content-Type': 'application/json'
    }
  });
}

修改store/modules/permission.js文件,在 generateRoutes方法里面调用上面的authMenu接口

import { asyncRoutes, constantRoutes } from '@/router'
import { authMenu } from '@/api/user'// 【新加入】引入请求,后面有文件,先不慌
import Layout from '@/layout'// 【新加入】引入layout


//这里自己写方法,作用就是向 asyncRoutes 插入路由,达到动态路由的效果
/**
 * 【新加入】后台查询的菜单数据拼装成路由格式的数据
 * @param routes
 */
export function generaMenu(routes, data) {
  //data挨个遍历
data.forEach(item => {
    //path不为空的话,就新建一个对象,装数据
  if (item.path !== '') {
      //这个就仿照目录的机构,搭建
    const menu = {
      path: item.path,
      component: Layout, //这个不用写data里面的内容,引用就行了
      redirect: item.redirect,
      children: [],
      meta: {   // 使用 title 和 icon 创建 meta 对象
        title: item.title,
        icon: item.icon
      }
    }
    //遍历子标签,并加入到主目录的children中去
    // 判断是否有子标签
    if (item.children && Array.isArray(item.children) && item.children.length > 0) {
      // 遍历子标签,并加入到主目录的 children 中去
      item.children.forEach(childItem => {
        const menu2 = {
          path: childItem.path,
          component: (resolve) => require([`@/views${childItem.component}`], resolve),
          name: childItem.name,
          meta: {   // 使用 title 和 icon 创建 meta 对象
            title: childItem.title,
            icon: childItem.icon
          }
        }
        // 加入到主目录的 children 中去
        menu.children.push(menu2)
      })
    }
    //追加
    routes.push(menu)
  }
})
//把404加到最后,因为作者说  // 404 page must be placed at the end !!!
const menu3 = {
  path: '*',
  redirect: '/404',
  hidden: true
}
//追加
routes.push(menu3)
}



const state = {
  routes: [],
  addRoutes: []
}

const mutations = {
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }
}

const actions = {
 generateRoutes({ commit,rootState },) {
    return new Promise(resolve => {
      const loadMenuData = [] // 保留加载动态路由的代码

      // authMenu 调用可能也需要根据你的需要来决定是否删除
      authMenu(rootState.user.token).then(response => {
        let data = response
        if (response.code !== 20000) {
          // 错误处理逻辑
        } else {
          data = response.data
          Object.assign(loadMenuData, data)
          const tempAsyncRoutes = Object.assign([], asyncRoutes)
          generaMenu(tempAsyncRoutes, loadMenuData)
          let accessedRoutes
          accessedRoutes = tempAsyncRoutes || []
          commit('SET_ROUTES', accessedRoutes)
          resolve(accessedRoutes)
        }
      })
    }).catch(error => {
      console.log(error)
    })
  }
}


export default {
  namespaced: true,
  state,
  mutations,
  actions
}

最后,修改views/login下的index.vue,dispatch 一下(在登录成功的前提下)

向handleLogin中添加dispatch( "permission/generateRoutes",userRoles)

handleLogin() {
      this.$refs.loginForm.validate((valid) => {
        if (valid) {
          this.loading = true;

          // 假设有一种方法用于获取用户角色,例如 getUserRoles()
          const userRoles = ["admin"];

          this.$store
            .dispatch("user/login", this.loginForm)
            .then(() => {
              // 将预定义的角色 "admin" 传递给 generateRoutes action
              return this.$store.dispatch(
                "permission/generateRoutes",
                userRoles
              );
            })
            .then((accessedRoutes) => {
              // 将动态路由添加到路由器
              this.$router.addRoutes(accessedRoutes);

              // 导航到指定路径或默认路径 ('/')
              this.$router.push({
                path: this.redirect || "/",
                query: this.otherQuery,
              });

              // 重置加载状态
              this.loading = false;
            })
            .catch(() => {
              // 在登录失败的情况下重置加载状态
              this.loading = false;
            });
        } else {
          console.log("提交错误!!");
          return false;
        }
      });
    },

2.后端改造

@Controller层

 @Resource
    private MenuService menuService;   

	/**
     * 动态菜单获取
     */
    @PostMapping("/selectMenu")
    public MenuResponse selectMenu(@RequestBody VoToken voToken) {
        MenuResponse res = new MenuResponse();
        try {
            // 验证token的合法和有效性
            String tokenValue = JwtUtil.verity(voToken.getToken());
            if (tokenValue != null && tokenValue.startsWith(JwtUtil.TOKEN_SUCCESS)) {
                // 从令牌中提取实际的用户名
                String username = tokenValue.replaceFirst(JwtUtil.TOKEN_SUCCESS, "");

                // 记录调用日志
                log.info("从令牌中提取的用户名: {}", username);

                // 调用 MenuService 获取菜单数据
                List<VoMenu> menus = menuService.getAllMenus();

                // 记录菜单数量的日志
                log.info("获取的菜单数量: {}", menus.size());

                // 构建响应对象
                res.setCode(Constants.STATUS_OK);
                res.setMsg(Constants.MESSAGE_OK);
                res.setData(menus);
            } else {
                // 记录token验证失败的日志
                log.warn("Token验证失败");

                // 验证失败
                res.setCode(Constants.STATUS_FAIL);
                res.setMsg(Constants.MESSAGE_FAIL);
            }
        } catch (Exception e) {
            // 记录处理请求时发生的异常
            log.error("处理请求时发生异常", e);

            // 处理异常
            res.setCode(60204);
            res.setMsg("返回失败");
        }
        return res;
    }

@Servicer

package com.mv.servicer;

import com.mv.entity.VO.VoMenu;

import java.awt.*;
import java.util.List;

public interface MenuService {
    List<VoMenu> getAllMenus();
}

@ServiceImpl

package com.mv.servicer.Impl;

import com.mv.entity.VO.VoMenu;
import com.mv.mapper.MenuMapper;
import com.mv.servicer.MenuService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.awt.*;
import java.util.List;


@Service
public class MenuServiceImpl implements MenuService {
    @Resource
    private MenuMapper menuMapper;


    @Override
    public List<VoMenu> getAllMenus() {
        List<VoMenu> allMenus = menuMapper.getAllMenus();
        return VoMenu.buildMenuTree(allMenus);
    }
    // 可以根据需要添加其他方法
}

@Mapper

package com.mv.mapper;

import com.mv.entity.VO.VoMenu;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface MenuMapper {

    List<VoMenu> getAllMenus();
}

@Mapper.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.mv.mapper.MenuMapper">
    <select id="getAllMenus" resultType="com.mv.entity.VO.VoMenu">
        SELECT * FROM menus;
    </select>
</mapper>

对应数据库的实体类@VoMenus

package com.mv.entity.VO;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class VoMenu {

    private Integer id;        // 使用 Integer 类型,与数据库表中的 id 类型匹配
    private String path;
    private String component;
    private String redirect;
    private String name;
    private String title;       // 新增字段 title
    private String icon;        // 新增字段 icon
    private Integer parent_id;   // 与数据库表中的 parent_id 类型匹配
    private List<VoMenu> children;

        // Getters and setters

        // Constructors
    public Integer getId() {
        return id;
    }

    public Integer getParentId() {
        return parent_id;
    }
    public static List<VoMenu> buildMenuTree(List<VoMenu> menuList) {
        Map<Integer, VoMenu> menuMap = new HashMap<>();

        for (VoMenu menu : menuList) {
            menuMap.put(menu.getId(), menu);
        }

        List<VoMenu> tree = new ArrayList<>();

        for (VoMenu menu : menuList) {
            if (menu.getParentId() != null) {
                VoMenu parent = menuMap.get(menu.getParentId());
                if (parent != null) {
                    if (parent.getChildren() == null) {
                        parent.setChildren(new ArrayList<>());
                    }
                    parent.getChildren().add(menu);
                }
            } else {
                // 如果没有父菜单,说明是顶级菜单
                tree.add(menu);
            }
        }

        return tree;
    }
}

后端单方面测试 我使用的是apifox

返回数据成功。

3.数据库menus以及前端目录结构参考

3.1数据库里的父子级菜单

** 我的动态菜单获取并没有设置权限,因为我的后台只有一个管理员admin

3.2.前端目录结构

位于原项目@/views下的permission文件夹

3.3.前后端互联展示

3.3.1后端返回数据:

{
    "code": 20000,
    "msg": "成功,",
    "data": [
        {
            "id": 1,
            "path": "/permission",
            "component": "Layout",
            "redirect": "/permission/page",
            "name": "Permission",
            "title": "Permission",
            "icon": "lock",
            "parent_id": null,
            "children": [
                {
                    "id": 2,
                    "path": "/permission/page",
                    "component": "/permission/page",
                    "redirect": null,
                    "name": "PagePermission",
                    "title": "Page Permission",
                    "icon": null,
                    "parent_id": 1,
                    "children": null,
                    "parentId": 1
                },
                {
                    "id": 3,
                    "path": "/permission/directive",
                    "component": "/permission/directive",
                    "redirect": null,
                    "name": "DirectivePermission",
                    "title": "Directive Permission",
                    "icon": null,
                    "parent_id": 1,
                    "children": null,
                    "parentId": 1
                },
                {
                    "id": 4,
                    "path": "/permission/role",
                    "component": "/permission/role",
                    "redirect": null,
                    "name": "RolePermission",
                    "title": "Role Permission",
                    "icon": null,
                    "parent_id": 1,
                    "children": null,
                    "parentId": 1
                }
            ],
            "parentId": null
        }
    ]
}

3.3.2前端渲染效果:

前后端互联完成。

  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值