chore: add new component tree-select (#21782)

pull/22523/head
凃锦东 2023-05-10 20:36:15 +08:00
parent f14b5ba540
commit 56d60e4197
18 changed files with 2985 additions and 52 deletions

View File

@ -87,5 +87,6 @@
"empty": "./packages/empty/index.js",
"descriptions": "./packages/descriptions/index.js",
"descriptions-item": "./packages/descriptions-item/index.js",
"result": "./packages/result/index.js"
"result": "./packages/result/index.js",
"tree-select": "./packages/tree-select/index.js"
}

View File

@ -0,0 +1,817 @@
## TreeSelect
The tree selector of the dropdown menu, it combines the functions of components el-tree and el-select.
### Basic usage
:::demo
```html
<template>
<el-row :gutter="12">
<el-col :span="12">
<p>click text to select</p>
<el-tree-select
placeholder="Select"
v-model="value1"
:data="data"
@change="handleNodeChange"
/>
</el-col>
<el-col :span="12">
<p>show-checkbox</p>
<el-tree-select
placeholder="Select"
v-model="value2"
:data="data"
show-checkbox
@change="handleNodeChange"
/>
</el-col>
</el-row>
</template>
<script>
export default {
data() {
return {
value1: "",
value2: "",
data: [
{
value: "1",
label: "Level One 1",
children: [
{
value: "1-1",
label: "Level Two 1-1",
children: [
{
value: "1-1-1",
label: "Level Three 1-1-1",
},
],
},
],
},
{
value: "2",
label: "Level One 2",
children: [
{
value: "2-1",
label: "Level Two 2-1",
children: [
{
value: "2-1-1",
label: "Level Three 2-1-1",
},
],
},
{
value: "2-2",
label: "Level Two 2-2",
children: [
{
value: "2-2-1",
label: "Level Three 2-2-1",
},
],
},
],
},
{
value: "3",
label: "Level One 3",
children: [
{
value: "3-1",
label: "Level Two 3-1",
children: [
{
value: "3-1-1",
label: "Level Three 3-1-1",
},
],
},
{
value: "3-2",
label: "Level Two 3-2",
children: [
{
value: "3-2-1",
label: "Level Three 3-2-1",
},
],
},
],
},
]
}
},
methods: {
handleNodeChange(e) {
console.log(e);
}
}
}
</script>
```
:::
### Select any level
When using the check-strictly=true attribute, any node can be checked, otherwise only leaf nodes are supported.
:::demo
```html
<el-row :gutter="12">
<el-col :span="12">
<p>click text to select</p>
<el-tree-select
placeholder="Select"
v-model="value1"
:data="data"
check-strictly
@change="handleNodeChange"
/>
</el-col>
<el-col :span="12">
<p>show-checkbox, only click checkbox to select</p>
<el-tree-select
placeholder="Select"
v-model="value2"
:data="data"
show-checkbox
check-strictly
@change="handleNodeChange"
/>
</el-col>
</el-row>
<script>
export default {
data() {
return {
value1: "",
value2: "",
data: [
{
value: "1",
label: "Level One 1",
children: [
{
value: "1-1",
label: "Level Two 1-1",
children: [
{
value: "1-1-1",
label: "Level Three 1-1-1",
},
],
},
],
},
{
value: "2",
label: "Level One 2",
children: [
{
value: "2-1",
label: "Level Two 2-1",
children: [
{
value: "2-1-1",
label: "Level Three 2-1-1",
},
],
},
{
value: "2-2",
label: "Level Two 2-2",
children: [
{
value: "2-2-1",
label: "Level Three 2-2-1",
},
],
},
],
},
{
value: "3",
label: "Level One 3",
children: [
{
value: "3-1",
label: "Level Two 3-1",
children: [
{
value: "3-1-1",
label: "Level Three 3-1-1",
},
],
},
{
value: "3-2",
label: "Level Two 3-2",
children: [
{
value: "3-2-1",
label: "Level Three 3-2-1",
},
],
},
],
},
]
}
},
methods: {
handleNodeChange(e) {
console.log(e);
}
}
}
</script>
```
:::
### Multiple Selection
Multiple selection using clicks or checkbox.
:::demo
```html
<el-row :gutter="12">
<el-col :span="8">
<p>click text to select</p>
<el-tree-select
placeholder="Select"
v-model="value1"
:data="data"
multiple
@change="handleNodeChange"
/>
</el-col>
<el-col :span="8">
<p>show checkbox</p>
<el-tree-select
placeholder="Select"
v-model="value2"
:data="data"
multiple
show-checkbox
@change="handleNodeChange"
/>
</el-col>
<el-col :span="8">
<p>show checkbox with `check-strictly`</p>
<el-tree-select
placeholder="Select"
v-model="value3"
:data="data"
multiple
show-checkbox
check-strictly
@change="handleNodeChange"
/>
</el-col>
</el-row>
<script>
export default {
data() {
return {
value1: [],
value2: [],
value3: [],
data: [
{
value: "1",
label: "Level One 1",
children: [
{
value: "1-1",
label: "Level Two 1-1",
children: [
{
value: "1-1-1",
label: "Level Three 1-1-1",
},
],
},
],
},
{
value: "2",
label: "Level One 2",
children: [
{
value: "2-1",
label: "Level Two 2-1",
children: [
{
value: "2-1-1",
label: "Level Three 2-1-1",
},
],
},
{
value: "2-2",
label: "Level Two 2-2",
children: [
{
value: "2-2-1",
label: "Level Three 2-2-1",
},
],
},
],
},
{
value: "3",
label: "Level One 3",
children: [
{
value: "3-1",
label: "Level Two 3-1",
children: [
{
value: "3-1-1",
label: "Level Three 3-1-1",
},
],
},
{
value: "3-2",
label: "Level Two 3-2",
children: [
{
value: "3-2-1",
label: "Level Three 3-2-1",
},
],
},
],
},
]
}
},
methods: {
handleNodeChange(e) {
console.log(e);
}
}
}
</script>
```
:::
### Clearable
You can clear tree-select using a clear icon.
Set clearable attribute for el-tree-select and a clear icon will appear.
:::demo
```html
<el-row :gutter="12">
<el-col :span="12">
<el-tree-select
placeholder="Select"
v-model="value1"
:data="data"
clearable
/>
</el-col>
<el-col :span="12">
<el-tree-select
placeholder="Select"
v-model="value2"
:data="data"
multiple
show-checkbox
clearable
/>
</el-col>
</el-row>
<script>
export default {
data() {
return {
value1: "",
value2: "",
data: [
{
value: "1",
label: "Level One 1",
children: [
{
value: "1-1",
label: "Level Two 1-1",
children: [
{
value: "1-1-1",
label: "Level Three 1-1-1",
},
],
},
],
},
{
value: "2",
label: "Level One 2",
children: [
{
value: "2-1",
label: "Level Two 2-1",
children: [
{
value: "2-1-1",
label: "Level Three 2-1-1",
},
],
},
{
value: "2-2",
label: "Level Two 2-2",
children: [
{
value: "2-2-1",
label: "Level Three 2-2-1",
},
],
},
],
},
{
value: "3",
label: "Level One 3",
children: [
{
value: "3-1",
label: "Level Two 3-1",
children: [
{
value: "3-1-1",
label: "Level Three 3-1-1",
},
],
},
{
value: "3-2",
label: "Level Two 3-2",
children: [
{
value: "3-2-1",
label: "Level Three 3-2-1",
},
],
},
],
},
]
}
},
methods: {
handleNodeChange(e) {
console.log(e);
}
}
}
</script>
```
:::
### Disabled Selection
Disable options using the disabled field.
:::demo
```html
<el-tree-select
placeholder="Select"
v-model="value"
:data="data"
/>
<script>
export default {
data() {
return {
value: "",
data: [
{
value: "1",
label: "Level One 1",
disabled: true,
children: [
{
value: "1-1",
label: "Level Two 1-1",
disabled: true,
children: [
{
value: "1-1-1",
label: "Level Three 1-1-1",
disabled: true,
},
],
},
],
},
{
value: "2",
label: "Level One 2",
children: [
{
value: "2-1",
label: "Level Two 2-1",
children: [
{
value: "2-1-1",
label: "Level Three 2-1-1",
},
],
},
{
value: "2-2",
label: "Level Two 2-2",
children: [
{
value: "2-2-1",
label: "Level Three 2-2-1",
},
],
},
],
},
{
value: "3",
label: "Level One 3",
children: [
{
value: "3-1",
label: "Level Two 3-1",
children: [
{
value: "3-1-1",
label: "Level Three 3-1-1",
},
],
},
{
value: "3-2",
label: "Level Two 3-2",
children: [
{
value: "3-2-1",
label: "Level Three 3-2-1",
},
],
},
],
},
]
}
},
}
</script>
```
:::
### Filterable
Use keyword filtering or custom filtering methods. filterMethod can custom filter method for data.
:::demo
```html
<el-row :gutter="12">
<el-col :span="12">
<p>filterable</p>
<el-tree-select
placeholder="Select"
v-model="value1"
:data="data"
filterable
/>
</el-col>
<el-col :span="12">
<p>filterMethod</p>
<el-tree-select
placeholder="Select"
v-model="value2"
:data="data"
filterable
:filter-method="filterMethod"
/>
</el-col>
</el-row>
<script>
export default {
data() {
return {
value1: "",
value2: "",
data: [
{
value: "1",
label: "Level One 1",
children: [
{
value: "1-1",
label: "Level Two 1-1",
children: [
{
value: "1-1-1",
label: "Level Three 1-1-1",
},
],
},
],
},
{
value: "2",
label: "Level One 2",
children: [
{
value: "2-1",
label: "Level Two 2-1",
children: [
{
value: "2-1-1",
label: "Level Three 2-1-1",
},
],
},
{
value: "2-2",
label: "Level Two 2-2",
children: [
{
value: "2-2-1",
label: "Level Three 2-2-1",
},
],
},
],
},
{
value: "3",
label: "Level One 3",
children: [
{
value: "3-1",
label: "Level Two 3-1",
children: [
{
value: "3-1-1",
label: "Level Three 3-1-1",
},
],
},
{
value: "3-2",
label: "Level Two 3-2",
children: [
{
value: "3-2-1",
label: "Level Three 3-2-1",
},
],
},
],
},
]
}
},
methods: {
filterMethod(value, data) {
return data.value.indexOf(value) !== -1;
}
}
}
</script>
```
:::
### LazyLoad
Lazy loading of tree nodes, suitable for large data lists.
:::demo
```html
<el-tree-select
:props="props"
placeholder="Select"
v-model="value"
clearable
filterable
lazy
:filterMethod="filterMethod"
:load="loadNode"
node-key="id"
/>
<script>
export default {
data() {
return {
value: "",
props: {
label: "name",
children: "zones",
isLeaf: "leaf",
},
}
},
methods: {
loadNode(node, resolve) {
if (node.level === 0) {
return resolve([{ id: "0", name: "Trunk" }]);
}
if (node.level > 3) return resolve([]);
setTimeout(() => {
const data = [
{
id: `leaf-${node.level}`,
name: `Leaf ${node.level}`,
leaf: true,
},
{
id: `branch-${node.level}`,
name: `Branch ${node.level}`,
},
];
resolve(data);
}, 500);
},
filterMethod(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
},
}
}
</script>
```
:::
### Attributes
| Name | Description | Type | Accepted Values | Default|
| --------------------- | ------------------------------------------------------------------------------------------------- | --------------------------- | ----------------- | ------ |
| value / v-model | binding value | boolean / string / number | — | — |
| data | tree data | array | — | — |
| multiple | whether multiple-select is activated | boolean | — | false |
| disabled | whether Select is disabled | boolean | — | false |
| size | size of Input | string | medium/small/mini | — |
| clearable | whether select can be cleared | boolean | — | false |
| collapse-tags | whether to collapse tags to a text when multiple selecting | boolean | — | false |
| name | the name attribute of Tree-select input | string | — | — |
| placeholder | placeholder | string | — | 请选择 |
| filterable | whether Tree-select is filterable | boolean | — | false |
| empty-text | displayed text when there is no data | String | — | — |
| node-key | unique identity key name for nodes, its value should be unique across the whole tree | String | — | — |
| props | configuration options, see the following table | object | — | — |
| render-after-expand | whether to render child nodes only after a parent node is expanded for the first time | boolean | — | true |
| load | method for loading subtree data, only works when lazy is true | function(node, resolve) | — | — |
| default-expand-all | whether to expand all nodes by default | boolean | — | false |
| auto-expand-parent | whether to expand father node when a child node is expanded | boolean | — | true |
| show-checkbox | whether node is selectable | boolean | — | false |
| check-strictly | whether checked state of a node not affects its father and child nodes when show-checkbox is true | boolean | — | false |
| filter-method | custom filter method | Function(value, data, node) | — | — |
| accordion | whether only one node among the same level can be expanded at one time | boolean | — | false |
| indent | horizontal indentation of nodes in adjacent levels in pixels | number | — | 16 |
| lazy | whether to lazy load leaf node, used with load attribute | boolean | — | false |
| popper-append-to-body | whether to append the popper menu to body. If the positioning of the popper is wrong, you can try to set this prop to false | boolean | - | true |
### props
| Attribute| Description | Type | Accepted Values | Default |
| -------- | ----------------------------------------------------------------------------- | ----------------------------- | -------------------| ----------------|
| label | specify which key of node object is used as the node's label | string, function(data, node) | — | — |
| children | specify which node object is used as the node's subtree | string | — | — |
| disabled | specify which key of node object represents if node's checkbox is disabled | boolean, function(data, node) | — | — |
| isLeaf | specify whether the node is a leaf node, only works when lazy load is enabled | boolean, function(data, node) | — | — |
### Events
| Method | Description | Parameters |
| -------------- | --------------------------------------------------------------- | ------------------------------------------ |
| change | triggers when the selected value changes | current selected value |
| visible-change | triggers when the dropdown appears/disappears | true when it appears, and false otherwise |
| remove-tag | triggers when a tag is removed in multiple mode | removed tag value |
| clear | triggers when the clear icon is clicked in a clearable Select | — |
| blur | triggers when Input blurs | (event: Event) |
| focus | triggers when Input focuses | (event: Event) |

