当前位置:   article > 正文

vue3树形下拉框组件_el-tree-v2

el-tree-v2

1.组件封装

  1. <!-- 树状选择器 -->
  2. <script lang="ts">
  3. import type { TreeNode } from 'element-plus/es/components/tree-v2/src/types'
  4. import type { TreeNodeData } from 'element-plus/es/components/tree/src/tree.type'
  5. import type { PropType } from 'vue'
  6. import { defineComponent, nextTick, onMounted, reactive, ref, toRefs, watch } from 'vue'
  7. interface PropsIter {
  8. value: string
  9. label: string
  10. children: string
  11. disabled?: boolean
  12. }
  13. const TreeProps: PropsIter = {
  14. value: 'id',
  15. label: 'name',
  16. children: 'children',
  17. }
  18. interface TreeIter {
  19. id: string
  20. label: string
  21. children?: TreeIter[]
  22. }
  23. export default defineComponent({
  24. props: {
  25. // 组件绑定的options
  26. options: {
  27. type: Array as PropType<TreeIter[]>,
  28. required: true,
  29. },
  30. // 配置选项
  31. keyProps: Object as PropType<PropsIter>,
  32. // 双向绑定值
  33. modelValue: [String, Number],
  34. // 组件样式宽
  35. width: {
  36. type: String,
  37. default: '240px',
  38. },
  39. // 空占位字符
  40. placeholder: String,
  41. },
  42. emits: ['update:modelValue'],
  43. setup(props, { emit }) {
  44. // 解决 props道具变异
  45. const { modelValue } = toRefs(props)
  46. const select: { value: string | number | undefined; currentNodeLabel: string | number | undefined; currentNodeKey: string | number | undefined } = reactive({
  47. value: modelValue.value,
  48. currentNodeKey: '',
  49. currentNodeLabel: '',
  50. })
  51. const treeSelect = ref<HTMLElement | null>(null)
  52. const blur = ref<HTMLElement | null>()
  53. const nodeClick = (data: TreeNodeData, node: TreeNode) => {
  54. select.currentNodeKey = data.id
  55. select.currentNodeLabel = data.label || data.name
  56. select.value = data.id
  57. emit('update:modelValue', select.value);
  58. // 关闭下拉框
  59. (treeSelect.value as any).handleClose()
  60. nextTick(() => {
  61. (treeSelect.value as any).handleClose()
  62. })
  63. }
  64. // 筛选方法
  65. const treeV2: any = ref<HTMLElement | null>(null)
  66. const selectFilter = (query: string) => {
  67. treeV2.value.filter(query)
  68. }
  69. // ztree-v2 筛选方法
  70. const treeFilter = (query: string, node: TreeNode) => {
  71. return node.label?.indexOf(query) !== -1
  72. }
  73. // 直接清空选择数据
  74. const clearSelected = () => {
  75. select.currentNodeKey = ''
  76. select.currentNodeLabel = ''
  77. select.value = ''
  78. emit('update:modelValue', undefined)
  79. }
  80. // setCurrent通过select.value 设置下拉选择tree 显示绑定的v-model值
  81. // 可能存在问题:当动态v-model赋值时 options的数据还没有加载完成就会失效,下拉选择时会警告 placeholder
  82. const setCurrent = () => {
  83. select.currentNodeKey = select.value
  84. treeV2.value.setCurrentKey(select.value)
  85. const data: TreeNodeData | undefined = treeV2.value.getCurrentNode(select.value)
  86. select.currentNodeLabel = data?.label || data?.name
  87. }
  88. // 监听外部清空数据源 清空组件数据
  89. watch(modelValue, (v) => {
  90. if (v === undefined && select.currentNodeKey !== '') {
  91. clearSelected()
  92. }
  93. // 动态赋值
  94. if (v) {
  95. select.value = v
  96. setCurrent()
  97. }
  98. })
  99. // 回显数据
  100. onMounted(async () => {
  101. await nextTick()
  102. if (select.value) {
  103. setCurrent()
  104. }
  105. })
  106. return {
  107. treeSelect,
  108. treeV2,
  109. TreeProps,
  110. ...toRefs(select),
  111. nodeClick,
  112. selectFilter,
  113. treeFilter,
  114. clearSelected,
  115. }
  116. },
  117. })
  118. </script>
  119. <template>
  120. <div class="tree_box" :style="width && { width: width.includes('px') ? width : width }">
  121. <el-select
  122. ref="treeSelect" v-model="value" clearable filterable :placeholder="placeholder || '请选择'"
  123. :filter-method="selectFilter" @clear="clearSelected"
  124. >
  125. <el-option :value="currentNodeKey" :label="currentNodeLabel">
  126. <el-tree-v2
  127. id="tree_v2" ref="treeV2" :data="options" :props="keyProps || TreeProps"
  128. :current-node-key="currentNodeKey" default-expand-all :expand-on-click-node="false"
  129. :filter-method="treeFilter" @node-click="nodeClick"
  130. />
  131. </el-option>
  132. </el-select>
  133. </div>
  134. </template>
  135. <style lang="scss" scoped>
  136. .tree_box {
  137. width: 214px;
  138. }
  139. .el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
  140. height: auto;
  141. max-height: 274px;
  142. padding: 0;
  143. overflow: hidden;
  144. overflow-y: auto;
  145. }
  146. .el-select-dropdown__item.selected {
  147. font-weight: normal;
  148. }
  149. ul li :deep(.el-tree .el-tree-node__content) {
  150. height: auto;
  151. padding: 0 20px;
  152. }
  153. .el-tree-node__label {
  154. font-weight: normal;
  155. }
  156. .el-tree :deep(.is-current .el-tree-node__label) {
  157. color: #409eff;
  158. font-weight: 700;
  159. }
  160. .el-tree :deep(.is-current .el-tree-node__children .el-tree-node__label) {
  161. color: #606266;
  162. font-weight: normal;
  163. }
  164. .selectInput {
  165. padding: 0 5px;
  166. box-sizing: border-box;
  167. }
  168. .el-select {
  169. width: 100% !important;
  170. }
  171. </style>

