为Vue-Cli添砖加瓦

2,299 阅读4分钟

哈喽大家好,这里是代码搬运工。第一次写还挺紧张的呀。

首先我们安装一个vue-cli(不会的同学可以看这里npm安装vue

现在我们的目录是这样的(eslint我没开):

然后装好依赖启动这个cli

1.路由懒加载

在router文件夹新建一个asyncload.js 代码如下:

export default function (url) {
  return () => System.import(`@/${url}`)
}
export const asyncImport = (url) => {
  return () => import(`@/${url}`)
}

这里导出两个懒加载的方法System.importimport()这两个方法都可以做路由懒加载,System在webpack2.0文档中说明已经废弃 但是到现在还是能用的,import是vue-router官方推荐的方法,同学们可以自由选择。当然import()还需要一个babel插件syntax-dynamic-import,请安装babel-plugin-syntax-dynamic-import并修改.babelrc plugins里加入syntax-dynamic-import 修改router为懒加载的方式

import Vue from 'vue'
import Router from 'vue-router'
import asyncLoad,{asyncImport} from './asyncload'
Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      // component: asyncImport('components/HelloWorld.vue') //两种方式都可以
      component: asyncLoad('components/HelloWorld.vue')
    }
  ]
})

重新启动cli一切正常我们的懒加载已经成功

2.开发环境接口代理

在config文件夹的index文件proxyTable属性上加入下面代码

 proxyTable: {
      '/api': {
        target: 'http://localhost:3000', // 接口的域名
        // secure: false,  // 如果是https接口,需要配置这个参数
        changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
        pathRewrite: {
          '^/api': ''
        }
      }
    }

这样我们就把接口代理的配置弄好了,然后在src目录下新建common文件夹,这这个文件夹下新建baseurl.jscode.jsconstant.jsurl.js这4个配置的js文化
baseurl.js:ajax的基础路径

//开发环境添加/api前缀
export default process.env.NODE_ENV === 'development' ? '/api' : '' 

code.js:ajax状态码

//同学们可以和后台协商添加上自己的
const SUCCESS = ['S0000','S0001']
const ABNORMAL = ['A0000']
const LOGIN_OUT=['U0000']
const ERROR = ['E0000']
export {SUCCESS, ABNORMAL, ERROR,LOGIN_OUT}

constant.js:vuex用的

const USER_INFO = 'USER_INFO'
const LOADING='LOADING'
export {USER_INFO,LOADING}

url.js:后台接口路径统一在这里管理

//假如有个登录请求
const LOGIN_URL='/login'
export {
  LOGIN_URL
}

好了所有的配置工作都完成了

3.axios2次封装和统一ajax异常处理

在src目录下新建文件夹network在network新建api文件夹在api文件夹下新建BaseApi.js用来接管axios代码如下:

import axios from 'axios'
import Qs from 'qs'
import BASE_URL from '../../common/config/baseurl'
class BaseApi {
  static isinIt=false;
  constructor () {
    this.createAxios();
    this.initNotice()
  }
  createAxios () {
    if (BaseApi.isinIt) {
      return this.axios=BaseApi.isinIt
    }
    let api = axios.create({
      // 请求的接口,在请求的时候,如axios.get(url,config);这里的url会覆盖掉config中的url
      url: '',
      // 请求方法同上
      method: 'post', // default
      // 基础url前缀
      baseURL: BASE_URL,//baseurl.js里面定义的前缀
      transformRequest: [function (data) {
        // 这里可以在发送请求之前对请求数据做处理,比如form-data格式化等,这里可以使用开头引入的Qs(这个模块在安装axios的时候就已经安装了,不需要另外安装)
        data = Qs.stringify(data)
        return data
      }],
      // paramsSerializer: function(params) {
      //
      // },
      transformResponse: [function (data) {
        // 这里提前处理返回的数据
        try {
          return JSON.parse(data)
        } catch (e) {
          return data
        }
      }],

      // 请求头信息
      headers: {

      },

      // parameter参数
      params: {
      },

      // post参数,使用axios.post(url,{},config);如果没有额外的也必须要用一个空对象,否则会报错
      data: {
      },
      // 设置超时时间
      timeout: 5000,
      // 返回数据类型
      responseType: 'json', // default
    })
    api.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
    api.interceptors.request.use(
      (config) => {
        return this.request(config)
      }, (err) => {
        return this.reject(err)
      })
    api.interceptors.response.use(
      (response) => {
        return this.response(response)
      },
      (error) => {
        return this.reject(error)
      }
    )
    BaseApi.isinIt=this.axios = api
  }
  request () {
    throw Error('必须实现request函数!!!')
  }
  response () {
    throw Error('必须实现response函数!!!')
  }
  reject () {
    throw Error('必须实现reject函数!!!')
  }
  initNotice () {
    throw Error('必须实现通知函数!!!')
  }
}
export default BaseApi

