本文档从零开始搭建一个通用的管理后台,技术栈为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 

Logo

前往低代码交流专区

更多推荐