Loading

关于树级结构懒加载 + 精准定位高亮选中问题的总结

需求前提:高级检索中选中的机关数据需对应高亮,检索条件删除搜索的机关需取消高亮,且每次改变搜索条件时该机关结构数据按条件变化。

初次发版:一次加载所有发布机关数据,可根据el-treecurrent-node-key属性进行默认值选中,highlight-current属性高亮匹配上的id值

现存问题:机关类数据共上万条,一次加载接口时间长达半分钟

解决问题:改进为树级数据懒加载形式,并默认展开高亮对应所选

  1. 查找el-plus的文档发现,可以配置lazy :load来进行数据懒加载,并在数据中添加leaf来判断当前是否为子节点or有子节点的父节点,
const loadNode = (node: Node, resolve: (data: Tree[]) => void) => {
      if (!node.level) return
      let curId = node.data.id ? node.data.id : ''
      let nextLevel = node.data.nextLevel ? node.data.nextLevel : ''
      httpRequest
        .post({
          url: props.state.url,
          params: {
            [props.state.key]: curId,
            [props.state.keyLevel]: nextLevel
          }
        })
        .then((res: any) => {
          nextTick(() => {
            defaultKey.value = props.state.defaultId
            if (defaultKey.value === '') defaultKey.value = null
            popularTree.value.setCurrentKey(defaultKey.value)
            checkedArr.value = [defaultKey.value]
          })
          return resolve(res.data)
        })
        .catch((err) => {
          console.log(err)
        })
    }
  1. 设置:default-expanded-keys="expandArr" :default-checked-keys="checkedArr"来解决默认展开及高亮搜索的对应id

  2. 每次点击树节点更改搜索条件,都会重新调取接口,树级整体进行刷新,又回到了只有一级数据的情况,因此需要用递归来找到当前所选id的所有父节点id并push到expandArr中,记得每次调用接口时要把expandArr清空

const handleNodeClick = (data: any, Node: any) => {
	expandArr.value = []
	function seekParent(o: any) {
  	o.parent.level > 0 && expandArr.value.push(o.parent.data.id)
  	o.parent.level > 1 && seekParent(o.parent)
	}
	seekParent(Node)
	checkedArr.value = [data.id]
}
  1. 高级检索搜索及删除搜索条件时会修改树节点的defaultId,由于在首页/搜索页都有高级检索,跨页面的检索条件会在窗口新开时重置为空,因此选择将搜索获取到的parentArr存储在sessionStorage,在关闭高级检索框or删除搜索条件时removeItem,在树级组件中监听defaultId的变化,当sessionStorage中有我们存储的父级数组时,则给expandArr赋值,并setCurrentKey,这里需要注意的是由于sessionStorage只能存储字符串,因此需要在setItem前将数组parentArr转为字符串setItem('parentArr', JSON.strinity(parentArr)),在我们需要getItem时,再转为数组形式,JSON.parse(getItem('parentArr'))

完整代码如下:

/parentView
template:
    <tree
      ref="fbjgRef"
      :state="fbjgState"
      @change="fbjgState.handleItemClick"
    ></tree>

		watch(
      () => store.state.form,
      (newV, oldV) => {
        if (newV.institutionId && newV.institutionId !== oldV.institutionId)
          return
        ;[fbjgState.defaultId, fbjgState.level] = [
          store.state.form.institutionId,
          store.state.form.institutionLevel
        ]
        fbjgState.getData()
      },
      {
        deep: true
      }
    )

// 发布机关
    const fbjgRef = ref()
    const fbjgState: any = reactive({
      title: '发布机关',
      key: 'institutionId',
      keyLevel: 'institutionLevel',
      data: [],
      url: 'institutionList',
      defaultId: store.state.form.institutionId,
      level: store.state.form.institutionLevel,
      parentId: '',
      nextLevel: '',
      loading: true,
      getData() {
        fbjgState.loading = true
        const form = JSON.parse(JSON.stringify(store.state.form))
        form.institutionId = ''
        form.institutionLevel = ''
        httpRequest
          .post({
            url: 'institutionList',
            params: form
          })
          .then((res: any) => {
            fbjgState.data = res.data
            fbjgState.loading = false
          })
          .catch((err) => {
            console.log(err)
          })
      },
      handleItemClick(obj: any) {
        const filterObj = JSON.parse(JSON.stringify(store.state.form))
        filterObj.institutionId = fbjgState.defaultId = obj.id
        filterObj.institutionLevel = fbjgState.level = obj.level
        store.commit('setForm', filterObj)
        // 此处提交文字到vuex数组中,用于检索条件展示
        searchListAction(obj)
      }
    })
    fbjgState.getData()
/tree.vue
<template>
  <div class="tree" v-loading="state.loading">
    <el-scrollbar height="100%">
      <el-tree
        ref="popularTree"
        :data="state.data"
        :props="defaultProps"
        node-key="id"
        lazy
        :load="loadNode"
        check-strictly
        check-on-click-node
        :expand-on-click-node="false"
        :current-node-key="defaultKey"
        :default-expanded-keys="expandArr"
        :default-checked-keys="checkedArr"
        highlight-current
        @node-click="handleNodeClick"
      >
        <template #default="{ node, data }">
          <div class="item-column">
            <span
              class="tree-label"
              :style="{ width: widthCal(data.value) }"
              :title="node.label"
              >{{ node.label }}</span
            >
            <span class="tree-value">({{ data.value }})</span>
          </div>
        </template>
      </el-tree>
    </el-scrollbar>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, watch, nextTick } from 'vue'
