2026 实战:Vue3 + Element Plus 搭前台,MyBatis-Plus 造数据(全栈保姆级教程)
这篇文章避开了网上泛滥的“TodoList”入门教程,而是以一个标准的企业级后台管理系统(Admin System)为蓝本,重点拆解登录鉴权流、MyBatis-Plus 高效CRUD、以及前后端接口规范化对接。代码可直接复用,逻辑深度对标大厂规范。
写在前面的话:
很多同学学完了 Vue3 和 Spring Boot,却卡在了“如何把它们无缝拼起来”这一步。本文将通过一个用户管理模块,带你从 0 到 1 实现一个标准的前后端分离项目。我们不搞花架子,只讲企业里天天在用的JWT鉴权、分页查询、统一响应体等核心痛点。
🏗️ 一、 技术选型与项目初始化
1. 技术栈(2026 稳如老狗版)
-
前端:Vue 3.4+ (Composition API) + Vite + Element Plus + Axios + Pinia
-
后端:Spring Boot 3.x + JDK 17 + MyBatis-Plus + MySQL 8.0 + Lombok
-
工具:Knife4j (Swagger增强) + Hutool (工具集)
2. 数据库设计(User表)
CREATE TABLE `sys_user` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`username` VARCHAR(50) NOT NULL COMMENT '用户名',
`password` VARCHAR(100) NOT NULL COMMENT '密码(加密存储)',
`nick_name` VARCHAR(50) DEFAULT NULL COMMENT '昵称',
`status` TINYINT DEFAULT '1' COMMENT '状态:1-正常 0-禁用',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
🛠️ 二、 后端实战:MyBatis-Plus 极速开发
1. 核心依赖 (pom.xml)
<dependencies>
<!-- Web & Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus (注意是3.5.7+版本,对SpringBoot3支持最好) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JWT (jjwt) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
</dependencies>
2. 实体类与 Mapper(告别 XML)
Entity (SysUser.java):
@Data
@TableName("sys_user")
public class SysUser {
@TableId(type = IdType.ASSIGN_ID) // 雪花算法生成ID
private Long id;
private String username;
private String password;
private String nickName;
private Integer status;
private LocalDateTime createTime;
}
Mapper (SysUserMapper.java):
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
// MyBatis-Plus 提供了通用的CRUD,这里无需写任何方法
}
3. Service 层实现分页与查询
Service (UserService.java):
public interface UserService extends IService<SysUser> {
PageResult<SysUser> queryUserPage(String username, Integer pageNum, Integer pageSize);
}
ServiceImpl (UserServiceImpl.java):
@Service
public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements UserService {
@Override
public PageResult<SysUser> queryUserPage(String username, Integer pageNum, Integer pageSize) {
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
// 动态SQL:如果用户名不为空,则拼接条件
wrapper.like(StringUtils.isNotBlank(username), SysUser::getUsername, username)
.orderByDesc(SysUser::getCreateTime);
Page<SysUser> page = this.page(new Page<>(pageNum, pageSize), wrapper);
return new PageResult<>(page.getTotal(), page.getRecords());
}
}
4. 统一响应体与全局异常(规范!规范!)
统一返回类 (ApiResponse.java):
@Data
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(200);
response.setMessage("success");
response.setData(data);
return response;
}
// fail 方法省略...
}
Controller (UserController.java):
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/page")
public ApiResponse<PageResult<SysUser>> page(
@RequestParam(required = false) String username,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
PageResult<SysUser> result = userService.queryUserPage(username, pageNum, pageSize);
return ApiResponse.success(result);
}
}
🎨 三、 前端实战:Vue3 + Element Plus
1. Axios 封装(拦截器是关键)
src/utils/request.js:
import axios from 'axios';
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API, // 环境变量配置
timeout: 5000
});
// 请求拦截器:注入 Token
service.interceptors.request.use(config => {
const token = localStorage.getItem('TOKEN');
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
}, error => Promise.reject(error));
// 响应拦截器:统一处理错误
service.interceptors.response.use(response => {
return response.data; // 直接返回 data 部分,简化调用
}, error => {
ElMessage.error(error.response?.data?.message || '请求失败');
return Promise.reject(error);
});
export default service;
2. 用户管理页面(核心 CRUD 组件)
src/views/user/index.vue:
<template>
<div class="app-container">
<!-- 搜索栏 -->
<el-form :inline="true">
<el-form-item label="用户名">
<el-input v-model="queryParams.username" placeholder="请输入用户名" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getList">查询</el-button>
</el-form-item>
</el-form>
<!-- 表格 -->
<el-table :data="tableData" style="width: 100%" v-loading="loading">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="username" label="用户名" />
<el-table-column prop="nickName" label="昵称" />
<el-table-column prop="createTime" label="创建时间" />
<el-table-column label="操作" width="180">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
background
layout="total, sizes, prev, pager, next"
:total="total"
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
@change="getList"
/>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { getUserPage } from '@/api/user'; // 封装的API方法
const tableData = ref([]);
const total = ref(0);
const loading = ref(false);
const queryParams = ref({
pageNum: 1,
pageSize: 10,
username: ''
});
const getList = async () => {
loading.value = true;
try {
const res = await getUserPage(queryParams.value);
tableData.value = res.data.records; // MP 默认返回 records 字段
total.value = res.data.total;
} finally {
loading.value = false;
}
};
onMounted(() => {
getList();
});
</script>
🔐 四、 前后端联调核心:登录与权限(JWT)
这是面试必问点,也是最容易出Bug的地方。
-
后端:登录接口验证密码后,使用
JJWT生成 Token 返回给前端。 -
前端:收到 Token 后存入
localStorage。 -
路由守卫:在 Vue Router 的
beforeEach中检查是否有 Token,无则跳转登录页。 -
请求头:如上所述,Axios 拦截器自动将 Token 塞进
Authorization头。 -
后端校验:使用 Spring Security 或自定义拦截器解析 Token,获取用户ID并存入 ThreadLocal,方便后续业务逻辑使用(如获取当前操作用户ID)。
🚀 五、 2026 进阶优化点(面试加分项)
在文章末尾,你可以抛出这几个点,瞬间拉高逼格:
-
MyBatis-Plus 逻辑删除:在实体类字段上加
@TableLogic,删除操作自动转为 Update,防止误删数据。 -
防抖搜索:在前端搜索框输入时,使用
lodash.debounce限制请求频率,减轻服务器压力。 -
后端字段脱敏:使用 MyBatis-Plus 的 TypeHandler 或 Jackson 注解,在返回给前端时自动将手机号、邮箱中间打星号(****)。
-
跨域配置:Spring Boot 使用
@CrossOrigin或全局配置WebMvcConfigurer解决跨域问题,而不是让前端瞎折腾代理。
💡 总结:
这套架构是目前中小企业甚至大厂中台最常用的组合。核心在于后端提供标准 Restful API,前端负责渲染与交互。掌握了这套代码,你不仅能应对面试,直接拿到公司去改改就能上线。
如果这篇实战文档帮你理清了思路,欢迎在 CSDN 点赞、收藏、关注!我是[代码不加糖],我们下期见!
更多推荐

所有评论(0)