uni-tree-view.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. <template>
  2. <view
  3. class="uni-tree-view-container"
  4. :style="{ '--theme-color': props.themeColor }">
  5. <scroll-view
  6. class="scroll-view-container"
  7. :scroll-y="true"
  8. :scroll-top="virtualEnabled ? virtualScrollCommandTop : undefined"
  9. :scroll-into-view="scrollIntoView"
  10. :style="scrollViewStyle"
  11. @scroll="handleVirtualScroll">
  12. <view v-if="visibleTreeList.length === 0" class="utv-tree-empty">
  13. <template v-if="hasFilterValue">
  14. <slot name="empty-filter" :filter-value="props.filterValue">
  15. <slot name="empty" :filter-value="props.filterValue">
  16. {{ props.emptyText }}
  17. </slot>
  18. </slot>
  19. </template>
  20. <slot v-else name="empty" :filter-value="props.filterValue">
  21. {{ props.emptyText }}
  22. </slot>
  23. </view>
  24. <view
  25. v-if="virtualEnabled && virtualTopPadding > 0"
  26. class="utv-tree-virtual-spacer"
  27. :style="{ height: `${virtualTopPadding}px` }"></view>
  28. <view
  29. v-for="item in renderedTreeList"
  30. :id="item.domId"
  31. :key="item.node.id"
  32. :style="{
  33. paddingLeft: `${item.node.level * props.indent}rpx`,
  34. height: virtualEnabled ? `${props.virtualItemHeight}px` : undefined,
  35. minHeight: virtualEnabled ? `${props.virtualItemHeight}px` : undefined
  36. }"
  37. class="utv-tree-item"
  38. :class="[
  39. props.nodeClass,
  40. {
  41. 'is-leaf': item.node.isLeaf,
  42. 'is-expanded': item.node.expanded,
  43. 'is-disabled': item.node.disabled,
  44. 'is-checked': props.selectable && item.node.checked === 'checked',
  45. 'is-virtual': virtualEnabled
  46. }
  47. ]"
  48. :hover-class="item.node.disabled ? 'none' : 'utv-tree-item--hover'"
  49. :hover-stay-time="80"
  50. @click="handleNodeClick(item.node)">
  51. <view class="utv-tree-item__state-layer"></view>
  52. <view
  53. v-if="isExpandable(item.node)"
  54. class="utv-tree-item__arrow-icon is-right"
  55. :class="{
  56. 'is-expand': item.node.expanded,
  57. 'is-loading': item.node.loading,
  58. 'is-load-error': Boolean(item.node.loadError)
  59. }"
  60. @click.stop="handleToggleExpand(item.node)"></view>
  61. <view v-else class="utv-tree-item__arrow-placeholder"></view>
  62. <view
  63. v-if="showSelectionControl && props.selectionPlacement === 'left'"
  64. class="utv-tree-item__checkbox"
  65. :class="{ 'is-disabled': item.node.disabled }"
  66. @click.stop="handleCheckChange(item.node)">
  67. <view
  68. class="utv-tree-item__checkbox-icon"
  69. :class="getSelectionIconClass(item.node)"></view>
  70. </view>
  71. <view class="utv-tree-node-content">
  72. <template v-if="slots.default">
  73. <slot
  74. name="default"
  75. :node="item.node"
  76. :data="item.node.source"
  77. :path="item.path"></slot>
  78. </template>
  79. <template v-else>
  80. <view v-if="item.node.icon || slots.icon" class="utv-tree-node-icon">
  81. <template v-if="slots.icon">
  82. <slot
  83. name="icon"
  84. :node="item.node"
  85. :data="item.node.source"
  86. :path="item.path"></slot>
  87. </template>
  88. <template v-else>{{ item.node.icon }}</template>
  89. </view>
  90. <view class="utv-tree-node-main">
  91. <view :class="['utv-tree-node-label',{'parent':item.node?.source?.children?.length > 0}]">
  92. <template v-if="slots.label">
  93. <slot
  94. name="label"
  95. :node="item.node"
  96. :data="item.node.source"
  97. :path="item.path"></slot>
  98. </template>
  99. <template v-else>
  100. <text
  101. v-for="(segment, segmentIndex) in getLabelSegments(item.node.label)"
  102. :key="segmentIndex"
  103. :class="{ 'utv-tree-node-label__match': segment.matched }">
  104. {{ segment.text }}
  105. </text>
  106. </template>
  107. </view>
  108. <view v-if="props.showPath && item.node.path.length > 1" class="utv-tree-node-path">
  109. {{ item.node.path.join(props.pathSeparator) }}
  110. </view>
  111. </view>
  112. <view v-if="item.node.append || slots.append" class="utv-tree-node-append">
  113. <template v-if="slots.append">
  114. <slot
  115. name="append"
  116. :node="item.node"
  117. :data="item.node.source"
  118. :path="item.path"></slot>
  119. </template>
  120. <template v-else>{{ item.node.append }}</template>
  121. </view>
  122. </template>
  123. </view>
  124. <view
  125. v-if="showSelectionControl && props.selectionPlacement === 'right'"
  126. class="utv-tree-item__checkbox"
  127. :class="{ 'is-disabled': item.node.disabled }"
  128. @click.stop="handleCheckChange(item.node)">
  129. <view
  130. class="utv-tree-item__checkbox-icon"
  131. :class="getSelectionIconClass(item.node)"></view>
  132. </view>
  133. </view>
  134. <view
  135. v-if="virtualEnabled && virtualBottomPadding > 0"
  136. class="utv-tree-virtual-spacer"
  137. :style="{ height: `${virtualBottomPadding}px` }"></view>
  138. </scroll-view>
  139. </view>
  140. </template>
  141. <script lang="ts" setup>
  142. import { computed, nextTick, shallowRef, watch } from "vue";
  143. import type {
  144. TreeCheckChangePayload,
  145. TreeKey,
  146. TreeNode,
  147. TreeScrollToOptions,
  148. UniTreeViewEmits,
  149. UniTreeViewExposed,
  150. UniTreeViewProps,
  151. UniTreeViewSlots
  152. } from "./types";
  153. import { useTreeViewState } from "./useTreeViewState";
  154. import { useVirtualTreeList } from "./useVirtualTreeList";
  155. defineOptions({
  156. name: "UniTreeView",
  157. options: {
  158. // #ifdef MP-WEIXIN || MP-ALIPAY
  159. virtualHost: true
  160. // #endif
  161. }
  162. });
  163. const props = withDefaults(defineProps<UniTreeViewProps>(), {
  164. modelValue: undefined,
  165. data: () => [],
  166. treeProps: undefined,
  167. filterValue: "",
  168. filterMethod: undefined,
  169. highlightFilter: true,
  170. themeColor: "#007aff",
  171. selectable: false,
  172. showRadioIcon: true,
  173. multiple: false,
  174. checkOnClickNode: false,
  175. checkOnClickLeaf: false,
  176. expandOnClickNode: false,
  177. accordion: false,
  178. checkStrictly: false,
  179. onlyRadioLeaf: false,
  180. defaultExpandAll: false,
  181. defaultExpandedKeys: () => [],
  182. defaultExpandParent: true,
  183. expandChecked: false,
  184. cacheExpandedKeys: false,
  185. defaultCheckedKeys: () => [],
  186. loadMode: false,
  187. loadApi: undefined,
  188. isLeafFn: undefined,
  189. alwaysFirstLoad: false,
  190. checkedDisabled: false,
  191. packDisabledKey: undefined,
  192. packDisabledkey: undefined,
  193. nodeClass: "",
  194. indent: 40,
  195. selectionPlacement: "left",
  196. emptyText: "暂无数据",
  197. showPath: false,
  198. pathSeparator: " / ",
  199. virtual: false,
  200. virtualItemHeight: 36,
  201. virtualHeight: 400,
  202. virtualOverscan: 8
  203. });
  204. const emit = defineEmits<UniTreeViewEmits>();
  205. const slots = defineSlots<UniTreeViewSlots>();
  206. const scrollIntoView = shallowRef("");
  207. const virtualScrollCommandTop = shallowRef<number>();
  208. const nodeDomIds = new Map<TreeKey, string>();
  209. let nextNodeDomId = 0;
  210. const {
  211. isMultiple,
  212. treeVersion,
  213. reconciledModelValue,
  214. pendingCheckChangePayload,
  215. visibleTreeList,
  216. toggleExpand,
  217. checkNode,
  218. isExpandable,
  219. getSelectionIconClass,
  220. loadNode: loadStateNode,
  221. setCheckedKeys: setStateCheckedKeys,
  222. getCheckedKeys,
  223. getHalfCheckedKeys,
  224. getUncheckedKeys,
  225. getCheckedNodes,
  226. getHalfCheckedNodes,
  227. getUncheckedNodes,
  228. setExpandedKeys,
  229. getExpandedKeys,
  230. getUnexpandedKeys,
  231. getVisibleKeys,
  232. getMatchedKeys,
  233. getExpandedNodes,
  234. getUnexpandedNodes,
  235. getVisibleNodes,
  236. getMatchedNodes,
  237. getNode,
  238. getNodePath,
  239. expandAll,
  240. collapseAll
  241. } = useTreeViewState(props);
  242. const showSelectionControl = computed(() => {
  243. return Boolean(props.selectable && (isMultiple.value || props.showRadioIcon));
  244. });
  245. const hasFilterValue = computed(() => String(props.filterValue ?? "").trim().length > 0);
  246. const {
  247. virtualEnabled,
  248. renderedItems: virtualRenderedTreeList,
  249. scrollTop: virtualScrollTop,
  250. topPadding: virtualTopPadding,
  251. bottomPadding: virtualBottomPadding,
  252. scrollViewStyle,
  253. handleScroll: handleVirtualScroll,
  254. scrollToIndex
  255. } = useVirtualTreeList({
  256. items: visibleTreeList,
  257. virtual: () => props.virtual,
  258. itemHeight: () => props.virtualItemHeight,
  259. height: () => props.virtualHeight,
  260. overscan: () => props.virtualOverscan
  261. });
  262. interface RenderedTreeItem {
  263. node: TreeNode;
  264. path: TreeNode[];
  265. domId: string;
  266. }
  267. const needsRenderedNodePath = computed(() => {
  268. return Boolean(slots.default || slots.icon || slots.label || slots.append);
  269. });
  270. const renderedTreeList = computed(() => {
  271. const includePath = needsRenderedNodePath.value;
  272. return virtualRenderedTreeList.value.map((node): RenderedTreeItem => ({
  273. node,
  274. path: includePath ? getNodePath(node) : [],
  275. domId: getNodeDomId(node)
  276. }));
  277. });
  278. async function handleToggleExpand(node: TreeNode) {
  279. if (node.loadError && !node.loading) {
  280. await retryLoadSafely(node);
  281. return;
  282. }
  283. const shouldLoad = !node.loaded;
  284. const payload = toggleExpand(node);
  285. if (!payload) {
  286. return;
  287. }
  288. emit("expand-change", payload);
  289. if (payload.expanded && shouldLoad) {
  290. await loadNodeSafely(node);
  291. }
  292. }
  293. function handleNodeClick(node: TreeNode) {
  294. emit("node-click", {
  295. id: node.id,
  296. node,
  297. path: getNodePath(node)
  298. });
  299. if (props.expandOnClickNode && isExpandable(node)) {
  300. void handleToggleExpand(node);
  301. }
  302. if (props.selectable && (props.checkOnClickNode || (props.checkOnClickLeaf && node.isLeaf))) {
  303. handleCheckChange(node);
  304. }
  305. }
  306. async function loadNode(node: TreeNode) {
  307. const wasLoaded = node.loaded;
  308. try {
  309. const children = await loadStateNode(node);
  310. if (!wasLoaded && node.loaded) {
  311. emit("load", { node, children });
  312. }
  313. return children;
  314. } catch (error) {
  315. emit("load-error", { node, error });
  316. throw error;
  317. }
  318. }
  319. async function loadNodeSafely(node: TreeNode) {
  320. try {
  321. await loadNode(node);
  322. } catch {
  323. // The load-error event carries the failure and the node remains retryable.
  324. }
  325. }
  326. async function retryLoad(keyOrNode: TreeKey | TreeNode) {
  327. const node = typeof keyOrNode === "object" ? keyOrNode : getNode(keyOrNode);
  328. if (!node) {
  329. return [];
  330. }
  331. return loadNode(node);
  332. }
  333. async function retryLoadSafely(node: TreeNode) {
  334. try {
  335. await retryLoad(node);
  336. } catch {
  337. // The load-error event carries the failure and another retry remains possible.
  338. }
  339. }
  340. async function scrollToKey(key: TreeKey, options: TreeScrollToOptions = {}) {
  341. const node = getNode(key);
  342. if (!node) {
  343. return false;
  344. }
  345. if (options.expandParents !== false && node.parentIds.length > 0) {
  346. setExpandedKeys(node.parentIds, true);
  347. }
  348. await nextTick();
  349. const visibleIndex = visibleTreeList.value.findIndex((item) => item.id === key);
  350. if (visibleIndex === -1) {
  351. return false;
  352. }
  353. if (virtualEnabled.value) {
  354. virtualScrollCommandTop.value = undefined;
  355. await nextTick();
  356. if (!scrollToIndex(visibleIndex)) {
  357. return false;
  358. }
  359. virtualScrollCommandTop.value = virtualScrollTop.value;
  360. return true;
  361. }
  362. scrollIntoView.value = "";
  363. await nextTick();
  364. scrollIntoView.value = getNodeDomId(node);
  365. return true;
  366. }
  367. function getNodeDomId(node: TreeNode) {
  368. const cachedDomId = nodeDomIds.get(node.id);
  369. if (cachedDomId) {
  370. return cachedDomId;
  371. }
  372. nextNodeDomId += 1;
  373. const domId = `utv-tree-node-${nextNodeDomId}`;
  374. nodeDomIds.set(node.id, domId);
  375. return domId;
  376. }
  377. function getLabelSegments(label: string) {
  378. const filterValue = String(props.filterValue ?? "").trim();
  379. if (!props.highlightFilter || !filterValue) {
  380. return [{ text: label, matched: false }];
  381. }
  382. const normalizedLabel = label.toLowerCase();
  383. const normalizedFilter = filterValue.toLowerCase();
  384. const segments: Array<{ text: string; matched: boolean }> = [];
  385. let startIndex = 0;
  386. let matchIndex = normalizedLabel.indexOf(normalizedFilter);
  387. while (matchIndex !== -1) {
  388. if (matchIndex > startIndex) {
  389. segments.push({ text: label.slice(startIndex, matchIndex), matched: false });
  390. }
  391. const endIndex = matchIndex + filterValue.length;
  392. segments.push({ text: label.slice(matchIndex, endIndex), matched: true });
  393. startIndex = endIndex;
  394. matchIndex = normalizedLabel.indexOf(normalizedFilter, startIndex);
  395. }
  396. if (startIndex < label.length) {
  397. segments.push({ text: label.slice(startIndex), matched: false });
  398. }
  399. return segments.length > 0 ? segments : [{ text: label, matched: false }];
  400. }
  401. function emitFilterChange() {
  402. emit("filter-change", {
  403. value: props.filterValue,
  404. keys: getVisibleKeys(),
  405. nodes: getVisibleNodes(),
  406. matchedKeys: getMatchedKeys(),
  407. matchedNodes: getMatchedNodes()
  408. });
  409. }
  410. // 过滤条件或状态树变化时对账:只要本次或上一次处于过滤中就同步结果,避免未过滤时的数据更新触发噪音事件。
  411. let previouslyHadFilterValue = false;
  412. watch(
  413. () => [props.filterValue, props.filterMethod, treeVersion.value] as const,
  414. () => {
  415. const currentlyHasFilterValue = hasFilterValue.value;
  416. const shouldEmit = currentlyHasFilterValue || previouslyHadFilterValue;
  417. previouslyHadFilterValue = currentlyHasFilterValue;
  418. if (!shouldEmit) {
  419. return;
  420. }
  421. emitFilterChange();
  422. },
  423. { flush: "post", immediate: true }
  424. );
  425. // 状态树重建或懒加载追加后回收已移除节点的 DOM id,保持 scrollToKey 的查询目标与渲染一致。
  426. watch(
  427. treeVersion,
  428. () => {
  429. for (const key of nodeDomIds.keys()) {
  430. if (!getNode(key)) {
  431. nodeDomIds.delete(key);
  432. }
  433. }
  434. },
  435. { flush: "sync" }
  436. );
  437. watch(
  438. reconciledModelValue,
  439. (payload) => {
  440. if (payload) {
  441. emit("update:modelValue", payload.value);
  442. }
  443. },
  444. { flush: "post" }
  445. );
  446. watch(
  447. pendingCheckChangePayload,
  448. (payload) => {
  449. if (payload) {
  450. commitSelectionChange(payload);
  451. }
  452. },
  453. { flush: "post" }
  454. );
  455. function handleCheckChange(node: TreeNode) {
  456. const payload = checkNode(node);
  457. if (!payload) {
  458. return;
  459. }
  460. commitSelectionChange(payload);
  461. }
  462. function commitSelectionChange(payload: TreeCheckChangePayload) {
  463. emit("update:modelValue", payload.value);
  464. emit("check-change", payload);
  465. }
  466. function setCheckedKeys(keys: TreeKey | TreeKey[], checked = true) {
  467. const payload = setStateCheckedKeys(keys, checked);
  468. if (payload) {
  469. commitSelectionChange(payload);
  470. return payload.value;
  471. }
  472. const checkedKeys = getCheckedKeys();
  473. return isMultiple.value ? checkedKeys : (checkedKeys[0] ?? null);
  474. }
  475. const exposed = {
  476. setCheckedKeys,
  477. getCheckedKeys,
  478. getHalfCheckedKeys,
  479. getUncheckedKeys,
  480. getCheckedNodes,
  481. getHalfCheckedNodes,
  482. getUncheckedNodes,
  483. setExpandedKeys,
  484. getExpandedKeys,
  485. getUnexpandedKeys,
  486. getVisibleKeys,
  487. getMatchedKeys,
  488. getExpandedNodes,
  489. getUnexpandedNodes,
  490. getVisibleNodes,
  491. getMatchedNodes,
  492. getNode,
  493. getNodePath,
  494. expandAll,
  495. collapseAll,
  496. loadNode,
  497. retryLoad,
  498. scrollToKey
  499. } satisfies UniTreeViewExposed;
  500. defineExpose(exposed);
  501. </script>
  502. <style lang="scss">
  503. @font-face {
  504. font-family: "uni-tree-iconfont";
  505. src: url('data:application/octet-stream;base64,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') format('truetype');
  506. }
  507. </style>
  508. <style lang="scss" scoped>
  509. @use "../../style/index.scss";
  510. .uni-tree-view-container {
  511. width: 100%;
  512. height: 100%;
  513. }
  514. </style>