前言
个人理解的路由权限,按钮权限无非就是,根据登录用户的角色来向后台请求这个角色拥有的权限;
换种说法就是,登录的账号它能查看的路由有哪些,对应的页面按钮操作又有哪些,知道了这些信息后就是交给前端去通过返回的数据操作替换路由表,展示返回数据里的路由。
一、登录
login.vue
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
// 将username和password派发到该方法中
this.$store.dispatch('user/login', this.loginForm)
.then(() => {
// 登录成功,跳转至首页
this.$router.push({ path: '/' })
})
.catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
}
src/store/modules/user.js
const actions = {
// user login
login({ commit }, userInfo) {
// 根据页面递来的数据
const { username, password } = userInfo
return new Promise((resolve, reject) => {
// api/user
// 连接后端接口,根据前端请求的用户信息,后端返回唯一对应的随机token
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
// commit会触发mutations,存储token
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
}
}
二、权限路由
src/permission.js
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'] // 白名单
//全局前置路由,若无next()则不会进行下一步骤
//在每个路由跳转前都会执行该方法
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
// 根据是否存在token,判断是否登录
const hasToken = getToken()
// 若token存在,则该用户已登录
if (hasToken) {
if (to.path === '/login') {
// 若此时页面在登录页面,则跳转至首页
next({ path: '/' })
NProgress.done()
} else {
// 若此时不在登录页面,则获取用户角色权限,判断所在页面该用户是否能进入
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
next()
} else {
try {
// 若该用户之前未存储角色权限在store中,则前往store目录下的该路径,根据store存储过的token,获取并存储该用户的角色权限
const { roles } = await store.dispatch('user/getInfo')
// 将角色权限派发到该路径下
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// 生成动态路由
router.addRoutes(accessRoutes)
// 再重新返回判断角色权限是否存在,进行下一步骤
next({ ...to, replace: true })
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
//这里会报个参数类型异常,所以将参数 由error 改为了 error.message
Message.error(error.message || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
src/store/modules/user.js
const actions = {
// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
// 连接后端接口,根据token数据,后端返回对应的用户信息,如角色权限roles
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
return reject('Verification failed, please Login again.')
}
const { name, avatar, roles } = data
// 存储用户信息,方便用于全局
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
commit('SET_ROLES', roles)
resolve(data)
}).catch(error => {
reject(error)
})
})
}
}
src/router/index.js
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);
/* Layout */
import Layout from "@/layout";
// 固定路由
export const constantRoutes = [
{
path: "/login",
component: () => import("@/views/login/index"),
// hidden: true,
neme: "Login",
},
{
path: "/rebuild",
component: () => import("@/views/login/rebuild"), // 忘记密码页
// hidden: true
},
{
path: "/auth-redirect",
component: () => import("@/views/login/auth-redirect"),
// hidden: true
},
{
path: "/403",
component: () => import("@/views/403"),
// hidden: true
},
{
path: "/",
component: Layout,
redirect: "/dashboard",
children: [
{
path: "dashboard",
component: () => import("@/views/dashboard/index"),
name: "Dashboard",
meta: {
title: "首页",
icon: "el-icon-s-home",
roles: ["admin"],
affix: true,
},
},
{
path: "/personal",
component: () => import("@/views/permission/personal.vue"),
name: "PersonalPermission",
hidden: true,
meta: {
title: "个人中心",
},
},
],
},
];
// 权限(动态)路由(需要根据权限进行判断)
export const asyncRoutes = [
// 后台权限
{
path: "/permission",
component: Layout,
redirect: "/permission/user",
alwaysShow: true, // will always show the root menu
name: "Permission",
meta: {
title: "权限模块",
icon: "lock",
roles: ["admin", "editor"], // you can set roles in root nav
},
children: [
{
path: "/permission/user",
component: () => import("@/views/permission/user.vue"),
name: "UserPermission",
meta: {
title: "用户管理",
},
},
{
path: "/permission/role",
component: () => import("@/views/permission/role.vue"),
name: "RolePermission",
// hidden: true,
meta: {
title: "角色管理",
roles: ["admin"],
},
},
{
path: "/permission/roleModify",
component: () => import("@/views/permission/roleModify.vue"),
name: "RoleModifyPermission",
hidden: true,
meta: {
title: "角色管理调整",
},
// hidden: true
},
{
path: "/permission/rightsManage",
component: () => import("@/views/permission/rightsManage.vue"),
name: "RightsManagePermission",
// hidden: true,
meta: {
title: "权限管理",
roles: ["admin"],
},
},
{
path: "/permission/file",
component: () => import("@/views/permission/file.vue"),
name: "FilePermission",
// hidden: true,
meta: {
title: "文件管理",
roles: ["admin"],
},
},
],
},
// 数据同步
{
path: "/dataSync",
component: Layout,
redirect: "/data-sync/database-access",
alwaysShow: true, // will always show the root menu
name: "DataSync",
meta: {
title: "数据同步",
icon: "el-icon-s-data",
roles: ["admin"], // you can set roles in root nav
},
children: [
{
path: "siteList",
component: () => import("@/views/dataSync/siteList.vue"),
name: "SiteList",
meta: {
title: "网站列表",
},
},
{
path: "taskList",
component: () => import("@/views/dataSync/taskList.vue"),
name: "TaskList",
meta: {
title: "任务列表",
},
},
{
path: "sourceList",
component: () => import("@/views/dataSync/sourceList.vue"),
name: "SourceList",
meta: {
title: "数据源列表",
},
},
{
path: "databaseAccess",
component: () => import("@/views/dataSync/databaseAccess.vue"),
name: "DatabaseAccess",
meta: {
title: "数据库接入",
},
},
{
path: "directorySync",
component: () => import("@/views/dataSync/directorySync.vue"),
name: "directorySync",
meta: {
title: "目录同步",
},
},
{
path: "theLog",
component: () => import("@/views/dataSync/theLog.vue"),
name: "theLog",
meta: {
title: "日志",
},
},
],
},
// 邮件课题管理 Email topic
{
path: "/emailTopic",
component: Layout,
redirect: "/email-topic/email-list",
alwaysShow: true, // will always show the root menu
name: "emailTopic",
meta: {
title: "邮件课题管理",
icon: "youJian",
roles: ["admin", "editor"], // you can set roles in root nav
},
children: [
{
path: "/emailTopic/emailTopicList",
component: () => import("@/views/emailTopic/emailTopicList.vue"),
name: "EmailTopicList",
meta: {
title: "邮件课题列表",
},
},
// hidden: true,
{
path: "additionTopic",
component: () => import("@/views/emailTopic/additionTopic.vue"),
name: "AdditionTopic",
hidden: true,
meta: {
title: "邮件课题新增",
},
},
{
path: "additionTopic_edit",
component: () => import("@/views/emailTopic/additionTopic.vue"),
name: "AdditionTopic_edit",
hidden: true,
meta: {
title: "邮件课题编辑",
},
},
{
path: "emailTopic_search",
component: () => import("@/views/emailTopic/emailTopic_se.vue"),
name: "EmailTopic_search",
hidden: true,
meta: {
title: "课题邮箱搜索",
},
},
{
path: "topic_upload",
component: () => import("@/views/emailTopic/topic_upload.vue"),
name: "Topic_Upload",
hidden: true,
meta: {
title: "上传课题记录",
},
},
{
path: "topic_log",
component: () => import("@/views/emailTopic/topic_log.vue"),
name: "Topic_Log",
hidden: true,
meta: {
title: "上传日志记录",
},
},
],
},
// // 发帖课题列表 Post topic
{
path: "/postTopic",
component: Layout,
redirect: "/post-topic/post-list",
alwaysShow: true, // will always show the root menu
name: "PostTopic",
meta: {
title: "发帖课题管理",
icon: "el-icon-s-unfold",
roles: ["admin"], // you can set roles in root nav
},
children: [
// 发帖课题列表
{
path: "postTopicList",
component: () => import("@/views/postTopic/postTopicList.vue"),
name: "PostTopicList",
meta: {
title: "发帖课题列表",
},
},
{
path: "addPostTopic",
component: () => import("@/views/postTopic/addPostTopic.vue"),
name: "AddPostTopic",
meta: {
title: "新增发帖课题",
},
hidden: true,
},
{
path: "uploadTopic",
component: () => import("@/views/postTopic/uploadTopic.vue"),
name: "UploadTopic",
meta: {
title: "上传课题记录",
},
hidden: true,
},
{
path: "uploadPostTopic",
component: () => import("@/views/postTopic/uploadPostTopic.vue"),
name: "UploadPostTopic",
meta: {
title: "上传发帖记录",
},
hidden: true,
},
{
path: "deletePostTopic",
component: () => import("@/views/postTopic/deletePostTopic.vue"),
name: "DeletePostTopic",
meta: {
title: "修改发帖课题",
},
hidden: true,
},
// 品牌版本列表
{
path: "brandVersionList",
component: () => import("@/views/postTopic/brandVersionList.vue"),
name: "BrandVersionList",
meta: {
title: "品牌版本列表",
},
},
// 平台列表
{
path: "platformList",
component: () => import("@/views/postTopic/platformList.vue"),
name: "PlatformList",
meta: {
title: "平台列表",
},
},
],
},
];
// 创建路由
const createRouter = () =>
new Router({
mode: "history",
// base: '/'
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes,
});
const router = createRouter();
// 重置路由
export function resetRouter() {
const newRouter = createRouter();
router.matcher = newRouter.matcher; // reset router
}
export default router;
src/store/modules/permission.js
这里是操作后端返回的数据,转变为本地路由的形式,在存储起来,之后 在 permission.js 文件中使用 router.addRoutes() 作用是动态添加可访问路由表
// 引入全部动态路由 和静态路由
import { asyncRoutes, constantRoutes } from "@/router/index";
import API from "@/api";
// 声明变量 储存数据
const state = {
routes: [],
};
// 同步修改state中变量的值
const mutations = {
setRoutes(state, menus) {
state.routes = [...constantRoutes, ...menus];
},
};
// 异步修改state中的值,
// 通过调用mutations中的函数来间接修改
const actions = {
// 根据权限数组和 全部动态路由数组
// 进行筛选 出具有权限的路由对象数组
async filterRoutes(store, menus) {
const getJurisd = [];
const menues = [];
const menu = [];
const numberF = [];
await API.getJurisdList().then((res) => {
let menus = res.data.data;
const num = [];
const value = [];
//拿到所有子路由
function getName(val) {
val.forEach((item, index) => {
if (item.children) {
//这里不加没有父级
value.push(item);
getName(item.children);
} else {
num.push(item);
}
});
return [num, value];
}
let items = getName(menus);
//子:过滤出符合条件的数据
let vueRouter = items[0].filter((item) => {
return item.route_vue !== null && item.route_vue !== "";
});
//父:过滤出符合条件的数据
let vueRoute = items[1].filter((item) => {
return item.route_vue !== null && item.route_vue !== "";
});
vueRouter.push(...vueRoute);
//只要需要的数据
vueRouter.forEach((item) => {
menues.push(item.name);
menu.push(item.route_vue);
getJurisd.push({ name: item.name, route_vue: item.route_vue });
});
//父级
menus.forEach((item) => {
numberF.push({ name: item.name, route_vue: item.route_vue });
});
});
//替换路由数据
function filterMenu(menuList, getJurisd) {
menuList.forEach((item) => {
if (item.children) {
filterMenu(item.children, getJurisd);
} else {
getJurisd.forEach((obj) => {
if (item.name == obj.route_vue) {
item.meta.title = obj.name;
}
});
}
});
}
const myMenu = filterMenu(asyncRoutes, getJurisd);
asyncRoutes.forEach((item) => {
numberF.forEach((obj) => {
if (item.name == obj.route_vue) {
item.meta.title = obj.name;
}
});
});
// memus: 请求回来的路由权限数组
// 全部的动态路由数组
//过滤选择出父级符合标准的路由
const res = asyncRoutes.filter((item) => menus.includes(item.name));
const data = [];
asyncRoutes.forEach(function (e, i) {
data.push(e.children.filter((item) => menus.includes(item.name)));
asyncRoutes[i].children = data[i];
});
//过滤出子路由为空的
const routes = asyncRoutes.filter(function (item) {
if (!item.children.length == 0) {
return item;
}
});
store.commit("setRoutes", routes);
return routes;
},
};
export default {
namespaced: true,
state,
mutations,
actions,
};
这里项目返回的后端数据形式是:
返回的路由数据形式:
返回的是路由的 name
返回的按钮权限数据形式:
src/store/getters.js
// 存储用于全局
roles: state =>state.user.roles,
permission_routes: state=>state.permission.routes
src/layout/components/Sidebar/index.vue
<!--根据角色权限路由表遍历菜单-->
<sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
<script>
import { mapGetters } from 'vuex'
import SidebarItem from './SidebarItem'
export default {
components: { SidebarItem },
computed: {
...mapGetters([
// 获取全局中存储的被角色权限过滤过的路由表
'permission_routes'
]),
}
}
</script>
三、按钮权限
mixin 全局混入的方法
按钮权限的话 使用 Vue自定义指令 或者 使用 mixin 全局混入的方法,
这里我使用的是 mixin 全局混入
在main.js 里面 导入
import mixin from '@/mixin/index'
Vue.mixin(mixin)
src/mixin/index.js 创建文件夹,代码如下:
import store from '@/store'
// 利用mixin(混入)来封装全局函数
// 这里封装在methods中的所有函数,都自动会添加到
// 每个vue文件中methods中
// 封装判断按钮权限的函数
export default {
methods: {
// 判断是否具有按钮权限的函数
// 判断需要有2个条件
// 1、需要有判断的标识符(到底要判断哪个按钮显示隐藏),这个标识符就是绑定函数时传递过来的值
// 2、按钮权限的数据集合,就是保存到vuex中的operation数组
isBtnPerm(key) {
const { userInfo } = store.state.user
if (userInfo.roles.operation && userInfo.roles.operation.length) {
return userInfo.roles.operation.some(item => item === key)
}
return false
}
}
}
以上是本人做路由权限及按钮权限的理解,希望对读者能有帮助 !