当前位置: 首页 > java >正文

【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`)
}

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

  1. 前端操作

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

    POST /course/course HTTP/1.1
    Content-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-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. 当前实现特点

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

2. 可优化方向

  1. 前端优化

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

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

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

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

http://www.xdnf.cn/news/16342.html

相关文章:

  • Vue 3 与 Element Plus 中的 /deep/ 选择器问题
  • 论文阅读-RaftStereo
  • haproxy配置详解
  • QT核心————信号槽
  • 外带服务的温度:藏在包装里的“生活共情力”
  • [RPA] 日期时间练习案例
  • 二维数组相关学习
  • FastAPI入门:demo、路径参数、查询参数
  • 【图像理解进阶】如何在自己的数据集上释放segment anything模型方案的潜力?
  • 【GaussDB】构建一个GaussDB的Docker镜像
  • MySQL数据库本地迁移到云端完整教程
  • 20250726-4-Kubernetes 网络-Service DNS名称解析_笔记
  • 虚拟直线阈值告警人员计数算法暑期应用
  • MySQL性能优化配置终极指南
  • 【深基12.例1】部分背包问题 Java
  • 二分查找-268.丢失的数字-力扣(LeetCode)
  • ABP VNext + Razor 邮件模板:动态、多租户隔离、可版本化的邮件与通知系统
  • java面试题1
  • IOPaint 图像修复工具,学习笔记
  • openmv识别数字
  • 质数、因数、最大公约数经典问题整理
  • KNN 算法进阶:从基础到优化的深度解析
  • lesson24:Python的logging模块
  • 将文件移入回收站而不是直接删除
  • 7月25号打卡
  • 太极生两仪,两仪生四象,四象生八卦
  • 13.使用C连接mysql
  • Windows Server 2003 R2系统C盘扩容教程
  • 【深度学习新浪潮】Claude code是什么样的一款产品?
  • 【Linux系统】基础IO(下)