赞
踩
本文档从零开始搭建一个通用的管理后台,技术栈为vue2.0 + vue-router + vuex + element-ui + axios 。最终效果如下:
左侧为菜单栏,右侧包含头部,面包屑,主内容区。左侧菜单栏可折叠,可全屏,刷新页面后还是之前页面状态。gitee地址:
https://gitee.com/liubangbo/yilin-admain/tree/master
如对您有帮助,麻烦给个star。另外如有问题,欢迎在此留言讨论。
下面对此框架主要部分进行详细阐述。
1. 准备工作
此框架采用了vue-cli 脚手架创建的项目,选中vuex vue-router,然后按官方文档安装element-ui 并按需加载。最后在安装sass sass-loader的时候如果报错的话,则直接在package.json的devDependencies字段加上"sass": "^1.53.0", "sass-loader": "^8.0.2", 再npm install 一下就可以了。
2. 模块化业务
业务按模块划分,在每个模块下写上路由,以及vuex状态。通过接口获取动态路由,对动态路由进行了一个map映射,这样前端就可以自己组织页面结构了。这一块代码在src/register-route-store.js 中:
- import { router, store } from "./main";
- // import { get } from "./http/index.js"; //real routes fetched from platform
- import { get } from "./mockRoutes.js"; //mock routes
-
- export let routesMap = new Map();
-
- const recursionRoutes = (destRoutes, sourceRoutes) => {
- if (sourceRoutes.length) {
- let temp = null;
- sourceRoutes.forEach((item, index) => {
- temp = routesMap.get(item.id) ? routesMap.get(item.id) : {};
- destRoutes.children &&
- destRoutes.children.push(
- Object.assign(
- {
- path: temp.path,
- component:
- `${item.children}` && `${item.children.length}`
- ? (resolve) =>
- require([
- "@/layouts/components/rightRouteView/index.vue",
- ], resolve)
- : temp.component,
- children:
- `${item.children}` && `${item.children.length}` ? [] : null,
- },
- {
- meta: {
- label: item.label,
- icon: item.icon,
- },
- }
- )
- );
- if (item.children && item.children.length) {
- recursionRoutes(destRoutes.children[index], item.children);
- }
- });
- }
- };
-
- const register_dynamicRoutes_store = (dynamicRoutes) => {
- console.log("dynamic routes: ", dynamicRoutes);
- let routes = [...require("@/layouts/routes/index.js").default];
- store.registerModule("layouts", require("@/layouts/store/index.js").default);
-
- dynamicRoutes.length &&
- dynamicRoutes.forEach((item, index) => {
- require(`@/views${item.path}/routes/index.js`);
- routes[0].children.push(
- Object.assign(
- {
- path: item.path,
- component: (resolve) =>
- require([
- `${item.children}` && `${item.children.length}`
- ? "@/layouts/components/rightContent/index.vue"
- : `@/views${item.path}`,
- ], resolve),
- children:
- `${item.children}` && `${item.children.length}` ? [] : null,
- },
- {
- meta: {
- label: item.label,
- icon: item.icon,
- },
- }
- )
- );
- store.registerModule(
- item.path.slice(1),
- require(`@/views${item.path}/store/index.js`).default
- );
- if (item.children && item.children.length) {
- recursionRoutes(routes[0].children[index], item.children);
- }
- });
-
- console.log("mapped routes is: ", routes);
- router.addRoutes(routes);
- store.commit("layouts/setSubRoutesList", routes[0].children);
- };
-
- const register_login_route = () => {
- const routes = [...require("@/views/login/routes/index.js").default];
- router.addRoutes(routes);
- };
-
- export const getRoutes = async () => {
- try {
- const res = await get({
- urlKey: "layouts-getNav",
- });
-
- if (res.success && res.data && res.data.length) {
- //had login, register dynamic routes
- register_dynamicRoutes_store(res.data);
- router.replace({ path: "/" });
- }
- } catch (error) {
- //no login, register login route
- console.error("get nav error, go to login: ", error);
- register_login_route();
- router.push({
- path: "/login",
- });
- }
- };
- getRoutes();

