若依项目源码阅读
源码阅读
前端代码分析
代码生成器生成的前端代码有两个,分别是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文件中添加以下内容:
相关文章:

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

JVM知识点学习-1
学习视频:狂神说Java 类加载器和双亲委派机制 类加载器 作用:加载Class文件 流程:这里的名字car1。。在栈里面,但是数据在堆里面 类加载器的几个类型: 虚拟机自带的类加载器;启动类(根Boot…...

TypeScript和JavaScript区别详解
文章目录 TypeScript和JavaScript区别详解一、引言二、类型系统1、静态类型检查TypeScript 示例JavaScript 示例 2、类型推断TypeScript 示例JavaScript 示例 三、面向对象编程TypeScript 示例JavaScript 示例 四、使用示例1. 环境搭建2. 创建TypeScript项目3. 安装TypeScript插…...

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

Vue进阶之单组件开发与组件通信
书接上篇,我们了解了如何快速创建一个脚手架,现在我们来学习如何基于vite创建属于自己的脚手架。在创建一个新的组件时,要在新建文件夹中打开终端创建一个基本的脚手架,可在脚手架中原有的文件中修改或在相应路径重新创建…...

OGRE 3D----5. OGRE和QML事件交互
在现代图形应用程序开发中,OGRE(Object-Oriented Graphics Rendering Engine)作为一个高性能的3D渲染引擎,广泛应用于游戏开发、虚拟现实和仿真等领域。而QML(Qt Modeling Language)则是Qt框架中的一种声明式语言,专注于设计用户界面。将OGRE与QML结合,可以充分利用OGR…...
ARIMA-神经网络混合模型在时间序列预测中的应用
ARIMA-神经网络混合模型在时间序列预测中的应用 1. 引言 1.1 研究背景与意义 时间序列预测在现代数据科学中扮演着越来越重要的角色。从金融市场的价格走势到工业生产的需求预测,从气象数据的天气预报到用电量的负荷预测,时间序列分析无处不在。传统的统计方法和现代深度学习…...

常见靶场的搭建
漏洞靶场 渗透测试(漏洞挖掘)切忌纸上谈兵,学习渗透测试(漏洞挖掘)知识的过程中,我们通常需要一个包含漏洞的测试环境来进行训练。而在非授权情况下,对于网站进行渗透测试攻击,是触及…...

[MacOS] [kubernetes] MacOS玩转虚拟化最佳实践
❓ 为什么不在MacOS本机安装呢?因为M系列芯片是Arm架构,与生产环境或者在本地调试时候,安装虚拟镜像和X86不同,造成不必要的切换环境的额外成本,所以在虚拟化的x86调试 步骤 & 详情 一: 安装OrbStack & 并配置…...

HarmonyOS:@Provide装饰器和@Consume装饰器:与后代组件双向同步
一、前言 Provide和Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,Provide和Consume摆脱参数传递机制的束缚,实现跨层级传递。 其中Provi…...

git 上传代码时报错
在上传代码时,显示无法上传 PS E:\JavaWeb\vue3-project> git push To https://gitee.com/evening-breeze-2003/vue3.git! [rejected] master -> master (non-fast-forward) error: failed to push some refs to https://gitee.com/evening-breeze-20…...
判断1456789876541是否为素数,是输出“是素数“,不是则输出“不是素数“
题目描述 判断1456789876541是否为素数,是输出"是素数",不是则输出"不是素数" 代码实现 int main() { long long n 1456789876541; //for (long long i 2; i < n; i)//数据量太大 for(long long i2;i<sqrt(n);i)//素数的优化 { if (n % i 0) …...

Flutter:封装发送验证码组件,注册页使用获取验证码并传递控制器和验证码类型
验证码:view import package:flutter/material.dart; import package:get/get.dart; import index.dart;class SendcodePage extends GetView<SendcodeController> {// 接收注册页面,传进来的手机号控制器,和发送验证码的类型final Tex…...

亚马逊IP关联是什么?
亚马逊不仅提供了广泛的商品和服务,也是许多企业和个人选择的电子商务平台。然而,与亚马逊相关的IP关联问题,特别是在网络安全和运营管理方面,经常成为使用亚马逊服务的用户和商家关注的焦点。通过了解亚马逊IP关联的含义、可能的…...

Electron + vue3 打包之后不能跳转路由
路由不跳转问题原因: 是因为electron需要将vue-router的mode调整为hash模式(两种写法) export default new Router({mode: hash, //这里history修改为hashscrollBehavior: () > ({y: 0}),routes: constantRouterMap, }) export default new createRouter({his…...
docker安装clickhouse副本集群
docker安装clickhouse副本集群 1、clickhouse副本集群搭建1.1、docker安装zookeeper集群1.1.1、zookeeper第一个节点安装1.1.2、zookeeper第二个节点安装1.1.3、zookeeper第三个节点安装1.1.4、zookeeper客户端命令 2、Clickhouse副本集群搭建2.1、clickhouse搭建2.2、验证集群…...

vue超过三行显示省略号和查看更多按钮
1、超过3行显示省略号和更多按钮,不超过3行正常显示; html: <div class"container"><div style"display: flex;"><div class"content"><div class"text-content" ref"textContentR…...
【软考速通笔记】系统架构设计师⑤——软件工程基础知识
文章目录 一、前言二、基础知识点2.1 软件危机2.2 软件生命周期 三、软件过程模型(论文)3.1 瀑布模型3.2 原型模型3.3 螺旋模型3.4 敏捷模型3.5 软件统一过程模型3.6 软件成熟度模型3.7 软件成熟度模型集成 四、需求工程五、软件测试5.1 根据程序执行状态…...

Qt 详解QRubberBand
文章目录 QRubberBand 简介前言 QRubberBand 的作用QRubberBand 的主要功能QRubberBand 的常用方法QRubberBand 的典型应用场景示例代码总结 QRubberBand 简介 前言 在 Qt 中,QRubberBand 是一个非常实用的控件,它通常用于图形界面中的“选择区域”功能…...

HTB:Love[WriteUP]
目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 使用nmap对靶机开放端口进行脚本、服务扫描 使用浏览器访问靶机443端口 尝试利用该功能访问靶机自身80端口 使用ffuf对靶机80端口进行路径FUZZ 漏洞利用 使用searchsploit搜索靶机80端…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...