解释一下BaseApi是一个抽象类,它使用静态属性保证axios值初始化一次,并接管所有的拦截器方法和初始化一个通知方法,这个类不实现这些方法,把实现的任务交给子类,这样可保证扩展性。剩下的我们就完成一个BaseApi的子类来实现这些方法,所以我们新建一个Api.js

import BaseApi from './BaseApi'
import {ABNORMAL, LOGIN_OUT, SUCCESS} from '../../common/config/code'
import {Notice} from 'iview'

class Api extends BaseApi {
  constructor() {
    super()
  }

  initNotice() {
    this.Notice = Notice
    // dosomething
  }

  request(config) {
    return config;
  }

  response(response) {
    return response;
  }

  reject(error) {
    console.error(error)
  }

  //用户未登录
  loginOut() {
    App.$router.push({
      name: 'login'
    })
  }

  before() {
  }

  after() {
  }

  abnormal(param, res) {
    this.showNotice(param, res, '温馨提示', 'warning')
  }

  error(param, res) {
    this.showNotice(param, res, '不好了', 'error')
  }

  showNotice(param, res, title, type = 'info') {
    this.Notice[type]({
      title,
      render: param.render ? param.render(...res) : h => {
        return h('span', [
          res.message,
        ])
      }
    })
  }

  async common(param) {
    let _config = Object.assign({}, param)
    await this.before()
    let res;
    try {
      let result = await this.axios(param.url, _config)
      res = (result && result.data) ? result.data : null;

      if (!res.data||!res.state || ABNORMAL.includes(res.state)) {
        param.abnormal ? param.abnormal(param, res) : this.abnormal(param, res)
      } else if (LOGIN_OUT.includes(res.state)) {
        this.loginOut();
      } else if (SUCCESS.includes(res.state)) {
        (param.successNotice) ? this.showNotice(param, res, '恭喜你', 'success') : '';
        param.success ? param.success(res) : ''
      } else {
        param.error ? param.error(res, param) : this.error(param, {message: "程序在开小差"})
      }
    } catch (e) {
      console.error(e);
      param.error ? param.error(res, param) : this.error(param, {message: "程序在开小差"})
    }
    await this.after()
    return res
  }
}
export default Api

Api.js的主要工作就是完成BaseApi.js的抽象方法并实现一个common的ajax通用方法,并且定义两个抽象环绕方法beforeafter以供每个子类实现(比如统一的loading),并且在根据后台的状态码返回对应的方法。common方法接受一个参数params里面除了axios需要的参数外还有successNotice(成功的时候是否显示通知)、error(ajax失败的时候调用的方法)、abnormal(ajax出现异常的是调用的方法)、success(请求成功的时候调用的方法)这些方法都会覆盖子类的配置(参数配置优先)这样我们就完成了axios二次封装和统一ajax异常处理

4.全局loading

在做全局loading之前我们先把vuex集成进来(关于vuex的配置我就不贴了有心去的可以去看看我的配置vuex配置),在state里面新建一个loading的状态。

<template>
  <section class="mark">
      <div class="loader"></div>
  </section>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  .mark{
    top:0;
    position: fixed;
    height: 100vh;
    width: 100vw;
    background: white;
  }
  .loader {
    position: relative;
    width: 2.5em;
    height: 2.5em;
    transform: rotate(165deg);
  }
  .loader:before, .loader:after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    display: block;
    width: 0.5em;
    height: 0.5em;
    border-radius: 0.25em;
    transform: translate(-50%, -50%);
  }
  .loader:before {
    animation: before 2s infinite;
  }
  .loader:after {
    animation: after 2s infinite;
  }

  @keyframes before {
    0% {
      width: 0.5em;
      box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75), -1em 0.5em rgba(111, 202, 220, 0.75);
    }
    35% {
      width: 2.5em;
      box-shadow: 0 -0.5em rgba(225, 20, 98, 0.75), 0 0.5em rgba(111, 202, 220, 0.75);
    }
    70% {
      width: 0.5em;
      box-shadow: -1em -0.5em rgba(225, 20, 98, 0.75), 1em 0.5em rgba(111, 202, 220, 0.75);
    }
    100% {
      box-shadow: 1em -0.5em rgba(225, 20, 98, 0.75), -1em 0.5em rgba(111, 202, 220, 0.75);
    }
  }
  @keyframes after {
    0% {
      height: 0.5em;
      box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75), -0.5em -1em rgba(233, 169, 32, 0.75);
    }
    35% {
      height: 2.5em;
      box-shadow: 0.5em 0 rgba(61, 184, 143, 0.75), -0.5em 0 rgba(233, 169, 32, 0.75);
    }
    70% {
      height: 0.5em;
      box-shadow: 0.5em -1em rgba(61, 184, 143, 0.75), -0.5em 1em rgba(233, 169, 32, 0.75);
    }
    100% {
      box-shadow: 0.5em 1em rgba(61, 184, 143, 0.75), -0.5em -1em rgba(233, 169, 32, 0.75);
    }
  }

  .loader {
    position: absolute;
    top: calc(50% - 1.25em);
    left: calc(50% - 1.25em);
  }
