这次的问题有这几点:
-
树型表格不能加载: 想用树型表格来加载权限列表但是死活不能展开也找不到按钮, 确认了api已经返回了正确的Json数据.
-
复选框全选与单选的对应效果不匹配: 用户角色分配功能区, 在为用户分配角色的时候发现勾选全选复选框之后剩下的复选框没有被选中的效果; 反之, 其余复选框全部勾选后,全选复选框没有选中效果.
-
用户登录系统选左侧菜单重复加载: 动态路由.
-
用户使用非超级管理员身份登录不能显示左侧菜单: 确认是api返回数据中"data"属性为空.
1. 树型表格不能加载的解决
#ElementUI版本太低需要升级
npm uninstall element-ui -S
npm install element-ui -S
详情可参考: https://blog.youkuaiyun.com/qq_45441466/article/details/114240954
2. 复选框Bug的解决
主要解决用户分配角色后台管理页面的bug
建议,可以把
data()
中绑定的isIndeterminate: false
初始化为这样,默认什么都不选中. 否则在没有任何复选框被选中情况下全选复选框默认是"-".
1、选复选框:
<el-checkbox
:indeterminate="isIndeterminate"
v-model="checkAll"
@change="handleCheckAllChange">全选</el-checkbox>
indeterminate
: false 不会选中; true 显示"-"表示未全选.v-model="checkAll"
: 双向绑定数据,判断全选复选框是否勾选.@change="handleCheckAllChange"
: 事件监听, 一旦用户点击选框就会触发事件.
2、复选框列表:
<el-checkbox-group
v-model="checkedCities"
@change="handleCheckedCitiesChange">
<el-checkbox v-for="city in cities" :label="city.id" :key="city.id">
{{city.roleName}}</el-checkbox>
</el-checkbox-group>
v-model="checkedCities"
: 已经被选择的复选框列表.@change="handleCheckedCitiesChange"
: 勾选复选框的触发事件.
bug位置--1
修复后代码
handleCheckAllChange(val) {
this.checkedCities = val ? this.getJsonToList(this.cities, "id") : [];
// this.checkedCities = val ? this.cities : [];
this.isIndeterminate = false;
}
这里使用一个已提供的功能函数, 从角色Json数据中取出id,组合成字符数组.因为this.checkedItems
需要是一个id字符数组才能被识别那些复选框被选中, 大概是因为复选框的label值设置为了id.
这里修复了以后就可以保证,全选复选框勾选之后,其余的复选框都自动被勾选.
bug位置--2
修复后代码
getById(userId) {
userApi.getAssign(userId).then((response) => {
var jsonObj = response.data.assignRoles;
this.checkedCities = this.getJsonToList(jsonObj, "id");
this.cities = response.data.allRolesList;
this.isIndeterminate =
this.checkedCities.length > 0 &&
this.checkedCities.length < this.cities.length;
this.checkAll = this.checkedCities.length === this.cities.length;
});
}
最后面添加两行代码, 设置isIndeterminate
有复选框选中的时候全选复选框会变成"-". 判断checkAll
是否为true,一旦全部子复选框被勾选,全选复选框就会自动变成√
.
这里修改完成以后就保证了自复选框全选后,全选复选框能显示勾选.
自此, 复选框有关的bug应该解决完毕, 如果其他功能部分的复选框有问题按此修改即可!
补充说明一个细节:
getJsonToList(json,key){
//把JSON字符串转成对象
var list = JSON.parse(JSON.stringify(json));
//var list = JSON.parse(json)
var strText = []
//遍历这个集合对象,获取key的值
for(var i = 0; i < list.length; i++){
strText.push(list[i][key])
}
return strText;
}
这段代码的意思是把json数据转成string再转成array对象,这样就可以遍历这个返回的数据取出其中的每相属性进行操作,这个操作很值得学习!
3. 左侧菜单重复加载的解决办法
这个很容易解决. 因为使用了动态路由,所以到 root 文件夹的 index.js 文件下把原来的
export const asyncRoutes = [...我们开始编写的静态路由]
直接把静态路由全部注释即可.然后改成 export const asyncRoutes = [ ] 因为路由规则要根据用户角色权限从后台api获取
4. 用户使用非超级管理员身份登录不能显示左侧菜单解决办法
首先需要确认数据库acl_permission
表中path和component列对应的路由信息是否和前台文件路径一致,如果之前的静态路由访问都能成功,那么就把数据库中的这两列信息和静态路由做比较即可.这样才能保证路由恩能够顺利跳转.
在浏览器的控制台中可以发现, 使用其他用户登录的时候, getMenu方法返回的data
的"permissionList"
属性值是空的. 很容易就把问题定位到了后台的 getMenu 接口(请求url:/admin/acl/index/menu
).
首先从控制台我们可以发现这个接口执行的一个sql语句
select
p.id,
p.pid,
p.name,
p.type,
p.permission_value,
path,
p.component,
p.icon,
p.status,
p.is_deleted,
p.gmt_create,
p.gmt_modified
from
acl_user_role ur
inner join
acl_role_permission rp
on rp.role_id = ur.role_id
inner join
acl_permission p
on p.id = rp.permission_id
where
ur.user_id = '2'
and ur.is_deleted = 0
and rp.is_deleted = 0
and p.is_deleted = 0
最开始我执行这个sql是没有数据的,经过排查是因为acl_user_role 的is_deleted字段为NULL 而不是0 ,在数据库中把这个字段设置为0就行了,由此可以看到查到数据:
注意:如果出现重复权限管理的路由,可能原因是 数据库中有相同的数据,可以在数据库查询去重加上 :distinct
那这就很好办了,排除了sql语句的问题. 这样问题基本就出在了数据的构造上, 因为我们这里要返回到前端的数据需要自行拼装成树型结构, 后台的很多方法都已经写好了逻辑也不是很复杂.
通过断点输出调试,发现存在bug的方法在于这个方法,位于PermissionServiceImpl.java
文件下的 selectPermissionByUserId() :
通过分别输出selectPermissionList
, permissionList
, result
发现数据丢失从permissionList
开始.
接着我们把问题聚焦于
PermissionHelper.bulid(selectPermissionList);
的build的方法, 其中代码如下
这是一个递归构造子树的逻辑, 根节点是pid为0的权限, 查看数据库以下就发现是一条默认name为"全部数据"的数据项. 然而, 我们在开始的sql查询中没有把他查询出来. 但这一个数据项是一个通用项,是全部菜单权限的根节点,所以我们可以手动对其进行添加.
这里提供两个解决办法:
方法一(更改后端代码):
注意: 修改的代码在PermissionServiceImpl.java
文件下
添加的代码:
boolean flag = false;
for (Permission p: selectPermissionList) {
if ("1".equals(p.getId())){
flag = true;
break;
}
}
if (!flag){
QueryWrapper<Permission> wrapper = new QueryWrapper<>();
wrapper.eq("id",1);
selectPermissionList.add(baseMapper.selectOne(wrapper));
}
为什么要加上这个判断呢? 大家应该很疑惑!因为在给角色分配资源的时候,后端并没有删除该角色之前的资源,而是再一次的插入。加上这个判断是为了防止
MemuHelper.bulid(permissionList); 中的
treeNodes.size() 大于1 而导致显示路由什么也没有!
在这你可以修改一下 给角色分配权限 的方法 加上
rolePermissionService.remove(new QueryWrapper<RolePermission>().eq("role_id", roleId));
我们查询出这个通用根,把它加入到权限列表中即可, 随后使用子树构造的方法就可以构造出路由菜单来了.
方法二(更改前端代码):
这里如果你使用的element-ui是项目提供的的那么树型菜单的源码已经被修改过了,可以直接获取半选中的父节点id,因此是没有这个问题的.但是如果你和我一样因为之前element-ui版本不能加载树型数组的原因,自己重新安装了最新版的element-ui那么就会有这个问题,但很幸运我们可以不用修改树型菜单的源码,直接使用一个函数就可以获取半选中的节点(父节点).
来到前端 /views/acl/role/roleForm.vue
, 这个页面就是分配权限的页面,我们需要修改保存按钮的逻辑代码,也就是save
方法
save(){
this.saveBtnDisabled = true
//vue elementUI tree树形控件获取父节点ID的实例
var ids = this.$refs.tree.getCheckedKeys().concat(this.$refs.tree.getHalfCheckedKeys()).join(",");
console.log(ids);
menu.doAssign(this.roleId, ids).then(response => {
if(response.success){
this.$message({
type:'success',
message:'保存成功'
})
this.$router.push({ path: '/acl/role/list' })
}
})
}
可以看到,我通过this.$refs.tree.getHalfCheckedKeys()
获取半选中节点,再使用了javascript
的arrary
的concat
方法把两个数组连接起来最后使用`join(",")变成字符串.
本文章参考:https://www.qiwu.ga/archives/gulibug