oxy-uni-ui 1.1.0 → 1.2.3
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.
- package/attributes.json +1 -1
- package/components/common/abstracts/variable.scss +59 -1
- package/components/common/path.ts +9 -0
- package/components/common/util.ts +42 -0
- package/components/composables/index.ts +1 -0
- package/components/composables/useGlobalLoading.ts +42 -0
- package/components/composables/useGlobalMessage.ts +48 -0
- package/components/composables/useGlobalToast.ts +84 -0
- package/components/composables/useVirtualScroll.ts +173 -0
- package/components/oxy-cell/oxy-cell.vue +15 -2
- package/components/oxy-cell/types.ts +4 -0
- package/components/oxy-checkbox/index.scss +1 -1
- package/components/oxy-checkbox/oxy-checkbox.vue +2 -2
- package/components/oxy-col-picker/oxy-col-picker.vue +3 -0
- package/components/oxy-col-picker/types.ts +5 -1
- package/components/oxy-corner/index.scss +121 -1
- package/components/oxy-corner/oxy-corner.vue +18 -5
- package/components/oxy-corner/types.ts +24 -3
- package/components/oxy-date-strip/index.scss +10 -0
- package/components/oxy-date-strip/oxy-date-strip.vue +198 -0
- package/components/oxy-date-strip/types.ts +98 -0
- package/components/oxy-date-strip/utils.ts +67 -0
- package/components/oxy-date-strip-item/index.scss +94 -0
- package/components/oxy-date-strip-item/oxy-date-strip-item.vue +102 -0
- package/components/oxy-date-strip-item/types.ts +53 -0
- package/components/oxy-datetime-picker/oxy-datetime-picker.vue +3 -1
- package/components/oxy-datetime-picker/types.ts +5 -1
- package/components/oxy-echarts/index.scss +17 -0
- package/components/oxy-echarts/index.ts +1 -0
- package/components/oxy-echarts/oxy-echarts.vue +32 -0
- package/components/oxy-echarts/types.ts +12 -0
- package/components/oxy-file-list/index.scss +26 -0
- package/components/oxy-file-list/oxy-file-list.vue +208 -34
- package/components/oxy-file-list/types.ts +58 -2
- package/components/oxy-global-loading/oxy-global-loading.vue +53 -0
- package/components/oxy-global-message/oxy-global-message.vue +64 -0
- package/components/oxy-global-toast/oxy-global-toast.vue +53 -0
- package/components/oxy-img-lazy/index.scss +17 -0
- package/components/oxy-img-lazy/oxy-img-lazy.vue +332 -0
- package/components/oxy-img-lazy/types.ts +69 -0
- package/components/oxy-link/index.scss +57 -0
- package/components/oxy-link/oxy-link.vue +130 -0
- package/components/oxy-link/types.ts +81 -0
- package/components/oxy-list/index.scss +8 -1
- package/components/oxy-list/oxy-list.vue +121 -40
- package/components/oxy-list/types.ts +3 -15
- package/components/oxy-picker/oxy-picker.vue +3 -0
- package/components/oxy-picker/types.ts +5 -1
- package/components/oxy-radio/index.scss +3 -3
- package/components/oxy-radio/oxy-radio.vue +1 -1
- package/components/oxy-rich-text/icon/emjio.svg +1 -0
- package/components/oxy-rich-text/icon/quote.svg +1 -0
- package/components/oxy-rich-text/icon/text.svg +1 -0
- package/components/oxy-rich-text/icon/title.svg +1 -0
- package/components/oxy-rich-text/index.scss +159 -0
- package/components/oxy-rich-text/mp-html/card/card.vue +122 -0
- package/components/oxy-rich-text/mp-html/card/index.js +7 -0
- package/components/oxy-rich-text/mp-html/editable/config.js +15 -0
- package/components/oxy-rich-text/mp-html/editable/index.js +553 -0
- package/components/oxy-rich-text/mp-html/emoji/index.js +203 -0
- package/components/oxy-rich-text/mp-html/highlight/config.js +5 -0
- package/components/oxy-rich-text/mp-html/highlight/index.js +96 -0
- package/components/oxy-rich-text/mp-html/highlight/prism.css +1 -0
- package/components/oxy-rich-text/mp-html/highlight/prism.min.js +7 -0
- package/components/oxy-rich-text/mp-html/img-cache/index.js +138 -0
- package/components/oxy-rich-text/mp-html/latex/index.js +80 -0
- package/components/oxy-rich-text/mp-html/latex/katex.css +1 -0
- package/components/oxy-rich-text/mp-html/latex/katex.min.js +1 -0
- package/components/oxy-rich-text/mp-html/markdown/index.js +50 -0
- package/components/oxy-rich-text/mp-html/markdown/marked.min.js +71 -0
- package/components/oxy-rich-text/mp-html/mp-html.d.ts +184 -0
- package/components/oxy-rich-text/mp-html/mp-html.vue +675 -0
- package/components/oxy-rich-text/mp-html/node/node.vue +1161 -0
- package/components/oxy-rich-text/mp-html/parser.js +1428 -0
- package/components/oxy-rich-text/mp-html/search/index.js +132 -0
- package/components/oxy-rich-text/mp-html/style/index.js +129 -0
- package/components/oxy-rich-text/mp-html/style/parser.js +175 -0
- package/components/oxy-rich-text/mp-html/template/index.js +67 -0
- package/components/oxy-rich-text/mp-html/txv-video/index.js +46 -0
- package/components/oxy-rich-text/oxy-rich-text.vue +642 -0
- package/components/oxy-rich-text/types.ts +71 -0
- package/components/oxy-select/index.scss +255 -0
- package/components/oxy-select/oxy-select.vue +421 -0
- package/components/oxy-select/types.ts +71 -0
- package/components/oxy-select-picker/oxy-select-picker.vue +3 -0
- package/components/oxy-select-picker/types.ts +5 -1
- package/components/oxy-stream-render/index.scss +6 -0
- package/components/oxy-stream-render/oxy-stream-render.vue +204 -0
- package/components/oxy-stream-render/types.ts +5 -0
- package/components/oxy-tree/index.scss +43 -5
- package/components/oxy-tree/oxy-tree.vue +233 -35
- package/components/oxy-tree/types.ts +54 -7
- package/components/oxy-tree/utils.ts +51 -0
- package/components/oxy-virtual-scroll/index.scss +1 -1
- package/components/oxy-virtual-scroll/oxy-virtual-scroll.vue +69 -110
- package/components/oxy-virtual-scroll/types.ts +95 -5
- package/components/oxy-waterfall/index.scss +18 -0
- package/components/oxy-waterfall/oxy-waterfall.vue +218 -0
- package/components/oxy-waterfall/types.ts +90 -0
- package/components/oxy-waterfall-item/index.scss +8 -0
- package/components/oxy-waterfall-item/oxy-waterfall-item.vue +89 -0
- package/components/oxy-waterfall-item/types.ts +16 -0
- package/global.d.ts +7 -0
- package/index.ts +3 -0
- package/locale/lang/en-US.ts +35 -9
- package/locale/lang/zh-CN.ts +31 -5
- package/oxy-uni-ui.zip +0 -0
- package/package.json +1 -1
- package/tags.json +1 -1
- package/uni-echarts/changelog.md +2 -0
- package/uni-echarts/components/index.js +1 -0
- package/uni-echarts/components/uni-echarts/events.js +95 -0
- package/uni-echarts/components/uni-echarts/types.d.ts +183 -0
- package/uni-echarts/components/uni-echarts/types.js +1 -0
- package/uni-echarts/components/uni-echarts/uni-echarts.vue +530 -0
- package/uni-echarts/components/uni-echarts/uni-echarts.vue.d.ts +19 -0
- package/uni-echarts/global.d.ts +7 -0
- package/uni-echarts/index.d.ts +440 -0
- package/uni-echarts/index.js +2 -0
- package/uni-echarts/package.json +105 -0
- package/uni-echarts/shared-core.d.ts +269 -0
- package/uni-echarts/shared-core.js +900 -0
- package/web-types.json +1 -1
|
@@ -1,17 +1,56 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<view class="oxy-tree" :class="customClass" :style="customStyle">
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
<slot name="search-button" v-if="showFilter">
|
|
4
|
+
<view class="oxy-tree__search">
|
|
5
|
+
<oxy-input
|
|
6
|
+
type="text"
|
|
7
|
+
no-border
|
|
8
|
+
class="oxy-tree__search-input"
|
|
9
|
+
prefix-icon="search"
|
|
10
|
+
:placeholder="filterPlaceholder"
|
|
11
|
+
v-model="searchValue"
|
|
12
|
+
@confirm="handleFilter"
|
|
13
|
+
/>
|
|
14
|
+
<oxy-button :round="false" @click="handleFilter">筛选</oxy-button>
|
|
15
|
+
</view>
|
|
16
|
+
</slot>
|
|
17
|
+
|
|
18
|
+
<view v-if="data.length" class="oxy-tree__virtual-scroll" :style="{ height: height }">
|
|
19
|
+
<scroll-view class="oxy-tree__view" scroll-y :scroll-x="true" :scroll-top="scrollTop" @scroll="handleScroll">
|
|
20
|
+
<view class="oxy-tree__container" :style="{ height: totalHeight + 'px' }">
|
|
21
|
+
<view class="oxy-tree__items" :style="{ transform: `translateY(${virtualOffsetY}px)` }">
|
|
22
|
+
<view v-for="(item, index) in virtualData" :key="index">
|
|
23
|
+
<view class="oxy-tree-node-content" :style="getNodeStyle(item)" :class="getNodeClass(item)" @click="handleNodeClick(item)">
|
|
24
|
+
<!-- 兼容支付宝、微信小程序 -->
|
|
25
|
+
<view @tap.stop="handleClickExpand(item)">
|
|
26
|
+
<oxy-icon name="fill-arrow-down" custom-class="oxy-tree-node-icon" size="22px"></oxy-icon>
|
|
27
|
+
</view>
|
|
28
|
+
<oxy-checkbox
|
|
29
|
+
v-if="showCheckbox"
|
|
30
|
+
:modelValue="item.checked"
|
|
31
|
+
:disabled="item.disabled"
|
|
32
|
+
:indeterminate="item.immediate"
|
|
33
|
+
shape="square"
|
|
34
|
+
></oxy-checkbox>
|
|
35
|
+
<view class="oxy-tree-node">
|
|
36
|
+
<slot name="node" :node="item" :data="item.data">
|
|
37
|
+
{{ item.label }}
|
|
38
|
+
</slot>
|
|
39
|
+
</view>
|
|
40
|
+
</view>
|
|
41
|
+
</view>
|
|
42
|
+
</view>
|
|
43
|
+
</view>
|
|
44
|
+
</scroll-view>
|
|
45
|
+
</view>
|
|
12
46
|
<view v-else>
|
|
13
47
|
<oxy-status-tip image="content" :tip="emptyText" />
|
|
14
48
|
</view>
|
|
49
|
+
|
|
50
|
+
<!-- 回到顶部按钮 -->
|
|
51
|
+
<view v-if="showBackToTop && showBackTopBtn" class="oxy-virtual-scroll__back-top" @click="scrollToTop">
|
|
52
|
+
<oxy-icon name="backtop" color="#fff" size="20px"></oxy-icon>
|
|
53
|
+
</view>
|
|
15
54
|
</view>
|
|
16
55
|
</template>
|
|
17
56
|
|
|
@@ -27,23 +66,27 @@ export default {
|
|
|
27
66
|
</script>
|
|
28
67
|
|
|
29
68
|
<script lang="ts" setup>
|
|
30
|
-
import { computed, nextTick, provide, ref, toRefs, watch } from 'vue'
|
|
31
|
-
import type { RawTreeNode, TreeNode } from './types'
|
|
32
|
-
import {
|
|
33
|
-
import
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
// 树结构类型定义
|
|
37
|
-
type Tree = {
|
|
38
|
-
treeNodeMap: Map<string, TreeNode>
|
|
39
|
-
levelTreeNodeMap: Map<number, TreeNode[]>
|
|
40
|
-
maxLevel: number
|
|
41
|
-
treeNodes: TreeNode[]
|
|
42
|
-
}
|
|
69
|
+
import { computed, nextTick, provide, type Ref, ref, toRefs, watch } from 'vue'
|
|
70
|
+
import type { RawTreeNode, Tree, TreeInstance, TreeNode } from './types'
|
|
71
|
+
import { treeProps, type TreeExpose } from './types'
|
|
72
|
+
import { isSetsEqual, useTreeMethods } from './utils'
|
|
73
|
+
import { useVirtualScroll } from '../composables/useVirtualScroll'
|
|
43
74
|
|
|
44
75
|
// 获取组件的 props 和 emit 函数
|
|
45
|
-
const props = defineProps(
|
|
46
|
-
const {
|
|
76
|
+
const props = defineProps(treeProps)
|
|
77
|
+
const {
|
|
78
|
+
modelValue,
|
|
79
|
+
data,
|
|
80
|
+
defaultExpandedKeys,
|
|
81
|
+
expandAll,
|
|
82
|
+
showCheckbox,
|
|
83
|
+
checkStrictly,
|
|
84
|
+
selectionLeafOnly,
|
|
85
|
+
height,
|
|
86
|
+
itemHeight,
|
|
87
|
+
nodeKey,
|
|
88
|
+
backToTopThreshold
|
|
89
|
+
} = toRefs(props)
|
|
47
90
|
const emit = defineEmits<{
|
|
48
91
|
(e: 'node-click', node: TreeNode): void
|
|
49
92
|
(e: 'update:modelValue', value: string[] | string | undefined): void
|
|
@@ -54,15 +97,13 @@ const emit = defineEmits<{
|
|
|
54
97
|
|
|
55
98
|
const { getDisabled, getChildren, getLabel, getKey } = useTreeMethods(props)
|
|
56
99
|
const expandedKeySet = ref<Set<string>>(new Set())
|
|
100
|
+
const bakExpandedKeySet = ref<Set<string> | null>(null)
|
|
57
101
|
const hiddenNodeKeySet = ref<Set<string>>(new Set())
|
|
58
102
|
const checkedKeys = ref<Set<string>>(new Set())
|
|
59
103
|
const immediateKeySet = ref<Set<string>>(new Set())
|
|
60
104
|
const currentNode = ref<TreeNode>()
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const tree = ref<Tree>()
|
|
65
|
-
|
|
105
|
+
const searchValue = ref('')
|
|
106
|
+
const tree = ref<Tree>() as Ref<Tree>
|
|
66
107
|
const flattenTree = computed<TreeNode[]>(() => {
|
|
67
108
|
const expandedKeys = expandedKeySet.value
|
|
68
109
|
const hiddenKeys = hiddenNodeKeySet.value
|
|
@@ -93,6 +134,32 @@ const flattenTree = computed<TreeNode[]>(() => {
|
|
|
93
134
|
traverse()
|
|
94
135
|
return flattenNodes
|
|
95
136
|
})
|
|
137
|
+
|
|
138
|
+
// 虚拟滚动逻辑
|
|
139
|
+
const {
|
|
140
|
+
scrollTop,
|
|
141
|
+
showBackTopBtn,
|
|
142
|
+
virtualData,
|
|
143
|
+
startIndex,
|
|
144
|
+
virtualOffsetY,
|
|
145
|
+
totalHeight,
|
|
146
|
+
displayData,
|
|
147
|
+
updateVisibleData,
|
|
148
|
+
scrollToTop: virtualScrollToTop,
|
|
149
|
+
scrollToBottom: virtualScrollToBottom,
|
|
150
|
+
scrollToPosition: virtualScrollToPosition,
|
|
151
|
+
scrollToElement: virtualScrollToElement,
|
|
152
|
+
scrollToElementById: virtualScrollToElementById,
|
|
153
|
+
onScroll: handleVirtualScroll
|
|
154
|
+
} = useVirtualScroll({
|
|
155
|
+
data: flattenTree,
|
|
156
|
+
virtual: ref(true),
|
|
157
|
+
height: height,
|
|
158
|
+
itemHeight: itemHeight,
|
|
159
|
+
idKey: ref('key'),
|
|
160
|
+
backToTopThreshold: backToTopThreshold
|
|
161
|
+
})
|
|
162
|
+
|
|
96
163
|
const createTree = (data: RawTreeNode[]) => {
|
|
97
164
|
const treeNodeMap = new Map<string, TreeNode>()
|
|
98
165
|
const levelTreeNodeMap = new Map<number, TreeNode[]>()
|
|
@@ -118,7 +185,6 @@ const createTree = (data: RawTreeNode[]) => {
|
|
|
118
185
|
node.checked = checkedKeys.value.has(value)
|
|
119
186
|
}
|
|
120
187
|
if (expandAll.value) {
|
|
121
|
-
node.expanded = true
|
|
122
188
|
expandedKeySet.value.add(value)
|
|
123
189
|
}
|
|
124
190
|
const children = getChildren(rawNode)
|
|
@@ -151,9 +217,8 @@ const createTree = (data: RawTreeNode[]) => {
|
|
|
151
217
|
}
|
|
152
218
|
|
|
153
219
|
const toggleExpand = (node: TreeNode, flag: boolean) => {
|
|
154
|
-
node.expanded = flag
|
|
155
220
|
const key = node.key
|
|
156
|
-
if (
|
|
221
|
+
if (flag) {
|
|
157
222
|
expandedKeySet.value.add(key)
|
|
158
223
|
emit('node-expand', node)
|
|
159
224
|
} else {
|
|
@@ -197,6 +262,11 @@ const handleClick = (node: TreeNode) => {
|
|
|
197
262
|
updateValue()
|
|
198
263
|
}
|
|
199
264
|
}
|
|
265
|
+
|
|
266
|
+
// 滚动事件处理
|
|
267
|
+
const handleScroll = (event: any) => {
|
|
268
|
+
handleVirtualScroll(event.detail.scrollTop)
|
|
269
|
+
}
|
|
200
270
|
const updateParentNode = (node: TreeNode) => {
|
|
201
271
|
if (!node.parent) return
|
|
202
272
|
updateNode(node.parent)
|
|
@@ -223,7 +293,6 @@ const updateNode = (node: TreeNode | undefined) => {
|
|
|
223
293
|
const expandNode = (node: TreeNode | undefined) => {
|
|
224
294
|
if (!node) return
|
|
225
295
|
!node.isLeaf && expandedKeySet.value.add(node.key)
|
|
226
|
-
node.expanded = true
|
|
227
296
|
if (node.parent) {
|
|
228
297
|
expandNode(node.parent)
|
|
229
298
|
}
|
|
@@ -231,6 +300,12 @@ const expandNode = (node: TreeNode | undefined) => {
|
|
|
231
300
|
const initValue = () => {
|
|
232
301
|
if (showCheckbox.value) {
|
|
233
302
|
if (isSetsEqual(checkedKeys.value, new Set(modelValue.value as string[]))) return
|
|
303
|
+
Array.from(checkedKeys.value).map((value) => {
|
|
304
|
+
const node = tree.value?.treeNodeMap.get(value as string)
|
|
305
|
+
if (node) {
|
|
306
|
+
toggleChecked(node, false)
|
|
307
|
+
}
|
|
308
|
+
})
|
|
234
309
|
checkedKeys.value = new Set(modelValue.value as string[])
|
|
235
310
|
|
|
236
311
|
Array.isArray(modelValue.value) &&
|
|
@@ -257,6 +332,30 @@ const updateValue = () => {
|
|
|
257
332
|
emit('update:modelValue', currentNode.value?.key)
|
|
258
333
|
}
|
|
259
334
|
}
|
|
335
|
+
const getNodeStyle = (item: TreeNode) => {
|
|
336
|
+
return {
|
|
337
|
+
height: itemHeight.value,
|
|
338
|
+
paddingLeft: `${(item.level - 1) * (props.indent || 16)}px`
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const getNodeClass = (item: TreeNode) => {
|
|
342
|
+
const currentValue = currentNode?.value
|
|
343
|
+
return {
|
|
344
|
+
expanded: expandedKeySet.value.has(item.key),
|
|
345
|
+
checked: item.checked,
|
|
346
|
+
'is-leaf': item.isLeaf,
|
|
347
|
+
immediate: item.immediate,
|
|
348
|
+
'is-current': currentValue === item,
|
|
349
|
+
'is-disabled': item.disabled
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const handleNodeClick = (item: TreeNode) => {
|
|
353
|
+
toggleChecked(item, !item.checked, true)
|
|
354
|
+
handleClick(item)
|
|
355
|
+
}
|
|
356
|
+
const handleClickExpand = (item: TreeNode) => {
|
|
357
|
+
toggleExpand(item, !expandedKeySet.value.has(item.key))
|
|
358
|
+
}
|
|
260
359
|
watch(
|
|
261
360
|
() => data.value,
|
|
262
361
|
() => {
|
|
@@ -277,10 +376,109 @@ watch(
|
|
|
277
376
|
initValue()
|
|
278
377
|
}
|
|
279
378
|
)
|
|
280
|
-
|
|
379
|
+
const getNodeById = (id: string | number) => {
|
|
380
|
+
return tree.value?.treeNodeMap.get(id as string)
|
|
381
|
+
}
|
|
382
|
+
const scrollToTop = () => {
|
|
383
|
+
virtualScrollToTop()
|
|
384
|
+
}
|
|
385
|
+
const scrollToBottom = () => {
|
|
386
|
+
const visibleCount = flattenTree.value.length
|
|
387
|
+
const containerHeight = parseFloat(height.value || '0')
|
|
388
|
+
const targetScrollTop = Math.max(visibleCount * parseFloat(itemHeight.value) - containerHeight, 0)
|
|
389
|
+
virtualScrollToPosition(targetScrollTop)
|
|
390
|
+
}
|
|
391
|
+
const scrollToPosition = (position: number | string) => {
|
|
392
|
+
virtualScrollToPosition(position)
|
|
393
|
+
}
|
|
394
|
+
const scrollToElement = (node: any) => {
|
|
395
|
+
expandNode(node)
|
|
396
|
+
nextTick(() => {
|
|
397
|
+
virtualScrollToElement(node)
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
const scrollToElementById = (id: string | number) => {
|
|
401
|
+
const targetNode = tree.value?.treeNodeMap.get(id as string)
|
|
402
|
+
if (targetNode) {
|
|
403
|
+
expandNode(targetNode)
|
|
404
|
+
nextTick(() => {
|
|
405
|
+
virtualScrollToElementById(id)
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const getCheckedNodes = (): (TreeNode | any)[] => {
|
|
410
|
+
const checkNodes = Array.from(checkedKeys.value).map((key) => {
|
|
411
|
+
return tree.value?.treeNodeMap.get(key as string) || { [props.nodeKey]: key }
|
|
412
|
+
})
|
|
413
|
+
return checkNodes
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const filter = (query: string) => {
|
|
417
|
+
if (bakExpandedKeySet.value === null) {
|
|
418
|
+
bakExpandedKeySet.value = new Set(expandedKeySet.value)
|
|
419
|
+
}
|
|
420
|
+
const keys = doFilter(query)
|
|
421
|
+
if (query && keys) {
|
|
422
|
+
expandedKeySet.value = keys
|
|
423
|
+
} else if (!query) {
|
|
424
|
+
bakExpandedKeySet.value && (expandedKeySet.value = new Set(bakExpandedKeySet.value))
|
|
425
|
+
bakExpandedKeySet.value = null
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const doFilter = (query: string) => {
|
|
430
|
+
if (typeof props.filterMethod !== 'function') {
|
|
431
|
+
return
|
|
432
|
+
}
|
|
433
|
+
const expandKeySet = new Set<string>()
|
|
434
|
+
const hiddenKeys = hiddenNodeKeySet.value
|
|
435
|
+
const family: TreeNode[] = []
|
|
436
|
+
const nodes = tree.value.treeNodes || []
|
|
437
|
+
const filter = props.filterMethod
|
|
438
|
+
hiddenKeys.clear()
|
|
439
|
+
function traverse(nodes: TreeNode[]) {
|
|
440
|
+
nodes.forEach((node: TreeNode) => {
|
|
441
|
+
family.push(node)
|
|
442
|
+
if (filter(query, node.data)) {
|
|
443
|
+
family.forEach((member) => {
|
|
444
|
+
expandKeySet.add(member.key)
|
|
445
|
+
})
|
|
446
|
+
} else if (node.isLeaf) {
|
|
447
|
+
hiddenKeys.add(node.key)
|
|
448
|
+
}
|
|
449
|
+
const children = node.children
|
|
450
|
+
if (children) {
|
|
451
|
+
traverse(children)
|
|
452
|
+
}
|
|
453
|
+
if (!node.isLeaf) {
|
|
454
|
+
if (!expandKeySet.has(node.key)) {
|
|
455
|
+
hiddenKeys.add(node.key)
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
family.pop()
|
|
459
|
+
})
|
|
460
|
+
}
|
|
461
|
+
traverse(nodes)
|
|
462
|
+
return expandKeySet
|
|
463
|
+
}
|
|
464
|
+
const handleFilter = () => {
|
|
465
|
+
filter(searchValue.value)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
defineExpose<TreeExpose>({
|
|
281
469
|
toggleExpand,
|
|
282
470
|
toggleChecked,
|
|
283
|
-
|
|
471
|
+
getNodeById,
|
|
472
|
+
getCheckedNodes,
|
|
473
|
+
getCurrentNode: () => currentNode.value,
|
|
474
|
+
useTree: () => tree.value,
|
|
475
|
+
getTree: () => tree,
|
|
476
|
+
scrollToTop,
|
|
477
|
+
scrollToBottom,
|
|
478
|
+
scrollToPosition,
|
|
479
|
+
scrollToElement,
|
|
480
|
+
scrollToElementById,
|
|
481
|
+
filter
|
|
284
482
|
})
|
|
285
483
|
</script>
|
|
286
484
|
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import type { ExtractPropTypes } from 'vue'
|
|
1
|
+
import type { ComponentPublicInstance, ExtractPropTypes, Ref } from 'vue'
|
|
2
2
|
import { baseProps, makeArrayProp, makeBooleanProp, makeNumberProp, makeNumericProp, makeStringProp } from '../common/props'
|
|
3
|
+
import { type PropType } from 'vue'
|
|
3
4
|
|
|
5
|
+
// 树结构类型定义
|
|
6
|
+
export type Tree = {
|
|
7
|
+
treeNodeMap: Map<string, TreeNode>
|
|
8
|
+
levelTreeNodeMap: Map<number, TreeNode[]>
|
|
9
|
+
maxLevel: number
|
|
10
|
+
treeNodes: TreeNode[]
|
|
11
|
+
}
|
|
4
12
|
// 原始树节点类型
|
|
5
13
|
export type RawTreeNode = {
|
|
6
14
|
disabled?: boolean
|
|
@@ -25,13 +33,13 @@ export type TreeNode = {
|
|
|
25
33
|
isCurrent?: boolean
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
export const
|
|
36
|
+
export const treeProps = {
|
|
29
37
|
...baseProps,
|
|
30
38
|
data: makeArrayProp<RawTreeNode>(),
|
|
31
39
|
showCheckbox: makeBooleanProp(false),
|
|
32
40
|
childrenKey: makeStringProp('children'),
|
|
33
41
|
labelKey: makeStringProp('name'),
|
|
34
|
-
|
|
42
|
+
nodeKey: makeStringProp('id'),
|
|
35
43
|
defaultExpandedKeys: makeArrayProp<string>(),
|
|
36
44
|
expandAll: makeBooleanProp(false),
|
|
37
45
|
checkStrictly: makeBooleanProp(false),
|
|
@@ -40,9 +48,48 @@ export const textProps = {
|
|
|
40
48
|
default: ''
|
|
41
49
|
},
|
|
42
50
|
emptyText: makeStringProp('暂无数据'),
|
|
43
|
-
|
|
51
|
+
height: makeStringProp('300px'),
|
|
52
|
+
/**
|
|
53
|
+
* 是否显示回到顶部按钮
|
|
54
|
+
* 类型:boolean
|
|
55
|
+
* 默认值:false
|
|
56
|
+
*/
|
|
57
|
+
showBackToTop: makeBooleanProp(false),
|
|
58
|
+
/**
|
|
59
|
+
* 滚动多远显示backToTop
|
|
60
|
+
* 类型:number
|
|
61
|
+
* 默认值:'300px'
|
|
62
|
+
*/
|
|
63
|
+
backToTopThreshold: makeStringProp('300px'),
|
|
64
|
+
/**
|
|
65
|
+
* 单个项目高度
|
|
66
|
+
* 类型:number
|
|
67
|
+
* 默认值:'44px'
|
|
68
|
+
*/
|
|
69
|
+
itemHeight: makeStringProp('44px'),
|
|
44
70
|
indent: makeNumberProp(16),
|
|
45
|
-
selectionLeafOnly: makeBooleanProp(false)
|
|
71
|
+
selectionLeafOnly: makeBooleanProp(false),
|
|
72
|
+
filterMethod: {
|
|
73
|
+
type: Function as PropType<(keyword: string, node: RawTreeNode) => boolean>,
|
|
74
|
+
default: null
|
|
75
|
+
},
|
|
76
|
+
showFilter: makeBooleanProp(false),
|
|
77
|
+
filterPlaceholder: makeStringProp('请输入节点名称')
|
|
46
78
|
}
|
|
47
|
-
|
|
48
|
-
|
|
79
|
+
export type TreeExpose = {
|
|
80
|
+
toggleExpand: (node: TreeNode, flag: boolean) => void
|
|
81
|
+
toggleChecked: (node: TreeNode, flag: boolean, isClick: boolean) => void
|
|
82
|
+
getNodeById: (id: string | number) => TreeNode | undefined
|
|
83
|
+
getCheckedNodes: () => TreeNode[] | any
|
|
84
|
+
getCurrentNode: () => TreeNode | undefined
|
|
85
|
+
useTree: () => Tree
|
|
86
|
+
getTree: () => Ref<Tree>
|
|
87
|
+
scrollToTop: () => void
|
|
88
|
+
scrollToBottom: () => void
|
|
89
|
+
scrollToPosition: (position: number | string) => void
|
|
90
|
+
scrollToElement: (item: any) => void
|
|
91
|
+
scrollToElementById: (id: string | number) => void
|
|
92
|
+
filter: (keyword: string) => void
|
|
93
|
+
}
|
|
94
|
+
export type TreeProps = ExtractPropTypes<typeof treeProps>
|
|
95
|
+
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
|
+
}
|