Skip to content

Commit 0863f54

Browse files
authored
feat:下拉树支持懒加载 (#3915)
1 parent 851cab3 commit 0863f54

File tree

102 files changed

+574
-169
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+574
-169
lines changed

examples/sites/demos/apis/tree-select.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,44 @@ export default {
264264
},
265265
mode: ['pc'],
266266
pcDemo: 'map-field'
267+
},
268+
{
269+
name: 'lazy',
270+
type: 'boolean',
271+
defaultValue: 'false',
272+
desc: {
273+
'zh-CN': '是否懒加载子节点,配合 load 属性使用',
274+
'en-US': 'Whether to lazily load child nodes and use them in conjunction with the load attribute'
275+
},
276+
mode: ['pc', 'mobile-first'],
277+
pcDemo: 'lazy',
278+
mfDemo: 'lazy'
279+
},
280+
{
281+
name: 'load',
282+
type: '(node, resolve) => void',
283+
defaultValue: '',
284+
desc: {
285+
'zh-CN':
286+
'加载子树数据的方法。点击节点后,组件开始调用load方法,只有在load函数内调用resolve(data),才表示返回下级的数据成功。',
287+
'en-US':
288+
'Method of loading subtree data. After the node is clicked, the component starts to call the load method. Only when resolve(data) is called in the load function, the data at the lower level is successfully returned.'
289+
},
290+
mode: ['pc', 'mobile-first'],
291+
pcDemo: 'lazy',
292+
mfDemo: 'lazy'
293+
},
294+
{
295+
name: 'after-load',
296+
type: 'Function',
297+
defaultValue: '',
298+
desc: {
299+
'zh-CN': '节点懒加载完成后的回调函数',
300+
'en-US': 'Callback function after node lazy loading is completed'
301+
},
302+
mode: ['pc', 'mobile-first'],
303+
pcDemo: 'lazy',
304+
mfDemo: 'lazy'
267305
}
268306
]
269307
}
@@ -281,6 +319,9 @@ interface ITreeNode {
281319
282320
interface ITreeOption {
283321
data: ITreeNode[] // 树数据,用法同 Tree
322+
lazy?: boolean // 是否懒加载子节点
323+
load?: (node: ITreeNodeVm, resolve: IResolveType) => void // 加载子树数据的方法
324+
afterLoad?: (data: any) => void // 节点懒加载完成后的回调函数
284325
}
285326
`
286327
},
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<template>
2+
<tiny-tree-select v-model="value" :tree-op="treeOp" lazy :load="load" :after-load="afterLoad"></tiny-tree-select>
3+
</template>
4+
5+
<script>
6+
import { TinyTreeSelect } from '@opentiny/vue'
7+
8+
export default {
9+
components: {
10+
TinyTreeSelect
11+
},
12+
data() {
13+
return {
14+
value: '',
15+
treeOp: {}
16+
}
17+
},
18+
methods: {
19+
// node 为点击的节点,resolve 为回调函数,用于异步返回下层节点的数据
20+
load(node, resolve) {
21+
console.log('即将加载节点下级数据:', node)
22+
23+
// 通过 level =0 来识别第一次加载
24+
if (node.level === 0) {
25+
resolve([
26+
{ value: '1', label: '一级 1' },
27+
{ value: '2', label: '一级 2' },
28+
{ value: '3', label: '一级 3' }
29+
])
30+
}
31+
// 通过 data 有值,识别是用户点击后的加载
32+
else if (node.data) {
33+
const parentId = node.data.value
34+
const parentLabel = node.data.label
35+
36+
const children = Array.from({ length: 5 }, (v, k) => k + 1).map((num) => ({
37+
value: `${parentId}-${num}`,
38+
label: `${parentLabel}-${num}`,
39+
disabled: Math.random() - 0.5 > 0, // 随机禁用节点
40+
isLeaf: Math.random() - 0.5 > 0 // 随机设置叶子节点
41+
}))
42+
43+
// 模拟异步返回
44+
setTimeout(() => resolve(children), Math.random() * 2 * 1000)
45+
}
46+
},
47+
afterLoad(data) {
48+
console.log('afterLoad 属性触发:', data)
49+
}
50+
}
51+
}
52+
</script>
53+
54+
<style scoped>
55+
[data-tag='tiny-base-select'],
56+
[data-tag='tiny-tree-select'] {
57+
width: 280px;
58+
}
59+
</style>

examples/sites/demos/mobile-first/app/tree-select/webdoc/tree-select.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,18 @@ export default {
6868
},
6969
codeFiles: ['disabled.vue']
7070
},
71+
{
72+
demoId: 'lazy',
73+
name: {
74+
'zh-CN': '懒加载',
75+
'en-US': 'Lazy loading'
76+
},
77+
desc: {
78+
'zh-CN':
79+
'通过 <code>lazy</code> 属性,启用懒加载模式。<br>通过 <code>load</code> 函数属性,触发加载,初始会执行一次。<br>通过 <code>after-load</code> 函数属性,监听下级节点加载完毕的事件。'
80+
},
81+
codeFiles: ['lazy.vue']
82+
},
7183
{
7284
demoId: 'map-field',
7385
name: {

examples/sites/demos/pc/app/select-wrapper/all-text.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test'
22

33
test('多选时自定义全部的文本', async ({ page }) => {
44
page.on('pageerror', (exception) => expect(exception).toBeNull())
5-
await page.goto('select#all-text')
5+
await page.goto('select-wrapper#all-text')
66
const wrap = page.locator('#all-text')
77
const select = wrap.locator('.tiny-select').nth(0)
88
const dropdown = page.locator('body > .tiny-select-dropdown')

examples/sites/demos/pc/app/select-wrapper/all-text.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
</template>
1414

1515
<script>
16-
import { TinySelect, TinyOption } from '@opentiny/vue'
16+
import { TinySelectWrapper as TinySelect, TinyOption } from '@opentiny/vue'
1717
1818
export default {
1919
components: {

examples/sites/demos/pc/app/select-wrapper/allow-create.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { test, expect } from '@playwright/test'
33
test('点击选中', async ({ page }) => {
44
page.on('pageerror', (exception) => expect(exception).toBeNull())
55
await page.waitForTimeout(300)
6-
await page.goto('select#allow-create')
6+
await page.goto('select-wrapper#allow-create')
77

88
const wrap = page.locator('#allow-create')
99
const select = wrap.locator('.tiny-select').nth(0)
@@ -16,18 +16,18 @@ test('点击选中', async ({ page }) => {
1616
await input.dispatchEvent('keyup', { KeyboardEvent })
1717

1818
await expect(input).toHaveValue('测试 allow-create')
19-
await dropdown.getByRole('listitem').filter({ hasText: '测试 allow-create' }).click()
19+
await dropdown.locator('.tiny-option').filter({ hasText: '测试 allow-create' }).click()
2020
await expect(input).toHaveValue('测试 allow-create')
2121

2222
await input.click()
2323
await expect(input).toHaveValue('')
24-
await expect(dropdown.getByRole('listitem').filter({ hasText: '测试 allow-create' })).toHaveClass(/selected/)
24+
await expect(dropdown.locator('.tiny-option').filter({ hasText: '测试 allow-create' })).toHaveClass(/selected/)
2525
})
2626

2727
test('enter 选中', async ({ page }) => {
2828
page.on('pageerror', (exception) => expect(exception).toBeNull())
2929
await page.waitForTimeout(300)
30-
await page.goto('select#allow-create')
30+
await page.goto('select-wrapper#allow-create')
3131

3232
const wrap = page.locator('#allow-create')
3333
const select = wrap.locator('.tiny-select').nth(1)
@@ -46,5 +46,5 @@ test('enter 选中', async ({ page }) => {
4646
await input.click()
4747

4848
await expect(input).toHaveValue('')
49-
await expect(dropdown.getByRole('listitem').filter({ hasText: 'ab' })).toHaveClass(/selected/)
49+
await expect(dropdown.locator('.tiny-option').filter({ hasText: 'ab' })).toHaveClass(/selected/)
5050
})

examples/sites/demos/pc/app/select-wrapper/allow-create.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,14 @@
5151
</template>
5252

5353
<script>
54-
import { TinySelect, TinyOption, TinyInput, TinyButton, TinyDialogBox, TinyModal } from '@opentiny/vue'
54+
import {
55+
TinySelectWrapper as TinySelect,
56+
TinyOption,
57+
TinyInput,
58+
TinyButton,
59+
TinyDialogBox,
60+
TinyModal
61+
} from '@opentiny/vue'
5562
5663
export default {
5764
components: {

examples/sites/demos/pc/app/select-wrapper/automatic-dropdown.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'
22

33
test('不可搜索时,获取焦点不下拉', async ({ page }) => {
44
page.on('pageerror', (exception) => expect(exception).toBeNull())
5-
await page.goto('select#automatic-dropdown')
5+
await page.goto('select-wrapper#automatic-dropdown')
66
const wrap = page.locator('#automatic-dropdown')
77
const input = wrap.locator('.tiny-input__inner').first()
88
const dropdown = page.locator('.tiny-select-dropdown').first()
@@ -16,16 +16,16 @@ test('不可搜索时,获取焦点不下拉', async ({ page }) => {
1616

1717
test('可搜索时,获取焦点自动下拉', async ({ page }) => {
1818
page.on('pageerror', (exception) => expect(exception).toBeNull())
19-
await page.goto('select#automatic-dropdown')
19+
await page.goto('select-wrapper#automatic-dropdown')
2020
const wrap = page.locator('#automatic-dropdown')
2121
const input = wrap.locator('.tiny-input__inner').nth(1)
2222
const dropdown = page.locator('.tiny-select-dropdown').nth(1)
2323

2424
await wrap.getByRole('button').nth(1).click()
2525
// 聚焦下拉
26-
await dropdown.getByRole('listitem').filter({ hasText: '上海' }).click()
26+
await dropdown.locator('.tiny-option').filter({ hasText: '上海' }).click()
2727
await expect(input).toHaveValue('上海')
2828
// 验证选中
2929
await input.click()
30-
await expect(page.getByRole('listitem').filter({ hasText: '上海' })).toHaveClass(/selected/)
30+
await expect(dropdown.locator('.tiny-option').filter({ hasText: '上海' })).toHaveClass(/selected/)
3131
})

examples/sites/demos/pc/app/select-wrapper/automatic-dropdown.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
</template>
1818

1919
<script>
20-
import { TinySelect, TinyOption, TinyButton } from '@opentiny/vue'
20+
import { TinySelectWrapper as TinySelect, TinyOption, TinyButton } from '@opentiny/vue'
2121
2222
export default {
2323
components: {

examples/sites/demos/pc/app/select-wrapper/basic-usage.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@ import { test, expect } from '@playwright/test'
22

33
test('基础用法标签式', async ({ page }) => {
44
page.on('pageerror', (exception) => expect(exception).toBeNull())
5-
await page.goto('select#basic-usage')
5+
await page.goto('select-wrapper#basic-usage')
66
const wrap = page.locator('#basic-usage')
77
const select = wrap.locator('.tiny-select').nth(0)
88
const input = select.locator('.tiny-input__inner')
9-
const dropdown = page.locator('body > .tiny-select-dropdown')
9+
const dropdown = page.locator('body > .tiny-select-dropdown').nth(0)
1010
const option = dropdown.locator('.tiny-option')
1111

1212
await input.click()
1313
await option.filter({ hasText: '天津' }).click()
1414
await expect(input).toHaveValue('天津')
1515
await select.locator('.tiny-input__suffix svg').click()
16-
await expect(page.getByRole('listitem').filter({ hasText: '天津' })).toHaveClass(/selected/)
16+
await expect(dropdown.locator('.tiny-option').filter({ hasText: '天津' })).toHaveClass(/selected/)
1717
await option.filter({ hasText: '深圳' }).click()
1818
await expect(input).toHaveValue('深圳')
1919
await input.click()
@@ -25,7 +25,7 @@ test('基础用法标签式', async ({ page }) => {
2525

2626
test('基础用法配置式', async ({ page }) => {
2727
page.on('pageerror', (exception) => expect(exception).toBeNull())
28-
await page.goto('select#basic-usage')
28+
await page.goto('select-wrapper#basic-usage')
2929
const wrap = page.locator('#basic-usage')
3030
const select = wrap.locator('.tiny-select').nth(1)
3131
const input = select.locator('.tiny-input__inner')
@@ -36,7 +36,7 @@ test('基础用法配置式', async ({ page }) => {
3636
await option.filter({ hasText: '天津' }).click()
3737
await expect(input).toHaveValue('天津')
3838
await select.locator('.tiny-input__suffix svg').click()
39-
await expect(page.getByRole('listitem').filter({ hasText: '天津' })).toHaveClass(/selected/)
39+
await expect(dropdown.locator('.tiny-option').filter({ hasText: '天津' })).toHaveClass(/selected/)
4040
await option.filter({ hasText: '深圳' }).click()
4141
await expect(input).toHaveValue('深圳')
4242
await input.click()

0 commit comments

Comments
 (0)