若依项目源码阅读
源码阅读
前端代码分析
代码生成器生成的前端代码有两个,分别是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端…...
【RabbitMQ 消息列队测试之:调试技巧】
RabbitMQ 消息列队测试之:调试技巧 1. 使用 RabbitMQ 管理界面2. 启用日志记录3. 使用 `rabbitmqctl` 命令行工具4. 检查和分析死信队列(DLQ)5. 监控系统资源6. 性能测试工具:`rabbitmq-perf-test`7. 使用工具调试消息内容8. 检查和调整消费者处理速率9. 启用长时间运行的测…...
Ubuntu FTP服务器的权限设置
在Ubuntu中设置FTP服务器的权限,主要涉及到用户权限管理和文件系统权限设置。以下是详细的步骤和配置方法: 安装FTP服务器软件 首先,确保已经安装了FTP服务器软件。常用的FTP服务器软件包括vsftpd和Pure-FTPd。以下是使用vsftpd作为示例的安…...
@Pattern (用于校验字符串是否符合特定正则表达式)
Pattern 是一个用于校验字符串是否符合特定正则表达式的注解,它在 Java 中常用于验证输入数据的格式。以下是 Pattern 注解的详解和使用方法: 含义 Pattern 注解用于在 Java 中对字段进行注解,以确保其值与指定的正则表达式匹配。这个注解可…...
5G学习笔记之随机接入
目录 1. 概述 2. MSG1 2.1 选择SSB 2.2 选择Preamble Index 2.3 选择发送Preamble的时频资源 2.4 确定RA-RNTI 2.5 确定发送功率 3. MSG2 4. MSG3 5. MSG4 6. 其它 6.1 切换中的随机接入 6.2 SI请求的随机接入 6.3 通过PDCCH order重新建立同步 1. 概述 随机接入…...
webGL入门教程_03GLSL、OpenGL、WebGL 定义及关系
GLSL、OpenGL、WebGL 定义及关系 1. 定义 1.1 GLSL(OpenGL Shading Language) 定义: GLSL 是 OpenGL 的着色器语言,用于编写 GPU 可编程着色器,定义图形渲染过程中顶点和像素(片元)的处理逻辑。…...
git基本操作说明
一 基本操作说明 Git常用命令: clone、push、add、commit、checkout、pull。 流程如下: 仓库说明: workspace:工作区staging area:暂存区/缓存区local repository:版本库或本地仓库remote repository&…...
微知-git如何添加空目录的几种方式?(.gitkeep, githook, gitconfig)
背景 在Git中,空目录(空文件夹)默认是不会被跟踪的,因为Git主要跟踪文件的变化。但是如何让git添加空目录? #mermaid-svg-3Y4NksLyEeuMs4FC {font-family:"trebuchet ms",verdana,arial,sans-serif;font-si…...
MySQL 数据库学习教程一:开启数据库探索之旅
在当今数字化时代,数据已然成为企业和组织最为宝贵的资产之一。而数据库管理系统则是存储、管理和操作这些数据的核心工具。MySQL 作为一款广泛应用的开源关系型数据库管理系统,以其可靠性、高性能和易用性而备受青睐。如果你渴望踏入数据库领域…...
Vue+Elementui el-tree树只能选择子节点并且支持检索
效果: 只能选择子节点 添加配置添加检索代码 源码: <template><div><el-button size"small" type"primary" clearable :disabled"disabled" click"showSign">危险点评估</el-button>…...
Lumos学习王佩丰Excel第十八讲:LOOKUP函数与数组
一、回顾统计函数 1、使用SUMIF函数 sumif(条件区域,求和条件,求和区域) 2、使用SUMIFS函数 SUMIFS(求和范围, 条件范围1, 条件1, 条件范围2, 条件2, ...) 二、认识数组 1、数组生成原理 所谓数组,是有序的元素序列。组成数组的各个变量称为数组的元素。对于Ex…...
要做网站/游戏推广公司
本文主要说明java的系统里字符串(string)的编码的情况首先一个问题,如何知道某个string(变量的值)的编码是什么?情况复杂,下面分开说明首先要知道系统默认编码(“系统”不是指操作系统,而是本java应用)。影响编码有以下情况&#…...
网站开发 自我评价/品牌推广外包
...
有的网站用流量打不开/百度论坛
深度解析串口、COM口、TTL、RS-232、RS-485的区别及应用 Point: 1、串口、COM口是指的物理接口形式(硬件)。而TTL、RS-232、RS-485是指的电平标准(电信号)。 2、接设备的时候,一般只接GNDRXTX。不会接Vcc或者3.3v的电源线,避免与目标设备上…...
手机高端设计网站建设/每日新闻摘要30条
在Redis中,我们可以将Set类型看作为没有排序的字符集合,和List类型一样,我们也可以在该类型的数据值上执行添加、删除或判断某一元素是否存在等操作。需要说明的是,这些操作的时间复杂度为O(1),即常量时间内完成次操作…...
外包加工网吧/白帽seo
我有一个私人/公钥对,由其他人生成并传给我 . 该对目前用于主动部署 . 两个密钥都存储在扩展名为.der的文件中 . 私钥我可以验证并解析好吧:openssl pkcs8 -inform DER -nocrypt -in private_key.der然而,公共的失败了:解密密钥错…...
wordpress改语言设置/南宁百度网站推广
Kubernetes 笔记 012 Pod 的自动扩容与缩容 原文:Kubernetes 笔记 012 Pod 的自动扩容与缩容本文首发于我的公众号 cloud_dev,专注于干货分享,号内有大量书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码…...