这是一位前端大佬自己封装后台管理系统模板,其中主要技术栈为NPM+Vue.js以及Element-UI,初次接触这个模板框架的时候认为这个模板框架很适合小白或者二次开发使用,虽然作者不提倡二次开发或者商用,但是我们在学习别人的框架设计的时候自然也会得到提升,由于我只是一个后端开发工作者,但是迫于行业内卷严重,不得已学了前端一些技术基础,专业前端大佬看到此篇博客有出入争议,尽然可以留言。
GitHub仓库地址
码云仓库地址
具体安装方式在仓库的README中
使用这个模板框架已经三年多了,一路跟着作者走来,但是毕竟是封装完整的模板框架,在实际定制化开发必然会遇见设计与模板相矛盾的地方,例如:该框架仅仅支持两种角色类型,admin和editor,对于前端专业大佬来说,其实只要把对鉴权的角色扩充即可,但是对于我们这些半路出家的小垃圾,只能另辟蹊径,只能在动态路由上面动刀子来适配我们实际开发中应对企业的角色模式。我的意思,可以通过后端来实现定制角色去管控不同用户的角色,这也是研究动态路由的原因。
其路由分为两种类型,静态路由和异步路由,静态路由主要是用于常规并且不涉及安全内容以及公共的页面,这一部分我们可以不做修改,或者后续我们有新的路由,我们也可以继续添加。
这是vue为了提供的静态路由中一些常用页面,比如根目录,登录,404等,如果还需要添加左边栏菜单,可以模仿静态路由的设计里面,还有权限的设计。
修改文件:src\router\index.js
切记文件路径要找对~~~
修改文件: src/store/modules/permission.js
import { asyncRoutes, constantRoutes } from '@/router'
import { getAuthMenu } from '@/api/user'
import Layout from '@/layout'
import { getToken } from '@/utils/auth'
/**
* Use meta.role to determine if the current user has permission
* @param roles
* @param route
*/
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
return true
}
}
/**
* 后台查询的菜单数据拼装成路由格式的数据
* @param routes (resolve: any) => require([`@/views/${view}.vue`], resolve)
*/
export function generaMenu(routes, data) {
data.forEach(item => {
// 这里的字段在实际业务开发中都有用的,当然如果只是简单学习的话,id,label可以移除
const menu = {
id: item.id,
label: item.label,
name: item.name,
path: item.path === '#' ? item.id + '_key' : item.path,
component: item.component === '#' ? Layout : (resolve) => require([`@/views${item.component}`], resolve),
redirect: item.redirect,
hidden: item.hidden,
alwaysShow: item.alwaysShow,
meta: item.meta,
children: [],
}
if (item.children) {
generaMenu(menu.children, item.children)
}
routes.push(menu)
})
}
/**
* Filter asynchronous routing tables by recursion
* @param routes asyncRoutes
* @param roles
*/
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
const state = {
routes: [],
addRoutes: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
generateRoutes({ commit }, roles) {
return new Promise(resolve => {
const loadMenuData = []
// 先查询后台并返回左侧菜单数据并把数据添加到路由
getAuthMenu(getToken()).then(response => {
let data = response
if (response.code !== 200) {
throw new Error('菜单数据加载异常')
} else {
data = response.data
Object.assign(loadMenuData, data)
const tempAsyncRoutes = Object.assign([], asyncRoutes)
// tempAsyncRoutes = asyncRoutes
generaMenu(tempAsyncRoutes, loadMenuData)
let accessedRoutes
if (roles.includes('admin')) {
accessedRoutes = tempAsyncRoutes || []
} else {
accessedRoutes = filterAsyncRoutes(tempAsyncRoutes, roles)
}
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
}
}).catch(error => {
console.log(error)
})
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
注意: 对照一下原版就可以发现更改的部分,这里面一般在编译的时候会出现rang of null的错误,这是因为你的babel-eslint版本过高的原因,因为老外改过打包的底层逻辑。所以我门一劳永逸,直接降低版本:
npm i [email protected]
添加动态路由的请求方法 /src/api/user.js 或者其他什么api都可以
export function getAuthMenu(token) {
return request({
url: BASE_CONTEXT + '/router' ,
method: 'get',
params: { token }
})
}
在这上面还有一个getToken() 方法,其实就是登录后返回的用户token
重点就是后端返回的JSON串了
这里返回的是data内容,具体的接口返回类可能不同,请关注上文修改permission.js文件中82后的response返回结构
[
{
"id": "ae3b9ea72fea49099622b54fe7cc8f3a",
"label": "企业中台",
"name": "EnterpriseMiddleOffice",
"path": "/enterprise-middle-office",
"component": "#",
"redirect": "/enterprise-middle-office",
"hidden": false,
"alwaysShow": true,
"meta": {
"title": "企业中台",
"icon": "EnterpriseMiddleOffice",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/enterprise-middle-office"
},
"children": [
{
"id": "143be130c938483d9a1ed033dc23b8f2",
"label": "用户管理",
"name": "UserManagement",
"path": "user-management",
"component": "/enterprise-middle-office/user-management",
"hidden": false,
"meta": {
"title": "用户管理",
"icon": "UserManagement",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/enterprise-middle-office/user-management"
},
"children": [
{
"id": "22f2cbfcb7bc49ca9fd3fa734f221b84",
"label": "用户列表",
"name": "UserList",
"path": "user-list",
"component": "/enterprise-middle-office/user-management/user-list",
"hidden": false,
"meta": {
"title": "用户列表",
"icon": "UserList",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/enterprise-middle-office/user-management/user-list"
}
},
{
"id": "bb5925e3fe9e44e88f6bbabcda8e4bf5",
"label": "用户配置",
"name": "UserConfig",
"path": "user-config",
"component": "/enterprise-middle-office/user-management/user-config",
"hidden": false,
"meta": {
"title": "用户配置",
"icon": "UserConfig",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/enterprise-middle-office/user-management/user-config"
},
"children": [
{
"id": "f948c43dcf524c06ab3a4b578dd9b514",
"label": "角色权限",
"name": "RolePermissions",
"path": "role-permissions",
"component": "/enterprise-middle-office/user-management/user-config/role-permissions",
"hidden": false,
"meta": {
"title": "角色权限",
"icon": "RolePermissions",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/enterprise-middle-office/user-management/user-config/role-permissions"
}
},
{
"id": "c43f140683d24b21bd509132a6118885",
"label": "路由配置",
"name": "RoutingConfig",
"path": "routing-config",
"component": "/enterprise-middle-office/user-management/user-config/routing-config",
"hidden": false,
"meta": {
"title": "路由配置",
"icon": "RoutingConfig",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/enterprise-middle-office/user-management/user-config/routing-config"
}
}
]
}
]
},
{
"id": "9a62da9c68fb4e0f80d67eae920fd8a5",
"label": "企业BG",
"name": "EnterpriseBusinessGroup",
"path": "enterprise-business-group",
"component": "/enterprise-middle-office/enterprise-business-group",
"hidden": false,
"meta": {
"title": "企业BG",
"icon": "EnterpriseBusinessGroup",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/enterprise-middle-office/enterprise-business-group"
}
},
{
"id": "8aed8501828841328c550da1d169da4a",
"label": "流程中心",
"name": "ProcessCenter",
"path": "process-center",
"component": "/enterprise-middle-office/process-center",
"hidden": false,
"meta": {
"title": "流程中心",
"icon": "ProcessCenter",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/enterprise-middle-office/process-center"
},
"children": [
{
"id": "8aed8501828841328c550da1d169da4a",
"label": "流程列表",
"name": "ApplicationList",
"path": "application-list",
"component": "/enterprise-middle-office/process-center/application-list",
"hidden": false,
"meta": {
"title": "流程列表",
"icon": "ApplicationList",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/enterprise-middle-office/process-center/application-list"
}
},
{
"id": "d36f437ed65f46e9b7d57f5a4d9eb39d",
"label": "流程设计",
"name": "ProcessDesign",
"path": "process-design",
"component": "/enterprise-middle-office/process-center/process-design",
"hidden": false,
"meta": {
"title": "流程设计",
"icon": "ProcessDesign",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/enterprise-middle-office/process-center/process-design"
}
}
]
}
]
},
{
"id": "4f175018ba1241b2bdf9bfd1bbebd2cb",
"label": "智能运维",
"name": "IntelligentOperationMaintenance",
"path": "/intelligent-operation-maintenance",
"component": "#",
"redirect": "/intelligent-operation-maintenance",
"hidden": false,
"alwaysShow": true,
"meta": {
"title": "智能运维",
"icon": "IntelligentOperationMaintenance",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/intelligent-operation-maintenance"
},
"children": [
{
"id": "1d235f96d64146d29c9aa538ba582958",
"label": "行为监控",
"name": "BehaviorMonitoring",
"path": "behavior-monitoring",
"component": "/intelligent-operation-maintenance/behavior-monitoring",
"hidden": false,
"meta": {
"title": "行为监控",
"icon": "BehaviorMonitoring",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/intelligent-operation-maintenance/behavior-monitoring"
}
}
]
},
{
"id": "805a427828d648559ba1bb01c80c5d6d",
"label": "高级配置",
"name": "AdvancedConfiguration",
"path": "/advanced-configuration",
"component": "#",
"redirect": "/advanced-configuration",
"hidden": false,
"alwaysShow": true,
"meta": {
"title": "高级配置",
"icon": "AdvancedConfiguration",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/advanced-configuration"
},
"children": [
{
"id": "6f8f0b9e910044509a17fd916f360b21",
"label": "基础配置",
"name": "BasicConfiguration",
"path": "basic-configuration",
"component": "/advanced-configuration/basic-configuration",
"hidden": false,
"meta": {
"title": "基础配置",
"icon": "BasicConfiguration",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/advanced-configuration/basic-configuration"
}
},
{
"id": "e4889d91830c4f769af26f3a8a596de3",
"label": "消息配置",
"name": "MessageConfiguration",
"path": "message-configuration",
"component": "/advanced-configuration/message-configuration",
"hidden": false,
"meta": {
"title": "消息配置",
"icon": "MessageConfiguration",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/advanced-configuration/message-configuration"
},
"children": [
{
"id": "329781b87a0c4da1a2199dad5f92017a",
"label": "通知中心",
"name": "NotificationCenter",
"path": "notification-center",
"component": "/advanced-configuration/message-configuration/notification-center",
"hidden": false,
"meta": {
"title": "通知中心",
"icon": "NotificationCenter",
"noCache": false,
"breadcrumb": true,
"affix": false,
"activeMenu": "/advanced-configuration/message-configuration/notification-center"
}
}
]
}
]
}
]
这里添加了 一级路由 ,二级路由,三级路由以及更多层级路由
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 动态路由信息模板类
* 详细设计参考: https://panjiachen.github.io/vue-element-admin-site/zh/guide/essentials/router-and-nav.html#%E9%85%8D%E7%BD%AE%E9%A1%B9
*
* @author lvxiwei
* @since 2022-08-28
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Router {
/**
* 路由唯一ID
*/
private String id;
/**
* 路由中文显示
*/
private String label;
/**
* 设定路由的名字,一定要填写不然使用时会出现各种问题
*/
private String name;
/**
* 一级路由路径
*/
private String path;
/**
* 包含组件 默认 Layout 以 # 代替
*/
private String component;
/**
* 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
* 重定向地址,在面包屑中点击会重定向去的地址
*/
private String redirect;
/**
* 不在侧边栏显示 实际就是路由隐藏
*/
private boolean hidden;
/**
* 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
* 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
* 若你想不管路由下面的 children 声明的个数都显示你的根路由
* 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
*/
private boolean alwaysShow;
/**
* 你可以在根路由设置权限,这样它下面所有的子路由都继承了这个权限
*/
private Meta meta;
/**
* 子路由
*/
private List<Router> children;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Meta {
/**
* 设置该路由在侧边栏和面包屑中展示的名字
*/
private String title;
/**
* 设置该路由的图标,支持 svg-class,也支持 el-icon-x element-ui 的 icon
*/
private String icon;
/**
* 如果设置为true,则不会被 缓存(默认 false)
*/
private boolean noCache;
/**
* 如果设置为false,则不会在breadcrumb面包屑中显示(默认 true)
*/
private boolean breadcrumb;
/**
* 如果设置为true,它则会固定在tags-view中(默认 false)
*/
private boolean affix;
/**
* 当路由设置了该属性,则会高亮相对应的侧边栏。
* 这在某些场景非常有用,比如:一个文章的列表页路由为:/article/list
* 点击文章进入文章详情页,这时候路由为/article/1,但你想在侧边栏高亮文章列表的路由,就可以进行如下设置
*/
private String activeMenu;
}
}
切记Router中的可能会有人使用二级路由来代替一级路由,那么实体类的name字段必然会重复,这个name字段实际就是index.vue页面中的name
ems.js会检测到路由重复问题,但是实际它找的还是path字段,因此只是告警,在实际开发中我们还是要注意,关于文档中介绍,这name还是有很大用处的,涉及到noCache这个功能使用,具体功能就是比如你这个页面有个分页,分页查询是携带参数的,当你从这个路由跳转至其他页面,同时你设置noCache=false,当你再返回分页页面的时候,将不会再重新刷新。