index.vue 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165
  1. <template>
  2. <view class="da-tree" :style="{'--theme-color': themeColor}">
  3. <scroll-view class="da-tree-scroll" :scroll-y="true" :scroll-x="false">
  4. <view
  5. class="da-tree-item"
  6. :class="{'is-show': item.show}"
  7. :style="{paddingLeft: item.level * indent + 'rpx'}"
  8. v-for="item in datalist"
  9. :key="item.key">
  10. <view
  11. v-if="item.showArrow && !filterValue"
  12. class="da-tree-item__icon"
  13. @click="handleExpandedChange(item)">
  14. <view :class="['da-tree-item__icon--arr','is-loading']" v-if="loadLoading && item.loading"></view>
  15. <view :class="['da-tree-item__icon--arr','is-expand', {'is-right':!item.expand}]" v-else></view>
  16. </view>
  17. <view v-else class="da-tree-item__icon"></view>
  18. <view
  19. class="da-tree-item__checkbox"
  20. :class="[`da-tree-item__checkbox--${checkboxPlacement}`,{'is--disabled': item.disabled}]"
  21. v-if="showCheckbox"
  22. @click="handleCheckChange(item)">
  23. <view class="da-tree-item__checkbox--icon da-tree-checkbox-checked"
  24. v-if="item.checkedStatus === isCheckedStatus"></view>
  25. <view class="da-tree-item__checkbox--icon da-tree-checkbox-indeterminate"
  26. v-else-if="item.checkedStatus === halfCheckedStatus"></view>
  27. <view class="da-tree-item__checkbox--icon da-tree-checkbox-outline" v-else></view>
  28. </view>
  29. <view
  30. class="da-tree-item__checkbox"
  31. :class="[`da-tree-item__checkbox--${checkboxPlacement}`,{'is--disabled': item.disabled}]"
  32. v-if="!showCheckbox && showRadioIcon"
  33. @click="handleRadioChange(item)">
  34. <view class="da-tree-item__checkbox--icon da-tree-radio-checked"
  35. v-if="item.checkedStatus === isCheckedStatus"></view>
  36. <view class="da-tree-item__checkbox--icon da-tree-radio-indeterminate"
  37. v-else-if="item.checkedStatus === halfCheckedStatus"></view>
  38. <view class="da-tree-item__checkbox--icon da-tree-radio-outline" v-else></view>
  39. </view>
  40. <view class="da-tree-item__label" :class="'da-tree-item__label--'+item.checkedStatus"
  41. @click="handleLabelClick(item)">{{ item.label }}
  42. <text class="da-tree-item__label--append" v-if="item.append">{{ item.append }}</text>
  43. </view>
  44. </view>
  45. </scroll-view>
  46. </view>
  47. </template>
  48. <script>
  49. import {defineComponent, ref, unref, watch} from 'vue'
  50. import {
  51. deepClone,
  52. getAllNodeKeys,
  53. getAllNodes,
  54. halfCheckedStatus,
  55. isArray,
  56. isCheckedStatus,
  57. isFunction,
  58. isNumber,
  59. isString,
  60. logError,
  61. unCheckedStatus,
  62. } from './utils'
  63. import basicProps from './props'
  64. export default defineComponent({
  65. name: 'DaTree',
  66. props: basicProps,
  67. emits: ['change', 'expand'],
  68. setup(props, {emit}) {
  69. /** 原始的树数据 */
  70. const dataRef = ref([])
  71. /** 处理后的一维树项数据 */
  72. const datalist = ref([])
  73. /** 处理后的以key为键值的树项数据 */
  74. const datamap = ref({})
  75. /** 默认的展开数据 */
  76. const expandedKeys = ref([])
  77. /** 默认的已选数据 */
  78. const checkedKeys = ref(null)
  79. /** 加载状态 */
  80. const loadLoading = ref(false)
  81. let fieldMap = {
  82. value: 'value',
  83. label: 'label',
  84. children: 'children',
  85. disabled: 'disabled',
  86. append: 'append',
  87. leaf: 'leaf',
  88. sort: 'sort',
  89. dataNode: "dataNode",
  90. }
  91. /**
  92. * 初始化数据结构
  93. */
  94. function initData() {
  95. fieldMap = {
  96. value: props.field?.key || props.field?.value || props.valueField || 'value',
  97. label: props.field?.label || props.labelField || 'label',
  98. children: props.field?.children || props.childrenField || 'children',
  99. disabled: props.field?.disabled || props.disabledField || 'disabled',
  100. append: props.field?.append || props.appendField || 'append',
  101. leaf: props.field?.leaf || props.leafField || 'leaf',
  102. sort: props.field?.sort || props.sortField || 'sort',
  103. dataNode: props.field?.dataNode || 'dataNode',
  104. }
  105. const data = deepClone(dataRef.value)
  106. datalist.value = []
  107. datamap.value = {}
  108. // clean tree
  109. handleTreeData(data)
  110. // flat tree
  111. datalist.value = checkInitData(datalist.value)
  112. // console.log('init datalist', datalist.value)
  113. // console.log('init datamap', datamap.value)
  114. }
  115. /**
  116. * 转换为节点数据
  117. * @param data
  118. * @param parent
  119. * @param level
  120. */
  121. function handleTreeData(data = [], parent = null, level = 0, insertIndex = -1) {
  122. return data.reduce((prev, cur, index) => {
  123. const key = cur[fieldMap.value]
  124. const children = cur[fieldMap.children] || null
  125. const newItem = createNewItem(cur, index, parent, level)
  126. if (insertIndex > -1) {
  127. // 插入子项尾部
  128. const index = (parent.childrenKeys?.length || 0) + insertIndex + 1
  129. if (!parent?.childrenKeys?.includes(key)) {
  130. datamap.value[key] = newItem
  131. datalist.value.splice(index, 0, newItem)
  132. parent.children.push(newItem)
  133. if (newItem.parentKeys?.length) {
  134. newItem.parentKeys.forEach(k => {
  135. datamap.value[k].childrenKeys = [...datamap.value[k].childrenKeys, newItem.key]
  136. })
  137. }
  138. }
  139. } else {
  140. datamap.value[key] = newItem
  141. datalist.value.push(newItem)
  142. }
  143. const hasChildren = children && children.length > 0
  144. if (hasChildren) {
  145. const childrenData = handleTreeData(children, newItem, level + 1)
  146. // childrenData.sort((a, b) => a.sort - b.sort)
  147. newItem.children = childrenData
  148. const childrenKeys = childrenData.reduce((p, k) => {
  149. const keys = k.childrenKeys
  150. p.push(...keys, k.key)
  151. return p
  152. }, [])
  153. newItem.childrenKeys = childrenKeys
  154. }
  155. prev.push(newItem)
  156. return prev
  157. }, [])
  158. }
  159. /**
  160. * 创建节点
  161. * @param item
  162. * @param index
  163. * @param parent
  164. * @param level
  165. */
  166. function createNewItem(item, index, parent, level) {
  167. const key = item[fieldMap.value]
  168. const label = item[fieldMap.label]
  169. const sort = item[fieldMap.sort] || 0
  170. const children = item[fieldMap.children] || null
  171. const append = item[fieldMap.append] || null
  172. const dataNode = item[fieldMap.dataNode] || false
  173. let disabled = item[fieldMap.disabled] || false
  174. // 优先继承父级禁用属性
  175. disabled = parent?.disabled || disabled
  176. let isLeaf = isFunction(props.isLeafFn) ? props.isLeafFn(item) : (item[fieldMap.leaf] || false)
  177. // const hasChildren = children && children.length > 0
  178. const isEmptyChildren = children && children.length === 0
  179. let showArrow = true
  180. // let isLeaf = !hasChildren
  181. let expand = props.defaultExpandAll || false
  182. // 是否异步加载模式
  183. const isLoadMode = props.loadMode && isFunction(props.loadApi)
  184. if (!children) {
  185. expand = false
  186. if (isLoadMode) {
  187. showArrow = true
  188. } else {
  189. isLeaf = true
  190. showArrow = false
  191. }
  192. }
  193. if (isEmptyChildren) {
  194. expand = false
  195. if (isLoadMode) {
  196. showArrow = true
  197. } else {
  198. isLeaf = true
  199. showArrow = false
  200. }
  201. }
  202. if (isLeaf) {
  203. showArrow = false
  204. expand = false
  205. } else {
  206. showArrow = true
  207. }
  208. // onlyRadioLeaf 单选只能选择末级节点
  209. if (!props.showCheckbox) {
  210. if (props.onlyRadioLeaf) {
  211. if (!isLeaf) {
  212. disabled = true
  213. } else {
  214. // 仍旧继承父类原始禁用状态
  215. disabled = parent?.originItem?.disabled || false
  216. }
  217. }
  218. }
  219. if (disabled) {
  220. if (isLeaf || !children || isEmptyChildren) {
  221. expand = false
  222. showArrow = false
  223. }
  224. }
  225. const parentKey = parent ? parent.key : null
  226. const show = props.defaultExpandAll || level === 0
  227. const newItem = {
  228. key,
  229. parentKey,
  230. label,
  231. append,
  232. isLeaf,
  233. showArrow,
  234. level,
  235. expand,
  236. show,
  237. sort,
  238. disabled,
  239. dataNode,
  240. loaded: false,
  241. loading: false,
  242. indexs: [index],
  243. checkedStatus: unCheckedStatus,
  244. parentKeys: [],
  245. childrenKeys: [],
  246. children: [],
  247. originItem: item,
  248. }
  249. if (parent) {
  250. newItem.parentKeys = [parent.key, ...parent.parentKeys]
  251. newItem.indexs = [...parent.indexs, index]
  252. }
  253. return newItem
  254. }
  255. /**
  256. * 处理选中
  257. * @param list
  258. * @param checkedKeyList
  259. */
  260. function handleCheckState(list, checkedKeyList, checked = true) {
  261. // 多选
  262. if (props.showCheckbox) {
  263. if (checkedKeyList?.length) {
  264. checkedKeyList.forEach(k => {
  265. const item = datamap.value[k]
  266. if (item) {
  267. checkTheChecked(item, checked)
  268. }
  269. })
  270. }
  271. return
  272. }
  273. // 单选
  274. for (let i = 0; i < list.length; i++) {
  275. const item = list[i]
  276. if (item.key === checkedKeyList) {
  277. checkTheRadio(item, checked)
  278. break
  279. }
  280. }
  281. }
  282. /**
  283. * 处理初始化内容
  284. * @param list
  285. */
  286. function checkInitData(list) {
  287. let checkedKeyList = null
  288. let expandedKeyList = []
  289. if (props.showCheckbox) {
  290. checkedKeyList = [...new Set(checkedKeys.value || [])]
  291. expandedKeyList = props.expandChecked ? ([...(checkedKeys.value || []), ...(expandedKeys.value || [])]) : expandedKeys.value
  292. } else {
  293. checkedKeyList = checkedKeys.value || null
  294. expandedKeyList = props.expandChecked && checkedKeys.value ? ([checkedKeys.value, ...(expandedKeys.value || [])]) : expandedKeys.value
  295. }
  296. handleCheckState(list, checkedKeyList, true)
  297. // 处理初始展开
  298. expandedKeyList = [...new Set(expandedKeyList)]
  299. if (!props.defaultExpandAll) {
  300. handleExpandState(list, expandedKeyList, true)
  301. }
  302. list.sort((a, b) => {
  303. if (a.sort === 0 && b.sort === 0) {
  304. return 0
  305. }
  306. if (a.parentKey === b.parentKey) {
  307. if (a.sort - b.sort > 0) {
  308. return 1
  309. } else {
  310. return -1
  311. }
  312. }
  313. return 0
  314. })
  315. return list
  316. }
  317. /**
  318. * 校验多选节点
  319. * @param item
  320. * @param checked
  321. */
  322. function checkTheChecked(item, checked = true) {
  323. const {childrenKeys, parentKeys, disabled = false} = item
  324. if (!props.checkedDisabled && disabled) return
  325. // 当前
  326. item.checkedStatus = checked ? isCheckedStatus : unCheckedStatus
  327. if (!props.checkStrictly) {
  328. // 子类
  329. childrenKeys.forEach(k => {
  330. const childrenItem = unref(datamap)[k]
  331. childrenItem.checkedStatus = (!props.checkedDisabled && childrenItem.disabled) ? childrenItem.checkedStatus : item.checkedStatus
  332. })
  333. // 父类
  334. parentKeys.forEach(k => {
  335. const parentItem = datamap.value[k]
  336. parentItem.checkedStatus = getParentCheckedStatus(parentItem)
  337. })
  338. }
  339. }
  340. /**
  341. * 校验单选节点
  342. * @param item
  343. */
  344. function checkTheRadio(item, checked) {
  345. const {parentKeys, isLeaf, disabled = false} = item
  346. if (!props.checkedDisabled && disabled) return
  347. // 限制末节点选中,但当前非末节点
  348. if (props.onlyRadioLeaf && !isLeaf) {
  349. logError(`限制了末节点选中,当前[${item.label}]非末节点`)
  350. return
  351. }
  352. if (datalist.value?.length) {
  353. datalist.value.forEach(k => {
  354. k.checkedStatus = unCheckedStatus
  355. })
  356. }
  357. parentKeys.forEach(k => {
  358. const parentItem = datamap.value[k]
  359. parentItem.checkedStatus = checked ? getParentCheckedStatus(parentItem) : unCheckedStatus
  360. })
  361. // 当前
  362. item.checkedStatus = checked ? isCheckedStatus : unCheckedStatus
  363. }
  364. /**
  365. * 处理父节点展开
  366. * @param item
  367. * @param expand
  368. */
  369. // function handleExpandParentNode(item, expand = true) {
  370. // if (!expand) return
  371. // if (item?.parentKeys?.length) {
  372. // item.parentKeys.forEach(pk => {
  373. // if (!datamap.value[pk].expand) {
  374. // datamap.value[pk].expand = true
  375. // }
  376. // })
  377. // }
  378. // }
  379. /**
  380. * 处理节点展开
  381. * @param list
  382. * @param expandedKeyList
  383. * @param expand
  384. */
  385. function handleExpandState(list, expandedKeyList, expand = true) {
  386. // 收起
  387. if (expand === false) {
  388. for (let i = 0; i < list.length; i++) {
  389. const item = list[i]
  390. if (expandedKeyList?.includes(item.key)) {
  391. item.expand = false
  392. if (item.childrenKeys?.length) {
  393. item.childrenKeys.forEach(ck => {
  394. datamap.value[ck].expand = false
  395. datamap.value[ck].show = false
  396. })
  397. }
  398. }
  399. }
  400. return
  401. }
  402. // 展开
  403. for (let i = 0; i < list.length; i++) {
  404. const item = list[i]
  405. // 处理展开
  406. if (expandedKeyList?.includes(item.key)) {
  407. // 父子
  408. item.expand = true
  409. if (item.children?.length) {
  410. item.children.forEach(k => {
  411. const kItem = unref(datamap)[k.key]
  412. kItem.show = true
  413. })
  414. }
  415. // 族系
  416. if (item.parentKeys?.length) {
  417. item.parentKeys.forEach(k => {
  418. const kItem = unref(datamap)[k]
  419. kItem.expand = true
  420. if (kItem.children?.length) {
  421. kItem.children.forEach(k => {
  422. const skItem = unref(datamap)[k.key]
  423. skItem.show = true
  424. })
  425. }
  426. })
  427. }
  428. }
  429. }
  430. }
  431. /**
  432. * 点击选框
  433. * @param item
  434. */
  435. function handleCheckChange(item) {
  436. const {childrenKeys, parentKeys, checkedStatus, isLeaf, disabled = false} = item
  437. if (!props.showCheckbox) return
  438. if (disabled) return
  439. // 当前
  440. item.checkedStatus = checkedStatus === isCheckedStatus ? unCheckedStatus : isCheckedStatus
  441. // 子类
  442. if (!props.checkStrictly) {
  443. if (props.expandChecked) {
  444. item.show = true
  445. item.expand = childrenKeys?.length > 0 || isLeaf
  446. }
  447. childrenKeys.forEach(k => {
  448. const childrenItem = unref(datamap)[k]
  449. childrenItem.checkedStatus = childrenItem.disabled ? childrenItem.checkedStatus : item.checkedStatus
  450. if (props.expandChecked) {
  451. childrenItem.show = true
  452. childrenItem.expand = childrenItem?.childrenKeys?.length > 0 || childrenItem.isLeaf
  453. }
  454. })
  455. } else {
  456. if (props.expandChecked) {
  457. logError(`多选时,当 checkStrictly 为 true 时,不支持选择自动展开子节点属性(expandChecked)`)
  458. }
  459. }
  460. // 父类
  461. if (!props.checkStrictly) {
  462. parentKeys.forEach(k => {
  463. const parentItem = datamap.value[k]
  464. parentItem.checkedStatus = getParentCheckedStatus(parentItem)
  465. })
  466. }
  467. const hasCheckedKeys = []
  468. for (let i = 0; i < datalist.value.length; i++) {
  469. const k = datalist.value[i]
  470. if (k.checkedStatus === isCheckedStatus) {
  471. if ((props.packDisabledkey && k.disabled) || !k.disabled) {
  472. hasCheckedKeys.push(k.key)
  473. }
  474. }
  475. }
  476. checkedKeys.value = [...hasCheckedKeys]
  477. emit('change', hasCheckedKeys, item)
  478. }
  479. /**
  480. * 点击单选
  481. * @param item
  482. */
  483. function handleRadioChange(item) {
  484. const {parentKeys, checkedStatus, key, disabled = false, isLeaf} = item
  485. if (props.showCheckbox) return
  486. if (props.onlyRadioLeaf && !isLeaf) handleExpandedChange(item)
  487. if (disabled) return
  488. // 重置所有选择
  489. if (datalist.value?.length) {
  490. for (let i = 0; i < datalist.value.length; i++) {
  491. const k = datalist.value[i]
  492. k.checkedStatus = unCheckedStatus
  493. }
  494. }
  495. parentKeys.forEach(k => {
  496. const parentItem = datamap.value[k]
  497. parentItem.checkedStatus = getParentCheckedStatus(parentItem)
  498. })
  499. // 当前
  500. item.checkedStatus = checkedStatus === isCheckedStatus ? unCheckedStatus : isCheckedStatus
  501. checkedKeys.value = key
  502. emit('change', key, item)
  503. }
  504. /**
  505. * 点击标签
  506. */
  507. function handleLabelClick(item) {
  508. if (props.showCheckbox) {
  509. handleCheckChange(item)
  510. } else {
  511. handleRadioChange(item)
  512. }
  513. }
  514. /**
  515. * 点击展开收起
  516. * @param item
  517. */
  518. async function handleExpandedChange(item) {
  519. if (props.filterValue) return
  520. const {expand, loading = false, disabled} = item
  521. if (loadLoading.value && loading) return
  522. checkExpandedChange(item)
  523. // 异步
  524. item.expand = !expand
  525. let currentItem = null
  526. if (!disabled) {
  527. if (!props.showCheckbox && props.onlyRadioLeaf && props.loadMode) {
  528. logError(`单选时,当 onlyRadioLeaf 为 true 时不支持动态数据`)
  529. } else {
  530. currentItem = await loadExpandNode(item)
  531. }
  532. }
  533. emit('expand', !expand, currentItem || item || null)
  534. }
  535. /**
  536. * 检查展开状态
  537. * @param item
  538. */
  539. function checkExpandedChange(item) {
  540. const {expand, childrenKeys, children = null} = item
  541. if (expand) {
  542. if (childrenKeys?.length) {
  543. childrenKeys.forEach(k => {
  544. if (unref(datamap)[k]) {
  545. unref(datamap)[k].show = false
  546. unref(datamap)[k].expand = false
  547. }
  548. })
  549. }
  550. } else {
  551. if (children?.length) {
  552. const childrenKeys = children.map(k => k.key)
  553. childrenKeys.forEach(k => {
  554. if (unref(datamap)[k]) {
  555. unref(datamap)[k].show = true
  556. }
  557. })
  558. }
  559. }
  560. }
  561. /**
  562. * 加载异步数据
  563. * @param item
  564. */
  565. async function loadExpandNode(item) {
  566. const {expand, key, loaded, children} = item
  567. if (children?.length && !props.alwaysFirstLoad) {
  568. return item
  569. }
  570. if (expand && props.loadMode && !loaded) {
  571. if (isFunction(props.loadApi)) {
  572. expandedKeys.value.push(key)
  573. loadLoading.value = true
  574. item.loading = true
  575. const currentNode = deepClone(item)
  576. const apiRes = await props.loadApi(currentNode)
  577. // 新增子项
  578. let newChildren = [...(item.originItem?.children || []), ...(apiRes || [])]
  579. const newChildrenObj = {}
  580. newChildren = newChildren.reduce((total, next) => {
  581. newChildrenObj[next[fieldMap.value]] ? '' : newChildrenObj[next[fieldMap.value]] = true && total.push(next)
  582. return total
  583. }, [])
  584. item.originItem.children = newChildren || null
  585. if (apiRes?.length) {
  586. const insertIndex = datalist.value.findIndex(k => k.key === item.key)
  587. handleTreeData(apiRes, item, item.level + 1, insertIndex)
  588. datalist.value = checkInitData(datalist.value)
  589. } else {
  590. // 加载后无数据就移除展开图标
  591. item.expand = false
  592. item.isLeaf = true
  593. item.showArrow = false
  594. }
  595. loadLoading.value = false
  596. item.loading = false
  597. item.loaded = true
  598. }
  599. } else {
  600. const eki = expandedKeys.value.findIndex(k => k === key)
  601. if (eki >= 0) {
  602. expandedKeys.value.splice(eki, 1)
  603. }
  604. }
  605. return item
  606. }
  607. /**
  608. * 获取父类的选中状态
  609. * @param item
  610. */
  611. function getParentCheckedStatus(item) {
  612. if (!item) {
  613. return unCheckedStatus
  614. }
  615. if (!props.checkedDisabled && item.disabled) {
  616. return item.checkedStatus || unCheckedStatus
  617. }
  618. // 单选时,父类永远为半选
  619. if (!props.showCheckbox) {
  620. return halfCheckedStatus
  621. }
  622. const {children} = item
  623. // 子类全选中
  624. const childrenCheckedAll = children.every(k => k.checkedStatus === isCheckedStatus)
  625. if (childrenCheckedAll) {
  626. return isCheckedStatus
  627. }
  628. // 子类全不选中
  629. const childrenUncheckedAll = children.every(k => k.checkedStatus === unCheckedStatus)
  630. if (childrenUncheckedAll) {
  631. return unCheckedStatus
  632. }
  633. return halfCheckedStatus
  634. }
  635. function filterData() {
  636. if (props.filterValue === '') {
  637. datalist.value.forEach(k => {
  638. k.show = true
  639. })
  640. return
  641. }
  642. datalist.value.forEach(k => {
  643. if (k.label.indexOf(props.filterValue) > -1) {
  644. k.show = true
  645. k.parentKeys.forEach(k => {
  646. datamap.value[k].show = true
  647. })
  648. } else {
  649. k.show = false
  650. }
  651. })
  652. datalist.value.forEach(k => {
  653. if (k.show) {
  654. k.parentKeys.forEach(k => {
  655. datamap.value[k].show = true
  656. })
  657. }
  658. })
  659. }
  660. /**
  661. * 返回已选的 key
  662. */
  663. const getCheckedKeys = () => getAllNodeKeys(datalist.value, 'checkedStatus', isCheckedStatus, props.packDisabledkey)
  664. /**
  665. * 根据key设置已选
  666. * @param keys 单选时为数字或者字符串,多选时为数组
  667. * @param checked 多选时为key的数组,单选时为key
  668. */
  669. function setCheckedKeys(keys, checked = true) {
  670. // 多选
  671. if (props.showCheckbox) {
  672. if (!isArray(keys)) {
  673. logError(`setCheckedKeys 第一个参数非数组,传入的是[${keys}]`)
  674. return
  675. }
  676. const list = datalist.value
  677. // 取消选择
  678. if (checked === false) {
  679. let newCheckedKeys = []
  680. for (let i = 0; i < checkedKeys.value.length; i++) {
  681. const ck = checkedKeys.value[i]
  682. if (!keys.includes(ck)) {
  683. newCheckedKeys.push(ck)
  684. }
  685. }
  686. newCheckedKeys = [...new Set(newCheckedKeys)]
  687. checkedKeys.value = newCheckedKeys
  688. handleCheckState(list, keys, false)
  689. return
  690. }
  691. // 选择
  692. const newCheckedKeys = [...checkedKeys.value, ...keys]
  693. checkedKeys.value = [...new Set(newCheckedKeys)]
  694. handleCheckState(list, checkedKeys.value, true)
  695. if (props.expandChecked && checked) {
  696. expandedKeys.value = [...new Set([...(checkedKeys.value || []), ...(keys || [])])]
  697. handleExpandState(list, keys, true)
  698. }
  699. return
  700. }
  701. // 单选
  702. // 如果为数组则拿第一个
  703. if (isArray(keys)) {
  704. keys = keys[0]
  705. }
  706. if (!isString(keys) && !isNumber(keys)) {
  707. logError('setCheckedKeys 第一个参数字符串或数字,传入的是==>', keys)
  708. return
  709. }
  710. const list = datalist.value
  711. checkedKeys.value = checked ? keys : null
  712. if (props.expandChecked && checked) {
  713. handleExpandState(list, [keys], true)
  714. }
  715. handleCheckState(list, keys, !!checked)
  716. }
  717. /**
  718. * 返回半选的 key
  719. */
  720. const getHalfCheckedKeys = () => getAllNodeKeys(datalist.value, 'checkedStatus', halfCheckedStatus, props.packDisabledkey)
  721. /**
  722. * 返回未选的 key
  723. */
  724. const getUncheckedKeys = () => getAllNodeKeys(datalist.value, 'checkedStatus', unCheckedStatus, props.packDisabledkey)
  725. /**
  726. * 返回已展开的 key
  727. */
  728. const getExpandedKeys = () => getAllNodeKeys(datalist.value, 'expand', true)
  729. /**
  730. * 返回未展开的 key
  731. */
  732. const getUnexpandedKeys = () => getAllNodeKeys(datalist.value, 'expand', false)
  733. /**
  734. * 根据key展开/收起
  735. *
  736. * @param keys 数组,或字符串 all
  737. * @param expand true为展开/false为收起
  738. */
  739. function setExpandedKeys(keys, expand = true) {
  740. if (!Array.isArray(keys) && keys !== 'all') {
  741. logError('setExpandedKeys 第一个参数非数组,传入的是===>', keys)
  742. return
  743. }
  744. const list = datalist.value
  745. // 展开/收起全部
  746. if (keys === 'all') {
  747. list.forEach(k => {
  748. k.expand = expand
  749. if (k.level > 0) {
  750. k.show = expand
  751. }
  752. })
  753. return
  754. }
  755. // 收起
  756. if (expand === false) {
  757. const newExpandedKeys = []
  758. for (let i = 0; i < expandedKeys.value.length; i++) {
  759. const ek = expandedKeys.value[i]
  760. if (!keys.includes(ek)) {
  761. newExpandedKeys.push(ek)
  762. }
  763. }
  764. expandedKeys.value = [...new Set(newExpandedKeys)]
  765. handleExpandState(list, keys, false)
  766. return
  767. }
  768. // 展开
  769. const newExpandedKeys = []
  770. for (let i = 0; i < list.length; i++) {
  771. if (keys.includes(list[i].key)) {
  772. newExpandedKeys.push(list[i].key)
  773. }
  774. }
  775. expandedKeys.value = [...new Set(newExpandedKeys)]
  776. handleExpandState(list, newExpandedKeys, true)
  777. }
  778. /**
  779. * 返回已选的节点
  780. */
  781. const getCheckedNodes = () => getAllNodes(datalist.value, 'checkedStatus', isCheckedStatus, props.packDisabledkey)
  782. /**
  783. * 返回半选的节点
  784. */
  785. const getHalfCheckedNodes = () => getAllNodes(datalist.value, 'checkedStatus', halfCheckedStatus, props.packDisabledkey)
  786. /**
  787. * 返回未选的节点
  788. */
  789. const getUncheckedNodes = () => getAllNodes(datalist.value, 'checkedStatus', unCheckedStatus, props.packDisabledkey)
  790. /**
  791. * 返回已展开的节点
  792. */
  793. const getExpandedNodes = () => getAllNodes(datalist.value, 'expand', true)
  794. /**
  795. * 返回未展开的节点
  796. */
  797. const getUnexpandedNodes = () => getAllNodes(datalist.value, 'expand', false)
  798. watch(
  799. () => props.defaultExpandedKeys,
  800. (v) => {
  801. if (v?.length) {
  802. expandedKeys.value = v
  803. } else {
  804. expandedKeys.value = []
  805. }
  806. // if (v) checkInitData(datalist.value)
  807. },
  808. {immediate: true}
  809. )
  810. watch(
  811. () => props.defaultCheckedKeys,
  812. (v) => {
  813. if (props.showCheckbox) {
  814. if (v?.length) {
  815. checkedKeys.value = v
  816. } else {
  817. checkedKeys.value = []
  818. }
  819. } else {
  820. if (v || v === 0) {
  821. checkedKeys.value = v
  822. } else {
  823. checkedKeys.value = null
  824. }
  825. }
  826. // checkInitData(datalist.value)
  827. },
  828. {immediate: true}
  829. )
  830. watch(
  831. () => props.data,
  832. (v) => {
  833. dataRef.value = deepClone(v)
  834. setTimeout(() => {
  835. initData()
  836. }, 36)
  837. },
  838. {immediate: true, deep: true}
  839. )
  840. watch(
  841. () => props.filterValue,
  842. () => {
  843. filterData()
  844. },
  845. )
  846. return {
  847. datalist,
  848. unCheckedStatus,
  849. halfCheckedStatus,
  850. isCheckedStatus,
  851. handleCheckChange,
  852. handleRadioChange,
  853. handleLabelClick,
  854. handleExpandedChange,
  855. loadLoading,
  856. // updateChildrenByKey: () => {},
  857. // insertBeforeByKey: () => {},
  858. // insertAfterByKey: () => {},
  859. getCheckedKeys,
  860. setCheckedKeys,
  861. getHalfCheckedKeys,
  862. getUncheckedKeys,
  863. getExpandedKeys,
  864. getUnexpandedKeys,
  865. setExpandedKeys,
  866. getCheckedNodes,
  867. getHalfCheckedNodes,
  868. getUncheckedNodes,
  869. getExpandedNodes,
  870. getUnexpandedNodes,
  871. }
  872. },
  873. })
  874. </script>
  875. <style lang="scss" scoped>
  876. @font-face {
  877. font-family: 'da-tree-iconfont'; /* Project id */
  878. src: url('data:application/octet-stream;base64,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') format('truetype');
  879. }
  880. .da-tree {
  881. width: 100%;
  882. height: 100%;
  883. &-scroll {
  884. width: 100%;
  885. height: 100%;
  886. }
  887. &-item {
  888. display: flex;
  889. align-items: center;
  890. height: 0;
  891. padding: 0;
  892. overflow: hidden;
  893. font-size: 28rpx;
  894. line-height: 1;
  895. visibility: hidden;
  896. opacity: 0;
  897. transition: opacity 0.2s linear;
  898. &.is-show {
  899. height: auto;
  900. padding: 12rpx 24rpx;
  901. visibility: visible;
  902. opacity: 1;
  903. }
  904. &__icon {
  905. display: flex;
  906. align-items: center;
  907. justify-content: center;
  908. width: 40rpx;
  909. height: 40rpx;
  910. overflow: hidden;
  911. &--arr {
  912. position: relative;
  913. display: flex;
  914. align-items: center;
  915. justify-content: center;
  916. width: 32rpx;
  917. height: 32rpx;
  918. &::after {
  919. position: relative;
  920. z-index: 1;
  921. overflow: hidden;
  922. /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
  923. font-family: 'da-tree-iconfont' !important;
  924. font-size: 32rpx;
  925. font-style: normal;
  926. color: #999;
  927. -webkit-font-smoothing: antialiased;
  928. -moz-osx-font-smoothing: grayscale;
  929. }
  930. &.is-expand {
  931. &::after {
  932. content: '\e604';
  933. }
  934. }
  935. &.is-right {
  936. transform: rotate(-90deg);
  937. }
  938. &.is-loading {
  939. animation: IconLoading 1s linear 0s infinite;
  940. &::after {
  941. content: '\e7f1';
  942. }
  943. }
  944. }
  945. }
  946. &__checkbox {
  947. width: 40rpx;
  948. height: 40rpx;
  949. overflow: hidden;
  950. &--left {
  951. order: 0;
  952. }
  953. &--right {
  954. order: 1;
  955. }
  956. &--icon {
  957. position: relative;
  958. display: flex;
  959. align-items: center;
  960. justify-content: center;
  961. width: 40rpx;
  962. height: 40rpx;
  963. &::after {
  964. position: relative;
  965. top: 0;
  966. left: 0;
  967. z-index: 1;
  968. overflow: hidden;
  969. /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
  970. font-family: 'da-tree-iconfont' !important;
  971. font-size: 32rpx;
  972. font-style: normal;
  973. -webkit-font-smoothing: antialiased;
  974. -moz-osx-font-smoothing: grayscale;
  975. }
  976. &.da-tree-checkbox-outline::after {
  977. color: #bbb;
  978. content: '\ead5';
  979. }
  980. &.da-tree-checkbox-checked::after {
  981. color: var(--theme-color, #007aff);
  982. content: '\ead4';
  983. }
  984. &.da-tree-checkbox-indeterminate::after {
  985. color: var(--theme-color, #007aff);
  986. content: '\ebce';
  987. }
  988. &.da-tree-radio-outline::after {
  989. color: #bbb;
  990. content: '\ecc5';
  991. }
  992. &.da-tree-radio-checked::after {
  993. color: var(--theme-color, #007aff);
  994. content: '\ecc4';
  995. }
  996. &.da-tree-radio-indeterminate::after {
  997. color: var(--theme-color, #007aff);
  998. content: '\ea4f';
  999. }
  1000. }
  1001. &.is--disabled {
  1002. cursor: not-allowed;
  1003. opacity: 0.35;
  1004. }
  1005. }
  1006. &__label {
  1007. flex: 1;
  1008. margin-left: 4rpx;
  1009. color: #555;
  1010. &--2 {
  1011. color: var(--theme-color, #007aff);
  1012. }
  1013. &--append {
  1014. font-size: 60%;
  1015. opacity: 0.6;
  1016. }
  1017. }
  1018. }
  1019. }
  1020. @keyframes IconLoading {
  1021. 0% {
  1022. transform: rotate(0deg);
  1023. }
  1024. 100% {
  1025. transform: rotate(360deg);
  1026. }
  1027. }
  1028. </style>