一、理解前后端权限的区别
后端:后端权限可以控制某个用户是否能够查询数据,是否能够修改数据等操作
前端:前端仅视图层的展示权限的核心是在于服务器中的数据变化
所以后端才是权限的关键
简单了解后端吧
1.后端如何知道该请求是哪个用户发过来的
cookie
session
token
2.后端的权限设计使用RBAC
用户
角色
权限工
二、前端的权限的意义
1.降低非法操作的可能性
2.尽可能排除不必要请求减轻服务器压力
3.提高用户体验
三、前端的权限控制常见的分类
菜单的控制
界面的控制
按钮的控制
请求和响应的控制
四、vue的权限控制实现前的准备工作
推荐下载vue-admin-template模板 打开文件,如下图所示:
目录结构说明
├── build # 构建相关
├── mock # 项目mock 模拟数据
├── plop-templates # 基本模板
├── public # 静态资源
│ │── favicon.ico # favicon图标
│ └── index.html # html模板
├── src # 源代码
│ ├── api # 所有请求
│ ├── assets # 主题 字体等静态资源
│ ├── components # 全局公用组件
│ ├── directive # 全局指令
│ ├── filters # 全局 filter
│ ├── icons # 项目所有 svg icons
│ ├── lang # 国际化 language
│ ├── layout # 全局 layout
│ ├── router # 路由
│ ├── store # 全局 store管理
│ ├── styles # 全局样式
│ ├── utils # 全局公用方法
│ ├── vendor # 公用vendor
│ ├── views # views 所有页面
│ ├── App.vue # 入口页面
│ ├── main.js # 入口文件 加载组件 初始化等
│ └── permission.js # 权限管理
├── tests # 测试
├── .env.xxx # 环境变量配置
├── .eslintrc.js # eslint 配置项
├── .babelrc # babel-loader 配置
├── .travis.yml # 自动化CI配置
├── vue.config.js # vue-cli 配置
├── postcss.config.js # postcss 配置
└── package.json # package.json
跨域配置在vue.config.js
中配置,如图:
说明:默认的模板中,使用的是mockjs,进一步了解mockjs官方对mockjs的解释有以下4点:
1.前后端分离
2.不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据
3.数据类型丰富
4.通过随机数据,模拟各种场景
看一个项目首先要从入口文件开始看,即:main.js和App.vue
main.js程序入口文件 初始化vue实例 并引入使用需要的插件和各种公共组件
[ new Vue()是新创建的实例 el是为实例提供挂载元素 ]
App.vue项目的主组件/页面入口文件 ,所有页面都在App.vue下进行切换,app.vue负责构建定义及页面组件归集。
这里就不做过多解释了
五、理解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'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
// determine whether the user has logged in
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({ path: '/' })
NProgress.done()
} else {
const hasGetUserInfo = store.getters.user
if (hasGetUserInfo) {
next()
} else {
try {
// get user infob
await store.dispatch('user/getInfo').then(res => { //触发获取用户信息到接口
store.dispatch('permission/generateRoutes', res).then(() => { //触发获取路由表的接口
router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
})
})
next()
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken') // 触发vuex中 resetToken => src/store/user.js 的 resetToken函数 清除token
Message.error(error.message || 'Has Error') // 弹出异常
next(`/login?redirect=${to.path}`)
// 然后就执行这里 跳转到 login redirect把从哪个页面出错的 做重定向的 => view/login/index.js的handleLogin函数
// =>src/store/user.js 的login函数
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()
})
六、理解request.js文件,理解axios的拦截请求/响应原理
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
// 创建axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // 跨域请求时发送cookie
timeout: 5000 // 请求超时
})
// 请求拦截器
service.interceptors.request.use(
config => {
// do something before request is sent
console.log(config)
if (store.getters.token) {
// 让每个请求都带有token
// ['AdminToken'] is a custom headers key
// 根据实际情况自行修改
config.headers['AdminToken'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* 根据HTTP状态码来判断code
*/
response => {
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 1) {
Message({
message: res.msg || 'Error',
type: 'error',
duration: 5 * 1000
})
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code == 1101) {
// to re-login
MessageBox.confirm('登录已失效,请重新登录!', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
//Location.reload() 方法用来刷新当前页面。该方法只有一个参数,当值为 true 时,将强制浏览器从服务器加载页面资源,
//当值为 false 或者未传参时,浏览器则可能从缓存中读取页面。
})
})
}
return Promise.reject(new Error(res.msg || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
七、理解并编辑router/index.js的文件 ,动态路由渲染侧边栏
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
//布局
import Layout from '@/layout'
export const constantRouterMap = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '控制面板', icon: 'dashboard' }
}
]
}
]
export const asyncRouterMap = [
{
path: '/permission',
component: Layout,
name: 'permission',
meta: {
title: '权限管理',
permissions: ['roleRead', 'roleEdit', 'adminUserRead', 'adminUserEdit'],
icon: 'el-icon-s-order'
},
children: [
{
path: 'role',
component: () => import('@/views/pemission/role'),
name: 'role',
meta: {
title: '角色管理',
permissions: ['roleRead', 'roleEdit'],
icon: 'el-icon-coordinate'
}
},
{
path: 'index',
component: () => import('@/views/pemission/admin'),
name: 'admin',
meta: {
title: '管理员管理',
permissions: ['adminUserRead', 'adminUserEdit'],
icon: 'el-icon-s-custom'
}
}
]
},
{
path: '/user',
component: Layout,
name: 'user',
meta: {
title: '用户管理',
permissions: ['userRead', 'userEdit'],
icon: 'el-icon-user-solid'
},
children: [
{
path: 'index',
component: () => import('@/views/user/user.vue'),
name: 'userIndex',
meta: {
title: '用户管理',
permissions: ['userRead', 'userEdit'],
icon: 'el-icon-user'
}
}
]
},
/*动态路由渲染的侧边栏菜单*/
// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }
]
const createRouter = () =>
new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRouterMap
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // 重置路由
}
export default router
八、新建文件,编辑api/admin.js文件
import request from '@/utils/request'
const prefix = '/admin'
// 登陆
export function login(data) {
return request({
url: prefix + '/login',
method: 'post',
data
})
}
// 获取登录用户信息
export function getInfo() {
return request({
url: prefix + '/getUserInfo',
method: 'get'
})
}
// 退出登录
export function logout() {
return request({
url: prefix + '/logout',
method: 'post'
})
}
// 获取用户列表
export function getList(params) {
return request({
url: prefix + '/getUserList',
method: 'get',
params
})
}
// 获取角色列表
export function getRoleList(params) {
return request({
url: prefix + '/getRoleList',
method: 'get',
params
})
}
// 增加角色
export function addRoles(params) {
return request({
url: prefix + '/addRole',
method: 'post',
params
})
}
// 编辑角色
export function editRoles(params) {
return request({
url: prefix + '/editRole',
method: 'post',
params
})
}
// 删除角色
export function deleteRoles(params) {
return request({
url: prefix + '/deleteRole',
method: 'post',
params
})
}
// 获取权限列表
export function getPermissionList(params) {
return request({
url: prefix + '/getAuthList',
method: 'get',
params
})
}
// 新增用户
export function adminUserCreate(data) {
return request({
url: prefix + '/userCreate',
method: 'post',
data
})
}
// 重置用户密码
export function resetAdminPassword(data) {
return request({
url: prefix + '/resetUserPassword',
method: 'post',
data
})
}
// 编辑用户
export function editUser(data) {
return request({
url: prefix + '/editUser',
method: 'post',
data
})
}
// 编辑用户角色
export function editAdminRoles(data) {
return request({
url: prefix + '/editUserRole',
method: 'post',
data
})
}
九、编辑view的vue文件,此处示例 :permission/admin.vue
查询
新增
{{ scope.row.id }}
{{ scope.row.account }}
{{
scope.row.is_admin == 1
? '超级管理员'
: scope.row.role.map(i => i.name).join()
}}
{{ scope.row.login_ip }}
{{ scope.row.login_time }}
{{
scope.row.status | statusFilter3
}}
编辑角色
重置密码
{{ scope.row.status | statusFilter4 }}
十、页面展示
总结
同理,结合ES6和Element-UI可以完成其它管理菜单
1.首先是理解vue_admin_template集成模板自带的主要文件
2.编辑登录页面的权限配置
3.生成侧边栏菜单管理
4.实现菜单管理页面的各个功能,常见的有增/删/改/除/查、禁用状态、上传图片等
5.新页面的实现:router[新增侧边栏菜单]
+api[配置接口]
+view[编辑菜单管理的功能]