oxy-uni-ui 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +1 -1
  2. package/attributes.json +1 -1
  3. package/components/common/abstracts/variable.scss +40 -4
  4. package/components/common/util.ts +44 -0
  5. package/components/composables/index.ts +1 -0
  6. package/components/composables/useVirtualScroll.ts +172 -0
  7. package/components/oxy-checkbox/index.scss +36 -6
  8. package/components/oxy-checkbox/oxy-checkbox.vue +5 -4
  9. package/components/oxy-checkbox/types.ts +2 -1
  10. package/components/oxy-col-picker/index.scss +18 -15
  11. package/components/oxy-col-picker/oxy-col-picker.vue +28 -3
  12. package/components/oxy-col-picker/types.ts +12 -0
  13. package/components/oxy-corner/index.scss +258 -0
  14. package/components/oxy-corner/oxy-corner.vue +67 -0
  15. package/components/oxy-corner/types.ts +50 -0
  16. package/components/oxy-drop-menu/index.scss +4 -0
  17. package/components/oxy-drop-menu/oxy-drop-menu.vue +5 -3
  18. package/components/oxy-drop-menu/types.ts +1 -1
  19. package/components/oxy-drop-menu-item/index.scss +4 -4
  20. package/components/oxy-drop-menu-item/oxy-drop-menu-item.vue +2 -0
  21. package/components/oxy-file-list/index.scss +83 -0
  22. package/components/oxy-file-list/oxy-file-list.vue +213 -0
  23. package/components/oxy-file-list/types.ts +54 -0
  24. package/components/oxy-list/index.scss +5 -0
  25. package/components/oxy-list/oxy-list.vue +206 -0
  26. package/components/oxy-list/types.ts +38 -0
  27. package/components/oxy-slider/index.scss +2 -2
  28. package/components/oxy-swiper/index.scss +1 -2
  29. package/components/oxy-textarea/oxy-textarea.vue +0 -4
  30. package/components/oxy-tree/components/tree-node-content.vue +72 -0
  31. package/components/oxy-tree/index.scss +83 -0
  32. package/components/oxy-tree/index.ts +51 -0
  33. package/components/oxy-tree/oxy-tree.vue +406 -0
  34. package/components/oxy-tree/types.ts +85 -0
  35. package/components/oxy-tree/utils.ts +51 -0
  36. package/components/oxy-upload/images/audio.png +0 -0
  37. package/components/oxy-upload/images/excle.png +0 -0
  38. package/components/oxy-upload/images/other.png +0 -0
  39. package/components/oxy-upload/images/pdf.png +0 -0
  40. package/components/oxy-upload/images/pic.png +0 -0
  41. package/components/oxy-upload/images/txt.png +0 -0
  42. package/components/oxy-upload/images/video.png +0 -0
  43. package/components/oxy-upload/images/word.png +0 -0
  44. package/components/oxy-upload/index.scss +50 -0
  45. package/components/oxy-upload/oxy-upload.vue +93 -7
  46. package/components/oxy-upload/types.ts +22 -1
  47. package/components/oxy-virtual-scroll/index.scss +35 -0
  48. package/components/oxy-virtual-scroll/oxy-virtual-scroll.vue +143 -0
  49. package/components/oxy-virtual-scroll/types.ts +155 -0
  50. package/components/oxy-virtual-scroll/virtual-scroll.ts +81 -0
  51. package/global.d.ts +3 -0
  52. package/locale/lang/ar-SA.ts +2 -1
  53. package/locale/lang/en-US.ts +10 -9
  54. package/locale/lang/zh-CN.ts +7 -6
  55. package/package.json +1 -1
  56. package/tags.json +1 -1
  57. package/web-types.json +1 -1