import httpRequest from '@/service'
import type Node from 'element-plus/es/components/tree/src/model/node'
interface Tree {
  name: string
  id: string
  value: number
  level: string
  leaf?: boolean
  children?: Tree[]
  parentId?: string
  nextLevel?: string
}
export default defineComponent({
  name: 'tree',
  props: {
    state: {
      type: Object,
      default: () => {
        return {}
      }
    }
  },
  emit: ['change'],
  setup(props, { emit }) {
    const expandArr = ref<string[]>([])
    const checkedArr = ref<string[]>([])
    const defaultProps = {
      label: 'name',
      value: 'value',
      children: 'children',
      isLeaf: 'leaf'
    }
    const defaultKey = ref(props.state.defaultId)
    const popularTree = ref()
    const widthCal = (e: any) => {
      return ` cal(100%-${String(e).length}*8)`
    }
    watch(
      () => [props.state.loading, props.state.defaultId],
      () => {
        if (props.state.key === 'institutionId') {
          expandArr.value = JSON.parse(
            sessionStorage.getItem('institutionParent') as string
          )
        }
        nextTick(() => {
          defaultKey.value = props.state.defaultId
          if (defaultKey.value === '') defaultKey.value = null
          popularTree.value.setCurrentKey(defaultKey.value)
          checkedArr.value = [defaultKey.value]
        })
      }
    )

    const loadNode = (node: Node, resolve: (data: Tree[]) => void) => {
      if (!node.level) return
      let curId = node.data.id ? node.data.id : ''
      let nextLevel = node.data.nextLevel ? node.data.nextLevel : ''
      httpRequest
        .post({
          url: props.state.url,
          params: {
            [props.state.key]: curId,
            [props.state.keyLevel]: nextLevel
          }
        })
        .then((res: any) => {
          nextTick(() => {
            defaultKey.value = props.state.defaultId
            if (defaultKey.value === '') defaultKey.value = null
            popularTree.value.setCurrentKey(defaultKey.value)
            checkedArr.value = [defaultKey.value]
          })
          return resolve(res.data)
        })
        .catch((err) => {
          console.log(err)
        })
    }
    const handleNodeClick = (data: any, Node: any) => {
      expandArr.value = []
      const obj = {
        id: data.id,
        level: data.level,
        parentId: data.parentId,
        nextLevel: data.nextLevel,
        type: props.state.key,
        name: props.state.title + ':' + data.name
      }
      function seekParent(o: any) {
        o.parent.level > 0 && expandArr.value.push(o.parent.data.id)
        o.parent.level > 1 && seekParent(o.parent)
      }
      seekParent(Node)
      if (props.state.key === 'institutionId') {
        sessionStorage.setItem(
          'institutionParent',
          JSON.stringify(expandArr.value)
        )
      }
      checkedArr.value = [data.id]
      emit('change', obj)
    }

    return {
      expandArr,
      checkedArr,
      popularTree,
      defaultProps,
      defaultKey,
      handleNodeClick,
      loadNode,
      widthCal
    }
  }
})
</script>
<style lang="less" scoped>
.tree {
  height: 100%;
  padding: 0 10px;
  overflow: auto;
  user-select: none;
  ::v-deep(.el-tree-node.is-current > .el-tree-node__content) {
    color: #4486e0;
  }
  ::v-deep(.el-tree-node__content) {
    height: auto;
  }
  ::v-deep(.el-tree-node__label) {
    font-size: 16px;
    line-height: 36px;
  }
  ::v-deep(.el-tree-node) {
    font-size: 16px;
    line-height: 36px;
  }
  &:deep(.expanded) {
    width: 15px;
    height: 15px;
    color: transparent;
    background: url('~@/assets/images/icon-close.png') no-repeat center !important;
  }
  &:deep(.expanded .el-tree-node__children .el-tree-node__expand-icon) {
    background: url('~@/assets/images/icon-expand.png') no-repeat center !important;
  }
  &:deep(.el-tree-node__expand-icon) {
    width: 15px;
    height: 15px;
    color: transparent;
    background: url('~@/assets/images/icon-expand.png') no-repeat center;
  }
  &:deep(.el-tree-node__expand-icon.is-leaf) {
    width: 8px;
    height: 8px;
    padding: 0;
    margin-left: 10px;
    margin-right: 10px;
    background: #e8e8e8;
    border-radius: 50%;
  }
  &:deep(.is-current.is-checked
      .el-tree-node__children
      .el-tree-node__expand-icon.is-leaf) {
    background: #e8e8e8;
  }
  &:deep(.is-current.is-checked .el-tree-node__expand-icon.is-leaf) {
    background: #4486e0;
  }
  .item-column {
    width: 90%;
    display: flex;
    .tree-label {
      display: block;
      // max-width: 60%;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
  }
}
</style>

posted @ 2022-07-03 21:45  顾诚的城  阅读(509)  评论(0编辑  收藏  举报