之前在项目中做登录注册还有后台管理的,发现有一个比较重要的地方,就是权限问题,于是乎写了一些关于权限的内容,好了开始。
用户权限
根据用户权限不同,所能看到的页面和可操作性也不同
admin ——> 所有页面都可以看到
svip ——> 属于超级会员的权限
vip ——> 属于会员的权限
先单独写个服务器其中的token就是用于判断权限(当然实际肯定不是下面)
const express = require("express")
const app = express()
const url = require("url")
const vipLogin = require("./data/vip_login.json")
const adminLogin = require("./data/admin_login.json")
app.get("/login", (req, res) => {
const user = url.parse(req.url, true).query.user
if (user === 'admin') {
res.send(adminLogin)
} else {
res.send(vipLogin)
}
})
app.listen(2020, () => {
console.log("服务器运行在2020")
})
{
"code": 0,
"message": "登录成功",
"data": {
"token": "admin"
}
}
{
"code": 0,
"message": "登录成功",
"data": {
"token": "vip"
}
}
http.js
import axios from 'axios'
import store from '@/store/iindex.js'
import baseURL from './baseUrl'
import {
Message
} from 'element-ui'
import {
request,
response
} from 'express'
const instance = axios.create({
timeout: 5000,
// baseURL
})
// 添加请求拦截器
instance.interceptors.request.use(
function (config) {
if (store.state.UserToken) {
config.headers.Authorization = store.state.UserToken
}
return config
},
function (error) {
return Promise.reject(error)
}
)
// 响应拦截器异常处理
instance.interceptors.response.use(
response => {
return response.data
},
err => {
if (err && err.response) {
switch (err.response.status) {
case 400:
err.message = "请求出错"
break
case 401:
Message.warning({
message: "授权失败,请重新登录"
})
store.commit('LOGIN_OUT')
setTimeout(() => {
window.location.reload()
}, 1000)
return
case 403:
err.message = "拒绝访问"
break
case 404:
err.message = "请求错误,未找到该资源"
break
case 500:
err.message = "服务器出错"
break
}
} else {
err.message = "连接服务器失败"
}
Message.error({
message: err.message
})
return Promise.reject(err.response)
}
)
http.get() = function (url, options) {
return new Promise((resolve, reject) => {
instance
.get(url, options)
.then(response => {
if (response.code === 0) {
resolve(response.data)
} else {
Message.error({
message: response.message
})
reject(response.message)
}
})
.catch(e => {
console.log(e)
})
})
}
http.post = function (url, data, options) {
return new Promise((resolve, reject) => {
instance
.post(url, data, options)
.then(response => {
if (response.code === 0) {
resolve(response.data)
} else {
Message.error({
message: response.message
})
reject(response.message)
}
})
.catch(e => {
console.log(e)
})
})
}
export default http
vue.config.js
这里使用的是proxy跨域处理
module.exports = {
devServer: {
proxy: {
'/api': {
target: "http://localhost:2020",
changeOrigin: true,
pathRewrite: {
'^api': ''
}
}
}
}
}
store和本地存储token
methods: {
async login() {
// 网络请求
let data = await login(this.account);
let token = data.token;
// 本地
this.state.commit("LOGIN_IN", token);
this.$router.replace("/");
},
},
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './defaultState'
import mutations from './mustations'
Vue.use(Vuex)
export default new Vuex.Store({
state: state,
mutations
})
mutations.js
export default {
LOGIN_IN(state, token) {
state.UserToken = token
},
LOGIN_OUT(state) {
state.UserToken = ""
}
}
defaultState.js
export default {
get UserToken() {
return localStorage.getItem('token')
},
set UserToken(value) {
return localStorage.setItem('token', value)
}
}
路由权限的设置
在router文件中新建permission.js
// 路由权限
import router from './index'
import store from '../store/index'
router.beforeEach((to, form, next) => {
if (!store.state.UserToken) {
// 未登录 页面是否需要登录
if (to.matched.length > 0 && !to.matched.some(record => record.meta.requestAuth)) {
next()
} else {
next({
path: '/login'
})
}
} else {
// 用户已经登录 路由的访问权限:当前页面是否呈现对应的用户权限的页面,比如admin,vip等
next()
}
})
路由权限的业务
1、定义好全部的路由地址;
2、通过不同的后台请求不同的用户权限数据;
3、对用户权限作对比:请求数据 对比 全部路由, 把能对比上的部分取出来作为路由配置,而对不上的部分说明他不具备权限,忽略它;
关于element-UI 中权限展示用到的补充
以下布局是行列布局 —— for 循环嵌套
v-for 循环 渲染 el-tag,layout布局使用里面的el-row和el-col
<!-- 角色列表 -->
<el-table :data="rolesList" border stripe>
<!-- 展开列 -->
<el-table-column type="expand">
<template slot-scope="scope">
<el-row
:class="['bdbottom', i1 === 0 ? 'bdtop' : '', 'vcenter']"
v-for="(item1, i1) in scope.row.children"
:key="item1.id"
>
<!-- 一级权限 -->
<el-col :span="5">
<el-tag closable @close="removeRightById(scope.row, item1.id)">{{ item1.authName}}</el-tag>
<i class="el-icon-caret-right"></i>
</el-col>
<!-- 二级和三级 -->
<el-col :span="19">
<!-- 通过for循环 渲染二级权限 -->
<el-row
:class="[i2 === 0 ? '' : 'bdtop', 'vcenter']"
v-for="(item2, i2) in item1.children"
:key="item2.id"
>
<el-col :span="6 ">
<el-tag
type="success"
closable
@close="removeRightById(scope.row, item2.id)"
>{{ item2.authName }}</el-tag>
<i class="el-icon-caret-right"></i>
</el-col>
<el-col :span="18">
<el-tag
type="warning"
v-for="(item3) in item2.children"
:key="item3.id"
closable
@close="removeRightById(scope.row, item3.id)"
>{{ item3.authName}}</el-tag>
</el-col>
</el-row>
</el-col>
</el-row>
<!-- 无权限的提示 -->
<span v-if="scope.row.children.length === 0">未分配权限</span>
</template>
</el-table-column>
角色列表代码
<template>
<div>
<!-- 面包屑导航区 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>权限管理</el-breadcrumb-item>
<el-breadcrumb-item>角色列表</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片视图 -->
<el-card>
<!-- 添加角色按钮 -->
<el-row>
<el-col>
<el-button type="primary" @click="AddRoleDialogVisible=true">添加角色</el-button>
</el-col>
</el-row>
<!-- 角色列表 -->
<el-table :data="rolesList" border stripe>
<!-- 展开列 -->
<el-table-column type="expand">
<template slot-scope="scope">
<el-row
:class="['bdbottom', i1 === 0 ? 'bdtop' : '', 'vcenter']"
v-for="(item1, i1) in scope.row.children"
:key="item1.id"
>
<!-- 一级权限 -->
<el-col :span="5">
<el-tag closable @close="removeRightById(scope.row, item1.id)">{{ item1.authName}}</el-tag>
<i class="el-icon-caret-right"></i>
</el-col>
<!-- 二级和三级 -->
<el-col :span="19">
<!-- 通过for循环 渲染二级权限 -->
<el-row
:class="[i2 === 0 ? '' : 'bdtop', 'vcenter']"
v-for="(item2, i2) in item1.children"
:key="item2.id"
>
<el-col :span="6 ">
<el-tag
type="success"
closable
@close="removeRightById(scope.row, item2.id)"
>{{ item2.authName }}</el-tag>
<i class="el-icon-caret-right"></i>
</el-col>
<el-col :span="18">
<el-tag
type="warning"
v-for="(item3) in item2.children"
:key="item3.id"
closable
@close="removeRightById(scope.row, item3.id)"
>{{ item3.authName}}</el-tag>
</el-col>
</el-row>
</el-col>
</el-row>
</template>
</el-table-column>
<!-- 索引列 -->
<el-table-column type="index" label="#"></el-table-column>
<el-table-column label="角色名称" prop="roleName"></el-table-column>
<el-table-column label="角色描述" prop="roleDesc"></el-table-column>
<el-table-column label="操作" width="300px">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini" @click="showEditDialog(scope.row.id)">编辑</el-button>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="removeRoleById(scope.row.id)">删除</el-button>
<el-button
type="warning"
icon="el-icon-setting"
size="mini"
@click="showSetRightDialog(scope.row)"
>分配权限</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 分配权限 -->
<el-dialog
title="分配权限"
:visible.sync="setRightDialogVisible"
width="50%"
@close="setRightDialogClosed"
>
<!--
树形结构
data —— 数据源 []
show-checkbox —— 选择框
node-key —— 每个节点唯一标识,通常时data数据源中key名id
default-expand-all —— 默认展开 [要展开的节点 id] (这里是展开全部)
default-checked-keys —— 默认要选择的id
props —— 配置项 {label,children}
label 节点的文字标题 children 节点的子节点 值来源于data绑定的数据源中该数据的key 名 'label', 'children'
-->
<el-tree
:data="rightsList"
:props="treeProps"
ref="treeRef"
show-checkbox
node-key="id"
default-expand-all
:default-checked-keys="defKeys"
></el-tree>
<span slot="footer" class="dialog-footer">
<el-button @click="setRightDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="allotRights">确 定</el-button>
</span>
</el-dialog>
<!-- 添加角色对话框 -->
<el-dialog title="添加角色" :visible.sync="AddRoleDialogVisible" width="40%" @close="addRoleDialogClosed">
<el-form
:model="addRoleForm"
ref="addRoleFormRef"
:rules="addRoleFormRules"
label-width="100px"
>
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="addRoleForm.roleName"></el-input>
</el-form-item>
<el-form-item label="角色描述" prop="roleDesc">
<el-input v-model="addRoleForm.roleDesc"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="AddRoleDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addRoles">确 定</el-button>
</span>
</el-dialog>
<!-- 编辑角色对话框 -->
<el-dialog title="编辑角色" :visible.sync="editRoleDialogVisible" width="40%" @close="addRoleDialogClosed">
<el-form
:model="editRoleForm"
ref="editRoleFormRef"
:rules="editRoleFormRules"
label-width="100px"
>
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="editRoleForm.roleName"></el-input>
</el-form-item>
<el-form-item label="角色描述" prop="roleDesc">
<el-input v-model="editRoleForm.roleDesc"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editRoleDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="editRoles">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
data () {
return {
// 所有角色列表
rolesList: [],
// 分配权限框
setRightDialogVisible: false,
// 权限列表
rightsList: [],
// 树形控件的属性绑定对象
treeProps: {
label: 'authName',
children: 'children'
},
// 默认选中节点ID值
defKeys: [],
// 添加用户对话框
AddRoleDialogVisible: false,
// 添加角色表单
addRoleForm: {},
// 添加角色表单验证
addRoleFormRules: {
roleName: [
{ required: true, message: '请输入角色名称', trigger: 'blur' }
],
roleDesc: [
{ required: true, message: '请输入角色描述', trigger: 'blur' }
]
},
// 编辑角色信息
editRoleForm: {},
editRoleDialogVisible: false,
editRoleFormRules: {
roleName: [
{ required: true, message: '请输入角色名称', trigger: 'blur' }
],
roleDesc: [
{ required: true, message: '请输入角色描述', trigger: 'blur' }
]
},
// 当前即将分配权限的Id
roleId: 0
}
},
created () {
this.getRolesList()
},
methods: {
async getRolesList () {
const { data: res } = await this.$http.get('roles')
if (res.meta.status !== 200) {
return this.$message.error('获取角色列表失败!')
}
this.rolesList = res.data
},
// 根据ID删除对应的权限
async removeRightById (role, rightId) {
// 弹框提示 删除
const confirmResult = await this.$confirm(
'此操作将永久删除该权限, 是否继续?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).catch(err => err)
// 点击确定 返回值为:confirm
// 点击取消 返回值为: cancel
if (confirmResult !== 'confirm') {
this.$message.info('已取消权限删除')
}
const { data: res } = await this.$http.delete(
`roles/${role.id}/rights/${rightId}`
)
if (res.meta.status !== 200) {
return this.$message.error('删除权限失败!')
}
this.rolesList = res.data
// 不建议使用
this.getRolesList()
},
// 分配权限
async showSetRightDialog (role) {
this.roleId = role.id
// 获取角色的所有权限
const { data: res } = await this.$http.get('rights/tree')
if (res.meta.status !== 200) {
return this.$message.error('获取权限数据失败!')
}
// 获取权限树
this.rightsList = res.data
// console.log(res)
// 递归获取三级节点的id
this.getLeafkeys(role, this.defKeys)
this.setRightDialogVisible = true
},
// 通过递归 获取角色下三级权限的 id, 并保存到defKeys数组
getLeafkeys (node, arr) {
// 没有children属性,则是三级节点
if (!node.children) {
return arr.push(node.id)
}
node.children.forEach(item => this.getLeafkeys(item, arr))
},
// 权限对话框关闭事件
setRightDialogClosed () {
this.rightsList = []
},
// 添加角色对话框的关闭
addRoleDialogClosed () {
this.$refs.addRoleFormRef.resetFields()
},
// 添加角色
addRoles () {
this.$refs.addRoleFormRef.validate(async valid => {
if (!valid) return
const { data: res } = await this.$http.post('roles', this.addRoleForm)
if (res.meta.status !== 201) {
this.$message.error('添加角色失败!')
}
this.$message.success('添加角色成功!')
this.AddRoleDialogVisible = false
this.getRolesList()
})
},
// 删除角色
async removeRoleById (id) {
const confirmResult = await this.$confirm(
'此操作将永久删除该角色, 是否继续?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).catch(err => err)
if (confirmResult !== 'confirm') {
return this.$message.info('已取消删除')
}
const { data: res } = await this.$http.delete('roles/' + id)
if (res.meta.status !== 200) return this.$message.error('删除角色失败!')
this.$message.success('删除用户成功!')
this.getRolesList()
},
// 编辑角色
async showEditDialog (id) {
const { data: res } = await this.$http.get('roles/' + id)
if (res.meta.status !== 200) return this.$message.error('查询角色信息失败!')
this.editRoleForm = res.data
this.editRoleDialogVisible = true
},
editRoles () {
this.$refs.editRoleFormRef.validate(async valid => {
// console.log(valid)
// 表单预校验失败
if (!valid) return
const { data: res } = await this.$http.put(
'roles/' + this.editRoleForm.roleId,
{
roleName: this.editRoleForm.roleName,
roleDesc: this.editRoleForm.roleDesc
}
)
if (res.meta.status !== 200) {
this.$message.error('更新角色信息失败!')
}
// 隐藏编辑角色对话框
this.editRoleDialogVisible = false
this.$message.success('更新角色信息成功!')
this.getRolesList()
})
},
// 分配权限
async allotRights (roleId) {
// 获得当前选中和半选中的Id
const keys = [
...this.$refs.treeRef.getCheckedKeys(),
...this.$refs.treeRef.getHalfCheckedKeys()
]
// join() 方法用于把数组中的所有元素放入一个字符串
const idStr = keys.join(',')
const { data: res } = await this.$http.post(`roles/${this.roleId}/rights`, { rids: idStr })
if (res.meta.status !== 200) { return this.$message.error('分配权限失败!') }
this.$message.success('分配权限成功!')
// 更新视图
this.getRolesList()
// 关闭对话框
this.setRightDialogVisible = false
}
}
}
</script>
<style lang="less" scoped>
.el-tag {
margin: 7px;
}
.bdtop {
border-top: 1px solid #eee;
}
.bdbottom {
border-bottom: 1px solid #eee;
}
.vcenter {
display: flex;
align-items: center;
}
</style>
其中获取权限id 如果不用递归的方式:forEach遍历(不推荐)
// 不推荐下面这种,建议用递归方式 通过递归 获取角色下三级权限的 id, 并保存到defKeys数组 效果一致
let arrtemp2 = []
role.children.forEach(item1 => {
// arrtemp2.push(item.id)
item1.children.forEach(item2 => {
// arrtemp2.push(item2.id)
item2.children.forEach(item3 => {
arrtemp2.push(item3.id)
})
})
})
this.arrcheck = arrtemp2
el-tree标签的js方法
// el-tree 标签的方法
// 获取全选的 id的数组 arr1 getCheckedKeys()
let arr1 = this.$refs.treeRef.getCheckedKeys()
// 获取半选的 id的数组 arr2 getHalfCheckedKeys()
let arr2 = this.$refs.treeRef.getHalfCheckedKeys()
// 合并 arr1 和 arr2 数组
let arr =[...arr1, ...arr2]
// div js方法/属性 innerText
// 1、获取div —— dom元素
// 2、DOM.innerText
var div = document.getElementById('div')
div.innerText;
// el-tree js方法/属性 innerText
// 1、给要操作的dom元素 设置ref 属性值 input ref = "txt"
// 2、this.$refs.ref属性值.js方法名() this.$refs.txt.focus()
后台管理系统的左侧菜单栏显示
一般为动态从后端数据中获取的,并渲染到页面中;
<template>
<el-container>
<!-- 头部 -->
<el-header>
<div>
<img src="../assets/logo.png" alt />
<span>电商后台管理系统</span>
</div>
<el-button type="info" @click="logout">退出</el-button>
</el-header>
<!-- 主体 -->
<el-container>
<!-- 侧边栏 -->
<el-aside :width="isCollapse ? '64px' : '200px'">
<div class="toggle-button" @click="togleCollapse">|||</div>
<el-menu unique-opened :collapse="isCollapse" :collapse-transition="false" router :default-active="activePath" background-color="#333744" text-color="#fff" active-text-color="#409FFF">
<!-- :unique-opened="true"->只允许展开一个菜单 -->
<!-- :collapse-transition="false" -> 关闭动画 -->
<!-- router -> 导航开启路由模式 -->
<!-- 一级菜单 -->
<el-submenu :index="item.id+''" v-for="item in menuList" :key="item.id" >
<!-- 一级菜单的模板区域 -->
<template slot="title">
<i :class="iconObj[item.id]"></i>
<span>{{ item.authName}}</span>
</template>
<!-- 二级菜单 -->
<el-menu-item :index="'/' + subItem.path" v-for="subItem in item.children" :key="subItem.id" @click="saveNavState('/' + subItem.path)">
<!-- 导航开启路由模式:
将index值作为导航路由 -->
<!-- 二级菜单的模板区域 -->
<template slot="title">
<i class="el-icon-menu"></i>
<span>{{ subItem.authName}}</span>
</template>
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<!-- 内容主体 -->
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</template>
<script>
export default {
data () {
return {
// 左侧菜单数据
menuList: [],
iconObj: {
'125': 'iconfont icon-user',
'103': 'iconfont icon-tijikongjian',
'101': 'iconfont icon-shangpin',
'102': 'iconfont icon-danju',
'145': 'iconfont icon-baobiao'
},
// 默认不折叠
isCollapse: false,
// 被激活导航地址
activePath: ''
}
},
created () {
this.getMenuList()
this.activePath = window.sessionStorage.getItem('activePath')
},
methods: {
logout () {
// 清空token
window.sessionStorage.clear()
this.$router.push('/login')
},
// 获取请求菜单
async getMenuList () {
const { data: res } = await this.$http.get('menus')
if (res.meta.status !== 200) return this.$message.error(res.meta.msg)
this.menuList = res.data
// console.log(res)
},
// 菜单的折叠与展开
togleCollapse () {
this.isCollapse = !this.isCollapse
},
// 保存连接的激活地址
saveNavState (activePath) {
window.sessionStorage.setItem('activePath', activePath)
}
}
}
</script>
<style lang="less" scoped>
.el-container {
height: 100%;
}
.el-header {
background-color: #373f41;
display: flex;
justify-content: space-between;
padding-left: 0;
align-items: center;
color: #fff;
font-size: 20px;
> div {
display: flex;
align-items: center;
img {
height: 40px;
}
span {
margin-left: 15px;
}
}
}
.el-aside {
background-color: #333744;
.el-menu {
border: none;
}
}
.el-main {
background-color: #eaedf1;
}
.iconfont{
margin-right: 10px;
}
.toggle-button {
background-color: #4A5064;
font-size: 10px;
line-height: 24px;
color: #fff;
text-align: center;
letter-spacing: 0.2em;
// 鼠标放上去变成小手
cursor: pointer;
}
</style>
不同角色用户登录,显示对应的权限
每个角色有不同的权限
1、添加用户 —— 分配角色;
2、回到登录页 登录新用户——> token;
3、渲染home新组件的侧边栏,使用header中的token;
4、发送getMenu()也会使用header;
以上只是整理部分,可能比较混乱,当然权限并没那么简单,但是也并不复杂,后续继续更新!!