目录

一、前言

二、后端调整 

1.实体类调整

2.菜单相关接口

3.用户相关接口

4.新增工具类

5.新增菜单树返回类

6.配置类、拦截器 

三、前端调整

1.请求调整

2.页面布局、样式调整

1.user.vue 

2.index.vue 

3.请求拦截

四、开发过程中的问题

五、附:源码

1.源码下载地址

六、结语

一、前言

此文章在上次的基础上进行了部分调整,并根据用户体验(我自己)确认了页面整体布局和数据呈现,暂定就先这样,后续有需要或者有不协调的地方再调整。
此项目是在我上一个文章的后续开发, 需要的同学可以关注一下,文章链接如下:SpringBoot+Mybatis+MySQL+Vue+ElementUI前后端分离版:项目搭建(一)

(注:源码我会在文章结尾提供gitee连接,需要的同学可以去自行下载)

二、后端调整 

1.实体类调整

1.完善UserEntity.java


import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;import java.util.Date;@Data
public class UserEntity extends BaseEntity{/*** id 主键*/private Integer id;/*** name 姓名*/private String name;/*** age 年龄*/private Integer age;/*** birthday 生日*/@JsonFormat(pattern = "yyyy-MM-dd")@DateTimeFormat(pattern = "yyyy-MM-dd")private Date birthday;}

2.新增菜单实体类MenuEntity.java


import lombok.Data;/*** 菜单表* @TableName menu*/
@Data
public class MenuEntity extends BaseEntity {/*** 主键*/private Integer id;/*** 菜单名称*/private String menuName;/*** 父菜单ID*/private Integer parentId;/*** 路由路径*/private String path;/*** 组件路径*/private String component;/*** 权限标识*/private String perms;/*** 图标*/private String icon;/*** 排序*/private Integer sort;/*** 是否显示(0隐藏,1显示)*/private Integer visible;}

这里在数据库新建menu表,并添加几条测试数据。