View File

@ -0,0 +1 @@
## TreeSelect

View File

@ -0,0 +1 @@
## TreeSelect

View File

@ -0,0 +1,821 @@
## TreeSelect 树形选择器
含有下拉菜单的树形选择器,结合了 el-tree 和 el-select 两个组件的功能。
### 基础单选
:::demo
```html
<template>
<el-row :gutter="12">
<el-col :span="12">
<p>click text to select</p>
<el-tree-select
placeholder="Select"
v-model="value1"
:data="data"
@change="handleNodeChange"
/>
</el-col>
<el-col :span="12">
<p>show-checkbox</p>
<el-tree-select
placeholder="Select"
v-model="value2"
:data="data"
show-checkbox
@change="handleNodeChange"
/>
</el-col>
</el-row>
</template>
<script>
export default {
data() {
return {
value1: "",
value2: "",
data: [
{
value: "1",
label: "Level One 1",
children: [
{
value: "1-1",
label: "Level Two 1-1",
children: [
{
value: "1-1-1",
label: "Level Three 1-1-1",
},
],
},
],
},
{
value: "2",
label: "Level One 2",
children: [
{
value: "2-1",
label: "Level Two 2-1",
children: [
{
value: "2-1-1",
label: "Level Three 2-1-1",
},
],
},
{
value: "2-2",
label: "Level Two 2-2",
children: [
{
value: "2-2-1",
label: "Level Three 2-2-1",
},
],
},
],
},
{
value: "3",
label: "Level One 3",
children: [
{
value: "3-1",
label: "Level Two 3-1",
children: [
{
value: "3-1-1",
label: "Level Three 3-1-1",
},
],
},
{
value: "3-2",
label: "Level Two 3-2",
children: [
{
value: "3-2-1",
label: "Level Three 3-2-1",
},
],
},
],
},
]
}
},
methods: {
handleNodeChange(e) {
console.log(e);
}
}
}
</script>
```
:::
### 选择任意级别
当属性 check-strictly=true 时,任何节点都可以被选择,否则只有子节点可被选择。
:::demo
```html
<el-row :gutter="12">
<el-col :span="12">
<p>click text to select</p>
<el-tree-select
placeholder="Select"
v-model="value1"
:data="data"
check-strictly
@change="handleNodeChange"
/>
</el-col>
<el-col :span="12">
<p>show-checkbox, only click checkbox to select</p>
<el-tree-select
placeholder="Select"
v-model="value2"
:data="data"
show-checkbox
check-strictly
@change="handleNodeChange"
/>
</el-col>
</el-row>
<script>
export default {
data() {
return {
value1: "",
value2: "",
data: [
{
value: "1",
label: "Level One 1",
children: [
{
value: "1-1",
label: "Level Two 1-1",
children: [
{
value: "1-1-1",
label: "Level Three 1-1-1",
},
],
},
],
},
{
value: "2",
label: "Level One 2",
children: [
{
value: "2-1",
label: "Level Two 2-1",
children: [
{
value: "2-1-1",
label: "Level Three 2-1-1",
},
],
},
{
value: "2-2",
label: "Level Two 2-2",
children: [
{
value: "2-2-1",
label: "Level Three 2-2-1",
},
],
},
],
},
{
value: "3",
label: "Level One 3",
children: [
{
value: "3-1",
label: "Level Two 3-1",
children: [
{
value: "3-1-1",
label: "Level Three 3-1-1",
},
],
},
{
value: "3-2",
label: "Level Two 3-2",
children: [
{
value: "3-2-1",
label: "Level Three 3-2-1",
},
],
},
],
},
]
}
},
methods: {
handleNodeChange(e) {
console.log(e);
}
}
}
</script>
```
:::
### 多选
通过点击或复选框选择多个选项。
:::demo
```html
<el-row :gutter="12">
<el-col :span="8">
<p>click text to select</p>
<el-tree-select
placeholder="Select"
v-model="value1"
:data="data"
multiple
@change="handleNodeChange"
/>
</el-col>
<el-col :span="8">
<p>show checkbox</p>
<el-tree-select
placeholder="Select"
v-model="value2"
:data="data"
multiple
show-checkbox
@change="handleNodeChange"
/>
</el-col>
<el-col :span="8">
<p>show checkbox with `check-strictly`</p>
<el-tree-select
placeholder="Select"
v-model="value3"
:data="data"
multiple
show-checkbox
check-strictly
@change="handleNodeChange"
/>
</el-col>
</el-row>
<script>
export default {
data() {
return {
value1: [],
value2: [],
value3: [],
data: [
{
value: "1",
label: "Level One 1",
children: [
{
value: "1-1",
label: "Level Two 1-1",
children: [
{
value: "1-1-1",
label: "Level Three 1-1-1",
},
],
},
],
},
{
value: "2",
label: "Level One 2",
children: [
{
value: "2-1",
label: "Level Two 2-1",
children: [
{
value: "2-1-1",
label: "Level Three 2-1-1",
},
],
},
{
value: "2-2",
label: "Level Two 2-2",
children: [
{
value: "2-2-1",
label: "Level Three 2-2-1",
},
],
},
],
},
{
value: "3",
label: "Level One 3",
children: [
{
value: "3-1",
label: "Level Two 3-1",
children: [
{
value: "3-1-1",
label: "Level Three 3-1-1",
},
],
},
{
value: "3-2",
label: "Level Two 3-2",
children: [
{
value: "3-2-1",
label: "Level Three 3-2-1",
},
],
},
],
},
]
}
},
methods: {
handleNodeChange(e) {
console.log(e);
}
}
}
</script>
```
:::
### 可清空选项
包含清空按钮,可将选择器清空为初始状态,为`el-tree-select`设置`clearable`属性,则可将选择器清空
:::demo
```html
<el-row :gutter="12">
<el-col :span="12">
<el-tree-select
placeholder="Select"
v-model="value1"
:data="data"
clearable
/>
</el-col>
<el-col :span="12">
<el-tree-select
placeholder="Select"
v-model="value2"
:data="data"
multiple
show-checkbox
clearable
/>
</el-col>
</el-row>
<script>
export default {
data() {
return {
value1: "",
value2: "",
data: [
{
value: "1",
label: "Level One 1",
children: [
{
value: "1-1",
label: "Level Two 1-1",
children: [
{
value: "1-1-1",
label: "Level Three 1-1-1",
},
],
},
],
},
{
value: "2",
label: "Level One 2",
children: [
{
value: "2-1",
label: "Level Two 2-1",
children: [
{
value: "2-1-1",
label: "Level Three 2-1-1",
},
],
},
{
value: "2-2",
label: "Level Two 2-2",
children: [
{
value: "2-2-1",
label: "Level Three 2-2-1",
},
],
},
],
},
{
value: "3",
label: "Level One 3",
children: [
{
value: "3-1",
label: "Level Two 3-1",
children: [
{
value: "3-1-1",
label: "Level Three 3-1-1",
},
],
},
{
value: "3-2",
label: "Level Two 3-2",
children: [
{
value: "3-2-1",
label: "Level Three 3-2-1",
},
],
},
],
},
]
}
},
methods: {
handleNodeChange(e) {
console.log(e);
}
}
}
</script>
```
:::
### 禁用选项
使用 disabled 字段禁用选项。
:::demo
```html
<el-tree-select
placeholder="Select"
v-model="value"
:data="data"
/>
<script>
export default {
data() {
return {
value: "",
data: [
{
value: "1",
label: "Level One 1",
disabled: true,
children: [
{
value: "1-1",
label: "Level Two 1-1",
disabled: true,
children: [
{
value: "1-1-1",
label: "Level Three 1-1-1",
disabled: true,
},
],
},
],
},
{
value: "2",
label: "Level One 2",
children: [
{
value: "2-1",
label: "Level Two 2-1",
children: [
{
value: "2-1-1",
label: "Level Three 2-1-1",
},
],
},
{
value: "2-2",
label: "Level Two 2-2",
children: [
{
value: "2-2-1",
label: "Level Three 2-2-1",
},
],
},
],
},
{
value: "3",
label: "Level One 3",
children: [
{
value: "3-1",
label: "Level Two 3-1",
children: [
{
value: "3-1-1",
label: "Level Three 3-1-1",
},
],
},
{
value: "3-2",
label: "Level Two 3-2",
children: [
{
value: "3-2-1",
label: "Level Three 3-2-1",
},
],
},
],
},
]
}
},
}
</script>
```
:::
### 可筛选
使用关键字筛选或自定义筛选方法。 filterMethod可以自定义数据筛选的方法。
:::demo
```html
<el-row :gutter="12">
<el-col :span="12">
<p>filterable</p>
<el-tree-select
placeholder="Select"
v-model="value1"
:data="data"
filterable
/>
</el-col>
<el-col :span="12">
<p>filterMethod</p>
<el-tree-select
placeholder="Select"
v-model="value2"
:data="data"
filterable
:filter-method="filterMethod"
/>
</el-col>
</el-row>
<script>
export default {
data() {
return {
value1: "",
value2: "",
data: [
{
value: "1",
label: "Level One 1",
children: [
{
value: "1-1",
label: "Level Two 1-1",
children: [
{
value: "1-1-1",
label: "Level Three 1-1-1",
},
],
},
],
},
{
value: "2",
label: "Level One 2",
children: [
{
value: "2-1",
label: "Level Two 2-1",
children: [
{
value: "2-1-1",
label: "Level Three 2-1-1",
},
],
},
{
value: "2-2",
label: "Level Two 2-2",
children: [
{
value: "2-2-1",
label: "Level Three 2-2-1",
},
],
},
],
},
{
value: "3",
label: "Level One 3",
children: [
{
value: "3-1",
label: "Level Two 3-1",
children: [
{
value: "3-1-1",
label: "Level Three 3-1-1",
},
],
},
{
value: "3-2",
label: "Level Two 3-2",
children: [
{
value: "3-2-1",
label: "Level Three 3-2-1",
},
],
},
],
},
]
}
},
methods: {
filterMethod(value, data) {
return data.value.indexOf(value) !== -1;
}
}
}
</script>
```
:::
### 懒加载
树节点懒加载,更加适合于数据量大的列表。
:::demo
```html
<el-tree-select
:props="props"
placeholder="Select"
v-model="value"
clearable
filterable
lazy
:filterMethod="filterMethod"
:load="loadNode"
node-key="id"
/>
<script>
export default {
data() {
return {
value: "",
props: {
label: "name",
children: "zones",
isLeaf: "leaf",
},
}
},
methods: {
loadNode(node, resolve) {
if (node.level === 0) {
return resolve([{ id: "0", name: "Trunk" }]);
}
if (node.level > 3) return resolve([]);
setTimeout(() => {
const data = [
{
id: `leaf-${node.level}`,
name: `Leaf ${node.level}`,
leaf: true,
},
{
id: `branch-${node.level}`,
name: `Branch ${node.level}`,
},
];
resolve(data);
}, 500);
},
filterMethod(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
},
}
}
</script>
```
:::
### Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| --------------------- | ----------------------------------------------------------------------------------------------- | --------------------------- | ----------------- | ------ |
| value / v-model | 绑定值 | boolean / string / number | — | — |
| data | 展示数据 | array | — | — |
| multiple | 是否多选 | boolean | — | false |
| disabled | 是否禁用 | boolean | — | false |
| size | 输入框尺寸 | string | medium/small/mini | — |
| clearable | 是否可以清空选项 | boolean | — | false |
| collapse-tags | 多选时是否将选中值按文字的形式展示 | boolean | — | false |
| name | tree-select input 的 name 属性 | string | — | — |
| placeholder | 占位符 | string | — | 请选择 |
| filterable | 是否可搜索 | boolean | — | false |
| empty-text | 内容为空的时候展示的文本 | String | — | — |
| node-key | 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 | String | — | — |
| props | 配置选项,具体看下表 | object | — | — |
| render-after-expand | 是否在第一次展开某个树节点后才渲染其子节点 | boolean | — | true |
| load | 加载子树数据的方法,仅当 lazy 属性为 true 时生效 | function(node, resolve) | — | — |
| default-expand-all | 是否默认展开所有节点 | boolean | — | false |
| auto-expand-parent | 展开子节点的时候是否自动展开父节点 | boolean | — | true |
| show-checkbox | 节点是否可被选择 | boolean | — | false |
| check-strictly | 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false | boolean | — | false |
| filter-method | 对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏 | Function(value, data, node) | — | — |
| accordion | 是否每次只打开一个同级树节点展开 | boolean | — | false |
| indent | 相邻级节点间的水平缩进,单位为像素 | number | — | 16 |
| lazy | 是否懒加载子节点,需与 load 方法结合使用 | boolean | — | false |
| popper-append-to-body | 是否将弹出框插入至 body 元素。在弹出框的定位出现问题时,可将该属性设置为 false | boolean | - | true |
### props
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
| -------- | -------------------------------------------------------- | ----------------------------- | ------ | ------ |
| label | 指定节点标签为节点对象的某个属性值 | string, function(data, node) | — | — |
| children | 指定子树为节点对象的某个属性值 | string | — | — |
| disabled | 指定节点选择框是否禁用为节点对象的某个属性值 | boolean, function(data, node) | — | — |
| isLeaf | 指定节点是否为叶子节点,仅在指定了 lazy 属性的情况下生效 | boolean, function(data, node) | — | — |
### Events
| 事件名称 | 说明 | 回调参数 |
| -------------- | ---------------------------------------- | ----------------------------- |
| change | 选中值发生变化时触发 | 目前的选中值 |
| visible-change | 下拉框出现/隐藏时触发 | 出现则为 true隐藏则为 false |
| remove-tag | 多选模式下移除 tag 时触发 | 移除的 tag 值 |
| clear | 可清空的单选模式下用户点击清空按钮时触发 | — |
| blur | 当 input 失去焦点时触发 | (event: Event) |
| focus | 当 input 获得焦点时触发 | (event: Event) |
### Methods
| 方法名 | 说明 | 参数 |
| ------ | ------------------------------- | ---- |
| focus | 使 input 获取焦点 | - |
| blur | 使 input 失去焦点,并隐藏下拉框 | - |

