本文档从零开始搭建一个通用的管理后台,技术栈为vue2.0 + vue-router + vuex + element-ui + axios 。最终效果如下:
左侧为菜单栏,右侧包含头部,面包屑,主内容区。左侧菜单栏可折叠,可全屏,刷新页面后还是之前页面状态。gitee地址:
https://gitee.com/liubangbo/yilin-admain/tree/master
如对您有帮助,麻烦给个star。另外如有问题,欢迎在此留言讨论。
下面对此框架主要部分进行详细阐述。
准备工作
此框架采用了vue-cli 脚手架创建的项目,选中vuex vue-router,然后按官方文档安装element-ui 并按需加载。最后在安装sass sass-loader的时候如果报错的话,则直接在package.json的devDependencies字段加上"sass": "^1.53.0", "sass-loader": "^8.0.2", 再npm install 一下就可以了。
模块化业务
业务按模块划分,在每个模块下写上路由,以及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();
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");
}
- 常量定义
在项目中如果多人协作开发,定义通用常量还是比较重要的,防止出现奇怪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
}
假路由数据
在这个通用框架中我们定义了一个假路由数据,如果你们后端返回的动态路由和这个假路由一样,那么这个框架就可以直接拿来用了,直接上手写业务。假路由数据在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
版权归原作者 夜跑者 所有, 如有侵权,请联系我们删除。