</style>

</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

然后新建components\base\loading\Loading.vue然后我们在App.vue引入然后和路由同级

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <router-view v-if="!loading"/>
    <loading v-else></loading>
  </div>
</template>

<script>
import {mapGetters} from 'vuex'
import Loading from '@/components/base/loading/Loading.vue'
export default {
  name: 'App',
  computed:{
    ...mapGetters([
      'loading'
    ])
  },
  components:{
    Loading
  }
}

</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

5.准备工作都做完了,我们现在测试一下

新建network\api\imp\里面新建一个api实现类UserApi.js继承与Api.js并实现环绕beforeafter

<template>
  <div class="hello">
    这里是模拟的axios测试页账号是admin123密码是111111
    这是login页<br>
    <div v-if="!user">
      账号<input type="text" v-model="userName"><br>
      密码<input type="text" v-model="pwd"><br>
      <button @click="login">登录</button>
      <button @click="login3">带通知的登录</button>
    </div>
    <div v-else>
      <button @click="out">退出登录</button>
    </div>
    <div>
      展示异常和错误处理
      <button @click="login1">异常通用</button>
      <button @click="login2">自定义异常</button>
    </div>
  </div>

</template>

<script>

  import UserApi from '@/network/api/imp/UserApi.js'
  export default {
    name: 'Login',
    data () {
      return {
        user:null,
        userName:"",
        pwd:"",
      }
    },
    async created(){
    },
    mounted(){


    },
    methods:{

      async login(){
        //既可以等待api执行完获得数据
        const data=await UserApi.login({
          data:{
            userName:this.userName||'admin',
            password:this.pwd||'111111',
          },
          //也可以在回调函数里面获得数据
          success:(res)=>{
          }
        })
        console.info(data)
      },
      async login1(){
      await UserApi.login({
          data:{
            userName:this.userName||'admin2',
            password:this.pwd||'111111',
          }
        })
      },
      async login2(){
        await UserApi.login({
          data:{
            userName:this.userName||'admin1',
            password:this.pwd||'111111',
          },
          abnormal(){
            alert('我是自定义的异常处理')
          }
        })
      },
      async login3(){
        await UserApi.login({
          data:{
            userName:this.userName||'admin',
            password:this.pwd||'111111',
          },
          successNotice:true
        })
      },
    },
  }
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  input{
    border: 1px solid black;
  }
  h1, h2 {
    font-weight: normal;
  }
  ul {
    list-style-type: none;
    padding: 0;
  }
  li {
    display: inline-block;
    margin: 0 10px;
    list-style: none;
  }
  a {
    color: #42b983;
  }
</style>

并在根目录下新建一个测试服务器serverk开启一个测试服务器

const Koa = require('koa2')
const app = new Koa()
const Router = require('koa-router')
const  bodyParser = require('koa-bodyparser');
let router = new Router()
const main = Context => {
  let {userName,password}=Context.request.body
  if(userName==='admin'&&password==='111111'){
    return Context.body={
      data:{
        token:'57af5b10-3a76-11e5-922a-75f42afeee38',
        name:'代码搬运工',
        userName,
      },
      state:'S0001',

      message:'登录成功'
    }
  }else if(userName!=='admin'){
    return Context.body={
      data:{},
      state:'A0001',
      message:'用户名不正确'
    }
  }else if(password!=='111111'){
    return Context.body={
      data:{},
      state:'A0001',
      message:'密码不正确'
    }
  }

};
// router.post('/login', main)
app.use(bodyParser());
router.post('/login', main)

