具体需求:
- 支持按层级选择一个/多个小区
- 层级懒加载
- 选中非小区节点时默认选中该节点范围下的所有小区
- 支持按列表选择一个/多个小区
- 列表懒加载
- “全部小区”选项和树/列表中的选项互斥
- 按树结构和按列表结构选中的小区是同步更新的
- 记忆节点选中状态
- 支持搜索,可以在搜索出来的列表中进行选中/取消选中操作
效果如图:
设计思路:
- 用一个变量a来存放真实选中的小区信息,树/列表只做展示用
- 树的懒加载:展开节点时再加载下一层信息
- 列表的懒加载:先加载50个小区,滚动到底部时再加载下一批小区,虚拟分页
- 树的节点选中:如果选中非小区节点,则向接口请求该节点下的所有小区放到变量a中;如果选中小区节点,则直接将小区信息放到变量a中
- 列表的节点选中:直接将选中信息同步到变量a中
- 选择“全部小区”后,清空所有树/列表节点的选中状态,并且清空变量a;选择树/列表节点时,取消“全部小区”的选中状态,并且更新变量a
- 切换tab时,同步更新树/列表的节点选中状态
- 树节点选中状态的设置(重点):由于需要记忆节点选中状态,这就意味着树结构需要记忆全选/半选状态。如果树是全量加载的,那么不需要我们自己来判断,只需要告诉树选中的小区即可,但麻烦的是,现在树节点是懒加载的,在未打开某个节点的情况下,无法根据当前选中的小区来判断某个省市区街是全选还是半选,这个时间就需要依赖接口的判断,也就是说,需要在请求某一层的树节点信息时,把当前选中的小区传回去,由接口来告诉你某个节点需要全选还是半选
- 列表选中状态的设置:直接更新就可以了
重点代码:
1.开发时遇到的比较麻烦的一个事情就是设置树的半选中状态,官方并没有提供半选的api,找了好久最后发现可以使用以下方法来直接修改节点状态:
let node = this.$refs.asyncTree.getNode(id);
node.indeterminate = true // 半选
node.checked = true // 全选
node.expand() // 展开
nodedata.loadData() // 加载数据
没错,就是这么简单粗暴,直接拿到节点,然后更改节点的属性或者调用节点的方法就好,需要哪个做哪个
2.想重新渲染树的话直接给树设置一个key,想渲染的时候更新下key就好了,简单方便
封装好的组件以及组件的使用:
PlotMultiSelectDialog.vue
<!-- 顶栏的选择小区的组件 -->
<!-- 只可用于全局,作为全局小区范围的控制 -->
<template>
<div>
<el-dialog
:visible="visible"
:append-to-body="true"
:show-close="true"
:close-on-click-modal="false"
:close-on-press-escape="false"
@close="clickCancle"
@open="open"
width="660px"
v-dialogDrag
>
<div slot="title">
<span class="dialog-title">更换小区</span>
</div>
<div class="plotSelectRoot">
<el-input
clearable
placeholder="请输入小区名称"
prefix-icon="el-icon-search"
v-model="searchText"
>
<i slot="suffix" class="el-icon-loading" v-if="searchLoading"></i>
</el-input>
<div
class="checkRect"
v-show="searchText !== ''"
style="margin-top: 20px"
>
<div style="height: 440px; overflow-y: auto">
<div
v-for="(item, index) in searchList"
:key="index"
class="plotItem"
>
<el-checkbox
v-model="item.checked"
@change="(val) => searchChange(item, val)"
>{
{
item.plotName }}
</el-checkbox>
</div>
</div>
</div>
<el-tabs
v-show="searchText === ''"
v-model="activeTabName"
@tab-click="handleTablick"
>
<el-tab-pane label="按地区选" name="byRegion">
<div class="selectAllBox">
<el-checkbox
v-model="allChecked"
@change="allCheckedChange"
style="margin-left: 23px"
>全部小区</el-checkbox
>
<div class="selectAllButton" @click="clickSelectNone">清空</div>
<div class="selectCount">(已选择: {
{
flatNum }}个小区)</div>
</div>
<div class="checkRect" :key="treeKey">
<div style="height: 440px; overflow-y: auto" class="innerbox">
<el-tree
:props="props"
:load="loadTreeNode"
node-key="id"
lazy
show-checkbox
ref="plotTree"
:expand-on-click-node="false"
:default-expanded-keys="defaultExpandedKeys"
@check="checkedTreeNodeChange"
>
</el-tree>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="按小区选" name="byCommunity">
<div class="selectAllBox">
<el-checkbox
v-model="allChecked"
@change="allCheckedChange"
style="margin-left: 23px"
>全部小区</el-checkbox
>
<div class="selectAllButton" @click="clickSelectNone">清空</div>
<div class="selectCount">(已选择: {
{
flatNum }}个小区)</div>
</div>
<div class="checkRect">
<div
style="height: 440px; overflow-y: auto"
class="innerbox"
@scroll="scrollEvent"
>
<el-checkbox-group id="checkboxGroup" v-model="checkedPlotList">
<el-checkbox
v-for="plot in flatPlotList"
:key="plot.id"
:label="plot.id"
@change="checkedListNodeChange"
>{
{
plot.plotName }}</el-checkbox
>
</el-checkbox-group>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
<span slot="footer">
<div>
<el-button @click="clickCancle">取消</el-button>
<el-button type="primary" @click="clickConfirm">确定</el-button>
</div>
</span>
</el-dialog>
</div>
</template>
<script lang="ts">
import {
Component, Vue, Watch, Pr