当前位置: 首页 > news >正文

SpringBoot+Mybatis+MySQL+Vue+ElementUI前后端分离版:整体布局、架构调整(二)

目录

一、前言

二、后端调整 

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.dtcms.com/a/269719.html

相关文章:

  • 基于FPGA的累加算法实现
  • 2. 两数相加
  • 从零实现一个GPT 【React + Express】--- 【1】初始化前后端项目,实现模型接入+SSE
  • 领域驱动设计(DDD)重塑金融系统架构
  • Qt 与Halcon联合开发九:算法类设计与实现讲解(附源码)
  • AlphaEvolve:谷歌的算法进化引擎 | 从数学证明到芯片设计的AI自主发现新纪元
  • 告别“电量焦虑”,BLE如何提升可穿戴设备续航能力?
  • Flutter基础(前端教程④-组件拼接)
  • Linux NUMA调优实战:多线程程序加速方法
  • 电路研究9.3.10——合宙Air780EP中的AT开发指南:阿里云应用指南
  • Deepoc大模型:重构无人机认知边界的具身智能革命
  • 华为泰山服务器重启后出现 XFS 文件系统磁盘“不识别”(无法挂载或访问),但挂载点目录仍在且无数据
  • WPA2 与 WPA3:深入解析Wi-Fi安全协议
  • Linux网络:UDP socket创建流程与简单通信
  • 手机能用酒精擦吗?
  • 前端学习3--position定位(relative+absolute+sticky)
  • Android kotlin 协程的详细使用指南
  • SpringBoot校园外卖服务系统设计与实现源码
  • EXCEL链接模板无法自动链接到PowerBI?试试这个方法
  • 自动驾驶的“安全基石”:NVIDIA如何用技术守护未来出行
  • 最新 HarmonyOS API 20 知识库 重磅推出
  • 【计算机网络】王道考研笔记整理(1)计算机网络体系结构
  • 嘉立创黄山派下载watch ui demo 教程(sf32)
  • Modbus TCP转Profinet网关实现视觉相机与西门子PLC配置实例研究
  • OpenCV 图像哈希类cv::img_hash::AverageHash
  • ​扣子Coze飞书多维表插件通用参数和通用返回值
  • Mysql常用内置函数,复合查询及内外连接
  • 利用外部Postgresql及zookeeper,启动Apache Dolphinscheduler3.1.9
  • 小程序订阅消息设计:用户触达与隐私保护的平衡法则
  • STM32-定时器