文章目录
-
- 一、系统架构概述
- 二、前端数据交互流程分析
-
- [1. 组件初始化与数据请求](#1. 组件初始化与数据请求)
- [2. API请求封装](#2. API请求封装)
- [3. 查询参数处理](#3. 查询参数处理)
- 三、后端数据处理流程
-
- [1. 控制器接收请求](#1. 控制器接收请求)
- [2. 分页处理机制](#2. 分页处理机制)
- [3. 服务层业务处理](#3. 服务层业务处理)
- 四、典型操作的数据流
-
- [1. 查询操作数据流](#1. 查询操作数据流)
- [2. 新增操作数据流](#2. 新增操作数据流)
- [3. 删除操作数据流](#3. 删除操作数据流)
- 五、关键技术点解析
-
- [1. 前端表单验证](#1. 前端表单验证)
- [2. 后端权限控制](#2. 后端权限控制)
- [3. 数据导出实现](#3. 数据导出实现)
- 六、完整交互示例:新增课程
- 七、完整代码
- 八、总结与优化建议
-
- [1. 当前实现特点](#1. 当前实现特点)
- [2. 可优化方向](#2. 可优化方向)
一、系统架构概述
本文将详细解析一个基于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`)
}
六、完整交互示例:新增课程
-
前端操作:
- 用户填写表单并点击"确定"按钮
- 触发
submitForm
方法 - 表单验证通过后调用
addCourse
API
-
网络请求:
httpPOST /course/course HTTP/1.1 Content-Type: application/json { "code": "CS101", "subject": "1", "name": "计算机科学导论", "price": 99.9, "applicablePerson": "计算机专业新生", "info": "计算机科学入门课程" }
-
后端处理:
-
Controller接收请求并调用Service
-
Service调用Mapper插入数据
-
返回操作结果:
json{ "code": 200, "msg": "操作成功" }
-
-
前端响应:
- 显示成功消息
- 关闭对话框
- 调用
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. 当前实现特点
- 完整的前后端分离架构
- 标准的RESTful API设计
- 完善的权限控制体系
- 统一的数据封装格式
- 前后端参数校验机制
2. 可优化方向
-
前端优化:
- 添加加载状态管理
- 实现更细粒度的错误提示
- 添加操作防抖/节流
-
后端优化:
- 增加数据缓存机制
- 实现更复杂的查询条件
- 添加操作日志记录
-
交互优化:
- 实现批量操作的结果分项显示
- 添加数据变更的实时提示
- 优化大数据量的分页加载
这种前后端分离的开发模式,通过明确的接口约定和职责划分,使得前后端可以并行开发,提高了开发效率,也便于系统的维护和扩展。理解这种数据交互机制,对于现代Web应用的开发具有重要意义。