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

若依项目源码阅读

源码阅读

前端代码分析

代码生成器生成的前端代码有两个,分别是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-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-select v-model="queryParams.applicablePerson" placeholder="请选择适用人群" clearable><el-optionv-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-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><!-- 按钮区域-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--><paginationv-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-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-select v-model="form.applicablePerson" placeholder="请选择适用人群"><el-optionv-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或选中其中一个的idconst _id = row.id || ids.value// 以id为条件进行后端的查询,将课程对象封装给response对象getCourse(_id).then(response => {//在表单中进行数据回显form.value = response.data;//打开对话框open.value = true;title.value = "修改课程管理";});
}/** 提交按钮 */
// 对应新增和修改的确定按钮
function submitForm() {
// 对表单进行校验,正则规则、是否必填项等。校验通过valid为trueproxy.$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
{@Autowiredprivate 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)@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));}
}

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 => {// 是否需要设置 tokenconst isToken = (config.headers || {}).isToken === false// 是否需要防止数据重复提交const isRepeatSubmit = (config.headers || {}).repeatSubmit === falseif (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; // 限制存放数据5Mif (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文件中添加以下内容:

在这里插入图片描述

在这里插入图片描述

相关文章:

若依项目源码阅读

源码阅读 前端代码分析 代码生成器生成的前端代码有两个&#xff0c;分别是course.js用于向后端发送ajax请求的接口代码&#xff0c;另一个是index.vue&#xff0c;用于在浏览器展示课程管理的视图组件。前端的代码是基于vue3elementplus。 template用于展示前端组件别的标签…...

Ubuntu20.04运行R-VIO2

目录 1.环境配置2.构建项目3. 运行 VIO 模式4.结果图 1.环境配置 CMakeLists.txt中 C 使用 14、opencv使用4 2.构建项目 克隆代码库&#xff1a; 在终端中执行以下命令克隆项目&#xff1a;git clone https://github.com/rpng/R-VIO2.git编译项目&#xff1a; 使用 catkin_m…...

【Python运维】容器管理新手入门:使用Python的docker-py库实现Docker容器管理与监控

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 随着容器技术的广泛应用,Docker已经成为开发和运维中的标准工具之一。使用Python语言管理Docker容器,不仅可以自动化繁琐的容器操作,还能…...

SQL基础入门——SQL基础语法

1. 数据库、表、列的创建与管理 在SQL中&#xff0c;数据库是一个数据的集合&#xff0c;包含了多个表、视图、索引、存储过程等对象。每个表由若干列&#xff08;字段&#xff09;组成&#xff0c;表中的数据行代表记录。管理数据库和表的结构是SQL的基础操作。 1.1 创建数据…...

Lumos学习王佩丰Excel第十八讲:LOOKUP函数与数组

一、回顾统计函数 1、使用SUMIF函数 sumif(条件区域,求和条件,求和区域) 2、使用SUMIFS函数 SUMIFS(求和范围, 条件范围1, 条件1, 条件范围2, 条件2, ...) 二、认识数组 1、数组生成原理 所谓数组&#xff0c;是有序的元素序列。组成数组的各个变量称为数组的元素。对于Ex…...

第二节——计算机网络(四)物理层

车载以太网采用差分双绞线车载以太网并未指定特定的连接器&#xff0c;连接方式更为灵活小巧&#xff0c;能够大大减轻线束重量。传统以太网一般使用RJ45连接器连接。车载以太网物理层需满足车载环境下更为严格的EMC要求&#xff0c;100BASE-T1\1000BASE-T1对于非屏蔽双绞线的传…...

【接口封装】——11、Qt 的单例模式

宏定义&#xff1a; Q_GLOBAL_STATIC(NotifyManager,theInstance) 函数定义&#xff1a; class NotifyManager : public QObject {Q_OBJECTpublic:NotifyManager(QObject *parent nullptr);~NotifyManager();static NotifyManager*getInstance(); //单例模式 } 源代码&#…...

理解字母形状,从而获得含义

英文字母&#xff0c;都是象形符号&#xff0c;所以&#xff0c;理解其形象&#xff0c;所象之形&#xff0c;是一项重要的工作&#xff0c;和非常有意义事情。也是我们快速记住大量单词&#xff0c;将单词从底层逻辑开始理清&#xff0c;融会贯通扩展记忆容量的重要办法之一。…...

redis揭秘-redis01-redis单例与集群安装总结

文章目录 【README】【1】安装单机【1.1】安装环境【1.2】安装步骤 【2】redis集群主从模式配置【2.1】集群架构【2.2】redis集群主从模式搭建步骤【2.3】redis集群主从模式的问题&#xff08;单点故障问题&#xff09; 【3】redis集群哨兵模式配置【3.1】集群架构【3.2】redis…...

mini-spring源码分析

IOC模块 关键解释 beanFactory&#xff1a;beanFactory是一个hashMap, key为beanName, Value为 beanDefination beanDefination: BeanDefinitionRegistry&#xff0c;BeanDefinition注册表接口&#xff0c;定义注册BeanDefinition的方法 beanReference&#xff1a;增加Bean…...

RVO动态避障技术方案介绍

原文&#xff1a;RVO动态避障技术方案介绍 - 哔哩哔哩 我们在开发游戏的时候经常会遇到这样的问题&#xff0c;当我们寻路的时候&#xff0c;其它人也在寻路&#xff0c;如何避免不从其它人的位置穿过。这个叫做动态避障&#xff0c;目前主流的解决方案就是RVO。本节我们来介绍…...

HTML CSS JS基础考试题与答案

一、选择题&#xff08;2分/题&#xff09; 1&#xff0e;下面标签中&#xff0c;用来显示段落的标签是&#xff08; d &#xff09;。 A、<h1> B、<br /> C、<img /> D、<p> 2. 网页中的图片文件位于html文件的下一级文件夹img中&#xff0c;…...

【C语言】二叉树(BinaryTree)的创建、3种递归遍历、3种非递归遍历、结点度的实现

代码主要实现了以下功能&#xff1a; 二叉树相关数据结构定义 定义了二叉树节点结构体 BiTNode&#xff0c;包含节点数据值&#xff08;字符类型&#xff09;以及指向左右子树的指针。 定义了顺序栈结构体 SqStack&#xff0c;用于存储二叉树节点指针&#xff0c;实现非递归遍历…...

MySQL Workbench 数据库建模详解:从设计到实践

目录 数据库建模基础概念MySQL Workbench 简介与安装 什么是 MySQL Workbench&#xff1f;安装与环境配置 MySQL Workbench 数据库建模功能详解 EER 图&#xff08;实体关系图&#xff09;数据库反向工程数据库正向工程模型同步与版本管理 MySQL Workbench 数据库建模实战教程…...

【字体】Fire Code连字效果开启

Vscode 开启方法 1、设置字体Fire Code 放在最前面的即可&#xff1a; 2、启用连字 继续往下找到“在 settings.json 中编辑”&#xff0c;然后设置"editor.fontLigatures": true &#xff1a; 保存即可。 Sublime 开启方法 设置中设置字体后&#xff0c;启…...

springboot kafka在kafka server AUTH变动后consumer自动销毁

前言 笔者使用了kafka用来传输数据&#xff0c;笔者在今年10月写了文章&#xff0c;怎么使用配置化实现kafka的装载&#xff1a;springboot kafka多数据源&#xff0c;通过配置动态加载发送者和消费者-CSDN博客 不过在实际运行中&#xff0c;kafka broker是加密的&#xff0c…...

第六届国际科技创新(IAECST 2024)暨第四届物流系统与交通运输(LSTT 2024)

重要信息 会议官网&#xff1a;www.lstt.org 大会时间&#xff1a;2024年12月6-8日 大会地点&#xff1a;中国-广州 简介 第六届国际科技创新暨第四届物流系统与交通运输国际&#xff08;LSTT 2024&#xff09;将于2024年12月6-8日在广州举办&#xff0c;这是一个集中探讨…...

【Vue3】【Naive UI】< a >标签

【Vue3】【Naive UI】< a >标签 超链接及相关属性其他属性 【VUE3】【Naive UI】&#xff1c;NCard&#xff1e; 标签 【VUE3】【Naive UI】&#xff1c;n-button&#xff1e; 标签 【VUE3】【Naive UI】&#xff1c;a&#xff1e; 标签 <a> 标签HTML中的一个锚&…...

Fortran mpi在Linux的安装

最近编译一个程序需要需要 Fortran mpi 编译器&#xff0c;则需要安装 Fortran编辑器和MPI库&#xff0c;以下是具体的安装步骤&#xff1a; 一、安装 Fortran 编译器&#xff08;gfortran&#xff09; 在conda环境中安装&#xff1a; conda install -c conda-forge gfortra…...

蓝桥-希尔排序模板题

第一眼看到这个题还在想希尔排序模板不记得了&#xff0c;于是去网上了搜了一个&#xff0c;但是考虑到这种题只看测试点能不能通过&#xff0c;于是用Arrays方法试了一下&#xff0c;发现也可以。 1.希尔排序模板ac代码 package yunkePra;import java.util.Scanner;public cl…...

深入学习指针(5)!!!!!!!!!!!!!!!

文章目录 1.回调函数是什么&#xff1f;2.qsort使用举例2.1使用qsort函数排序整形数据2.2使用sqort排序结构数据 3.qsort函数的模拟实现 1.回调函数是什么&#xff1f; 回调函数就是⼀个通过函数指针调⽤的函数。 如果你把函数的指针&#xff08;地址&#xff09;作为参数传递…...

windows 应用 UI 自动化实战

UI 自动化技术架构选型 UI 自动化是软件测试过程中的重要一环&#xff0c;网络上也有很多 UI 自动化相关的知识或资料&#xff0c;具体到 windows 端的 UI 自动化&#xff0c;我们需要从以下几个方面考虑&#xff1a; 开发语言 毋庸置疑&#xff0c;在 UI 自动化测试领域&am…...

nodejs相关知识介绍

1、nodejs官方文档&#xff1a; https://nodejs.org/zh-cn nodejs可以用nvm进入安装&#xff1b; 2、npm说明&#xff1a; npm官方教程&#xff1a;https://npm.p2hp.com/ npm是 Node.js 的标准包管理器&#xff0c;也就是说nodejs安装好&#xff0c;npm也就安装好了&#…...

How to monitor Spring Boot apps with the AppDynamics Java Agent

本文介绍如何使用 AppDynamics Java 代理监视 Azure Spring Apps 中的 Spring Boot 应用程序。 使用 AppDynamics Java 代理可以&#xff1a; 监视应用程序使用环境变量配置 AppDynamics Java 代理 在 AppDynamics 仪表板中检查所有监视数据 How to monitor Spring Boot app…...

安装SQL Server 2022提示需要Microsoft .NET Framework 4.7.2 或更高版本

安装SQL Server 2022提示需要Microsoft .NET Framework 4.7.2 或更高版本。 原因是&#xff1a;当前操作系统版本为Windows Server 2016 Standard版本&#xff0c;其自带的Microsoft .NET Framework 版本为4.6太低&#xff0c;不满足要求。 根据报错的提示&#xff0c;点击链接…...

TypeScript核心语法(5)——函数

简介​ 函数的类型声明&#xff0c;需要在声明函数时&#xff0c;给出参数的类型和返回值的类型。 function hello(a: string): void {console.log("hello " txt); } 上面示例中&#xff0c;函数hello()在声明时&#xff0c;需要给出参数a的类型&#xff08;stri…...

【MyBatis】验证多级缓存及 Cache Aside 模式的应用

文章目录 前言1. 多级缓存的概念1.1 CPU 多级缓存1.2 MyBatis 多级缓存 2. MyBatis 本地缓存3. MyBatis 全局缓存3.1 MyBatis 全局缓存过期算法3.2 CacheAside 模式 后记MyBatis 提供了缓存切口&#xff0c; 采用 Redis 会引入什么问题&#xff1f;万一遇到需强一致场景&#x…...

ARIMA-神经网络混合模型在时间序列预测中的应用

ARIMA-神经网络混合模型在时间序列预测中的应用 1. 引言 1.1 研究背景与意义 时间序列预测在现代数据科学中扮演着越来越重要的角色。从金融市场的价格走势到工业生产的需求预测,从气象数据的天气预报到用电量的负荷预测,时间序列分析无处不在。传统的统计方法和现代深度学习…...

Scala关于成绩的常规操作

score.txt中的数据&#xff1a; 姓名&#xff0c;语文&#xff0c;数学&#xff0c;英语 张伟&#xff0c;87&#xff0c;92&#xff0c;88 李娜&#xff0c;90&#xff0c;85&#xff0c;95 王强&#xff0c;78&#xff0c;90&#xff0c;82 赵敏&#xff0c;92&#xff0c;8…...

【Maven】项目创建

3. Maven的应用 本章主要内容&#xff1a; 使用 Maven 创建 JavaSE 项目使用 Maven 创建 JavaWeb 项目&#xff0c;在本地部署 Tomcat 测试导入 Maven 项目 3.1 基于Maven开发JavaSE的项目 3.1.1 流程 1、File—>new—>Project—>Empty Project Location&#xff1…...

基于 LlamaFactory 的 LoRA 微调模型支持 vllm 批量推理的实现

背景 LlamaFactory 的 LoRA 微调功能非常便捷&#xff0c;微调后的模型&#xff0c;没有直接支持 vllm 推理&#xff0c;故导致推理速度不够快。 LlamaFactory 目前支持通过 VLLM API 进行部署&#xff0c;调用 API 时的响应速度&#xff0c;仍然没有vllm批量推理的速度快。 …...

Vue进阶之单组件开发与组件通信

书接上篇&#xff0c;我们了解了如何快速创建一个脚手架&#xff0c;现在我们来学习如何基于vite创建属于自己的脚手架。在创建一个新的组件时&#xff0c;要在新建文件夹中打开终端创建一个基本的脚手架&#xff0c;可在脚手架中原有的文件中修改或在相应路径重新创建&#xf…...

HCIE IGP双栈综合实验

实验拓扑 实验需求及解法 本实验模拟ISP网络结构&#xff0c;R1/2组成国家骨干网&#xff0c;R3/4组成省级网络&#xff0c;R5/6/7组成数据中 心网络。 配置所有ipv4地址&#xff0c;请自行测试直连。 R1 sysname R1 interface GigabitEthernet0/0/0ip address 12.1.1.1 255.…...

Unity 超链接文本类

注&#xff1a;该脚本在文本显示不全时会有问题。 HyperlinkText.cs using System; using System.Text; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems;namespace MYT…...

Vim小白学习指南

博客 Vim编辑器简介 Vim是一个非常高效的文本编辑器&#xff0c;最初源于Vi编辑器。它以其强大的文本编辑能力和快捷键而闻名于程序员和系统管理员。Vim的特别之处在于它提供了多种模式&#xff0c;每种模式都有不同的功能。 Vim的基本模式 1. 普通模式&#xff08;Normal …...

【微服务】Nacos配置管理

一、统一配置管理 1、配置统一管理 2、微服务获取配置 ①引入Nacos的配置管理客户端依赖(usersevice下) <!--nacos的配置管理依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-confi…...

从单机缓存到分布式缓存那些事

作者&#xff1a;秦怀 1 缓存前世今生 1.1 故事从硬件开始 Cache 一词来源于 1967 年的一篇电子工程期刊论文。其作者将法语词“cache”赋予“safekeeping storage”的涵义&#xff0c;用于电脑工程领域。当时没有 Cache&#xff0c;CPU 和内存都很慢&#xff0c;CPU 直接访…...

华为新手机和支付宝碰一下 带来更便捷支付体验

支付正在变的更简单。 11月26日&#xff0c;华为新品发布会引起众多关注。发布会上&#xff0c;华为常务董事余承东专门提到&#xff0c;华为Mate 70和Mate X6折叠屏手机的“独门支付秘技”——“碰一下”&#xff0c;并且表示经过华为和支付宝的共同优化&#xff0c;使用“碰…...

element ui select绑定的值是对象的属性时,显示异常.

需要声明 value-key"value",如果还不行可能是数据类型不一致数字0和字符串0是不一致的. el-select v-model"value" clearable placeholder"Select" value-key"value" style"width: 240px"><!-- <el-option v-for&…...

基于Springboot开发的时光兼职网

一、功能介绍 时光兼职网包含管理员、用户、商家三个角色以及前后台系统。 前台系统功能 首页、兼职信息推荐、查看更多等 职位申请、申请日期、上传简历、点击下载简历、留言反馈等 个人中心、上传图片、更新信息等 后台系统功能 用户登录&#xff1a; 个人中心、修改密码…...

Vue3 Ts 如何获取组件的类型

vue3 Ts ref 子组件 1、默认写法 typeof&#xff1a;获取ts类型 InstanceType&#xff1a;获取模版的实例 <tempolate><myComponent ref"myCompRef"> </tempolate><script setup lang"ts"> import { ref } from "vue&quo…...

Unity类银河战士恶魔城学习总结(P146 Delete Save file-P147 Encryption of save data删除数据和加密数据)

【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili 教程源地址&#xff1a;https://www.udemy.com/course/2d-rpg-alexdev/ 本章节实现了快速删除存档和加密存档 以下是加密前和加密后的对比 SaveManager.cs using System.Collections; using System.Collection…...

Uniapp 使用自定义字体

技术栈&#xff1a;Uniapp 简介 为了更好的还原UI图片效果&#xff0c;往往需要使用特殊字体&#xff0c;引入字体包。 因实际业务运行平台在微信小程序上&#xff0c;对发布包的项目文件大小有限制&#xff0c;项目中某些比较大的静态资源需要放在服务器上来远程加载&#x…...

Scala

统计成绩练习 1.计算每个同学的总分和平均分 2.统计每个科目的平均分 3.列出总分前三名和单科前三名&#xff0c;并保存结果到文件中 解题思路如下&#xff1a; 1.读入txt文件&#xff0c;按行读入 2.处理数据 &#xff08;1&#xff09;计算每个同学的总分…...

fnOS中安装HAOS,集成haier

只作为自己记录重要事项&#xff0c;不做详细教程。大致流程 安装飞牛OS&#xff0c;简称fnosfnos中有集成Docker在docker中安装haos在haos中安装hacs在hacs中添加haier 在docker中安装haos 安装好fnos后&#xff0c;docker里面找到haos&#xff0c;里面下载最多的&#xff0c…...

基于群晖搭建个人图书架-TaleBook based on Docker

前言 在群晖Container Manager中部署失败&#xff0c;转通过ssh部署。 一、准备工作 名称备注群晖SSH“终端机和SNMP”中启用SSH软件secureCRT等docker-compose.ymlGithub下载并修改 二、过程 2.1 创建本地文件夹 本地路径为&#xff1a; /docker/Calibre/data 2.2 下载d…...

spring导出多个文件,要求打包成压缩包

背景 业务要求我们批量生成一批excel&#xff0c;并将这些excel压缩成一个压缩包导出给前端。 实现 java自带了ZipOutputStream&#xff0c;可以直接生成压缩包&#xff0c;因此&#xff0c;我们直接使用这个&#xff0c;在内存中生成压缩包&#xff0c;直接返回给前端。&am…...

Vue 3中实现多个自定义组件之间的切换

在 Vue 3 中&#xff0c;如果你想在 HTML 页面中实现多个自定义组件之间的切换&#xff0c;你可以使用 Vue 的条件渲染功能&#xff0c;比如 v-if、v-else-if 和 v-else 指令&#xff0c;或者使用 <component> 标签结合 is 属性来动态绑定组件。 1. 打开HBuilder X 图1 …...

opengl 三角形

最后效果&#xff1a; OpenGL version: 4.1 Metal 不知道为啥必须使用VAO 才行。 #include <glad/glad.h> #include <GLFW/glfw3.h>#include <iostream> #include <vector>void framebuffer_size_callback(GLFWwindow *window, int width, int heigh…...

shell脚本练习(2)

1. 使用case实现成绩优良差的判断 2. for创建20用户 用户前缀由用户输入 用户初始密码由用户输入 例如&#xff1a;test01,test10 3. for ping测试指网段的主机 网段由用户输入&#xff0c;例如用户输入192.168.2 &#xff0c;则ping 192.168.2.10 --- 192.168.2.2…...