View File

@ -104,6 +104,10 @@
"path": "/cascader",
"title": "Cascader 级联选择器"
},
{
"path": "/tree-select",
"title": "TreeSelect 树形选择器"
},
{
"path": "/switch",
"title": "Switch 开关"
@ -422,6 +426,10 @@
"path": "/cascader",
"title": "Cascader"
},
{
"path": "/tree-select",
"title": "TreeSelect"
},
{
"path": "/switch",
"title": "Switch"
@ -1271,4 +1279,4 @@
]
}
]
}
}

View File

@ -6,19 +6,20 @@
/* Transition
-------------------------- */
$--all-transition: all .3s cubic-bezier(.645,.045,.355,1) !default;
$--all-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !default;
$--fade-transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) !default;
$--fade-linear-transition: opacity 200ms linear !default;
$--md-fade-transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) !default;
$--border-transition-base: border-color .2s cubic-bezier(.645,.045,.355,1) !default;
$--color-transition-base: color .2s cubic-bezier(.645,.045,.355,1) !default;
$--md-fade-transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1),
opacity 300ms cubic-bezier(0.23, 1, 0.32, 1) !default;
$--border-transition-base: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !default;
$--color-transition-base: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1) !default;
/* Color
-------------------------- */
/// color|1|Brand Color|0
$--color-primary: #409EFF !default;
$--color-primary: #409eff !default;
/// color|1|Background Color|4
$--color-white: #FFFFFF !default;
$--color-white: #ffffff !default;
/// color|1|Background Color|4
$--color-black: #000000 !default;
$--color-primary-light-1: mix($--color-white, $--color-primary, 10%) !default; /* 53a8ff */
@ -31,11 +32,11 @@ $--color-primary-light-7: mix($--color-white, $--color-primary, 70%) !default; /
$--color-primary-light-8: mix($--color-white, $--color-primary, 80%) !default; /* d9ecff */
$--color-primary-light-9: mix($--color-white, $--color-primary, 90%) !default; /* ecf5ff */
/// color|1|Functional Color|1
$--color-success: #67C23A !default;
$--color-success: #67c23a !default;
/// color|1|Functional Color|1
$--color-warning: #E6A23C !default;
$--color-warning: #e6a23c !default;
/// color|1|Functional Color|1
$--color-danger: #F56C6C !default;
$--color-danger: #f56c6c !default;
/// color|1|Functional Color|1
$--color-info: #909399 !default;
@ -55,19 +56,19 @@ $--color-text-regular: #606266 !default;
/// color|1|Font Color|2
$--color-text-secondary: #909399 !default;
/// color|1|Font Color|2
$--color-text-placeholder: #C0C4CC !default;
$--color-text-placeholder: #c0c4cc !default;
/// color|1|Border Color|3
$--border-color-base: #DCDFE6 !default;
$--border-color-base: #dcdfe6 !default;
/// color|1|Border Color|3
$--border-color-light: #E4E7ED !default;
$--border-color-light: #e4e7ed !default;
/// color|1|Border Color|3
$--border-color-lighter: #EBEEF5 !default;
$--border-color-lighter: #ebeef5 !default;
/// color|1|Border Color|3
$--border-color-extra-light: #F2F6FC !default;
$--border-color-extra-light: #f2f6fc !default;
// Background
/// color|1|Background Color|4
$--background-color-base: #F5F7FA !default;
$--background-color-base: #f5f7fa !default;
/* Link
-------------------------- */
@ -91,9 +92,9 @@ $--border-radius-zero: 0 !default;
// Box-shadow
/// boxShadow|1|Shadow|1
$--box-shadow-base: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04) !default;
$--box-shadow-base: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04) !default;
// boxShadow|1|Shadow|1
$--box-shadow-dark: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .12) !default;
$--box-shadow-dark: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.12) !default;
/// boxShadow|1|Shadow|1
$--box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1) !default;
@ -103,8 +104,8 @@ $--fill-base: $--color-white !default;
/* Typography
-------------------------- */
$--font-path: 'fonts' !default;
$--font-display: 'auto' !default;
$--font-path: "fonts" !default;
$--font-display: "auto" !default;
/// fontSize|1|Font Size|0
$--font-size-extra-large: 20px !default;
/// fontSize|1|Font Size|0
@ -210,8 +211,6 @@ $--checkbox-button-checked-font-color: $--color-white !default;
/// color||Color|0
$--checkbox-button-checked-border-color: $--color-primary !default;
/* Radio
-------------------------- */
/// fontSize||Font|1
@ -478,8 +477,8 @@ $--cascader-menu-radius: $--border-radius-base !default;
$--cascader-menu-border: solid 1px $--border-color-light !default;
$--cascader-menu-shadow: $--box-shadow-light !default;
$--cascader-node-background-hover: $--background-color-base !default;
$--cascader-node-color-disabled:$--color-text-placeholder !default;
$--cascader-color-empty:$--color-text-placeholder !default;
$--cascader-node-color-disabled: $--color-text-placeholder !default;
$--cascader-color-empty: $--color-text-placeholder !default;
$--cascader-tag-background: #f0f2f5;
/* Group
@ -589,7 +588,6 @@ $--button-info-background-color: $--color-info !default;
$--button-hover-tint-percent: 20% !default;
$--button-active-shade-percent: 10% !default;
/* cascader
-------------------------- */
$--cascader-height: 200px !default;
@ -637,7 +635,7 @@ $--table-row-hover-background-color: $--background-color-base !default;
$--table-current-row-background-color: $--color-primary-light-9 !default;
/// color||Color|0
$--table-header-background-color: $--color-white !default;
$--table-fixed-box-shadow: 0 0 10px rgba(0, 0, 0, .12) !default;
$--table-fixed-box-shadow: 0 0 10px rgba(0, 0, 0, 0.12) !default;
/* Pagination
-------------------------- */
@ -825,8 +823,8 @@ $--loading-fullscreen-spinner-size: 50px !default;
/* Scrollbar
--------------------------*/
$--scrollbar-background-color: rgba($--color-text-secondary, .3) !default;
$--scrollbar-hover-background-color: rgba($--color-text-secondary, .5) !default;
$--scrollbar-background-color: rgba($--color-text-secondary, 0.3) !default;
$--scrollbar-hover-background-color: rgba($--color-text-secondary, 0.5) !default;
/* Carousel
--------------------------*/
@ -935,7 +933,7 @@ $--link-info-font-color: $--color-info !default;
/// border||Other|4
$--calendar-border: $--table-border !default;
/// color||Other|4
$--calendar-selected-background-color: #F2F8FE !default;
$--calendar-selected-background-color: #f2f8fe !default;
$--calendar-cell-width: 85px !default;
/* Form
@ -948,7 +946,7 @@ $--form-label-font-size: $--font-size-base !default;
/// color||Color|0
$--avatar-font-color: #fff !default;
/// color||Color|0
$--avatar-background-color: #C0C4CC !default;
$--avatar-background-color: #c0c4cc !default;
/// fontSize||Font Size|1
$--avatar-text-font-size: 14px !default;
/// fontSize||Font Size|1
@ -983,7 +981,7 @@ $--skeleton-to-color: #e6e6e6 !default;
/* Svg
--------------- */
$--svg-monochrome-grey: #DCDDE0 !default;
$--svg-monochrome-grey: #dcdde0 !default;
/* Result
-------------------------- */
@ -998,6 +996,23 @@ $--result-success-color: $--color-success !default;
$--result-warning-color: $--color-warning !default;
$--result-danger-color: $--color-danger !default;
/* Tree-select
-------------------------- */
$--tree-select-font-size: $--font-size-base !default;
$--tree-select-dropdown-border: solid 1px $--border-color-light !default;
$--tree-select-multiple-input-color: $--color-text-placeholder !default;
$--tree-select-dropdown-background: $--color-white !default;
$--tree-select-option-selected-hover: $--background-color-base !default;
$--tree-select-dropdown-max-height: 272px !default;
$--tree-select-dropdown-padding: 6px 0 !default;
$--tree-select-dropdown-empty-color: #999 !default;
$--tree-select-dropdown-empty-padding: 10px 0 !default;
$--tree-select-mini-dropdown-max-height: 176px !default;
$--tree-select-small-dropdown-max-height: 216px !default;
$--tree-select-large-dropdown-max-height: 386px !default;
/* Break-point
--------------------------*/
$--sm: 768px !default;
@ -1006,23 +1021,49 @@ $--lg: 1200px !default;
$--xl: 1920px !default;
$--breakpoints: (
'xs' : (max-width: $--sm - 1),
'sm' : (min-width: $--sm),
'md' : (min-width: $--md),
'lg' : (min-width: $--lg),
'xl' : (min-width: $--xl)
"xs": (
max-width: $--sm - 1,
),
"sm": (
min-width: $--sm,
),
"md": (
min-width: $--md,
),
"lg": (
min-width: $--lg,
),
"xl": (
min-width: $--xl,
),
);
$--breakpoints-spec: (
'xs-only' : (max-width: $--sm - 1),
'sm-and-up' : (min-width: $--sm),
'sm-only': "(min-width: #{$--sm}) and (max-width: #{$--md - 1})",
'sm-and-down': (max-width: $--md - 1),
'md-and-up' : (min-width: $--md),
'md-only': "(min-width: #{$--md}) and (max-width: #{$--lg - 1})",
'md-and-down': (max-width: $--lg - 1),
'lg-and-up' : (min-width: $--lg),
'lg-only': "(min-width: #{$--lg}) and (max-width: #{$--xl - 1})",
'lg-and-down': (max-width: $--xl - 1),
'xl-only' : (min-width: $--xl),
"xs-only": (
max-width: $--sm - 1,
),
"sm-and-up": (
min-width: $--sm,
),
"sm-only": "(min-width: #{$--sm}) and (max-width: #{$--md - 1})",
"sm-and-down": (
max-width: $--md - 1,
),
"md-and-up": (
min-width: $--md,
),
"md-only": "(min-width: #{$--md}) and (max-width: #{$--lg - 1})",
"md-and-down": (
max-width: $--lg - 1,
),
"lg-and-up": (
min-width: $--lg,
),
"lg-only": "(min-width: #{$--lg}) and (max-width: #{$--xl - 1})",
"lg-and-down": (
max-width: $--xl - 1,
),
"xl-only": (
min-width: $--xl,
),
);

