添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
想发财的苹果  ·  python - 'MyClass' ...·  1 年前    · 

先来认识下面的小玩意

vue-property-decorator

这里单页面组件的书写采用的是 vue-property-decorator 库,该库完全依赖于 vue-class-component ,也是 vue 官方推荐的库。

单页面组件中,在 @Component({}) 里面写 props data 等调用起来极其不方便,而 vue-property-decorator 里面包含了 8 个装饰符则解决了此类问题,他们分别为:

  • @Emit 指定事件emit,可以使用此修饰符,也可以直接使用 this.$emit()
  • @Inject 指定依赖注入
  • @Mixins mixin 注入
  • @Model 指定 model
  • @Prop 指定 Prop
  • @Provide 指定 Provide
  • @Watch 指定 Watch
  • @Component export from vue-class-component
  • import {
      Component, Prop, Watch, Vue
    } from 'vue-property-decorator'
    @Component
    export class MyComponent extends Vue {
      dataA: string = 'test'
      count = 0
      @Prop({ default: 0 }) private propA!: number
      @Prop({ default: () => [10, 20, 30, 50] }) private propB!: number[]
      @Prop({ default: 'total, sizes, prev, pager, next, jumper' }) private propC!: string
      @Prop({ default: true }) private propD!: boolean,
      @prop([String, Boolean]) propE: string | boolean;
      @Emit('reset')
      resetCount() {
        this.count = 0
      @Emit()
      returnValue() {
        return 10
      @Emit()
      onInputChange(e) {
        return e.target.value
      // watcher
      @Watch('child')
      onChildChanged (val: string, oldVal: string) {}
      @Watch('person', { immediate: true, deep: true })
      onPersonChanged (val: Person, oldVal: Person) {}
      // 其他修饰符详情见上面的 github 地址,这里就不一一做说明了
    

    解析之后会变成

    export default {
      data () {
        return {
          dataA: 'test'
      props: {
        propA: {
    	  type: Number
        propB: {
          type: Array,
          default: [10, 20, 30, 50]
        propC: {
          type: String,
          default: 'total, sizes, prev, pager, next, jumper'
        propD: {
          type: String,
          default: 'total, sizes, prev, pager, next, jumper'
        propE: {
          type: [String, Boolean]
      watch: {
        'child': {
          // handler:其值是一个回调函数。即监听到变化时应该执行的函数。
          handler: 'onChildChanged',
          // immediate:其值是true或false;immediate:true代表如果在 wacth
          // 里声明了之后,就会立即先去执行里面的handler方法,如果为
          // false就跟我们以前的效果一样,不会在绑定的时候就执行
          immediate: false,
          // deep:其值是true或false;确认是否深入监听。deep的意思就是深入观
          // 察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器(
          // 受现代 JavaScript 的限制 (以及废弃 Object.observe),Vue 
          // 不能检测到对象属性的添加或删除)
          deep: false
        'person': {
          handler: 'onPersonChanged',
          immediate: true,
          deep: true
      methods: {
         resetCount() {
          this.count = 0
          this.$emit('reset')
        returnValue() {
          this.$emit('return-value', 10)
        onInputChange(e) {
          this.$emit('on-input-change', e.target.value, e)
        onChildChanged (val, oldVal) {},
        onPersonChanged (val, oldVal) {}
    

    这里有两个常用修饰符 !``? ,!和可选参数 ? 是相对的, ! 表示强制解析(也就是告诉 typescript 编译器,我这里一定有值),你写?的时候再调用, typescript 会提示可能为 undefined

    @Emit

    @Emit 装饰器的函数会在运行之后触发等同于其函数名(驼峰式会转为横杠式写法)的事件, 并将其函数传递给 $emit

  • @Emit() 不传参数,那么它触发的事件名就是它所修饰的函数名.
  • @Emit(name: string) ,里面传递一个字符串,该字符串为要触发的事件名
  • @Watch

    watch 是一个对象,对象就有键,有值。

  • 第一个 handler :其值是一个回调函数。即监听到变化时应该执行的函数。
  • 第二个是 deep :其值是 true false ;确认是否深入监听。 deep 的意思就是深入观察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器(受现代 JavaScript 的限制 (以及废弃 Object.observe ), Vue 不能检测到对象属性的添加或删除)
  • 第三个是 immediate :其值是 true false immediate:true 代表如果在 wacth 里声明了之后,就会立即先去执行里面的handler方法,如果为 false 就跟我们以前的效果一样,不会在绑定的时候就执行
  • @Watch 使用非常简单,接受第一个参数为要监听的属性名, 第二个属性为可选对象。 @Watch 所装饰的函数即监听到属性变化之后应该执行的函数。 @Watch 装饰的函数的函数名并非如上 onStateChanged 严格命名,它是多元化的,你可以随心所欲的命名,当然,能按照规范化的命名会使你的代码阅读性更好。

    @Minxins

    // myMixin.ts
    import { Vue, Component } from 'vue-property-decorator';
    declare module 'vue/types/vue' {
        interface Vue {
            mixinValue: string;
    @Component
    export default class myMixins extends Vue {
        mixinValue: string = 'Hello World!!!'
    
    import { Vue, Component, Prop } from 'vue-property-decorator';
    import MyMixin from './myMixin.js'
    @Component({
        mixins: [MyMixin]
    export default class extends Vue{
        created(){
            console.log(mixinValue) // => Hello World!!!
    

    mixin另一写法,在下面会有出现。

    @Model

    @Model装饰器允许我们在一个组件上自定义v-model,接收两个参数:

  • event: string 事件名。
  • options: Constructor | Constructor[] | PropOptions 与@Prop的第一个参数一致。
  • import { Vue, Component, Model } from 'vue-property-decorator'
    @Component
    export default class MyInput extends Vue {
      @Model('change', { type: String, default: 'Hello world!!!' }) readonly value!: string
    
    <template>
      <input
        type="text"
        :value="value"
        @change="$emit('change', $event.target.value)"
    </template>
    export default {
      model: {
        prop: 'value',
        event: 'change'
      props: {
        value: {
          type: String,
          default: 'Hello world!!!'
    

    @Provide @Inject

    @Provide 声明一个值 , 在其他地方用@Inject 接收,在实战项目中用得不多,一般用于不依赖于任何第三方状态管理库(如vuex)的组件编写

    @Ref(refKey?: string)

    @Ref装饰器接收一个可选参数,用来指向元素或子组件的引用信息。如果没有提供这个参数,会使用装饰器后面的属性名充当参数

    import { Vue, Component, Ref } from 'vue-property-decorator'
    import { Form } from 'element-ui'
    @Componentexport default class MyComponent extends Vue {
      @Ref() readonly loginForm!: Form
      @Ref('changePasswordForm') readonly passwordForm!: Form
      public handleLogin() {
        this.loginForm.validate(valide => {
          if (valide) {
            // login...
          } else {
            // error tips
    
    export default {
      computed: {
        loginForm: {
          cache: false,
          get() {
            return this.$refs.loginForm
        passwordForm: {
          cache: false,
          get() {
            return this.$refs.changePasswordForm
    
    ? Please pick a preset:(使用上下箭头)
     ◯ default (babel, eslint)        //默认配置
    ❯◉ Manually select features       //手动选择
    
    ? Check the features needed for your project:
     ◉ Babel                                    // javascript转译器
     ◉ TypeScript                               // 使用 TypeScript 书写源码
     ◯ Progressive Web App (PWA) Support        // 渐进式WEB应用
     ◉ Router                                   // 使用vue-router
     ◉ Vuex                                     // 使用vuex
     ◉ CSS Pre-processors                       // 使用css预处理器
    ❯◉ Linter / Formatter                       // 代码规范标准
     ◯ Unit Testing                             // 单元测试
     ◯ E2E Testing                              // e2e测试
    

    是否使用class风格的组件语法: 使用前:home = new Vue()创建vue实例 使用后:class home extends Vue{}

    ? Use class-style component syntax? (Y/n) Y
    // 使用Babel与TypeScript一起用于自动检测的填充
    ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n) Y
    // 路由
    ? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y
    // 预处理器
    ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
    ❯◉ Sass/SCSS (with dart-sass)    // 保存后编译
     ◯ Sass/SCSS (with node-sass)    // 实时编译 
     ◯ Less
     ◯ Stylus
    // 代码格式化检测
    ? Pick a linter / formatter config: (Use arrow keys)
     ◯ ESLint with error prevention only     // 只进行报错提醒
     ◯ ESLint + Airbnb config                // 不严谨模式
     ◯ ESLint + Standard config              // 正常模式
     ◯ ESLint + Prettier                     // 严格模式
    ❯◉ TSLint(deprecated)                    // typescript格式验证工具
    // 代码检查方式
    ? Pick additional lint features: (Press <space> to select, <a>
    to toggle all, <i> to invert selection)
    ❯◉ Lint on save             // 保存检查
     ◯ Lint and fix on commit   // commit时fix
    // 文件配置
    ? Where do you prefer placing config for Babel, ESLint, etc.? (
    Use arrow keys)
      In dedicated config files // 配置在独立的文件中
    ❯ In package.json
    // 保存上述配置,保存后下一次可直接根据上述配置生成项目
    ? Save this as a preset for future projects? (y/N) N
    // 创建成功
    🎉  Successfully created project vue-typescript-admin-demo.
    

    yarn run serve运行项目之后会报一堆莫名的错误,这都是 tslint.json 搞的鬼,配置一下重新运行即可

    // tsconfig.json
    Error: Calls to 'console.log' are not allowed.
    Error: 去除行尾必加';'
    Error: 禁止自动检测末尾行必须使用逗号,always总是检测,never从不检测,ignore忽略检测
    "rules": {
        "no-console": false,
        "semicolon": [
            false,
            "always"
        "trailing-comma": [true, {
            "singleline": "never",
            "multiline": {
                "objects": "ignore",
                "arrays": "ignore",
                "functions": "never",
                "typeLiterals": "ignore"
    

    至此,整个项目算是正常运行起来了。But... 这还是传统的Vue项目,我们要开发的是Vue+ts实战项目,所以需要改造一番,详细的目录结构,等改造完之后再附上吧。

    这是改造后的目录结构

    ├── public                          // 静态页面
    ├── scripts                         // 相关脚本配置
    ├── src                             // 主目录
        ├── assets                      // 静态资源
        ├── api                         // axios封装
        ├── filters                     // 过滤
        ├── lib                         // 全局插件
        ├── router                      // 路由配置
        ├── store                       // vuex 配置
        ├── styles                      // 样式
        ├── types                       // 全局注入
        ├── utils                       // 工具方法(全局方法等)
        ├── views                       // 页面
        ├── App.vue                     // 页面主入口
        ├── main.ts                     // 脚本主入口
        ├── registerServiceWorker.ts    // PWA 配置
    ├── tests                           // 测试用例
    ├── .editorconfig                   // 编辑相关配置
    ├── .npmrc                          // npm 源配置
    ├── .postcssrc.js                   // postcss 配置
    ├── babel.config.js                 // preset 记录
    ├── cypress.json                    // e2e plugins
    ├── f2eci.json                      // 部署相关配置
    ├── package.json                    // 依赖
    ├── README.md                       // 项目 readme
    ├── tsconfig.json                   // ts 配置
    ├── tslint.json                     // tslint 配置
    └── vue.config.js                   // webpack 配置
    

    主要涉及 shims-tsx.d.tsshims-vue.d.ts 两个文件

  • shims-tsx.d.ts,允许你以 .tsx 结尾的文件,在 Vue 项目中编写 jsx代码
  • shims-vue.d.ts 主要用于 TypeScript 识别 .vue文件, ts 默认并不支持导入 .vue文件,这个文件告诉 ts 导入.vue 文件都按 VueConstructor<Vue> 处理。
  • tslint添加如下配置

    // tslint.json
    // 不检测隐式类型
      "defaultSeverity": "none", // 值为warn时为警告
    	"rules": {
      "arrow-parens": [
    	false,
    	"as-needed"
    

    其他内容配置(自选)

    // tslint.json
      "defaultSeverity": "warning",
      "extends": [
        "tslint:recommended"
      "linterOptions": {
        "exclude": [
          "node_modules/**"
      "rules": {
        "quotemark": false, // 字符串文字需要单引号或双引号。
        "indent": false, // 使用制表符或空格强制缩进。
        "member-access": false, // 需要类成员的显式可见性声明。
        "interface-name": false, // 接口名要求大写开头
        "ordered-imports": false, // 要求将import语句按字母顺序排列并进行分组。
        "object-literal-sort-keys": false, // 检查对象文字中键的排序。
        "no-consecutive-blank-lines": false, // 不允许连续出现一个或多个空行。
        "no-shadowed-variable": false, // 不允许隐藏变量声明。
        "no-trailing-whitespace": false, // 不允许在行尾添加尾随空格。
        "semicolon": false, // 是否分号结尾
        "trailing-comma": false, // 是否强象添加逗号
        "eofline": false, // 是否末尾另起一行
        "prefer-conditional-expression": false, // for (... in ...)语句必须用if语句过滤
        "curly": true, //for if do while 要有括号
        "forin": false, //用for in 必须用if进行过滤
        "import-blacklist": true, //允许使用import require导入具体的模块
        "no-arg": true, //不允许使用 argument.callee
        "no-bitwise": true, //不允许使用按位运算符
        "no-console": false, //不能使用console
        "no-construct": true, //不允许使用 String/Number/Boolean的构造函数
        "no-debugger": true, //不允许使用debugger
        "no-duplicate-super": true, //构造函数两次用super会发出警告
        "no-empty": true, //不允许空的块
        "no-eval": true, //不允许使用eval
        "no-floating-promises": false, //必须正确处理promise的返回函数
        "no-for-in-array": false, //不允许使用for in 遍历数组
        "no-implicit-dependencies": false, //不允许在项目的package.json中导入未列为依赖项的模块
        "no-inferred-empty-object-type": false, //不允许在函数和构造函数中使用{}的类型推断
        "no-invalid-template-strings": true, //警告在非模板字符中使用${
        "no-invalid-this": true, //不允许在非class中使用 this关键字
        "no-misused-new": true, //禁止定义构造函数或new class
        "no-null-keyword": false, //不允许使用null关键字
        "no-object-literal-type-assertion": false, //禁止object出现在类型断言表达式中
        "no-return-await": true, //不允许return await
        "arrow-parens": false, //箭头函数定义的参数需要括号
        "adjacent-overload-signatures": false, //  Enforces function overloads to be consecutive.
        "ban-comma-operator": true, //禁止逗号运算符。
        "no-any": false, //不需使用any类型
        "no-empty-interface": true, //禁止空接口 {}
        "no-internal-module": true, //不允许内部模块
        "no-magic-numbers": false, //不允许在变量赋值之外使用常量数值。当没有指定允许值列表时,默认允许-1,0和1
        "no-namespace": [true, "allpw-declarations"], //不允许使用内部modules和命名空间
        "no-non-null-assertion": true, //不允许使用!后缀操作符的非空断言。
        "no-parameter-reassignment": true, //不允许重新分配参数
        "no-reference": true, // 禁止使用/// <reference path=> 导入 ,使用import代替
        "no-unnecessary-type-assertion": false, //如果类型断言没有改变表达式的类型就发出警告
        "no-var-requires": false, //不允许使用var module = require("module"),用 import foo = require('foo')导入
        "prefer-for-of": true, //建议使用for(..of)
        "promise-function-async": false, //要求异步函数返回promise
        "max-classes-per-file": [true, 2], // 一个脚本最多几个申明类
        "variable-name": false,
        "prefer-const": false // 提示可以用const的地方
    

    世界顿时清净了~~~ 有硬需要的朋友可以自行打开,前提是一定要配置好tslint规则,否则还是有点痛苦不堪的,毕竟warn多了看着难受。告辞

    ./src/config/index.ts

    * 线上环境 export const ONLINEHOST: string = 'https://xxx.com' * 测试环境 export const QAHOST: string = 'http://xxx.com' * 线上mock export const MOCKHOST: string = 'http://xxx.com' * 是否mock export const ISMOCK: boolean = true * 当前的host ONLINEHOST | QAHOST | MOCKHOST export const MAINHOST: string = ONLINEHOST * 请求的公共参数 export const conmomPrams: any = {} * @description token在Cookie中存储的天数,默认1天 export const cookieExpires: number = 1

    ./src/utils/common.ts

    // 下载js-cookie
    cnpm i js-cookie --S
    cnpm install @types/js-cookie --D
    import Cookies from 'js-cookie'
    import { cookieExpires } from '@/config' // cookie保存的天数
     * @Author: asheng
     * @msg: 存取token
     * @param {string} token
    export const TOKEN_KEY: string = 'token'
    export const setToken = (token: string) => {
      Cookies.set(TOKEN_KEY, token, { expires: cookieExpires || 1 })
    export const getToken = () => {
      const token = Cookies.get(TOKEN_KEY)
      if (token) {
        return token
      } else {
        return false
     * @param {String} url
     * @description 从URL中解析参数
    export const getParams = (url: string) => {
      const keyValueArr = url.split('?')[1].split('&')
      let paramObj: any = {}
      keyValueArr.forEach(item => {
        const keyValue = item.split('=')
        paramObj[keyValue[0]] = keyValue[1]
      return paramObj
     * 判断一个对象是否存在key,如果传入第二个参数key,则是判断这个obj对象是否存在key这个属性
     * 如果没有传入key这个参数,则判断obj对象是否有键值对
    export const hasKey = (obj: any, key: string | number) => {
      if (key) {
        return key in obj
      } else {
        const keysArr = Object.keys(obj)
        return keysArr.length
     * @msg: 获取系统当前时间
     * @param {string} fmt 时间格式 具体看代码
     * @return: string
    export const getDate = (fmt: any) => {
      let time = ''
      const date = new Date()
      const o: any = {
        "M+": date.getMonth() + 1, // 月份 
        "d+": date.getDate(), // 日 
        "H+": date.getHours(), // 小时 
        "m+": date.getMinutes(), // 分 
        "s+": date.getSeconds(), // 秒 
        "q+": Math.floor((date.getMonth() + 3) / 3), // 季度 
        "S": date.getMilliseconds() // 毫秒 
      if (/(y+)/.test(fmt)) {
        time = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
      for (const k in o) {
        if (new RegExp("(" + k + ")").test(fmt)) {
          time = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
      return time
     * @msg: 获取系统当前时间
     * @param {string} date 时间
     * @param {string} fmt 时间格式
     * @return: string
    export const formatDate = (date: any, fmt: string) => {
      let time = ''
      const o: any = {
        "M+": date.getMonth() + 1, // 月份 
        "d+": date.getDate(), // 日 
        "H+": date.getHours(), // 小时 
        "m+": date.getMinutes(), // 分 
        "s+": date.getSeconds(), // 秒 
        "q+": Math.floor((date.getMonth() + 3) / 3), // 季度 
        "S": date.getMilliseconds() // 毫秒 
      if (/(y+)/.test(fmt)) {
        time = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
      for (const k in o) {
        if (new RegExp("(" + k + ")").test(fmt)) {
          time = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
      return time
    // copy in the 'fx-fuli' utils
     * 校验手机号是否正确
     * @param phone 手机号
    export const verifyPhone = (phone: string | number) => {
      const reg = /^1[34578][0-9]{9}$/
      const _phone = phone.toString().trim()
      let toastStr = _phone === '' ? '手机号不能为空~' : !reg.test(_phone) && '请输入正确手机号~'
      return {
        errMsg: toastStr,
        done: !toastStr,
        value: _phone
    export const verifyStr = (str: string | number, text: string) => {
      const _str = str.toString().trim()
      const toastStr = _str.length ? false : `请填写${text}~`
      return {
        errMsg: toastStr,
        done: !toastStr,
        value: _str
    // 截取字符串
    export const sliceStr = (str: any, sliceLen: number) => {
      if (!str) { return '' }
      let realLength = 0
      const len = str.length
      let charCode = -1
      for (let i = 0; i < len; i++) {
        charCode = str.charCodeAt(i)
        if (charCode >= 0 && charCode <= 128) {
          realLength += 1
        } else {
          realLength += 2
        if (realLength > sliceLen) {
          return `${str.slice(0, i)}...`
      return str
     * JSON 克隆
     * @param {Object | Json} jsonObj json对象
     * @return {Object | Json} 新的json对象
    export function objClone(jsonObj: any) {
      let buf: any
      if (jsonObj instanceof Array) {
        buf = []
        let i = jsonObj.length
        while (i--) {
          buf[i] = objClone(jsonObj[i])
        return buf
      } else if (jsonObj instanceof Object) {
        buf = {}
        for (let k in jsonObj) {
          buf[k] = objClone(jsonObj[k])
        return buf
      } else {
        return jsonObj
    

    一、巧用Webpack

    Webpack是实现我们前端项目工程化的基础,但其实她的用处远不仅仅如此,我们可以通过Webpack来帮我们做一些自动化的事情。首先我们要了解require.context()这个API

    require.context()

    您可以使用require.context()函数创建自己的上下文。 它允许您传入一个目录进行搜索,一个标志指示是否应该搜索子目录,还有一个正则表达式来匹配文件。

    其实是Webpack通过解析 require()的调用,提取出来如下这些信息:

    Directory: ./template
    Regular expression: /^.*\.ejs$/
    

    然后来创建我们自己的上下文,什么意思呢,就是我们可以通过这个方法筛选出来我们需要的文件并且读取

    * @param directory 要搜索的文件夹目录不能是变量,否则在编译阶段无法定位目录 * @param useSubdirectories 是否搜索子目录 * @param regExp 匹配文件的正则表达式 * @return function 返回一个具有 resolve, keys, id 三个属性的方法 resolve() 它返回请求被解析后得到的模块 id keys() 它返回一个数组,由所有符合上下文模块处理的请求组成。 id 是上下文模块里面所包含的模块 id. 它可能在你使用 module.hot.accept 的时候被用到 require.context('.', useSubdirectories = false, regExp = /\.js$/) // (创建了)一个包含了 demo 文件夹(不包含子目录)下面的、所有文件名以 `js` 结尾的、能被 require 请求到的文件的上下文。

    这么讲,是否觉得抽象,接下来我们应用下这个小东西。

    对于Vue中的路由,大家都很熟悉,类似于声明式的配置文件,其实已经很简洁了。现在我们来让他更简洁

    router                           // 路由文件夹
      |__index.ts                    // 路由组织器:用来初始化路由等等
      |__common.ts                   // 通用路由:声明通用路由
      |__modules                     // 业务逻辑模块:所以的业务逻辑模块
            |__index.ts              // 自动化处理文件:自动引入路由的核心文件
            |__home.ts               // 业务模块home:业务模块
    

    modules

    modules文件夹中存放着我们所有的业务逻辑模块,至于业务逻辑模块怎么分,我相信大家自然有自己的一套标准。我们通过上面提到的require.context()接下来编写自动化的核心部分index.js。

    const files: any = require.context('.', false, /\.ts/)
    let configRouters: Array<any> = []
    files.keys().forEach((key) => {
      if (key === './index.ts') {
        return
      configRouters = configRouters.concat(files(key).default)
    export default configRouters
    

    common

    common路由处理 我们的项目中有一大堆的公共路由需要处理比如404阿,503阿等等路由我们都在common.ts中进行处理。

    export default [
        path: '/',
        name: 'Login',
        // redirect: '/Login',
        component: Login
        path: '*',
        name: 'Lost',
        component: () => import('@/views/404.vue')
    

    路由初始化 这是我们的最后一步了,用来初始化我们的项目路由

    import Vue from 'vue'
    import Router from 'vue-router'
    import ConfigRouters from './modules'
    import Common from './common'
    // 由于是网站开发,这个是进度条,具体开百度了解一下
    import NProgress from 'nprogress'
    import 'nprogress/nprogress.css'
    import { getToken } from '@/utils/common'
    Vue.use(Router)
    const router = new Router({
      // mode: "history",
      // base: process.env.BASE_URL,
      scrollBehavior() {
        return { x: 0, y: 0 }
      routes: ConfigRouters.concat(Common)
    // 登陆页面路由 name
    const LOGIN_PAGE_NAME = 'Login'
    // 跳转之前
    router.beforeEach((to, from, next) => {
      NProgress.start()
      const token = getToken()
      if (!token && to.name !== LOGIN_PAGE_NAME) {
        // 未登录且要跳转的页面不是登录页
        next({
          name: LOGIN_PAGE_NAME // 跳转到登录页
      } else if (!token && to.name === LOGIN_PAGE_NAME) {
        // 未登陆且要跳转的页面是登录页
        next() // 跳转
      } else if (token && to.name === LOGIN_PAGE_NAME) {
        // 已登录且要跳转的页面是登录页
        next({
          name: 'Home' // 跳转到 index 页
      } else {
        if (token) {
          next() // 跳转
        } else {
          next({
            name: LOGIN_PAGE_NAME
    router.afterEach(() => {
      NProgress.done() // finish progress bar
    export default router
    

    三、充分利用Nodejs

    放着node这么好得东西不用真是有点浪费,那么我们来看看node能为我们增加效率做出什么贡献。

    有这么一个场景,我们每次创建模块的时候都要新建一个vue文件和对应的router配置,而且新页面的大部分东西都还差不多,还得去复制粘贴别得页面。这想想就有点low。那既然有了node我们可不可以通过node来做这写乱七八糟得事情? 下面来把我们的想法付诸于显示。

    ./scripts/template.js
    const fs = require('fs')
    const path = require('path')
    const basePath = path.resolve(__dirname, '../src')
    const dirName = process.argv[2]
    const capPirName = dirName.substring(0, 1).toUpperCase() + dirName.substring(1)
    if (!dirName) {
        console.log('文件夹名称不能为空!')
        console.log('示例:npm run tep ${capPirName}')
        process.exit(0)
     * @msg: vue页面模版
    const VueTep = `<template>
      <div class="${dirName}-wrap">
        {{data.pageName}}
    </template>
    <script lang="ts" src="./${dirName}.ts"></script>
    <style lang="scss">
      @import './${dirName}.scss'
    </style>
    // ts 模版
    const tsTep = `import { Component, Vue } from "vue-property-decorator"
    import { Getter, Action } from "vuex-class"
    import { ${capPirName}Data } from '@/types/views/${dirName}.interface'
    // import {  } from "@/components" // 组件
    @Component({})
    export default class About extends Vue {
      // Getter
      // @Getter ${dirName}.author
      // Action
      // @Action GET_DATA_ASYN
      // data
      data: ${capPirName}Data = {
        pageName: '${dirName}'
      created() {
      activated() {
      mounted() {
      // 初始化函数
      init() {
    // scss 模版
    const scssTep = `@import "@/assets/scss/variables.scss";
    .${dirName}-wrap {
      width: 100%;
    // interface 模版
    const interfaceTep = `// ${dirName}.Data 参数类型
    export interface ${capPirName}Data {
      pageName: string
    // VUEX ${dirName}.State 参数类型
    export interface ${capPirName}State {
      data?: any
    // GET_DATA_ASYN 接口参数类型
    // export interface DataOptions {}
    // vuex 模版
    const vuexTep = `import { ${capPirName}State } from '@/types/views/${dirName}.interface'
    import { GetterTree, MutationTree, ActionTree } from 'vuex'
    import * as ${capPirName}Api from '@/api/${dirName}'
    const state: ${capPirName}State = {
      ${dirName}: {
       author: undefined
    // 强制使用getter获取state
    const getters: GetterTree<${capPirName}State, any> = {
      author: (state: ${capPirName}State) => state.${dirName}.author
    // 更改state
    const mutations: MutationTree<${capPirName}State> = {
      // 更新state都用该方法
      UPDATE_STATE(state: ${capPirName}State, data: ${capPirName}State) {
        for (const key in data) {
          if (!data.hasOwnProperty(key)) { return }
          state[key] = data[key]
    const actions: ActionTree<${capPirName}State, any> = {
      UPDATE_STATE_ASYN({ commit, state: ${capPirName}State }, data: ${capPirName}State) {
        commit('UPDATE_STATE', data)
      // GET_DATA_ASYN({ commit, state: LoginState }) {
      //   ${capPirName}.getData()
    export default {
      state,
      getters,
      mutations,
      actions
    // api 接口模版
    const apiTep = `import Api from '@/utils/request'
    export const getData = () => {
      return Api.getData()
    fs.mkdirSync(`${basePath}/views/${dirName}`) // mkdir
    process.chdir(`${basePath}/views/${dirName}`) // cd views
    fs.writeFileSync(`${dirName}.vue`, VueTep) // vue 
    fs.writeFileSync(`${dirName}.ts`, tsTep) // ts
    fs.writeFileSync(`${dirName}.scss`, scssTep) // scss
    process.chdir(`${basePath}/types/views`); // cd types
    fs.writeFileSync(`${dirName}.interface.ts`, interfaceTep) // interface
    process.chdir(`${basePath}/store/module`); // cd store
    fs.writeFileSync(`${dirName}.ts`, vuexTep) // vuex
    process.chdir(`${basePath}/api`); // cd api
    fs.writeFileSync(`${dirName}.ts`, apiTep) // api
    process.exit(0)
    
    ./scripts/component.js
    const fs = require('fs')
    const path = require('path')
    const basePath = path.resolve(__dirname, '../src')
    const dirName = process.argv[2]
    const capPirName = dirName.substring(0, 1).toUpperCase() + dirName.substring(1)
    if (!dirName) {
        console.log('文件夹名称不能为空!')
        console.log('示例:npm run tep ${capPirName}')
        process.exit(0)
     * @msg: vue页面模版
    const VueTep = `<template>
      <div class="${dirName}-wrap">
        {{data.pageName}}
    </template>
    <script lang="ts" src="./${dirName}.ts"></script>
    <style lang="scss">
      @import './${dirName}.scss'
    </style>
    // ts 模版
    const tsTep = `import { Component, Vue } from "vue-property-decorator"
    import { Getter, Action } from "vuex-class"
    import { ${capPirName}Data } from '@/types/views/${dirName}.interface'
    // import {  } from "@/components" // 组件
    @Component({})
    export default class About extends Vue {
      // Getter
      // @Getter ${dirName}.author
      // Action
      // @Action GET_DATA_ASYN
      // data
      data: ${capPirName}Data = {
        pageName: '${dirName}'
      created() {
      activated() {
      mounted() {
      // 初始化函数
      init() {
    // scss 模版
    const scssTep = `@import "@/assets/scss/variables.scss";
    .${dirName}-wrap {
      width: 100%;
    // interface 模版
    const interfaceTep = `// ${dirName}.Data 参数类型
    export interface ${capPirName}Data {
      pageName: string
    // VUEX ${dirName}.State 参数类型
    export interface ${capPirName}State {
      data?: any
    // GET_DATA_ASYN 接口参数类型
    // export interface DataOptions {}
    // vuex 模版
    const vuexTep = `import { ${capPirName}State } from '@/types/views/${dirName}.interface'
    import { GetterTree, MutationTree, ActionTree } from 'vuex'
    import * as ${capPirName}Api from '@/api/${dirName}'
    const state: ${capPirName}State = {
      ${dirName}: {
       author: undefined
    // 强制使用getter获取state
    const getters: GetterTree<${capPirName}State, any> = {
      author: (state: ${capPirName}State) => state.${dirName}.author
    // 更改state
    const mutations: MutationTree<${capPirName}State> = {
      // 更新state都用该方法
      UPDATE_STATE(state: ${capPirName}State, data: ${capPirName}State) {
        for (const key in data) {
          if (!data.hasOwnProperty(key)) { return }
          state[key] = data[key]
    const actions: ActionTree<${capPirName}State, any> = {
      UPDATE_STATE_ASYN({ commit, state: ${capPirName}State }, data: ${capPirName}State) {
        commit('UPDATE_STATE', data)
      // GET_DATA_ASYN({ commit, state: LoginState }) {
      //   ${capPirName}.getData()
    export default {
      state,
      getters,
      mutations,
      actions
    // api 接口模版
    const apiTep = `import Api from '@/utils/request'
    export const getData = () => {
      return Api.getData()
    fs.mkdirSync(`${basePath}/views/${dirName}`) // mkdir
    process.chdir(`${basePath}/views/${dirName}`) // cd views
    fs.writeFileSync(`${dirName}.vue`, VueTep) // vue 
    fs.writeFileSync(`${dirName}.ts`, tsTep) // ts
    fs.writeFileSync(`${dirName}.scss`, scssTep) // scss
    process.chdir(`${basePath}/types/views`); // cd types
    fs.writeFileSync(`${dirName}.interface.ts`, interfaceTep) // interface
    process.chdir(`${basePath}/store/module`); // cd store
    fs.writeFileSync(`${dirName}.ts`, vuexTep) // vuex
    process.chdir(`${basePath}/api`); // cd api
    fs.writeFileSync(`${dirName}.ts`, apiTep) // api
    process.exit(0)
    
    cnpm run tep index
    cnpm run tep login
    

    我们实现这个功能主要要借助Nodefsprocess, 感兴趣的话可以深入研究一下。

    首先我们要编写我们的node脚本,这里是一个比较简单的版本。什么验证文件夹或者文件的都没有,只是来实现我们这个想法:

    四、状态管理Vuex

    vuex-module-decorators

    传统的vuexvue+ts的项目里面是行不通的,vue 2.0版本对ts的兼容性本身并不是特别友好,所以要达到状态管理的效果,这里要额外引用一个类库vuex-module-decorators,它是基于vue-class-component 所做的拓展,它提供了一系列的装饰器,让vue+ts结合的项目达到状态管理的作用。

    先来看看要完成的模块化管理的目录结构

    ├─ src/ │ ├─ store/ │ ├─── modules/ │ │ ├─ app.ts │ │ ├─ user.ts │ ├─── index.ts
    import Vue from 'vue'
    import Vuex from 'vuex'
    import { IAppState } from './modules/app'
    import { IUserState } from './modules/user'
    Vue.use(Vuex)
    export interface IRootState {
        app: IAppState
        user: IUserState
    // Declare empty store first, dynamically register all modules later.
    export default new Vuex.Store<IRootState>({})
    
    import Vue from 'vue'
    import Vuex from 'vuex'
    import app from './modules/app'
    import user from './modules/user'
    Vue.use(Vuex)
    const store = new Vuex.Store({
      modules: {
    export default store
    

    这样,模块化状态管理的雏形就完成了。对比来看,只是语法风格的变化,其它的变化不大。ts版的状态管理最大的改变体现在各个功能功能函数上

    先看一看原始的vuex配置,轻车熟路

    export default new Vuex.Store({
        state: {
        mutations: {
        actions: {
        modules: {
    

    为了显得不那么啰嗦,直接上版ts版的状态管理吧,可以有个直观的对比

    // user.ts
    import { VuexModule, Module, Action, Mutation, getModule } from 'vuex-module-decorators'
    import store from '@/store'
    export interface IUserState {
        id_token: string
    @Module({ dynamic: true, store, name: 'user' })
    class User extends VuexModule implements IUserState {
        public id_token = ''
        @Mutation
        private SET_TOKEN(token: string) {
            this.id_token = token
        @Action
        public async Login(params: any) {
            this.SET_TOKEN(`token!!!`)
    export const UserModule = getModule(User)
    

    Module

    定义一个modules,直接使用装饰器@Module 注意:原始的vuex同样有一个名为Module的类,但它不是一个装饰器,所以别用混淆了

    @Module({ dynamic: true, store, name: 'user' })
    

    从上面可以看到,我们定义modules不单单用了装饰器,还带了参数值,这个是表明是通过命名空间的形式来使用module,如上,这里的namespaced值即为user

    详细vuex命名空间的说明,可以参考vuex命名空间

    除了namespaced,我们看到还有另外一个参数值store,它即为主入口页对应的整个vuex模块的store

    import store from '@/store'
    

    如果去掉它的话,浏览器会报以下错误

    state

    这里所有的state属性因为加了tslint都会添加上public修饰,其它的用法都是相似的

    Getters

    原始的getters计算函数,在这里对应的即使get方法,即

    @Module
    export default class UserModule extends VuexModule {
      countsNum = 2020
      get calculatCount() {
        return countsNum / 2
    
    export default {
      state: {
        countsNum: 2
      getters: {
        calculatCount: (state) => state.countsNum / 2
    

    Mutations

    @Mutation
    private SET_TOKEN(token: string) {
        this.token = token
    @Mutation
    
    mutations: {
        SET_TOKEN: (state, token) => {
            state.token = token
    两者的区别其实就是语法糖,原始的Mutation同步方法都是定义在mutations内,而ts版的每一个Mutation都要加上装饰器@Mutation修饰

    注意: 一旦使用@Mutation装饰某一函数后, 函数内的this上下文即指向当前的state,所以想引用state的值,可以直接this.token访问即可。

    Muation函数不可为async函数, 也不能使用箭头函数来定义, 因为在代码需要在运行重新绑定执行的上下文

    Action

    @Action
    public async Login(userInfo: { username: string, password: string}) {
        this.SET_TOKEN(data.accessToken)
    
    actions: {
        async Login({ commit }, data) {
            commit('SET_TOKEN', data.accessToken)
    异步函数Action和同步函数Mutation使用方法大同小异,区别就是一个是同步,一个是异步,只要做好区分即可

  • 如果需要在action函数中运行耗时很长的任务/函数, 建议将该任务定义为异步函数*(async methods)*
  • 千万不要使用箭头函数=>来定义action函数, 因为在运行时需要动态绑定this上下文
  • vuex+ts版的配置搭建成功,接下来我们把它运用到项目中来吧,这里抽一个登陆页面的模块做介绍

    import {
      VuexModule,
      Module,
      Action,
      Mutation,
      getModule
    } from 'vuex-module-decorators'
    import { login } from '@/api/users' //调用api方法
    import store from '@/store'
    //声明user模块的state变量类型
    //export interface 只是对一个东西的声明(不能具体的操作)
    //export class 导出一个类 类里面可有参数 ,函数,方法(干一些具体的事情)
    export interface IUserState {
      id_token: string
    @Module({ dynamic: true, store, name: 'user' })
    class User extends VuexModule implements IUserState {
      public id_token = ''
      @Mutation
      private SET_TOKEN(token: string) {
        //同步存储id_token变量
        this.id_token = token
      @Action
      public async Login(params: any) {
        let { mobilePhone, password } = params
        const { data } = await login({ mobilePhone, password })
        this.SET_TOKEN(`Bearer ${data.id_token}`)
    export const UserModule = getModule(User)
    

    login页面中调用

    import { UserModule } from '@/store/modules/user'
    await UserModule.Login({
      ...this.loginForm,
      router: this.$router
    

    把路由对象作为参数传过去是为了根据不同的响应状态做判断,当请求成功后,可以直接应用传过来的路由对象参数跳转页面。

    router.push('/')
    这一步操作其实是调用了vuexAction操作,即原始的this.$store.commit('action'),但是在vuex+ts项目中,调用异步函数Action,不需要再用this.$store.commit('action')这种方法,引用模块后,直接调用里面的Action方法就好了,同样的,同步的Mutation也是这样调用。这些都要归功于vuex-module-decorators类库的封装
    好了,调用Action后粗发Mutation同步操作,保存好token令牌,因为登录之后所有的请求都要把token值放在header头中发起请求
    除了vuex状态管理,在项目中可能我们还会结合工具类js-cookie一起使用,管理各种变量的值,具体用法跟原始版没有什么区别,最主要的是安装类库的过程中,还得安装一个开发ts编译版

    yarn add js-cookie // dependencies yarn add @types/js-cookie --dev // devDependencies(必装)
    

    这里使用的是vuex-modulevuex-class还是有不少区别,在下面的内容,使用的是vuex-class

    可能有人会有疑问,为什么介绍vuex-module,而使用vuex-class。。。 当初构建项目时,使用的是vuex-class,最近学习到vuex-module,就记录下。

    具体vuex-class的使用,文章最开始就把文档贴出来了,也可参考下窝的写法。

    vuex-class

    vuex-class 是一个基于 VueVuexvue-class-component 的库,和 vue-property-decorator 一样,它也提供了4 个修饰符以及 namespace,解决了 vuex.vue 文件中使用上的不便的问题。

  • @State
  • @Getter
  • @Mutation
  • @Action
  • namespace
  • 到了这里,就不再说明State,Getter...

    先来看看要完成的模块化管理的目录结构

    ├─ src/ │ ├─ store/ │ ├─── modules/ │ │ ├─ user.ts │ │ ├─ index.ts │ ├─── index.ts
    // ./store/modules/user.ts
    import { GetterTree, MutationTree, ActionTree } from 'vuex'
    interface LoginState {
      [key: string]: any
    const state: LoginState = {
      user_id: '1', // 用户id
      authority: 1, // 开户权限
      token: ''
    // 强制使用getter获取state
    const getters: GetterTree<LoginState, any> = {
      getUserId: (state: LoginState) => state.user_id,
      getToken: (state: LoginState) => state.token
    // 更改state
    const mutations: MutationTree<LoginState> = {
      // 更新state都用该方法
      UPDATE_STATE(state: LoginState, data: LoginState) {
        Object.keys(data).forEach((item) => {
          state[item] = data[item]
    const actions: ActionTree<LoginState, any> = {
      UPDATE_STATE_ASYN({ commit, state: LoginState }, data: LoginState) {
        commit('UPDATE_STATE', data)
    export default {
      namespaced: true,
      state,
      getters,
      mutations,
      actions
    
    // ./store/modules/index.ts
    import { ModuleTree } from 'vuex'
    const files: any = require.context('.', false, /\.ts$/)
    // 这里为了方便演示,重复写了一个接口。
    // 个人可放置到 * 目录下,来同一管理接口
    interface LoginState {
      [key: string]: any
    let modules: ModuleTree<any> = {}
    files.keys().forEach((key) => {
      if (key === './index.ts') {
        return
      modules[key.replace(/(\.\/|\.ts)/g, '')] = files(key).default
    export default modules
    

    整理好./store/modules/*下的文件,那么就该到'使用'登场啦。。。

    // ./store/index.ts
    import Vue from 'vue'
    import Vuex from 'vuex'
    import modules from './modules'
    Vue.use(Vuex)
    export default new Vuex.Store({
      modules
    

    我们在login.vue下使用康康

    import { State, Action, namespace } from 'vuex-class' // 在上面 ./store/modules/user.ts 并使用了命名空间,这里直接使用就行啦 const usreModel = namespace('user') @Component({}) export default class Login extends Vue { // 窝是这样使用 @usreModel.State((state) => state.user_id) user_id @usreModel.State((state) => state.authority) authority // 调用user/actions中的UPDATE_STATE_ASYN @usreModel.Action('UPDATE_STATE_ASYN') UPDATE_STATE_ASYN

    (窝是结束符)...

    在来康康官网vuex-class(参考)

    import Vue from 'vue'
    import Component from 'vue-class-component'
    import {
      State,
      Getter,
      Action,
      Mutation,
      namespace
    } from 'vuex-class'
    const someModule = namespace('path/to/module')
    @Component
    export class MyComp extends Vue {
      @State('foo') stateFoo
      @State(state => state.bar) stateBar
      @Getter('foo') getterFoo
      @Action('foo') actionFoo
      @Mutation('foo') mutationFoo
      @someModule.Getter('foo') moduleGetterFoo
      // If the argument is omitted, use the property name
      // for each state/getter/action/mutation type
      @State foo
      @Getter bar
      @Action baz
      @Mutation qux
      created () {
        this.stateFoo // -> store.state.foo
        this.stateBar // -> store.state.bar
        this.getterFoo // -> store.getters.foo
        this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true })
        this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true })
        this.moduleGetterFoo // -> store.getters['path/to/module/foo']
    

    这里再次强调,vuex-classvuex-module是两个不同的东西,切勿同时使用,避免人为bug。

    五、Mixins

    如果我们有大量的表格页面,仔细一扒拉你发现非常多的东西都是可以复用的例如分页,表格高度,加载方法, laoding声明等一大堆的东西。下面我们来整理出来一个简单通用混入index.js

    import { Provide, Vue } from 'vue-property-decorator'
    import Component from 'vue-class-component'
    // 这里使用的是vuex-class,与上面的vuex-module不同,请注意
    import { namespace } from 'vuex-class'
    import moment from 'moment'
    const usreModel = namespace('user')
    @Component
    export default class MyMixin extends Vue {
      @Provide() public loading: boolean = false
      @Provide() public form: any
      @Provide() public data: Array<any> = []
      @Provide() public pagination: any = {
        defaultPageSize: 6,
        showQuickJumper: true,
        hideOnSinglePage: false
      @usreModel.State(state => state.user_id) user_id
      @usreModel.State(state => state.authority) authority
      formatDate(value, format = 'YYYY-MM-DD HH:mm') {
        if (value) {
          return moment(value).format(format)
    

    mixins使用

    import Component, { mixins } from 'vue-class-component'
    import { Vue, Provide } from 'vue-property-decorator'
    import MyMixin from '@/mixins'
    @Component
    export default class Home extends mixins(MyMixin) {
      @Provide() private columns: Object = Columns
      @Provide() private search: string = ''
    

    这样就可以正常使用lodingform等数据方法等

    注意:全局mixins一定要慎用,如果不是必须要用的话我还是不建议使用。

    六、axios的封装

    vue项目中,和后台交互获取数据这块,我们通常使用的是axios库,它是基于promisehttp库,可运行在浏览器端和node.js中。他有很多优秀的特性,例如拦截请求和响应、取消请求、转换json、客户端防御XSRF等。所以我们的尤大大也是果断放弃了对其官方库vue-resource的维护,直接推荐我们使用axios库。如果还对axios不了解的,可以移步axios文档。

    npm install axios; // 安装axios
    

    一般我会在项目的src目录中,新建一个api文件夹,然后在里面新建一个api.ts和一个requestConfig.ts文件。api.ts文件用来封装我们的axiosrequestConfig.ts用来统一管理我们的接口。

    // src/api/api.ts
    import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'
    // config文件夹往后会出现,这里就不说明了
    import { MAINHOST, ISMOCK, QAHOST, conmomPrams } from '@/config' 
    // 接口
    import requestConfig from './requestConfig'
    // 获取存储在 cookies 的 token
    import { getToken, removeToken } from '@/utils/common'
    // 这里我使用了 antd ,大家根据自己的UI来使用
    import { message } from 'ant-design-vue'
    // 路由
    import router from '@/router'
    // 下面两个是加解密文件,因为用的是http,为了安全考虑,使用到这两个。(可忽略)
    import apiEncrypt from '@/utils/apiEncrypt'
    import apiDecrypt from '@/utils/apiDecrypt'
    declare type Methods = 'GET' | 'OPTIONS' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT'
    declare interface Datas {
      method?: Methods
      [key: string]: any
    // 根据环境,切换请求不同的url
    const baseURL = process.env.NODE_ENV === 'production' ? MAINHOST : QAHOST//QAHOST
    class HttpRequest {
      public queue: any // 请求的url集合
      public hide: any
      public constructor() {
        this.queue = {}
      destroy(url: string) {
        delete this.queue[url]
        if (!Object.keys(this.queue).length) {
          // 关闭loding
          setTimeout(this.hide, 0)
      interceptors(instance: any, url?: string) {
        // 请求拦截
        instance.interceptors.request.use(
          (config: AxiosRequestConfig) => {
            // 添加全局的loading...
            if (!Object.keys(this.queue).length) {
              // show loading
              this.hide = message.loading('加载中..', 0)
            if (url) {
              this.queue[url] = true
            return config
          (error: any) => {
            console.error(error)
        // 响应拦截
        instance.interceptors.response.use(
          (res: AxiosResponse) => {
            if (url) {
              this.destroy(url)
            let { data, status } = res
            data = apiDecrypt(data)
            if (status === 200 && ISMOCK) {
              return data.result
            } // 如果是mock数据,直接返回
            if (status === 200 && data && data.code === 200) {
              return data.result
            } // 请求成功
            res.data = data
            return requestFail(res) // 失败回调
          (error: any) => {
            if (url) {
              this.destroy(url)
            message.error('服务器出错')
            console.error(error)
      async request(options: AxiosRequestConfig) {
        const instance = axios.create()
        await this.interceptors(instance, options.url)
        return instance(options)
    // 请求失败
    const requestFail = (res: AxiosResponse) => {
      let errStr = '网络繁忙!'
      if (res.data.code) {
        switch (res.data.code) {
          // 401: 未登录
          // 未登录则跳转登录页面,并携带当前页面的路径
          // 在登录成功后返回当前页面,这一步需要在登录页操作。
          case 401:
            router.replace({
              path: '/'
            removeToken()
            break
          // 403 token过期
          // 登录过期对用户进行提示
          // 清除本地token和清空vuex中token对象
          // 跳转登录页面
          case 403:
            // 清除token
            // store.commit('loginSuccess', null);
            // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
            router.replace({
              path: '/'
            removeToken()
            // localStorage.removeItem('token')
            break
          // 404请求不存在
          case 404:
            break
      console.error({
        code: res.data.errcode || res.data.code,
        msg: res.data.errMsg || errStr
      if (typeof res.data.errMsg === 'object') {
        res.data.errMsg = '服务器错误'
      message.error(res.data.errMsg || errStr)
      return null
    // 合并axios参数
    const conbineOptions = (_opts: any, data: Datas, method: Methods): AxiosRequestConfig => {
      let opts = _opts
      if (typeof opts === 'string') {
        opts = { url: opts }
      const _data = { ...conmomPrams, ...opts.data, ...data }
      const options = {
        method: opts.method || data.method || method || 'GET',
        url: opts.url,
        headers: { Authorization: `Bearer${getToken()}` },// 这个需要与后端配合,让后端去除掉Bearer,加上这个是为了(安全考虑)
        baseURL,
        timeout: 10000
      const c = apiEncrypt(_data) // 加密数据
      return options.method !== 'GET' ? Object.assign(options, { data: c }) : Object.assign(options, { params: _data })
    const HTTP = new HttpRequest()
     * 抛出整个项目的api方法
    const Api = (() => {
      const apiObj: any = {}
      const requestList: any = requestConfig
      const fun = (opts: AxiosRequestConfig | string) => {
        return async (data = {}, method: Methods = 'POST') => {
          const newOpts = conbineOptions(opts, data, method)
          const res = await HTTP.request(newOpts)
          return res
      Object.keys(requestConfig).forEach((key) => {
        apiObj[key] = fun(requestList[key])
      return apiObj
    export default Api as any
    
    src/api/requestConfig
    export default {
      getData: '/mock/5e23f600df5e86413d7f1486/example/upload', // 随机数据 来自 easy mock
    
  • 如果定义了 .d.ts 文件,请重新启动服务让你的服务能够识别你定义的模块,并重启 vscode 让编辑器也能够识别(真的恶心)
  • 设置好你的 tsconfig ,比如记得把 strictPropertyInitialization 设为 false,不然你定义一个变量就必须给它一个初始值。
  • 千万管理好你的路由层级,不然到时连正则都拯救不了你 业务层面千万做好类型检测或者枚举定义,这样不仅便利了开发,还能在出了问题的时候迅速定位
  • 跨模块使用 vuex,请直接使用 rootGetters
  • 如果你需要改造某组件库主题,请单开一个文件进行集中管理,别一个组件分一个文件去改动,不然编译起来速度堪忧
  • 能够复用团队其他人开发好的东西,尽量别去开发第二遍,不然到时浪费的可能就不是单纯的开发时间,还有 code review的时间
  • vue 文件中 TS 上下文顺序

  • @Prop
  • @State
  • @Getter
  • @Action
  • @Mutation
  • @Watch
  • 生命周期钩子

  • beforeCreate(按照生命周期钩子从上到下)
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • activated
  • deactivated
  • beforeDestroy
  • destroyed
  • errorCaptured(最后一个生命周期钩子)
  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave
  • computed

    methods

    分享不易,喜欢的话一定别忘了点💖!!!

    只关注不点💖的都是耍流氓,只收藏也不点💖的也一样是耍流氓。

    结束👍👍👍。

    axios封装(戳窝)

    加速vue项目开发速度(戳窝)

    TypeScript + 大型项目实战(戳窝)

    Typescript+Vue大型后台管理系统实战(戳窝)

    vue-cli3.0 搭建项目模版教程(戳窝)

    分类:
    前端