3. axios封装
之前接触的框架,网络接口一般定义在一个文件中,所有业务模块用到的网络接口都写到一个文件中,文件比较长,维护起来也费尽。这里我们把网络接口也进行了业务划分,每个模块写自己用到的网络接口。这部分代码在src/http/index.js文件中:
- import axios from "axios";
- import { getStorage } from "@/common/js/util.js";
- import { TOKEN } from "@/common/js/constant.js";
-
- let pagesUrls = [];
- pagesUrls.push({
- key: "layouts-getNav",
- url: "web/user/getNav",
- });
- const _pagesUrls = require.context("../views/", true, /urls\/index\.js/);
-
- _pagesUrls.keys().forEach((key) => {
- const _moduleName = key.split("/")[1];
- const _moduleUrls = require("@/views/" +
- _moduleName +
- "/urls/index.js").default;
- pagesUrls.push(..._moduleUrls);
- });
-
- const urlMap = new Map();
- pagesUrls.forEach((i) => {
- if (i.url) {
- urlMap.set(i.key, {
- url: "/" + i.url.replace(/^\//, ""), // both sg/cms/homepage and /sg/cms/homepage are OK
- });
- }
- });
-
- let _httpRequest = (obj, _method) => {
- if (Object.prototype.toString.call(obj) !== "[object Object]") {
- console.error(`the params of http request should be Object`);
- return;
- }
- if (!obj.urlKey || !obj.urlKey.length) {
- console.error(`url is empty, you should set it`);
- return;
- }
-
- const hostKey = obj.hostKey || "HOST";
- let _url = urlMap.get(obj.urlKey).url;
- if (obj.dynamic) _url = `${_url}${obj.dynamic}`; //dynamic url
- delete obj.urlKey;
- delete obj.hostKey;
- return new Promise((resolve, reject) => {
- let _params = {
- method: _method,
- url: _url,
- baseURL: process.env[`VUE_APP_${hostKey.toUpperCase()}`],
- };
- Object.assign(_params, obj);
- axios(_params)
- .then((res) => {
- resolve(res.data);
- })
- .catch((err) => {
- console.error("axios error: ", err.response);
- if (err.response.data.code === 401) {
- // loginAgain()
- }
- reject(err);
- });
- });
- };
-
- axios.interceptors.request.use((config) => {
- const token = getStorage(TOKEN);
- if (token) {
- config.headers["novaAuth"] = token;
- } else {
- config.headers["Authorization"] = "Basic dGVuYW50OjEyMzQ1Ng==";
- }
- return config;
- });
-
- axios.interceptors.response.use((res) => {
- console.log("http res: ", res);
- //TODO token过期需要处理
- return res;
- });
- /**
- * get方法,对应get请求
- * @param {Object} obj
- */
- export function get(obj) {
- return _httpRequest(obj, "GET");
- }
- /**
- * post方法,对应post请求
- * @param {Object} obj
- */
- export function post(obj) {
- return _httpRequest(obj, "POST");
- }

4. 常量定义
在项目中如果多人协作开发,定义通用常量还是比较重要的,防止出现奇怪bug。例如local-storage的key,我们统一写到常量文件中。这部分代码在src/common/js/constant.js中:
- const TOKEN = "iotToken"
- const USER_NAME = "userName"
-
- const TABS = "tabs"
- const ACTIVE_TAB = "activeTabs"
-
- export {
- TOKEN,
- USER_NAME,
- TABS,
- ACTIVE_TAB
- }
5. 假路由数据
在这个通用框架中我们定义了一个假路由数据,如果你们后端返回的动态路由和这个假路由一样,那么这个框架就可以直接拿来用了,直接上手写业务。假路由数据在src/mockRoutes.js中。
- const mockRoutes = {
- code: 200,
- success: true,
- data: [
- {
- id: "8",
- icon: "el-icon-setting",
- label: "系统管理",
- path: "/config",
- children: [
- {
- id: "9",
- icon: "el-icon-setting",
- label: "页面管理",
- path: "/web",
- children: [],
- },
- ],
- },
- {
- id: "100",
- icon: "el-icon-user",
- label: "测试",
- path: "/test",
- children: [
- {
- id: "101",
- icon: "el-icon-user",
- label: "测试一级菜单",
- path: "/test1",
- children: [
- {
- id: "101-0",
- icon: "el-icon-user",
- label: "一级子页面",
- path: "/big",
- children: [],
- },
- ],
- },
- {
- id: "102",
- icon: "el-icon-user",
- label: "测试一级页面",
- path: "/test2",
- children: [],
- },
- {
- id: "103",
- icon: "el-icon-user",
- label: "测试一级菜单",
- path: "/test3",
- children: [
- {
- id: "103-0",
- icon: "el-icon-user",
- label: "测试二级菜单",
- path: "/third",
- children: [
- {
- id: "103-0-0",
- icon: "el-icon-user",
- label: "二级子页面",
- path: "/small",
- children: [],
- },
- ],
- },
- ],
- },
- ],
- },
- ],
- };
- const get = (obj) => {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- if (obj) {
- resolve(mockRoutes);
- } else {
- reject({});
- }
- }, 200);
- });
- };
-
- export { get };

在src/register-route-store.js中的如下代码是进行真假路由数据切换的地方:
- // import { get } from "./http/index.js"; //real routes fetched from platform
- import { get } from "./mockRoutes.js"; //mock routes
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。