View File

@ -85,3 +85,4 @@
@import "./descriptions.scss";
@import "./descriptions-item.scss";
@import "./result.scss";
@import "./tree-select.scss";

View File

@ -0,0 +1,165 @@
@import "mixins/mixins";
@import "common/var";
@include b(tree-select) {
display: inline-block;
position: relative;
width: 240px;
.el-tree-select__tags > span {
display: contents;
}
.el-input__inner {
cursor: pointer;
padding-right: 35px;
transition: none;
}
.el-input {
display: block;
& .el-tree-select__caret {
transition: transform 0.3s;
transform: rotateZ(180deg);
cursor: pointer;
@include when(reverse) {
transform: rotateZ(0deg);
}
}
& .el-icon-reload {
animation: spin 1s linear infinite;
}
&.is-disabled {
& .el-input__inner {
cursor: not-allowed;
}
}
}
@include e(input) {
border: none;
outline: none;
padding: 0;
margin-left: 15px;
color: $--tree-select-multiple-input-color;
font-size: $--tree-select-font-size;
appearance: none;
height: 28px;
background-color: transparent;
flex-grow: 1;
}
@include e(tags) {
position: absolute;
line-height: normal;
white-space: normal;
z-index: $--index-normal;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
flex-wrap: wrap;
}
.el-tag {
box-sizing: border-box;
border-color: transparent;
margin: 2px 0 2px 6px;
background-color: #f0f2f5;
display: flex;
max-width: 100%;
align-items: center;
&__close.el-icon-close {
background-color: $--color-text-placeholder;
top: 0;
color: $--color-white;
flex-shrink: 0;
&:hover {
background-color: $--color-text-secondary;
}
&::before {
display: block;
transform: translate(0, .5px);
}
}
}
}
@include b(tree-select-dropdown) {
position: absolute;
z-index: #{$--index-top + 1};
border-radius: $--border-radius-base;
border: $--tree-select-dropdown-border;
box-sizing: border-box;
background-color: $--tree-select-dropdown-background;
margin: 0 0 5px;
@include b(tree-select-dropdown__wrap) {
max-height: $--tree-select-dropdown-max-height;
}
@include b(tree-select-dropdown__list) {
list-style: none;
padding: $--tree-select-dropdown-padding;
margin: 0;
box-sizing: border-box;
.el-tree-node {
&:focus {
> .el-tree-node__content:not(.is-disabled) {
background: $--tree-select-option-selected-hover;
}
}
.el-tree-node__content {
transition: background-color 0.3s;
color: $--color-text-primary;
.el-tree-node__content-text {
font-size: $--tree-select-font-size;
}
&:hover:not(.is-disabled),
&:focus:not(.is-disabled) {
background: $--tree-select-option-selected-hover;
color: $--color-text-primary;
}
> .is-disabled {
cursor: not-allowed;
color: $--color-text-secondary;
}
> .is-selected {
color: $--color-primary;
}
}
}
}
@include b(tree-select-dropdown__empty) {
padding: $--tree-select-dropdown-empty-padding;
margin: 0;
text-align: center;
color: $--tree-select-dropdown-empty-color;
font-size: $--tree-select-font-size;
}
@include m(mini) {
@include b(tree-select-dropdown__wrap) {
max-height: $--tree-select-mini-dropdown-max-height;
}
}
@include m(small) {
@include b(tree-select-dropdown__wrap) {
max-height: $--tree-select-small-dropdown-max-height;
}
}
@include m(large) {
@include b(tree-select-dropdown__wrap) {
max-height: $--tree-select-large-dropdown-max-height;
}
}
}

View File

@ -0,0 +1,8 @@
import TreeSelect from './src/main';
/* istanbul ignore next */
TreeSelect.install = function(Vue) {
Vue.component(TreeSelect.name, TreeSelect);
};
export default TreeSelect;

View File

@ -0,0 +1,771 @@
<template>
<div
class="el-tree-select"
v-clickoutside="handleClose"
@click.stop="toggleMenu"
>
<div
class="el-tree-select__tags"
v-if="multiple"
ref="tags"
:style="{ 'max-width': inputWidth - 32 + 'px', width: '100%' }"
>
<span v-if="collapseTags && selected.length">
<el-tag
:closable="!selectDisabled"
:size="collapseTagSize"
:hit="selected[0].hitState"
type="info"
@close="deleteTag($event, selected[0])"
disable-transitions
>{{ selected[0].label }}</el-tag
>
<el-tag
v-if="selected.length > 1"
:closable="false"
:size="collapseTagSize"
type="info"
disable-transitions
>
+ {{ selected.length - 1 }}
</el-tag>
</span>
<transition-group
v-if="!collapseTags"
name="el-popper-in-center"
@after-leave="resetInputHeight"
>
<el-tag
v-for="item in selected"
:key="item.data.value"
:closable="!selectDisabled"
:size="collapseTagSize"
:hit="item.hitState"
type="info"
@close="deleteTag($event, item)"
disable-transitions
>
{{ item.data.label }}
</el-tag>
</transition-group>
<input
v-if="filterable"
v-model="query"
ref="input"
type="text"
class="el-tree-select__input"
:disabled="disabled"
@focus="handleFocus"
@blur="softFocus = false"
@keyup="managePlaceholder"
@keydown="resetInputState"
@keydown.down.prevent="handleNavigate('next')"
@keydown.up.prevent="handleNavigate('prev')"
@keydown.esc.stop.prevent="visible = false"
@keydown.delete="deletePrevTag"
@keydown.tab="visible = false"
@compositionstart="handleComposition"
@compositionupdate="handleComposition"
@compositionend="handleComposition"
@input="debouncedQueryChange"
:style="{
'flex-grow': '1',
width: inputLength / (inputWidth - 32) + '%',
'max-width': inputWidth - 42 + 'px',
}"
/>
</div>
<el-input
:class="{ 'is-focus': visible }"
ref="reference"
type="text"
v-model="selectedLabel"
:name="name"
:id="id"
:placeholder="currentPlaceholder"
:disabled="disabled"
:readonly="readonly"
:size="treeSelectSize"
:validate-event="false"
:tabindex="multiple && filterable ? '-1' : null"
@focus="handleFocus"
@blur="handleBlur"
@input="debouncedOnInputChange"
@keydown.native.down.stop.prevent="handleNavigate('next')"
@keydown.native.up.stop.prevent="handleNavigate('prev')"
@keydown.native.esc.stop.prevent="visible = false"
@keydown.native.tab="visible = false"
@compositionstart="handleComposition"
@compositionupdate="handleComposition"
@compositionend="handleComposition"
@mouseenter.native="inputHovering = true"
@mouseleave.native="inputHovering = false"
>
<template slot="prefix" v-if="$slots.prefix">
<slot name="prefix"></slot>
</template>
<template slot="suffix">
<i v-if="loading" class="el-tree-select__caret el-icon-reload"></i>
<template v-if="!loading">
<i v-show="!showClose" :class="['el-tree-select__caret', 'el-input__icon', 'el-icon-' + iconDirection]"></i>
<i v-if="showClose" class="el-tree-select__caret el-input__icon el-icon-circle-close" @click="handleClearClick"></i>
</template>
</template>
</el-input>
<transition name="el-zoom-in-top" @afterLeave="doDestroy">
<el-tree-select-dropdown
ref="popper"
:append-to-body="popperAppendToBody"
v-show="visible"
:class="[
treeSelectSize ? `el-tree-select-dropdown--${treeSelectSize}` : '',
]"
>
<el-scrollbar
tag="ul"
v-show="lazy || !loading"
wrap-class="el-tree-select-dropdown__wrap"
view-class="el-tree-select-dropdown__list"
ref="scrollbar"
:class="{
'is-empty': query && filteredOptionsCount === 0,
}"
>
<el-tree
ref="tree"
:data="data"
:node-key="nodeKey"
:expand-on-click-node="!lazy && (!checkStrictly || !showCheckbox)"
:check-strictly="checkStrictly"
:show-checkbox="showCheckbox"
:accordion="accordion"
:default-expanded-keys="defaultExpandedKeys"
:filter-node-method="filterMethod ? filterMethod : filterNodeMethod"
:props="props"
:lazy="lazy"
:load="load"
:emptyText="emptyText"
:renderAfterExpand="renderAfterExpand"
:defaultExpandAll="defaultExpandAll"
:autoExpandParent="autoExpandParent"
:indent="indent"
:iconClass="iconClass"
@node-click="handleNodeClick"
@check="handleNodeCheck"
>
<span
slot-scope="{ node, data }"
:class="{
'el-tree-node__content-text': true,
'is-selected': node.selected,
'is-disabled': node.disabled
}"
>
{{ data[props['label']] }}
</span>
</el-tree>
</el-scrollbar>
</el-tree-select-dropdown>
</transition>
</div>
</template>
<script type="text/babel">
import ElTag from 'element-ui/packages/tag';
import ElInput from 'element-ui/packages/input';
import ElTree from 'element-ui/packages/tree';
import ElScrollbar from 'element-ui/packages/scrollbar';
import ElTreeSelectDropdown from './tree-select-dropdown';
import Clickoutside from 'element-ui/src/utils/clickoutside';
import {
addResizeListener,
removeResizeListener
} from 'element-ui/src/utils/resize-event';
import Emitter from 'element-ui/src/mixins/emitter';
import Locale from 'element-ui/src/mixins/locale';
import debounce from 'throttle-debounce/debounce';
import { valueEquals } from 'element-ui/src/utils/util';
import { isKorean } from 'element-ui/src/utils/shared';
export default {
name: 'ElTreeSelect',
directives: { Clickoutside },
mixins: [Emitter, Locale],
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
},
components: { ElTag, ElInput, ElTree, ElScrollbar, ElTreeSelectDropdown },
props: {
name: String,
id: String,
nodeKey: {
type: [String, Number],
default: 'value'
},
value: {
required: true,
type: [String, Number, Array]
},
disabled: Boolean,
clearable: Boolean,
filterable: Boolean,
multiple: Boolean,
loading: Boolean,
collapseTags: Boolean,
showCheckbox: Boolean,
accordion: Boolean,
filterMethod: Function,
popperAppendToBody: {
type: Boolean,
default: true
},
size: {
type: String,
default: ''
},
placeholder: {
type: String,
required: false
},
checkStrictly: {
type: Boolean,
default: false
},
load: Function,
data: Array,
props: {
default() {
return {
value: 'value',
label: 'label',
children: 'children',
disabled: 'disabled'
};
}
},
lazy: {
type: Boolean,
default: false
},
emptyText: {
type: String
},
renderAfterExpand: {
type: Boolean,
default: false
},
defaultExpandAll: Boolean,
autoExpandParent: {
type: Boolean,
default: true
},
indent: {
type: Number,
default: 22
},
iconClass: String
},
data() {
return {
selectedLabel: '',
visible: false,
softFocus: false,
currentPlaceholder: '',
inputHovering: false,
selected: this.multiple ? [] : {},
query: '',
filteredOptionsCount: 0,
cachedPlaceHolder: '',
menuVisibleOnFocus: false,
defaultExpandedKeys: [],
isOnComposition: false,
isSilentBlur: false,
inputWidth: 0,
inputLength: 20
};
},
computed: {
selectDisabled() {
return this.disabled;
},
iconDirection() {
return this.visible ? 'arrow-up is-reverse' : 'arrow-up';
},
readonly() {
return !this.filterable || this.multiple || !this.visible;
},
treeSelectSize() {
return this.size || this._elFormItemSize || (this.elForm || {}).size;
},
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
showClose() {
const hasValue =
this.value !== undefined && this.value !== null && this.value !== '';
return this.clearable && this.inputHovering && hasValue && !this.disabled;
},
propPlaceholder() {
return typeof this.placeholder !== 'undefined'
? this.placeholder
: this.t('el.select.placeholder');
},
collapseTagSize() {
return ['small', 'mini'].indexOf(this.size) > -1
? 'mini'
: 'small';
}
},
watch: {
propPlaceholder(val) {
this.cachedPlaceHolder = this.currentPlaceholder = val;
},
value(val, oldVal) {
if (this.multiple) {
this.$nextTick(() => {
this.resetInputHeight();
this.broadcast('TuTreeSelectDropdown', 'updatePopper');
});
if (
(val && val.length > 0) ||
(this.$refs.input && this.query !== '')
) {
this.currentPlaceholder = '';
} else {
this.currentPlaceholder = this.cachedPlaceHolder;
}
}
this.query = '';
this.handleQueryChange(this.query);
this.setOldSelected(oldVal);
this.setSelected();
if (!valueEquals(val, oldVal)) {
this.dispatch('ElFormItem', 'el.form.change', val);
}
},
visible(val, oldVal) {
if (!val) {
this.broadcast('ElTreeSelectDropdown', 'destroyPopper');
if (this.$refs.input) {
this.$refs.input.blur();
}
this.query = '';
this.inputLength = 20;
this.menuVisibleOnFocus = false;
} else {
if (this.multiple) {
this.resetInputHeight();
} else {
}
this.$nextTick(() => {
this.broadcast('ElTreeSelectDropdown', 'updatePopper');
});
}
this.$emit('visible-change', val);
}
},
created() {
this.cachedPlaceHolder = this.currentPlaceholder = this.propPlaceholder;
if (this.multiple && !Array.isArray(this.value)) {
this.$emit('input', []);
}
if (!this.multiple && Array.isArray(this.value)) {
this.$emit('input', '');
}
this.debouncedOnInputChange = debounce(this.debounce, () => {
this.onInputChange();
});
this.debouncedQueryChange = debounce(this.debounce, (e) => {
this.handleQueryChange(e.target.value);
});
},
mounted() {
if (this.multiple && Array.isArray(this.value) && this.value.length > 0) {
this.currentPlaceholder = '';
}
addResizeListener(this.$el, this.handleResize);
const reference = this.$refs.reference;
if (reference && reference.$el) {
const sizeMap = {
medium: 36,
small: 32,
mini: 28
};
const input = reference.$el.querySelector('input');
this.initialInputHeight =
input.getBoundingClientRect().height || sizeMap[this.treeSelectSize];
}
addResizeListener(this.$el, this.handleResize);
this.$nextTick(() => {
if (reference && reference.$el) {
this.inputWidth = reference.$el.getBoundingClientRect().width;
}
});
this.setSelected();
},
beforeDestroy() {
if (this.$el && this.handleResize) {
removeResizeListener(this.$el, this.handleResize);
}
},
methods: {
handleClose() {
this.visible = false;
},
toggleMenu() {
if (!this.selectDisabled) {
if (this.menuVisibleOnFocus) {
this.menuVisibleOnFocus = false;
} else {
this.visible = !this.visible;
}
if (this.visible) {
(this.$refs.input || this.$refs.reference).focus();
}
}
},
setSoftFocus() {
this.softFocus = true;
const input = this.$refs.input || this.$refs.reference;
if (input) {
input.focus();
}
},
handleFocus(event) {
if (!this.softFocus) {
if (this.filterable) {
if (this.filterable && !this.visible) {
this.menuVisibleOnFocus = true;
}
this.visible = true;
}
this.$emit('focus', event);
} else {
this.softFocus = false;
}
},
blur() {
this.visible = false;
this.$refs.reference.blur();
},
handleBlur(event) {
setTimeout(() => {
if (this.isSilentBlur) {
this.isSilentBlur = false;
} else {
this.$emit('blur', event);
}
}, 50);
this.softFocus = false;
},
handleNavigate(direction) {
if (this.isOnComposition) return;
const tree = this.$refs.tree;
tree.initFocusedItem(direction);
},
handleComposition(event) {
const text = event.target.value;
if (event.type === 'compositionend') {
this.isOnComposition = false;
this.$nextTick((_) => this.handleQueryChange(text));
} else {
const lastCharacter = text[text.length - 1] || '';
this.isOnComposition = !isKorean(lastCharacter);
}
},
onInputChange() {
if (this.filterable && this.query !== this.selectedLabel) {
this.query = this.selectedLabel;
this.handleQueryChange(this.query);
}
},
handleClearClick(event) {
event.stopPropagation();
const value = this.multiple ? [] : '';
this.$emit('input', value);
this.emitChange(value);
this.visible = false;
this.$emit('clear');
},
deleteTag(event, node) {
event.stopPropagation();
this.handleMultipSelect(node.data, node);
this.$emit('remove-tag', node);
},
managePlaceholder() {
if (this.currentPlaceholder !== '') {
this.currentPlaceholder = this.$refs.input.value
? ''
: this.cachedPlaceHolder;
}
},
resetInputState(e) {
if (e.keyCode !== 8) this.toggleLastOptionHitState(false);
this.inputLength = this.$refs.input.value.length * 15 + 20;
this.resetInputHeight();
},
toggleLastOptionHitState(hit) {
if (!Array.isArray(this.selected)) return;
const option = this.selected[this.selected.length - 1];
if (!option) return;
if (hit === true || hit === false) {
option.hitState = hit;
return hit;
}
option.hitState = !option.hitState;
return option.hitState;
},
deletePrevTag(e) {
if (e.target.value.length <= 0 && !this.toggleLastOptionHitState()) {
const value = this.value.slice();
value.pop();
this.$emit('input', value);
this.emitChange(value);
}
},
handleQueryChange(val) {
if (this.isOnComposition) return;
const tree = this.$refs.tree;
tree.filter(val);
},
filterNodeMethod(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
handleResize() {
this.resetInputWidth();
if (this.multiple) this.resetInputHeight();
},
doDestroy() {
this.$refs.popper && this.$refs.popper.doDestroy();
},
resetInputWidth() {
this.inputWidth =
this.$refs.reference &&
this.$refs.reference.$el.getBoundingClientRect().width;
},
resetInputHeight() {
if (this.collapseTags && !this.filterable) return;
this.$nextTick(() => {
if (!this.$refs.reference) return;
let inputChildNodes = this.$refs.reference.$el.childNodes;
let input = [].filter.call(
inputChildNodes,
(item) => item.tagName === 'INPUT'
)[0];
const tags = this.$refs.tags;
const tagsHeight = tags
? Math.round(tags.getBoundingClientRect().height)
: 0;
const sizeInMap = this.initialInputHeight || 40;
input.style.height =
this.selected.length === 0
? sizeInMap + 'px'
: Math.max(
tags ? tagsHeight + (tagsHeight > sizeInMap ? 6 : 0) : 0,
sizeInMap
) + 'px';
if (this.visible && this.emptyText !== false) {
this.broadcast('ElTreeSelectDropdown', 'updatePopper');
}
});
},
emitChange(val) {
if (!valueEquals(this.value, val)) {
this.$emit('change', val);
}
},
setSelected() {
const tree = this.$refs.tree;
if (this.multiple) {
const selected = [];
if (Array.isArray(this.value)) {
if (this.showCheckbox) {
tree.setCheckedKeys(this.value);
}
this.value.forEach((val) => {
const node = tree.getNode(val);
if (node) {
this.$set(node, 'selected', true);
this.$set(node, 'hitState', false);
selected.push(node);
}
});
}
this.selected = selected;
} else {
const node = tree.getNode(this.value);
if (node) {
if (this.showCheckbox) {
tree.setCheckedKeys([this.value]);
}
this.$set(node, 'selected', true);
this.selected = node;
this.selectedLabel = node.label;
} else {
this.selected = {};
this.selectedLabel = '';
if (this.showCheckbox) {
tree.setCheckedKeys([]);
}
}
}
},
setOldSelected(oldVal) {
const tree = this.$refs.tree;
if (this.multiple) {
if (Array.isArray(oldVal)) {
oldVal.forEach((val) => {
const node = tree.getNode(val);
if (node) {
this.$set(node, 'selected', false);
}
});
}
} else {
const oldNode = tree.getNode(oldVal);
if (oldNode) {
this.$set(oldNode, 'selected', false);
}
}
},
findNode(tree, func) {
for (const node of tree.childNodes) {
if (node.data && node.disabled) break;
if (func(node)) return node;
if (node.childNodes && node.childNodes.length) {
const res = this.findNode(node, func);
if (res) return res;
}
}
return null;
},
handleNodeCheck(data, state, node) {
const { label } = this.props;
if (this.multiple) {
this.$emit('input', state.checkedKeys);
this.emitChange(state.checkedKeys);
} else {
let currentValue = '';
let currentLabel = '';
let currentNode = '';
currentNode = this.checkStrictly
? node
: this.findNode(node, (node) => node.isLeaf) || node;
const isCheckedNode = this.checkStrictly
? currentNode.checked
: currentNode.checked &&
!this.findNode(
node,
(node) => node.data[this.nodeKey] === this.value
);
if (isCheckedNode) {
currentLabel = currentNode.data[label];
currentValue = currentNode.data[this.nodeKey];
this.$set(currentNode, 'selected', true);
}
this.selectedLabel = currentLabel;
this.$emit('input', currentValue);
this.emitChange(currentValue);
}
this.setSoftFocus();
},
handleNodeClick(data, node) {
if (this.showCheckbox || node.disabled) return;
if (!this.checkStrictly && node.childNodes.length !== 0) return;
if (this.multiple) {
this.handleMultipSelect(data, node);
} else {
this.handleSingleSelect(data, node);
}
this.setSoftFocus();
},
handleMultipSelect(data, node) {
const currentValue = (this.value || []).slice();
const selected = (this.selected || []).slice();
const index = currentValue.indexOf(data[this.nodeKey]);
if (index === -1) {
selected.push(node);
currentValue.push(data[this.nodeKey]);
this.$set(node, 'selected', true);
} else {
selected.splice(index, 1);
currentValue.splice(index, 1);
this.$set(node, 'selected', false);
}
this.selected = selected;
this.$emit('input', currentValue);
},
handleSingleSelect(data, node) {
if (node.selected) return;
const { label } = this.props;
this.selectedLabel = data[label];
this.$set(node, 'selected', true);
this.$emit('input', data[this.nodeKey]);
this.emitChange(data[this.nodeKey]);
this.visible = false;
}
}
};
</script>

