最近项目需要用到树形表格,咱们公司用的是 elment-plus 这个ui库暂时还不成熟,有好多bug,需要自己处理
注意的地方
*1,树状表格的节点都需要自己手动设置,调用 elemet-plus库抛出的方法toggleRowSelection;
*2. 表格里数据在显示的时候不能变动,不然就会出现问题,所以在此之间不能去更改每一行数据的值;
*3. 表格没有暴露出来半选状态的方法,需要自己去设置;
思路
*(1)上面说了,表格没有暴露出来半选状态的方法,所以我们要通过设置类名的方法自己设置,咱们需要用到element-plus 库抛出的属性 :row-class-name=“rowClassNameFun” ;
*(2)由于不能修改显示的时候修改表格数据,所以我们需要用map对象将所有的数据初始化,这个map对象包含的key->value;就是所有行的 id->false; 当对象保存的状态为 id-> indeterminate 就是用来设置半选;
(3).核心就是点击复选框时候的逻辑, 也就是这个事件@selectAll;判断当前操作行有没有children列表,有就循环chilldern列表将每个子数据的状态设置成当前操作行的状态;没有的话,就计算parentList 里的所有子数据的勾选情况,设置相应的状态(选中 / 非选中/ 半选);
<template>
<div class="userGroupList">
<div class="selectUser">
<el-checkbox v-model="allCheckedOutUser" :disabled='IsDisabled' @change='handleAllCheckedOutUser'>选择全部外部用户</el-checkbox>
<div><span>已选用户 </span><span>{{ selectBIAll }}</span></div>
</div>
<div class="userList">
<el-table
ref="multipleTable"
:data="userGroupsData"
v-loading="loading"
row-key="id"
:border='false'
style="width: 100%"
:select-on-indeterminate="true"
:row-class-name="rowClassNameFun"
:header-row-class-name="headerRowClassName"
:tree-props="{ children: 'userList', hasChildren: 'hasChildren' }"
@select-all="selectAll"
@select="handleSelect"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55"> </el-table-column>
<el-table-column
prop="name"
label="组/姓名"
width="240"
>
<template #default="scope">
<span>
<IconFont
v-if="scope.row.type === 2"
colorful
customClass="dir-icon"
icon="wenjianshuxing_guanlixiaozu"
/>
<IconFont
v-else-if="scope.row.type === 1"
colorful
customClass="dir-icon"
icon="wenjianshuxing_chayuexiaozu1"
/>
<IconFont
v-else-if="scope.row.type === 0"
colorful
customClass="dir-icon"
icon="wenjianshuxing_guanliyuanxiaozu"
/>
<span @click="goDetail(scope.row)" :style="{cursor:(scope.row.tenantType? 'pointer':''),color:(scope.row.tenantType?'#11a870':'')}">{{ scope.row.name }}</span><span class="out-user" v-if="scope.row.tenantType === 2">外部</span></span
>
</template>
</el-table-column>
<el-table-column prop="roleId" label="角色" width="120">
<template #default="scope">
{{ getRoleName(scope.row.roleId) }}
</template>
</el-table-column>
<el-table-column prop="address" label="类型">
<template #default="scope">
<span v-if="scope.row.tenantType == 1">企业</span>
<span v-else-if="scope.row.tenantType == 2">外部</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="公司">
<template #default="scope">
<span v-if="scope.row.company">{{scope.row.company}}</span>
<span v-else>-</span>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
import getRoleName from "@/mixin/getRoleName";
import IconFont from "@/components/common/Iconfont/index.vue";
import { ElMessage,ElMessageBox } from 'element-plus'
import {
queryAllGroupByLibrary,
cleanLibraryUser,
} from "@/services/userAndGroups/list";
import { reactive, ref, onMounted, nextTick, getCurrentInstance, computed, h } from 'vue';
import { useRoute, useRouter } from 'vue-router'
export default {
name:'UserGroupList',
props: ["detail"],
emits:['closeCleanUserDialog'],
setup(props,{ attrs, slots, emit }){
let selectedGroups = ref([]);
let userGroupsData = ref([]);
let loading = ref(false);
let ckeckAll = ref(false);
let allCheckedOutUser = ref(false);
let isIndeterminateMap = ref({});
let multipleTable = ref(null);
let selectTotal = ref(0);
let { ctx } = getCurrentInstance();
const $route = useRoute();//获取当前路由信息;
const $router = useRouter();//获取路由实例;
const selectBIAll = computed(()=>{
selectTotal.value = calcFilterTotal(selectedGroups.value,'userList');
let allUserTotal = calcFilterTotal(userGroupsData.value,'userSize', false);
return `(${ selectTotal.value } / ${ allUserTotal })`
});
//计算外部用户
const getOutUserData = computed(()=>{
let arr = [];
userGroupsData.value.forEach((item) => {
if(item.userList && item.userList.length > 0){
item.userList.forEach(val=>{
if(val?.tenantType == 2){
arr.push(val)
}
})
}
});
return arr || [];
});
//计算外部用户复选框是否启用
const IsDisabled = computed(()=>{
return getOutUserData.value.length ? false : true;
})
//计算用户个数;
const calcFilterTotal = ( list, filterName , isSelect=true)=>{
if(isSelect){
let arr = list.filter(item=>{
return !!!item[filterName];
})
return arr?.length || 0;
}else{
let sum = 0;
list.forEach(item=>{
sum +=item.userSize;
})
return sum;
}
};
// 选中删除
const userDelete = ()=> {
if (selectTotal.value == 0) {
ElMessage.warning("请选择要清理的用户!");
return;
} else {
const delDatas = [];
selectedGroups.value.forEach((value) => {
const table = {
groupId: value?.groupId,
userList: [value.id],
libraryId:props.detail.id
};
delDatas.push(table);
});
let delContent = `确认清理所选${selectTotal.value}个用户?`;
open(delContent,delDatas);
}
};
const open = (delContent,delDatas)=>{
ElMessageBox({
title: '清理用户',
message: h('p', null,[
h('p', null , delContent),
h('span', null, '清理后,用户将无法访问该资料室'),
]),
showCancelButton: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
dangerouslyUseHTMLString: true,
distinguishCancelAndClose: true,
buttonSize:'small',
type: "warning",
customClass: "deleteClass",
beforeClose:async(action, instance, done) => {
if (action === 'confirm') {
const res = await cleanLibraryUser({
data: delDatas,
});
if (res.code === 0) {
emit('closeCleanUserDialog')
ElMessage.success("删除成功");
queryTableData();
}
done();
}else{
done();
}
}
})
};
const handleSelectionChange = (selected)=>{
selectedGroups.value = selected;
checkOutUserAll(selected);
}
//全选按钮
const selectAll = (selection)=> {
let isAllSelect = checkIsAllSelect();
userGroupsData.value.forEach((item) => {
isIndeterminateMap.value[item.id] = isAllSelect;
toggleSelection(item, !isAllSelect)
handleSelect(selection, item);
});
};
//选择复选框时候
const handleSelect = (selection, row) => {
setRowIsSelect(selection,row);
};
const setRowIsSelect = (selection,row)=>{
//当点击父级点复选框时,当前的状态可能为未知状态,所以当前行状态设为false并选中,即可实现子级点全选效果
let isSelect = isIndeterminateMap.value[row.id];
if (isSelect == "indeterminate") {
isIndeterminateMap.value[row.id] = false;
isSelect = false;
toggleSelection(row, true)
}
isIndeterminateMap.value[row.id] = !isIndeterminateMap.value[row.id];//基础类型
isSelect = !isSelect;
//判断操作的是子级点复选框还是父级点复选框,如果是父级点,则控制子级点的全选和不全选
if (row.userList && row.userList.length > 0) {
row.userList.forEach((item) => {
isIndeterminateMap.value[item.id] = isSelect;
toggleSelection(item, isSelect)
});
} else {
//操作的是子节点 1、获取父节点
// 2、判断子节点选中个数,如果全部选中则父节点设为选中状态,如果都不选中,则为不选中状态,如果部分选择,则设为不明确状态
let groupId = row.groupId;
toggleSelection(row, isSelect)
userGroupsData.value.forEach((item) => {
if (item.id == groupId) {
let isALLTrue = selectStatus(item,true);
let isALLFalse = selectStatus(item,false);
if ( isALLTrue ){
isIndeterminateMap.value[item.id] = true;
toggleSelection(item, true)
} else if ( isALLFalse ) {
isIndeterminateMap.value[item.id] = false;
toggleSelection(item, false)
} else {
isIndeterminateMap.value[item.id] = "indeterminate";
}
}
});
}
};
//判断外部用户是否全选
const checkOutUserAll = (selection)=>{
let isOutUserAll=[];
getOutUserData.value.forEach(item=>{
isOutUserAll.push(selection.includes(item));
})
// debugger
let res = isOutUserAll.every(item=>{
return true == item
})
allCheckedOutUser.value = res;
};
const selectStatus = (item,status=true)=>{
let isAllSelect = [];
item.userList.forEach((childrenItem) => {
isAllSelect.push(isIndeterminateMap.value[childrenItem.id]);
});
let isStauts = isAllSelect.every((selectItem) => {
return status == selectItem;
})
return isStauts
};
const toggleSelection = async (row, select)=> {
if (row) {
await nextTick();
multipleTable.value && multipleTable.value.toggleRowSelection(row, select);
}
};
const checkIsAllSelect = ()=> {
let oneProductIsSelect = [];
userGroupsData.value.forEach((item) => {
let isSelect = isIndeterminateMap.value[item.id];
oneProductIsSelect.push(isSelect);
});
//判断一级是否是全选.如果一级全为true,则设置为取消全选,否则全选
let isAllSelect = oneProductIsSelect.every((selectStatusItem) => {
return true == selectStatusItem;
});
return isAllSelect;
};
//选择全部外部用户
const handleAllCheckedOutUser = (val)=>{
getOutUserData.value.forEach(item=>{
isIndeterminateMap.value[item.id] = val;
toggleSelection(item,val)
})
userGroupsData.value.forEach((item) => {
let isALLTrue = selectStatus(item,true);
let isALLFalse = selectStatus(item,false);
if ( isALLTrue ){
isIndeterminateMap.value[item.id] = true;
toggleSelection(item, true)
} else if ( isALLFalse ) {
isIndeterminateMap.value[item.id] = false;
toggleSelection(item, false)
} else {
isIndeterminateMap.value[item.id] = "indeterminate";
}
});
};
const rowClassNameFun = ({ row })=> {
let isSelect = isIndeterminateMap.value[row.id];
if (isSelect == "indeterminate") {
return "indeterminate";
}
};
const headerRowClassName = ({ row })=>{
let oneProductIsSelect = [];
userGroupsData.value.forEach((item) => {
oneProductIsSelect.push(isIndeterminateMap.value[item.id])
// oneProductIsSelect.push(item.isSelect);
});
if (oneProductIsSelect.includes("indeterminate")) {
return "indeterminate";
}
return "";
};
// 查询用户和组列表
const queryTableData = async ()=> {
loading.value = true;
const tableData = {
libraryId: props.detail?.id || $route.params.id,
pageSize: 999,
pageNum: 1,
};
const res = await queryAllGroupByLibrary({
data: tableData,
});
if (res.code === 0) {
const { result } = res;
userGroupsData.value = result?.groupResList.filter(item=>{
return item.name != '管理小组';
});
userGroupsData.value.forEach((item)=>{
// item.isSelect = false; //默认为不选中
isIndeterminateMap.value[item.id] = false;
if(item.userList && item.userList.length>0){
item.userList.forEach(user=>{
isIndeterminateMap.value[user.id] = false;
})
}
})
loading.value = false;
nextTick(() => {
multipleTable.value && multipleTable.value.clearSelection();
});
}
};
const goDetail = (event) => {
if(event.tenantType){
$router.push(`/userDetail/${props.detail.id}/${event.id}`);
}else {
return
}
};
onMounted(() => {
queryTableData();
});
return {
loading,
ckeckAll,
allCheckedOutUser,
selectedGroups,
userGroupsData,
multipleTable,
userDelete,
goDetail,
selectBIAll,
IsDisabled,
handleSelectionChange,
handleSelect,
selectAll,
queryTableData,
rowClassNameFun,
headerRowClassName,
handleAllCheckedOutUser
}
},
mixins:[getRoleName],
components:{
IconFont
}
}
</script>
<style lang='scss'>
.userList {
.indeterminate {
.el-checkbox__input{
.el-checkbox__inner {
background-color: $color-primary !important;
border-color: $color-primary !important;
color: #fff !important;
&::after {
border-color: #C0C4CC !important;
background-color: #C0C4CC;
content: "";
position: absolute;
display: block;
background-color: #fff;
height: 2px;
transform: scale(0.5);
left: 0;
right: 0;
top: 5px;
width: auto !important;
}
}
}
}
}
</style>
<style lang='scss' scoped>
IconFont组件需要用的也可以用
<template>
<svg v-if="colorful" :class="['iconfont-svg', customClass]" aria-hidden="true">
<use :xlink:href="`#icon-${icon}`"></use>
</svg>
<i v-else :class="['iconfont', `icon-${icon}`, customClass]"></i>
</template>
<script>
export default {
name: 'iconFont',
props: {
icon: String,
colorful: {
type: Boolean,
default: () => false
},
customClass: {
type: String,
default: () => ''
}
}
}
</script>