vue3树形下拉框组件

2023-11-02

1.组件封装

<!-- 树状选择器 -->
<script lang="ts">
import type { TreeNode } from 'element-plus/es/components/tree-v2/src/types'
import type { TreeNodeData } from 'element-plus/es/components/tree/src/tree.type'
import type { PropType } from 'vue'
import { defineComponent, nextTick, onMounted, reactive, ref, toRefs, watch } from 'vue'
interface PropsIter {
  value: string
  label: string
  children: string
  disabled?: boolean
}
const TreeProps: PropsIter = {
  value: 'id',
  label: 'name',
  children: 'children',
}
interface TreeIter {
  id: string
  label: string
  children?: TreeIter[]
}
export default defineComponent({
  props: {
    // 组件绑定的options
    options: {
      type: Array as PropType<TreeIter[]>,
      required: true,
    },
    // 配置选项
    keyProps: Object as PropType<PropsIter>,
    // 双向绑定值
    modelValue: [String, Number],
    // 组件样式宽
    width: {
      type: String,
      default: '240px',
    },
    // 空占位字符
    placeholder: String,
  },
  emits: ['update:modelValue'],
  setup(props, { emit }) {
    // 解决 props道具变异
    const { modelValue } = toRefs(props)
    const select: { value: string | number | undefined; currentNodeLabel: string | number | undefined; currentNodeKey: string | number | undefined } = reactive({
      value: modelValue.value,
      currentNodeKey: '',
      currentNodeLabel: '',
    })
    const treeSelect = ref<HTMLElement | null>(null)
    const blur = ref<HTMLElement | null>()
    const nodeClick = (data: TreeNodeData, node: TreeNode) => {
      select.currentNodeKey = data.id
      select.currentNodeLabel = data.label || data.name
      select.value = data.id
      emit('update:modelValue', select.value);
      //  关闭下拉框
      (treeSelect.value as any).handleClose()
      nextTick(() => {
        (treeSelect.value as any).handleClose()
      })
    }
    // 筛选方法
    const treeV2: any = ref<HTMLElement | null>(null)
    const selectFilter = (query: string) => {
      treeV2.value.filter(query)
    }
    // ztree-v2 筛选方法
    const treeFilter = (query: string, node: TreeNode) => {
      return node.label?.indexOf(query) !== -1
    }
    // 直接清空选择数据
    const clearSelected = () => {
      select.currentNodeKey = ''
      select.currentNodeLabel = ''
      select.value = ''
      emit('update:modelValue', undefined)
    }
    // setCurrent通过select.value 设置下拉选择tree 显示绑定的v-model值
    // 可能存在问题:当动态v-model赋值时 options的数据还没有加载完成就会失效,下拉选择时会警告 placeholder
    const setCurrent = () => {
      select.currentNodeKey = select.value
      treeV2.value.setCurrentKey(select.value)
      const data: TreeNodeData | undefined = treeV2.value.getCurrentNode(select.value)
      select.currentNodeLabel = data?.label || data?.name
    }
    // 监听外部清空数据源 清空组件数据
    watch(modelValue, (v) => {
      if (v === undefined && select.currentNodeKey !== '') {
        clearSelected()
      }
      // 动态赋值
      if (v) {
        select.value = v
        setCurrent()
      }
    })
    // 回显数据
    onMounted(async () => {
      await nextTick()
      if (select.value) {
        setCurrent()
      }
    })
    return {
      treeSelect,
      treeV2,
      TreeProps,
      ...toRefs(select),
      nodeClick,
      selectFilter,
      treeFilter,
      clearSelected,
    }
  },
})
</script>

<template>
  <div class="tree_box" :style="width && { width: width.includes('px') ? width : width }">
    <el-select
      ref="treeSelect" v-model="value" clearable filterable :placeholder="placeholder || '请选择'"
      :filter-method="selectFilter" @clear="clearSelected"
    >
      <el-option :value="currentNodeKey" :label="currentNodeLabel">
        <el-tree-v2
          id="tree_v2" ref="treeV2" :data="options" :props="keyProps || TreeProps"
          :current-node-key="currentNodeKey" default-expand-all :expand-on-click-node="false"
          :filter-method="treeFilter" @node-click="nodeClick"
        />
      </el-option>
    </el-select>
  </div>
</template>