2.组件使用

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

3.数据定义

  1. const deptProps = reactive({
  2. parent: 'pid', value: 'id', label: 'name', children: 'children',
  3. })
  4. const useDeptList = ref<deptType[]>([]) // 使用部门列表
  5. // 获取部门列表
  6. getDeptTreeList().then((res) => {
  7. // 转成树结构
  8. useDeptList.value = toTreeList(res.data, '0', true)
  9. })

4.转树结构方法

  1. // 数据结构转换工具
  2. // 定义数组项的数据类型,包含id、name、parentId基本属性
  3. interface ArrayItem {
  4. pid: string
  5. id: string
  6. name?: string
  7. }
  8. // 定义树节点的数据类型,包含id、name、可能存在的子节点
  9. interface TreeNode {
  10. id: string
  11. name?: string
  12. pid: string
  13. children?: TreeNode[] // 叶子节点没有子节点
  14. }
  15. /**
  16. * 判断是否有转树的必要
  17. * @param plainList 平行数据列表
  18. * @param id 祖宗id
  19. * @returns {boolean} 有返回true,无返回false
  20. */
  21. export function judgeTree(plainList: ArrayItem[], id?: '0') {
  22. if (plainList && plainList.length > 0) {
  23. let flag = false // 是否需要转成树结构
  24. const pid = id
  25. for (const item of plainList) {
  26. if (item.pid !== pid) { // 只要有一个元素的pid没有指向第一个元素的父id,认为有必要转换成树,否则认为无必要
  27. flag = true
  28. break
  29. }
  30. }
  31. return flag
  32. }
  33. else { return false }
  34. }
  35. /**
  36. * 平面数据数据转树结构
  37. * @param plainList 平行数据列表
  38. * @param id 祖宗id
  39. * @param isSelect 是否是下拉需要顶级的树
  40. * @returns {*}
  41. */
  42. export function toTreeList<T extends ArrayItem>(plainList: T[], rootId = '0', isSelect = false): T[] {
  43. const pid = findPid(plainList)
  44. if (pid.length > 1) { // 如果有多个pid,直接返回列表, 不去构造树
  45. return plainList
  46. }
  47. else {
  48. const tree = cleanChildren(buildTree<T>(plainList, rootId, isSelect))
  49. return tree
  50. }
  51. }
  52. // 构建树
  53. /**
  54. *
  55. * @param plainList 待转换数组
  56. * @param id 父节点
  57. * @param isSelect 是否是下拉框所使用的树
  58. * @returns 树节点列表
  59. */
  60. function buildTree<T extends TreeNode>(plainList: T[], id = '0', isSelect = false): T[] | [] {
  61. // 递归函数
  62. const fa = (parentId: string): Array<T> | [] => {
  63. const temp = []
  64. for (let i = 0; i < plainList.length; i++) {
  65. const n: TreeNode = { ...plainList[i] }
  66. const id = `${n.id}`
  67. const pid = `${n.pid}`
  68. if (pid === parentId) {
  69. n.children = fa(id)
  70. temp.push(n)
  71. }
  72. }
  73. return temp as T[]
  74. }
  75. // 如果是下拉框需要使用的树,首先寻找顶级,将顶级也放入列表
  76. if (isSelect) {
  77. let flag = 1
  78. const list = []
  79. if (Array.isArray(plainList)) {
  80. for (const item of plainList) {
  81. const n: T = { ...item }
  82. const nid = `${n.id}`
  83. if (nid === id) {
  84. n.children = fa(id)
  85. flag = 0
  86. list.push(n)
  87. return list
  88. }
  89. else {
  90. continue
  91. }
  92. }
  93. }
  94. if (flag === 1) { // 没有找到父级,按原流程走
  95. return fa(id)
  96. }
  97. else {
  98. return []
  99. }
  100. }
  101. else {
  102. return fa(id)
  103. }
  104. }
  105. // 清除children为空列表的children项
  106. function cleanChildren<T extends TreeNode>(data: T[]): T[] {
  107. const fa = (list: TreeNode[]): T[] => {
  108. list.map((e) => {
  109. if (e && e.children && e.children.length) {
  110. fa(e.children)
  111. }
  112. else {
  113. delete e.children
  114. }
  115. return e
  116. })
  117. return list as T[]
  118. }
  119. return fa(data)
  120. }
  121. /**
  122. *
  123. * @param plainList 寻找列表中的父id
  124. * @returns 父id列表
  125. */
  126. function findPid(plainList: Array<ArrayItem>): Array<string> {
  127. const pidList = new Set<string>()
  128. if (plainList) {
  129. for (const item of plainList) { // 1.添加所有的父id
  130. pidList.add(item.pid)
  131. }
  132. for (const item of plainList) { // 2.删除所有的子id
  133. if (pidList.has(item.id)) {
  134. pidList.delete(item.id)
  135. }
  136. }
  137. const arr = Array.from(pidList) // 剩下的就是最终的父节点
  138. return arr
  139. }
  140. else {
  141. return []
  142. }
  143. }
  144. // 从树列表中删除指定元素
  145. export function deleteItem(list: Array<TreeNode>, des: TreeNode) {
  146. const del = (list: Array<TreeNode>, item: TreeNode) => {
  147. for (let i = 0; i < list.length; i++) {
  148. if (list[i].id === item.id) {
  149. list.splice(i, 1)
  150. return
  151. }
  152. else { // 遍历子孙,继续递归寻找
  153. if (list[i].children && list[i].children!.length > 0) {
  154. del(list[i].children!, item)
  155. }
  156. }
  157. }
  158. }
  159. del(list, des)
  160. }
  161. interface treeItem {
  162. id: string
  163. open: string | boolean
  164. checked: string | boolean
  165. }
  166. /**
  167. *获取列表中的展开项和选中项
  168. * @param plainList
  169. * @param id
  170. * @returns{展开项, 选中项}
  171. */
  172. export function getShowItem(plainList: treeItem[]): { expandList: string[]; openedList: string[] } {
  173. const expandList = []
  174. const openedList = []
  175. for (let i = 0; i < plainList.length; i++) {
  176. if (plainList[i].open === 'true' || plainList[i].open === true) {
  177. expandList.push(plainList[i].id)
  178. }
  179. if (plainList[i].checked === 'true' || plainList[i].checked === true) {
  180. openedList.push(plainList[i].id)
  181. }
  182. }
  183. return { expandList, openedList }
  184. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/119291
推荐阅读
相关标签
  

闽ICP备14008679号