el-tree的动态加载问题与解决

最近有一个业务,需要自定义一个树状结构,并实现一系列的操作

实现效果

需要对查询结果展开对应项,加载数据并展开子项,然后再获取子项,然后展开

实现代码

html 复制代码
<template>
   <section :style="{ height: `${tableHeight - 50}px` }" class="tree-section">
          <el-tree
            :key="treeKey"
            :load="loadNode"
            lazy
            ref="treeRef"
            class="overflow-auto p-[5px] flex-1 no-scrollbar"
            :data="treeData"
            :props="defaultProps"
            highlight-current
            show-checkbox
            node-key="id"
            :default-expanded-keys="defaultExpandedKeys"
            @node-contextmenu="onContextMenu"
            @node-click="handleNodeClick"
            @check="handleTreeCheck"
          />
          <div class="search-result" v-if="searchResult.length">
            <div class="close-with-icon">
              <div class="result-text">搜索结果</div>
              <el-icon @click="searchResult = []"><CircleCloseFilled /></el-icon>
            </div>
            <ul class="search-result_list">
              <li v-for="item in searchResult" :key="item.id" @click="handleSearchResultClick(item)">
                {{ item.bdzName }}--{{ item.xlName }}
              </li>
            </ul>
          </div>
        </section>
</template>
<script setup lang="ts">
import type Node from 'element-plus/es/components/tree/src/model/node'
import { Http } from '@/utils/http'
import { useApi } from '../../api'
import { useUserStore } from '@/store'
import { dayjs } from 'element-plus'
const { stationLineTree, lineNameSearch, generatrixAllocation } = useApi(Http)
type IEmit = {
  (e: 'titleClick'): any
}
const emit = defineEmits<IEmit>()
const { user } = useUserStore()
// 是否管理员
const isAdmin = user.role == 2
const treeRef = ref<InstanceType<typeof ElTree>>()
const queryParams = reactive({
  /**线路名称 */
  xlName: '',
  time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
})

const show = ref(false)

//For component
const optionsComponent = reactive({
  theme: 'mac',
  zIndex: 3,
  minWidth: 150,
  x: 0,
  y: 0,
})
function onContextMenu(e: MouseEvent, node: any) {
  e.preventDefault()

  if (!node.isLeaf || !isAdmin || !treeCheck.value.length) return
  optionsComponent.x = e.clientX
  optionsComponent.y = e.clientY
  show.value = true
}
function onContextMenuClick(type: string) {
  // generatrix 母线 0未分配,1一段,2二段,3三段
  let generatrix
  switch (type) {
    case '添加I段':
      generatrix = 1
      break
    case '添加II段':
      generatrix = 2
      break
    case '添加III段':
      generatrix = 3
      break
    case '移除':
      generatrix = 0
      break
  }

  const data = {
    unitCode: user.unitCode,
    generatrix,
    ids: treeCheck.value,
  }

  generatrixAllocation(data).then(() => {
    ElMessage.success('操作成功')
    show.value = false
    treeKey.value += 1
    getTreeList()
  })
}
const handleNodeClick = () => {}
const treeCheck = ref<string[]>([])
const defaultExpandedKeys = ref<string[]>([])

/**
 * 判断数组A的所有元素是否都存在于数组B中
 * @param subset 待检查的数组 (例: [16, 17])
 * @param fullset 目标数组 (例: [16, 17, 18])
 * @returns 当且仅当subset每个元素都在fullset中存在时返回true
 */
const isAllElementsIncluded = <T>(subset: T[], fullset: T[]): boolean => {
  // 使用 Set 提升包含判断性能
  const fullsetSet = new Set(fullset)
  // 双重验证:元素存在性 + 数量控制
  return subset.length <= fullset.length && subset.every((item) => fullsetSet.has(item))
}
const handleTreeCheck = (node: any, detail: any) => {
  // 判断是否选择的父节点
  if (node.children?.length) {
    const childNodesKey = node.children?.map((i: any) => i.id)
    // 判断选择的节点是否包含子节点&选择的节点有长度(存在)
    if (isAllElementsIncluded(treeCheck.value, childNodesKey) && treeCheck.value.length) {
      treeCheck.value = []
      treeRef.value?.setCheckedKeys([])
    } else {
      treeCheck.value = childNodesKey
      treeRef.value?.setCheckedKeys(childNodesKey)
    }
    return
  }
  const { checkedKeys } = detail

  // 1. 获取当前节点实例
  const currentNode = treeRef.value?.getNode(node)

  // 2. 获取父节点实例
  const parentNode = currentNode?.parent

  // 3. 判断是否都在同一级
  const parentKeys = parentNode?.childNodes.map((i: any) => i.data.id)

  treeCheck.value = (checkedKeys as any[]).filter(Boolean)
  if (!isAllElementsIncluded(treeCheck.value, parentKeys as any[])) {
    treeRef.value?.setCheckedKeys([node.id])
    treeCheck.value = [node.id]
  }
}

/**组件key,为了更新 */
const treeKey = ref(0)

// 树形数据
const treeData = ref<any[]>([
  {
    id: '000',
    nodeCode: '000',
    nodeName: 'xxx公司',
    disabled: true,
    children: [],
  },
])
type ISearchResult = {
  id: number
  bdzName: string
  bdzCode: string
  xlName: string
  xlCode: string
  nodeType: string
  /**
   * generatrix 母线 0未分配,1一段,2二段,3三段
   */
  generatrix: '0' | '1' | '2' | '3'
  unitName: string
  unitCode: string
}
const searchResult = ref<ISearchResult[]>([])