@@ -0,0 +1,83 @@
1
+ @import '../common/abstracts/variable';
2
+ @import '../common/abstracts/mixin';
3
+ @import '../oxy-virtual-scroll/index.scss';
4
+
5
+ .oxy-tree {
6
+ position: relative;
7
+ &__virtual-scroll {
8
+ position: relative;
9
+ overflow: hidden;
10
+ }
11
+
12
+ &__view {
13
+ height: 100%;
14
+ }
15
+
16
+ &__container {
17
+ position: relative;
18
+ }
19
+
20
+ &__items {
21
+ position: absolute;
22
+ top: 0;
23
+ left: 0;
24
+ right: 0;
25
+ }
26
+ }
27
+
28
+ .oxy-tree-node-content {
29
+ display: flex;
30
+ width: fit-content;
31
+ width: 100%;
32
+ white-space: nowrap;
33
+ align-items: center;
34
+
35
+ &.expanded {
36
+ :deep(.oxy-tree-node-icon) {
37
+ transform: rotate(0deg);
38
+ }
39
+ }
40
+
41
+ &.is-leaf {
42
+ :deep(.oxy-tree-node-icon) {
43
+ opacity: 0;
44
+ }
45
+ }
46
+
47
+ &.is-current {
48
+ background-color: $-tree-node-current-bg;
49
+ color: $-tree-node-current-color;
50
+ }
51
+
52
+ &.is-disabled {
53
+ color: $-tree-node-disabled-color;
54
+ }
55
+
56
+ :deep(.oxy-checkbox) {
57
+ margin-right: 6px;
58
+
59
+ .oxy-checkbox__label {
60
+ display: none;
61
+ }
62
+
63
+ .oxy-checkbox__shape {
64
+ transition: none;
65
+ }
66
+
67
+ .oxy-checkbox__indeterminate {
68
+ transition: none;
69
+ }
70
+
71
+ .oxy-checkbox__check {
72
+ transition: none;
73
+ }
74
+ }
75
+
76
+ :deep(.oxy-tree-node-icon) {
77
+ transform: rotate(-90deg);
78
+ }
79
+
80
+ .oxy-tree-node {
81
+ flex: 1;
82
+ }
83
+ }
@@ -0,0 +1,51 @@
1
+ import type { TextProps, RawTreeNode } from './types'
2
+
3
+ export const useTreeMethods = (props: TextProps) => {
4
+ function getDisabled(node: RawTreeNode): boolean {
5
+ return node.disabled ?? false
6
+ }
7
+
8
+ function getChildren(node: RawTreeNode): RawTreeNode[] {
9
+ return node[props.childrenKey] ?? []
10
+ }
11
+
12
+ function getLabel(node: RawTreeNode): string {
13
+ return node[props.labelKey] ?? ''
14
+ }
15
+
16
+ function getKey(node: RawTreeNode | undefined): string {
17
+ if (!node) {
18
+ return ''
19
+ }
20
+ return (node[props.valueKey] ?? '') as string
21
+ }
22
+
23
+ return {
24
+ getDisabled,
25
+ getChildren,
26
+ getLabel,
27
+ getKey
28
+ }
29
+ }
30
+ /**
31
+ * 判断两个 Set 中的元素是否完全相同(不考虑顺序)
32
+ * @param {Set} setA - 第一个 Set
33
+ * @param {Set} setB - 第二个 Set
34
+ * @returns {boolean} 两个 Set 是否相同
35
+ */
36
+ export function isSetsEqual(setA: Set<any>, setB: Set<any>) {
37
+ // 步骤1:如果大小不同,直接返回 false
38
+ if (setA.size !== setB.size) {
39
+ return false
40
+ }
41
+
42
+ // 步骤2:遍历 setA 的所有元素,检查 setB 是否都包含
43
+ for (const item of setA) {
44
+ if (!setB.has(item)) {
45
+ return false // 有元素不匹配,返回 false
46
+ }
47
+ }
48
+
49
+ // 所有元素匹配,返回 true
50
+ return true
51
+ }
@@ -0,0 +1,406 @@
1
+ <template>
2
+ <view class="oxy-tree" :class="customClass" :style="customStyle">
3
+ <view v-if="data.length" class="oxy-tree__virtual-scroll" :style="{ height: height }">
4
+ <scroll-view class="oxy-tree__view" scroll-y :scroll-x="true" :scroll-top="scrollTop" @scroll="handleScroll">
5
+ <view class="oxy-tree__container" :style="{ height: totalHeight + 'px' }">
6
+ <view class="oxy-tree__items" :style="{ transform: `translateY(${virtualOffsetY}px)` }">
7
+ <view v-for="(item, index) in virtualData" :key="index">
8
+ <view class="oxy-tree-node-content" :style="getNodeStyle(item)" :class="getNodeClass(item)" @click="handleNodeClick(item)">
9
+ <!-- 兼容支付宝、微信小程序 -->
10
+ <view @tap.stop="handleClickExpand(item)">
11
+ <oxy-icon name="fill-arrow-down" custom-class="oxy-tree-node-icon" size="22px"></oxy-icon>
12
+ </view>
13
+ <oxy-checkbox
14
+ v-if="showCheckbox"
15
+ :modelValue="item.checked"
16
+ :disabled="item.disabled"
17
+ :indeterminate="item.immediate"
18
+ shape="square"
19
+ ></oxy-checkbox>
20
+ <view class="oxy-tree-node">
21
+ <slot name="node" :node="item" :data="item.data">
22
+ {{ item.label }}
23
+ </slot>
24
+ </view>
25
+ </view>
26
+ </view>
27
+ </view>
28
+ </view>
29
+ </scroll-view>
30
+ </view>
31
+ <view v-else>
32
+ <oxy-status-tip image="content" :tip="emptyText" />
33
+ </view>
34
+
35
+ <!-- 回到顶部按钮 -->
36
+ <view v-if="showBackToTop && showBackTopBtn" class="oxy-virtual-scroll__back-top" @click="scrollToTop">
37
+ <oxy-icon name="backtop" color="#fff" size="20px"></oxy-icon>
38
+ </view>
39
+ </view>
40
+ </template>
41
+
42
+ <script lang="ts">
43
+ export default {
44
+ name: 'oxy-tree',
45
+ options: {
46
+ virtualHost: true,
47
+ addGlobalClass: true,
48
+ styleIsolation: 'shared'
49
+ }
50
+ }
51
+ </script>
52
+
53
+ <script lang="ts" setup>
54
+ import { computed, nextTick, provide, type Ref, ref, toRefs, watch } from 'vue'
55
+ import type { RawTreeNode, Tree, TreeInstance, TreeNode } from './types'
56
+ import { treeProps, type TreeExpose } from './types'
57
+ import { isSetsEqual, useTreeMethods } from './utils'
58
+ import { useVirtualScroll } from '../composables/useVirtualScroll'
59
+
60
+ // 获取组件的 props 和 emit 函数
61
+ const props = defineProps(treeProps)
62
+ const {
63
+ modelValue,
64
+ data,
65
+ defaultExpandedKeys,
66
+ expandAll,
67
+ showCheckbox,
68
+ checkStrictly,
69
+ selectionLeafOnly,
70
+ height,
71
+ itemHeight,
72
+ nodeKey,
73
+ backToTopThreshold
74
+ } = toRefs(props)
75
+ const emit = defineEmits<{
76
+ (e: 'node-click', node: TreeNode): void
77
+ (e: 'update:modelValue', value: string[] | string | undefined): void
78
+ (e: 'node-check', node: TreeNode): void
79
+ (e: 'node-expand', node: TreeNode): void
80
+ (e: 'node-collapse', node: TreeNode): void
81
+ }>()
82
+
83
+ const { getDisabled, getChildren, getLabel, getKey } = useTreeMethods(props)
84
+ const expandedKeySet = ref<Set<string>>(new Set())
85
+ const hiddenNodeKeySet = ref<Set<string>>(new Set())
86
+ const checkedKeys = ref<Set<string>>(new Set())
87
+ const immediateKeySet = ref<Set<string>>(new Set())
88
+ const currentNode = ref<TreeNode>()
89
+
90
+ const tree = ref<Tree>() as Ref<Tree>
91
+ const flattenTree = computed<TreeNode[]>(() => {
92
+ const expandedKeys = expandedKeySet.value
93
+ const hiddenKeys = hiddenNodeKeySet.value
94
+ const flattenNodes: TreeNode[] = []
95
+ const nodes = (tree.value && tree.value.treeNodes) || []
96
+ function traverse() {
97
+ const stack: TreeNode[] = []
98
+ for (let i = nodes.length - 1; i >= 0; --i) {
99
+ stack.push(nodes[i])
100
+ }
101
+ while (stack.length) {
102
+ const node = stack.pop()
103
+ if (!node) continue
104
+ if (!hiddenKeys.has(node.key)) {
105
+ flattenNodes.push(node)
106
+ }
107
+ if (expandedKeys.has(node.key)) {
108
+ const children = node.children
109
+ if (children) {
110
+ const length = children.length
111
+ for (let i = length - 1; i >= 0; --i) {
112
+ stack.push(children[i])
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+ traverse()
119
+ return flattenNodes
120
+ })
121
+
122
+ // 虚拟滚动逻辑
123
+ const {
124
+ scrollTop,
125
+ showBackTopBtn,
126
+ virtualData,
127
+ startIndex,
128
+ virtualOffsetY,
129
+ totalHeight,
130
+ displayData,
131
+ updateVisibleData,
132
+ scrollToTop: virtualScrollToTop,
133
+ scrollToBottom: virtualScrollToBottom,
134
+ scrollToPosition: virtualScrollToPosition,
135
+ scrollToElement: virtualScrollToElement,
136
+ scrollToElementById: virtualScrollToElementById,
137
+ onScroll: handleVirtualScroll
138
+ } = useVirtualScroll({
139
+ data: flattenTree,
140
+ virtual: ref(true),
141
+ height: height,
142
+ itemHeight: itemHeight,
143
+ idKey: ref('key'),
144
+ backToTopThreshold: backToTopThreshold
145
+ })
146
+
147
+ const createTree = (data: RawTreeNode[]) => {
148
+ const treeNodeMap = new Map<string, TreeNode>()
149
+ const levelTreeNodeMap = new Map<number, TreeNode[]>()
150
+
151
+ let maxLevel = 1
152
+
153
+ function traverse(nodes: RawTreeNode[], level = 1, parent?: TreeNode): TreeNode[] {
154
+ const siblings: TreeNode[] = []
155
+ nodes.forEach((rawNode, index) => {
156
+ const value = getKey(rawNode)
157
+ const node: TreeNode = {
158
+ level,
159
+ key: value,
160
+ data: rawNode,
161
+ index,
162
+ isLast: index === nodes.length - 1,
163
+ expanded: expandedKeySet.value.has(value),
164
+ label: getLabel(rawNode),
165
+ parent: parent,
166
+ isLeaf: false
167
+ }
168
+ if (showCheckbox.value) {
169
+ node.checked = checkedKeys.value.has(value)
170
+ }
171
+ if (expandAll.value) {
172
+ node.expanded = true
173
+ expandedKeySet.value.add(value)
174
+ }
175
+ const children = getChildren(rawNode)
176
+ node.disabled = getDisabled(rawNode)
177
+ node.isLeaf = !children || children.length === 0
178
+ if (children && children.length) {
179
+ node.children = traverse(children, level + 1, node)
180
+ }
181
+ siblings.push(node)
182
+ treeNodeMap.set(value, node)
183
+ if (!levelTreeNodeMap.has(level)) {
184
+ levelTreeNodeMap.set(level, [])
185
+ }
186
+ levelTreeNodeMap.get(level)!.push(node)
187
+ })
188
+
189
+ if (level > maxLevel) {
190
+ maxLevel = level
191
+ }
192
+ return siblings
193
+ }
194
+ const treeNodes = traverse(data)
195
+
196
+ return {
197
+ treeNodeMap,
198
+ levelTreeNodeMap,
199
+ maxLevel,
200
+ treeNodes
201
+ }
202
+ }
203
+
204
+ const toggleExpand = (node: TreeNode, flag: boolean) => {
205
+ node.expanded = flag
206
+ const key = node.key
207
+ if (node.expanded) {
208
+ expandedKeySet.value.add(key)
209
+ emit('node-expand', node)
210
+ } else {
211
+ expandedKeySet.value.delete(key)
212
+ emit('node-collapse', node)
213
+ }
214
+ }
215
+ const toggleChecked = (node: TreeNode, flag: boolean, isClick: boolean = false) => {
216
+ if (!showCheckbox.value || !node || node.disabled) {
217
+ return
218
+ }
219
+
220
+ function toggle(currentNode: TreeNode) {
221
+ const key = currentNode.key
222
+ currentNode.checked = flag
223
+ currentNode.immediate = false
224
+
225
+ if (currentNode.checked && (!selectionLeafOnly.value || currentNode.isLeaf)) {
226
+ checkedKeys.value.add(key)
227
+ } else {
228
+ checkedKeys.value.delete(key)
229
+ }
230
+ if (!checkStrictly.value && currentNode.children) {
231
+ currentNode.children.forEach((child) => {
232
+ toggle(child)
233
+ })
234
+ }
235
+ }
236
+ toggle(node)
237
+ !checkStrictly.value && updateParentNode(node)
238
+ updateValue()
239
+ isClick && emit('node-check', node)
240
+ }
241
+ const handleClick = (node: TreeNode) => {
242
+ emit('node-click', node)
243
+
244
+ if (node.disabled) return
245
+ currentNode.value = node
246
+
247
+ if (!showCheckbox.value) {
248
+ updateValue()
249
+ }
250
+ }
251
+
252
+ // 滚动事件处理
253
+ const handleScroll = (event: any) => {
254
+ handleVirtualScroll(event.detail.scrollTop)
255
+ }
256
+ const updateParentNode = (node: TreeNode) => {
257
+ if (!node.parent) return
258
+ updateNode(node.parent)
259
+ updateParentNode(node.parent)
260
+ }
261
+ const updateNode = (node: TreeNode | undefined) => {
262
+ if (!node) return
263
+ if (node.children?.every((item) => item.checked)) {
264
+ node.checked = true
265
+ node.immediate = false
266
+ !node.disabled && (!selectionLeafOnly.value || node.isLeaf) && checkedKeys.value.add(node.key)
267
+ } else if (node.children?.every((item) => !item.checked && !item.immediate)) {
268
+ node.checked = false
269
+ node.immediate = false
270
+ checkedKeys.value.delete(node.key)
271
+ } else {
272
+ node.checked = false
273
+ node.immediate = true
274
+ checkedKeys.value.delete(node.key)
275
+ !node.disabled && immediateKeySet.value.add(node.key)
276
+ }
277
+ }
278
+
279
+ const expandNode = (node: TreeNode | undefined) => {
280
+ if (!node) return
281
+ !node.isLeaf && expandedKeySet.value.add(node.key)
282
+ node.expanded = true
283
+ if (node.parent) {
284
+ expandNode(node.parent)
285
+ }
286
+ }
287
+ const initValue = () => {
288
+ if (showCheckbox.value) {
289
+ if (isSetsEqual(checkedKeys.value, new Set(modelValue.value as string[]))) return
290
+ checkedKeys.value = new Set(modelValue.value as string[])
291
+
292
+ Array.isArray(modelValue.value) &&
293
+ modelValue.value.map((value) => {
294
+ const node = tree.value?.treeNodeMap.get(value as string)
295
+ if (node) {
296
+ toggleChecked(node, true)
297
+ }
298
+ })
299
+ } else {
300
+ currentNode.value = tree.value?.treeNodeMap.get(modelValue.value as string)
301
+ expandNode(currentNode.value?.parent)
302
+ }
303
+ }
304
+ const initDefaultExpandedKeys = () => {
305
+ defaultExpandedKeys.value.map((key) => {
306
+ expandNode(tree.value?.treeNodeMap.get(key as string))
307
+ })
308
+ }
309
+ const updateValue = () => {
310
+ if (showCheckbox.value) {
311
+ emit('update:modelValue', Array.from(checkedKeys.value))
312
+ } else {
313
+ emit('update:modelValue', currentNode.value?.key)
314
+ }
315
+ }
316
+ const getNodeStyle = (item: TreeNode) => {
317
+ return {
318
+ height: itemHeight.value,
319
+ paddingLeft: `${(item.level - 1) * (props.indent || 16)}px`
320
+ }
321
+ }
322
+ const getNodeClass = (item: TreeNode) => {
323
+ const currentValue = currentNode?.value
324
+ return {
325
+ expanded: item.expanded,
326
+ checked: item.checked,
327
+ 'is-leaf': item.isLeaf,
328
+ immediate: item.immediate,
329
+ 'is-current': currentValue === item,
330
+ 'is-disabled': item.disabled
331
+ }
332
+ }
333
+ const handleNodeClick = (item: TreeNode) => {
334
+ toggleChecked(item, !item.checked, true)
335
+ handleClick(item)
336
+ }
337
+ const handleClickExpand = (item: TreeNode) => {
338
+ toggleExpand(item, !item.expanded)
339
+ }
340
+ watch(
341
+ () => data.value,
342
+ () => {
343
+ tree.value = createTree(data.value)
344
+ nextTick(() => {
345
+ checkedKeys.value = new Set()
346
+ initValue()
347
+ initDefaultExpandedKeys()
348
+ })
349
+ },
350
+ {
351
+ immediate: true
352
+ }
353
+ )
354
+ watch(
355
+ () => modelValue.value,
356
+ () => {
357
+ initValue()
358
+ }
359
+ )
360
+ const getNodeById = (id: string | number) => {
361
+ return tree.value?.treeNodeMap.get(id as string)
362
+ }
363
+ const scrollToTop = () => {
364
+ virtualScrollToTop()
365
+ }
366
+ const scrollToBottom = () => {
367
+ const visibleCount = flattenTree.value.length
368
+ const containerHeight = parseFloat(height.value || '0')
369
+ const targetScrollTop = Math.max(visibleCount * parseFloat(itemHeight.value) - containerHeight, 0)
370
+ virtualScrollToPosition(targetScrollTop)
371
+ }
372
+ const scrollToPosition = (position: number | string) => {
373
+ virtualScrollToPosition(position)
374
+ }
375
+ const scrollToElement = (node: any) => {
376
+ expandNode(node)
377
+ nextTick(() => {
378
+ virtualScrollToElement(node)
379
+ })
380
+ }
381
+ const scrollToElementById = (id: string | number) => {
382
+ const targetNode = tree.value?.treeNodeMap.get(id as string)
383
+ if (targetNode) {
384
+ expandNode(targetNode)
385
+ nextTick(() => {
386
+ virtualScrollToElementById(id)
387
+ })
388
+ }
389
+ }
390
+ defineExpose<TreeExpose>({
391
+ toggleExpand,
392
+ toggleChecked,
393
+ getNodeById,
394
+ useTree: () => tree.value,
395
+ getTree: () => tree,
396
+ scrollToTop,
397
+ scrollToBottom,
398
+ scrollToPosition,
399
+ scrollToElement,
400
+ scrollToElementById
401
+ })
402
+ </script>
403
+
404
+ <style lang="scss" scoped>
405
+ @import './index.scss';
406
+ </style>
@@ -0,0 +1,85 @@
1
+ import type { ComponentPublicInstance, ExtractPropTypes, Ref } from 'vue'
2
+ import { baseProps, makeArrayProp, makeBooleanProp, makeNumberProp, makeNumericProp, makeStringProp } from '../common/props'
3
+
4
+ // 树结构类型定义
5
+ export type Tree = {
6
+ treeNodeMap: Map<string, TreeNode>
7
+ levelTreeNodeMap: Map<number, TreeNode[]>
8
+ maxLevel: number
9
+ treeNodes: TreeNode[]
10
+ }
11
+ // 原始树节点类型
12
+ export type RawTreeNode = {
13
+ disabled?: boolean
14
+ [key: string]: any
15
+ }
16
+
17
+ // 处理后的树节点类型
18
+ export type TreeNode = {
19
+ level: number
20
+ key: string
21
+ data: RawTreeNode
22
+ index: number
23
+ isLast: boolean
24
+ expanded: boolean
25
+ label: string
26
+ parent?: TreeNode
27
+ children?: TreeNode[]
28
+ disabled?: boolean
29
+ isLeaf: boolean
30
+ checked?: boolean
31
+ immediate?: boolean
32
+ isCurrent?: boolean
33
+ }
34
+
35
+ export const treeProps = {
36
+ ...baseProps,
37
+ data: makeArrayProp<RawTreeNode>(),
38
+ showCheckbox: makeBooleanProp(false),
39
+ childrenKey: makeStringProp('children'),
40
+ labelKey: makeStringProp('name'),
41
+ nodeKey: makeStringProp('id'),
42
+ defaultExpandedKeys: makeArrayProp<string>(),
43
+ expandAll: makeBooleanProp(false),
44
+ checkStrictly: makeBooleanProp(false),
45
+ modelValue: {
46
+ type: [Array<string>, String],
47
+ default: ''
48
+ },
49
+ emptyText: makeStringProp('暂无数据'),
50
+ height: makeStringProp('300px'),
51
+ /**
52
+ * 是否显示回到顶部按钮
53
+ * 类型:boolean
54
+ * 默认值:false
55
+ */
56
+ showBackToTop: makeBooleanProp(false),
57
+ /**
58
+ * 滚动多远显示backToTop
59
+ * 类型:number
60
+ * 默认值:'300px'
61
+ */
62
+ backToTopThreshold: makeStringProp('300px'),
63
+ /**
64
+ * 单个项目高度
65
+ * 类型:number
66
+ * 默认值:'44px'
67
+ */
68
+ itemHeight: makeStringProp('44px'),
69
+ indent: makeNumberProp(16),
70
+ selectionLeafOnly: makeBooleanProp(false)
71
+ }
72
+ export type TreeExpose = {
73
+ toggleExpand: (node: TreeNode, flag: boolean) => void
74
+ toggleChecked: (node: TreeNode, flag: boolean, isClick: boolean) => void
75
+ getNodeById: (id: string | number) => TreeNode | undefined
76
+ useTree: () => Tree
77
+ getTree: () => Ref<Tree>
78
+ scrollToTop: () => void
79
+ scrollToBottom: () => void
80
+ scrollToPosition: (position: number | string) => void
81
+ scrollToElement: (item: any) => void
82
+ scrollToElementById: (id: string | number) => void
83
+ }
84
+ export type TreeProps = ExtractPropTypes<typeof treeProps>
85
+ export type TreeInstance = ComponentPublicInstance<TreeProps, TreeExpose>
@@ -0,0 +1,51 @@
1
+ import type { RawTreeNode, TreeProps } from './types'
2
+
3
+ export const useTreeMethods = (props: TreeProps) => {
4
+ function getDisabled(node: RawTreeNode): boolean {
5
+ return node.disabled ?? false
6
+ }
7
+
8
+ function getChildren(node: RawTreeNode): RawTreeNode[] {
9
+ return node[props.childrenKey] ?? []
10
+ }
11
+
12
+ function getLabel(node: RawTreeNode): string {
13
+ return node[props.labelKey] ?? ''
14
+ }
15
+
16
+ function getKey(node: RawTreeNode | undefined): string {
17
+ if (!node) {
18
+ return ''
19
+ }
20
+ return (node[props.nodeKey] ?? '') as string
21
+ }
22
+
23
+ return {
24
+ getDisabled,
25
+ getChildren,
26
+ getLabel,
27
+ getKey
28
+ }
29
+ }
30
+ /**
31
+ * 判断两个 Set 中的元素是否完全相同(不考虑顺序)
32
+ * @param {Set} setA - 第一个 Set
33
+ * @param {Set} setB - 第二个 Set
34
+ * @returns {boolean} 两个 Set 是否相同
35
+ */
36
+ export function isSetsEqual(setA: Set<any>, setB: Set<any>) {
37
+ // 步骤1:如果大小不同,直接返回 false
38
+ if (setA.size !== setB.size) {
39
+ return false
40
+ }
41
+
42
+ // 步骤2:遍历 setA 的所有元素,检查 setB 是否都包含
43
+ for (const item of setA) {
44
+ if (!setB.has(item)) {
45
+ return false // 有元素不匹配,返回 false
46
+ }
47
+ }
48
+
49
+ // 所有元素匹配,返回 true
50
+ return true
51
+ }