【Vue项目总结】后台管理项目总结

栏目: JavaScript · 发布时间: 5年前

内容简介:公司做的大部分都是后台管理项目,剔除每个项目的业务逻辑,其实都可以用通用的一套模版来做。每个系统都有自己的登录登出逻辑,而我们前端所要做的其实是请求后台,拿到登录权限,带上登录权限,获取用户信息和菜单信息。在

公司做的大部分都是后台管理项目,剔除每个项目的业务逻辑,其实都可以用通用的一套模版来做。

登录逻辑

每个系统都有自己的登录登出逻辑,而我们前端所要做的其实是请求后台,拿到登录权限,带上登录权限,获取用户信息和菜单信息。

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 文件,或者是后台接口;用这种方式可以灵活的修改项目中的配置,而不用每次都打包死进入项目,直接可以要运维童靴修改对应的配置信息,就可以了。

菜单权限

以前的菜单路由是直接写死在前端,但是当我们直接访问这个路由时,用户还是可以进入到这个功能页面;后来直接改成动态添加路由的方式 router.addRoutes

  1. 前端先获取菜单列表
  2. 根据获取的菜单列表循环添加用户菜单路由集合
  3. 动态添加路由

具体可 查看

请求方案

项目请求是使用的 axios ,可以对它添加拦截器来处理我们的请求,也可以处理通过 axios.CancelToken 重复请求,具体可看代码:

// 设置请求统一信息
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

// user.js
import http from '@/utils/http.js';

export function getUser() {
  return http.get('/user');
}

// main.js
Vue.prototype.$http = http;

按钮Loading处理

按钮的 loading 效果可以处理后台响应时间有点长场景,这里使用 store 封装了下处理方式。

// 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');
    ...
})

在组件内定义

<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 中定义了。

总结

以上都是后台系统中可以用到的一些处理方式,具体代码可 查看


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Development Recipes

Web Development Recipes

Brian P. Hogan、Chris Warren、Mike Weber、Chris Johnson、Aaron Godin / Pragmatic Bookshelf / 2012-1-22 / USD 35.00

You'll see a full spectrum of cutting-edge web development techniques, from UI and eye candy recipes to solutions for data analysis, testing, and web hosting. Make buttons and content stand out with s......一起来看看 《Web Development Recipes》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

SHA 加密
SHA 加密

SHA 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试