View File

@ -0,0 +1,76 @@
<template>
<div
ref="dropdown"
class="el-tree-select-dropdown el-popper"
:class="[{ 'is-multiple': $parent.multiple }, popperClass]"
:style="{ minWidth: minWidth }"
>
<slot></slot>
</div>
</template>
<script>
import Popper from 'element-ui/src/utils/vue-popper';
export default {
name: 'ElTreeSelectDropdown',
componentName: 'ElTreeSelectDropdown',
mixins: [Popper],
props: {
placement: {
default: 'bottom-start'
},
boundariesPadding: {
default: 0
},
popperOptions: {
default() {
return {
gpuAcceleration: false
};
}
},
visibleArrow: {
default: true
},
appendToBody: {
type: Boolean,
default: true
}
},
data() {
return {
minWidth: ''
};
},
computed: {
popperClass() {
return this.$parent.popperClass;
}
},
watch: {
'$parent.inputWidth'() {
this.minWidth = this.$parent.$el.getBoundingClientRect().width + 'px';
}
},
mounted() {
this.referenceElm = this.$parent.$refs.reference.$el;
this.$parent.popperElm = this.popperElm = this.$el;
this.$on('updatePopper', () => {
if (this.$parent.visible) this.updatePopper();
});
this.$on('destroyPopper', this.destroyPopper);
}
};
</script>

