> 技术文档 > 【web应用】基于Vue3和Spring Boot的课程管理前后端数据交互过程

【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. 组件初始化与数据请求

语法糖中,系统通过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`)}

六、完整交互示例:新增课程

  1. 前端操作

    • 用户填写表单并点击\"确定\"按钮
    • 触发submitForm方法
    • 表单验证通过后调用addCourseAPI
  2. 网络请求

    POST /course/course HTTP/1.1Content-Type: application/json{ \"code\": \"CS101\", \"subject\": \"1\", \"name\": \"计算机科学导论\", \"price\": 99.9, \"applicablePerson\": \"计算机专业新生\", \"info\": \"计算机科学入门课程\"}
  3. 后端处理

    • Controller接收请求并调用Service
    • Service调用Mapper插入数据
    • 返回操作结果:
      { \"code\": 200, \"msg\": \"操作成功\"}
  4. 前端响应

    • 显示成功消息
    • 关闭对话框
    • 调用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. 当前实现特点

  1. 完整的前后端分离架构
  2. 标准的RESTful API设计
  3. 完善的权限控制体系
  4. 统一的数据封装格式
  5. 前后端参数校验机制

2. 可优化方向

  1. 前端优化

    • 添加加载状态管理
    • 实现更细粒度的错误提示
    • 添加操作防抖/节流
  2. 后端优化

    • 增加数据缓存机制
    • 实现更复杂的查询条件
    • 添加操作日志记录
  3. 交互优化

    • 实现批量操作的结果分项显示
    • 添加数据变更的实时提示
    • 优化大数据量的分页加载

这种前后端分离的开发模式,通过明确的接口约定和职责划分,使得前后端可以并行开发,提高了开发效率,也便于系统的维护和扩展。理解这种数据交互机制,对于现代Web应用的开发具有重要意义。