公司作的大部分都是後臺管理項目,剔除每一個項目的業務邏輯,其實均可以用通用的一套模版來作。前端
每一個系統都有本身的登陸登出邏輯,而咱們前端所要作的實際上是請求後臺,拿到登陸權限,帶上登陸權限,獲取用戶信息和菜單信息。
在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
中定義了。segmentfault
以上都是後臺系統中能夠用到的一些處理方式,具體代碼可查看。
其餘總結文章: