Vue-cli3项目搭建优化


2019/2/11 frontend vue

自动注册组件

components 中创建 index.js,用来扫描全局组件对象并自动注册:

import Vue from 'vue'

// 自动加载 global 目录下的 .js 结尾的文件
const componentsContext = require.context('./global', true, /\.js$/)

componentsContext.keys().forEach(component => {
  const componentConfig = componentsContext(component)
  /**
   * 兼容 import export 和 require module.export 两种规范
   */
  const ctrl = componentConfig.default || componentConfig
  Vue.component(ctrl.name, ctrl)
})
1
2
3
4
5
6
7
8
9
10
11
12
13

最后在 main.js 中引入 import '@/components' 即可。

自动导入路由

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

let routes = []
cosnt routeContext = require.context('./', true, /index\.js$/)
routeContext.keys().forEach(route => {
  if (route.startsWith('./index')) return
  const routeModule = routeContext(route)
  routes = [...routes, ...(routeModule.default || routeModule)]
})

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: routes
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

配置路由权限

vue-router 提供了非常方便的钩子,可以让我们在做路由跳转的时候进行统一操作,比如常见的权限验证。

首先,需要在 src/utils 下创建 auth.js,用于存储 token

import Cookies from 'js-cookie'
const TokenKey = 'project-token'

export function getToken() {
  return Cookies.get(TokenKey)
}

export function setToken(token) {
  return Cookies.set(TokenKey, token)
}

