如何实现树形选择的懒加载及回显(前后端)

965 阅读2分钟

本文采用了vue3、element-plus、koa的作为demo的演示
以市辖区作为内容演示树形选择的懒加载及回显

0. 用平展的json模拟数据库表的结构region.json

[
  {
    "id": "440000000",
    "name": "广东省",
    "parentId": ""
  },
  {
    "id": "440100000",
    "name": "广州市",
    "parentId": "440000000"
  },
  {
    "id": "440106000",
    "name": "天河区",
    "parentId": "440100000"
  },
  {
    "id": "440111000",
    "name": "白云区",
    "parentId": "440100000"
  },
  {
    "id": "440300000",
    "name": "深圳市",
    "parentId": "440000000"
  },
  {
    "id": "440305000",
    "parentId": "440300000",
    "name": "南山区"
  },
  {
    "id": "440306000",
    "parentId": "440300000",
    "name": "宝安区"
  }
]

1. 首先创建一个<el-tree-select>的组件

<template>
  <el-tree-select
    :model-value="modelValue"
    node-key="id"
    lazy
    :expand-on-click-node="false"
    :load="loadLazyRegion"
    :props="{ label: 'name', value: 'id', children: 'children'}"
    :cache-data="regionData"
    @node-click="data => modelValue = data.id"
  ></el-tree-select>
</template>
<script setup>
import {ref, onMounted} from 'vue'

const modelValue = ref('')
const regionData = ref([])

async function loadLazyRegion(node, resolve) {
}
</script>

2. 创建懒加载子节点的逻辑(以koa接口为例)

router.get('/lazyRegion', async (ctx, next) => {
  ctx.response.set('Access-Control-Allow-Origin', '*')
  ctx.body = getLazyRegion(ctx.request.query.id || '')
})

// 通过id筛选出该节点下所有的直接子节点
function getLazyRegion(id) {
  const region = require('./region.json')
  const result = region.filter(e => e.parentId === id)
  return result
}

3. 完善vue组件的懒加载逻辑

async function loadLazyRegion(node, resolve) {
  const tree = (await axios.get('//localhost:3001/lazyRegion' + `?id=${node.data.id || ''}`)).data
  resolve(tree)
  console.log(`tree`, tree)
}

首先有了懒加载的效果

屏幕录制2024-01-02 下午4.38.12.gif

如果不做回显逻辑,就会显示option的label:

image.png

4.增加懒树的回显逻辑(以koa接口为例)

router.get('/regionTree', async (ctx, next) => {
  ctx.response.set('Access-Control-Allow-Origin', '*')
  ctx.body = getRegionTree(ctx.request.query.id || '')
})

// 通过id生成该节点到根节点的树
function getRegionTree(id) {
  const region = require('./region.json')
  // 先根据id找到这个节点
  let node = region.find(e => e.id === id)
  if (!node) return [] // 没找到就返回空

  // 定义parent和nodes平展数组
  let parent
  const nodes = [node]

  // 生成nodes数组
  do {
    parent = region.find(e => e.id === node.parentId)
    if (parent) {
      // parent放在前面
      nodes.unshift(parent)
    }
    // 滑动当前节点
    node = parent
  } while(parent)

  // 通过nodes生成一颗tree
  const tree = nodes[0]
  let currentNode = tree
  for (const item of nodes.slice(1)) {
    currentNode.children ||= []
    currentNode.children = [item]
    // 滑动当前节点
    currentNode = item
  }

  return [tree]
}

5. 完善vue组件的懒树回显逻辑

async function loadRegionTree(id) {
  const tree = (await axios.get('//localhost:3001/regionTree' + `?id=${id || ''}`)).data
  regionData.value = tree
}

onMounted(() => {
  if (modelValue.value) {
    loadRegionTree(modelValue.value)
  }
})

回显效果如下:

屏幕录制2024-01-02 下午4.39.10.gif

总结

  1. 懒加载用的方式是根据一个节点的id,去找到该节点下的所有节点,是一个平展的一维数组
  2. 懒树回显用的方式是根据一个回显节点的id,去找到该节点到根节点的路径,形成一棵树

所有代码归纳

后端

router.get('/lazyRegion', async (ctx, next) => {
  ctx.response.set('Access-Control-Allow-Origin', '*')
  ctx.body = getLazyRegion(ctx.request.query.id || '')
})

// 通过id筛选出该节点下所有的直接子节点
function getLazyRegion(id) {
  const region = require('./region.json')
  const result = region.filter(e => e.parentId === id)
  return result
}

router.get('/regionTree', async (ctx, next) => {
  ctx.response.set('Access-Control-Allow-Origin', '*')
  ctx.body = getRegionTree(ctx.request.query.id || '')
})

// 通过id生成该节点到根节点的树
function getRegionTree(id) {
  const region = require('./region.json')
  // 先根据id找到这个节点
  let node = region.find(e => e.id === id)
  if (!node) return [] // 没找到就返回空

  // 定义parent和nodes平展数组
  let parent
  const nodes = [node]

  // 生成nodes数组
  do {
    parent = region.find(e => e.id === node.parentId)
    if (parent) {
      // parent放在前面
      nodes.unshift(parent)
    }
    // 滑动当前节点
    node = parent
  } while(parent)

  // 通过nodes生成一颗tree
  const tree = nodes[0]
  let currentNode = tree
  for (const item of nodes.slice(1)) {
    currentNode.children ||= []
    currentNode.children = [item]
    // 滑动当前节点
    currentNode = item
  }

  return [tree]
}

前端

<template>
  <el-tree-select
    :model-value="modelValue"
    node-key="id"
    lazy
    :expand-on-click-node="false"
    :load="loadLazyRegion"
    :props="{ label: 'name', value: 'id', children: 'children'}"
    :cache-data="regionData"
    @node-click="data => modelValue = data.id"
  ></el-tree-select>
</template>

<script setup>
import {ref, onMounted} from 'vue'
import axios from 'axios'

const modelValue = ref('')
// const modelValue = ref('440106000')
const regionData = ref([])

async function loadLazyRegion(node, resolve) {
  const tree = (await axios.get('//localhost:3001/lazyRegion' + `?id=${node.data.id || ''}`)).data
  resolve(tree)
  console.log(`tree`, tree)
}

async function loadRegionTree(id) {
  const tree = (await axios.get('//localhost:3001/regionTree' + `?id=${id || ''}`)).data
  regionData.value = tree
}

onMounted(() => {
  if (modelValue.value) {
    loadRegionTree(modelValue.value)
  }
})
</script>