最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue.js + Vuex + TypeScript实战项目开发与项目优化

    正文概述 掘金(贤惠)   2021-01-29   492

    一、创建项目

    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版本管理

    1. 创建远程仓库
    2. 将本地仓库推到线上

    如果没有本地仓库:

    # 创建本地仓库
    git init
    
    # 将文件添加到暂存区
    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.js + Vuex + 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 校验规则typescriptTypeScript编译器,提供类型校验和转换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.componentVue.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来简化一些选项的写法

    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…
      • 官⽅⽂档:<https://eslint.vuejs.org/ >
      • 该插件使我们可以使⽤ ESLint 检查 .vue ⽂件的 <template><script>
      • 查找语法错误
      • 查找对Vue.js指令的错误使⽤
      • 查找违反Vue.js风格指南的⾏为
    • @vue/eslint-config-standard
      • eslint-plugin-standard
      • JavaScript Standard Style
    • @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 表示来自插件 plugin1rule1 规则。你也可以使用这种格式的注释配置,比如:

    /* 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. 接口处理-配置后端代理

    开发环境生产环境
    corscorsproxynginx

    虽然其他的跨域方式都还有很多但都不推荐,真心主流的就这两种方式。
    配置客户端层面的服务端代理跨域可以参考官方文档中的说明: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);
    

    更多内容参考

    四、根据登录状态校验页面访问权限

    登录成功后把用户相关信息存储到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.beforeEachmeta字段来处理访问权限

    // 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
    

    五、使用请求拦截器统一设置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
    

    下载网 » Vue.js + Vuex + TypeScript实战项目开发与项目优化

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元