const defaultProps = {
  children: 'children',
  label: 'nodeCode',
  isLeaf: 'isLeaf',
}

const refresh = () => {
  queryParams.time = dayjs().format('YYYY-MM-DD HH:mm:ss')
}

const getSearchResult = () => {
  const data = {
    unitCode: user.unitCode,
    xlName: queryParams.xlName,
  }
  lineNameSearch(data).then((res) => {
    const { result } = res
    searchResult.value = result
  })
}

const handleSearchResultClick = (item: ISearchResult) => {
  const { bdzCode, generatrix, xlCode } = item
  // 点击的变电站
  const clickedBdz = treeData.value[0].children.find((i: any) => i.nodeCode == bdzCode)
  if (!clickedBdz) return
  // 找到变电站
  const bzdNode = treeRef.value?.getNode(clickedBdz.id)
  if (!bzdNode) return
  if (!bzdNode.expanded) {
    // 展开变电站
    bzdNode.expand()
    const stopWatch = watch(
      () => loadNodeLoading.value,
      (loading) => {
        if (!loading) {
          if (bzdNode?.childNodes.length) {
            // 找到对应母线
            const generatrixNode = bzdNode.childNodes.find((i: any) => i.data.generatrix == generatrix) as Node
            if (!generatrixNode.expanded) {
              // 展开母线
              generatrixNode.expand()
            }
            // 找到对应线路高亮显示
            const xlNode = generatrixNode.childNodes.find((i: any) => i.data.xlCode == xlCode) as Node
            if (xlNode) {
              treeRef.value?.setCurrentKey(xlNode.data.id)
            }
            // stopWatch()
          }
        }
      },
    )
  }
}

const getTreeList = () => {
  const data = {
    unitCode: user.unitCode,
    nodeType: 'bdz',
  }
  treeData.value[0].children = []
  stationLineTree(data).then((res) => {
    treeData.value[0].children = res.result.children.map((i: any) => ({ ...i, disabled: true }))
    nextTick().then(() => {
      // 获取第一个节点的 key
      const firstNode = treeData.value[0]
      defaultExpandedKeys.value = [firstNode.id]
      // 设置展开状态
    })
  })
}
interface Tree {

  generatrix: '0' | '1' | '2' | '3'
  nodeName: string
  isLeaf?: boolean
  disabled?: boolean
  children: any[]
}
const loadNodeLoading = ref(false)
const loadNode = (node: Node, resolve: (data: Tree[]) => void) => {
  // todo
  loadNodeLoading.value=true
  return new Promise<void>((resolve, reject) => {
  setTimeout(() => {
    loadNodeLoading.value=false
    resolve()
  }, 1000)
})
  ...
}

onMounted(() => {
  getTreeList()
})
</script>

<style scoped lang="scss">

</style>

主要实现

typescript 复制代码
// 用于保存当前活动的监听器
const activeStop = ref<() => void>()
const handleSearchResultClick = (item: ISearchResult) => {
  // 清理之前的监听器
  if (activeStop.value) {
    activeStop.value() // 停止旧监听器
    activeStop.value = undefined
  }
  const { bdzCode, generatrix, xlCode } = item
  // 点击的变电站
  const clickedBdz = treeData.value[0].children.find((i: any) => i.nodeCode == bdzCode)
  if (!clickedBdz) return
  // 找到变电站
  const bzdNode = treeRef.value?.getNode(clickedBdz.id)
  if (!bzdNode) return
  if (!bzdNode.expanded) {
    // 展开变电站
    bzdNode.expand()
    // 创建新监听器
    activeStop.value = watch(
      () => loadNodeLoading.value,
      (loading) => {
        if (!loading) {
          if (bzdNode?.childNodes.length) {
            // 找到对应母线
            const generatrixNode = bzdNode.childNodes.find((i: any) => i.data.generatrix == generatrix) as Node
            if (!generatrixNode.expanded) {
              // 展开母线
              generatrixNode.expand()
            }
            // 找到对应线路高亮显示
            const xlNode = generatrixNode.childNodes.find((i: any) => i.data.xlCode == xlCode) as Node
            if (xlNode) {
              treeRef.value?.setCurrentKey(xlNode.data.id)
            }
            // 自动销毁监听器
            activeStop.value?.() // 停止当前监听器
            activeStop.value = undefined
          }
        }
      },
    )
  }
}

通过监听方式实现判断是否加载完成,然后继续向下执行

typesript 复制代码

ok!记录一下

相关推荐
锋行天下1 分钟前
大屏可视化适配不同宽高比屏幕,保持网页宽高比不变的代码
前端
依辰9 分钟前
小程序SAAS产品定制化需求解决方案
前端·javascript·微信小程序
anyup13 分钟前
uni-app 蓝牙打印:实现数据分片传输机制
前端·uni-app·trae
云端看世界30 分钟前
为什么要学习 ECMAScript 协议
前端·javascript·ecmascript 6
91732 分钟前
无缝轮播图实现:从原理到实践
前端
我爱鸿蒙开发41 分钟前
🥇聊聊鸿蒙的一端开发,多端部署。
前端·开源·harmonyos
前端付杰41 分钟前
深入理解 IndexedDB:索引与游标查询的高效应用
前端·javascript·indexeddb
best66642 分钟前
前端项目SVG展示方案总结,以Vue3+TS为例
前端
啊花是条龙42 分钟前
Angular 开发指南:组件、数据绑定、指令、服务、HTTP、路由和表单
前端·angular.js
bilibilibiu灬42 分钟前
解析Vue 3中 `trigger` 函数是如何触发 DOM 更新的
vue.js