树形结构的勾选、取消勾选、删除、清空已选、回显、禁用
基本页面:
分为上传文件和编辑的页面
代码实现要点:
-
上传文件页面:
-
点开选择范围弹窗,三个radio单选框都为可选状态,默认显示的是第一个单选框(按机构项目组)对应的内容
-
可以对树形数据进行勾选,勾选的数据展示在右边已选框内
-
可以对树形数据进行全选,取消全选
-
右边已选框内数据有对应的删除按钮,可以点击删除按钮或对已选数据进行删除
-
可以点击清空已选按钮对已选数据进行一次性全部删除
-
在左边树形数据取消勾选,右边已选框的数据也会对应发生变化
-
点击确定按钮但未确认上传时,再次打开弹框,页面中应把上一次选择的数据展示出来,且不能禁用,并同步展示在右边已选框内,同样可以对数据进行以上操作
-
点击确定并在上一层点击确认上传按钮,已经勾选的数据就会传给后端,同步新增到了页面中,后续可以对数据进行编辑操作来修改数据
-
-
编辑页面:
-
点开选择范围弹窗,只有从后端返回的对应的单选框类型的单选框才能可选,其他的单选框都为禁用状态
-
树形数据显示的也是可选单选框对应的树形数据
-
从后端得到的上一次确认上传的数据,对数据进行禁用,即只能继续添加不能删除原有的
-
从后端得到的上一次确认上传的数据,对数据同步展示在右边已选框
-
从后端得到的上一次确认上传的数据,在右边已选框内不能存在删除按钮
-
可以继续勾选想要勾选的数据,会同步展示在右边已选框,有删除按钮,可以删除
-
点击全选,全选数据,点击反选,把除了从后端得到的上一次确认上传的数据都取消勾选,同时右边已选框也要同步展示
-
点击清空已选,把除了从后端得到的上一次确认上传的数据在右边已选框内删除,同时左边的树形结构也要同步取消勾选数据
-
点击确定按钮但未确认上传时,再次打开弹框,显示的勾选数据为上次点击确定前勾选的数据,禁用的数据也仅为从后端得到的上一次确认上传的数据,右边已选框也展示的是上次点击确定前勾选的数据,没有删除按钮的也仅为从后端得到的上一次确认上传的数据
-
点击确定并在上一层点击确认上传按钮,已经勾选的数据就会传给后端,同步更新到页面中,后续也可以对数据进行编辑操作来修改数据
-
代码设计:
单选框用到的是el-radio组件
<div class="person_top_nav">
<span>选择签署范围</span>
<el-radio-group style="margin-left: 12px" v-model="searchRange" @change="handleSearchChange">
<el-radio style="max-width: 90px" :disabled="!(SelectedType == 'clientOrganization') && disabledArr.length > 0" :label="1">
按机构项目组
</el-radio>
<el-radio style="max-width: 60px" :disabled="!(SelectedType == 'organization') && disabledArr.length > 0" :label="2">
按事业部
</el-radio>
<el-radio style="max-width: 90px" :disabled="!(SelectedType == 'branchCompany') && disabledArr.length > 0" :label="3">
按合约归属公司
</el-radio>
</el-radio-group>
</div>
绑定的是searchRange,点击事件调用的函数是handleSearchChange()函数
:disabled="!(SelectedType == 'clientOrganization') && disabledArr.length > 0" 是上传文件和编辑页面对单选框是否禁用的设置,即:第一个按机构项目组被禁用的时候是:在编辑页面disabledArr.length > 0,并且得到的从后端返回的类型不等于clientOrganization(机构项目组)
左边树形数据和右边已选框用到的都是el-tree组件
左边树形数据:
<el-tree
:data="dataTree" // 展示数据
:default-checked-keys="checkboxId" // 默认勾选节点
show-checkbox // 节点可被选择
v-loading="loading"
class="filter-tree" // 绑定类
node-key="value" // 树节点唯一标识
ref="tree" // 绑定树,可后续调用方法
:props="defaultProps" // 配置选项 defaultProps: { children: 'children', label: 'label'}
@check-change="getCheckedNodes" // 节点选中状态发生变化时的回调
>
</el-tree>
data属性是绑定展示的数据,这里绑定的是dataTree
default-expand-all属性是绑定默认勾选的节点的 key 的数组,绑定的数据为checkboxId,是默认勾选的数据value数组
node-key属性是每个树节点用来作为唯一标识的属性,整棵树应该是唯一的,这里绑定的是节点的value
check-change事件是节点选中状态发生变化时的回调,绑定着函数getCheckedNodes()
右边已选框数据:
<el-tree :data="isSelectedData" :props="defaultProps" :default-expand-all="true">
<span class="custom-tree-node" slot-scope="{ node, data }">
<span class="tree_label">{{ node.label }}</span>
<el-button
class="remove_button"
v-if="!(node.disabled || data.ifShow)"
type="text"
size="medium"
@click="() => remove(node, data)"
>
×
</el-button>
</span>
</el-tree>
绑定的数据为isSelectedData,default-expand-all默认展开所有节点
重要数据:
dataTree:调用选择radio的接口数据,也就是一棵树
checkboxId:左边树的默认勾选节点,是一个value数组
isSelectedData:右边已选框的展示数据,也就是左边树的数据
searchRange:radio单选框绑定的数据
disabledArr:原始数据,应被禁用和默认勾选,展示【从父组件传入】
peList:上一次勾选提交的数据,应被默认勾选和展示(每一次点击确定后都会被改变)【从父组件传入】
oldDisabledArr:disabledArr得来
oldDisabledArrId:disabledArr得来
SelectedType:从父组件传入的选择范围类型
用到的el-tree方法:
setCheckedNodes():设置目前勾选的节点,使用此方法必须设置 node-key 属性,(nodes) 接收勾选节点数据的数组
getCheckedNodes():若节点可被选择(即
show-checkbox
为true
),则返回目前被选中的节点所组成的数组,(leafOnly, includeHalfChecked) 接收两个 boolean 类型的参数,1. 是否只是叶子节点,默认值为false
2. 是否包含半选节点,默认值为false
setChecked():通过 key / data 设置某个节点的勾选状态,使用此方法必须设置 node-key 属性,(key/data, checked, deep) 接收三个参数,1. 勾选节点的 key 或者 data 2. boolean 类型,节点是否选中 3. boolean 类型,是否设置子节点 ,默认为 false
代码执行流程:
编辑页面:
点击选择范围弹框,调用open()方法,打开弹框,根据是否有从父组件传入的peList(上一次选择的数据)来判断是上传文件还是编辑页面
上传文件:默认调用机构项目组接口和展示机构项目组树形数据
编辑:调用从父组件传入的SelectedType(选择范围类型),在handleSearchChange()函数内调用对应的接口和展示对应的树形数据
async opened() { // 存在已选数据 this.disabledArr.forEach((item, index) => { this.$set(this.oldDisabledArr, index, item); }); this.disabledArr.forEach((item, index) => { this.$set(this.oldDisabledArrId, index, item.value); }); // 打开选择范围弹框 this.dialogVisible = true; // clientOrganization 机构项目组 organization 事业部 branchCompany 合约归属公司 if (!this.peList) { // 上传文件 默认选中机构项目组 await this.getInstitutionIssueList(); } else { // 编辑 if (this.SelectedType === 'clientOrganization') { this.searchRange = 1; } else if (this.SelectedType === 'organization') { this.searchRange = 2; } else if (this.SelectedType === 'branchCompany') { this.searchRange = 3; } // 调用对应接口 await this.handleSearchChange(); // 初始化 this.init(); } }
调用init()函数,执行的逻辑为
init() { this.$nextTick(() => { // 给checkboxId赋值 this.checkboxId = []; this.peList.map((item, index) => this.$set(this.checkboxId, index, item.value)); this.$refs.tree.setCheckedNodes(this.peList); this.$forceUpdate(); // 把禁用节点显示在右边 this.getCheckedNodes(); // 禁用已选节点 this.reShowDisabled(this.dataTree, this.oldDisabledArrId); // 去掉父节点的删除按钮 this.getParentValueList(); }); }
初始化checkboxId,从peList中得到(上一次选择的数据)【注意:不是从disabledArr中得到,因为如果第一次勾选数据后点击确认,以后在确认上传前再次点击选择范围,页面不会回显上一次勾选的数据,但是确认上传后,提交给后端的又是上一次勾选的数据,不符合逻辑】
通过el-tree的方法setCheckedNodes()设置目前勾选的节点【注意:一定要执行这一步,否则后续指定删除、清空已选、取消勾选并确定后,在确认上传前的再次点击选择范围,被删除的数据节点还是会被勾选和展示在已选框,猜测的原因是没有取消勾选删除的节点数据,即再次设置勾选节点,而不是通过default-expand-all属性来自动勾选】
调用getCheckedNodes()函数,把上一次勾选提交的数据节点展示在右边已选框
getCheckedNodes()函数,【这个函数也绑定着左边树的change回调】,里面的逻辑为:调用el-tree的getCheckedNodes()方法,获取目前被选中的节点的数组checkedList,然后用checkedList作为参数调用depthTraversal()函数,递归来返回选中的树形节点,并赋值给isSelectedData,就同步展示在右边已选框了
// 选择数据 -> 展示 el-tree回调 getCheckedNodes() { const checkedList = this.$refs.tree.getCheckedNodes(false, true); this.isSelectedData = this.depthTraversal(JSON.parse(JSON.stringify(this.dataTree)), checkedList); } // 返回选中的数据列表 depthTraversal(list, filterList) { const addObj = (list) => { return list.filter((v) => { if (v.children && filterList.findIndex((val) => val.value === v.value) !== -1) { v.children = addObj(v.children); return v; } return filterList.findIndex((val) => val.value === v.value) !== -1; }); }; return addObj(list); }
调用reShowDisabled()函数,把禁用节点进行禁用
传入的参数为oldDisabledArrId(感觉等同于checkboxId),利用闭包写一个递归函数并调用,找到value在oldDisabledArrId数组中的节点:
给它设置disabled为true的属性,实现禁用
调用setdisabledParentList()函数,把它的parentValue放入disabledParentList中,便于后续删除掉父节点的删除按钮【存在逻辑是SelectedType是branchCompany时取反parentValue,树形节点的唯一性问题】
// 回显禁用节点 checkboxId 禁用节点value数组 reShowDisabled(list, checkboxId) { this.disabledParentList = []; const disabledObj = (list) => { list.forEach((v) => { if (checkboxId.findIndex((val) => val === v.value) !== -1) { this.$set(v, 'disabled', true); this.setdisabledParentList(v); // console.log('禁用', v); } if (v.children) { disabledObj(v.children); } }); }; disabledObj(list); }
调用getParentValueList()函数,去掉父节点的删除按钮
利用闭包写递归函数,在树节点中找到value在disabledParentList中的节点,为它设置ifShow为true的属性,实现父节点删除按钮的去掉
// 获取需要隐藏删除按钮的所有父节点,设置ifShow属性 getParentValueList() { const ifShowObj = (list) => { list.forEach((v) => { if (this.disabledParentList.findIndex((val) => val === v.value) !== -1) { this.$set(v, 'ifShow', true); } if (v.children) { ifShowObj(v.children); } }); }; ifShowObj(this.dataTree); }
初始化完成
对左边树形数据进行勾选和取消勾选,都会触发树的change事件,调用getCheckedNodes()函数,逻辑如上
// 选择数据 -> 展示 el-tree回调 getCheckedNodes() { const checkedList = this.$refs.tree.getCheckedNodes(false, true); this.isSelectedData = this.depthTraversal(JSON.parse(JSON.stringify(this.dataTree)), checkedList); }
对右边已选框的数据点击删除按钮进行删除,调用remove()函数,调用el-tree的setChecked()方法,取消对对应节点的勾选,然后再次调用getCheckedNodes()函数
// delete 右边移除选中的数据 remove(node, data) { this.$refs.tree.setChecked(data.value, false, true); this.getCheckedNodes(); }
点击清空已选按钮,调用empty()函数,在dataTree中过滤出除了禁用节点(oldDisabledArr/disabledArr)以外的节点,通过el-tree的setChecked()方法设置节点为取消勾选
//点击清空时清空所有人员跟人员id empty() { // 过滤出除了所有外的数据,取消勾选 const aa = this.dataTree.filter((item) => this.oldDisabledArr.findIndex((val) => val.value === item.value) === -1); aa.map((item) => this.$refs.tree.setChecked(item.value, false, true)); this.checkAll = false; }
点击全选或反选按钮,调用handleCheckAllChange()函数,也是在dataTree中过滤出除了禁用节点(oldDisabledArr/disabledArr)以外的节点:
若是全选,则用setChecked()方法设置节点为勾选
若是反选,则用setChecked()方法设置节点为取消勾选
// 全选反选 handleCheckAllChange() { console.log('checkAll', this.checkAll); const aa = this.dataTree.filter((item) => this.oldDisabledArr.findIndex((val) => val.value === item.value) === -1); if (this.checkAll) { console.log('全选'); aa.map((item) => this.$refs.tree.setChecked(item.value, true, true)); } else { console.log('反选'); console.log(this.disabledArr); aa.map((item) => this.$refs.tree.setChecked(item.value, false, true)); } }
点击确定按钮,调用surePromoter()方法,校验通过后,通过getCheckedNodes()方法得到目前所有被选择的节点赋值给resultSelectedData:
若是上传文件,则通过$emit传给父组件对应的radio类型和resultSelectedData
若是编辑,则通过$emit传给父组件SelectedType和resultSelectedData
然后关闭选择范围弹框
surePromoter() { if (this.isSelectedData.length === 0) { this.$message({ message: '请选择成员!', type: 'warning' }); return; } // 提交的list const resultSelectedData = this.$refs.tree.getCheckedNodes(false, false); // clientOrganization 机构项目组 organization 事业部 branchCompany 合约归属公司 if (this.oldDisabledArr.length === 0) { // 上传文件 if (this.searchRange === 1) { this.$emit('getOrgList', 'clientOrganization', resultSelectedData, this.checkAll); } else if (this.searchRange === 2) { this.$emit('getOrgList', 'organization', resultSelectedData, this.checkAll); } else if (this.searchRange === 3) { this.$emit('getOrgList', 'branchCompany', resultSelectedData, this.checkAll); } } else { //编辑 this.$emit('getOrgList', this.SelectedType, resultSelectedData, this.checkAll); } this.dialogVisible = false; }
还存在的问题:
1. 全选反选按钮是否勾选问题,已解决
2. 树形节点全选反选的速度较慢,待改进