app.use(router.routes())
app.use(router.allowedMethods())

app.listen(3000, () => {
  console.log('[demo] route-use-middleware is starting at port 3000')
})

最后的效果就是

6.装饰器模式

到这一步vue-cli的改造基本上是完了,但是还是缺了点什么我们可以利用装饰器对api的实现层就行进一步改造我们先安装以下装饰的依赖并把babel-plugin-transform-decoratorsbabel-plugin-transform-decorators-legacy,并在.babelrc plugins里面引入"plugins": ["transform-vue-jsx", "transform-runtime","syntax-dynamic-import","transform-decorators-legacy"],,对Api进行改造

import BaseApi from './BaseApi'
import {ABNORMAL,LOGIN_OUT,SUCCESS} from '../../common/config/code'
import {Notice} from 'iview'
import {symbolContext} from '../../decorator/decorator'
class Api extends BaseApi {
  constructor (target) {
    super()
    if(target){
      this.context.call(this,target)
    }

  }
  //由于装饰得到的是Api这个类而不是实例我们需要一些特殊的方法来实现
  context(target){
    target.prototype[symbolContext]=this
  }
  initNotice () {
    this.Notice=Notice
    // dosomething
  }
  request (config) {
    return config;
  }
  response (response) {
    return response;
  }
  reject (error) {
    console.error(error)
  }
  //用户未登录
  loginOut(){
    App.$router.push({
      name:'login'
    })
  }
  before () {}
  after () {}
  abnormal (param,res) {
    this.showNotice(param,res,'温馨提示','warning')
  }
  error (param,res) {
    this.showNotice(param,res,'不好了','error')
  }
  showNotice(param,res,title,type='info'){
    this.Notice[type]({
      title,
      render:param.render?param.render(...res):h=>{
        return h('span', [
          res.message,
        ])
      }
    })
  }
  async common (param) {
    console.info(param)
    let _config = Object.assign({}, param)
    await this.before()
    let res;
    try {
      res = await this.axios(param.url, _config)
      res=(res&&res.data)?res.data:null;

      if (!res.data||!res.state || ABNORMAL.includes(res.state)) {

        param.abnormal ? param.abnormal(param,res) : this.abnormal(param,res)
      }else if (LOGIN_OUT.includes(res.state)){
        this.loginOut();
      } else if(SUCCESS.includes(res.state)){
        (param.successNotice)?this.showNotice(param,res,'恭喜你','success'):'';
        param.success?param.success(res):''
      }else{
        param.error ? param.error(res,param) : this.error(param,{message:"程序在开小差"})
      }
    } catch (e) {
      console.error(e);
      param.error ? param.error(res,param) : this.error(param,{message:"程序在开小差"})
    }
    await this.after()
    return res
  }
}
export default Api

UserApi.js进行改造

import Api from '../Api'
import {controller,post,get} from "../../../decorator/decorator";
import {LOGIN_URL} from '../../../common/config/url'
@controller('')
class UserApi extends Api{
  constructor(){
    super(UserApi);
  }

  before(){
   App.$store.dispatch('changeLoading',true)

  }
  after(){
    return new Promise(resolve=>{
      setTimeout(()=>{
        resolve( App.$store.dispatch('changeLoading',false))
      },2000)
    })

  }
  @post(LOGIN_URL,true)
  async login(params){
    return await this.common(params)
  }

}
export {UserApi}
export default new UserApi()

新建src\decorator\decorator.js

export const symbolPrefix = Symbol('prefix')
export const symbolContext = Symbol('context');
export function controller(path) {
  return (target)=>{
    target.prototype[symbolPrefix] =path;
    target.prototype[symbolContext] =null;
  }
}
function baseMethods(target, key, descriptor,name,path,successNotice) {
  let method = descriptor.value;
  descriptor.value =  async (arg)=>{
    arg.successNotice=successNotice
    arg.url = target[symbolPrefix]?target[symbolPrefix]+path:path;
    arg.method=name;
    return await method.call(target[symbolContext],arg)
  }
}
export function get(path,successNotice) {
  return function (target, key, descriptor) {
    baseMethods(target, key, descriptor,'get',path,successNotice)
  }
}
export function post(path,successNotice) {
  return function (target, key, descriptor) {
    baseMethods(target, key, descriptor,'post',path,successNotice)
  }
}

controller为api的路径前缀,post为ajax为axios的发送方式,里面接受两个参数ajax路径和是否在成功的时候显示通知,改造后在测试哦一切如常完美。对vue-cli的改造结束。