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

文章目录


一、系统架构概述

本文将详细解析一个基于Vue3 + Element Plus前端框架和Spring Boot后端框架的课程管理系统中前后端数据交互的全过程。该系统实现了课程信息的增删改查(CRUD)和导出功能,采用了RESTful API设计风格,是典型的前后端分离架构实现。

二、前端数据交互流程分析

1. 组件初始化与数据请求

<script setup>语法糖中,系统通过onMounted生命周期钩子(虽未显式写出,但getList()在最后执行)自动加载课程列表数据:

javascript 复制代码
// 组件挂载后自动执行
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请求:

javascript 复制代码
// 查询课程列表
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响应式对象管理查询条件:

javascript 复制代码
const queryParams = ref({
  pageNum: 1,
  pageSize: 10,
  code: null,
  subject: null,
  name: null,
  applicablePerson: null
})

当用户点击搜索按钮时,触发handleQuery方法:

javascript 复制代码
function handleQuery() {
  queryParams.value.pageNum = 1 // 重置为第一页
  getList() // 重新获取数据
}

三、后端数据处理流程

1. 控制器接收请求

Spring Boot控制器通过@RestController注解暴露API接口:

java 复制代码
@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):

java 复制代码
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. 服务层业务处理

服务接口与实现:

java 复制代码
public interface ICourseService {
    List<Course> selectCourseList(Course course);
}

@Service
public 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的表单验证规则:

javascript 复制代码
const rules = {
  code: [{ required: true, message: "课程编码不能为空", trigger: "blur" }],
  subject: [{ required: true, message: "课程学科不能为空", trigger: "change" }]
  // 其他字段验证...
}

2. 后端权限控制

通过Spring Security注解实现方法级权限控制:

java 复制代码
@PreAuthorize("@ss.hasPermi('course:course:add')")
@PostMapping
public AjaxResult add(@RequestBody Course course) {
    return toAjax(courseService.insertCourse(course));
}

3. 数据导出实现

导出功能通过Excel工具类实现:

java 复制代码
@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, "课程管理数据");
}

前端调用:

javascript 复制代码
function handleExport() {
  proxy.download('course/course/export', {...queryParams.value}, `course_${new Date().getTime()}.xlsx`)
}

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

  1. 前端操作

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

    http 复制代码
    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插入数据

    • 返回操作结果:

      json 复制代码
      {
        "code": 200,
        "msg": "操作成功"
      }
  4. 前端响应

    • 显示成功消息
    • 关闭对话框
    • 调用getList()刷新列表

七、完整代码

前端页面

c 复制代码
<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接口

c 复制代码
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'
  })
}

后端控制器

c 复制代码
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应用的开发具有重要意义。

相关推荐
GEM的左耳返4 分钟前
Java面试全攻略:Spring生态与微服务架构实战
spring boot·redis·spring cloud·微服务·kafka·java面试
愿你天黑有灯下雨有伞5 分钟前
Spring Boot SSE实战:SseEmitter实现多客户端事件广播与心跳保活
java·spring boot·spring
德育处主任16 分钟前
p5.js 正方形square的基础用法
前端·数据可视化·canvas
烛阴17 分钟前
Mix - Bilinear Interpolation
前端·webgl
90后的晨仔22 分钟前
Vue 3 应用实例详解:从 createApp 到 mount,你真正掌握了吗?
前端·vue.js
德育处主任28 分钟前
p5.js 矩形rect绘制教程
前端·数据可视化·canvas
前端工作日常1 小时前
我学习到的babel插件移除Flow 类型注解效果
前端·babel·前端工程化
前端工作日常1 小时前
我学习到的 Babel 配置
前端·babel·前端工程化
xw52 小时前
uni-app项目跑APP报useStore报错
前端·uni-app
!win !2 小时前
uni-app项目跑APP报useStore报错
前端·uni-app