Vue 后台管理项目13-权限管理实现
权限管理实现
1.角色列表页
1.1 完成roles组件静态布局
Elemenet组件 Table 表格 展开行:
通过设置 type="expand" 和 Scoped slot 可以开启展开行功能,el-table-column 的模板会被渲染成为展开行的内容,展开行可访问的属性与使用自定义列模板时的 Scoped slot 相同。
传送门http://element-cn.eleme.io/#/zh-CN/component/table
Elemenet组件 Tag 标签 可移除标签 :
传送们http://element-cn.eleme.io/#/zh-CN/component/tag
//直接复制user组件,重命名roles.vue,设置router.js,roles.vue组件demo
<template>
<div id="user">
<!-- 顶级面包屑 -->
<el-row>
<el-col :span="24">
<breadcrumb :level2="level2" :level3="level3"></breadcrumb>
</el-col>
</el-row>
<!-- 操纵框 -->
<el-row class="operate">
<el-col :span="24">
<el-button type="primary" plain @click="visible=true">添加角色</el-button>
</el-col>
</el-row>
<!-- 用户数据 -->
<el-row>
<el-col :sapn="24">
<el-table :data="userList" style="width: 100%" border>
<!-- 展开的table -->
<el-table-column type="expand">
<!-- 模板 -->
<template slot-scope="props">
<!-- {{props.row.children}} -->
<!-- 生成tag标签 -->
<!-- <el-tag closable>测试</el-tag> -->
<!-- 生成最左边的一级菜单 -->
<el-row v-for="item in props.row.children" :key="item.id">
<el-col :span="4">
<el-tag closable>{{item.authName}}</el-tag>
<!-- 小箭头 -->
<i class="el-icon-arrow-right"></i>
</el-col>
<el-col :span="20">
<!-- 二级菜单 需要当度占一行 用row嵌套即可-->
<el-row v-for="level2 in item.children" :key="level2.id">
<el-col :span="4">
<el-tag closable type="success">{{level2.authName}}</el-tag>
<!-- 小箭头 -->
<i class="el-icon-arrow-right"></i>
</el-col>
<el-col :span="20">
<el-tag closable v-for="level3 in level2.children" :key="level3.id" type="warning">{{level3.authName}}</el-tag>
<!-- 小箭头 -->
<i class="el-icon-arrow-right"></i>
</el-col>
</el-row>
</el-col>
</el-row>
<el-row v-if="props.row.children.length==0">
<el-col :span="24">没有分配权限</el-col>
</el-row>
</template>
</el-table-column>
<!-- 返回的数据没有对应的属性名,把prop删掉 -->
<!-- 增加type="index",会设置排序 -->
<el-table-column label="#" width="30" type="index"></el-table-column>
<el-table-column prop="roleName" label="角色名称" width="180"></el-table-column>
<el-table-column prop="roleDesc" label="角色描述" width="300"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button type="primary" plain size="mini" icon="el-icon-edit"></el-button>
<el-button type="danger" plain size="mini" icon="el-icon-check"></el-button>
<el-button type="warning" plain size="mini" icon="el-icon-delete"></el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
//写了name方便在Vue Devtools谷歌插件应用内找到对应的组件
name: "user",
data() {
return {
level2: "权限管理",
level3: "角色列表",
//用户的数据
userList: []
};
},
methods: {},
//生命周期函数,回调函数
async created() {
let res = await this.$axios.get("roles");
console.log(res);
this.userList = res.data.data;
}
};
</script>
<style lang="scss">
#user {
.operate {
background-color: #e8edf3;
}
.el-dialog {
width: 30%;
}
}
</style>
image
image
1.2 完成roles组件动态交互
Ⅰ. 添加角色:同user组件类似(完整代码在页面底部)
代码逻辑同user组件差不多,由于显示状态码201之前没在axios拦截器上设置,所以修改vue-axios.js
//原先的代码
<script>
if(response.data.meta.status===200){
//成功,提示返回的信息
Vue.prototype.$message.success(response.data.meta.msg);
}else if(response.data.meta.status===400){
//失败,提示返回的信息
Vue.prototype.$message.error(response.data.meta.msg);
}
</script>
//修改后代码
<script>
//if(response.data.meta.status===200){
//便捷写法,indexOf判断响应的状态码是否存在
if([200,201].indexOf(response.data.meta.status)){
//成功,提示返回的信息
Vue.prototype.$message.success(response.data.meta.msg);
}else if(response.data.meta.status===400){
//失败,提示返回的信息
Vue.prototype.$message.error(response.data.meta.msg);
}
</script>
Ⅱ. 删除角色:同user组件类似(完整代码在页面底部)
Ⅲ. 编辑角色:同user组件类似(完整代码在页面底部)
Ⅳ. 权限分配:
Elemenet组件 Tree 树形控件 默认展开和默认选中:
传送门http://element-cn.eleme.io/#/zh-CN/component/tree
常用 | 作用 |
---|---|
1.default-expand-all | 是否默认展开所有节点,默认为false |
2.default-checked-keys | 默认勾选的节点的 key 的数组 |
3.node-key | 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 |
4.ref="tree" | 获取和设置选中节点 |
Ⅴ. 删除单个 Tag 标签 权限
Elemenet组件 Tag 标签 事件 close,表示关闭 Tag 时触发的事件
@close="delCurrentTag(当前角色,当前权限)"
VI. 子组件修改权限后,父组件菜单栏不同步更新
解决方法 | 局限性 |
---|---|
1.window.location.reload() | 画面闪烁效果不好 |
2.$emit子组件向父组件通信 | 代码比方法1多,效果好,详情看Vue 后台管理项目11-Vue中的通信传值
|
//完整roles组件代码
<template>
<div id="roles">
<!-- 顶级面包屑 -->
<el-row>
<el-col :span="24">
<breadcrumb :level2="level2" :level3="level3"></breadcrumb>
</el-col>
</el-row>
<!-- 操纵框 -->
<el-row class="operate">
<el-col :span="24">
<el-button type="primary" plain @click="addVistable=true">添加角色</el-button>
</el-col>
</el-row>
<!-- 用户数据 -->
<el-row>
<el-col :sapn="24">
<el-table :data="rolesList" style="width: 100%" border>
<!-- 展开的table -->
<el-table-column type="expand">
<!-- 模板 -->
<template slot-scope="props">
<!-- {{props.row.children}} -->
<!-- 生成tag标签 -->
<!-- <el-tag closable>测试</el-tag> -->
<!-- 生成最左边的一级菜单 -->
<el-row v-for="item in props.row.children" :key="item.id">
<el-col :span="4">
<el-tag closable @close="delCurrentTag(props.row,item)">{{item.authName}}</el-tag>
<!-- 小箭头 -->
<i class="el-icon-arrow-right"></i>
</el-col>
<el-col :span="20">
<!-- 二级菜单 需要当度占一行 用row嵌套即可-->
<el-row v-for="level2 in item.children" :key="level2.id">
<el-col :span="4">
<el-tag closable @close="delCurrentTag(props.row,level2)" type="success">{{level2.authName}}</el-tag>
<!-- 小箭头 -->
<i class="el-icon-arrow-right"></i>
</el-col>
<el-col :span="20">
<el-tag closable @close="delCurrentTag(props.row,level3)" v-for="level3 in level2.children" :key="level3.id" type="warning">{{level3.authName}}</el-tag>
<!-- 小箭头 -->
<i class="el-icon-arrow-right"></i>
</el-col>
</el-row>
</el-col>
</el-row>
<el-row v-if="props.row.children.length==0">
<el-col :span="24">没有分配权限</el-col>
</el-row>
</template>
</el-table-column>
<!-- 返回的数据没有对应的属性名,把prop删掉 -->
<!-- 增加type="index",会设置排序 -->
<el-table-column label="#" width="40" type="index"></el-table-column>
<el-table-column prop="roleName" label="角色名称" width="180"></el-table-column>
<el-table-column prop="roleDesc" label="角色描述" width="300"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button type="primary" @click="showEditDialog(scope.row)" plain size="mini" icon="el-icon-edit"></el-button>
<el-button type="danger" @click="delOne(scope.row)" plain size="mini" icon="el-icon-delete"></el-button>
<el-button type="warning" plain size="mini" icon="el-icon-check" @click="showRolesDialog(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
<!-- 添加角色的对话框 -->
<el-dialog title="添加角色" :visible.sync="addVistable">
<!-- label-position控制label名的显示位置,注意要设置label-width才能生效 -->
<!-- 表单数据验证要加form上添加:rules="rules"属性 -->
<!-- 表单提交验证要加ref="userForm"属性,并将userForm传给提交按钮 -->
<el-form :model="addForm" :rules="rules" ref="addForm" label-position="left" label-width="80px">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="addForm.roleName" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="角色描述" prop="roleDesc">
<el-input v-model="addForm.roleDesc" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="addVistable = false">取 消</el-button>
<!-- 记住这边的userForm要加单引号 -->
<el-button type="primary" @click="submitForm('addForm')">确 定</el-button>
</div>
</el-dialog>
<!-- 编辑角色的对话框 -->
<el-dialog title="编辑角色" :visible.sync="editVistable">
<!-- 这里不能用addForm,因为addForm默认是空,而编辑点谁就有 -->
<el-form :model="editForm" :rules="rules" ref="editForm" label-position="left" label-width="80px">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="editForm.roleName" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="角色描述" prop="roleDesc">
<el-input v-model="editForm.roleDesc" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="editVistable = false">取 消</el-button>
<!-- 记住这边的userForm要加单引号 -->
<el-button type="primary" @click="submitEdit('editForm')">确 定</el-button>
</div>
</el-dialog>
<!-- 权限分配对话框 -->
<el-dialog title="分配权限" :visible.sync="roleVistable">
<!-- label-position控制label名的显示位置,注意要设置label-width才能生效 -->
<!-- 表单数据验证要加form上添加:rules="rules"属性 -->
<!-- 表单提交验证要加ref="userForm"属性,并将userForm传给提交按钮 -->
<el-row>
<el-col :span="24">
<!-- 树形结构 -->
<el-tree
:data="totalRoles"
show-checkbox
node-key="id"
:default-checked-keys="checkedKeys"
:props="defaultProps"
default-expand-all
ref="tree">
</el-tree>
</el-col>
</el-row>
<div slot="footer" class="dialog-footer">
<el-button @click="roleVistable = false">取 消</el-button>
<!-- 这里不是获取from了而是要获取树形结构的数据 -->
<el-button type="primary" @click="submitRoles">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
//写了name方便在Vue Devtools谷歌插件应用内找到对应的组件
name: "roles",
data() {
return {
level2: "权限管理",
level3: "角色列表",
//角色的数据
rolesList: [],
//新增框显示标记
addVistable:false,
//新增的数据
addForm: {
roleName:"",
roleDesc:""
},
//编辑的数据
editForm: {
roleName:"",
roleDesc:""
},
//编辑框显示标记
editVistable:false,
//权限框显示标记
roleVistable:false,
//所有的权限数据
totalRoles:[],
//提交权限分配需要,共享角色数据
selectRole:{},
//权限tree的对应关系
defaultProps:{
label:'authName'
},
//默认选中的字段
checkedKeys:[],
//表单验证规则
rules: {
roleName:[
{required:true,message:"请输入角色名",trigger:"blur"},
{min:3,max:32,message:"长度在3到32个字符",trigger:"blur"}
],
roleDesc:[
{required:true,message:"请输入角色描述",trigger:"blur"},
{min:3,max:32,message:"长度在3到32个字符",trigger:"blur"}
]
}
};
},
methods: {
//获取roles数据的方法
async getRoles() {
//依次往下执行
let res = await this.$axios.get("roles");
this.rolesList = res.data.data;
},
submitForm(addForm){
this.addVistable = true;
//直接获取表单验证
this.$refs[addForm].validate(async valid=>{
if(valid){
//提交数据
let res = await this.$axios.post('roles',this.addForm)
//关闭对话框
this.addVistable = false;
if(res.data.meta.status == 201) {
//成功重新获取数据
this.getRoles()
}else{
//提示失败,axios拦截器有了
}
}else {
this.$message.error('请检查数据');
return false;
}
})
},
delOne(data){
console.log(data);
let id = data.id;
//提示用户
this.$confirm('此操作将永久删除这类角色,是否继续?',"提示",{
confirmButtonText:"确定",
cancelButtonText:"取消",
type:"danger"
})
.then(async()=>{
//确定
let res = await this.$axios.delete(`roles/${id}`);
//console.log(res);
//重新获取用户数据即可
this.getRoles();
})
.catch(()=>{
//取消
})
},
//显示编辑框
showEditDialog(data){
this.editVistable = true;
this.editForm = data;
},
//保存编辑结果
submitEdit(formName){
this.$refs[formName].validate(async valid => {
if (valid) {
//验证成功
//调用接口
let res = await this.$axios.put(`roles/${this.editForm.id}`,this.editForm);
console.log(res);
if(res.data.meta.status===200){
//201表示成功请求并创建了新的资源,可以继续执行下一步
//关闭弹框
this.editVistable=false;
//重新获取数据
this.getRoles();
}
} else {
//验证失败
this.$message.error('请检查数据')
return false;
}
});
},
//显示权限框
async showRolesDialog(data){
//提交权限分配需要:保存正在编辑权限的角色数据
this.selectRole = data;
//显示对话框
this.roleVistable = true;
//获取所有权限列表
let res = await this.$axios.get('rights/tree');
console.log(res);
//保存数据到data中
this.totalRoles = res.data.data;
// 每次调用前先将this.checkedKeys变为空数组,避免被之前的权限覆盖
this.checkedKeys = []
//利用递归获取所有的权限id
let findChild = (father)=>{
//找后代
if(father.children){
// console.log('有儿子');
father.children.forEach(v=>{
// this.checkedKeys.push(v.id)
//每一个儿子都可能有后代
findChild(v);
})
}else{
//没有后代
// console.log('没有后代了');
//因为tree这个组件的问题,如果父id设置选中,会默认选中它的所有子节点
//只需要添加最底层的即可
this.checkedKeys.push(father.id)
}
}
//调用递归
// console.log(data);
findChild(data)
},
//提交权限分配
async submitRoles(){
//获取选中的权限对象数据
// console.log(this.$refs.tree.getCheckedNodes());
//获取半选中的节点对象数据
// console.log(this.$refs.tree.getHalfCheckedNodes());
//获取半选中的权限ID
// console.log(this.$refs.tree.getHalfCheckedKeys());
//获取选中的权限ID
// console.log(this.$refs.tree.getCheckedKeys());
//选中的key
let checkedKeys = this.$refs.tree.getCheckedKeys();
//半选中的key,父节点的id也要获取
let halfCheckedKeys = this.$refs.tree.getHalfCheckedKeys();
//两个数组→id1,id2,id3的字符串
//concat数组拼接方法
//用join将数组转换成分隔符隔开的字符串
let totalKeys = checkedKeys.concat(halfCheckedKeys);
let rids = totalKeys.join(',')
console.log(rids);
console.log(this.selectRole);
let res = await this.$axios.post(`roles/${this.selectRole.id}/rights`,{
rids
})
console.log(res);
//隐藏权限框
this.roleVistable = false;
//重新获取角色数据即可
this.getRoles();
},
//删除角色的指定权限
async delCurrentTag(role,right){
// console.log(role);
// console.log(right);
let res = await this.$axios.delete(`roles/${role.id}/rights/${right.id}`);
console.log(res);
//这边全部重新获取页面数据,会折叠起来,用户体验不好
// this.getRoles();
//可以直接把服务器返回的权限赋值给当前角色
role.children = res.data.data;
}
},
//生命周期函数,回调函数
async created() {
this.getRoles()
}
};
</script>
<style lang="scss">
#roles {
.operate {
background-color: #e8edf3;
}
.el-dialog {
width: 30%;
}
}
</style>
2.权限列表页
直接参照之前的页面写,没什么交互,效果如下:
Snipaste_2019-04-03_14-23-52.png
<template>
<div id="user">
<!-- 顶级面包屑 -->
<el-row>
<el-col :span="24">
<breadcrumb :level2="level2" :level3="level3"></breadcrumb>
</el-col>
</el-row>
<!-- 用户数据 -->
<el-row>
<el-col :sapn="24">
<el-table :data="rightList" style="width: 100%" border>
<!-- 返回的数据没有对应的属性名,把prop删掉 -->
<!-- 增加type="index",会设置排序 -->
<el-table-column label="#" width="40" type="index"></el-table-column>
<el-table-column prop="authName" label="权限名称" width="180"></el-table-column>
<el-table-column prop="path" label="路径" width="200"></el-table-column>
<el-table-column prop="level" label="层级" width="80">
<template slot-scope="scope">
<span v-if="scope.row.level==='0'">一级</span>
<span v-if="scope.row.level==='1'">二级</span>
<span v-if="scope.row.level==='2'">三级</span>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
//写了name方便在Vue Devtools谷歌插件应用内找到对应的组件
name: "user",
data() {
return {
level2: "权限管理",
level3: "权限列表",
//权限的数据
rightList: []
}
},
methods: {
},
//生命周期函数,回调函数
async created() {
//get请求需要通过params属性来传对象
let res = await this.$axios.get("rights/list");
console.log(res);
this.rightList = res.data.data
}
};
</script>
<style lang="scss">
#user {
.operate {
background-color: #e8edf3;
}
.el-dialog {
width:30%;
}
}
</style>
PS:有读者问我要这阶段写的源码,这里共享下百度云地址
链接:https://pan.baidu.com/s/1hJAZU28I2PFjueBfzd7M6g
提取码:4zn9
本文同步发表在我的个人博客:https://www.lubaojun.com/