哈嘍大家好,這裡是程式碼搬運工。第一次寫還挺緊張的呀。
首先我們安裝一個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.import
和import()
這兩個方法都可以做路由懶載入,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.js
、code.js
、constant.js
、url.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通用方法,並且定義兩個抽象環繞方法before
和after
以供每個子類實現(比如統一的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
並實現環繞before
和after
<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-decorators
和babel-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的改造結束。