【web应用】基于Vue3和Spring Boot的课程管理前后端数据交互过程
文章目录
- 一、系统架构概述
- 二、前端数据交互流程分析
- 1. 组件初始化与数据请求
- 2. API请求封装
- 3. 查询参数处理
- 三、后端数据处理流程
- 1. 控制器接收请求
- 2. 分页处理机制
- 3. 服务层业务处理
- 四、典型操作的数据流
- 1. 查询操作数据流
- 2. 新增操作数据流
- 3. 删除操作数据流
- 五、关键技术点解析
- 1. 前端表单验证
- 2. 后端权限控制
- 3. 数据导出实现
- 六、完整交互示例:新增课程
- 七、完整代码
- 八、总结与优化建议
- 1. 当前实现特点
- 2. 可优化方向
一、系统架构概述
本文将详细解析一个基于Vue3 + Element Plus前端框架和Spring Boot后端框架的课程管理系统中前后端数据交互的全过程。该系统实现了课程信息的增删改查(CRUD)和导出功能,采用了RESTful API设计风格,是典型的前后端分离架构实现。
二、前端数据交互流程分析
1. 组件初始化与数据请求
在<script setup>
语法糖中,系统通过onMounted
生命周期钩子(虽未显式写出,但getList()
在最后执行)自动加载课程列表数据:
// 组件挂载后自动执行
getList()function getList() {loading.value = truelistCourse(queryParams.value).then(response => {courseList.value = response.rowstotal.value = response.totalloading.value = false})
}
2. API请求封装
前端通过@/api/course/course
模块封装了所有API请求:
// 查询课程列表
export function listCourse(query) {return request({url: '/course/course/list',method: 'get',params: query})
}// 新增课程
export function addCourse(data) {return request({url: '/course/course',method: 'post',data: data})
}
这里使用了axios的封装(@/utils/request
),自动处理了请求拦截、响应拦截和错误处理。
3. 查询参数处理
前端通过queryParams
响应式对象管理查询条件:
const queryParams = ref({pageNum: 1,pageSize: 10,code: null,subject: null,name: null,applicablePerson: null
})
当用户点击搜索按钮时,触发handleQuery
方法:
function handleQuery() {queryParams.value.pageNum = 1 // 重置为第一页getList() // 重新获取数据
}
三、后端数据处理流程
1. 控制器接收请求
Spring Boot控制器通过@RestController
注解暴露API接口:
@RestController
@RequestMapping("/course/course")
public class CourseController extends BaseController {@Autowiredprivate ICourseService courseService;@GetMapping("/list")public TableDataInfo list(Course course) {startPage(); // 启动分页List<Course> list = courseService.selectCourseList(course);return getDataTable(list); // 封装分页结果}
}
2. 分页处理机制
后端使用了MyBatis分页插件(通过继承BaseController
):
protected void startPage() {PageDomain pageDomain = TableSupport.buildPageRequest();Integer pageNum = pageDomain.getPageNum();Integer pageSize = pageDomain.getPageSize();if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize)) {PageHelper.startPage(pageNum, pageSize);}
}
3. 服务层业务处理
服务接口与实现:
public interface ICourseService {List<Course> selectCourseList(Course course);
}@Service
public class CourseServiceImpl implements ICourseService {@Overridepublic List<Course> selectCourseList(Course course) {return courseMapper.selectCourseList(course);}
}
四、典型操作的数据流
1. 查询操作数据流
前端组件 → API封装(listCourse) → Axios请求 →
Spring Controller(list方法) → Service层 → Mapper → 数据库 →
返回数据沿原路返回 → 前端更新courseList显示
2. 新增操作数据流
前端表单提交 → API封装(addCourse) → Axios POST请求 →
Spring Controller(add方法) → Service层 → Mapper → 数据库 →
返回操作结果 → 前端显示成功消息并刷新列表
3. 删除操作数据流
前端选择记录 → 调用handleDelete → API封装(delCourse) → Axios DELETE请求 →
Spring Controller(remove方法) → Service层 → Mapper → 数据库 →
返回操作结果 → 前端显示消息并刷新列表
五、关键技术点解析
1. 前端表单验证
使用Element Plus的表单验证规则:
const rules = {code: [{ required: true, message: "课程编码不能为空", trigger: "blur" }],subject: [{ required: true, message: "课程学科不能为空", trigger: "change" }]// 其他字段验证...
}
2. 后端权限控制
通过Spring Security注解实现方法级权限控制:
@PreAuthorize("@ss.hasPermi('course:course:add')")
@PostMapping
public AjaxResult add(@RequestBody Course course) {return toAjax(courseService.insertCourse(course));
}
3. 数据导出实现
导出功能通过Excel工具类实现:
@PostMapping("/export")
public void export(HttpServletResponse response, Course course) {List<Course> list = courseService.selectCourseList(course);ExcelUtil<Course> util = new ExcelUtil<>(Course.class);util.exportExcel(response, list, "课程管理数据");
}
前端调用:
function handleExport() {proxy.download('course/course/export', {...queryParams.value}, `course_${new Date().getTime()}.xlsx`)
}
六、完整交互示例:新增课程
-
前端操作:
- 用户填写表单并点击"确定"按钮
- 触发
submitForm
方法 - 表单验证通过后调用
addCourse
API
-
网络请求:
POST /course/course HTTP/1.1 Content-Type: application/json{"code": "CS101","subject": "1","name": "计算机科学导论","price": 99.9,"applicablePerson": "计算机专业新生","info": "计算机科学入门课程" }
-
后端处理:
- Controller接收请求并调用Service
- Service调用Mapper插入数据
- 返回操作结果:
{"code": 200,"msg": "操作成功" }
-
前端响应:
- 显示成功消息
- 关闭对话框
- 调用
getList()
刷新列表
七、完整代码
前端页面
<template><div class="app-container"><el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px"><el-form-item label="课程编码" prop="code"><el-inputv-model="queryParams.code"placeholder="请输入课程编码"clearable@keyup.enter="handleQuery"/></el-form-item><el-form-item label="课程学科" prop="subject"><el-select v-model="queryParams.subject" placeholder="请选择课程学科" clearable><el-optionv-for="dict in course_subject":key="dict.value":label="dict.label":value="dict.value"/></el-select></el-form-item><el-form-item label="课程名称" prop="name"><el-inputv-model="queryParams.name"placeholder="请输入课程名称"clearable@keyup.enter="handleQuery"/></el-form-item><el-form-item label="适用人群" prop="applicablePerson"><el-inputv-model="queryParams.applicablePerson"placeholder="请输入适用人群"clearable@keyup.enter="handleQuery"/></el-form-item><el-form-item><el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button><el-button icon="Refresh" @click="resetQuery">重置</el-button></el-form-item></el-form><el-row :gutter="10" class="mb8"><el-col :span="1.5"><el-buttontype="primary"plainicon="Plus"@click="handleAdd"v-hasPermi="['course:course:add']">新增</el-button></el-col><el-col :span="1.5"><el-buttontype="success"plainicon="Edit":disabled="single"@click="handleUpdate"v-hasPermi="['course:course:edit']">修改</el-button></el-col><el-col :span="1.5"><el-buttontype="danger"plainicon="Delete":disabled="multiple"@click="handleDelete"v-hasPermi="['course:course:remove']">删除</el-button></el-col><el-col :span="1.5"><el-buttontype="warning"plainicon="Download"@click="handleExport"v-hasPermi="['course:course:export']">导出</el-button></el-col><right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar></el-row><el-table v-loading="loading" :data="courseList" @selection-change="handleSelectionChange"><el-table-column type="selection" width="55" align="center" /><el-table-column label="课程id" align="center" prop="id" /><el-table-column label="课程编码" align="center" prop="code" /><el-table-column label="课程学科" align="center" prop="subject"><template #default="scope"><dict-tag :options="course_subject" :value="scope.row.subject"/></template></el-table-column><el-table-column label="课程名称" align="center" prop="name" /><el-table-column label="价格" align="center" prop="price" /><el-table-column label="适用人群" align="center" prop="applicablePerson" /><el-table-column label="课程介绍" align="center" prop="info" /><el-table-column label="操作" align="center" class-name="small-padding fixed-width"><template #default="scope"><el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['course:course:edit']">修改</el-button><el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['course:course:remove']">删除</el-button></template></el-table-column></el-table><paginationv-show="total>0":total="total"v-model:page="queryParams.pageNum"v-model:limit="queryParams.pageSize"@pagination="getList"/><!-- 添加或修改课程管理对话框 --><el-dialog :title="title" v-model="open" width="500px" append-to-body><el-form ref="courseRef" :model="form" :rules="rules" label-width="80px"><el-form-item label="课程编码" prop="code"><el-input v-model="form.code" placeholder="请输入课程编码" /></el-form-item><el-form-item label="课程学科" prop="subject"><el-select v-model="form.subject" placeholder="请选择课程学科"><el-optionv-for="dict in course_subject":key="dict.value":label="dict.label":value="dict.value"></el-option></el-select></el-form-item><el-form-item label="课程名称" prop="name"><el-input v-model="form.name" placeholder="请输入课程名称" /></el-form-item><el-form-item label="价格" prop="price"><el-input v-model="form.price" placeholder="请输入价格" /></el-form-item><el-form-item label="适用人群" prop="applicablePerson"><el-input v-model="form.applicablePerson" placeholder="请输入适用人群" /></el-form-item><el-form-item label="课程介绍" prop="info"><el-input v-model="form.info" placeholder="请输入课程介绍" /></el-form-item></el-form><template #footer><div class="dialog-footer"><el-button type="primary" @click="submitForm">确 定</el-button><el-button @click="cancel">取 消</el-button></div></template></el-dialog></div>
</template><script setup name="Course">
import { listCourse, getCourse, delCourse, addCourse, updateCourse } from "@/api/course/course"const { proxy } = getCurrentInstance()
const { course_subject } = proxy.useDict('course_subject')const courseList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref("")const data = reactive({form: {},queryParams: {pageNum: 1,pageSize: 10,code: null,subject: null,name: null,applicablePerson: null,},rules: {code: [{ required: true, message: "课程编码不能为空", trigger: "blur" }],subject: [{ required: true, message: "课程学科不能为空", trigger: "change" }],name: [{ required: true, message: "课程名称不能为空", trigger: "blur" }],price: [{ required: true, message: "价格不能为空", trigger: "blur" }],applicablePerson: [{ required: true, message: "适用人群不能为空", trigger: "blur" }],info: [{ required: true, message: "课程介绍不能为空", trigger: "blur" }],}
})const { queryParams, form, rules } = toRefs(data)/** 查询课程管理列表 */
function getList() {loading.value = truelistCourse(queryParams.value).then(response => {courseList.value = response.rowstotal.value = response.totalloading.value = false})
}// 取消按钮
function cancel() {open.value = falsereset()
}// 表单重置
function reset() {form.value = {id: null,code: null,subject: null,name: null,price: null,applicablePerson: null,info: null,createTime: null,updateTime: null}proxy.resetForm("courseRef")
}/** 搜索按钮操作 */
function handleQuery() {queryParams.value.pageNum = 1getList()
}/** 重置按钮操作 */
function resetQuery() {proxy.resetForm("queryRef")handleQuery()
}// 多选框选中数据
function handleSelectionChange(selection) {ids.value = selection.map(item => item.id)single.value = selection.length != 1multiple.value = !selection.length
}/** 新增按钮操作 */
function handleAdd() {reset()open.value = truetitle.value = "添加课程管理"
}/** 修改按钮操作 */
function handleUpdate(row) {reset()const _id = row.id || ids.valuegetCourse(_id).then(response => {form.value = response.dataopen.value = truetitle.value = "修改课程管理"})
}/** 提交按钮 */
function submitForm() {proxy.$refs["courseRef"].validate(valid => {if (valid) {if (form.value.id != null) {updateCourse(form.value).then(response => {proxy.$modal.msgSuccess("修改成功")open.value = falsegetList()})} else {addCourse(form.value).then(response => {proxy.$modal.msgSuccess("新增成功")open.value = falsegetList()})}}})
}/** 删除按钮操作 */
function handleDelete(row) {const _ids = row.id || ids.valueproxy.$modal.confirm('是否确认删除课程管理编号为"' + _ids + '"的数据项?').then(function() {return delCourse(_ids)}).then(() => {getList()proxy.$modal.msgSuccess("删除成功")}).catch(() => {})
}/** 导出按钮操作 */
// function handleExport()
// {
// proxy.download('course/course/export', {
// ...queryParams.value
// }, `course_${new Date().getTime()}.xlsx`)
// }function handleExport()
{proxy.download('course/course/export', {...queryParams.value}, `course_${new Date().getTime()}.xlsx`)
}getList()
</script>
前端API接口
import request from '@/utils/request'// 查询课程管理列表
export function listCourse(query) {return request({url: '/course/course/list',method: 'get',params: query})
}export function getCourseList(query){return request({url:'',method:'get',params:query})
}// 查询课程管理详细
export function getCourse(id) {return request({url: '/course/course/' + id,method: 'get'})
}// 新增课程管理
export function addCourse(data) {return request({url: '/course/course',method: 'post',data: data})
}// 修改课程管理
export function updateCourse(data) {return request({url: '/course/course',method: 'put',data: data})
}// 删除课程管理
export function delCourse(id) {return request({url: '/course/course/' + id,method: 'delete'})
}
后端控制器
package com.ruoyi.course.controller;import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.course.domain.Course;
import com.ruoyi.course.service.ICourseService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;/*** 课程管理Controller* * @author * @date 2025-05-27*/
@RestController
@RequestMapping("/course/course")
public class CourseController extends BaseController
{@Autowiredprivate ICourseService courseService;/*** 查询课程管理列表*/@PreAuthorize("@ss.hasPermi('course:course:list')")@GetMapping("/list")public TableDataInfo list(Course course){startPage();List<Course> list = courseService.selectCourseList(course);return getDataTable(list);}/*** 导出课程管理列表*/@PreAuthorize("@ss.hasPermi('course:course:export')")@Log(title = "课程管理", businessType = BusinessType.EXPORT)@PostMapping("/export")public void export(HttpServletResponse response, Course course){List<Course> list = courseService.selectCourseList(course);ExcelUtil<Course> util = new ExcelUtil<Course>(Course.class);util.exportExcel(response, list, "课程管理数据");}/*** 获取课程管理详细信息*/@PreAuthorize("@ss.hasPermi('course:course:query')")@GetMapping(value = "/{id}")public AjaxResult getInfo(@PathVariable("id") Long id){return success(courseService.selectCourseById(id));}/*** 新增课程管理*/@PreAuthorize("@ss.hasPermi('course:course:add')")@Log(title = "课程管理", businessType = BusinessType.INSERT)@PostMappingpublic AjaxResult add(@RequestBody Course course){return toAjax(courseService.insertCourse(course));}/*** 修改课程管理*/@PreAuthorize("@ss.hasPermi('course:course:edit')")@Log(title = "课程管理", businessType = BusinessType.UPDATE)@PutMappingpublic AjaxResult edit(@RequestBody Course course){return toAjax(courseService.updateCourse(course));}/*** 删除课程管理*/@PreAuthorize("@ss.hasPermi('course:course:remove')")@Log(title = "课程管理", businessType = BusinessType.DELETE)@DeleteMapping("/{ids}")public AjaxResult remove(@PathVariable Long[] ids){return toAjax(courseService.deleteCourseByIds(ids));}
}
八、总结与优化建议
1. 当前实现特点
- 完整的前后端分离架构
- 标准的RESTful API设计
- 完善的权限控制体系
- 统一的数据封装格式
- 前后端参数校验机制
2. 可优化方向
-
前端优化:
- 添加加载状态管理
- 实现更细粒度的错误提示
- 添加操作防抖/节流
-
后端优化:
- 增加数据缓存机制
- 实现更复杂的查询条件
- 添加操作日志记录
-
交互优化:
- 实现批量操作的结果分项显示
- 添加数据变更的实时提示
- 优化大数据量的分页加载
这种前后端分离的开发模式,通过明确的接口约定和职责划分,使得前后端可以并行开发,提高了开发效率,也便于系统的维护和扩展。理解这种数据交互机制,对于现代Web应用的开发具有重要意义。