【web应用】基于Vue3和Spring Boot的课程管理前后端数据交互过程
文章目录
一、系统架构概述
本文将详细解析一个基于Vue3 + Element Plus前端框架和Spring Boot后端框架的课程管理系统中前后端数据交互的全过程。该系统实现了课程信息的增删改查(CRUD)和导出功能,采用了RESTful API设计风格,是典型的前后端分离架构实现。
二、前端数据交互流程分析
1. 组件初始化与数据请求
在语法糖中,系统通过
onMounted
生命周期钩子(虽未显式写出,但getList()
在最后执行)自动加载课程列表数据:
// 组件挂载后自动执行getList()function getList() { loading.value = true listCourse(queryParams.value).then(response => { courseList.value = response.rows total.value = response.total loading.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 { @Autowired private 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);}@Servicepublic class CourseServiceImpl implements ICourseService { @Override public 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\')\")@PostMappingpublic 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.1Content-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-input v-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-option v-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-input v-model=\"queryParams.name\" placeholder=\"请输入课程名称\" clearable @keyup.enter=\"handleQuery\" /> </el-form-item> <el-form-item label=\"适用人群\" prop=\"applicablePerson\"> <el-input v-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-button type=\"primary\" plain icon=\"Plus\" @click=\"handleAdd\" v-hasPermi=\"[\'course:course:add\']\" >新增</el-button> </el-col> <el-col :span=\"1.5\"> <el-button type=\"success\" plain icon=\"Edit\" :disabled=\"single\" @click=\"handleUpdate\" v-hasPermi=\"[\'course:course:edit\']\" >修改</el-button> </el-col> <el-col :span=\"1.5\"> <el-button type=\"danger\" plain icon=\"Delete\" :disabled=\"multiple\" @click=\"handleDelete\" v-hasPermi=\"[\'course:course:remove\']\" >删除</el-button> </el-col> <el-col :span=\"1.5\"> <el-button type=\"warning\" plain icon=\"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> <pagination v-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-option v-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 = true listCourse(queryParams.value).then(response => { courseList.value = response.rows total.value = response.total loading.value = false })}// 取消按钮function cancel() { open.value = false reset()}// 表单重置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 = 1 getList()}/** 重置按钮操作 */function resetQuery() { proxy.resetForm(\"queryRef\") handleQuery()}// 多选框选中数据function handleSelectionChange(selection) { ids.value = selection.map(item => item.id) single.value = selection.length != 1 multiple.value = !selection.length}/** 新增按钮操作 */function handleAdd() { reset() open.value = true title.value = \"添加课程管理\"}/** 修改按钮操作 */function handleUpdate(row) { reset() const _id = row.id || ids.value getCourse(_id).then(response => { form.value = response.data open.value = true title.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 = false getList() }) } else { addCourse(form.value).then(response => { proxy.$modal.msgSuccess(\"新增成功\") open.value = false getList() }) } } })}/** 删除按钮操作 */function handleDelete(row) { const _ids = row.id || ids.value proxy.$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{ @Autowired private 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) @PostMapping public AjaxResult add(@RequestBody Course course) { return toAjax(courseService.insertCourse(course)); } /** * 修改课程管理 */ @PreAuthorize(\"@ss.hasPermi(\'course:course:edit\')\") @Log(title = \"课程管理\", businessType = BusinessType.UPDATE) @PutMapping public 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应用的开发具有重要意义。