CREATE TABLE `menu` (`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',`menu_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称',`parent_id` int DEFAULT '0' COMMENT '父菜单ID',`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '路由路径',`component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '组件路径',`perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '权限标识',`icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '图标',`sort` int DEFAULT '0' COMMENT '排序',`visible` tinyint(1) DEFAULT '1' COMMENT '是否显示(0隐藏,1显示)',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`create_by` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`update_by` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '更新人',`del_flag` int(10) unsigned zerofill DEFAULT '0000000000' COMMENT '删除标识0未删除,1已删除',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='菜单表';
-- 插入数据
INSERT INTO `menu` (id, menu_name, parent_id, path, component, perms, icon, sort, visible, create_time, create_by, update_time, update_by, del_flag)
VALUES
(1, '权限管理', 0, '/permission', '', '', 'lock', 1, 1, NOW(), 'admin', NULL, NULL, 0),(2, '用户管理', 1, '/user', 'src/view/user.vue', 'user:list', 'user', 1, 1, NOW(), 'admin', NULL, NULL, 0),(3, '角色管理', 1, '/role', 'src/view/role.vue', 'role:list', 'role', 2, 1, NOW(), 'admin', NULL, NULL, 0),(4, '菜单管理', 1, '/menu', 'src/view/menu.vue', 'menu:list', 'menu', 3, 1, NOW(), 'admin', NULL, NULL, 0);

 3.对于实体类公共字段,我提取了一个BaseEntity.java,后续实体类都继承此实体类。

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;import java.util.Date;/*** 实体类公共字段* @Author: wal* @Date: 2025/6/26*/
@Data
public class BaseEntity {/*** 创建时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date createTime;/*** 创建人*/private String createBy;/*** 修改时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date updateTime;/*** 修改人*/private String updateBy;/*** 删除标记0未删除1已删除(逻辑删除)*/private Integer delFlag;}

2.菜单相关接口

1.MenuController.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.wal.userdemo.DTO.resp.TreeDataResp;
import org.wal.userdemo.service.MenuService;
import org.wal.userdemo.utils.Result;import java.util.List;@RestController
@RequestMapping("/api/menu")
public class MenuController {@Autowiredprivate MenuService menuService;@GetMapping("/getMenuList")public Result<List<TreeDataResp>> getMenuList() {return Result.success(menuService.getMenuList(""));}
}

2.MenuService.java

import org.wal.userdemo.DTO.resp.TreeDataResp;import java.util.List;public interface MenuService {List<TreeDataResp> getMenuList(String  userId);
}

 3.MenuServiceImpl.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.wal.userdemo.DTO.resp.TreeDataResp;
import org.wal.userdemo.entity.MenuEntity;
import org.wal.userdemo.mapper.MenuMapper;
import org.wal.userdemo.service.MenuService;
import org.wal.userdemo.utils.BeanUtils;import java.util.*;@Service
public class MenuServiceImpl implements MenuService {@Autowiredprivate MenuMapper menuMapper;/*** 获取用户菜单列表* @param userId* @return*/@Overridepublic List<TreeDataResp> getMenuList(String userId) {List<MenuEntity> menuList = menuMapper.getMenuList(userId);List<TreeDataResp> treeDataRespList =BeanUtils.copyAsList(menuList, TreeDataResp.class);return buildMenuTree(treeDataRespList);}/*** 构建菜单树* @param menus* @return*/public List<TreeDataResp> buildMenuTree(List<TreeDataResp> menus) {Map<Integer, TreeDataResp> menuMap = new HashMap<>();menus.forEach(menu -> menuMap.put(menu.getId(), menu));List<TreeDataResp> rootMenus = new ArrayList<>();menus.forEach(menu -> {Integer parentId = menu.getParentId();if (parentId == null || parentId == 0) {rootMenus.add(menu);} else {TreeDataResp parent = menuMap.get(parentId);if (parent != null) {if (parent.getChildren() == null) {parent.setChildren(new ArrayList<>());}parent.getChildren().add(menu);}}});return rootMenus;}}

 4.MenuMapper.java

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.wal.userdemo.entity.MenuEntity;import java.util.List;/**
* @author Administrator
* @description 针对表【menu(菜单表)】的数据库操作Mapper
* @createDate 2025-07-07 00:12:30
* @Entity org.wal.userdemo.entity.Menu
*/
@Mapper
public interface MenuMapper {List<MenuEntity> getMenuList(@Param("userId") String  userId);}

5.MenuMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.wal.userdemo.mapper.MenuMapper"><resultMap id="BaseResultMap" type="org.wal.userdemo.entity.MenuEntity"><id property="id" column="id" /><result property="menuName" column="menu_name" /><result property="parentId" column="parent_id" /><result property="path" column="path" /><result property="component" column="component" /><result property="perms" column="perms" /><result property="icon" column="icon" /><result property="sort" column="sort" /><result property="visible" column="visible" /><result property="createTime" column="create_time" /><result property="createBy" column="create_by" /><result property="updateTime" column="update_time" /><result property="updateBy" column="update_by" /><result property="delFlag" column="del_flag" /></resultMap><select id="getMenuList" parameterType="String" resultMap="BaseResultMap">SELECT * FROM menu WHERE del_flag = 0 ORDER BY parent_id, sort;</select></mapper>

3.用户相关接口

1.UserController.java

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;
import org.wal.userdemo.DTO.req.QueryUserReq;
import org.wal.userdemo.entity.UserEntity;
import org.wal.userdemo.service.UserService;
import org.wal.userdemo.utils.Result;import java.util.List;@Slf4j
@RestController
@RequestMapping("/api/user")
public class UserController {@Autowiredprivate UserService userService;/*** 获取所有用户信息** @return List<UserEntity>*/@PostMapping("/getUserList")public Result<UserEntity> getUserList(@RequestBody QueryUserReq queryUserReq) {List<UserEntity> dataList = userService.getUserList(queryUserReq);Integer total = userService.getUserCount(queryUserReq);return Result.page(dataList, total);}
}

2.定义通用分页Result.java(前文已体现,只是新增一个分页构造函数)

   public static <T> Result<T> page(List<T> list, Integer total) {Result<T> result = new Result<>();result.setCode(200);result.setData(list);result.setTotal(total);result.setMessage("success");return result;}

 3.UserService.java(新增两个接口)

    /*** 查询所有用户** @return*/List<UserEntity> getUserList(QueryUserReq queryUserReq);/*** 查询用户数量** @return*/Integer getUserCount(QueryUserReq queryUserReq);

4.UserServiceImpl.java(新增两个实现方法)

    /*** 获取所有用户信息** @return List<UserEntity>*/@Overridepublic List<UserEntity> getUserList(QueryUserReq queryUserReq) {List<UserEntity> resp = userMapper.getUserList(queryUserReq);return resp;}/*** 获取用户数量** @return Integer*/@Overridepublic Integer getUserCount(QueryUserReq queryUserReq) {return userMapper.getUserCount(queryUserReq);}

5.UserMapper.java(新增两个mapper接口)

/*** 查询所有用户** @return*/List<UserEntity> getUserList(QueryUserReq queryUserReq);/*** 查询用户数量** @return*/Integer getUserCount(QueryUserReq queryUserReq);

 6.UserMapper.xml(新增两个sql)

    <select id="getUserList" resultMap="BaseResultMap" parameterType="org.wal.userdemo.DTO.req.QueryUserReq">select * from user<where><if test="name != null and name != ''">and name like concat('%',#{name},'%')</if><if test="birthday != null">and birthday = #{birthday}</if>and del_flag = 0</where>limit #{page},#{limit};</select><select id="getUserCount" resultType="Integer" parameterType="org.wal.userdemo.DTO.req.QueryUserReq">select count(*) from user<where><if test="name != null and name != ''">and name like concat('%',#{name},'%')</if><if test="birthday != null">and birthday = #{birthday}</if>and del_flag = 0</where>;</select>

4.新增工具类

1.新增工具类BeanUtils.java,具体体现在MenuServiceImpl.java类中copy菜单树,如下:

    /*** 获取用户菜单列表* @param userId* @return*/@Overridepublic List<TreeDataResp> getMenuList(String userId) {List<MenuEntity> menuList = menuMapper.getMenuList(userId);List<TreeDataResp> treeDataRespList =BeanUtils.copyAsList(menuList, TreeDataResp.class);return buildMenuTree(treeDataRespList);}
(为什么不直接用MenuEntity.java来构建树结构?,为了确保entity无属性、字段、方法侵入,解耦entity,声明resp类更容易理解和维护)。

此工具类是对org.springframework.beans.BeanUtils的封装。有需要的同学可以去一下链接查找:

 gitee地址dev-utils分支,此分支是我用来实现和调试、测试工具类的分支。

5.新增菜单树返回类


import lombok.Data;import java.util.List;
@Data
public class TreeDataResp {/*** 主键*/private Integer id;/*** 菜单名称*/private String menuName;/*** 父菜单ID*/private Integer parentId;/*** 路由路径*/private String path;/*** 组件路径*/private String component;/*** 权限标识*/private String perms;/*** 图标*/private String icon;/*** 排序*/private Integer sort;/*** 是否显示(0隐藏,1显示)*/private Integer visible;/*** 子菜单*/private List<TreeDataResp> children;
}

6.配置类、拦截器 

1.新增JwtInterceptor.java拦截web请求,校验token信息。


import io.jsonwebtoken.JwtException;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.wal.userdemo.utils.JwtUtil;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Slf4j
@Component
public class JwtInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("Authorization");if (token != null && token.startsWith("Bearer ")) {token = token.substring(7);try {String username = JwtUtil.parseUsername(token);// 可以将 username 存入 request 或 SecurityContextlog.info("用户 {} 使用正确的token访问了后端接口", username);return true;} catch (JwtException e) {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "无效 Token");return false;}} else {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "缺少 Token");return false;}}
}

2.新增WebConfig.java类,针对特定路由接口挂载JwtInterceptor拦截器,忽略登录接口。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.wal.userdemo.interceptor.JwtInterceptor;@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate JwtInterceptor jwtInterceptor;/*** 添加拦截器* 拦截路径为/api/**的请求,除了 /api/auth/login请求* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(jwtInterceptor).addPathPatterns("/api/**").excludePathPatterns("/api/auth/login");}
}

至此,后端的调整暂时就这样。 

三、前端调整

1.请求调整

1.重写login.vue的js部分,抽离请求体,在src创建api目录,在api下创建login.js,在js部分引入

import { login } from '@/api/login';export default {name: 'UserLogin',data() {return {formData: {username: '',password: ''},rules: {username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],}};},methods: {async login() {try {const res = await login(this.formData);console.log('res.code', res)if (res.data.code === 200) {const token = res.data.data;localStorage.setItem('token', token);this.$router.push('/');this.$message.success('登录成功');} else {this.$message.error(res.data.message || '登录失败');}} catch (error) {this.$message.error('请求异常,请检查网络或服务端状态');}}}
};

2.login.js如下:

// src/api/login.jsimport request from '@/utils/request';/*** 用户登录* @param {Object} data - 登录参数,如用户名和密码* @returns {Promise}*/
export function login(data) {return request({url: '/auth/login',method: 'post',data,});
}/*** 用户退出(登出)* @returns {Promise}*/
export function logout() {return request({url: '/auth/logout',method: 'post',});
}

2.页面布局、样式调整

1.user.vue 

1.user.vue布局调整

<template><div><!-- 查询条件 --><el-form :inline="true" label-position="right" label-width="80px" :model="queryForm"class="demo-form-inline query-border-container"><el-row :gutter="20" justify="center"><!-- 姓名 --><el-col :span="7"><el-form-item label="姓名"><el-input v-model="queryForm.name" placeholder="请输入姓名"></el-input></el-form-item></el-col><!-- 出生日期 --><el-col :span="7"><el-form-item label="出生日期"><el-date-picker v-model="queryForm.birthday" type="date" placeholder="选择日期"style="width: 100%;"></el-date-picker></el-form-item></el-col><!-- 按钮组 --><el-col :span="7"><el-form-item><div style="display: flex; gap: 10px;"><el-button type="primary" @click="onQuery">查询</el-button><el-button @click="onReset">重置</el-button></div></el-form-item></el-col></el-row></el-form><!-- 用户列表 --><el-table :data="tableData" style="width: 100%;" class="table-border-container" max-height="480"v-loading="loading"><el-table-column type="index" label="序号" width="100" align="center"></el-table-column><el-table-column prop="name" label="姓名" width="180" align="center"></el-table-column><el-table-column prop="age" label="年龄" width="180" align="center"></el-table-column><el-table-column prop="birthday" label="出生日期" width="180" align="center"></el-table-column><el-table-column prop="birthday" label="出生日期" width="180" align="center"></el-table-column><el-table-column prop="birthday" label="出生日期" width="180" align="center"></el-table-column><el-table-column label="操作" width="180" align="center"><template #default="scope"><el-button type="primary" size="small" @click="handleEdit(scope.$index, scope.row)">编辑</el-button><el-button type="danger" size="small" @click="handleDelete(scope.$index, scope.row)">删除</el-button></template></el-table-column></el-table><el-pagination background layout="total,sizes,prev, pager, next" :total="total" @size-change="handleSizeChange":page-size.sync="queryForm.limit" :page-sizes="[10, 20, 50, 100]" class="page-border-container"></el-pagination></div></template><script>
import { getUserList } from '@/api/permission/user';export default {name: 'userView',data() {return {tableData: [],queryForm: {page: 1,limit: 10,username: '',birthday: '',},total: 0,loading: false,};},created() {this.getUserList();},methods: {getUserList() {this.loading = true;getUserList(this.queryForm).then(res => {if (res.data.code == 200) {this.tableData = res.data.data;this.total = res.data.total;// this.$message.success("获取用户列表成功!");} else {this.$message.error("获取用户列表失败!");}}).finally(() => {this.loading = false;});},// 查询onQuery() {this.getUserList();},// 重置表单并查询onReset() {this.queryForm = {page: 1,limit: 10,name: '',birthday: '',};this.getUserList();},handleSizeChange(val) {this.queryForm.limit = val;this.getUserList();},handleEdit(index, row) {console.log(index, row);this.$message.success('编辑成功');},handleDelete(index, row) {console.log(index, row);this.$message.success('删除成功');},},
};
</script>
<style scoped>
.query-border-container {border: 1px dashed #dcdcdc;border-radius: 8px;padding: 8px 16px;background-color: #fff;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);margin-bottom: 12px;
}.table-border-container {border: 1px dashed #dcdcdc;border-radius: 8px;padding: 8px 16px;background-color: #fff;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);margin-bottom: 12px;
}.page-border-container {border: 1px dashed #dcdcdc;border-radius: 8px;padding: 8px 16px 8px 16px;background-color: #fff;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);margin-bottom: 1px;
}.el-table .el-table__cell {padding: 5px 0px !important;
}.el-form-item {margin-bottom: 2px !important;
}
</style>

 2.同样的,js请求抽出来到user.js下,目录在src/api/permission/下,

import request from '@/utils/request';/*** 查询用户列表(分页)* @param {Object} params - 请求参数,如 page, limit 等*/
export function getUserList(params) {return request({url: '/user/getUserList',method: 'post',data : params,});
}

3. user.vue作为后续页面的参考页面,所以我把CSS部分抽出来到src/assets/css/global.css如下:

.query-border-container {border: 1px dashed #dcdcdc;border-radius: 8px;padding: 8px 16px;background-color: #fff;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);margin-bottom: 12px;
}.table-border-container {border: 1px dashed #dcdcdc;border-radius: 8px;padding: 8px 16px;background-color: #fff;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);margin-bottom: 12px;
}.page-border-container {border: 1px dashed #dcdcdc;border-radius: 8px;padding: 8px 16px 8px 16px;background-color: #fff;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);margin-bottom: 1px;
}.el-table .el-table__cell {padding: 5px 0px !important;
}.el-form-item {margin-bottom: 2px !important;
}

 CSS作为全局页面的样式需要在main.js中挂载,添加如下代码:

import '@/assets/css/global.css';
2.index.vue 

1.index.vue页面调整,主要是抽取js请求,调整布局和响应式菜单,如下:

<template><el-container class="home-container"><!-- 左侧区域 --><el-aside class="left-section" :width="'12%'"><!-- 左上部分:logo + 标题 --><div class="top-left"><div class="logo-container"><img src="../assets/logo.png" alt="logo"></div><h1>我的管理系统</h1></div><!-- 左下部分:菜单 --><el-menu default-active="1" class="sidebar-menu" :collapse="isCollapse" :collapse-transition="false"@open="handleOpen" @close="handleClose" background-color="#304156" text-color="#fff"active-text-color="#ffd04b"><el-submenu v-for="menu in menuList" :key="menu.id" :index="menu.id + ''"><template #title><i :class="'el-icon-' + menu.icon"></i><span>{{ menu.menuName }}</span></template><el-menu-item v-for="child in menu.children" :key="child.id" :index="child.path"@click="handleMenuClick(child)">{{ child.menuName }}</el-menu-item></el-submenu></el-menu></el-aside><!-- 右侧区域 --><el-container class="right-section"><!-- 右上部分:顶部导航 --><el-header class="top-right-header"><div class="header-right"><span>欢迎,Admin</span><el-button type="text" @click="logout">退出</el-button></div></el-header><!-- 右下部分:主内容区域 --><el-main class="main-content"><router-view /><user /></el-main></el-container></el-container>
</template><script>
import user from './user.vue'
import { logout } from '@/api/login'
import { getMenuList } from '@/api/permission/menu'export default {name: 'userIndex',components: { user },data() {return {isCollapse: false, // 默认展开menuList: [],// 菜单列表};},created() {this.getMenuList();},methods: {getMenuList() {getMenuList().then(res => {console.log('res.data', res.data)if (res.data.code === 200) {this.menuList = res.data.data || [];this.$message.success("获菜单列表c成功!");} else {this.$message.error("获菜单列表失败!");}});},logout() {logout().then(res => {if (res.code === 200) {localStorage.removeItem('token');this.$router.push('/login');this.$message.success('退出成功');} else {this.$message.error('退出失败');}}).catch(() => {this.$message.error('请求异常');});},handleMenuClick(menuItem) {this.$router.push(menuItem.path); // 跳转到对应路径},handleOpen(key, keyPath) {console.log(key, keyPath);},handleClose(key, keyPath) {console.log(key, keyPath);},},
};
</script><style scoped>
.home-container {height: 100vh;
}/* 左侧整体样式 */
.left-section {display: flex;flex-direction: column;background-color: #304156;color: white;padding: 10px;width: 50px;
}/* 左上角 logo 和标题 */
.top-left {display: flex;align-items: center;margin-bottom: 20px;
}.logo-container {margin-right: 10px;margin-top: 5px;
}.logo-container img {height: 20px;width: auto;object-fit: contain;
}.top-left h1 {font-size: 18px;margin: 0;color: white;
}/* 菜单样式 */
.sidebar-menu {flex: 1;border-right: none;
}/* 右侧整体样式 */
.right-section {display: flex;flex-direction: column;
}/* 右上角导航栏 */
.top-right-header {display: flex;justify-content: flex-end;align-items: center;background-color: #ffffff;box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);padding: 0 20px;
}.header-right {display: flex;align-items: center;
}/* 主内容区域 */
.main-content {padding: 20px;
}
</style>

2.index.vue抽取的js在src/api/permission/下,menu.js如下:

import request from '@/utils/request';
/*** 查询菜单列表* @param {Object} userId {可选} - 用户ID
}*/
export function getMenuList() {return request({url: '/menu/getMenuList',method: 'get',});
}

3.请求拦截

1.在src下新建utils目录,在utils下新建request.js,所以请求js都要导入request.js,在request.js中声明请求配置、请求拦截器,如下:

import axios from 'axios';const service = axios.create({baseURL: process.env.VUE_APP_BASE_API || '/api', // 使用环境变量或默认值timeout: 5000,
});// 请求拦截器:添加 token 到 header
service.interceptors.request.use(config => {const token = localStorage.getItem('token');if (token) {config.headers['Authorization'] = 'Bearer ' + token;}return config;},error => {return Promise.reject(error);}
);
export default service;

至此,前端布局、请求调整到此结束。 

四、开发过程中的问题

1.code review

在调试过程中,不断的重启后端项目,导致token失效,请求都是401未授权访问。

解决方案:在request.js中定义响应拦截器,把遇到error = 401重新跳转到登录页。

//响应拦截器(可选启用)
service.interceptors.response.use(response => {return response;},error => {if(error.response.data.error == 'Unauthorized'){console.error('token已失效请重新登录');localStorage.removeItem('token');window.location.href = '/login';}console.error('网络异常:', error);return Promise.reject(error.message);}
);

五、附:源码

1.源码下载地址

https://gitee.com/wangaolin/user-demo.git

同学们有需要可以自行下载查看,此文章是dev-vue分支。

六、结语

此次开发+调整只是为了后续开发有个参照,下一篇文章具体开发首页和权限管理,有需要的同学可以关注我。

(注:接定制化开发前后端分离项目,私我)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/88082.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/88082.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/88082.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue3官方文档学习心得

这几天抽空把vue3的文档整个看了一遍。简介 | Vue.js 23年写过一个vue2的项目&#xff0c;24年写了一个vue3的项目&#xff0c;页面功能比较简单&#xff0c;用几个简单的API&#xff0c;watch、watchEffect、ref、reactive就能实现的业务功能。 写了几年的react的&#xff0…

Pycharm恢复默认设置,配置导致复制粘贴等不能使用

在file 种找到manage IDE settings在manage IDE settings中找到restore default settings

【王树森推荐系统】召回12:曝光过滤 Bloom Filter

概述 曝光过滤通常是在召回阶段做&#xff0c;具体的方法就是用 Bloom Filter 曝光过滤问题 如果用户看过某个物品&#xff0c;则不再把该物品曝光给用户。原因是同一个物品重复曝光给用户会损害用户体验&#xff0c;但也不是所有推荐系统都有曝光过滤&#xff0c;像 youtube 这…

基于STM32单片机的心率血氧监测系统设计(STM32代码编写+手机APP设计+PCB设计+Proteus仿真)

系列文章目录 文章目录 系列文章目录前言1 资料获取与演示视频1.1 资料介绍1.2 资料获取1.3 演示视频 2 系统框架3 硬件3.1 主控制器3.2 显示屏3.3 WIFI模块3.4心率血氧传感器 4 设计PCB4.1 安装下载立创EDA专业版4.2 画原理图4.4 使用嘉立创下单助手进行下单&#xff0c;打板。…

main(int argc,char **agrv)的含义

今天和大家讨论一个常见的但是不容易深入了解的知识点。那就是 main 函数声明中使用到的 argc 和 argv 的含义。通常我们写主函数的时候一般都是直接使用int main() 或者 void main() 来声明 main 函数。但是你知道吗&#xff1f;在c89/c99的语言标准中&#xff0c;main函数的声…

如何简单实现发版不影响客户使用?nginx负载

nginx负载发版不影响客户使用 1.需要二台服务器 2.二台服务器均是正式环境配置 3.服务器Nginx配置修改 发版顺序&#xff1a;先在服务器2发版&#xff0c;发布成功后&#xff0c;再改服务器Nginx配置&#xff0c;重新加载nginx&#xff1b;然后在服务器再发版&#xff0c;发布成…

qt笔记(1)——Qtablewidget使用

1.基础使用方法 &#xff08;略&#xff09; 2.坑和注意点 2.1 设置一个单元格的编辑属性 在代码中&#xff0c;想要修改一个单元格的编辑属性&#xff0c;需要对这个item的flags进行设置&#xff1b;注意对一个tablewidget的一个item成员进行设置后&#xff0c;进行一次编…

字符串的模糊匹配方法介绍

字符串的模糊匹配方法介绍 目录字符串的模糊匹配方法介绍一、编辑距离&#xff08;Levenshtein Distance&#xff09;复杂度分析二、Jaro-Winkler 距离复杂度分析三、最长公共子序列&#xff08;LCS&#xff09;复杂度分析四、模糊搜索&#xff08;Fuzzy Search&#xff09;复杂…

ActiveMQ在Spring Boot中的详细使用指南

📋 目录 🚀 ActiveMQ简介 什么是ActiveMQ? 核心概念 🏗️ 基础架构组件 📝 重要概念解释 ActiveMQ vs 其他消息中间件 🔧 环境搭建 1. ActiveMQ服务端安装 Docker方式(推荐初学者) 手动安装方式 2. 验证安装 访问Web管理界面 连接参数 测试连接 �…

二元一次方程

前言 最近刚学二元一次方程&#xff0c;想写一篇专栏熟悉一下本文写给初一的同学看&#xff0c;学过的就划了吧二元一次方程 两个未知数最高项次数为 111 次为整式方程二元一次方程的解不唯一&#xff0c;但是二元一次方程可以用一个未知数来表达另一个未知数eg:eg:eg: xy1x y…

AI编程的未来是智能体原生开发?

目录 前言 一、从“串行”到“并行”&#xff1a;什么是智能体原生开发&#xff1f; 1.1 传统模式&#xff08;串行思维&#xff09; 1.2 智能体原生模式&#xff08;并行思维&#xff09; 二、程序员的新角色&#xff1a;从代码手艺人到系统思想家 三、软件开发的终局&a…

【牛客刷题】小红的与运算

文章目录 一、题目介绍1.1 题目描述1.2 输入描述1.3 输出描述1.4 示例二、 解题思路2.1 核心算法设计2.2 性能优化关键2.3 算法流程图三、解法实现3.1 解法一:基础实现3.1.1 初级版本分析3.2 解法二:优化版本(推荐)3.2.1 优化版本分析四、总结与拓展4.1 关键优化技术4.2 算…

spring中 方法上@Transation实现原理

Spring中Transactional注解方法实现原理Spring的Transactional注解在方法级别实现事务管理的原理主要基于动态代理和拦截器机制&#xff0c;以下是其核心实现流程&#xff1a;1. 代理创建阶段当Spring容器启动时&#xff0c;会为带有Transactional注解的类创建代理对象&#xf…

qt-C++语法笔记之Stretch与Spacer的关系分析

qt-C语法笔记之Stretch与Spacer的关系分析 code review! 文章目录qt-C语法笔记之Stretch与Spacer的关系分析1. Stretch&#xff08;拉伸因子&#xff09;2. Horizontal Spacer 和 Vertical Spacer3. Stretch 和 Spacer 的关系4. 实际应用中的选择5. 注意事项6. 代码与 Qt Desig…

Qwen3技术综述

1. 引入 2025年5月&#xff0c;qwen推出了旗舰模型&#xff08;flagship model&#xff09;Qwen3-235B-A22B。并以Apache 2.0版权发布&#xff08;可自由商业使用&#xff0c;修改代码和商用要包含原始版权&#xff09;。本文对其技术报告中提到的数据处理技术与模型结构进行综…

[特殊字符] Excel 读取收件人 + Outlook 批量发送带附件邮件 —— Python 自动化实战

许多公司定期需要将不同部门或客户的报告发送给指定人员。手动操作容易出错、耗时且繁琐。今天这篇文章教你如何利用 Python 实现&#xff1a; &#x1f9e9; 从 Excel 中读取“收件人 抄送人 附件文件路径”&#xff1b; &#x1f4e4; 使用 win32com.client 调用 Outlook …

多模态大语言模型arxiv论文略读(152)

VidComposition: Can MLLMs Analyze Compositions in Compiled Videos? ➡️ 论文标题&#xff1a;VidComposition: Can MLLMs Analyze Compositions in Compiled Videos? ➡️ 论文作者&#xff1a;Yunlong Tang, Junjia Guo, Hang Hua, Susan Liang, Mingqian Feng, Xinya…

基于AR和SLAM技术的商场智能导视系统技术原理详解

本文面对室内定位算法工程师、智慧商场系统开发者、对VR/AR应用开发感兴趣的技术人员&#xff0c;解决如何通过SLAMAR技术破解大型商场室内导航的空间认知壁垒&#xff0c;实现沉浸式导览&#xff0c;本文提供完整技术方案与代码实现。 如需获取商场智能导视系统解决方案请前往…

Debezium日常分享系列之:认识Debezium Operator

Debezium日常分享系列之&#xff1a;认识Debezium Operator什么是Debezium OperatorDebezium Operator 的工作原理Debezium Operator 的优点Debezium Operator 使用场景Debezium Operator 的关键组件部署Debezium OperatorDebezium Operator 的使用什么是Debezium Operator De…

POSIX信号量,环形队列

是一种进程间或线程间同步机制&#xff0c;用于控制多个线程/进程对共享资源的访问&#xff0c;避免并发冲突。可以看作是一个计数器&#xff0c;通过对计数器的操作&#xff08;PV操作&#xff09;实现同步P操作(原子性)&#xff1a;&#xff0d;&#xff0d;&#xff0c;将信…