公司作的大部分都是後臺管理項目,剔除每一個項目的業務邏輯,其實均可以用通用的一套模版來作。前端
每一個系統都有本身的登陸登出邏輯,而咱們前端所要作的實際上是請求後臺,拿到登陸權限,帶上登陸權限,獲取用戶信息和菜單信息。 在vue
項目開發當中,咱們通常都是在全局路由鉤子作這一系列判斷。vue
router.beforeEach(async(to, from, next) => {
NProgress.start();
await store.dispatch('SetConfigApi'); // 獲取配置
await store.dispatch('SetApi'); // 設置基本配置
const token = await store.dispatch('getToken'); // 獲取token
if (token) {
// 用戶信息不存在
if (!store.getters.userInfo) {
await store.dispatch('GetUser'); // 獲取用戶信息
const menuList = await store.dispatch('GetMenu', localRoute); // 獲取菜單
await store.dispatch('GenerateRoutes', localRoute);
router.addRoutes(store.getters.addRoutes);
...
} else {
next();
}
} else {
if (whiteList.includes(to.path)) {
// 在免登陸白名單,直接進入
next();
} else {
window.location.href = store.getters.api.IPORTAL_LOCAL_API;
NProgress.done();
}
}
});
複製代碼
當用戶進入系統的時候,先獲取系統的配置信息,這個配置信息能夠是前端json
文件,或者是後臺接口;用這種方式能夠靈活的修改項目中的配置,而不用每次都打包死進入項目,直接能夠要運維童靴修改對應的配置信息,就能夠了。webpack
之前的菜單路由是直接寫死在前端,可是當咱們直接訪問這個路由時,用戶仍是能夠進入到這個功能頁面;後來直接改爲動態添加路由的方式router.addRoutes
。ios
具體可查看git
項目請求是使用的axios
,能夠對它添加攔截器來處理咱們的請求,也能夠處理經過axios.CancelToken
重複請求,具體可看代碼:github
// 設置請求統一信息
import axios from 'axios';
import store from '@/store/index.js';
import qs from 'qs';
import { messages } from './msg-box.js';
const service = axios.create({
timeout: 300000, // 超時設置
withCredentials: true // 跨域請求
});
let hasLogoutStatus = false; // 是否某個請求存在須要退出的狀態
const queue = []; // 請求隊列
const CancelToken = axios.CancelToken; // axios內置的中斷方法
/**
* 拼接請求的url和方法;
* 一樣的`url + method` 能夠視爲相同的請求
* @param {Object} config 請求頭對象
*/
const token = config => {
return `${config.url}_${config.method}`;
};
/**
* 中斷重複的請求,並從隊列中移除
* @param {Object} config 請求頭對象
*/
const removeQueue = config => {
for (let i = 0, size = queue.length; i < size; i++) {
const task = queue[i];
if (!task) return;
// 出現401,403狀態碼中斷後續請求
const isLogout = token(config).includes('logout');
// 退出接口跳過中斷邏輯
if (!isLogout && hasLogoutStatus) {
task.token();
queue.splice(i, 1);
} else {
const cancelMethods = ['post', 'put', 'delete']; // 須要中斷的請求方式
const { method } = config;
if (cancelMethods.includes(method)) {
if (task.token === token(config)) {
task.cancel();
queue.splice(i, 1);
}
}
}
}
};
/**
* 請求錯誤統一處理
* @param {Object} response 錯誤對象
*/
const errorHandle = response => {
// eslint-disable-next-line prettier/prettier
const { status, data: { message = '' }} = response;
let msg = message;
if (!message) {
switch (status) {
case 401:
msg = '您沒有權限訪問此操做!';
break;
case 403:
msg = '您的登陸狀態已失效,請從新登陸。';
break;
case 424:
msg = response.data.error;
break;
default:
msg = '服務請求異常,請刷新重試。';
}
}
hasLogoutStatus = status === 401 || status === 403;
if (hasLogoutStatus) {
messages('error', msg, () => {
store.dispatch('Logout');
});
}
messages('error', msg);
};
// 請求攔截器
service.interceptors.request.use(
config => {
// 中斷以前的同名請求
removeQueue(config);
// 添加cancelToken
config.cancelToken = new CancelToken(c => {
queue.push({ token: token(config), cancel: c });
});
// 登陸後添加token
if (store.getters.token) {
config.headers['Authorization'] =
store.getters.token.token_type + ' ' + store.getters.token.access_token;
}
return config;
},
error => {
return Promise.reject(error);
}
);
// 響應攔截器
service.interceptors.response.use(
response => {
// 在請求完成後,自動移出隊列
removeQueue(response.config);
// 關閉全局按鈕Loading響應
store.dispatch('CancalLoading');
// 錯誤碼處理
if (response.status !== 200) {
return Promise.reject(response);
}
return response;
},
error => {
const { response } = error;
if (response) {
// 錯誤處理
errorHandle(response);
return Promise.reject(response);
} else {
// 請求超時
if (error.message.includes('timeout')) {
console.log('超時了');
messages('error', '請求已超時,請刷新或檢查互聯網鏈接');
} else {
// 斷網,能夠展現斷網組件
console.log('斷網了');
messages('error', '請檢查網絡是否已鏈接');
}
}
}
);
export default {
get: (url, data = {}) => {
return new Promise((resolve, reject) => {
service
.get(store.getters.api.API + url, { params: data })
.then(response => {
resolve(response.data);
})
.catch(error => {
reject(error);
});
}).catch(error => {
throw new Error(error);
});
},
post: (url, data = {}) => {
return new Promise((resolve, reject) => {
service
.post(store.getters.api.API + url, data, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
},
withCredentials: true,
transformRequest: [
data => {
return qs.stringify(data);
}
]
})
.then(response => {
resolve(response.data);
})
.catch(error => {
reject(error);
});
}).catch(error => {
return Promise.reject(error);
});
},
...
/**
* blob下載
* @param {String} url 請求地址
* @param {String} method 請求方式 默認`get`
* @param {Object} data 請求數據
*/
exportFile({ url = '', data = {}, method = 'get' }) {
return new Promise((resolve, reject) => {
const isPost =
method.toLocaleUpperCase() === 'POST'
? {
headers: { 'Content-Type': 'application/json' },
data
}
: {
params: data
};
const downConfig = {
withCredentials: true,
responseType: 'blob',
...isPost
};
service
// eslint-disable-next-line no-unexpected-multiline
[method](store.getters.api.API + url, downConfig)
.then(response => {
resolve(response);
})
.catch(error => {
reject(error);
});
}).catch(error => {
return Promise.reject(error);
});
}
};
複製代碼
當須要使用請求時,能夠引用文件http.js
,也能夠掛在到vue
原型上,在組件內使用this.$http
web
// user.js
import http from '@/utils/http.js';
export function getUser() {
return http.get('/user');
}
// main.js
Vue.prototype.$http = http;
複製代碼
按鈕的loading
效果能夠處理後臺響應時間有點長場景,這裏使用store
封裝了下處理方式。json
// loading.js
import Vue from 'vue';
const loading = {
state: {},
mutations: {
SET_LOADING: (state, data) => {
const isObject =
Object.prototype.toString.call(data) === '[object Object]';
if (!isObject) return;
Object.keys(data).forEach(key => {
Vue.set(state, key, data[key]);
});
},
CANCAL_LOADING: state => {
Object.keys(state).forEach(key => {
Vue.delete(state, key);
});
}
},
actions: {
SetLoading({ commit }, data) {
commit('SET_LOADING', data);
},
CancalLoading({ commit }, data) {
commit('CANCAL_LOADING', data);
}
}
};
export default loading;
// http.js
service.interceptors.response.use(
response => {
// 關閉全局按鈕Loading響應
store.dispatch('CancalLoading');
...
})
複製代碼
在組件內定義axios
<el-button :loading="btn.save" @click="handleClick">保存</el-button>
computed: {
btn() {
return this.$store.state.loading;
}
}
methods: {
handleClick() {
this.$store.dispatch('SetLoading', { save: true });
}
}
複製代碼
以上就能夠完美的使用loading
,而不用每一個都在data
中定義了。api
以上都是後臺系統中能夠用到的一些處理方式,具體代碼可查看。
其餘總結文章: