资讯专栏INFORMATION COLUMN

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

codeKK / 2807人阅读

摘要:公司做的大部分都是后台管理项目,剔除每个项目的业务逻辑,其实都可以用通用的一套模版来做。总结以上都是后台系统中可以用到的一些处理方式,具体代码可查看。其他总结文章常规打包优化方案组件通信处理方案

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

登录逻辑

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

前端先获取菜单列表

根据获取的菜单列表循环添加用户菜单路由集合

动态添加路由

具体可查看

请求方案

项目请求是使用的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");
    ...
})    

在组件内定义

保存

computed: {
    btn() {
        return this.$store.state.loading;
    }
}
methods: {
    handleClick() {
        this.$store.dispatch("SetLoading", { save: true });    
    }
}

以上就可以完美的使用loading,而不用每个都在data中定义了。

总结

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

其他总结文章:

webpack常规打包优化方案

组件通信处理方案

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104839.html

相关文章

  • Vue项目总结项目nginx部署

    摘要:项目开发完成,接下来是上线,关于项目的部署,我司前端是部署在服务器上,关于的相关文档,请自行查阅本文只记录部署时碰到的一些问题。其他总结文章常规打包优化方案组件通信处理方案后台管理项目总结 项目开发完成,接下来是上线,关于vue项目的部署,我司前端是部署在nginx服务器上,关于nginx的相关文档,请自行查阅;本文只记录部署时碰到的一些问题。 打包 vue项目打包后,是生成一系列的静...

    CntChen 评论0 收藏0
  • Vue项目总结】基于饿了么组件封装

    摘要:项目中,组件是项目的基石,每个页面都是组件来组装起来,我司没有自己的组件库,选用的是组件库,在它的基础上再次封装。部分代码三级效果如下总结组件是项目的积木条,公用组件的封装成功与否其实是对项目的开发效率有直接影响。 vue项目中,组件是项目的基石,每个页面都是组件来组装起来,我司没有自己的组件库,选用的是ElementUI组件库,在它的基础上再次封装。 可编辑表格 由于是后台管理项目,...

    YPHP 评论0 收藏0
  • Vue 2.x 实战之后台管理系统开发(二)

    摘要:导语承接上文实战之后台管理系统开发一在上一篇文章中,我详细叙述了如何创建项目框架和引入各种后台常用插件,做好这些准备工作后,我们就可以着手进行页面的开发了。如果传入的数据不符合规格,会发出警告。 1. 导语 承接上文:Vue 2.x 实战之后台管理系统开发(一) 在上一篇文章中,我详细叙述了如何创建项目框架和引入各种后台常用插件,做好这些准备工作后,我们就可以着手进行页面的开发了。在开...

    Ilikewhite 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<