内容输出来源:拉勾教育大前端高薪训练营
一、创建项目
1. 使用@vue/cli构建项目
安装Vue Cli
npm i -g @vue/cli
创建项目
vue create edu-boss-fed
进入项目目录
cd edu-boss-fed
启动开发服务
npm run serve
启动成功,根据提示访问给出的服务地址
App running at:
- Local: http://localhost:8080/
- Network: http://10.10.100.145:8080/
Note that the development build is not optimized.
To create a production build, run npm run build.
看到该页面,则项目创建成功!
2、加入Git版本管理
- 创建远程仓库
- 将本地仓库推到线上 如果没有本地仓库:
# 创建本地仓库,cd 到本地项目目录下
git init
# 将文件添加到暂存区
git add 文件
git add . # 首次提交
# 将暂存区的文件提交到本地仓库
git commit "提交日志"
# 添加远端仓库地址
git remote add origin 你的远程仓库地址
# 推送提交
git push -u origin master
如果已有本地仓库:
# 添加远端仓库地址
git remote add origin 你的远程仓库地址
# 推送提交
git push -u origin master
3. 初始目录结构说明
1 .
2 ├── node_modules # 第三⽅包存储⽬录
3 ├── public # 静态资源⽬录,任何放置在 public ⽂件夹的静态资源都会被简单的复 制,⽽不经过 webpack
4 │ ├── favicon.ico
5 │ └── index.html
6 ├── src
7 │ ├── assets # 公共资源⽬录,放图⽚等资源
8 │ ├── components # 公共组件⽬录
9 │ ├── router # 路由相关模块
10 │ ├── store # 容器相关模块
11 │ ├── views # 路由⻚⾯组件存储⽬录
12 │ ├── App.vue # 根组件,最终被替换渲染到 index.html ⻚⾯中 #app ⼊⼝ 节点
13 │ ├── main.ts # 整个项⽬的启动⼊⼝模块 14 │ ├── shims-tsx.d.ts # ⽀持以 .tsc 结尾的⽂件,在 Vue 项⽬中编写 jsx 代码
15 │ └── shims-vue.d.ts # 让 TypeScript 识别 .vue 模块
16 ├── .browserslistrc # 指定了项⽬的⽬标浏览器的范围。这个值会被 @babel/pre set-env 和 Autoprefixer ⽤来确定需要转译的 JavaScript 特性和需要添加的 C SS 浏览器前缀
17 ├── .editorconfig # EditorConfig 帮助开发⼈员定义和维护跨编辑器(或IDE) 的统⼀的代码⻛格
18 ├── .eslintrc.js # ESLint 的配置⽂件 19 ├── .gitignore # Git 的忽略配置⽂件,告诉Git项⽬中要忽略的⽂件或⽂件夹
20 ├── README.md # 说明⽂档
21 ├── babel.config.js # Babel 配置⽂件
22 ├── package-lock.json # 记录安装时的包的版本号,以保证⾃⼰或其他⼈在 npm i nstall 时⼤家的依赖能保证⼀致
23 ├── package.json # 包说明⽂件,记录了项⽬中使⽤到的第三⽅包依赖信息等内容
24 └── tsconfig.json # TypeScript 配置⽂件
在Vue项目中启用TypeScript支持的两种方式:
(1)全新项目:使用Vue CLI脚手架工具创建Vue项目,勾选TypeScript选项
(2)已有项目:添加Vue官方配置的TypeScript适配插件
使用@vue/cli安装TypeScript插件
vue add @vue/typescript
4. TypeScript相关配置介绍
(1)安装了TypeScript相关的依赖项 dependencies依赖:
依赖项 | 说明 |
---|---|
vue-class-component | 提供使用Class语法写Vue组件 |
vue-property-decorator | 在Class语法基础上提供了一些辅助装饰器 |
devDependencies依赖:
依赖项 | 说明 |
---|---|
@typescript-eslint/eslint-plugin | 使用ESLint校验TypeScript代码 |
@typescript-eslint/parser | 将TypeScript转为 AST 共 ESLint 校验使用 |
@vue/cli-plugin-typescript | 使用TypeScript + ts-loader + fork-ts-checker-webpack-plugin进行更快的类型检查 |
@vue/eslint-config-typescript | 兼容ESLint的TypeScript 校验规则 |
typescript | TypeScript编译器,提供类型校验和转换javaScript功能 |
(2)TypeScript 配置文件 tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
(3)shims-vue.d.ts文件的作用:
主要用于 TypeScript 识别 .vue 文件模块
TypeScript 默认不支持导入 .vue 模块,这个文件告诉 TypeScript 导入 .vue 文件模块都按照vueconstructor 类型识别处理
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
(4)shims-tsx.d.ts文件的作用:
为 jsx 组件模板补充类型声明,如果项目中没有使用到 jsx 可以忽略此文件
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
}
}
}
(5)TypeScript 模块都是用 .ts
后缀
5.定义组件的方式
(1)使用Options APIs
要让 TypeScript 正确推断Vue 组件选项中的类型,您需要使用 Vue.component
或 Vue.extend
定义组件:
<script lang="ts">
import Vue from 'Vue'
export default Vue.extend({
// 以前怎么写,现在还怎么写
data () {
return {
a: 1,
b: '2',
c: [],
d: {
e: 1,
f: '2'
}
}
},
methos: {
test () {
this.a.abc() // 编辑器报错:Property 'abc' does not exist on type 'number'
}
}
})
</script>
(2)使用Class APIs 在 TypeScript 下, Vue 的组件可以使用一个继承自Vue类型的子类表示, 这种类型需要使用 Component 装饰器取修饰 装饰器函数接收的参数就是以前的组件选项对象(data、props、methods之类)
<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component' // 官方库
// @Component 修饰符注明了此类为一个 Vue 组件
@Component({
// 所有的组件选项都可以放在这里
template: '<button @click="onClick">Click!</button>'
})
export default class MyComponent extends Vue {
// 初始数据可以直接声明为实例的 property
message: string = 'Hello!'
// 组件方法也可以直接声明为实例的方法
onClick (): void {
window.alert(this.message)
}
}
</script>
其他的如computed、props、mixin等参考vue-class-component文档,官方库文vue-class-component有些选项的写法比较麻烦,可以在项目中安装vue-property-decorator来简化一些选项的写法
装饰器语法可以参考阮一峰的es6-入门教程的相关内容。装饰器语法是ES草案中的一个新特性,可能会进行重大调整,所以并不建议在生产环境中使用。
6.代码格式规范
常用的标准:
- JavaScript Standard Style
- Airbnb JavaScript Style Guide
- Google Javascript Style Guide
项目中通常使用ESlint来约束代码规范。
项目中的代码规范:
// ESLint配置文件:.eslintrc.js
module.exports = {
root: true,
env: {
node: true
},
// 插件: 扩展了校验规则
extends: [
'plugin:vue/essential', // eslint-plugin-vue
'@vue/standard', // @vue/eslint-config-standard
'@vue/typescript/recommended' // @vue/eslint-config-typescrip
],
parserOptions: {
ecmaVersion: 2020
},
// 自定义验证规则
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}
- eslint-plugin-vue
- GitHub仓库:github.com/vuejs/eslin…
- 官⽅⽂档:<eslint.vuejs.org/ >
- 该插件使我们可以使⽤ ESLint 检查 .vue ⽂件的
<template>
和<script>
- 查找语法错误
- 查找对Vue.js指令的错误使⽤
- 查找违反Vue.js风格指南的⾏为
- @vue/eslint-config-standard
- @vue/eslint-config-typescript
- 规则列表:github.com/typescript-… 如何⾃定义代码格式校验规范:
{
"rules": {
"semi": ["error", "always"],
"quotes": ["error", "double"]
}
}
ESLint附带有大量的规则。你可以使用注释或者配置文件修改项目中要使用的规则。要改变一个规则设置,你必须讲规则 ID 设置为下列值之一:
"off"
或者0
——关闭规则"warn"
或1
——开启规则,使用警告级别的错误:"warn"
(不会导致程序退出)"error"
或2
——开启规则,使用错误级别的错误:"error"
(当被触发的时候,程序会退出) 在文件注释里配置规则,使用以下格式的注释:
/* eslint eqeqeq: "off", curly: "error" */
在这个例子里,eqeqeq
规则被关闭,curly
规则被打开,定义为错误级别。
如果一个规则有额外的选项,你可以使用数组字面量指定它们,比如:
/* eslint quotes: ["error", "double"], curly: 2 */
这条注释为规则 quotes
指定了 “double”选项。数组的第⼀项总是规则的严重程度(数字或字符串)。
配置定义在插件中的⼀个规则的时候,你必须使⽤ 插件名/规则ID 的形式。⽐如:
{
"plugins": [
"plugin1"
],
"rules": {
"eqeqeq": "off",
"curly": "error",
"quotes": ["error", "double"],
"plugin1/rule1": "error"
}
}
在这些配置文件中,规则 plugin1/rule1
表示来自插件 plugin1
的 rule1
规则。你也可以使用这种格式的注释配置,比如:
/* eslint "plugin1/rule1": "error" */
注意:当指定来自插件的规则时,确保删除 eslint-plugin- 前缀。ESLint 在内部只使用没有前缀的名称去定位规则。
7. 导入Element组件库
安装element:
npm i element-ui
在main.js
中导入配置
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
8. 处理样式
在src/styles文件夹下创建一下几个文件:
// src/styles
├── index.scss # 全局样式(在⼊⼝模块被加载⽣效)
├── mixin.scss # 公共的 mixin 混⼊(可以把重复的样式封装为 mixin 混⼊到复⽤ 的地⽅)
├── reset.scss # 重置基础样式
└── variables.scss # 公共样式变量
variables.scss
$primary-color: #40586F;
$success-color: #51cf66;
$warning-color: #fcc419;
$danger-color: #ff6b6b;
$info-color: #868e96; // #22b8cf;
$body-bg: #E9EEF3; // #f5f5f9;
$sidebar-bg: #F8F9FB;
$navbar-bg: #F8F9FB;
$font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
index.scss
@import './variables.scss';
// globals
html {
font-family: $font-family;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
// better Font Rendering
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
background-color: $body-bg;
}
// custom element theme
$--color-primary: $primary-color;
$--color-success: $success-color;
$--color-warning: $warning-color;
$--color-danger: $danger-color;
$--color-info: $info-color;
// change font path, required
$--font-path: '~element-ui/lib/theme-chalk/fonts';
// import element default theme
@import '~element-ui/packages/theme-chalk/src/index';
// node_modules/element-ui/packages/theme-chalk/src/common/var.scss
// overrides
// .el-menu-item, .el-submenu__title {
// height: 50px;
// line-height: 50px;
// }
.el-pagination {
color: #868e96;
}
// components
.status {
display: inline-block;
cursor: pointer;
width: .875rem;
height: .875rem;
vertical-align: middle;
border-radius: 50%;
&-primary {
background: $--color-primary;
}
&-success {
background: $--color-success;
}
&-warning {
background: $--color-warning;
}
&-danger {
background: $--color-danger;
}
&-info {
background: $--color-info;
}
}
共享全局样式变量:
参考:vue官方文档中CSS相关向预处理器 Loader 传递选项一节的介绍
项目根目录下创建vue.config.js文件,并进行如下配置
// vue.config.js
module.exports = {
css: {
loaderOptions: {
scss: {
prependData: `@import "~@/variables.scss";`
},
}
}
}
父组件改变子组件样式-深度选择器
建议你使用::v-deep
的写法,它不仅css的>>>
写法,而且它还是vue3.0 RFC
中指定的写法。
而且原本/deep/
的写法也本身就Chrome所废弃,你现在经常能在控制台中发现Chrome提示你不要使用/deep/
的警告。
9. 接口处理-配置后端代理
接口跨域问题:最推荐的也是我⼯作中在使⽤的⽅式就是: cors 全称为 Cross Origin Resource Sharing(跨域资 源共享)。这种⽅案对于前端来说没有什么⼯作量,和正常发送请求写法上没有任何区别,⼯作量基本都 在后端这⾥。每⼀次请求,浏览器必须先以 OPTIONS 请求⽅式发送⼀个预请求(也不是所有请求都会发 送 options,展开介绍 点我),通过预检请求从⽽获知服务器端对跨源请求⽀持的 HTTP ⽅法。在确认 服务器允许该跨源请求的情况下,再以实际的 HTTP 请求⽅法发送那个真正的请求。推荐的原因是:只要 第⼀次配好了,之后不管有多少接⼝和项⽬复⽤就可以了,⼀劳永逸的解决了跨域问题,⽽且不管是开发 环境还是正式环境都能⽅便的使⽤。详细 MDN ⽂档。
但总有后端觉得麻烦不想这么搞,那纯前端也是有解决⽅案的。
在 dev 开发模式下可以下使⽤ webpack 的 proxy 使⽤也是很⽅便,参照 ⽂档 就会使⽤了,楼主⼀ 些个⼈项⽬使⽤的该⽅法。但这种⽅法在⽣产环境是不能使⽤的。在⽣产环境中需要使⽤ nginx 进⾏反 向代理。不管是 proxy 和 nginx 的原理都是⼀样的,通过搭建⼀个中转服务器来转发请求规避跨域的 问题。 | 开发环境 | 生产环境 | | :----: | :----: | | cors | cors | | proxy | nginx |
虽然其他的跨域方式都还有很多但都不推荐,真心主流的就这两种方式。
配置客户端层面的服务端代理跨域可以参考官方文档中的说明:cli.vuejs.org/zh/config/#…
下面是具体的操作流程:
在项目根目录下添加vue.config.js
配置文件
module.exports = {
...
devServer: {
proxy: {
'/front': {
target: 'http://edufront.lagou.com',
changeOrigin: true // 设置请求头中的 host 为 target,防⽌后端 反向代理服务器⽆法识别
},
'/boss': {
target: 'http://eduboss.lagou.com',
changeOrigin: true
}
}
}
}
10. 封装请求模块
安装axios
npm i axios
创建src/utils/request.js
import axios from 'axios'
// 创建axios实例
const service = axios.creat({
})
// request 拦截器
// response 拦截器
export default service
二、布局
三、以 application/x-www-form-urlencoded 格式发送请求
axios 默认发送是 application/json格式的数据,转 application/x-www-form-urlencoded建议使用qs
安装:
npm i qs
使用:
import qs from 'qs';
const data = { 'bar': 123 };
const options = {
method: 'POST',
//headers: {
// 'content-type': 'application/x-www-form-urlencoded'
//cd},
data: qs.stringify(data),
url,
};
axios(options);
axios会根据data的类型自动设置请求headers中的Content-Type;如果 data 是普通对象,则 Content-type 是 application/json;如果 data 是 qs.stringify(data) 转换之后的数据:key=value&key=value, 则 Content-Type 会被设置为 application/x-www-form-urlencoded;如果 data 是 FormData 对象,则 Content-Type 是 multipart/form-data 更多内容参考
四、根据登录状态校验页面访问权限
登录成功后把用户相关信息存储到vuex中方便组件间的共享,为了避免刷新页面数据丢失需要把用户信息持久化到storage中;
// 提交登录表单信息
import { login } from '@/services/user'
import { Form } from 'element-ui'
async onSubmit () {
try {
// 1、表单验证
await (this.$refs.form as Form).validate() // 注明Form类型,否则编辑器会提示错误
// 2、验证通过 ->提交表单
this.isLoginLoading = true
const { data } = await login(this.form)
// 3、处理请求结果
// 失败-输出失败原因
if (data.state !== 1) {
this.$message.error(data.message)
} else {
// 1.登录成功,记录登录状态,状态需要能够全局访问(放到 Vuex 容器中)
this.$store.commit('setUser', data.content)
// 2.然后在访问需要登录的页面的时候判断有没有登录状态(路由拦截器)
// 成功-跳转会将要访问的页面
this.$router.push((this.$route.query.redirect as string) || '/')
this.$message.success('登录成功')
}
} catch (err) {
console.log('登录失败', err)
}
// 结束登录按钮的 loading
this.isLoginLoading = false
}
// store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// 容器的状态实现了数据共享,在组件里面访问方便,但是没有持久化的功能
state: {
user: JSON.parse(window.localStorage.getItem('user') || 'null') // 当前登录用户状态
},
mutations: {
// 修改热容器数据必须使用 mutation 函数
setUser (state, payload) {
state.user = JSON.parse(payload)
// 为了防止页面刷新数据丢失,我们需要把 user 数据持久化
window.localStorage.setItem('user', payload)
}
},
actions: {
},
modules: {
}
})
利用vue-router的全局前置守卫router.beforeEach
和meta
字段来处理访问权限
// router/index.ts
import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'
import LayOut from '@/layout/index.vue'
import store from '@/store'
Vue.use(VueRouter)
// 路由配置规则
const routes: Array<RouteConfig> = [
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: 'login' */ '@/views/login/index.vue')
},
{
path: '/',
component: LayOut,
children: [
{
path: '', // 默认子路由
name: 'home',
component: () => import(/* webpackChunkName: 'home' */ '@/views/home/index.vue'),
meta: {
requiresAuth: true
}
},
{
path: '/role',
name: 'role',
component: () => import(/* webpackChunkName: 'role' */ '@/views/role/index.vue')
},
{
path: '/menu',
name: 'menu',
component: () => import(/* webpackChunkName: 'menu' */ '@/views/menu/index.vue')
},
{
path: '/resource',
name: 'resource',
component: () => import(/* webpackChunkName: 'resource' */ '@/views/resource/index.vue')
},
{
path: '/course',
name: 'course',
component: () => import(/* webpackChunkName: 'course' */ '@/views/course/index.vue')
},
{
path: '/user',
name: 'user',
component: () => import(/* webpackChunkName: 'user' */ '@/views/user/index.vue')
},
{
path: '/advert',
name: 'advert',
component: () => import(/* webpackChunkName: 'advert' */ '@/views/advert/index.vue')
},
{
path: '/advert-space',
name: 'advert-space',
component: () => import(/* webpackChunkName: 'advert-space' */ '@/views/advert-space/index.vue')
}
]
},
{
path: '*',
name: '404',
component: () => import(/* webpackChunkName: '404' */ '@/views/error-page/404.vue')
}
]
const router = new VueRouter({
routes
})
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!store.state.user) {
next({
name: 'login',
query: { // 通过 url 传递查询字符串参数
redirect: to.fullPath // 把登录成功需要返回的页面告诉登录页面
}
})
} else {
next()
}
} else {
next() // 确保一定调用 next()
}
})
export default router
注意: (1)一个路由匹配到的所有路由记录会暴露为
$route
对象的matched
数组,所以一旦父级路由限制登录访问的权限,则它下面的子路由也都需要登录后才可以访问,为了灵活一般都给子路由单独添加权限标识meta:{ requiresAuth: true}
;(2)确保next()方法一定要调用
五、使用请求拦截器统一设置Token
// store/index.js
import axios from 'axios'
import store from '@/store/index'
// 创建axios实例
const request = axios.create({
})
// request 拦截器
request.interceptors.request.use(function (config) {
// 在这里通过改写 config 配置信息来实现业务功能统一处理
const { user } = store.state
if (user && user.access_token) {
config.headers.Authorization = user.access_token
}
// 注意这里一定要返回 config ,否则请求就发送不出去了
return config
}, function (error) {
// Do something with request error
return Promise.reject(error)
})
// response 拦截器
export default request
六、关于Token过期问题
1. 概念介绍
access_token: 作用:获取需要授权的接口数据
expires_in: 作用:access_token 过期时间
refresh_token: 作用:刷新获取access_token
2. 处理Token过期的两种方法:
方法一:
在请求发起前拦截每个请求,判断 token 的有效时间是否已过期,若已过期,则将请求挂起,先刷新token后再继续请求。
优点:在请求前拦截,能节省请求,省流量
缺点:需要后端额外提供一个token过期时间的字段;使用了本地时间判断,若本地时间被篡改,特别是本地时间比服务器时间慢时,拦截会失败。
方法二:
不在请求前拦截,而是拦截返回后的数据。先发起请求,接口返回过期后,先刷新token,再进行一次重试。
优点:不需要额外的token过期字段,不需要判断时间。
缺点:会消耗多一次请求,耗流量。
总结:方法一和方法二优缺点是互补的,方法一有校验失败的风险(本地时间被篡改时),方法二,等知道服务器已经过期了再重试一次,除了会多耗一个请求没有其他问题,推荐使用方法二。
3. 使用响应拦截器处理token过期
import axios from 'axios'
import store from '@/store/index'
import { Message } from 'element-ui' // 引入element-ui的消息提示组件
import router from '@/router' // 引入vue-router
import qs from 'qs'
// 创建axios实例
const request = axios.create({
})
function redirectLogin () {
router.push({
name: 'login',
query: {
redirect: router.currentRoute.fullPath // 记录当前页面地址,登录成功重新返回回来
}
})
}
function refreshToken () {
return axios.create()({
method: 'POST',
url: '/front/user/refresh_token',
data: qs.stringify({
refreshtoken: store.state.user.refresh_token
})
})
}
// 省略request 拦截器(参见上文五)
// response 拦截器
let isRefreshing = false // 控制刷新 token 的状态
let requests: any[] = [] // 存储刷新 token 期间过来的 401 请求
request.interceptors.response.use(function (response) { // 状态码为 2xx 都会进入这里
// 如果是自定义错误状态码,错误处理就写这里
return response
}, async function (error) { // 超出 2xx 状态码都执行这里
// 如果是使用的 HTTP 状态码,错误处理就写这里
if (error.response) { // 请求发出去收到响应了,但是状态码超出了 2xx 范围
// 状态码根据与服务端协商处理
const { status } = error.response
if (status === 400) {
Message.error('请求参数错误')
} else if (status === 401) {
// token 无效(没有提供 token、是无效的、token过期了)
// 如果没有,则直接跳转登录页
if (!store.state.user) {
redirectLogin()
return Promise.reject(error)
}
// 如果有 refresh_token 则尝试使用 refresh_token 获取新的access_token
if (!isRefreshing) {
isRefreshing = true // 开启刷新状态
// 尝试刷新获取新的 token
return refreshToken().then(res => {
if (!res.data.success) {
throw new Error('刷新 Token 失败')
}
// 刷新 token 成功
store.commit('setUser', res.data.content) // 把刷新拿到的新的 access_token 更新到容器和本地存储中
// 把 requests 队列中的请求重新发出去
requests.forEach(cb => cb())
// 队列中的请求发送后,需要重置requests 数组
requests = []
// 把本次失败的请求重新发出去
return request(error.config) // error.config中包含失败请求的方法、url、参数
}).catch(err => {
console.log(err)
// 把当前登录用户的状态清除
store.commit('setUser', null)
// 刷新 token 失败 ——> 跳转登录页面重新登录获取新的 token
redirectLogin()
return Promise.reject(error)
}).finally(() => {
isRefreshing = false // 无论刷新 token 成功还是失败,最后都需要重置刷新状态
})
}
// 刷新状态下,把请求挂起放到 requests 数组中
return new Promise(resolve => {
requests.push(() => {
resolve(request(error.config))
})
})
} else if (status === 403) {
Message.error('没有权限,请联系管理员')
} else if (status === 404) {
Message.error('请求资源不存在')
} else if (status >= 500) {
Message.error('服务端错误,请联系管理员')
}
} else if (error.request) { // 请求发出去没有收到响应
Message.error('请求超时,请刷新重试')
} else { // 在设置请求时发生了一些事情,触发了一个错误
Message.error(`请求失败:${error.message}`)
}
// 把请求失败的错误对象继续抛出,扔给上一个调用者
return Promise.reject(error)
})
export default request
七、axios错误处理
import axios from 'axios'
import store from '@/store/index'
import { Message } from 'element-ui' // 引入element-ui消息提示组件
// 创建axios实例
const request = axios.create({
})
// request 拦截器(省略,见上文五)
// response 拦截器
request.interceptors.response.use(function (response) { // 状态码为 2xx 都会进入这里
// 如果是自定义错误状态码,错误处理就写这里
return response
}, function (error) { // 超出 2xx 状态码都执行这里
// 如果是使用的 HTTP 状态码,错误处理就写这里
if (error.response) { // 请求发出去收到响应了,但是状态码超出了 2xx 范围
// 状态码根据与服务端协商处理
const { status } = error.response
if (status === 400) {
Message.error('请求参数错误')
} else if (status === 401) {
// token 无效(没有提供 token、是无效的、token过期了)
} else if (status === 403) {
Message.error('没有权限,请联系管理员')
} else if (status === 404) {
Message.error('请求资源不存在')
} else if (status >= 500) {
Message.error('服务端错误,请联系管理员')
}
} else if (error.request) { // 请求发出去没有收到响应
Message.error('请求超时,请刷新重试')
} else { // 在设置请求时发生了一些事情,触发了一个错误
Message.error(`请求失败:${error.message}`)
}
// 把请求失败的错误对象继续抛出,扔给上一个调用者
return Promise.reject(error)
})
export default request