<style lang="scss" scoped>
.tree_box {
  width: 214px;
}

.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
  height: auto;
  max-height: 274px;
  padding: 0;
  overflow: hidden;
  overflow-y: auto;
}

.el-select-dropdown__item.selected {
  font-weight: normal;
}

ul li :deep(.el-tree .el-tree-node__content) {
  height: auto;
  padding: 0 20px;
}

.el-tree-node__label {
  font-weight: normal;
}

.el-tree :deep(.is-current .el-tree-node__label) {
  color: #409eff;
  font-weight: 700;
}

.el-tree :deep(.is-current .el-tree-node__children .el-tree-node__label) {
  color: #606266;
  font-weight: normal;
}

.selectInput {
  padding: 0 5px;
  box-sizing: border-box;
}

.el-select {
  width: 100% !important;
}
</style>

2.组件使用

<com-tree-select
    v-model="ruleForm.mesureDept" :options="useDeptList as any" placeholder="使用部门"
    :tree-props="deptProps"
 />

3.数据定义

const deptProps = reactive({
  parent: 'pid', value: 'id', label: 'name', children: 'children',
})
const useDeptList = ref<deptType[]>([]) // 使用部门列表

// 获取部门列表
  getDeptTreeList().then((res) => {
    // 转成树结构
    useDeptList.value = toTreeList(res.data, '0', true)
  })

4.转树结构方法

// 数据结构转换工具
// 定义数组项的数据类型,包含id、name、parentId基本属性
interface ArrayItem {
  pid: string
  id: string
  name?: string
}
// 定义树节点的数据类型,包含id、name、可能存在的子节点
interface TreeNode {
  id: string
  name?: string
  pid: string
  children?: TreeNode[] // 叶子节点没有子节点
}
/**
 * 判断是否有转树的必要
 * @param plainList 平行数据列表
 * @param id 祖宗id
 * @returns {boolean}  有返回true,无返回false
 */
export function judgeTree(plainList: ArrayItem[], id?: '0') {
  if (plainList && plainList.length > 0) {
    let flag = false // 是否需要转成树结构
    const pid = id
    for (const item of plainList) {
      if (item.pid !== pid) { // 只要有一个元素的pid没有指向第一个元素的父id,认为有必要转换成树,否则认为无必要
        flag = true
        break
      }
    }
    return flag
  }
  else { return false }
}

/**
 * 平面数据数据转树结构
 * @param plainList 平行数据列表
 * @param id 祖宗id
 * @param isSelect 是否是下拉需要顶级的树
 * @returns {*}
 */
export function toTreeList<T extends ArrayItem>(plainList: T[], rootId = '0', isSelect = false): T[] {
  const pid = findPid(plainList)
  if (pid.length > 1) { // 如果有多个pid,直接返回列表, 不去构造树
    return plainList
  }
  else {
    const tree = cleanChildren(buildTree<T>(plainList, rootId, isSelect))
    return tree
  }
}
// 构建树
/**
 *
 * @param plainList 待转换数组
 * @param id 父节点
 * @param isSelect 是否是下拉框所使用的树
 * @returns 树节点列表
 */
function buildTree<T extends TreeNode>(plainList: T[], id = '0', isSelect = false): T[] | [] {
  // 递归函数
  const fa = (parentId: string): Array<T> | [] => {
    const temp = []
    for (let i = 0; i < plainList.length; i++) {
      const n: TreeNode = { ...plainList[i] }
      const id = `${n.id}`
      const pid = `${n.pid}`
      if (pid === parentId) {
        n.children = fa(id)
        temp.push(n)
      }
    }
    return temp as T[]
  }
  // 如果是下拉框需要使用的树,首先寻找顶级,将顶级也放入列表
  if (isSelect) {
    let flag = 1
    const list = []
    if (Array.isArray(plainList)) {
      for (const item of plainList) {
        const n: T = { ...item }
        const nid = `${n.id}`
        if (nid === id) {
          n.children = fa(id)
          flag = 0
          list.push(n)
          return list
        }
        else {
          continue
        }
      }
    }

    if (flag === 1) { // 没有找到父级,按原流程走
      return fa(id)
    }
    else {
      return []
    }
  }
  else {
    return fa(id)
  }
}

