*小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前端渲染效果:
前后端互联完成。
版权归原作者 梁一叙 所有, 如有侵权,请联系我们删除。