View File

@ -211,7 +211,7 @@
checkedKeys: store.getCheckedKeys(),
halfCheckedNodes: store.getHalfCheckedNodes(),
halfCheckedKeys: store.getHalfCheckedKeys(),
});
}, this.node);
});
},

View File

@ -291,6 +291,16 @@
this.treeItems[0] && this.treeItems[0].setAttribute('tabindex', 0);
},
initFocusedItem(direction) {
if (!this.treeItemArray.length) return;
if (direction === 'next') {
this.treeItemArray[0].focus();
}
if (direction === 'prev') {
this.treeItemArray[this.treeItemArray.length - 1].focus();
}
},
handleKeydown(ev) {
const currentItem = ev.target;
if (currentItem.className.indexOf('el-tree-node') === -1) return;

View File

@ -89,6 +89,7 @@ import Empty from '../packages/empty/index.js';
import Descriptions from '../packages/descriptions/index.js';
import DescriptionsItem from '../packages/descriptions-item/index.js';
import Result from '../packages/result/index.js';
import TreeSelect from '../packages/tree-select/index.js';
import locale from 'element-ui/src/locale';
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
@ -177,6 +178,7 @@ const components = [
Descriptions,
DescriptionsItem,
Result,
TreeSelect,
CollapseTransition
];
@ -305,5 +307,6 @@ export default {
Empty,
Descriptions,
DescriptionsItem,
Result
Result,
TreeSelect
};

View File

@ -0,0 +1,95 @@
import { createTest, createVue, destroyVM } from '../util';
import TreeSelect from 'packages/tree-select';
const data = [
{
value: 1,
label: '一级 1',
children: [
{
value: 11,
label: '二级 1-1',
children: [
{
value: 111,
label: '三级 1-1'
}
]
}
]
}
];
describe('TreeSelect', () => {
const getTreeSelectVm = (configs = {}, options) => {
['multiple', 'checkStrictly', 'showCheckbox'].forEach(config => {
configs[config] = configs[config] || false;
});
const vm = createVue(Object.assign({
template: `
<el-tree-select
v-model="value"
:data="data"
:multiple="multiple"
:checkStrictly="checkStrictly"
:showCheckbox="showCheckbox"
/>
`,
data() {
return {
data,
value: '',
multiple: configs.multiple,
checkStrictly: configs.checkStrictly,
showCheckbox: configs.showCheckbox
};
}
}, options), true);
return vm;
};
let vm;
afterEach(() => {
destroyVM(vm);
});
it('create', () => {
vm = createTest(TreeSelect, true);
expect(vm.$el).to.exist;
});
it('render dropdown', async() => {
vm = getTreeSelectVm();
expect(vm.$el.querySelector('.el-tree')).to.exist;
expect(vm.$el.querySelectorAll('.el-tree > .el-tree-node').length).to.equal(1);
expect(vm.$el.querySelectorAll('.el-tree .el-tree-node').length).to.equal(3);
});
it('default value', done => {
vm = createVue({
template: `
<div>
<el-tree-select
v-model="value"
:data="data"
/>
</div>
`,
data() {
return {
data: data,
value: 111
};
}
}, true);
setTimeout(() => {
expect(vm.$el.querySelector('.el-input__inner').value).to.equal('三级 1-1');
done();
}, 100);
});
it('disabled select', () => {
vm = createTest(TreeSelect, { disabled: true, value: '' }, true);
expect(vm.$el.querySelector('.el-input').classList.contains('is-disabled')).to.true;
});
});

View File

@ -89,7 +89,8 @@ import { ElDescriptions } from './descriptions'
import { ElDescriptionsItem } from './descriptions-item'
import { ElResult } from './result'
import { ElStatistic } from './statistic'
import { ElTreeSelect } from './tree-select'
export interface InstallationOptions {
locale: any,
i18n: any,
@ -379,4 +380,6 @@ export class DescriptionsItem extends ElDescriptionsItem {}
export class Result extends ElResult {}
/** Statistic Component */
export class Statistic extends ElStatistic {}
export class Statistic extends ElStatistic {}
/** TreeSelect Component */
export class TreeSelect extends ElTreeSelect {}

111
types/tree-select.d.ts vendored Normal file
View File

@ -0,0 +1,111 @@
import { ElementUIComponent, ElementUIComponentSize } from "./component";
export interface TreeData {
value?: any;
label?: string;
disabled?: boolean;
isLeaf?: boolean;
children?: TreeData[];
}
export interface TreeProps {
label: string;
disabled: string;
isLeaf: string;
children: string;
}
export interface QueryChangeHandler {
/**
* @param queryString Current value of the text input
*/
(queryString: string): void;
}
/** TreeSelect Component */
export declare class ElTreeSelect<V = any, D = TreeData> extends ElementUIComponent {
/** The form input value */
value: V | V[];
/** Tree data */
data: D[];
/** Whether multiple-select is activated */
multiple: boolean;
/** Whether Select is disabled */
disabled: boolean;
/** Unique identity key name for value, required when value is an object */
nodeKey: string;
/** Configuration options, see the following table */
props: TreeProps;
/** Size of Input */
size: ElementUIComponentSize;
/** Whether single select can be cleared */
clearable: boolean;
/** The name attribute of select input */
name: string;
/** Placeholder */
placeholder: string;
/** Whether Select is filterable */
filterable: boolean;
/** Custom filter method */
filterMethod: QueryChangeHandler;
/** Whether to append the popper menu to body */
popperAppendToBody: boolean;
/** Method for loading subtree data */
load: (data: D, resolve: Function) => void;
/** Displayed text when data is empty */
emptyText: string;
/** Horizontal indentation of nodes in adjacent levels in pixels */
indent: number;
/** Whether checked state of a node not affects its father and child nodes when show-checkbox is true */
checkStrictly: boolean;
/** Whether only one node among the same level can be expanded at one time */
accordion: boolean;
/** Whether node is selectable */
showCheckbox: boolean;
/**whether to collapse tags to a text when multiple selecting */
collapseTags: boolean;
/** Whether Select is loading data from server */
loading: boolean;
/** Method for loading subtree data */
load: (data: D, resolve: Function) => void;
/** whether to lazy load leaf node, used with load attribute */
lazy: boolean;
/** Whether to expand all nodes by default */
defaultExpandAll: boolean;
/** Whether to expand father node when a child node is expanded */
autoExpandParent: boolean;
/**
* Focus the Input component
*/
focus(): void;
/**
* Blur the Input component, and hide the dropdown
*/
blur(): void;
}