// 清除children为空列表的children项
function cleanChildren<T extends TreeNode>(data: T[]): T[] {
  const fa = (list: TreeNode[]): T[] => {
    list.map((e) => {
      if (e && e.children && e.children.length) {
        fa(e.children)
      }
      else {
        delete e.children
      }
      return e
    })
    return list as T[]
  }
  return fa(data)
}
/**
 *
 * @param plainList 寻找列表中的父id
 * @returns 父id列表
 */
function findPid(plainList: Array<ArrayItem>): Array<string> {
  const pidList = new Set<string>()
  if (plainList) {
    for (const item of plainList) { // 1.添加所有的父id
      pidList.add(item.pid)
    }
    for (const item of plainList) { // 2.删除所有的子id
      if (pidList.has(item.id)) {
        pidList.delete(item.id)
      }
    }
    const arr = Array.from(pidList) // 剩下的就是最终的父节点
    return arr
  }
  else {
    return []
  }
}

// 从树列表中删除指定元素
export function deleteItem(list: Array<TreeNode>, des: TreeNode) {
  const del = (list: Array<TreeNode>, item: TreeNode) => {
    for (let i = 0; i < list.length; i++) {
      if (list[i].id === item.id) {
        list.splice(i, 1)
        return
      }
      else { // 遍历子孙,继续递归寻找
        if (list[i].children && list[i].children!.length > 0) {
          del(list[i].children!, item)
        }
      }
    }
  }
  del(list, des)
}

interface treeItem {
  id: string
  open: string | boolean
  checked: string | boolean
}
/**
 *获取列表中的展开项和选中项
 * @param plainList
 * @param id
 * @returns{展开项, 选中项}
 */
