為Vue-Cli添磚加瓦

569835014發表於2019-03-04

哈嘍大家好,這裡是程式碼搬運工。第一次寫還挺緊張的呀。

首先我們安裝一個vue-cli(不會的同學可以看這裡npm安裝vue

現在我們的目錄是這樣的(eslint我沒開):

為Vue-Cli添磚加瓦

然後裝好依賴啟動這個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>
複製程式碼

然後新建componentsaseloadingLoading.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.準備工作都做完了,我們現在測試一下

新建networkapiimp裡面新建一個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`)
})

複製程式碼

最後的效果就是

為Vue-Cli添磚加瓦

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()
複製程式碼

新建srcdecoratordecorator.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的改造結束。

相關文章