index.vue 37 KB

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