源码阅读
前端代码分析
代码生成器生成的前端代码有两个,分别是course.js用于向后端发送ajax请求的接口代码,另一个是index.vue,用于在浏览器展示课程管理的视图组件。前端的代码是基于vue3+elementplus。
template用于展示前端组件别的标签,script用于编写组件的逻辑代码。
搜索表单:
课程编码:
搜索表单区域用到了elementplus的el表单,:model是用于双向绑定,将前端录入的条件封装给详细的对象queryParams。v-show是控制搜索栏显示还是隐藏。
第一个表单项为课程编码,里面包含一个input文本框,v-model是queryParams.code编码的双向绑定,用户输入的值会绑定到code中。第二个placeholder是灰色文本占位符的提示信息,clearable是用于清理用户在文本框中输入的内容,恢复到占位符提示信息。最后一个keyup.enter是键盘的回车事件,用户点击键盘的回车就会调用handleQuery方法完成搜索,等同于搜索按钮。
课程学科:
第二个表单项为课程学科,这里面涉及到下拉框和数据字典。下拉框用到select标签,v-model用于双向绑定,placeholder用于灰色显示提示信息,clearable用于清理文本框的内容恢复提示信息。el-option标签是下拉选项,v-for用于遍历学科的数据字典列表,展示的信息是label属性,用户选择完提交的就是value属性,一个是字典标签一个是字典值。
课程名称和适用人群也属于文本框,可以参考课程编码。
搜索和重置按钮:
按钮区域:
新增按钮:
type、plain和icon是控制按钮的颜色样式和图标的。@click是点击事件会调用相应的方法。v-hasPermi是一个自定义的属性,会结合RBAC权限控制模型来完成菜单按钮的显示和隐藏。
修改按钮:
修改按钮会比新增按钮多一个属性disable,这个属性是控制该按钮是否可用的,会结合下面的js代码,选择对应的对话框,这个按钮就会可用,否则不可用,来做到动态的控制。
删除和导出按钮可以参考新增和修改。
自定义组件按钮:
right-toolbar,该组件控制两个按钮,点击第一个按钮会触发showSearch属性的修改,可以控制搜索栏是否显示或者隐藏。点击第二个按钮会触发queryTable事件,会调用getList事件重新去加载表格展示的数据。
数据展示表格:
使用的是el-table标签,第一个属性v-loading是一个指令用于控制表格的加载状态,如果后台网络非常慢没有返回,前端就会显示一个表格的加载状态来给用户一个友好的提示。可以点击浏览器的开发者工具,找到网络属性,将网速调慢。data是绑定属性用于指定表格的数据源,比如返沪的课程列表,所有的数据都会封装到courseList中,表格就会遍历展示每一条数据。selection-change是事件监听器用于监听选中行的变化,比如选中第一行复选框,事件就会触发调用handleSelectionChange方法来处理后续的业务逻辑。
复选框:
第一列展示的并不是数据的本身,通过type的属性设置为selection来指定为复选框,用户勾中其中一个复选框,就会触发事件监听调用handleSelectionChange方法来处理后续的业务逻辑,第二个属性指定列的宽度55像素,align用于指定对其方式为居中。
从第二列开始就是展示具体的数据源内容,通过label属性来指定列的标题,prop展示该列具体展示的内容。
课程学科:
数据库存放的是数据字典的值,但是页面展示的字典标签,使用模板插槽做到的,通过scope获取整个表格的数据,取出当前行拿到字典值value,将字典值交给dict-tag组件,该组件就拿字典值匹配字典数据列表,找到该字典值对应的字典标签,将其显示到前端页面。
操作:
也使用了模板插槽,修改按钮的前三个属性是用来控制样式和图标的,click是绑定点击事件,会将当前行的数据传给对应的方法执行相应的业务逻辑。c-hasPermi是自定义权限相关的属性。
分页栏:
分页栏用的是pagination标签,v-show是做判断的,如果返回的数据大于0条,分页栏就会显示,反之就会隐藏。:total展示总条数,是从后端查询返回。page会展示分页页码,并对当前页进行高亮。limit限制一页显示的条数,默认是10条。@pagination是分页事件,当用户进行分页操作时,也就是一页10条改为一页20条,或者从第一页跳转到第二页,就会调用getList方法完成新数据的查询。
添加或修改课程对话框:
对话框使用的是el-dialog标签,该标签默认是隐藏的,当用户点击新增按钮,通过双向绑定v-model将对话框的值open改为true,对话框就能弹出了,默认将对话框元素添加到body元素上,就可以在页面进行展示。对话框的标题不是写死的,而是通过属性动态绑定的,因为新增和修改用的是同一个对话框。对话框内部提供了表单元素,通过model进行双向绑定,用户输入的数据课程编码、学科等都会封装给表单对象form。rules是表单的规则,比如前面都加*,用户没填会进行校验。
代码:
course.js:
// 引入request请求工具类,工具类内部封装了axios基础代码,
// 包括请求的拦截器和响应的拦截器,每隔方法只需要调用这个工具类即可。
import request from '@/utils/request'
// 查询课程管理列表
// 用户输入的参数,封装给query对象。
export function listCourse(query) {
// 调用工具类将参数传过去,项后台发送请求,完成数据列表查询,并返回给前端展示。
return request({
url: '/course/course/list',
method: 'get',
params: query
})
}
// 查询课程管理详细
// 当用户点击修改按钮时,需要根据id去查询
export function getCourse(id) {
return request({
url: '/course/course/' + id,
method: 'get'
})
}
// 新增课程管理
// 当用户点击新增按钮时,弹筐里输入每个课程的信息,将数据封装给data对象。
export function addCourse(data) {
return request({
url: '/course/course',
method: 'post',
data: data
})
}
// 修改课程管理
// 当用户修改之后,会将修改后的值传给data,与新增相比会多一个i,以id为条件去更新数据库
export function updateCourse(data) {
return request({
url: '/course/course',
method: 'put',
data: data
})
}
// 删除课程管理
// 支持接收一个或多个选中的课程id
export function delCourse(id) {
return request({
url: '/course/course/' + id,
method: 'delete'
})
}
index.vue:
<template>
<div class="app-container">
<!-- 搜索表单-start -->
<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-select v-model="queryParams.applicablePerson" placeholder="请选择适用人群" clearable>
<el-option
v-for="dict in range"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</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>
<!-- 搜索表单-end -->
<!-- 按钮区域-start-->
<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>
<!-- 按钮区域-end-->
<!-- 数据展示表格区域-start -->
<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">
<template #default="scope">
<dict-tag :options="range" :value="scope.row.applicablePerson"/>
</template>
</el-table-column>
<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>
<!-- 数据展示表格区域-end -->
<!-- 分页区域-start-->
<pagination
v-show="total>0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 分页区域-end-->
<!-- 添加或修改课程管理对话框 -->
<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-select v-model="form.applicablePerson" placeholder="请选择适用人群">
<el-option
v-for="dict in range"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</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">
// 引入后端api接口
import { listCourse, getCourse, delCourse, addCourse, updateCourse } from "@/api/course/course";
// 获取当前实例的代理对象,用于访问组件数据、方法。proxy就是实例的代理对象。
const { proxy } = getCurrentInstance();
// 获取课程学科的数据字典,调用查询课程学科的数据字典,将列表封装给course_subject对象
const { range, course_subject } = proxy.useDict('range', 'course_subject');
// 通过ref定义简单类型的响应式数据
// 列表数据,接收后端返回的列表
const courseList = ref([]);
// 是否显示弹框,默认是隐藏的
const open = ref(false);
// 是否显示加载状态
const loading = ref(true);
// 是否显示搜索栏
const showSearch = ref(true);
// 复选框,被选中id的数组
const ids = ref([]);
// 复选框,是否单选,用于高亮修改、删除按钮
const single = ref(true);
// 复选框,是否多选,仅高亮删除按钮
const multiple = ref(true);
// 总记录数
const total = ref(0);
// 用于区分新增、修改对话框标题
const title = ref("");
// 定义reactive响应式对象
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: "change" }
],
info: [
{ required: true, message: "课程介绍不能为空", trigger: "blur" }
],
}
});
// 为了方便上面三个属性form、queryParams和 rules的操作,把reactive对象转为ref响应式对象,单独操作这三个部分。
const { queryParams, form, rules } = toRefs(data);
// 展示每个方法的作用
/** 查询课程管理列表 */
function getList() {
// 显示表格加载状态
loading.value = true;
// 调用api接口的listCourse,将前端录入的参数作为条件传过去,向后台发送请求进行查询
// 后台返回的结果会封装到response对象,包含课程列表和总记录数,
listCourse(queryParams.value).then(response => {
courseList.value = response.rows;
total.value = response.total;
loading.value = false;
});
}
// 取消按钮
function cancel() {
open.value = false;
// 调用下面的reset方法
// 表单重置会将双向绑定的内容清空,再把表单显示的内容重置掉。
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方法向后台发送请求。
getList();
}
/** 重置按钮操作 */
// 将搜索框的全部内容清空,再调用handleQuery完成无条件搜索。
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
}
// 多选框选中数据
// 用户点击复选框的勾选会触发事件执行该方法,将选中的复选框对象传递过来,拿到复选框对象select
function handleSelectionChange(selection) {
// 调用对象的map方法遍历取每个复选框的id,封装给ids的响应式数组对象
ids.value = selection.map(item => item.id);
// 检查selection数组的长度是否不等于1。如果不等于1,说明选中的复选框数量不是单个,那么single.value就会被设置为true,表示当前没有选中单个复选框。如果selection的长度等于1,那么single.value就会被设置为false,表示当前选中了单个复选框。
single.value = selection.length != 1;
// 检查selection数组是否为空。如果数组为空,即没有复选框被选中,那么multiple.value就会被设置为true,表示当前没有选中任何复选框。如果数组不为空,即至少有一个复选框被选中,那么multiple.value就会被设置为false,表示当前选中了至少一个复选框。
multiple.value = !selection.length;
}
/** 新增按钮操作 */
function handleAdd() {
// 清空原有表单数据
reset();
// 打开对话框
open.value = true;
title.value = "添加课程管理";
}
/** 修改按钮操作 */
// 接收当前的行对象
function handleUpdate(row) {
// 重置表单
reset();
// 取出当前行的id或选中其中一个的id
const _id = row.id || ids.value
// 以id为条件进行后端的查询,将课程对象封装给response对象
getCourse(_id).then(response => {
//在表单中进行数据回显
form.value = response.data;
//打开对话框
open.value = true;
title.value = "修改课程管理";
});
}
/** 提交按钮 */
// 对应新增和修改的确定按钮
function submitForm() {
// 对表单进行校验,正则规则、是否必填项等。校验通过valid为true
proxy.$refs["courseRef"].validate(valid => {
if (valid) {
if (form.value.id != null) {
// 如果表单对象中包含id属性,代表修改操作,调用api接口的修改方法。
updateCourse(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
// 关闭对话框
open.value = false;
// 再执行一次查询操作,展示最新的内容。
getList();
});
} else {
// 如果表单对象中包含id属性,代表新增操作,调用api接口的修改方法。
addCourse(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
});
}
}
});
}
/** 删除按钮操作 */
// 支持单个和批量删除,
function handleDelete(row) {
// row对象可能是一行中取出一个id也可能是数组
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`)
}
// 页面加载时执行-查询课程管理列表
getList();
</script>
后端源代码分析
后端代码结构:
CourseController:
Controller主要是接收前端的请求,调用Service处理业务逻辑并返回结果。
ruoyi-admin模块下找到这个类:com.ruoyi.web.controller.course.CourseController里面有5个对应的方法接口,这写接口都遵循了Rest风格,get查询、post新增、put修改和delete删除详细代码如下:
package com.sky.course.controller;
import com.sky.common.annotation.Log;
import com.sky.common.core.controller.BaseController;
import com.sky.common.core.domain.AjaxResult;
import com.sky.common.core.page.TableDataInfo;
import com.sky.common.enums.BusinessType;
import com.sky.common.utils.poi.ExcelUtil;
import com.sky.course.domain.Course;
import com.sky.course.service.ICourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 课程管理Controller
*
* @author itheima
*/
@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)
{
//1. 开启分页
startPage();
//2. 查询课程列表
List<Course> list = courseService.selectCourseList(course);
//3. 返回表格分页数据对象
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));
}
}
BaseController:
Controller继承了BaseController,其中BaseController是web层通用数据处理,com.ruoyi.common.core.controller
详细定义如下图:
分页插件实现的原理:
startPage(),开启分页,调用分页工具类。
PageUtils类下的startPage():
TableDataInfo:
分页查询统一返回对象:表格分页数据对象
AjaxResult:
非分页的查询结果,增删改查统一返回对象:操作消息提醒
BaseEntity:
所有实体类默认继承的BaseEntity基类
权限注解:
@PreAuthorize 注解是 Spring Security 框架中用来做权限检查的。
它在运行方法前先验证权限,权限够就放行,不够就拦截。
根据权限表,用户登录后就可以查看自己的权限标识了,在执行该方法时就可以去匹配这个方法所需要的权限这个用户是否拥有。
演示操作:
原来小智用户是可以查看课程管理查询的
登录超级管理员账号,去掉课程管理的权限字符串。
再次登录小智账号,没有权限。
权限控制流程图:
前后端交互流程
查询课程管理列表
接口文档:
视图组件加载完成后调用getList方法:
getList方法会调用listCourse方法,传入一个查询的对象。listCourse方法在course.js文件下
为了简化axios请求的发送,直接调用request工具类,给工具类传递请求路径、参数和方式。这就是按照api接口文档进行指定的。
// 引入request请求工具类,工具类内部封装了axios基础代码,
// 包括请求的拦截器和响应的拦截器,每隔方法只需要调用这个工具类即可。
import request from '@/utils/request'
// 查询课程管理列表
// 用户输入的参数,封装给query对象。
export function listCourse(query) {
// 调用工具类将参数传过去,项后台发送请求,完成数据列表查询,并返回给前端展示。
return request({
url: '/course/course/list',
method: 'get',
params: query
})
}
找到request.js文件,代码非常多,只介绍部分重要的。
首先通过axios创建实例对象,创建的时候有两个参数,baseURL作用是Ajax在发送请求之前为地址会拼接一个前缀,这个前缀并没有写死,而是读取当下的环境文件。
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 10000
})
项目中的三个环境文件如下开发环境文件、生产环境文件和测试环境文件。启动命令npm run dev中的dev就是开发环境文件,将来的开发环境测试环境等可能会改变,所有没有写死。
创建好实例之后还为请求和响应增加了拦截器:
// request拦截器
service.interceptors.request.use(config => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
// 防止表单重复提交
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
}
const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
if (requestSize >= limitSize) {
console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。')
return config;
}
const sessionObj = cache.session.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj)
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ` + message)
return Promise.reject(new Error(message))
} else {
cache.session.setJSON('sessionObj', requestObj)
}
}
}
return config
}, error => {
console.log(error)
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
}
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true;
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
isRelogin.show = false;
useUserStore().logOut().then(() => {
location.href = '/index';
})
}).catch(() => {
isRelogin.show = false;
});
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) {
ElMessage({ message: msg, type: 'error' })
return Promise.reject(new Error(msg))
} else if (code === 601) {
ElMessage({ message: msg, type: 'warning' })
return Promise.reject(new Error(msg))
} else if (code !== 200) {
ElNotification.error({ title: msg })
return Promise.reject('error')
} else {
return Promise.resolve(res.data)
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error)
}
)
前端工程通过Axios工具向后端发送请求,发送请求之前会通过拦截器对请求进行增强,将增强后的内容交给后端进行业务结果的处理,后端的响应结果返回给前端之前,响应拦截器也能对响应的数据进行增强。
跨域:
在前端开发中,跨域是一个常见的问题,特别是在使用Vue框架进行开发时。跨域是指在浏览器中发送的AJAX请求的目标地址与当前页面的地址不在同一个域下,这会导致浏览器的同源策略产生限制,从而阻止了跨域请求的发送。然而,我们可以通过代理服务器来解决这个问题。
代理服务器是位于客户端和目标服务器之间的一台服务器,它接收客户端发送的请求,并将请求转发给目标服务器。通过在代理服务器上进行请求转发,可以绕过浏览器的同源策略限制,从而实现跨域请求。
在vue.config.js文件中添加以下内容: