vue-cli3.0 搭建項目模版教程(ts+vuex+axios)

之前寫了一篇vue2.5+typescript+vuex+axios 搭建教程,隨著vue-cli3.0的發布,最近琢磨著重新寫一篇基于3.0搭建的教程,全面升級一下項目模版,趁著空閑期把demo搭建完了,今天特此寫一篇教程,也用于自己備忘和查閱。

Tip: 為了避免浪費您的時間,本文符合滿足以下條件的同學借鑒參考

1.本文模版不適用于小型項目,兩三個頁面的也沒必要用vue
2.對typescriptvue全家桶能夠掌握和運用

此次項目模版主要涉及的技術框架:
  1. vue2.5
  2. vuex3.0
  3. vue-router3.0
  4. axios
  5. typescript3.2

Tip: 由于vue-cli3.0幫我們簡化了webpack的配置,我們只需要在根目錄下的vue.config.js文件的chainWebpack進行配置即可。

接下來進入正題(前提是你已經安裝并配置好了node環境)

一.初始化項目

安裝vue-cli3.0

如果你之前安裝的是vue-cli2.0版本的,那么你需要先卸載2.0版本,再安裝3.0版本

// cnpm 為淘寶鏡像
cnpm uninstall vue-cli -g // 卸載
cnpm install -g @vue/cli // 安裝新版本
創建項目
vue create vue-cli3-tpl

選擇Manually select features回車,按照下圖所示選中(空格選中)回車安裝插件


然后一路回車,放一下配置圖
配置圖

等安裝完之后,進入項目并啟動項目

cd vue-cli3-tpl
cnpm run serve

啟動后顯示如下,第一步完成。


啟動成功顯示界面

二.刪除不必要的文件

1.刪除assetscomponentsviews目錄下的所有文件。
2.刪除./src/store.ts
3.刪除./src/router.ts

三.添加并配置文件

1.添加文件夾并創建文件

1.在根目錄下創建scripts文件夾,并添加template.jscomponent.js
2.在./src目錄下創建api文件夾
3.在./src目錄下創建config文件夾,并添加index.tsrequestConfig.ts
4.在./src目錄下創建router文件夾,并添加index.tsrouter.ts
5.在./src目錄下創建store文件夾,并添加index.tsmodule文件夾
6.在./src目錄下創建types文件夾,并添加index.tscomponents文件夾和views文件夾
7.在./src目錄下創建utils文件夾,并添加common.tsrequest.ts
8.在./src/assets目錄下創建imagesscss兩個文件夾,并在scss文件夾下添加common.scssvariables.scss

2.配置文件

首先配置一下根目錄下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的地方
  }
}

1.打開./scripts/template.js,并添加以下內容

/*
 * @Description: 頁面快速生成腳本
 * @Date: 2018-12-06 10:28:08
 * @LastEditTime: 2018-12-10 09:43:50
 */
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}}
  </div>
</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)

2.打開./scripts/component.js,并添加以下內容

/*
 * @Description: 組件快速生成腳本
 * @Date: 2018-12-06 10:26:23
 * @LastEditTime: 2018-12-10 09:44:19
 */

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.componentName}}
  </div>
</template>

<script lang="ts">
  import { Component, Vue, Prop } from "vue-property-decorator"
  import { Getter, Action } from 'vuex-class'
  import { ${capPirName}Data } from '@/types/components/${dirName}.interface'
  // import {  } from "@/components" // 組件

  @Component({})
  export default class About extends Vue {
    // prop
    @Prop({
      required: false,
      default: ''
    }) name!: string

    // data
    data: ${capPirName}Data = {
      componentName: '${dirName}'
    }

    created() {
      //
    }
    
    activated() {
      //
    }

    mounted() {
      //
    }

  }
</script>

<style lang="scss">
  @import "@/assets/scss/variables";

  .${dirName}-wrap {
    width: 100%;
  }
</style>

`

// interface 模版
const interfaceTep = `// ${dirName}.Data 參數類型
export interface ${capPirName}Data {
  componentName: string
}

`

fs.mkdirSync(`${basePath}/components/${dirName}`) // mkdir

process.chdir(`${basePath}/components/${dirName}`) // cd views
fs.writeFileSync(`${dirName}.vue`, VueTep) // vue 

process.chdir(`${basePath}/types/components`) // cd components
fs.writeFileSync(`${dirName}.interface.ts`, interfaceTep) // interface 

process.exit(0)

3.打開./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

4.打開./src/config/requestConfig.ts,并添加以下內容

export default {
  getData: '/mock/5c09ca373601b6783189502a/example/mock', // 隨機數據 來自 easy mock
}

5.打開./src/router/index.ts,并添加以下內容

import Vue from 'vue'
import Router from 'vue-router'
import routes from './router'
import { getToken } from '@/utils/common'

Vue.use(Router)

const router = new Router({
  routes,
  mode: 'history'
})

// 登陸頁面路由 name
const LOGIN_PAGE_NAME = 'login'

// 跳轉之前
router.beforeEach((to, from, next) => {
  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: 'index' // 跳轉到 index 頁
    })
  } else {
    if (token) {
      next() // 跳轉
    } else {
      next({
        name: LOGIN_PAGE_NAME
      })
    }
  }
})


// 跳轉之后
router.afterEach(to => {
  //
})

export default router

6.打開./src/router/router.ts,并添加以下內容

/**
 * meta 可配置參數
 * @param {boolean} icon 頁面icon
 * @param {boolean} keepAlive 是否緩存頁面
 * @param {string} title 頁面標題
 */
export default [
  {
    path: '/',
    redirect: '/index'
  },
  {
    path: '/login',
    name: 'login',
    component: () => import('@/views/login/login.vue'),
    meta: {
      icon: '',
      keepAlive: true,
      title: 'login'
    }
  },
  {
    path: '/index',
    name: 'index',
    component: () => import('@/views/index/index.vue'),
    meta: {
      icon: '',
      keepAlive: true,
      title: 'index'
    }
  }
]

7.打開./src/store/index.ts,并添加以下內容

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    //
  },
  mutations: {
    //
  },
  actions: {
    //
  },
  modules: {
    // 
  }
})

8.打開./src/utils/common.ts,在此之前先下載js-cookie,并添加以下內容

// 下載js-cookie
cnpm i js-cookie --S
cnpm install @types/js-cookie --D
/*
 * @Description: 公共函數
 * @Author: asheng
 * @Date: 2018-12-07 11:36:27
 * @LastEditors: asheng
 * @LastEditTime: 2018-12-12 13:37:30
 */

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

9.打開./src/utils/request.ts,先下載axios,并添加以下內容

// 下載axios
cnpm i axios --S
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'
import { MAINHOST, ISMOCK, conmomPrams } from '@/config'
import requestConfig from '@/config/requestConfig'
import { getToken } from '@/utils/common'
import router from '@/router'

declare type Methods = "GET" | "OPTIONS" | "HEAD" | "POST" | "PUT" | "DELETE" | "TRACE" | "CONNECT"
declare interface Datas {
  method?: Methods
  [key: string]: any
}
const baseURL = process.env.NODE_ENV === 'production' ? MAINHOST : location.origin
const token = getToken()

class HttpRequest {
  public queue: any // 請求的url集合
  public constructor() {
    this.queue = {}
  }
  destroy(url: string) {
    delete this.queue[url]
    if (!Object.keys(this.queue).length) {
      // hide loading
    }
  }
  interceptors(instance: any, url?: string) {
    // 請求攔截
    instance.interceptors.request.use((config: AxiosRequestConfig) => {
      // 添加全局的loading...
      if (!Object.keys(this.queue).length) {
        // show loading
      }
      if (url) {
        this.queue[url] = true
      }
      return config
    }, (error: any) => {
      console.error(error)
    })
    // 響應攔截
    instance.interceptors.response.use((res: AxiosResponse) => {
      if (url) {
        this.destroy(url)
      }
      const { data, status } = res
      if (status === 200 && ISMOCK) { return data } // 如果是mock數據,直接返回
      if (status === 200 && data && data.code === 0) { return data } // 請求成功
      return requestFail(res) // 失敗回調
    }, (error: any) => {
      if (url) {
        this.destroy(url)
      }
      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 = '網絡繁忙!'
  // token失效重新登陸
  if (res.data.code === 1000001) {
    return router.replace({ name: 'login' })
  }

  return {
    err: console.error({
      code: res.data.errcode || res.data.code,
      msg: res.data.errmsg || errStr
    })
  }
}

// 合并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,
    header: { 'user-token': token },
    baseURL
  }
  return options.method !== 'GET' ? Object.assign(options, { data: _data }) : 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 = "GET") => {
      if (!token) {
        console.error('No Token')
        return router.replace({ name: 'login' })
      }
      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

10.打開./src/main.ts,引用common.scss

import "@/assets/scss/common.scss"

11.打開./src/App.vue,按照如下修改(未貼代碼表示不變)

<template>
    <div id="app">
        <keep-alive>
            <router-view v-if="$route.meta.keepAlive"/>
        </keep-alive>
        <router-view v-if="!$route.meta.keepAlive"/>
    </div>
</template>

12.還有非常重要的一步,打開根目錄下的vue.config.js,如果沒有就自己創建一個,放在根目錄下,并添加以下內容

const path = require('path')

const resolve = dir => {
  return path.join(__dirname, dir)
}

// 線上打包路徑,請根據項目實際線上情況
const BASE_URL = process.env.NODE_ENV === 'production' ? '/' : '/'

module.exports = {
  baseUrl: BASE_URL,
  outputDir: 'dist', // 打包生成的生產環境構建文件的目錄
  assetsDir: '', // 放置生成的靜態資源路徑,默認在outputDir
  indexPath: 'index.html', // 指定生成的 index.html 輸入路徑,默認outputDir
  pages: undefined, // 構建多頁
  productionSourceMap: false, // 開啟 生產環境的 source map?
  chainWebpack: config => {
    // 配置路徑別名
    config.resolve.alias
      .set('@', resolve('src'))
      .set('_c', resolve('src/components'))
  },
  css: {
    modules: false, // 啟用 CSS modules
    extract: true, // 是否使用css分離插件
    sourceMap: false, // 開啟 CSS source maps?
    loaderOptions: {} // css預設器配置項
  },
  devServer: {
    port: 8080, // 端口
    proxy: 'https://www.easy-mock.com' // 設置代理
  }
}

13.打開根目錄下的package.json,在scripts中添加如下代碼

"scripts": {
    ...
    "tep": "node scripts/template",
    "com": "node scripts/component"
}

四.編寫業務代碼

1.編寫page頁

運行我們之前添加的腳本命令,創建page,也就是運行之前寫的template.js這個腳本,實現快速創建我們所需要的page模版,而不需要一個一個的再創建,大大節省了時間,如果不用用腳本跑也是可以的,分別需要創建以下文件夾:

  • views文件夾下創建login文件夾,再向login文件夾下添加login.vuelogin.tslogin.scss
  • ./src/api下添加login.ts
  • ./src/store/module下添加login.ts
  • ./src/types/views下添加login.interface.ts

是不是非常繁瑣,還可能搞錯(不推薦,浪費時間 0.0),而使用腳本只需要在命令行敲一條命令搞定(推薦)如下(根據demo需求,我們創建兩個頁面index、login):

cnpm run tep index
cnpm run tep login

打開./src/views/login/login.ts,發現報錯,沒有安裝模塊vuex-class,安裝一下就好了

 cnpm i vuex-class --S

再運行創建組件腳本,隨意創建一個test組件

cnpm run com test

ok,這時候發現./src/components目錄下創建了test組件,為了引用組件更方便看起來更優雅,我們在./src/components目錄下添加一個index.ts,把所有組件都引入到這里,作為一個中轉文件,如下:

import Test from './test/test.vue'

export {
  Test
}
引用組件

上面創建好了組件后,打開./src/views/login/login.ts,如下引用:

import { Test } from "@/components" // 組件

@Component({
    components: {
        Test
    }
})

./src/views/login/login.vue中添加組件

<template>
    <div class="login-wrap">
        {{data.pageName}}
        <div>
            <Test></Test>
        </div>
    </div>
</template>

這時候頁面顯示為如下:


組件示意圖

調用http請求示例

最后再說一下怎么調用http請求吧,在這之前,先重啟一遍服務

cnpm run serve

按照我的步驟來,啟動是不會報錯的,如果報錯,那么可以留言看到會回復,或者重新走一遍。
沒問題的話,我們按照如下步驟:
1.打開./src/config/requestConfig.ts文件添加接口,由于我們之前的添加過了,那么我們進行下一步。
2.打開./src/api/login.ts文件添加請求函數,我們之前也寫好了,跳過。
3.進入./src/store/module/login.ts文件,把GET_DATA_ASYN函數的注釋打開。
4.在./src/store/index.ts中的module添加login,如下:

// modules
import Login from './module/login'
import Index from './module/index'

export default new Vuex.Store({
  ...
  modules: {
    Login,
    Index
  }
}

完成上述動作后就可以在任意頁面調用了,我們打開./src/views/index/index.ts,如下調用:

export default class About extends Vue {
  @Action GET_DATA_ASYN

  created() {
    this.GET_DATA_ASYN()
  }
}

到現在有沒有發現不管怎么樣路由都到不了index頁面,是因為做了路由攔截,我們在cookies添加一個token,如下:

token

添加完之后,刷新就可以正常跳轉到index了,(順帶一句,對于需要登錄的項目路由攔截是很有必要的),這時候就可以發現在network里面發現我們的接口請求成功了,如下:
接口請求成功示意圖

篇幅比較長,終于寫完了,有問題大家可以提,有更好的優化方法歡迎提出來,共同進步。

END

github源碼地址:https://github.com/chenfangsheng/vue-cli3-tpl.git

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容