export function removeToken() {
  return Cookies.remove(TokenKey)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

src/utils 下创建 permission.js

import store from '@/store'
import router from '@/router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from './auth'
import { Message } from 'element-ui'
// 路由白名单
const whiteList = ['/login']
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    if (to.path === '/login') {
      next({
        path: '/'
      })
      NProgress.done()
    } else {
      // 实时拉取用户的信息
      store
        .dispatch('GetUserInfo')
        .then(res => {
          next()
        })
        .catch(err => {
          store.dispatch('FedLogOut').then(() => {
            Message.error('拉取用户信息失败,请重新登录!' + err)
            next({
              path: '/'
            })
          })
        })
    }
  } else {
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next('/login')
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  NProgress.done()
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

封装 Axios

src 目录下创建 utils, 并创建 axios.js 用来封装 axios

import axios from 'axios'

const service = axios.create({
  baseURL: process.env.BASE_URL,
  timeout: 10000
})
// request 拦截器
service.interceptors.request.use(
  config => {
    const token = localStorage.getItem('token')
    if (token) {
      config.headers['Authorization'] = token
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)
// response 拦截器
service.interceptors.response.use(
  response => {
    const status = response.status
    if (status === 200) {
      return Promise.resolve(response.data)
    } else {
      return Promise.reject('error')
    }
  },
  error => {
    // 断网或请求超时
    if (!error.response) {
      if (error.message.includes('timeout')) {
        Message.error('请求超时,请检查网络是否连接正常')
      } else {
        Message.error('请求失败,请检查网络是否已连接')
      }
      return
    }

    const status = error.response.status
    switch (status) {
      // 未登录
      case 401:
        router.replace({
          path: '/login',
          query: {
            redirect: router.currentRoute.fullPath
          }
        })
        break
      // 登录过期
      case 403:
        Message({
          type: 'error',
          message: '登录信息过期,请重新登录'
        })

        localStorage.removeItem('token')
        setTimeout(() => {
          router.replace({
            path: '/login',
            query: {
              redirect: router.currentRoute.fullPath
            }
          })
        }, 1000)
        break
      // 请求错误
      case 404:
        Message({
          message: '网络请求错误',
          type: 'error'
        })
        break
      default:
        Message({
          message: error.response.data.message,
          type: 'error'
        })
    }
    return Promise.reject(error)
  }
)

export default service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

既然要使用 axios ,必不可少的需要配置环境变量以及需要请求的地址,这里可以简单的修改 poackage.json

"scripts": {
    "dev": "vue-cli-service serve --project-mode dev",
    "test": "vue-cli-service serve --project-mode test",
    "pro": "vue-cli-service serve --project-mode pro",
    "pre": "vue-cli-service serve --project-mode pre",
    "build:dev": "vue-cli-service build --project-mode dev",
    "build:test": "vue-cli-service build --project-mode test",
    "build:pro": "vue-cli-service build --project-mode pro",
    "build:pre": "vue-cli-service build --project-mode pre",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
1
2
3
4
5
6
7
8
9
10
11
12

同时修改 vue.config.js

const path = require('path')

function resolve(dir) {
  return path.join(__dirname, './', dir)
}

module.exports = {
  chainWebpack: config => {
    // 配置BASE_API
    config.plugin('define').tap(args => {
      const argv = process.argv
      const mode = argv[argv.indexOf('--project-mode') + 1]
      args[0]['process.env'].MODE = `"${mode}"`
      args[0]['process.env'].BASE_URL = 'http://192.168.1.187:8080'
      // args[0]['process.env'].BASE_URL = JSON.stringify(process.env.BASE_URL)
      return args
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

这样的好处是方便管理、后期维护,还可以和后端的微服务对应,建立多文件存放不同模块的 api,剩下的就是你使用到哪个 api 时,自己引入便可。

全局引用 SVG

首先在 src/components 下创建 SvgIcon.vue

<template>
  <svg :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconName"></use>
  </svg>
</template>

<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator'

@Component({
  name: 'SvgIcon'
})
export default class extends Vue {
  @Prop({ required: true }) private icon!: string
  @Prop({ default: '' }) private className!: string

  get iconName() {
    return `#icon-${this.icon}`
  }

  get svgClass() {
    if (this.className) {
      return 'svg-icon ' + this.className
    } else {
      return 'svg-icon'
    }
  }
}
</script>

<style lang="scss" scoped>
.svg-icon {
  overflow: hidden;
  fill: currentColor;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

src 下创建 icons 文件夹,并在内部创建 svg 文件夹用于存放 svg 图标,创建 index.js 作为入口文件:

import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon.vue'

Vue.component('svg-icon', SvgIcon)

const svgContext = require.context('./svg', false, /\.svg$/)
const svgIcon = context => context.keys().map(context)
svgIcon(svgContext)
1
2
3
4
5
6
7
8

通过 svg-sprite-loader 对项目中使用的 svg 进行处理:

const path = require('path')

function resolve(dir) {
  return path.join(__dirname, './', dir)
}

module.exports = {
  chainWebpack: config => {
    // 配置SVG
    config.module
      .rule('svg')
      .exclude.add(resolve('src/icons'))
      .end()
    config.module
      .rule('icons')
      .test(/\.svg$/)
      .include.add(resolve('src/icons'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      })
      .end()
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

最后在 main.js 中引入 import '@/icons' 即可。

全局 Loading 效果

平时写网络请求之前一般都需要开启 loading 效果,成功之后结束 loading 效果,这就会产生大量重复代码,我们可以结合 axiosvuex 统一处理。

首先,在 store/modules/app.js 里写 loading 效果:

const app = {
  state: {
    requestLoading: 0
  },
  mutations: {
    SET_LOADING: (state, status) => {
      if (status === 0) {
        state.requestLoading = 0
        return
      }
      state.requestLoading = status ? ++state.requestLoading : --state.requestLoading
    }
  },
  actions: {
    SetLoading({ commit }, status) {
      commit('SET_LOADING', status)
    }
  }
}

export default app
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

再来修改一下 utils/axios.js

import axios from 'axios'
import store from '@/store'

const service = axios.create({
  baseURL: process.env.BASE_URL,
  timeout: 10000
})
// request拦截器
service.interceptors.request.use(
  config => {
    store.dispatch('SetLoading', true)
    return config
  },
  error => {
    setTimeout(function() {
      store.dispatch('SetLoading', 0)
    }, 500)
    Promise.reject(error)
  }
)
// response拦截器
service.interceptors.response.use(
  response => {
    const res = response.data
    store.dispatch('SetLoading', false)
    return res
  },
  error => {
    store.dispatch('SetLoading', false)
    return Promise.reject(error)
  }
)

export default service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

最后在 src/components 下创建 RequestLoading.vue 组件:

<template>
  <transition name="fade-transform" mode="out-in">
    <div class="request-loading-component" v-if="requestLoading">
      <svg-icon icon-class="loading" />
    </div>
  </transition>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  name: 'RequestLoading',
  computed: {
    ...mapGetters([
      'requestLoading'
    ])
  }
}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
上次更新: 10/24/2019, 12:39:39 AM