export function getShowItem(plainList: treeItem[]): { expandList: string[]; openedList: string[] } {
  const expandList = []
  const openedList = []
  for (let i = 0; i < plainList.length; i++) {
    if (plainList[i].open === 'true' || plainList[i].open === true) {
      expandList.push(plainList[i].id)
    }
    if (plainList[i].checked === 'true' || plainList[i].checked === true) {
      openedList.push(plainList[i].id)
    }
  }
  return { expandList, openedList }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

vue3树形下拉框组件 的相关文章

随机推荐

  • 详解Spring Ioc(控制反转)

    Spring Ioc 控制反转是一个比较抽象的概念 可以举例来说明 生活中 人们要用到一种东西 人们的基本想法就是找到东西 比如想喝果汁 在没有饮品店的日子里 最直接的做法就是 买果汁机 水果 准备开水 这时我们 主动 创造的过程 也就是一
  • Python自动化测试——基础理论思维导图

    1 自动化测试的定义 2 自动化测试的优势 3 自动化测试和手工测试相比 有哪些区别 4 主流的测试用具 5 自动化测试的流程 6 适用于自动化测试的条件 7 选择自动化测试技术时以语言为主的技术线 主要用到的自动化测试工具是Seleniu
  • ​兔子快跑/rabbit-UI和接口自动化测试平台​

    Rabbit 是一个开源的自动化测试平台 基于经典技术组合 Spring Boot Spring Security MyBatis Jwt Vue 目前版本已支持UI自动化和接口自动化 平台采用关键字驱动 测试人员无需任何代码基础 即可轻松
  • ubuntu18.04解决因没有集成显卡驱动进不去界面问题

    1 问题及设备描述 设备 工控机 ubuntu18 04 i9cpu 集成显卡 问题 更化软件源时不小心将微软Microsoft等的源给删除了 没有备份 查资料发现集显就在主板上 与cpu有很大关系 于是觉得问题在于将集成显卡驱动软件的源给
  • 【计算机网络】——I/O复用之poll

    文章目录 1 poll的概述 2 poll函数的功能和作用 3 poll的特点 4 代码实现I O复用poll 1 poll的概述 在上一篇文章中 我们详细的介绍了I O复用技术中的select使用 这篇文章我们来主要介绍一下poll po
  • node初识

    一 什么是node node官网 https nodejs cn Node js是一个开源的 跨平台的JavaScript运行环境 它基于Chrome V8 JavaScript引擎 使得JavaScript可以在服务器端运行 Node j
  • 关于pandas中to_sql性能太慢的优化

    pd to sql table name db 这种方法虽然很简单 但是性能特别慢 插入6万条数据 需要将近5分钟 engine sqla create engine postgresql psycopg2 user pwd IPCLOUD
  • 台式计算机耳机有杂音怎么办,电脑耳机有杂音滋滋怎么办

    快速导读 Q1 电脑耳机插上有杂音是怎么回事 应该是你打开了麦克风的声音 你可以右击小喇叭 打开音量控制 勾选麦克风下面的静音 试试看 如果还不行的话应该是耳机和电脑音频插口接触不良 你可以对接触处进行清洁 如果是耳机出问题就直接换一个好了
  • 内推名企实习,就来CSDN超级实习生计划,2022年名企实习内推开始发车

    一份拿得出手的大厂实习经验有多重要 对没有经验的大二 大三学生来说 这是为自身真实能力背书 是拓宽视野 结交人脉的好机会 更是为以后拿到互联网大厂offer奠定基础 对秋招失利 错过秋招的应届生来说 这是抓住最后时机给简历镀金 逆袭春招的最
  • ReadTimeoutError报错解决

    ReadTimeoutError报错成功解决 报错 在python安装库的时候 可能会出现像下面的这种报错情况 raise ReadTimeoutError self pool None Read timed out ReadTimeout
  • web前端学习路线(含20个真实web开发项目集合)

    目前web前端工程师日均岗位缺口已经超过50000 随着互联网 的深入发展 html5作为前端展示技术 市场人才需求量将呈直线上涨 Web前端工程师的岗位职责是利用HTML CSS Java DOM等各种web技能结合产品的界面开发 制作标
  • linux4.6 EC11旋转编码器的驱动

    最近项目使用了旋转编码器EC11 遍查内核 发现并没有它的驱动 查了查 终于找到一篇有用的 根据自己的需要和对最基础的gpio key c的理解 我改写出了一份EC11的专用驱动 感谢下面博主的启发 有了这位高人的指点 我才有信心改写成功
  • exec failed: exec failed..... exec: “ip“(Docker容器没有ip addr命令:exec ip addr 报错)

    exec failed exec failed exec ip Docker容器没有ip addr命令 exec ip addr 报错 原因 镜像为精简版 无ip addr命令 解决 更换apt 配置文件中的镜像 安装工具 iproute2
  • Python if语句的嵌套应用

    视频版教程 Python3零基础7天入门实战视频教程 有时候业务上有多维度复杂条件判断 我们需要用到if语句的嵌套来实现 举例 我们在一些游戏网站活动充值的时候 冲100送 20 冲200送50 但是vip用户的话 冲100送 30 冲20
  • mysql的分页查询

    开发工具与关键技术 mysql 撰写时间 2022 6 7 mysql分页查询 语法 SELECT 字段列表 FROM 表名 LIMIT 起始索引 查询条目数 注意 分页查询limit是mySQL数据库的方言 Oracle分页查询使用row
  • Jaspersoft 报表:基于Parameters属性传入Map数据源填充报表

    第一步 设计报表模板 在Jaspersoft Studio中新建一个报表模板 report2 移除不需要的Band 段落 Column Header ColumnFooter Summary在outline界面中选中要删除Band 段落 右
  • 微信小程序毕业设计论文医院挂号预约系统+后台管理系统项目源代码

    更多项目资源 最下方联系我们 目录 一 项目介绍 二 项目截图 三 项目获取 一 项目介绍 计算机毕业设计微信小程序毕设项目之挂号预约系统 后台管理系统 哔哩哔哩 bilibili计算机毕业设计微信小程序毕设项目之挂号预约系统 后台管理系统
  • unbuntu 16.04+cuda10.0 安装pytorch 1.4.0 +MMCV 1.3.3 + mmdetection 2.13.0 + mmsegmentation 0.14.0

    环境 ubuntu 16 04 cuda 10 0 python 3 8 pytorch 1 4 0 mmcv 1 3 3 mmdetection 2 13 0 mmsegmentation 0 14 0 由于本机已经安装好 cuda 10
  • PieCloudDB|在云原生时代构筑数据安全防线

    随着数据量和计算能力的爆发式增长 云计算技术的迅猛发展 云原生时代应运而生 公有云带来了可以按需申请 释放的无限的计算资源 无限的存储池和低价的对象存储 让云原生数据库 PieCloudDB Database摆脱 PC 架构的限制 完美解决
  • vue3树形下拉框组件

    1 组件封装