Skip to content

Commit adab1b0

Browse files
committed
feat(tree-select): add tree-select component
1 parent 5e02624 commit adab1b0

File tree

15 files changed

+479
-1
lines changed

15 files changed

+479
-1
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
export default {
2+
mode: ['pc'],
3+
apis: [
4+
{
5+
name: 'tree-select',
6+
type: 'component',
7+
props: [
8+
{
9+
name: 'clearable',
10+
type: 'boolean',
11+
defaultValue: 'false',
12+
desc: {
13+
'zh-CN': '是否启用一键清除的功能',
14+
'en-US': 'Whether to display the one click clear button, only applicable to radio selection'
15+
},
16+
mode: ['pc'],
17+
pcDemo: 'filter'
18+
},
19+
{
20+
name: 'filter-method',
21+
type: '(query: string) => void',
22+
defaultValue: '',
23+
desc: {
24+
'zh-CN': '自定义过滤方法',
25+
'en-US': 'Custom filtering method'
26+
},
27+
mode: ['pc'],
28+
pcDemo: 'filter'
29+
},
30+
{
31+
name: 'filterable',
32+
type: 'boolean',
33+
defaultValue: 'false',
34+
desc: {
35+
'zh-CN': '是否可搜索',
36+
'en-US': 'Is it searchable'
37+
},
38+
mode: ['pc'],
39+
pcDemo: 'filter'
40+
},
41+
{
42+
name: 'modelValue / v-model',
43+
type: 'string | number | Array<string|number>',
44+
defaultValue: '',
45+
desc: {
46+
'zh-CN': '绑定值',
47+
'en-US': 'Bind value'
48+
},
49+
mode: ['pc'],
50+
pcDemo: 'basic-usage'
51+
},
52+
{
53+
name: 'multiple',
54+
type: 'boolean',
55+
defaultValue: 'false',
56+
desc: {
57+
'zh-CN': '是否允许选择多个选项',
58+
'en-US': 'Allow multiple options to be selected'
59+
},
60+
mode: ['pc'],
61+
pcDemo: 'multiple'
62+
},
63+
{
64+
name: 'text-field',
65+
type: 'string',
66+
defaultValue: "'label'",
67+
desc: {
68+
'zh-CN': '显示值字段',
69+
'en-US': 'Show Value Fields'
70+
},
71+
mode: ['pc'],
72+
pcDemo: 'map-field'
73+
},
74+
{
75+
name: 'tree-op',
76+
typeAnchorName: 'ITreeOption',
77+
type: 'ITreeOption',
78+
defaultValue: '',
79+
desc: {
80+
'zh-CN': '下拉树时,内置树组件的配置,用法同 Tree 组件。',
81+
'en-US':
82+
'When pulling down a tree, the configuration of the built-in tree component is the same as that of the Tree component. To be used in conjunction with the render type attribute'
83+
},
84+
mode: ['pc'],
85+
pcDemo: 'basic-usage'
86+
},
87+
{
88+
name: 'value-field',
89+
type: 'string',
90+
defaultValue: "'value'",
91+
desc: {
92+
'zh-CN': '绑定值字段',
93+
'en-US': 'Bind Value Field'
94+
},
95+
mode: ['pc'],
96+
pcDemo: 'map-field'
97+
}
98+
]
99+
}
100+
],
101+
types: [
102+
{
103+
name: 'ITreeOption',
104+
type: 'interface',
105+
code: `
106+
interface ITreeNode {
107+
label: string // 默认树节点的文本字段
108+
id: number|string // 树节点唯一标识
109+
children: ITreeNode[] // 子节点
110+
}
111+
112+
interface ITreeOption {
113+
data: ITreeNode[] // 树数据,用法同 Tree
114+
}
115+
`
116+
}
117+
]
118+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<template>
2+
<tiny-tree-select v-model="value" :tree-op="treeOp"></tiny-tree-select>
3+
</template>
4+
5+
<script setup>
6+
import { ref } from 'vue'
7+
import { TreeSelect as TinyTreeSelect } from '@opentiny/vue'
8+
9+
const value = ref('')
10+
11+
const treeOp = ref({
12+
data: [
13+
{
14+
value: 1,
15+
label: '一级 1',
16+
children: [
17+
{
18+
value: 4,
19+
label: '二级 1-1',
20+
children: [
21+
{
22+
value: 9,
23+
label: '三级 1-1-1'
24+
},
25+
{
26+
value: 10,
27+
label: '三级 1-1-2'
28+
}
29+
]
30+
}
31+
]
32+
},
33+
{
34+
value: 2,
35+
label: '一级 2',
36+
children: [
37+
{
38+
value: 5,
39+
label: '二级 2-1'
40+
},
41+
{
42+
value: 6,
43+
label: '二级 2-2'
44+
}
45+
]
46+
}
47+
]
48+
})
49+
</script>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { expect, test } from '@playwright/test'
2+
3+
test('测试基本用法', async ({ page }) => {
4+
page.on('pageerror', (exception) => expect(exception).toBeNull())
5+
await page.goto('tree-select#basic-usage')
6+
7+
const wrap = page.locator('#basic-usage')
8+
const select = wrap.locator('.tiny-tree-select').nth(0)
9+
const input = select.locator('.tiny-input__inner')
10+
const dropdown = page.locator('body > .tiny-select-dropdown')
11+
const treeNode = dropdown.locator('.tiny-tree-node')
12+
13+
await input.click()
14+
await expect(treeNode).toHaveCount(7)
15+
16+
await treeNode.filter({ hasText: /^ 2-1$/ }).click()
17+
await expect(input).toHaveValue('二级 2-1')
18+
await input.click()
19+
await expect(treeNode.filter({ hasText: /^ 2-1$/ })).toHaveClass(/is-current/)
20+
})
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<template>
2+
<tiny-tree-select v-model="value" :tree-op="treeOp"></tiny-tree-select>
3+
</template>
4+
5+
<script>
6+
import { TreeSelect } from '@opentiny/vue'
7+
8+
export default {
9+
components: {
10+
TinyTreeSelect: TreeSelect
11+
},
12+
data() {
13+
return {
14+
treeOp: {
15+
data: [
16+
{
17+
value: 1,
18+
label: '一级 1',
19+
children: [
20+
{
21+
value: 4,
22+
label: '二级 1-1',
23+
children: [
24+
{
25+
value: 9,
26+
label: '三级 1-1-1'
27+
},
28+
{
29+
value: 10,
30+
label: '三级 1-1-2'
31+
}
32+
]
33+
}
34+
]
35+
},
36+
{
37+
value: 2,
38+
label: '一级 2',
39+
children: [
40+
{
41+
value: 5,
42+
label: '二级 2-1'
43+
},
44+
{
45+
value: 6,
46+
label: '二级 2-2'
47+
}
48+
]
49+
}
50+
]
51+
}
52+
}
53+
}
54+
}
55+
</script>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: TreeSelect 树形选择器
3+
---
4+
5+
# TreeSelect 树形选择器
6+
7+
结合了 BaseSelect 和 Tree 组件的选择器,用于从一个下拉树中选择一个或多个选项。
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: TreeSelect
3+
---
4+
5+
# TreeSelect
6+
7+
A selector that combines the BaseSelect and Tree components to select one or more options from a drop-down tree.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export default {
2+
column: '2',
3+
owner: '',
4+
demos: [
5+
{
6+
demoId: 'basic-usage',
7+
name: {
8+
'zh-CN': '基本用法',
9+
'en-US': 'Basic Usage'
10+
},
11+
desc: {
12+
'zh-CN': '<p>最基础的用法,通过 <code>tree-op</code> 设置下拉树的数据源,<code>v-model</code> 设置绑定值。</p>',
13+
'en-US': ''
14+
},
15+
codeFiles: ['basic-usage.vue']
16+
}
17+
]
18+
}

examples/sites/demos/pc/menus.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,13 @@ export const cmpMenus = [
147147
{ 'nameCn': '开关', 'name': 'Switch', 'key': 'switch' },
148148
{ 'nameCn': '时间选择器', 'name': 'TimePicker', 'key': 'time-picker' },
149149
{ 'nameCn': '时间选择', 'name': 'TimeSelect', 'key': 'time-select' },
150-
{ 'nameCn': '穿梭框', 'name': 'Transfer', 'key': 'transfer' }
150+
{ 'nameCn': '穿梭框', 'name': 'Transfer', 'key': 'transfer' },
151+
{
152+
'nameCn': '树形选择器',
153+
'name': 'TreeSelect',
154+
'key': 'tree-select',
155+
'mark': { 'type': 'warning', 'text': 'Beta' }
156+
}
151157
]
152158
},
153159
{

packages/modules.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2978,6 +2978,19 @@
29782978
"type": "template",
29792979
"exclude": false
29802980
},
2981+
"TreeSelect": {
2982+
"path": "vue/src/tree-select/index.ts",
2983+
"type": "component",
2984+
"exclude": false,
2985+
"mode": [
2986+
"pc"
2987+
]
2988+
},
2989+
"TreeSelectPc": {
2990+
"path": "vue/src/tree-select/src/pc.vue",
2991+
"type": "template",
2992+
"exclude": false
2993+
},
29812994
"Upload": {
29822995
"path": "vue/src/upload/index.ts",
29832996
"type": "component",
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export const filter =
2+
({ vm }) =>
3+
(value) => {
4+
vm.$refs.treeRef.filter(value)
5+
}
6+
7+
export const nodeClick =
8+
({ props }) =>
9+
(data, { updateSelectedData, hidePanel }) => {
10+
if (!props.multiple) {
11+
updateSelectedData({
12+
...data,
13+
currentLabel: data[props.textField],
14+
value: data[props.valueField],
15+
state: {
16+
currentLabel: data[props.textField]
17+
}
18+
})
19+
20+
hidePanel()
21+
}
22+
}
23+
24+
export const check =
25+
({ props }) =>
26+
(checkedNodes, { updateSelectedData }) => {
27+
if (props.multiple) {
28+
updateSelectedData(
29+
checkedNodes.map((node) => {
30+
return {
31+
...node,
32+
currentLabel: node[props.textField],
33+
value: node[props.valueField]
34+
}
35+
})
36+
)
37+
}
38+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { filter, nodeClick, check } from './index'
2+
3+
export const api = ['state', 'filter', 'nodeClick', 'check']
4+
5+
export const renderless = (props, { reactive }, { vm }) => {
6+
const api = {}
7+
8+
const state = reactive({
9+
value: props.modelValue,
10+
treeData: props.treeOp.data
11+
})
12+
13+
Object.assign(api, {
14+
state,
15+
filter: filter({ vm }),
16+
nodeClick: nodeClick({ props }),
17+
check: check({ props })
18+
})
19+
20+
return api
21+
}

packages/vue/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@
242242
"@opentiny/vue-transfer-panel": "workspace:~",
243243
"@opentiny/vue-tree": "workspace:~",
244244
"@opentiny/vue-tree-menu": "workspace:~",
245+
"@opentiny/vue-tree-select": "workspace:~",
245246
"@opentiny/vue-upload": "workspace:~",
246247
"@opentiny/vue-upload-dragger": "workspace:~",
247248
"@opentiny/vue-upload-list": "workspace:~",

0 commit comments

Comments
 (0)