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>

难点:饿了吗组件 栅格布局

效果预览

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中的通信传值
Snipaste_2019-04-09_12-41-27.png
//完整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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348