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.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;將信…