先看效果

image.png
任意树形数据均可直接展示

主要代码片段

注意:最好是去看示例的完整实现,这个非常重要,下面只是稍微提了下主要方法和思路。
思路:使用栈来深度优先遍历,将除根节点外所有节点都找到它的父节点,并添加到节点上。为后续选中子节点判断和修改父节点状态做准备。

树形结构结构如下:

  1. [
  2. {
  3. "title": "水果",
  4. "children": [
  5. {
  6. "title": "香蕉",
  7. "children": [
  8. {
  9. "title": "香蕉A",
  10. "children": [
  11. {
  12. "title": "香蕉AA"
  13. },
  14. {
  15. "title": "香蕉BB"
  16. }
  17. ]
  18. },
  19. {
  20. "title": "香蕉B"
  21. }
  22. ]
  23. },
  24. {
  25. "title": "苹果"
  26. },
  27. {
  28. "title": "梨"
  29. },
  30. {
  31. "title": "草莓"
  32. }
  33. ]
  34. },
  35. {
  36. "title": "蔬菜",
  37. "children": [
  38. {
  39. "title": "芹菜"
  40. },
  41. {
  42. "title": "菠菜"
  43. },
  44. {
  45. "title": "土豆"
  46. },
  47. {
  48. "title": "白菜"
  49. }
  50. ]
  51. }
  52. ]

添加父节点方法

  1. transform() {
  2. console.log("transform")
  3. let stack = [...this.list]
  4. let childCount = 0
  5. let parent = null
  6. while (stack.length) {
  7. let top = stack.pop()
  8. // console.log(top)
  9. if (top.children) {
  10. if (childCount) {
  11. stack.push(top)
  12. let length = stack.length
  13. while (childCount) {
  14. stack[length - 1].parent = parent
  15. childCount--
  16. length--
  17. }
  18. stack.pop()
  19. }
  20. childCount = top.children.length
  21. parent = top
  22. stack.push(... top.children)
  23. } else {
  24. if (childCount) {
  25. top.parent = parent
  26. childCount--
  27. }
  28. }
  29. }
  30. console.log(this.list)
  31. }

组件递归

  1. app.component('boxs', {
  2. name: "boxs",
  3. data() {
  4. return {isShow: false}
  5. },
  6. props: [
  7. "child", 'list'
  8. ],
  9. methods: {
  10. check(child) { // console.log(this)
  11. console.log("check")
  12. this.$emit('my-parent', child)
  13. },
  14. getAllchildren(child) {
  15. if (!child.children)
  16. return []
  17. let all = []
  18. let stack = [...child.children]
  19. while (stack.length) {
  20. let top = stack.pop()
  21. all.push(top)
  22. if (top.children) {
  23. stack.push(... top.children)
  24. }
  25. }
  26. return all
  27. },
  28. judgeParent(child) { // parent
  29. let parent = child // 选中节点也要判断父亲情况
  30. while (parent) {
  31. let all = this.getAllchildren(parent) // 获取所有孩子
  32. let checkes = all.filter(item => item.checked) // 判断孩子的勾选状态
  33. if (checkes.length === 0) { // 如果孩子的勾选成功个数为0,则置空父亲状态
  34. parent.status = null
  35. } else { // 如果孩子的勾选成功个数不为0
  36. if (checkes.length === all.length) { // 如果孩子的勾选成功个数为所有孩子个数,父亲勾选状态置为成功,且置父亲状态位空
  37. parent.checked = true
  38. parent.status = null
  39. } else { // 如果孩子的勾选成功个数不为所有孩子个数,父亲勾选状态置为失败,且置父亲状态为半成功
  40. parent.checked = false
  41. parent.status = "half"
  42. }
  43. } parent = parent.parent // 更新父亲
  44. }
  45. },
  46. oks(child) {
  47. console.log("boxs")
  48. console.log(child)
  49. // 判断选中节点有没有child
  50. if (child.children) { // 如果有孩子
  51. let checked = !child.checked
  52. // 本身勾选取反
  53. // 下面是遍历所有孩子
  54. let stack = [child]
  55. while (stack.length) {
  56. let top = stack.pop()
  57. top.checked = checked // 孩子勾选取反
  58. if (top.children) {
  59. stack.push(... top.children)
  60. }
  61. }
  62. // 判断父亲
  63. this.judgeParent(child)
  64. } else { // 如果没有孩子
  65. child.checked = !child.checked // 本身勾选取反
  66. this.judgeParent(child) // 判断父亲
  67. }
  68. }
  69. },
  70. template: `
  71. <div>
  72. <i style="cursor: pointer;user-select: none;" v-if="child.children" @click="isShow=!isShow">*</i><i v-else style="visibility: hidden;">*</i>
  73. <span>
  74. <!-- <i v-if="child.status === 'half'">half</i> -->
  75. <input :class="{'half': child.status === 'half'}" @click="check(child)" :checked="child.checked" type="checkbox" :id="child.title">
  76. <label :for="child.title">{{child.title}}</label>
  77. </span>
  78. <div v-show="isShow" style="padding-left: 20px;" v-if="child.children">
  79. <boxs :list="list" v-for="item in child.children" :child="item" @my-parent="oks"></boxs>
  80. </div>
  81. </div>
  82. `
  83. })

判断孩子和父亲是否被选中

示例代码

完整示例

https://github.com/liulinboyi/tree

在线示例

https://blog.heyliubo.top/tree/