hc-tree.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <template>
  2. <view class="hc-tree-box">
  3. <template v-if="nodeData.length > 0" v-for="item in nodeData" :key="item.key">
  4. <hc-tree-node ref="treeNodeRef"
  5. :item="item"
  6. :check="isCheck"
  7. :strictly="isStrictly"
  8. :radio="isRadio"
  9. :counts="isCounts"
  10. :currentKey="currentNodeKey"
  11. @nodeLoad="treeLoadTap"
  12. @nodeTap="treeNodeTap"
  13. />
  14. </template>
  15. <template v-else>
  16. <view class="no-data">暂无相关数据</view>
  17. </template>
  18. </view>
  19. </template>
  20. <script setup>
  21. import {getCurrentInstance, nextTick, onMounted, ref, watch} from 'vue'
  22. import {getArrValue, isNullES} from "js-fast-way";
  23. const instance = getCurrentInstance().proxy
  24. //参数
  25. const props = defineProps({
  26. nodeKey: {
  27. type: String,
  28. default: 'primaryKeyId',
  29. },
  30. labelKey: {
  31. type: String,
  32. default: 'title',
  33. },
  34. leafKey: {
  35. type: String,
  36. default: 'notExsitChild',
  37. },
  38. check: {
  39. type: Boolean,
  40. default: false,
  41. },
  42. //父子是否不关联
  43. strictly: {
  44. type: Boolean,
  45. default: false,
  46. },
  47. checkKey: {
  48. type: Array,
  49. default: () => ([]),
  50. },
  51. radio: {
  52. type: Boolean,
  53. default: false,
  54. },
  55. radioKey: {
  56. type: String,
  57. default: '',
  58. },
  59. currentKey: {
  60. type: String,
  61. default: '',
  62. },
  63. counts: {
  64. type: Boolean,
  65. default: false,
  66. },
  67. autoExpandKey: {
  68. type: Array,
  69. default: () => ([]),
  70. },
  71. })
  72. //节点数据
  73. const treeNodeRef = ref(null)
  74. const nodeData = ref([]);
  75. //参数配置
  76. const isCheck = ref(props.check)
  77. const isRadio = ref(props.radio)
  78. const isStrictly = ref(props.strictly)
  79. const isCounts = ref(props.counts)
  80. const currentNodeKey = ref(props.currentKey)
  81. const AutoExpandKeys = ref(props.autoExpandKey)
  82. //事件
  83. const emit = defineEmits(['load', 'nodeTap'])
  84. //渲染完成
  85. onMounted(() => {
  86. getLazyLoad({
  87. level: 0,
  88. data: {},
  89. parentNodes: {},
  90. isLoad: false
  91. })
  92. })
  93. //监听
  94. watch(() => [
  95. props.autoExpandKey
  96. ], ([keys]) => {
  97. AutoExpandKeys.value = keys
  98. }, {deep: true})
  99. const treeLoadTap = (item) => {
  100. getLazyLoad(item)
  101. }
  102. //懒加载数据
  103. const getLazyLoad = (item) => {
  104. if (!item.isLoad) {
  105. emit('load', item, (data) => {
  106. setLazyLoad(item, data)
  107. })
  108. }
  109. }
  110. //处理获取到的数据
  111. const setLazyLoad = (data, arr) => {
  112. const children = getArrValue(arr), newNodeData = []
  113. if (children.length <= 0) {
  114. data.loading = false
  115. data.isExpand = false
  116. data.isLeaf = true
  117. data.isLoad = true
  118. //如果是跟节点,直接赋值
  119. if (data.level === 0) {
  120. nodeData.value = newNodeData
  121. }
  122. } else {
  123. for (let i = 0; i < children.length; i++) {
  124. const item = children[i]
  125. newNodeData.push({
  126. level: data.level + 1,
  127. key: item[props.nodeKey] ?? '',
  128. label: item[props.labelKey] ?? '',
  129. isLeaf: item[props.leafKey] ?? false,
  130. isExpand: false,
  131. loading: false,
  132. childNodes: [],
  133. isCheck: ifCheckData(data, item),
  134. isRadio: false,
  135. data: item,
  136. parentNodes: data,
  137. isLoad: item[props.leafKey] ?? false,
  138. })
  139. }
  140. //设置父级数据
  141. data.childNodes = newNodeData
  142. data.loading = false
  143. data.isExpand = setBrotherExpand(data.parentNodes)
  144. data.isLoad = true
  145. //如果是跟节点,直接赋值
  146. if (data.level === 0) {
  147. nodeData.value = newNodeData
  148. }
  149. //节点自动展开的处理
  150. const keys = getArrValue(AutoExpandKeys.value)
  151. if(keys.length > 0 && (keys.length - 1) === data.level) {
  152. const item = newNodeData.find(item => item.key === keys[data.level])
  153. if(!isNullES(item)) {
  154. nextTick(() => {
  155. instance.treeModule.treeNodeClick('tree-' + item.key)
  156. })
  157. }
  158. }
  159. //如果没有自动展开的,那么自动展开第一个节点
  160. if (keys.length <= 0 && data.level === 0) {
  161. const item = newNodeData.length > 0 ? newNodeData[0] : {}
  162. if(!isNullES(item)) {
  163. nextTick(() => {
  164. instance.treeModule.treeNodeClick('tree-' + item.key)
  165. })
  166. }
  167. }
  168. }
  169. }
  170. //设置兄弟节点展开状态
  171. const setBrotherExpand = (nodes) => {
  172. const children = getArrValue(nodes.childNodes)
  173. for (let i = 0; i < children.length; i++) {
  174. nodes.childNodes[i].isExpand = false
  175. }
  176. return true
  177. }
  178. //0未选中 1选中 2半选
  179. const ifCheckData = (data, item) => {
  180. const keys = getArrValue(props.checkKey)
  181. if (keys.indexOf(item[props.nodeKey]) > -1) {
  182. return 1
  183. } else {
  184. return 0
  185. }
  186. }
  187. //树节点被点击
  188. const treeNodeTap = async (item) => {
  189. currentNodeKey.value = item.key
  190. let autoKeysArr = []
  191. await getNodeExpandKeys(item, autoKeysArr)
  192. const autoKeys = autoKeysArr.reverse()
  193. emit('nodeTap', item, autoKeys)
  194. }
  195. //处理自动展开的节点KEY
  196. const getNodeExpandKeys = async (item, newKeys) => {
  197. if (item.key && item.level > 0) {
  198. newKeys.push(item.key)
  199. await getNodeExpandKeys(item.parentNodes, newKeys)
  200. }
  201. }
  202. //获取选中的key
  203. const getCheckKeys = async () => {
  204. const nodes = nodeData.value
  205. const nodeKeys = {
  206. nodes: [], keys: [],
  207. halfNodes: [], halfKeys: [],
  208. }
  209. if (!isCheck.value) {
  210. return nodeKeys
  211. }
  212. await forCheckKeys(nodes, nodeKeys)
  213. return nodeKeys
  214. }
  215. //遍历获取选中的key
  216. const forCheckKeys = async (nodes, nodeKeys) => {
  217. for (let i = 0; i < nodes.length; i++) {
  218. const item = nodes[i]
  219. //0未选中 1选中 2半选
  220. if (nodes[i].isCheck === 1) {
  221. nodeKeys.nodes.push(item)
  222. nodeKeys.keys.push(item.key)
  223. } else if (nodes[i].isCheck === 2) {
  224. nodeKeys.halfNodes.push(item)
  225. nodeKeys.halfKeys.push(item.key)
  226. }
  227. if (item.childNodes.length > 0) {
  228. await forCheckKeys(item.childNodes, nodeKeys)
  229. }
  230. }
  231. }
  232. //导出方法函数
  233. defineExpose({
  234. getCheckKeys
  235. })
  236. </script>
  237. <script module="treeModule" lang="renderjs">
  238. export default {
  239. methods: {
  240. treeNodeClick(id) {
  241. document.getElementById(id).click();
  242. }
  243. }
  244. }
  245. </script>
  246. <style lang="scss">
  247. @import "./style.scss";
  248. </style>