tree-node.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <template>
  2. <view class="hc-tree-node" :class="[nodeItem.level > 1 ? 'pl': '']">
  3. <view class="content-bar" :id="'tree-' + nodeItem.key" :class="currentNodeKey===nodeItem.key?'current-node-key':''" @click="nodeTap(nodeItem)">
  4. <view class="expand-icon" @click.stop="expandTap(nodeItem)" :class="nodeItem.isExpand?'is-expanded':''">
  5. <text class="i-ri-arrow-right-s-fill" v-if="!nodeItem.isLeaf"/>
  6. </view>
  7. <view class="tree-check" @click.stop="checkClick" v-if="isCheck">
  8. <text class="i-ri-checkbox-blank-line c1" v-if="nodeItem.isCheck === 0"/>
  9. <text class="i-ri-checkbox-fill c2" v-if="nodeItem.isCheck === 1"/>
  10. <text class="i-ri-checkbox-indeterminate-fill c3" v-if="nodeItem.isCheck === 2"/>
  11. </view>
  12. <view class="tree-radio" v-if="isRadio">
  13. <text class="i-ri-checkbox-blank-circle-line c1" v-if="!nodeItem.isRadio"/>
  14. <text class="i-ri-checkbox-circle-fill c2" v-else/>
  15. </view>
  16. <view class="i-ri-loader-line cuIconfont-spin" v-if="nodeItem.loading"/>
  17. <view class="label">{{nodeItem.label?.replace(/\r|\n/ig,"")}} {{ isCounts? `【${nodeItem.data?.submitCounts ?? 0}】` : '' }}</view>
  18. </view>
  19. <template v-if="nodeItem.isExpand">
  20. <view class="children-bar" v-if="nodeItem.childNodes && nodeItem.childNodes.length > 0">
  21. <template v-for="items in nodeItem.childNodes">
  22. <hc-tree-node :item="items"
  23. :check="isCheck"
  24. :strictly="isStrictly"
  25. :radio="isRadio"
  26. :currentKey="currentNodeKey"
  27. :counts="isCounts"
  28. @nodeLoad="getLazyLoad"
  29. @nodeTap="nodeTaps"
  30. />
  31. </template>
  32. </view>
  33. </template>
  34. </view>
  35. </template>
  36. <script setup>
  37. import {ref, watch} from "vue";
  38. import {getArrValue} from "js-fast-way";
  39. //参数
  40. const props = defineProps({
  41. item: {
  42. type: Object,
  43. default: () => ({}),
  44. },
  45. check: {
  46. type: Boolean,
  47. default: false,
  48. },
  49. radio: {
  50. type: Boolean,
  51. default: false,
  52. },
  53. strictly: {
  54. type: Boolean,
  55. default: false,
  56. },
  57. currentKey: {
  58. type: String,
  59. default: '',
  60. },
  61. counts: {
  62. type: Boolean,
  63. default: false,
  64. },
  65. })
  66. //事件
  67. const emit = defineEmits(['nodeLoad', 'nodeTap'])
  68. //变量
  69. const nodeItem = ref(props.item)
  70. //参数配置
  71. const isCheck = ref(props.check)
  72. const isRadio = ref(props.radio)
  73. const isStrictly = ref(props.strictly)
  74. const isCounts = ref(props.counts)
  75. const currentNodeKey = ref(props.currentKey)
  76. //监听
  77. watch(() => [
  78. props.item
  79. ], ([item]) => {
  80. nodeItem.value = item
  81. }, {deep: true})
  82. //监听
  83. watch(() => [
  84. props.currentKey
  85. ], ([val]) => {
  86. currentNodeKey.value = val
  87. })
  88. //节点被点击
  89. const nodeTap = (item) => {
  90. expandTap(item)
  91. nodeTaps(item)
  92. }
  93. //展开和收缩节点
  94. const expandTap = (item) => {
  95. const {isLoad, isExpand, childNodes} = item
  96. const children = getArrValue(childNodes)
  97. //加载状态
  98. if (!isLoad) {
  99. item.loading = true
  100. }
  101. //展开和折叠
  102. if (children.length > 0) {
  103. setBrotherExpand(item, item.parentNodes)
  104. item.isExpand = !isExpand
  105. }
  106. //触发事件
  107. emit('nodeLoad', item)
  108. }
  109. //设置兄弟节点展开状态
  110. const setBrotherExpand = (item, nodes) => {
  111. const children = getArrValue(nodes.childNodes)
  112. for (let i = 0; i < children.length; i++) {
  113. if (children[i].key !== item.key) {
  114. nodes.childNodes[i].isExpand = false
  115. }
  116. }
  117. }
  118. //子节点被点击
  119. const nodeTaps = (item) => {
  120. currentNodeKey.value = item.key
  121. emit('nodeTap', item)
  122. }
  123. //懒加载子节点数据
  124. const getLazyLoad = (item) => {
  125. emit('nodeLoad', item)
  126. }
  127. //复选框被点击 0未选中 1选中 2半选
  128. const checkClick = () => {
  129. const {isCheck} = nodeItem.value
  130. if (isStrictly.value) {
  131. //父子级节点不关联选择
  132. nodeItem.value.isCheck = isCheck !== 1 ? 1 : 0
  133. } else {
  134. // 先设置自己的状态,选中或不选中
  135. nodeItem.value.isCheck = isCheck !== 1 ? 1 : 0
  136. // 设置子节点状态
  137. setChildrenCheck(nodeItem.value.childNodes)
  138. // 设置父节点状态
  139. setParentCheck(nodeItem.value.parentNodes)
  140. }
  141. }
  142. //设置子节点复选框状态
  143. const setChildrenCheck = (children) => {
  144. for (let i = 0; i < children.length; i++) {
  145. children[i].isCheck = nodeItem.value.isCheck
  146. if (children[i].childNodes.length > 0) {
  147. setChildrenCheck(children[i].childNodes)
  148. }
  149. }
  150. }
  151. //设置父节点复选框状态 0未选中 1选中 2半选
  152. const setParentCheck = (nodes) => {
  153. if (nodes.level <= 0) return
  154. const children = getArrValue(nodes.childNodes)
  155. const { isCheck } = nodeItem.value
  156. const result = children.every((item)=> {
  157. return item.isCheck === isCheck
  158. })
  159. if (isCheck === 1) {
  160. nodes.isCheck = result ? 1 : 2
  161. } else {
  162. nodes.isCheck = result ? 0 : 2
  163. }
  164. // 递归设置父节点
  165. if (nodes.parentNodes) {
  166. setParentCheck(nodes.parentNodes)
  167. }
  168. }
  169. </script>