feat: add tree-select
parent
c0fb36468f
commit
7cd4f39756
|
@ -117,6 +117,16 @@ const getComponentFromProp = (instance, prop) => {
|
|||
}
|
||||
}
|
||||
|
||||
const getAllProps = (ele) => {
|
||||
let data = ele.data || {}
|
||||
let componentOptions = ele.componentOptions || {}
|
||||
if (ele.$vnode) {
|
||||
data = ele.$vnode.data || {}
|
||||
componentOptions = ele.$vnode.componentOptions || {}
|
||||
}
|
||||
return { ...data.props, ...data.attrs, ...componentOptions.propsData }
|
||||
}
|
||||
|
||||
const getPropsData = (ele) => {
|
||||
let componentOptions = ele.componentOptions
|
||||
if (ele.$vnode) {
|
||||
|
@ -247,5 +257,6 @@ export {
|
|||
isValidElement,
|
||||
camelize,
|
||||
getSlots,
|
||||
getAllProps,
|
||||
}
|
||||
export default hasProp
|
||||
|
|
|
@ -21,7 +21,7 @@ import { getComponentFromProp, getOptionProps, filterEmpty, isValidElement } fro
|
|||
// }
|
||||
|
||||
const AutoCompleteProps = {
|
||||
...AbstractSelectProps,
|
||||
...AbstractSelectProps(),
|
||||
value: SelectValue,
|
||||
defaultValue: SelectValue,
|
||||
dataSource: PropTypes.array,
|
||||
|
|
|
@ -102,7 +102,7 @@ import { default as Transfer } from './transfer'
|
|||
|
||||
import { default as Tree } from './tree'
|
||||
|
||||
// import { default as TreeSelect } from './tree-select'
|
||||
import { default as TreeSelect } from './tree-select'
|
||||
|
||||
import { default as Tabs } from './tabs'
|
||||
|
||||
|
@ -196,6 +196,8 @@ const components = [
|
|||
Transfer,
|
||||
Tree,
|
||||
Tree.TreeNode,
|
||||
TreeSelect,
|
||||
TreeSelect.TreeNode,
|
||||
Tabs,
|
||||
Tabs.TabPane,
|
||||
Tag,
|
||||
|
@ -269,6 +271,7 @@ export {
|
|||
Table,
|
||||
Transfer,
|
||||
Tree,
|
||||
TreeSelect,
|
||||
Tabs,
|
||||
Tag,
|
||||
TimePicker,
|
||||
|
|
|
@ -5,7 +5,7 @@ import LocaleReceiver from '../locale-provider/LocaleReceiver'
|
|||
import defaultLocale from '../locale-provider/default'
|
||||
import { getComponentFromProp, getOptionProps, filterEmpty } from '../_util/props-util'
|
||||
|
||||
const AbstractSelectProps = {
|
||||
const AbstractSelectProps = () => ({
|
||||
prefixCls: PropTypes.string,
|
||||
size: PropTypes.oneOf(['small', 'large', 'default']),
|
||||
notFoundContent: PropTypes.any,
|
||||
|
@ -28,7 +28,7 @@ const AbstractSelectProps = {
|
|||
autoFocus: PropTypes.bool,
|
||||
backfill: PropTypes.bool,
|
||||
showArrow: PropTypes.bool,
|
||||
}
|
||||
})
|
||||
const Value = PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
}).loose
|
||||
|
@ -45,7 +45,7 @@ const SelectValue = PropTypes.oneOfType([
|
|||
])
|
||||
|
||||
const SelectProps = {
|
||||
...AbstractSelectProps,
|
||||
...AbstractSelectProps(),
|
||||
value: SelectValue,
|
||||
defaultValue: SelectValue,
|
||||
mode: PropTypes.oneOf(['default', 'multiple', 'tags', 'combobox']),
|
||||
|
|
|
@ -46,3 +46,4 @@ import './layout/style'
|
|||
import './form/style'
|
||||
import './anchor/style'
|
||||
import './list/style'
|
||||
import './tree-select/style'
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders ./components/tree-select/demo/basic.md correctly 1`] = `
|
||||
<span class="ant-select ant-select-enabled ant-select-allow-clear ant-select ant-select-enabled ant-select-allow-clear" style="width: 300px;"><span role="combobox" aria-autocomplete="list" aria-haspopup="true" tabindex="0" class="ant-select-selection
|
||||
ant-select-selection--single"><span class="ant-select-selection__rendered"><span class="ant-select-selection__placeholder">Please select</span></span><span class="ant-select-arrow" style="outline: none;"><b></b></span></span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/tree-select/demo/checkable.md correctly 1`] = `
|
||||
<span class="ant-select ant-select-enabled ant-select ant-select-enabled" style="width: 300px;"><span role="combobox" aria-autocomplete="list" aria-haspopup="true" class="ant-select-selection
|
||||
ant-select-selection--multiple"><div class="ant-select-selection__rendered"><li title="Node1" unselectable="unselectable" class="ant-select-selection__choice"><span class="ant-select-selection__choice__remove"></span><span class="ant-select-selection__choice__content">Node1</span></li>
|
||||
<li
|
||||
class="ant-select-search ant-select-search--inline"><span class="ant-select-search__field__wrap"><input role="textbox" class="ant-select-search__field"><span class="ant-select-search__field__mirror"> </span></span>
|
||||
</li>
|
||||
</div><span class="ant-select-search__field__placeholder" style="display: none;">Please select</span></span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/tree-select/demo/multiple.md correctly 1`] = `
|
||||
<span class="ant-select ant-select-enabled ant-select-allow-clear ant-select ant-select-enabled ant-select-allow-clear" style="width: 300px;"><span role="combobox" aria-autocomplete="list" aria-haspopup="true" class="ant-select-selection
|
||||
ant-select-selection--multiple"><div class="ant-select-selection__rendered"><li class="ant-select-search ant-select-search--inline"><span class="ant-select-search__field__wrap"><input role="textbox" class="ant-select-search__field"><span class="ant-select-search__field__mirror"> </span></span>
|
||||
</li>
|
||||
</div><span class="ant-select-search__field__placeholder" style="display: block;">Please select</span></span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/tree-select/demo/treeData.md correctly 1`] = `
|
||||
<span class="ant-select ant-select-enabled ant-select ant-select-enabled" style="width: 300px;"><span role="combobox" aria-autocomplete="list" aria-haspopup="true" tabindex="0" class="ant-select-selection
|
||||
ant-select-selection--single"><span class="ant-select-selection__rendered"><span class="ant-select-selection__placeholder">Please select</span></span><span class="ant-select-arrow" style="outline: none;"><b></b></span></span>
|
||||
</span>
|
||||
`;
|
|
@ -0,0 +1,3 @@
|
|||
import demoTest from '../../../tests/shared/demoTest'
|
||||
|
||||
demoTest('tree-select')
|
|
@ -0,0 +1,6 @@
|
|||
import TreeSelect from '..'
|
||||
import focusTest from '../../../tests/shared/focusTest'
|
||||
|
||||
describe('TreeSelect', () => {
|
||||
focusTest(TreeSelect)
|
||||
})
|
|
@ -0,0 +1,53 @@
|
|||
<cn>
|
||||
#### 基本用法
|
||||
最简单的用法。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Basic
|
||||
The most basic usage.
|
||||
</us>
|
||||
|
||||
```html
|
||||
<template>
|
||||
<a-tree-select
|
||||
showSearch
|
||||
style="width: 300px"
|
||||
:value="value"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
placeholder='Please select'
|
||||
allowClear
|
||||
treeDefaultExpandAll
|
||||
@change="onChange"
|
||||
>
|
||||
<a-tree-select-node value='parent 1' title='parent 1' key='0-1'>
|
||||
<a-tree-select-node value='parent 1-0' title='parent 1-0' key='0-1-1'>
|
||||
<a-tree-select-node value='leaf1' title='my leaf' key='random' />
|
||||
<a-tree-select-node value='leaf2' title='your leaf' key='random1' />
|
||||
</a-tree-select-node>
|
||||
<a-tree-select-node value='parent 1-1' title='parent 1-1' key='random2'>
|
||||
<a-tree-select-node value='sss' key='random3'>
|
||||
<b style="color: #08c" slot="title">sss</b>
|
||||
</a-tree-select-node>
|
||||
</a-tree-select-node>
|
||||
</a-tree-select-node>
|
||||
</a-tree-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
value: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (value) {
|
||||
console.log(arguments)
|
||||
this.value = value
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
|
@ -0,0 +1,73 @@
|
|||
<cn>
|
||||
#### 可勾选
|
||||
使用勾选框实现多选功能。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Checkable
|
||||
Multiple and checkable.
|
||||
</us>
|
||||
|
||||
```html
|
||||
<template>
|
||||
<a-tree-select
|
||||
style="width: 300px"
|
||||
:treeData="treeData"
|
||||
:value="value"
|
||||
@change="onChange"
|
||||
treeCheckable
|
||||
:showCheckedStrategy="SHOW_PARENT"
|
||||
searchPlaceholder='Please select'
|
||||
treeNodeFilterProp='label'
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { TreeSelect } from 'vue-antd-ui'
|
||||
const SHOW_PARENT = TreeSelect.SHOW_PARENT
|
||||
|
||||
const treeData = [{
|
||||
label: 'Node1',
|
||||
value: '0-0',
|
||||
key: '0-0',
|
||||
children: [{
|
||||
label: 'Child Node1',
|
||||
value: '0-0-0',
|
||||
key: '0-0-0',
|
||||
}],
|
||||
}, {
|
||||
label: 'Node2',
|
||||
value: '0-1',
|
||||
key: '0-1',
|
||||
children: [{
|
||||
label: 'Child Node3',
|
||||
value: '0-1-0',
|
||||
key: '0-1-0',
|
||||
disabled: true,
|
||||
}, {
|
||||
label: 'Child Node4',
|
||||
value: '0-1-1',
|
||||
key: '0-1-1',
|
||||
}, {
|
||||
label: 'Child Node5',
|
||||
value: '0-1-2',
|
||||
key: '0-1-2',
|
||||
}],
|
||||
}]
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
value: ['0-0-0'],
|
||||
treeData,
|
||||
SHOW_PARENT,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (value) {
|
||||
console.log('onChange ', value, arguments)
|
||||
this.value = value
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
|
@ -0,0 +1,48 @@
|
|||
<script>
|
||||
import Basic from './basic'
|
||||
import Checkable from './checkable'
|
||||
import Multiple from './multiple'
|
||||
import TreeData from './treeData'
|
||||
|
||||
import CN from '../index.zh-CN.md'
|
||||
import US from '../index.en-US.md'
|
||||
|
||||
const md = {
|
||||
cn: `# 树型选择控件。
|
||||
## 何时使用
|
||||
|
||||
类似 Select 的选择控件,可选择的数据结构是一个树形结构时,可以使用 TreeSelect,例如公司层级、学科系统、分类目录等等。
|
||||
|
||||
## 代码演示`,
|
||||
us: `# TreeSelect
|
||||
## When To Use
|
||||
|
||||
\`TreeSelect\` is similar to \`Select\`, but the values are provided in a tree like structure.
|
||||
Any data whose entries are defined in a hierarchical manner is fit to use this control. Examples of such case may include a corporate hierarchy, a directory structure, and so on.
|
||||
## Examples
|
||||
`,
|
||||
}
|
||||
export default {
|
||||
category: 'Components',
|
||||
subtitle: '树选择',
|
||||
type: 'Data Entry',
|
||||
title: 'TreeSelect',
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<md cn={md.cn} us={md.us}/>
|
||||
<Basic/>
|
||||
<Checkable/>
|
||||
<Multiple/>
|
||||
<TreeData/>
|
||||
<api>
|
||||
<template slot='cn'>
|
||||
<CN/>
|
||||
</template>
|
||||
<US/>
|
||||
</api>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,62 @@
|
|||
<cn>
|
||||
#### 多选
|
||||
多选的树选择。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Multiple Selection
|
||||
Multiple selection usage.
|
||||
</us>
|
||||
|
||||
```html
|
||||
<template>
|
||||
<a-tree-select
|
||||
showSearch
|
||||
style="width: 300px"
|
||||
:value="value"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
placeholder='Please select'
|
||||
allowClear
|
||||
multiple
|
||||
treeDefaultExpandAll
|
||||
@change="onChange"
|
||||
@search="onSearch"
|
||||
@select="onSelect"
|
||||
>
|
||||
<a-tree-select-node value='parent 1' title='parent 1' key='0-1'>
|
||||
<a-tree-select-node value='parent 1-0' title='parent 1-0' key='0-1-1'>
|
||||
<a-tree-select-node value='leaf1' title='my leaf' key='random' />
|
||||
<a-tree-select-node value='leaf2' title='your leaf' key='random1' />
|
||||
</a-tree-select-node>
|
||||
<a-tree-select-node value='parent 1-1' title='parent 1-1' key='random2'>
|
||||
<a-tree-select-node value='sss' key='random3'>
|
||||
<b style="color: #08c" slot="title">sss</b>
|
||||
</a-tree-select-node>
|
||||
</a-tree-select-node>
|
||||
</a-tree-select-node>
|
||||
</a-tree-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
value: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (value) {
|
||||
console.log(arguments)
|
||||
this.value = value
|
||||
},
|
||||
onSearch () {
|
||||
console.log(arguments)
|
||||
},
|
||||
onSelect () {
|
||||
console.log(arguments)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
|
@ -0,0 +1,63 @@
|
|||
<cn>
|
||||
#### 从数据直接生成
|
||||
使用 `treeData` 把 JSON 数据直接生成树结构。
|
||||
</cn>
|
||||
|
||||
<us>
|
||||
#### Generate form tree data
|
||||
The tree structure can be populated using `treeData` property. This is a quick and easy way to provide the tree content.
|
||||
</us>
|
||||
|
||||
```html
|
||||
<template>
|
||||
<a-tree-select
|
||||
style="width: 300px"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:treeData="treeData"
|
||||
placeholder='Please select'
|
||||
treeDefaultExpandAll
|
||||
labelInValue
|
||||
v-model="value"
|
||||
>
|
||||
<span style="color: #08c" slot="label" slot-scope="{key, value}" v-if="key='0-0-1'">
|
||||
<a-icon type="home"/>Child Node1 {{value}}
|
||||
</span>
|
||||
</a-tree-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const treeData = [{
|
||||
label: 'Node1',
|
||||
value: '0-0',
|
||||
key: '0-0',
|
||||
children: [{
|
||||
value: '0-0-1',
|
||||
key: '0-0-1',
|
||||
scopedSlots: { // custom label
|
||||
label: 'label',
|
||||
},
|
||||
}, {
|
||||
label: 'Child Node2',
|
||||
value: '0-0-2',
|
||||
key: '0-0-2',
|
||||
}],
|
||||
}, {
|
||||
label: 'Node2',
|
||||
value: '0-1',
|
||||
key: '0-1',
|
||||
}]
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
value: undefined,
|
||||
treeData,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (value) {
|
||||
console.log(value)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
|
@ -0,0 +1,61 @@
|
|||
|
||||
## API
|
||||
|
||||
### Tree props
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| -------- | ----------- | ---- | ------- |
|
||||
| allowClear | Whether allow clear | boolean | false |
|
||||
| defaultValue | To set the initial selected treeNode(s). | string\|string\[] | - |
|
||||
| disabled | Disabled or not | boolean | false |
|
||||
| dropdownClassName | className of dropdown menu | string | - |
|
||||
| dropdownMatchSelectWidth | Determine whether the dropdown menu and the select input are the same width | boolean | true |
|
||||
| dropdownStyle | To set the style of the dropdown menu | object | - |
|
||||
| filterTreeNode | Whether to filter treeNodes by input value. The value of `treeNodeFilterProp` is used for filtering by default. | boolean\|Function(inputValue: string, treeNode: TreeNode) (should return boolean) | Function |
|
||||
| getPopupContainer | To set the container of the dropdown menu. The default is to create a `div` element in `body`, you can reset it to the scrolling area and make a relative reposition. [example](https://codepen.io/afc163/pen/zEjNOy?editors=0010) | Function(triggerNode) | () => document.body |
|
||||
| labelInValue | whether to embed label in value, turn the format of value from `string` to `{value: string, label: VNode, halfChecked: string[]}` | boolean | false |
|
||||
| loadData | Load data asynchronously. | function(node) | - |
|
||||
| multiple | Support multiple or not, will be `true` when enable `treeCheckable`. | boolean | false |
|
||||
| placeholder | Placeholder of the select input | string\|slot | - |
|
||||
| searchPlaceholder | Placeholder of the search input | string\|slot | - |
|
||||
| showCheckedStrategy | The way show selected item in box. **Default:** just show child nodes. **`TreeSelect.SHOW_ALL`:** show all checked treeNodes (include parent treeNode). **`TreeSelect.SHOW_PARENT`:** show checked treeNodes (just show parent treeNode). | enum { TreeSelect.SHOW_ALL, TreeSelect.SHOW_PARENT, TreeSelect.SHOW_CHILD } | TreeSelect.SHOW_CHILD |
|
||||
| showSearch | Whether to display a search input in the dropdown menu(valid only in the single mode) | boolean | false |
|
||||
| size | To set the size of the select input, options: `large` `small` | string | 'default' |
|
||||
| treeCheckable | Whether to show checkbox on the treeNodes | boolean | false |
|
||||
| treeCheckStrictly | Whether to check nodes precisely (in the `checkable` mode), means parent and child nodes are not associated, and it will make `labelInValue` be true | boolean | false |
|
||||
| treeData | Data of the treeNodes, manual construction work is no longer needed if this property has been set(ensure the Uniqueness of each value) | array<{ value, label, children, [disabled, disableCheckbox, selectable] }> | \[] |
|
||||
| treeDataSimpleMode | Enable simple mode of treeData.(treeData should like this: [{id:1, pId:0, value:'1', label:"test1",...},...], pId is parent node's id) | false\|Array<{ id: string, pId: string, rootPId: null }> | false |
|
||||
| treeDefaultExpandAll | Whether to expand all treeNodes by default | boolean | false |
|
||||
| treeDefaultExpandedKeys | Default expanded treeNodes | string\[] | - |
|
||||
| treeNodeFilterProp | Will be used for filtering if `filterTreeNode` returns true | string | 'value' |
|
||||
| treeNodeLabelProp | Will render as content of select | string | 'title' |
|
||||
| value(v-model) | To set the current selected treeNode(s). | string\|string\[] | - |
|
||||
|
||||
|
||||
### Events
|
||||
| Events Name | Description | Arguments |
|
||||
| --- | --- | --- |
|
||||
| change | A callback function, can be executed when selected treeNodes or input value change | function(value, label, extra) |
|
||||
| search | A callback function, can be executed when the search input changes. | function(value: string) |
|
||||
| select | A callback function, can be executed when you select a treeNode. | function(value, node, extra) |
|
||||
|
||||
### Tree Methods
|
||||
|
||||
| Name | Description |
|
||||
| ---- | ----------- |
|
||||
| blur() | remove focus |
|
||||
| focus() | get focus |
|
||||
|
||||
### TreeNode props
|
||||
|
||||
> We recommend you to use `treeData` rather than `TreeNode`, to avoid the trouble of manual construction.
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
| -------- | ----------- | ---- | ------- |
|
||||
| disableCheckbox | Disables the checkbox of the treeNode | boolean | false |
|
||||
| disabled | Disabled or not | boolean | false |
|
||||
| isLeaf | Leaf node or not | boolean | false |
|
||||
| key | Required property, should be unique in the tree | string | - |
|
||||
| title | Content showed on the treeNodes | string\|slot | '---' |
|
||||
| value | Will be treated as `treeNodeFilterProp` by default, should be unique in the tree | string | - |
|
||||
| scopedSlots | When using treeNodes, you can use this property to configure the properties that support the slot, such as `scopedSlots: { title: 'XXX'}` | object | - |
|
|
@ -0,0 +1,115 @@
|
|||
|
||||
import VcTreeSelect, { TreeNode, SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from '../vc-tree-select'
|
||||
import classNames from 'classnames'
|
||||
import { TreeSelectProps } from './interface'
|
||||
import LocaleReceiver from '../locale-provider/LocaleReceiver'
|
||||
import warning from '../_util/warning'
|
||||
import { initDefaultProps, getOptionProps, getComponentFromProp, filterEmpty } from '../_util/props-util'
|
||||
|
||||
export { TreeData, TreeSelectProps } from './interface'
|
||||
|
||||
export default {
|
||||
TreeNode: { ...TreeNode, name: 'ATreeSelectNode' },
|
||||
SHOW_ALL,
|
||||
SHOW_PARENT,
|
||||
SHOW_CHILD,
|
||||
name: 'ATreeSelect',
|
||||
props: initDefaultProps(TreeSelectProps(), {
|
||||
prefixCls: 'ant-select',
|
||||
transitionName: 'slide-up',
|
||||
choiceTransitionName: 'zoom',
|
||||
showSearch: false,
|
||||
}),
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
|
||||
created () {
|
||||
warning(
|
||||
this.multiple !== false || !this.treeCheckable,
|
||||
'`multiple` will alway be `true` when `treeCheckable` is true',
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
focus () {
|
||||
this.$refs.vcTreeSelect.focus()
|
||||
},
|
||||
|
||||
blur () {
|
||||
this.$refs.vcTreeSelect.blur()
|
||||
},
|
||||
onChange () {
|
||||
this.$emit('change', ...arguments)
|
||||
},
|
||||
updateTreeData (list = []) {
|
||||
for (let i = 0, len = list.length; i < len; i++) {
|
||||
const { label, title, scopedSlots = {}, children } = list[i]
|
||||
const { $scopedSlots } = this
|
||||
let newLabel = typeof label === 'function' ? label(this.$createElement) : label
|
||||
let newTitle = typeof title === 'function' ? title(this.$createElement) : title
|
||||
if (!newLabel && scopedSlots.label && $scopedSlots[scopedSlots.label]) {
|
||||
newLabel = $scopedSlots.label(list[i])
|
||||
}
|
||||
if (!newTitle && scopedSlots.title && $scopedSlots[scopedSlots.title]) {
|
||||
newTitle = $scopedSlots.title(list[i])
|
||||
}
|
||||
const item = {
|
||||
// label: newLabel,
|
||||
title: newTitle || newLabel,
|
||||
}
|
||||
this.updateTreeData(children)
|
||||
Object.assign(list[i], item)
|
||||
}
|
||||
},
|
||||
renderTreeSelect (locale) {
|
||||
const props = getOptionProps(this)
|
||||
const {
|
||||
prefixCls,
|
||||
size,
|
||||
notFoundContent,
|
||||
dropdownStyle,
|
||||
dropdownClassName,
|
||||
...restProps
|
||||
} = props
|
||||
this.updateTreeData(props.treeData)
|
||||
const cls = {
|
||||
[`${prefixCls}-lg`]: size === 'large',
|
||||
[`${prefixCls}-sm`]: size === 'small',
|
||||
}
|
||||
|
||||
let checkable = getComponentFromProp(this, 'treeCheckable')
|
||||
if (checkable) {
|
||||
checkable = <span class={`${prefixCls}-tree-checkbox-inner`} />
|
||||
}
|
||||
const VcTreeSelectProps = {
|
||||
props: {
|
||||
...restProps,
|
||||
dropdownClassName: classNames(dropdownClassName, `${prefixCls}-tree-dropdown`),
|
||||
prefixCls,
|
||||
dropdownStyle: { maxHeight: '100vh', overflow: 'auto', ...dropdownStyle },
|
||||
treeCheckable: checkable,
|
||||
notFoundContent: notFoundContent || locale.notFoundContent,
|
||||
},
|
||||
class: cls,
|
||||
on: { ...this.$listeners, change: this.onChange },
|
||||
ref: 'vcTreeSelect',
|
||||
}
|
||||
return (
|
||||
<VcTreeSelect {...VcTreeSelectProps}>{filterEmpty(this.$slots.default)}</VcTreeSelect>
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<LocaleReceiver
|
||||
componentName='Select'
|
||||
defaultLocale={{}}
|
||||
scopedSlots={
|
||||
{ default: this.renderTreeSelect }
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
|
||||
## API
|
||||
|
||||
### Tree props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| allowClear | 显示清除按钮 | boolean | false |
|
||||
| defaultValue | 指定默认选中的条目 | string/string\[] | - |
|
||||
| disabled | 是否禁用 | boolean | false |
|
||||
| dropdownClassName | 下拉菜单的 className 属性 | string | - |
|
||||
| dropdownMatchSelectWidth | 下拉菜单和选择器同宽 | boolean | true |
|
||||
| dropdownStyle | 下拉菜单的样式 | object | - |
|
||||
| filterTreeNode | 是否根据输入项进行筛选,默认用 treeNodeFilterProp 的值作为要筛选的 TreeNode 的属性值 | boolean\|Function(inputValue: string, treeNode: TreeNode) (函数需要返回bool值) | Function |
|
||||
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 | Function(triggerNode) | () => document.body |
|
||||
| labelInValue | 是否把每个选项的 label 包装到 value 中,会把 value 类型从 `string` 变为 `{value: string, label: VNode, halfChecked(treeCheckStrictly 时有效): string[] }` 的格式 | boolean | false |
|
||||
| loadData | 异步加载数据 | function(node) | - |
|
||||
| multiple | 支持多选(当设置 treeCheckable 时自动变为true) | boolean | false |
|
||||
| placeholder | 选择框默认文字 | string\|slot | - |
|
||||
| searchPlaceholder | 搜索框默认文字 | string\|slot | - |
|
||||
| showCheckedStrategy | 定义选中项回填的方式。`TreeSelect.SHOW_ALL`: 显示所有选中节点(包括父节点). `TreeSelect.SHOW_PARENT`: 只显示父节点(当父节点下所有子节点都选中时). 默认只显示子节点. | enum{TreeSelect.SHOW_ALL, TreeSelect.SHOW_PARENT, TreeSelect.SHOW_CHILD } | TreeSelect.SHOW_CHILD |
|
||||
| showSearch | 在下拉中显示搜索框(仅在单选模式下生效) | boolean | false |
|
||||
| size | 选择框大小,可选 `large` `small` | string | 'default' |
|
||||
| treeCheckable | 显示 checkbox | boolean | false |
|
||||
| treeCheckStrictly | checkable 状态下节点选择完全受控(父子节点选中状态不再关联),会使得 `labelInValue` 强制为 true | boolean | false |
|
||||
| treeData | treeNodes 数据,如果设置则不需要手动构造 TreeNode 节点(value 在整个树范围内唯一) | array<{value, label, children, [disabled, disableCheckbox, selectable]}> | \[] |
|
||||
| treeDataSimpleMode | 使用简单格式的 treeData,具体设置参考可设置的类型 (此时 treeData 应变为这样的数据结构: [{id:1, pId:0, value:'1', label:"test1",...},...], `pId` 是父节点的 id) | false\|Array<{ id: string, pId: string, rootPId: null }> | false |
|
||||
| treeDefaultExpandAll | 默认展开所有树节点 | boolean | false |
|
||||
| treeDefaultExpandedKeys | 默认展开的树节点 | string\[] | - |
|
||||
| treeNodeFilterProp | 输入项过滤对应的 treeNode 属性 | string | 'value' |
|
||||
| treeNodeLabelProp | 作为显示的 prop 设置 | string | 'title' |
|
||||
| value(v-model) | 指定当前选中的条目 | string/string\[] | - |
|
||||
|
||||
### 事件
|
||||
|
||||
| 事件名称 | 说明 | 回调参数 |
|
||||
| --- | --- | --- |
|
||||
| change | 选中树节点时调用此函数 | function(value, label, extra) | - |
|
||||
| search | 文本框值变化时回调 | function(value: string) | - |
|
||||
| select | 被选中时调用 | function(value, node, extra) | - |
|
||||
|
||||
### Tree 方法
|
||||
|
||||
| 名称 | 描述 |
|
||||
| --- | --- |
|
||||
| blur() | 移除焦点 |
|
||||
| focus() | 获取焦点 |
|
||||
|
||||
### TreeNode props
|
||||
|
||||
> 建议使用 treeData 来代替 TreeNode,免去手工构造麻烦
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| disableCheckbox | 禁掉 checkbox | boolean | false |
|
||||
| disabled | 是否禁用 | boolean | false |
|
||||
| isLeaf | 是否是叶子节点 | boolean | false |
|
||||
| key | 此项必须设置(其值在整个树范围内唯一) | string | - |
|
||||
| title | 树节点显示的内容 | string\|slot | '---' |
|
||||
| value | 默认根据此属性值进行筛选(其值在整个树范围内唯一) | string | - |
|
||||
| scopedSlots | 使用treeData时,可以通过该属性配置支持slot的属性,如 `scopedSlots: { title: 'XXX'}` | object | - |
|
|
@ -0,0 +1,37 @@
|
|||
import PropTypes from '../_util/vue-types'
|
||||
import { AbstractSelectProps } from '../select'
|
||||
|
||||
export const TreeData = PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
label: PropTypes.any,
|
||||
scopedSlots: PropTypes.object,
|
||||
children: PropTypes.array,
|
||||
}).loose
|
||||
|
||||
export const TreeSelectProps = () => ({
|
||||
...AbstractSelectProps(),
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.array]),
|
||||
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
||||
multiple: PropTypes.bool,
|
||||
// onSelect: (value: any) => void,
|
||||
// onChange: (value: any, label: any) => void,
|
||||
// onSearch: (value: any) => void,
|
||||
searchPlaceholder: PropTypes.string,
|
||||
dropdownClassName: PropTypes.string,
|
||||
dropdownStyle: PropTypes.object,
|
||||
dropdownMatchSelectWidth: PropTypes.bool,
|
||||
treeDefaultExpandAll: PropTypes.bool,
|
||||
treeCheckable: PropTypes.bool,
|
||||
treeDefaultExpandedKeys: PropTypes.arrayOf(String),
|
||||
filterTreeNode: PropTypes.func,
|
||||
treeNodeFilterProp: PropTypes.string,
|
||||
treeNodeLabelProp: PropTypes.string,
|
||||
treeData: PropTypes.arrayOf(Object),
|
||||
treeDataSimpleMode: PropTypes.oneOfType([Boolean, Object]),
|
||||
loadData: PropTypes.func,
|
||||
showCheckedStrategy: PropTypes.oneOf(['SHOW_ALL', 'SHOW_PARENT', 'SHOW_CHILD']),
|
||||
labelInValue: PropTypes.bool,
|
||||
treeCheckStrictly: PropTypes.bool,
|
||||
getPopupContainer: PropTypes.func,
|
||||
})
|
|
@ -0,0 +1,6 @@
|
|||
import '../../style/index.less'
|
||||
import './index.less'
|
||||
|
||||
// style dependencies
|
||||
import '../../select/style'
|
||||
import '../../checkbox/style'
|
|
@ -0,0 +1,145 @@
|
|||
@import "../../style/themes/default";
|
||||
@import "../../style/mixins/index";
|
||||
@import "../../tree/style/mixin";
|
||||
@import "../../checkbox/style/mixin";
|
||||
|
||||
@select-prefix-cls: ~"@{ant-prefix}-select";
|
||||
@select-tree-prefix-cls: ~"@{ant-prefix}-select-tree";
|
||||
|
||||
.antCheckboxFn(@checkbox-prefix-cls: ~"@{ant-prefix}-select-tree-checkbox");
|
||||
|
||||
.@{select-tree-prefix-cls} {
|
||||
.reset-component;
|
||||
margin: 0;
|
||||
padding: 0 4px;
|
||||
margin-top: -4px;
|
||||
li {
|
||||
padding: 0;
|
||||
margin: 8px 0;
|
||||
list-style: none;
|
||||
white-space: nowrap;
|
||||
outline: 0;
|
||||
&.filter-node {
|
||||
> span {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0 0 0 18px;
|
||||
}
|
||||
.@{select-tree-prefix-cls}-node-content-wrapper {
|
||||
display: inline-block;
|
||||
padding: 3px 5px;
|
||||
border-radius: 2px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: @text-color;
|
||||
transition: all .3s;
|
||||
width: ~"calc(100% - 24px)";
|
||||
&:hover {
|
||||
background-color: @item-hover-bg;
|
||||
}
|
||||
&.@{select-tree-prefix-cls}-node-selected {
|
||||
background-color: @primary-2;
|
||||
}
|
||||
}
|
||||
span {
|
||||
&.@{select-tree-prefix-cls}-checkbox {
|
||||
margin: 0 4px 0 0;
|
||||
+ .@{select-tree-prefix-cls}-node-content-wrapper {
|
||||
width: ~"calc(100% - 46px)";
|
||||
}
|
||||
}
|
||||
&.@{select-tree-prefix-cls}-switcher,
|
||||
&.@{select-tree-prefix-cls}-iconEle {
|
||||
margin: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 22px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
border: 0 none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
}
|
||||
&.@{select-tree-prefix-cls}-icon_loading {
|
||||
&:after {
|
||||
display: inline-block;
|
||||
.iconfont-font("\e6ae");
|
||||
animation: loadingCircle 1s infinite linear;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
&.@{select-tree-prefix-cls}-switcher {
|
||||
&.@{select-tree-prefix-cls}-switcher-noop {
|
||||
cursor: auto;
|
||||
}
|
||||
&.@{select-tree-prefix-cls}-switcher_open {
|
||||
.antTreeSwitcherIcon();
|
||||
}
|
||||
&.@{select-tree-prefix-cls}-switcher_close {
|
||||
.antTreeSwitcherIcon();
|
||||
&:after {
|
||||
transform: rotate(270deg) scale(0.59);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&-child-tree {
|
||||
display: none;
|
||||
&-open {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
li&-treenode-disabled {
|
||||
> span:not(.@{select-tree-prefix-cls}-switcher),
|
||||
> .@{select-tree-prefix-cls}-node-content-wrapper,
|
||||
> .@{select-tree-prefix-cls}-node-content-wrapper span {
|
||||
color: @disabled-color;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
> .@{select-tree-prefix-cls}-node-content-wrapper:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
&-icon__open {
|
||||
margin-right: 2px;
|
||||
vertical-align: top;
|
||||
}
|
||||
&-icon__close {
|
||||
margin-right: 2px;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
.@{select-prefix-cls}-tree-dropdown {
|
||||
.reset-component;
|
||||
.@{select-prefix-cls}-dropdown-search {
|
||||
display: block;
|
||||
padding: 4px;
|
||||
.@{select-prefix-cls}-search__field__wrap {
|
||||
width: 100%;
|
||||
}
|
||||
.@{select-prefix-cls}-search__field {
|
||||
padding: 4px 7px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border: @border-width-base @border-style-base @border-color-base;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
}
|
||||
&.@{select-prefix-cls}-search--hide {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.@{select-prefix-cls}-not-found {
|
||||
cursor: not-allowed;
|
||||
color: @disabled-color;
|
||||
padding: 7px 16px;
|
||||
display: block;
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1,2 @@
|
|||
@import "./select.less";
|
||||
@import "./tree.less";
|
Binary file not shown.
After Width: | Height: | Size: 381 B |
Binary file not shown.
After Width: | Height: | Size: 45 B |
|
@ -0,0 +1,500 @@
|
|||
@selectPrefixCls: rc-tree-select;
|
||||
|
||||
.effect() {
|
||||
animation-duration: .3s;
|
||||
animation-fill-mode: both;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.@{selectPrefixCls} {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
color: #666;
|
||||
|
||||
&-allow-clear {
|
||||
.@{selectPrefixCls}-selection--single .@{selectPrefixCls}-selection__rendered {
|
||||
padding-right: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
ul, li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
> ul > li > a {
|
||||
padding: 0;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
// arrow
|
||||
&-arrow {
|
||||
height: 26px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
width: 20px;
|
||||
b {
|
||||
border-color: #999999 transparent transparent transparent;
|
||||
border-style: solid;
|
||||
border-width: 5px 4px 0 4px;
|
||||
height: 0;
|
||||
width: 0;
|
||||
margin-left: -4px;
|
||||
margin-top: -2px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&-selection {
|
||||
outline: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
|
||||
background-color: #fff;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d9d9d9;
|
||||
|
||||
&__clear {
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
|
||||
&:after {
|
||||
content: '×'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-enabled &-selection {
|
||||
&:hover {
|
||||
border-color: #23c0fa;
|
||||
box-shadow: 0 0 2px fadeout(#2db7f5, 20%);
|
||||
}
|
||||
&:active {
|
||||
border-color: #2db7f5;
|
||||
}
|
||||
}
|
||||
|
||||
&-selection--single {
|
||||
height: 28px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
.@{selectPrefixCls}-selection__rendered {
|
||||
display: block;
|
||||
padding-left: 10px;
|
||||
padding-right: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.@{selectPrefixCls}-selection-selected-value {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.@{selectPrefixCls}-selection__clear {
|
||||
top: 5px;
|
||||
right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&-disabled {
|
||||
color: #ccc;
|
||||
cursor: not-allowed;
|
||||
|
||||
.@{selectPrefixCls}-selection--single,
|
||||
.@{selectPrefixCls}-selection__choice__remove {
|
||||
cursor: not-allowed;
|
||||
color: #ccc;
|
||||
|
||||
&:hover {
|
||||
cursor: not-allowed;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-search__field__wrap {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&-search__field__placeholder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 3px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
&-search__field__mirror {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -9999px;
|
||||
white-space: pre;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&-search--inline {
|
||||
float: left;
|
||||
width: 100%;
|
||||
.@{selectPrefixCls}-search__field__wrap {
|
||||
width: 100%;
|
||||
}
|
||||
.@{selectPrefixCls}-search__field {
|
||||
border: none;
|
||||
font-size: 100%;
|
||||
//margin-top: 5px;
|
||||
background: transparent;
|
||||
outline: 0;
|
||||
width: 100%;
|
||||
}
|
||||
> i {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
&-enabled&-selection--multiple {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
&-selection--multiple {
|
||||
min-height: 28px;
|
||||
|
||||
.@{selectPrefixCls}-search--inline {
|
||||
width: auto;
|
||||
.@{selectPrefixCls}-search__field {
|
||||
width: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.@{selectPrefixCls}-search__field__placeholder {
|
||||
top: 5px;
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
.@{selectPrefixCls}-selection__rendered {
|
||||
//display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-left: 8px;
|
||||
padding-bottom: 2px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
> ul > li {
|
||||
margin-top: 4px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.@{selectPrefixCls}-selection__clear {
|
||||
top: 5px;
|
||||
right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&-enabled {
|
||||
.@{selectPrefixCls}-selection__choice {
|
||||
cursor: default;
|
||||
&:hover {
|
||||
.@{selectPrefixCls}-selection__choice__remove {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
.@{selectPrefixCls}-selection__choice__remove +
|
||||
.@{selectPrefixCls}-selection__choice__content {
|
||||
margin-left: -8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& &-selection__choice {
|
||||
background-color: #f3f3f3;
|
||||
border-radius: 4px;
|
||||
float: left;
|
||||
padding: 0 15px;
|
||||
margin-right: 4px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: padding .3s cubic-bezier(0.6, -0.28, 0.735, 0.045), width .3s cubic-bezier(0.6, -0.28, 0.735, 0.045);
|
||||
|
||||
&__content {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
transition: margin .3s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
}
|
||||
|
||||
&-zoom-enter, &-zoom-appear, &-zoom-leave {
|
||||
.effect();
|
||||
opacity: 0;
|
||||
animation-play-state: paused;
|
||||
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
&-zoom-leave {
|
||||
opacity: 1;
|
||||
animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045);
|
||||
}
|
||||
|
||||
&-zoom-enter.@{selectPrefixCls}-selection__choice-zoom-enter-active,
|
||||
&-zoom-appear.@{selectPrefixCls}-selection__choice-zoom-appear-active {
|
||||
animation-play-state: running;
|
||||
animation-name: rcSelectChoiceZoomIn;
|
||||
}
|
||||
|
||||
&-zoom-leave.@{selectPrefixCls}-selection__choice-zoom-leave-active {
|
||||
animation-play-state: running;
|
||||
animation-name: rcSelectChoiceZoomOut;
|
||||
}
|
||||
|
||||
@keyframes rcSelectChoiceZoomIn {
|
||||
0% {
|
||||
transform: scale(0.6);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rcSelectChoiceZoomOut {
|
||||
to {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__remove {
|
||||
color: #919191;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
padding: 0 0 0 8px;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
top: 0;
|
||||
right: 2px;
|
||||
transition: opacity .3s, transform .3s;
|
||||
&:before {
|
||||
content: '×'
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-dropdown {
|
||||
background-color: white;
|
||||
border: 1px solid #d9d9d9;
|
||||
box-shadow: 0 0px 4px #d9d9d9;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
z-index: 100;
|
||||
left: -9999px;
|
||||
top: -9999px;
|
||||
//border-top: none;
|
||||
//border-top-left-radius: 0;
|
||||
//border-top-right-radius: 0;
|
||||
position: absolute;
|
||||
outline: none;
|
||||
|
||||
&-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-menu {
|
||||
outline: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
z-index: 9999;
|
||||
|
||||
> li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&-item-group-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
> li.@{selectPrefixCls}-menu-item {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&-item-group-title {
|
||||
color: #999;
|
||||
line-height: 1.5;
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid #dedede;
|
||||
}
|
||||
|
||||
li&-item {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 7px 10px;
|
||||
font-weight: normal;
|
||||
color: #666666;
|
||||
white-space: nowrap;
|
||||
|
||||
&-selected {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
&-active {
|
||||
background-color: #5897fb;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-disabled {
|
||||
color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&-divider {
|
||||
height: 1px;
|
||||
margin: 1px 0;
|
||||
overflow: hidden;
|
||||
background-color: #e5e5e5;
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-slide-up-enter, &-slide-up-appear {
|
||||
.effect();
|
||||
opacity: 0;
|
||||
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
&-slide-up-leave {
|
||||
.effect();
|
||||
opacity: 1;
|
||||
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
&-slide-up-enter&-slide-up-enter-active&-placement-bottomLeft, &-slide-up-appear&-slide-up-appear-active&-placement-bottomLeft {
|
||||
animation-name: rcSelectDropdownSlideUpIn;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
&-slide-up-leave&-slide-up-leave-active&-placement-bottomLeft {
|
||||
animation-name: rcSelectDropdownSlideUpOut;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
&-slide-up-enter&-slide-up-enter-active&-placement-topLeft, &-slide-up-appear&-slide-up-appear-active&-placement-topLeft {
|
||||
animation-name: rcSelectDropdownSlideDownIn;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
&-slide-up-leave&-slide-up-leave-active&-placement-topLeft {
|
||||
animation-name: rcSelectDropdownSlideDownOut;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
@keyframes rcSelectDropdownSlideUpIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform-origin: 0% 0%;
|
||||
transform: scaleY(0);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform-origin: 0% 0%;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
}
|
||||
@keyframes rcSelectDropdownSlideUpOut {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform-origin: 0% 0%;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform-origin: 0% 0%;
|
||||
transform: scaleY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rcSelectDropdownSlideDownIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform-origin: 0% 100%;
|
||||
transform: scaleY(0);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform-origin: 0% 100%;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
}
|
||||
@keyframes rcSelectDropdownSlideDownOut {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform-origin: 0% 100%;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform-origin: 0% 100%;
|
||||
transform: scaleY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-dropdown-search {
|
||||
display: block;
|
||||
padding: 4px;
|
||||
.@{selectPrefixCls}-search__field__wrap {
|
||||
width: 100%;
|
||||
}
|
||||
.@{selectPrefixCls}-search__field__placeholder {
|
||||
top: 4px;
|
||||
}
|
||||
.@{selectPrefixCls}-search__field {
|
||||
padding: 4px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
}
|
||||
&.@{selectPrefixCls}-search--hide {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-open {
|
||||
.@{selectPrefixCls}-arrow b {
|
||||
border-color: transparent transparent #888 transparent;
|
||||
border-width: 0 4px 5px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&-not-found {
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,347 @@
|
|||
/* eslint react/no-multi-comp:0, no-console:0, no-alert: 0 */
|
||||
|
||||
import '../assets/index.less'
|
||||
import './demo.less'
|
||||
|
||||
import '../../vc-dialog/assets/index.less'
|
||||
import Dialog from '../../vc-dialog'
|
||||
import TreeSelect, { TreeNode, SHOW_PARENT } from '../index'
|
||||
import { gData } from './util'
|
||||
|
||||
function isLeaf (value) {
|
||||
if (!value) {
|
||||
return false
|
||||
}
|
||||
let queues = [...gData]
|
||||
while (queues.length) { // BFS
|
||||
const item = queues.shift()
|
||||
if (item.value === value) {
|
||||
if (!item.children) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (item.children) {
|
||||
queues = queues.concat(item.children)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function findPath (value, data) {
|
||||
const sel = []
|
||||
function loop (selected, children) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const item = children[i]
|
||||
if (selected === item.value) {
|
||||
sel.push(item)
|
||||
return
|
||||
}
|
||||
if (item.children) {
|
||||
loop(selected, item.children, item)
|
||||
if (sel.length) {
|
||||
sel.push(item)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loop(value, data)
|
||||
return sel
|
||||
}
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
tsOpen: false,
|
||||
visible: false,
|
||||
inputValue: '0-0-0-label',
|
||||
value: '0-0-0-value1',
|
||||
// value: ['0-0-0-0-value', '0-0-0-1-value', '0-0-0-2-value'],
|
||||
lv: { value: '0-0-0-value', label: 'spe label' },
|
||||
multipleValue: [],
|
||||
simpleTreeData: [
|
||||
{ key: 1, pId: 0, label: 'test1', value: 'test1' },
|
||||
{ key: 121, pId: 0, label: 'test1', value: 'test121' },
|
||||
{ key: 11, pId: 1, label: 'test11', value: 'test11' },
|
||||
{ key: 12, pId: 1, label: 'test12', value: 'test12' },
|
||||
{ key: 111, pId: 11, label: 'test111', value: 'test111' },
|
||||
],
|
||||
treeDataSimpleMode: {
|
||||
id: 'key',
|
||||
rootPId: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
// console.log(this.refs.mul.getInputDOMNode());
|
||||
// this.refs.mul.getInputDOMNode().setAttribute('disabled', true);
|
||||
},
|
||||
methods: {
|
||||
onClick () {
|
||||
this.visible = true
|
||||
},
|
||||
|
||||
onClose () {
|
||||
this.visible = false
|
||||
},
|
||||
|
||||
onSearch (value) {
|
||||
console.log(value, arguments)
|
||||
},
|
||||
|
||||
onChange (value) {
|
||||
console.log('onChange', arguments)
|
||||
this.value = value
|
||||
},
|
||||
|
||||
onChangeChildren (value) {
|
||||
console.log('onChangeChildren', arguments)
|
||||
const pre = value ? this.value : undefined
|
||||
this.value = isLeaf(value) ? value : pre
|
||||
},
|
||||
|
||||
onChangeLV (value) {
|
||||
console.log('labelInValue', arguments)
|
||||
if (!value) {
|
||||
this.lv = undefined
|
||||
return
|
||||
}
|
||||
const path = findPath(value.value, gData).map(i => i.label).reverse().join(' > ')
|
||||
this.lv = { value: value.value, label: path }
|
||||
},
|
||||
|
||||
onMultipleChange (value) {
|
||||
console.log('onMultipleChange', arguments)
|
||||
this.multipleValue = value
|
||||
},
|
||||
|
||||
onSelect () {
|
||||
// use onChange instead
|
||||
console.log(...arguments)
|
||||
},
|
||||
|
||||
onDropdownVisibleChange (visible, info) {
|
||||
console.log(visible, this.value, info)
|
||||
if (Array.isArray(this.value) && this.value.length > 1 &&
|
||||
this.value.length < 3) {
|
||||
alert('please select more than two item or less than one item.')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
|
||||
filterTreeNode (input, child) {
|
||||
return String(child.title).indexOf(input) === 0
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div style={{ margin: '20px' }}>
|
||||
<h2>tree-select in dialog</h2>
|
||||
<button class='btn btn-primary' onClick={this.onClick}>show dialog</button>
|
||||
{this.visible ? <Dialog
|
||||
visible={this.visible}
|
||||
animation='zoom'
|
||||
maskAnimation='fade'
|
||||
onClose={this.onClose}
|
||||
style={{ width: '600px', height: '400px', overflow: 'auto' }}
|
||||
id='area'
|
||||
>
|
||||
<div style={{ height: '600px', paddingTop: '100px' }}>
|
||||
<TreeSelect
|
||||
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
||||
style={{ width: '300px' }}
|
||||
transitionName='rc-tree-select-dropdown-slide-up'
|
||||
choiceTransitionName='rc-tree-select-selection__choice-zoom'
|
||||
dropdownStyle={{ maxHeight: '200px', overflow: 'auto', zIndex: 1500 }}
|
||||
placeholder={<i>请下拉选择</i>}
|
||||
searchPlaceholder='please search'
|
||||
showSearch allowClear treeLine
|
||||
value={this.value}
|
||||
treeData={gData}
|
||||
treeNodeFilterProp='label'
|
||||
filterTreeNode={false}
|
||||
onSearch={this.onSearch}
|
||||
onChange={this.onChange}
|
||||
onSelect={this.onSelect}
|
||||
/>
|
||||
</div>
|
||||
</Dialog> : null}
|
||||
|
||||
<h2>single select</h2>
|
||||
<TreeSelect
|
||||
style={{ width: '300px' }}
|
||||
transitionName='rc-tree-select-dropdown-slide-up'
|
||||
choiceTransitionName='rc-tree-select-selection__choice-zoom'
|
||||
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
|
||||
placeholder={<i>请下拉选择</i>}
|
||||
searchPlaceholder='please search'
|
||||
showSearch allowClear treeLine
|
||||
inputValue={this.inputValue}
|
||||
value={this.value}
|
||||
treeData={gData}
|
||||
treeNodeFilterProp='label'
|
||||
filterTreeNode={false}
|
||||
onSearch={this.onSearch}
|
||||
open={this.tsOpen}
|
||||
onChange={(value) => {
|
||||
console.log('onChange', value, arguments)
|
||||
if (value === '0-0-0-0-value') {
|
||||
this.tsOpen = true
|
||||
} else {
|
||||
this.tsOpen = false
|
||||
}
|
||||
this.value = value
|
||||
} }
|
||||
dropdownVisibleChange={(v, info) => {
|
||||
console.log('single dropdownVisibleChange', v, info)
|
||||
// document clicked
|
||||
if (info.documentClickClose && this.value === '0-0-0-0-value') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} }
|
||||
onSelect={this.onSelect}
|
||||
/>
|
||||
|
||||
<h2>single select (just select children)</h2>
|
||||
<TreeSelect
|
||||
style={{ width: '300px' }}
|
||||
transitionName='rc-tree-select-dropdown-slide-up'
|
||||
choiceTransitionName='rc-tree-select-selection__choice-zoom'
|
||||
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
|
||||
placeholder={<i>请下拉选择</i>}
|
||||
searchPlaceholder='please search'
|
||||
showSearch allowClear treeLine
|
||||
value={this.value}
|
||||
treeData={gData}
|
||||
treeNodeFilterProp='label'
|
||||
filterTreeNode={false}
|
||||
onChange={this.onChangeChildren}
|
||||
/>
|
||||
|
||||
<h2>multiple select</h2>
|
||||
<TreeSelect ref='mul'
|
||||
style={{ width: '300px' }}
|
||||
transitionName='rc-tree-select-dropdown-slide-up'
|
||||
choiceTransitionName='rc-tree-select-selection__choice-zoom'
|
||||
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
|
||||
placeholder={<i>请下拉选择</i>}
|
||||
searchPlaceholder='please search'
|
||||
multiple
|
||||
value={this.multipleValue}
|
||||
treeData={gData}
|
||||
treeNodeFilterProp='title'
|
||||
onChange={this.onMultipleChange}
|
||||
onSelect={this.onSelect}
|
||||
allowClear
|
||||
/>
|
||||
|
||||
<h2>check select</h2>
|
||||
<TreeSelect
|
||||
class='check-select'
|
||||
transitionName='rc-tree-select-dropdown-slide-up'
|
||||
choiceTransitionName='rc-tree-select-selection__choice-zoom'
|
||||
dropdownStyle={{ height: '200px', overflow: 'auto' }}
|
||||
dropdownPopupAlign={{ overflow: { adjustY: 0, adjustX: 0 }, offset: [0, 2] }}
|
||||
onDropdownVisibleChange={this.onDropdownVisibleChange}
|
||||
placeholder={<i>请下拉选择</i>}
|
||||
searchPlaceholder='please search'
|
||||
treeLine maxTagTextLength={10}
|
||||
value={this.value}
|
||||
inputValue={null}
|
||||
treeData={gData}
|
||||
treeNodeFilterProp='title'
|
||||
treeCheckable showCheckedStrategy={SHOW_PARENT}
|
||||
onChange={this.onChange}
|
||||
onSelect={this.onSelect}
|
||||
/>
|
||||
|
||||
<h2>labelInValue & show path</h2>
|
||||
<TreeSelect
|
||||
style={{ width: '500px' }}
|
||||
transitionName='rc-tree-select-dropdown-slide-up'
|
||||
choiceTransitionName='rc-tree-select-selection__choice-zoom'
|
||||
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
|
||||
placeholder={<i>请下拉选择</i>}
|
||||
searchPlaceholder='please search'
|
||||
showSearch allowClear treeLine
|
||||
value={this.lv} labelInValue
|
||||
treeData={gData}
|
||||
treeNodeFilterProp='label'
|
||||
filterTreeNode={false}
|
||||
onChange={this.onChangeLV}
|
||||
/>
|
||||
|
||||
<h2>use treeDataSimpleMode</h2>
|
||||
<TreeSelect
|
||||
style={{ width: '300px' }}
|
||||
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
|
||||
placeholder={<i>请下拉选择</i>}
|
||||
searchPlaceholder='please search'
|
||||
treeLine maxTagTextLength={10}
|
||||
inputValue={'test111'}
|
||||
value={this.value}
|
||||
treeData={this.simpleTreeData}
|
||||
treeNodeFilterProp='title'
|
||||
treeDataSimpleMode={this.treeDataSimpleMode}
|
||||
treeCheckable showCheckedStrategy={SHOW_PARENT}
|
||||
onChange={this.onChange}
|
||||
onSelect={this.onSelect}
|
||||
/>
|
||||
|
||||
<h2>Testing in extreme conditions (Boundary conditions test) </h2>
|
||||
<TreeSelect
|
||||
style={{ width: '200px' }}
|
||||
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
|
||||
defaultValue={'leaf1'} multiple treeCheckable showCheckedStrategy={SHOW_PARENT}
|
||||
treeDefaultExpandAll
|
||||
treeData={[
|
||||
{ key: '', value: '', label: 'empty value', children: [] },
|
||||
{
|
||||
key: '0', value: '0', label: '0 label', children: [
|
||||
{ key: '00', value: '00', label: '00 label', children: [] },
|
||||
{ key: '01', value: '01', label: '01 label', children: [] },
|
||||
],
|
||||
},
|
||||
]}
|
||||
onChange={(val) => console.log(val, arguments)}
|
||||
/>
|
||||
|
||||
<h2>use TreeNode Component (not recommend)</h2>
|
||||
<TreeSelect
|
||||
style={{ width: '200px' }}
|
||||
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
|
||||
defaultValue={'leaf1'}
|
||||
treeDefaultExpandAll
|
||||
treeNodeFilterProp='title'
|
||||
filterTreeNode={this.filterTreeNode}
|
||||
onChange={(val) => console.log(val, arguments)}
|
||||
>
|
||||
<TreeNode value='' title='parent 1' key=''>
|
||||
<TreeNode value='parent 1-0' title='parent 1-0' key='0-1-0'>
|
||||
<TreeNode value='leaf1' title='my leaf' key='random' />
|
||||
<TreeNode value='leaf2' title='your leaf' key='random1' disabled />
|
||||
</TreeNode>
|
||||
<TreeNode value='parent 1-1' title='parent 1-1' key='0-1-1'>
|
||||
<TreeNode value='sss'
|
||||
title={<span style={{ color: 'red' }}>sss</span>} key='random3'
|
||||
/>
|
||||
<TreeNode value='same value1' title='same txtle' key='0-1-1-1'>
|
||||
<TreeNode value='same value10' title='same titlexd' key='0-1-1-1-0' />
|
||||
</TreeNode>
|
||||
</TreeNode>
|
||||
</TreeNode>
|
||||
<TreeNode value='same value2' title='same title' key='0-2'>
|
||||
<TreeNode value='2same value' title='2same title' key='0-2-0' />
|
||||
</TreeNode>
|
||||
<TreeNode value='same value3' title='same title' key='0-3' />
|
||||
</TreeSelect>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import PropTypes from '../../_util/vue-types'
|
||||
import { generateData, calcTotal } from './util'
|
||||
|
||||
const Gen = {
|
||||
props: {
|
||||
x: PropTypes.number.def(20),
|
||||
y: PropTypes.number.def(18),
|
||||
z: PropTypes.number.def(1),
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
nums: '',
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.$refs.x.value = this.x
|
||||
this.$refs.y.value = this.y
|
||||
this.$refs.z.value = this.z
|
||||
const vals = this.getVals()
|
||||
this.$emit('gen', generateData(vals.x, vals.y, vals.z))
|
||||
},
|
||||
methods: {
|
||||
onGen (e) {
|
||||
e.preventDefault()
|
||||
const vals = this.getVals()
|
||||
this.$emit('gen', generateData(vals.x, vals.y, vals.z))
|
||||
this.nums = calcTotal(vals.x, vals.y, vals.z)
|
||||
},
|
||||
getVals () {
|
||||
return {
|
||||
x: parseInt(this.$refs.x.value, 10),
|
||||
y: parseInt(this.$refs.y.value, 10),
|
||||
z: parseInt(this.$refs.z.value, 10),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { x, y, z } = this
|
||||
return (<div style={{ padding: '0 20px' }}>
|
||||
<h2>big data generator</h2>
|
||||
<form onSubmit={this.onGen}>
|
||||
<span style={{ marginRight: '10px' }}>
|
||||
x: <input ref='x' type='number' min='1' required style={{ width: '50px' }} />
|
||||
</span>
|
||||
<span style={{ marginRight: '10px' }}>
|
||||
y: <input ref='y' type='number' min='1' required style={{ width: '50px' }} />
|
||||
</span>
|
||||
<span style={{ marginRight: '10px' }}>
|
||||
z: <input ref='z' type='number' min='1' required style={{ width: '50px' }} />
|
||||
</span>
|
||||
<button type='submit'>Generate</button>
|
||||
<p>total nodes: {this.nums || calcTotal(x, y, z)}</p>
|
||||
</form>
|
||||
<p style={{ fontSize: '12px' }}>
|
||||
x:每一级下的节点总数。y:每级节点里有y个节点、存在子节点。z:树的level层级数(0表示一级)
|
||||
</p>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
export default Gen
|
|
@ -0,0 +1,78 @@
|
|||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import '../assets/index.less'
|
||||
import './demo.less'
|
||||
import TreeSelect, { SHOW_PARENT } from '../index'
|
||||
import Gen from './big-data-generator'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
gData: [],
|
||||
gData1: [],
|
||||
value: '',
|
||||
value1: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (value) {
|
||||
console.log('onChange', arguments)
|
||||
this.value = value
|
||||
},
|
||||
|
||||
onChangeStrictly (value1) {
|
||||
console.log('onChangeStrictly', arguments)
|
||||
const ind = parseInt(Math.random() * 3, 10)
|
||||
value1.push({ value: `0-0-0-${ind}-value`, label: `0-0-0-${ind}-label`, halfChecked: true })
|
||||
this.value1 = value1
|
||||
},
|
||||
|
||||
onGen (data) {
|
||||
Object.assign(this.$data, {
|
||||
gData: data,
|
||||
gData1: [...data],
|
||||
value: '0-0-0-value',
|
||||
value1: [
|
||||
{ value: '0-0-value', label: '0-0-label', halfChecked: true },
|
||||
{ value: '0-0-0-value', label: '0-0-0-label' },
|
||||
],
|
||||
// value: ['0-0-0-0-value', '0-0-0-1-value', '0-0-0-2-value'],
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
return (<div style={{ padding: '0 20px' }}>
|
||||
<Gen onGen={this.onGen} />
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ marginRight: '20px' }}>
|
||||
<h3>normal check</h3>
|
||||
<TreeSelect
|
||||
style={{ width: '300px' }}
|
||||
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
|
||||
treeData={this.gData} treeLine
|
||||
value={this.value}
|
||||
placeholder={<i>请下拉选择</i>}
|
||||
treeCheckable
|
||||
showCheckedStrategy={SHOW_PARENT}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3>checkStrictly</h3>
|
||||
<TreeSelect
|
||||
style={{ width: '300px' }}
|
||||
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
|
||||
treeData={this.gData1} treeLine
|
||||
value={this.value1}
|
||||
placeholder={<i>请下拉选择</i>}
|
||||
treeCheckable
|
||||
treeCheckStrictly
|
||||
showCheckedStrategy={SHOW_PARENT}
|
||||
onChange={this.onChangeStrictly}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>)
|
||||
},
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
.rc-tree-select-selection--multiple {
|
||||
max-height: 50px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.rc-tree-select-dropdown {
|
||||
max-height: 350px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.check-select {
|
||||
width: 300px;
|
||||
.rc-tree-select-selection--multiple {
|
||||
min-height: 50px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
import '../assets/index.less'
|
||||
import TreeSelect from '../index'
|
||||
|
||||
const SHOW_PARENT = TreeSelect.SHOW_PARENT
|
||||
|
||||
const treeData = [{
|
||||
label: 'Node1',
|
||||
value: '0-0',
|
||||
key: '0-0',
|
||||
children: [{
|
||||
label: 'Child Node1',
|
||||
value: '0-0-0',
|
||||
key: '0-0-0',
|
||||
}],
|
||||
}, {
|
||||
label: 'Node2',
|
||||
value: '0-1',
|
||||
key: '0-1',
|
||||
children: [{
|
||||
label: 'Child Node3',
|
||||
value: '0-1-0',
|
||||
key: '0-1-0',
|
||||
}, {
|
||||
label: 'Child Node4',
|
||||
value: '0-1-1',
|
||||
key: '0-1-1',
|
||||
}, {
|
||||
label: 'Child Node5',
|
||||
value: '0-1-2',
|
||||
key: '0-1-2',
|
||||
}],
|
||||
}]
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
value: ['0-0-0'],
|
||||
disabled: false,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange (value) {
|
||||
console.log('onChange ', value, arguments)
|
||||
this.value = value
|
||||
},
|
||||
switch (checked) {
|
||||
this.disabled = checked
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const tProps = {
|
||||
props: {
|
||||
treeData,
|
||||
disabled: this.disabled,
|
||||
value: this.value,
|
||||
multiple: true,
|
||||
allowClear: true,
|
||||
treeCheckable: true,
|
||||
showCheckedStrategy: SHOW_PARENT,
|
||||
searchPlaceholder: 'Please select',
|
||||
},
|
||||
on: {
|
||||
change: this.onChange,
|
||||
},
|
||||
style: {
|
||||
width: '300px',
|
||||
},
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<TreeSelect {...tProps} />
|
||||
<input type='checkbox' onChange={e => this.switch(e.target.checked)}/> 禁用
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import '../assets/index.less'
|
||||
import TreeSelect from '../index'
|
||||
import { getNewTreeData, generateTreeNodes } from './util'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
treeData: [
|
||||
{ label: 'pNode 01', value: '0-0', key: '0-0' },
|
||||
{ label: 'pNode 02', value: '0-1', key: '0-1' },
|
||||
{ label: 'pNode 03', value: '0-2', key: '0-2', isLeaf: true },
|
||||
],
|
||||
// value: '0-0',
|
||||
value: { value: '0-0-0-value', label: '0-0-0-label' },
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChange (value) {
|
||||
console.log(value)
|
||||
this.value = value
|
||||
},
|
||||
|
||||
onLoadData (treeNode) {
|
||||
console.log(treeNode)
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const treeData = [...this.treeData]
|
||||
getNewTreeData(treeData, treeNode.eventKey, generateTreeNodes(treeNode), 2)
|
||||
this.treeData = treeData
|
||||
resolve()
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div style={{ padding: '10px 30px' }}>
|
||||
<h2>dynamic render</h2>
|
||||
<TreeSelect
|
||||
style={{ width: '300px' }}
|
||||
treeData={this.treeData}
|
||||
labelInValue
|
||||
value={this.value}
|
||||
onChange={this.onChange}
|
||||
loadData={this.onLoadData}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/* eslint react/no-multi-comp:0, no-console:0 */
|
||||
|
||||
import '../assets/index.less'
|
||||
import TreeSelect, { SHOW_PARENT } from '../index'
|
||||
import { gData } from './util'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
value: '11',
|
||||
// value: ['0-0-0-0-value', '0-0-0-1-value', '0-0-0-2-value'],
|
||||
simpleTreeData: [
|
||||
{ key: 1, pId: 0, label: 'a', value: 'a' },
|
||||
{ key: 11, pId: 1, label: 'a12', value: 'a12', disabled: true },
|
||||
{ key: 111, pId: 11, label: 'a00', value: 'a00', selectable: false },
|
||||
{ key: 2, pId: 0, label: 'b', value: 'b' },
|
||||
{ key: 20, pId: 2, label: 'b10', value: 'b10' },
|
||||
{ key: 21, pId: 2, label: 'b1', value: 'b1' },
|
||||
{ key: 22, pId: 2, label: 'b12', value: 'b12' },
|
||||
],
|
||||
treeDataSimpleMode: {
|
||||
id: 'key',
|
||||
rootPId: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (value) {
|
||||
if (value.length === 1) {
|
||||
// return;
|
||||
}
|
||||
console.log('onChange', arguments, this.simpleTreeData)
|
||||
this.value = value
|
||||
},
|
||||
|
||||
onSelect () {
|
||||
// use onChange instead
|
||||
// console.log(arguments);
|
||||
},
|
||||
|
||||
onDataChange () {
|
||||
const data = [...this.simpleTreeData]
|
||||
data.forEach(i => {
|
||||
if (i.key === 11) {
|
||||
delete i.disabled
|
||||
}
|
||||
if (i.key === 20) {
|
||||
i.disabled = true
|
||||
}
|
||||
})
|
||||
this.simpleTreeData = data
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div style={{ margin: '20px' }}>
|
||||
<h2>check select</h2>
|
||||
<TreeSelect
|
||||
style={{ width: '300px' }}
|
||||
transitionName='rc-tree-select-dropdown-slide-up'
|
||||
choiceTransitionName='rc-tree-select-selection__choice-zoom'
|
||||
dropdownStyle={{ height: '200px', overflow: 'auto' }}
|
||||
dropdownPopupAlign={{ overflow: { adjustY: 0, adjustX: 0 }, offset: [0, 2] }}
|
||||
placeholder={<i>请下拉选择</i>}
|
||||
searchPlaceholder='please search'
|
||||
treeLine maxTagTextLength={10}
|
||||
value={this.value}
|
||||
treeData={gData}
|
||||
treeNodeFilterProp='title'
|
||||
treeCheckable
|
||||
onChange={this.onChange}
|
||||
onSelect={this.onSelect}
|
||||
/>
|
||||
|
||||
<h2>use treeDataSimpleMode</h2>
|
||||
<TreeSelect
|
||||
style={{ width: '300px' }}
|
||||
dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
|
||||
placeholder={<i>请下拉选择</i>}
|
||||
searchPlaceholder='please search'
|
||||
treeLine maxTagTextLength={10}
|
||||
inputValue={null}
|
||||
value={this.value}
|
||||
treeData={this.simpleTreeData}
|
||||
treeDefaultExpandAll
|
||||
treeNodeFilterProp='title'
|
||||
treeDataSimpleMode={this.treeDataSimpleMode}
|
||||
treeCheckable showCheckedStrategy={SHOW_PARENT}
|
||||
onChange={this.onChange}
|
||||
onSelect={this.onSelect}
|
||||
/>
|
||||
<button onClick={this.onDataChange}>change data</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
import '../assets/index.less'
|
||||
import TreeSelect from '../index'
|
||||
import Select from '../../vc-select'
|
||||
import { createForm } from '../../vc-form'
|
||||
import { regionStyle, errorStyle } from './styles'
|
||||
import { gData } from './util'
|
||||
import '../../vc-select/assets/index.less'
|
||||
import './demo.less'
|
||||
|
||||
const { Option } = Select
|
||||
|
||||
const TreeSelectInput = {
|
||||
props: [
|
||||
'multiple',
|
||||
'treeData',
|
||||
'treeCheckable',
|
||||
'value',
|
||||
],
|
||||
methods: {
|
||||
onChange (value) {
|
||||
console.log(value, arguments)
|
||||
this.$emit('change', value)
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<TreeSelect {...{ props: this.$props }} onChange={this.onChange.bind(this)} />
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const Form = {
|
||||
methods: {
|
||||
onSubmit (e) {
|
||||
console.log('submit')
|
||||
e.preventDefault()
|
||||
this.form.validateFields((error, values) => {
|
||||
if (!error) {
|
||||
console.log('ok', values)
|
||||
} else {
|
||||
console.log('error', error, values)
|
||||
}
|
||||
})
|
||||
},
|
||||
reset (e) {
|
||||
e.preventDefault()
|
||||
this.form.resetFields()
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { form } = this
|
||||
const { getFieldDecorator, getFieldError } = form
|
||||
const tProps = {
|
||||
props: {
|
||||
multiple: true,
|
||||
treeData: gData,
|
||||
treeCheckable: true,
|
||||
// treeDefaultExpandAll: true,
|
||||
},
|
||||
}
|
||||
return (<div style={{ margin: '20px' }}>
|
||||
<h2>validity</h2>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<div style={regionStyle}>
|
||||
<div>
|
||||
<p style={{ color: 'blue' }}>no onChange</p>
|
||||
{getFieldDecorator('tree-select', {
|
||||
initialValue: ['0-0-0-value'],
|
||||
rules: [
|
||||
{ required: true, type: 'array', message: 'tree-select 需要必填' },
|
||||
],
|
||||
})(
|
||||
<TreeSelect
|
||||
{...tProps}
|
||||
style={{ width: '300px' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p style={errorStyle}>
|
||||
{(getFieldError('tree-select')) ? getFieldError('tree-select').join(',') : null}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={regionStyle}>
|
||||
<div>
|
||||
<p style={{ color: 'blue' }}>custom onChange</p>
|
||||
{getFieldDecorator('tree-select1', {
|
||||
initialValue: ['0-0-0-value'],
|
||||
rules: [
|
||||
{ required: true, type: 'array', message: 'tree-select1 需要必填' },
|
||||
],
|
||||
})(
|
||||
<TreeSelectInput
|
||||
{...tProps}
|
||||
style={{ width: '300px' }}
|
||||
// treeData={gData}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p style={errorStyle}>
|
||||
{(getFieldError('tree-select1')) ? getFieldError('tree-select1').join(',') : null}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={regionStyle}>
|
||||
{getFieldDecorator('select', {
|
||||
initialValue: ['jack'],
|
||||
rules: [
|
||||
{ required: true, type: 'array', message: 'select 需要必填' },
|
||||
],
|
||||
})(
|
||||
<Select
|
||||
style={{ width: '200px' }} allowClear multiple
|
||||
>
|
||||
<Option value='jack'>jack</Option>
|
||||
<Option value='lucy'>lucy</Option>
|
||||
<Option value='disabled' disabled>disabled</Option>
|
||||
<Option value='yiminghe'>yiminghe</Option>
|
||||
</Select>
|
||||
)}
|
||||
<p style={errorStyle}>
|
||||
{(getFieldError('select')) ? getFieldError('select').join(',') : null}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={regionStyle}>
|
||||
<button onClick={this.reset}>reset</button>
|
||||
|
||||
<input type='submit' value='submit'/>
|
||||
</div>
|
||||
</form>
|
||||
</div>)
|
||||
},
|
||||
}
|
||||
|
||||
export default createForm()(Form)
|
|
@ -0,0 +1,11 @@
|
|||
export const regionStyle = {
|
||||
border: '1px solid red',
|
||||
marginTop: '10px',
|
||||
padding: '10px',
|
||||
}
|
||||
|
||||
export const errorStyle = {
|
||||
color: 'red',
|
||||
marginTop: '10px',
|
||||
padding: '10px',
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
/* eslint no-loop-func: 0, no-console: 0 */
|
||||
|
||||
export function generateData (x = 3, y = 2, z = 1, gData = []) {
|
||||
// x:每一级下的节点总数。y:每级节点里有y个节点、存在子节点。z:树的level层级数(0表示一级)
|
||||
function _loop (_level, _preKey, _tns) {
|
||||
const preKey = _preKey || '0'
|
||||
const tns = _tns || gData
|
||||
|
||||
const children = []
|
||||
for (let i = 0; i < x; i++) {
|
||||
const key = `${preKey}-${i}`
|
||||
tns.push({
|
||||
label: `${key}-label`,
|
||||
value: `${key}-value`,
|
||||
key,
|
||||
disabled: key === '0-0-0-1' || false,
|
||||
})
|
||||
if (i < y) {
|
||||
children.push(key)
|
||||
}
|
||||
}
|
||||
if (_level < 0) {
|
||||
return tns
|
||||
}
|
||||
const __level = _level - 1
|
||||
children.forEach((key, index) => {
|
||||
tns[index].children = []
|
||||
return _loop(__level, key, tns[index].children)
|
||||
})
|
||||
}
|
||||
_loop(z)
|
||||
return gData
|
||||
}
|
||||
export function calcTotal (x = 3, y = 2, z = 1) {
|
||||
/* eslint no-param-reassign:0*/
|
||||
const rec = (n) => n >= 0 ? x * Math.pow(y, n--) + rec(n) : 0
|
||||
return rec(z + 1)
|
||||
}
|
||||
console.log('总节点数(单个tree):', calcTotal())
|
||||
export const gData = generateData()
|
||||
|
||||
export function generateTreeNodes (treeNode) {
|
||||
const arr = []
|
||||
const key = treeNode.eventKey
|
||||
for (let i = 0; i < 3; i++) {
|
||||
arr.push({ label: `${key}-${i}-label`, value: `${key}-${i}-value`, key: `${key}-${i}` })
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
function setLeaf (treeData, curKey, level) {
|
||||
const loopLeaf = (data, lev) => {
|
||||
const l = lev - 1
|
||||
data.forEach((item) => {
|
||||
if ((item.key.length > curKey.length) ? item.key.indexOf(curKey) !== 0
|
||||
: curKey.indexOf(item.key) !== 0) {
|
||||
return
|
||||
}
|
||||
if (item.children) {
|
||||
loopLeaf(item.children, l)
|
||||
} else if (l < 1) {
|
||||
item.isLeaf = true
|
||||
}
|
||||
})
|
||||
}
|
||||
loopLeaf(treeData, level + 1)
|
||||
}
|
||||
|
||||
export function getNewTreeData (treeData, curKey, child, level) {
|
||||
const loop = (data) => {
|
||||
if (level < 1 || curKey.length - 3 > level * 2) return
|
||||
data.forEach((item) => {
|
||||
if (curKey.indexOf(item.key) === 0) {
|
||||
if (item.children) {
|
||||
loop(item.children)
|
||||
} else {
|
||||
item.children = child
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
loop(treeData)
|
||||
setLeaf(treeData, curKey, level)
|
||||
}
|
||||
|
||||
function loopData (data, callback) {
|
||||
const loop = (d, level = 0) => {
|
||||
d.forEach((item, index) => {
|
||||
const pos = `${level}-${index}`
|
||||
if (item.children) {
|
||||
loop(item.children, pos)
|
||||
}
|
||||
callback(item, index, pos)
|
||||
})
|
||||
}
|
||||
loop(data)
|
||||
}
|
||||
|
||||
function isPositionPrefix (smallPos, bigPos) {
|
||||
if (bigPos.length < smallPos.length) {
|
||||
return false
|
||||
}
|
||||
// attention: "0-0-1" "0-0-10"
|
||||
if ((bigPos.length > smallPos.length) && (bigPos.charAt(smallPos.length) !== '-')) {
|
||||
return false
|
||||
}
|
||||
return bigPos.substr(0, smallPos.length) === smallPos
|
||||
}
|
||||
// console.log(isPositionPrefix("0-1", "0-10-1"));
|
||||
|
||||
export function getFilterValue (val, sVal, delVal) {
|
||||
const allPos = []
|
||||
const delPos = []
|
||||
loopData(gData, (item, index, pos) => {
|
||||
if (sVal.indexOf(item.value) > -1) {
|
||||
allPos.push(pos)
|
||||
}
|
||||
if (delVal.indexOf(item.value) > -1) {
|
||||
delPos.push(pos)
|
||||
}
|
||||
})
|
||||
const newPos = []
|
||||
delPos.forEach((item) => {
|
||||
allPos.forEach((i) => {
|
||||
if (isPositionPrefix(item, i) || isPositionPrefix(i, item)) {
|
||||
// 过滤掉 父级节点 和 所有子节点。
|
||||
// 因为 node节点 不选时,其 父级节点 和 所有子节点 都不选。
|
||||
return
|
||||
}
|
||||
newPos.push(i)
|
||||
})
|
||||
})
|
||||
const newVal = []
|
||||
if (newPos.length) {
|
||||
loopData(gData, (item, index, pos) => {
|
||||
if (newPos.indexOf(pos) > -1) {
|
||||
newVal.push(item.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
return newVal
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
// rc-tree-select 1.12.13 tag
|
||||
// export this package's api
|
||||
import TreeSelect from './src'
|
||||
|
||||
export default TreeSelect
|
||||
|
||||
export { TreeNode, SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './src'
|
|
@ -0,0 +1,108 @@
|
|||
import PropTypes from '../../_util/vue-types'
|
||||
import { SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './strategies'
|
||||
|
||||
function nonEmptyStringType (props, propsName) {
|
||||
const value = props[propsName]
|
||||
if (typeof value !== 'string' || !value) {
|
||||
return new Error() // Just a flag, so don't need message.
|
||||
}
|
||||
}
|
||||
|
||||
function valueType (props, propName, componentName) {
|
||||
const labelInValueShape = PropTypes.shape({
|
||||
value: nonEmptyStringType,
|
||||
label: PropTypes.node,
|
||||
})
|
||||
if (props.labelInValue) {
|
||||
const validate = PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(labelInValueShape),
|
||||
labelInValueShape,
|
||||
])
|
||||
const error = validate(...arguments)
|
||||
if (error) {
|
||||
return new Error(
|
||||
`Invalid prop \`${propName}\` supplied to \`${componentName}\`, ` +
|
||||
`when \`labelInValue\` is \`true\`, \`${propName}\` should in ` +
|
||||
`shape of \`{ value: string, label?: string }\`.`
|
||||
)
|
||||
}
|
||||
} else if (props.treeCheckable && props.treeCheckStrictly) {
|
||||
const validate = PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(labelInValueShape),
|
||||
labelInValueShape,
|
||||
])
|
||||
const error = validate(...arguments)
|
||||
if (error) {
|
||||
return new Error(
|
||||
`Invalid prop \`${propName}\` supplied to \`${componentName}\`, ` +
|
||||
`when \`treeCheckable\` and \`treeCheckStrictly\` are \`true\`, ` +
|
||||
`\`${propName}\` should in shape of \`{ value: string, label?: string }\`.`
|
||||
)
|
||||
}
|
||||
} else if (props.multiple && props[propName] === '') {
|
||||
return new Error(
|
||||
`Invalid prop \`${propName}\` of type \`string\` supplied to \`${componentName}\`, ` +
|
||||
`expected \`array\` when \`multiple\` is \`true\`.`
|
||||
)
|
||||
} else {
|
||||
const validate = PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.string),
|
||||
PropTypes.string,
|
||||
])
|
||||
return validate(...arguments)
|
||||
}
|
||||
}
|
||||
|
||||
export const SelectPropTypes = {
|
||||
// className: PropTypes.string,
|
||||
prefixCls: PropTypes.string,
|
||||
multiple: PropTypes.bool,
|
||||
filterTreeNode: PropTypes.any,
|
||||
showSearch: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
showArrow: PropTypes.bool,
|
||||
allowClear: PropTypes.bool,
|
||||
defaultOpen: PropTypes.bool,
|
||||
open: PropTypes.bool,
|
||||
transitionName: PropTypes.string,
|
||||
animation: PropTypes.string,
|
||||
choiceTransitionName: PropTypes.string,
|
||||
// onClick: PropTypes.func,
|
||||
// onChange: PropTypes.func,
|
||||
// onSelect: PropTypes.func,
|
||||
// onDeselect: PropTypes.func,
|
||||
// onSearch: PropTypes.func,
|
||||
searchPlaceholder: PropTypes.string,
|
||||
placeholder: PropTypes.any,
|
||||
inputValue: PropTypes.any,
|
||||
value: PropTypes.any,
|
||||
defaultValue: PropTypes.any,
|
||||
label: PropTypes.any, // vnode
|
||||
defaultLabel: PropTypes.any,
|
||||
labelInValue: PropTypes.bool,
|
||||
dropdownClassName: PropTypes.string,
|
||||
dropdownStyle: PropTypes.object,
|
||||
dropdownPopupAlign: PropTypes.object,
|
||||
dropdownVisibleChange: PropTypes.func,
|
||||
maxTagTextLength: PropTypes.number,
|
||||
showCheckedStrategy: PropTypes.oneOf([
|
||||
SHOW_ALL, SHOW_PARENT, SHOW_CHILD,
|
||||
]),
|
||||
treeCheckStrictly: PropTypes.bool,
|
||||
treeIcon: PropTypes.bool,
|
||||
treeLine: PropTypes.bool,
|
||||
treeDefaultExpandAll: PropTypes.bool,
|
||||
treeCheckable: PropTypes.any, // bool vnode
|
||||
treeNodeLabelProp: PropTypes.string,
|
||||
treeNodeFilterProp: PropTypes.string,
|
||||
treeData: PropTypes.array,
|
||||
treeDataSimpleMode: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.object,
|
||||
]),
|
||||
loadData: PropTypes.func,
|
||||
dropdownMatchSelectWidth: PropTypes.bool,
|
||||
notFoundContent: PropTypes.any,
|
||||
children: PropTypes.any,
|
||||
autoFocus: PropTypes.bool,
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,371 @@
|
|||
import PropTypes from '../../_util/vue-types'
|
||||
import classnames from 'classnames'
|
||||
import omit from 'omit.js'
|
||||
import Trigger from '../../trigger'
|
||||
import Tree, { TreeNode } from '../../vc-tree'
|
||||
import { SelectPropTypes } from './PropTypes'
|
||||
import BaseMixin from '../../_util/BaseMixin'
|
||||
import {
|
||||
loopAllChildren,
|
||||
flatToHierarchy,
|
||||
getValuePropValue,
|
||||
labelCompatible,
|
||||
saveRef,
|
||||
} from './util'
|
||||
|
||||
import { cloneElement } from '../../_util/vnode'
|
||||
import { isEmptyElement, getSlotOptions, getKey, getAllProps, getComponentFromProp } from '../../_util/props-util'
|
||||
import { noop } from '../../_util/vue-types/utils'
|
||||
|
||||
const BUILT_IN_PLACEMENTS = {
|
||||
bottomLeft: {
|
||||
points: ['tl', 'bl'],
|
||||
offset: [0, 4],
|
||||
overflow: {
|
||||
adjustX: 0,
|
||||
adjustY: 1,
|
||||
},
|
||||
},
|
||||
topLeft: {
|
||||
points: ['bl', 'tl'],
|
||||
offset: [0, -4],
|
||||
overflow: {
|
||||
adjustX: 0,
|
||||
adjustY: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const SelectTrigger = {
|
||||
mixins: [BaseMixin],
|
||||
name: 'SelectTrigger',
|
||||
props: {
|
||||
...SelectPropTypes,
|
||||
dropdownMatchSelectWidth: PropTypes.bool,
|
||||
dropdownPopupAlign: PropTypes.object,
|
||||
visible: PropTypes.bool,
|
||||
filterTreeNode: PropTypes.any,
|
||||
treeNodes: PropTypes.any,
|
||||
inputValue: PropTypes.string,
|
||||
prefixCls: PropTypes.string,
|
||||
popupClassName: PropTypes.string,
|
||||
_cachetreeData: PropTypes.any,
|
||||
_treeNodesStates: PropTypes.any,
|
||||
halfCheckedValues: PropTypes.any,
|
||||
inputElement: PropTypes.any,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
sExpandedKeys: [],
|
||||
fireOnExpand: false,
|
||||
dropdownWidth: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.$nextTick(() => {
|
||||
this.setDropdownWidth()
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
inputValue (val) {
|
||||
// set autoExpandParent to true
|
||||
this.setState({
|
||||
sExpandedKeys: [],
|
||||
fireOnExpand: false,
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
updated () {
|
||||
this.$nextTick(() => {
|
||||
this.setDropdownWidth()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
onExpand (expandedKeys) {
|
||||
// rerender
|
||||
this.setState({
|
||||
sExpandedKeys: expandedKeys,
|
||||
fireOnExpand: true,
|
||||
}, () => {
|
||||
// Fix https://github.com/ant-design/ant-design/issues/5689
|
||||
if (this.$refs.trigger && this.$refs.trigger.forcePopupAlign) {
|
||||
this.$refs.trigger.forcePopupAlign()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
setDropdownWidth () {
|
||||
const width = this.$el.offsetWidth
|
||||
if (width !== this.dropdownWidth) {
|
||||
this.setState({ dropdownWidth: width })
|
||||
}
|
||||
},
|
||||
|
||||
getPopupEleRefs () {
|
||||
return this.$refs.popupEle
|
||||
},
|
||||
|
||||
getPopupDOMNode () {
|
||||
return this.$refs.trigger.getPopupDomNode()
|
||||
},
|
||||
|
||||
getDropdownTransitionName () {
|
||||
const props = this.$props
|
||||
let transitionName = props.transitionName
|
||||
if (!transitionName && props.animation) {
|
||||
transitionName = `${this.getDropdownPrefixCls()}-${props.animation}`
|
||||
}
|
||||
return transitionName
|
||||
},
|
||||
|
||||
getDropdownPrefixCls () {
|
||||
return `${this.prefixCls}-dropdown`
|
||||
},
|
||||
|
||||
highlightTreeNode (treeNode) {
|
||||
const props = this.$props
|
||||
const filterVal = treeNode.$props[labelCompatible(props.treeNodeFilterProp)]
|
||||
if (typeof filterVal === 'string') {
|
||||
return props.inputValue && filterVal.indexOf(props.inputValue) > -1
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
filterTreeNode_ (input, child) {
|
||||
if (!input) {
|
||||
return true
|
||||
}
|
||||
const filterTreeNode = this.filterTreeNode
|
||||
if (!filterTreeNode) {
|
||||
return true
|
||||
}
|
||||
const props = getAllProps(child)
|
||||
if (props && props.disabled) {
|
||||
return false
|
||||
}
|
||||
return filterTreeNode.call(this, input, child)
|
||||
},
|
||||
|
||||
processTreeNode (treeNodes) {
|
||||
const filterPoss = []
|
||||
this._expandedKeys = []
|
||||
loopAllChildren(treeNodes, (child, index, pos) => {
|
||||
if (this.filterTreeNode_(this.inputValue, child)) {
|
||||
filterPoss.push(pos)
|
||||
this._expandedKeys.push(String(getKey(child)))
|
||||
}
|
||||
})
|
||||
|
||||
// Include the filtered nodes's ancestral nodes.
|
||||
const processedPoss = []
|
||||
filterPoss.forEach(pos => {
|
||||
const arr = pos.split('-')
|
||||
arr.reduce((pre, cur) => {
|
||||
const res = `${pre}-${cur}`
|
||||
if (processedPoss.indexOf(res) < 0) {
|
||||
processedPoss.push(res)
|
||||
}
|
||||
return res
|
||||
})
|
||||
})
|
||||
const filterNodesPositions = []
|
||||
loopAllChildren(treeNodes, (child, index, pos) => {
|
||||
if (processedPoss.indexOf(pos) > -1) {
|
||||
filterNodesPositions.push({ node: child, pos })
|
||||
}
|
||||
})
|
||||
|
||||
const hierarchyNodes = flatToHierarchy(filterNodesPositions)
|
||||
|
||||
const recursive = children => {
|
||||
return children.map(child => {
|
||||
if (child.children) {
|
||||
return cloneElement(child.node, {
|
||||
children: recursive(child.children),
|
||||
})
|
||||
}
|
||||
return child.node
|
||||
})
|
||||
}
|
||||
return recursive(hierarchyNodes)
|
||||
},
|
||||
onSelect () {
|
||||
this.__emit('select', ...arguments)
|
||||
},
|
||||
|
||||
renderTree (keys, halfCheckedKeys, newTreeNodes, multiple) {
|
||||
const props = this.$props
|
||||
|
||||
const trProps = {
|
||||
multiple,
|
||||
prefixCls: `${props.prefixCls}-tree`,
|
||||
showIcon: props.treeIcon,
|
||||
showLine: props.treeLine,
|
||||
defaultExpandAll: props.treeDefaultExpandAll,
|
||||
defaultExpandedKeys: props.treeDefaultExpandedKeys,
|
||||
filterTreeNode: this.highlightTreeNode,
|
||||
}
|
||||
const trListeners = {}
|
||||
|
||||
if (props.treeCheckable) {
|
||||
trProps.selectable = false
|
||||
trProps.checkable = props.treeCheckable
|
||||
trListeners.check = this.onSelect
|
||||
trProps.checkStrictly = props.treeCheckStrictly
|
||||
if (props.inputValue) {
|
||||
// enable checkStrictly when search tree.
|
||||
trProps.checkStrictly = true
|
||||
} else {
|
||||
trProps._treeNodesStates = props._treeNodesStates
|
||||
}
|
||||
if (trProps.treeCheckStrictly && halfCheckedKeys.length) {
|
||||
trProps.checkedKeys = { checked: keys, halfChecked: halfCheckedKeys }
|
||||
} else {
|
||||
trProps.checkedKeys = keys
|
||||
}
|
||||
} else {
|
||||
trProps.selectedKeys = keys
|
||||
trListeners.select = this.onSelect
|
||||
}
|
||||
|
||||
// expand keys
|
||||
if (!trProps.defaultExpandAll && !trProps.defaultExpandedKeys && !props.loadData) {
|
||||
trProps.expandedKeys = keys
|
||||
}
|
||||
trProps.autoExpandParent = true
|
||||
trListeners.expand = this.onExpand
|
||||
if (this._expandedKeys && this._expandedKeys.length) {
|
||||
trProps.expandedKeys = this._expandedKeys
|
||||
}
|
||||
if (this.fireOnExpand) {
|
||||
trProps.expandedKeys = this.sExpandedKeys
|
||||
trProps.autoExpandParent = false
|
||||
}
|
||||
|
||||
// async loadData
|
||||
if (props.loadData) {
|
||||
trProps.loadData = props.loadData
|
||||
}
|
||||
return (
|
||||
<Tree ref='popupEle' {...{ props: trProps, on: trListeners }}>
|
||||
{newTreeNodes}
|
||||
</Tree>
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const props = this.$props
|
||||
const multiple = props.multiple
|
||||
const dropdownPrefixCls = this.getDropdownPrefixCls()
|
||||
const popupClassName = {
|
||||
[props.dropdownClassName]: !!props.dropdownClassName,
|
||||
[`${dropdownPrefixCls}--${multiple ? 'multiple' : 'single'}`]: 1,
|
||||
}
|
||||
let visible = props.visible
|
||||
const search = multiple || !props.showSearch ? null : (
|
||||
<span class={`${dropdownPrefixCls}-search`}>{props.inputElement}</span>
|
||||
)
|
||||
const recursive = children => {
|
||||
return children.map(function handler(child) { // eslint-disable-line
|
||||
// if (isEmptyElement(child) || (child.data && child.data.slot)) {
|
||||
// return null
|
||||
// }
|
||||
if (!getSlotOptions(child).__ANT_TREE_SELECT_NODE) {
|
||||
return null
|
||||
}
|
||||
const treeNodeProps = {
|
||||
...child.data,
|
||||
props: {
|
||||
...getAllProps(child),
|
||||
title: getComponentFromProp(child, 'title') || getComponentFromProp(child, 'label'),
|
||||
},
|
||||
key: String(child.key),
|
||||
}
|
||||
if (child && child.componentOptions.children) {
|
||||
// null or String has no Prop
|
||||
return (
|
||||
<TreeNode {...treeNodeProps}>
|
||||
{recursive(child.componentOptions.children) }
|
||||
</TreeNode>
|
||||
)
|
||||
}
|
||||
return <TreeNode {...treeNodeProps} />
|
||||
})
|
||||
}
|
||||
// const s = Date.now();
|
||||
let treeNodes
|
||||
if (props._cachetreeData && this.cacheTreeNodes) {
|
||||
treeNodes = this.cacheTreeNodes
|
||||
} else {
|
||||
treeNodes = recursive(props.treeData || props.treeNodes)
|
||||
this.cacheTreeNodes = treeNodes
|
||||
}
|
||||
// console.log(Date.now()-s);
|
||||
|
||||
if (props.inputValue) {
|
||||
treeNodes = this.processTreeNode(treeNodes)
|
||||
}
|
||||
|
||||
const keys = []
|
||||
const halfCheckedKeys = []
|
||||
loopAllChildren(treeNodes, (child) => {
|
||||
if (props.value.some(item => item.value === getValuePropValue(child))) {
|
||||
keys.push(String(getKey(child)))
|
||||
}
|
||||
if (props.halfCheckedValues &&
|
||||
props.halfCheckedValues.some(item => item.value === getValuePropValue(child))) {
|
||||
halfCheckedKeys.push(String(getKey(child)))
|
||||
}
|
||||
})
|
||||
|
||||
let notFoundContent
|
||||
if (!treeNodes.length) {
|
||||
if (props.notFoundContent) {
|
||||
notFoundContent = (
|
||||
<span class={`${props.prefixCls}-not-found`}>
|
||||
{props.notFoundContent}
|
||||
</span>
|
||||
)
|
||||
} else if (!search) {
|
||||
visible = false
|
||||
}
|
||||
}
|
||||
const popupElement = (
|
||||
<div>
|
||||
{search}
|
||||
{notFoundContent || this.renderTree(keys, halfCheckedKeys, treeNodes, multiple)}
|
||||
</div>
|
||||
)
|
||||
|
||||
const popupStyle = { ...props.dropdownStyle }
|
||||
const widthProp = props.dropdownMatchSelectWidth ? 'width' : 'minWidth'
|
||||
if (this.dropdownWidth) {
|
||||
popupStyle[widthProp] = `${this.dropdownWidth}px`
|
||||
}
|
||||
|
||||
return (
|
||||
<Trigger
|
||||
action={props.disabled ? [] : ['click']}
|
||||
ref='trigger'
|
||||
popupPlacement='bottomLeft'
|
||||
builtinPlacements={BUILT_IN_PLACEMENTS}
|
||||
popupAlign={props.dropdownPopupAlign}
|
||||
prefixCls={dropdownPrefixCls}
|
||||
popupTransitionName={this.getDropdownTransitionName()}
|
||||
onPopupVisibleChange={props.dropdownVisibleChange}
|
||||
popup={popupElement}
|
||||
popupVisible={visible}
|
||||
getPopupContainer={props.getPopupContainer}
|
||||
popupClassName={classnames(popupClassName)}
|
||||
popupStyle={popupStyle}
|
||||
>
|
||||
{this.$slots.default}
|
||||
</Trigger>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default SelectTrigger
|
|
@ -0,0 +1,12 @@
|
|||
import { TreeNode } from '../../vc-tree'
|
||||
export default {
|
||||
name: 'TreeNode',
|
||||
__ANT_TREE_SELECT_NODE: true,
|
||||
props: {
|
||||
...TreeNode.props,
|
||||
value: String,
|
||||
},
|
||||
render () {
|
||||
return this
|
||||
},
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// export this package's api
|
||||
import TreeSelect from './Select'
|
||||
import TreeNode from './TreeNode'
|
||||
import omit from 'omit.js'
|
||||
import { SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './strategies'
|
||||
TreeSelect.TreeNode = TreeNode
|
||||
|
||||
export default {
|
||||
functional: true,
|
||||
render (h, context) {
|
||||
const { props, listeners, children = [], data } = context
|
||||
const treeSelectProps = {
|
||||
...omit(data, ['attrs']),
|
||||
props: {
|
||||
...props,
|
||||
children,
|
||||
__propsSymbol__: Symbol(),
|
||||
},
|
||||
on: listeners,
|
||||
}
|
||||
return <TreeSelect {...treeSelectProps}/>
|
||||
},
|
||||
TreeNode,
|
||||
SHOW_ALL, SHOW_PARENT, SHOW_CHILD,
|
||||
}
|
||||
export { TreeNode, SHOW_ALL, SHOW_PARENT, SHOW_CHILD }
|
|
@ -0,0 +1,3 @@
|
|||
export const SHOW_ALL = 'SHOW_ALL'
|
||||
export const SHOW_PARENT = 'SHOW_PARENT'
|
||||
export const SHOW_CHILD = 'SHOW_CHILD'
|
|
@ -0,0 +1,569 @@
|
|||
import { getPropsData, getAllProps, getKey, getAttrs, getSlotOptions, filterEmpty, getSlots } from '../../_util/props-util'
|
||||
import { cloneVNodes, cloneElement } from '../../_util/vnode'
|
||||
export function toTitle (title) {
|
||||
if (typeof title === 'string') {
|
||||
return title
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function getValuePropValue (child) {
|
||||
const props = getAllProps(child)
|
||||
if ('value' in props) {
|
||||
return props.value
|
||||
}
|
||||
if (getKey(child) !== undefined) {
|
||||
return getKey(child)
|
||||
}
|
||||
throw new Error(`no key or value for ${child}`)
|
||||
}
|
||||
|
||||
export function getPropValue (child, prop) {
|
||||
if (prop === 'value') {
|
||||
return getValuePropValue(child)
|
||||
}
|
||||
const slots = getSlots(child)
|
||||
if (prop === 'children') {
|
||||
const newChild = child.$slots ? cloneVNodes(child.$slots.default, true) : cloneVNodes(child.componentOptions.children, true)
|
||||
if (newChild.length === 1 && !newChild[0].tag) {
|
||||
return newChild[0].text
|
||||
}
|
||||
return newChild
|
||||
}
|
||||
if (slots[prop]) {
|
||||
return cloneVNodes(slots[prop], true)
|
||||
}
|
||||
const data = getPropsData(child)
|
||||
if (prop in data) {
|
||||
return data[prop]
|
||||
} else {
|
||||
return getAttrs(child)[prop]
|
||||
}
|
||||
}
|
||||
|
||||
export function isMultiple (props) {
|
||||
return !!(props.multiple || props.treeCheckable)
|
||||
}
|
||||
|
||||
export function toArray (value) {
|
||||
let ret = value
|
||||
if (value === undefined) {
|
||||
ret = []
|
||||
} else if (!Array.isArray(value)) {
|
||||
ret = [value]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
export function preventDefaultEvent (e) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
export const UNSELECTABLE_STYLE = {
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
}
|
||||
|
||||
export const UNSELECTABLE_ATTRIBUTE = {
|
||||
unselectable: 'unselectable',
|
||||
}
|
||||
|
||||
export function labelCompatible (prop) {
|
||||
let newProp = prop
|
||||
if (newProp === 'label') {
|
||||
newProp = 'title'
|
||||
}
|
||||
return newProp
|
||||
}
|
||||
|
||||
export function isInclude (smallArray, bigArray) {
|
||||
// attention: [0,0,1] [0,0,10]
|
||||
return smallArray.every((ii, i) => {
|
||||
return ii === bigArray[i]
|
||||
})
|
||||
}
|
||||
|
||||
export function isPositionPrefix (smallPos, bigPos) {
|
||||
if (!bigPos || !smallPos) {
|
||||
// console.log(smallPos, bigPos);
|
||||
return false
|
||||
}
|
||||
if (bigPos.length < smallPos.length) {
|
||||
return false
|
||||
}
|
||||
// attention: "0-0-1" "0-0-10"
|
||||
if ((bigPos.length > smallPos.length) && (bigPos.charAt(smallPos.length) !== '-')) {
|
||||
return false
|
||||
}
|
||||
return bigPos.substr(0, smallPos.length) === smallPos
|
||||
}
|
||||
|
||||
/*
|
||||
export function getCheckedKeys(node, checkedKeys, allCheckedNodesKeys) {
|
||||
const nodeKey = node.props.eventKey;
|
||||
let newCks = [...checkedKeys];
|
||||
let nodePos;
|
||||
const unCheck = allCheckedNodesKeys.some(item => {
|
||||
if (item.key === nodeKey) {
|
||||
nodePos = item.pos;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (unCheck) {
|
||||
newCks = [];
|
||||
allCheckedNodesKeys.forEach(item => {
|
||||
if (isPositionPrefix(item.pos, nodePos) || isPositionPrefix(nodePos, item.pos)) {
|
||||
return;
|
||||
}
|
||||
newCks.push(item.key);
|
||||
});
|
||||
} else {
|
||||
newCks.push(nodeKey);
|
||||
}
|
||||
return newCks;
|
||||
}
|
||||
*/
|
||||
|
||||
function getChildrenlength (children) {
|
||||
let len = 1
|
||||
if (Array.isArray(children)) {
|
||||
len = children.length
|
||||
}
|
||||
return len
|
||||
}
|
||||
|
||||
function getSiblingPosition (index, len, siblingPosition) {
|
||||
if (len === 1) {
|
||||
siblingPosition.first = true
|
||||
siblingPosition.last = true
|
||||
} else {
|
||||
siblingPosition.first = index === 0
|
||||
siblingPosition.last = index === len - 1
|
||||
}
|
||||
return siblingPosition
|
||||
}
|
||||
|
||||
function filterChild (childs) {
|
||||
const newChilds = []
|
||||
childs.forEach(child => {
|
||||
const options = getSlotOptions(child)
|
||||
if (options.__ANT_TREE_NODE || options.__ANT_TREE_SELECT_NODE) {
|
||||
newChilds.push(child)
|
||||
}
|
||||
})
|
||||
return newChilds
|
||||
}
|
||||
|
||||
export function loopAllChildren (childs, callback, parent) {
|
||||
const loop = (children, level, _parent) => {
|
||||
const len = getChildrenlength(children)
|
||||
children.forEach(function handler(item, index) { // eslint-disable-line
|
||||
const pos = `${level}-${index}`
|
||||
if (item && item.componentOptions && item.componentOptions.children) {
|
||||
loop(filterChild(item.componentOptions.children), pos, { node: item, pos })
|
||||
}
|
||||
if (item) {
|
||||
callback(item, index, pos, item.key || pos, getSiblingPosition(index, len, {}), _parent)
|
||||
}
|
||||
})
|
||||
}
|
||||
loop(filterChild(childs), 0, parent)
|
||||
}
|
||||
|
||||
// export function loopAllChildren(childs, callback) {
|
||||
// const loop = (children, level) => {
|
||||
// React.Children.forEach(children, (item, index) => {
|
||||
// const pos = `${level}-${index}`;
|
||||
// if (item && item.props.children) {
|
||||
// loop(item.props.children, pos);
|
||||
// }
|
||||
// if (item) {
|
||||
// callback(item, index, pos, getValuePropValue(item));
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
// loop(childs, 0);
|
||||
// }
|
||||
|
||||
// TODO: Here has the side effect. Update node children data affect.
|
||||
export function flatToHierarchy (arr) {
|
||||
if (!arr.length) {
|
||||
return arr
|
||||
}
|
||||
const hierarchyNodes = []
|
||||
const levelObj = {}
|
||||
arr.forEach((item) => {
|
||||
if (!item.pos) {
|
||||
return
|
||||
}
|
||||
const posLen = item.pos.split('-').length
|
||||
if (!levelObj[posLen]) {
|
||||
levelObj[posLen] = []
|
||||
}
|
||||
levelObj[posLen].push(item)
|
||||
})
|
||||
const levelArr = Object.keys(levelObj).sort((a, b) => b - a)
|
||||
// const s = Date.now();
|
||||
// todo: there are performance issues!
|
||||
levelArr.reduce((pre, cur) => {
|
||||
if (cur && cur !== pre) {
|
||||
levelObj[pre].forEach((item) => {
|
||||
let haveParent = false
|
||||
levelObj[cur].forEach((ii) => {
|
||||
if (isPositionPrefix(ii.pos, item.pos)) {
|
||||
haveParent = true
|
||||
if (!ii.children) {
|
||||
ii.children = []
|
||||
}
|
||||
ii.children.push(item)
|
||||
}
|
||||
})
|
||||
if (!haveParent) {
|
||||
hierarchyNodes.push(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
return cur
|
||||
})
|
||||
// console.log(Date.now() - s);
|
||||
return levelObj[levelArr[levelArr.length - 1]].concat(hierarchyNodes)
|
||||
}
|
||||
|
||||
// arr.length === 628, use time: ~20ms
|
||||
export function filterParentPosition (arr) {
|
||||
const levelObj = {}
|
||||
arr.forEach((item) => {
|
||||
const posLen = item.split('-').length
|
||||
if (!levelObj[posLen]) {
|
||||
levelObj[posLen] = []
|
||||
}
|
||||
levelObj[posLen].push(item)
|
||||
})
|
||||
const levelArr = Object.keys(levelObj).sort()
|
||||
for (let i = 0; i < levelArr.length; i++) {
|
||||
if (levelArr[i + 1]) {
|
||||
levelObj[levelArr[i]].forEach(ii => {
|
||||
for (let j = i + 1; j < levelArr.length; j++) {
|
||||
levelObj[levelArr[j]].forEach((_i, index) => {
|
||||
if (isPositionPrefix(ii, _i)) {
|
||||
levelObj[levelArr[j]][index] = null
|
||||
}
|
||||
})
|
||||
levelObj[levelArr[j]] = levelObj[levelArr[j]].filter(p => p)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
let nArr = []
|
||||
levelArr.forEach(i => {
|
||||
nArr = nArr.concat(levelObj[i])
|
||||
})
|
||||
return nArr
|
||||
}
|
||||
// console.log(filterParentPosition(
|
||||
// ['0-2', '0-3-3', '0-10', '0-10-0', '0-0-1', '0-0', '0-1-1', '0-1']
|
||||
// ));
|
||||
|
||||
function stripTail (str) {
|
||||
const arr = str.match(/(.+)(-[^-]+)$/)
|
||||
let st = ''
|
||||
if (arr && arr.length === 3) {
|
||||
st = arr[1]
|
||||
}
|
||||
return st
|
||||
}
|
||||
function splitPosition (pos) {
|
||||
return pos.split('-')
|
||||
}
|
||||
|
||||
// todo: do optimization.
|
||||
export function handleCheckState (obj, checkedPositionArr, checkIt) {
|
||||
// console.log(stripTail('0-101-000'));
|
||||
// let s = Date.now();
|
||||
let objKeys = Object.keys(obj)
|
||||
|
||||
objKeys.forEach((i, index) => {
|
||||
const iArr = splitPosition(i)
|
||||
let saved = false
|
||||
checkedPositionArr.forEach((_pos) => {
|
||||
const _posArr = splitPosition(_pos)
|
||||
if (iArr.length > _posArr.length && isInclude(_posArr, iArr)) {
|
||||
obj[i].halfChecked = false
|
||||
obj[i].checked = checkIt
|
||||
objKeys[index] = null
|
||||
}
|
||||
if (iArr[0] === _posArr[0] && iArr[1] === _posArr[1]) {
|
||||
saved = true
|
||||
}
|
||||
})
|
||||
if (!saved) {
|
||||
objKeys[index] = null
|
||||
}
|
||||
})
|
||||
objKeys = objKeys.filter(i => i) // filter non null;
|
||||
|
||||
for (let pIndex = 0; pIndex < checkedPositionArr.length; pIndex++) {
|
||||
// loop to set ancestral nodes's `checked` or `halfChecked`
|
||||
const loop = (__pos) => {
|
||||
const _posLen = splitPosition(__pos).length
|
||||
if (_posLen <= 2) { // e.g. '0-0', '0-1'
|
||||
return
|
||||
}
|
||||
let sibling = 0
|
||||
let siblingChecked = 0
|
||||
const parentPosition = stripTail(__pos)
|
||||
objKeys.forEach((i /* , index*/) => {
|
||||
const iArr = splitPosition(i)
|
||||
if (iArr.length === _posLen && isInclude(splitPosition(parentPosition), iArr)) {
|
||||
sibling++
|
||||
if (obj[i].checked) {
|
||||
siblingChecked++
|
||||
const _i = checkedPositionArr.indexOf(i)
|
||||
if (_i > -1) {
|
||||
checkedPositionArr.splice(_i, 1)
|
||||
if (_i <= pIndex) {
|
||||
pIndex--
|
||||
}
|
||||
}
|
||||
} else if (obj[i].halfChecked) {
|
||||
siblingChecked += 0.5
|
||||
}
|
||||
// objKeys[index] = null;
|
||||
}
|
||||
})
|
||||
// objKeys = objKeys.filter(i => i); // filter non null;
|
||||
const parent = obj[parentPosition]
|
||||
// not check, checked, halfChecked
|
||||
if (siblingChecked === 0) {
|
||||
parent.checked = false
|
||||
parent.halfChecked = false
|
||||
} else if (siblingChecked === sibling) {
|
||||
parent.checked = true
|
||||
parent.halfChecked = false
|
||||
} else {
|
||||
parent.halfChecked = true
|
||||
parent.checked = false
|
||||
}
|
||||
loop(parentPosition)
|
||||
}
|
||||
loop(checkedPositionArr[pIndex], pIndex)
|
||||
}
|
||||
// console.log(Date.now()-s, objKeys.length, checkIt);
|
||||
}
|
||||
|
||||
function getCheck (treeNodesStates, checkedPositions) {
|
||||
const halfCheckedKeys = []
|
||||
const checkedKeys = []
|
||||
const checkedNodes = []
|
||||
Object.keys(treeNodesStates).forEach((item) => {
|
||||
const itemObj = treeNodesStates[item]
|
||||
if (itemObj.checked) {
|
||||
checkedKeys.push(itemObj.key)
|
||||
// checkedNodes.push(getValuePropValue(itemObj.node));
|
||||
checkedNodes.push({ ...itemObj, pos: item })
|
||||
} else if (itemObj.halfChecked) {
|
||||
halfCheckedKeys.push(itemObj.key)
|
||||
}
|
||||
})
|
||||
return {
|
||||
halfCheckedKeys, checkedKeys, checkedNodes, treeNodesStates, checkedPositions,
|
||||
}
|
||||
}
|
||||
|
||||
export function getTreeNodesStates (children, values) {
|
||||
const checkedPositions = []
|
||||
const treeNodesStates = {}
|
||||
loopAllChildren(children, (item, index, pos, keyOrPos, siblingPosition) => {
|
||||
treeNodesStates[pos] = {
|
||||
node: item,
|
||||
key: keyOrPos,
|
||||
checked: false,
|
||||
halfChecked: false,
|
||||
siblingPosition,
|
||||
}
|
||||
if (values.indexOf(getValuePropValue(item)) !== -1) {
|
||||
treeNodesStates[pos].checked = true
|
||||
checkedPositions.push(pos)
|
||||
}
|
||||
})
|
||||
|
||||
handleCheckState(treeNodesStates, filterParentPosition(checkedPositions.sort()), true)
|
||||
|
||||
return getCheck(treeNodesStates, checkedPositions)
|
||||
}
|
||||
|
||||
// can add extra prop to every node.
|
||||
export function recursiveCloneChildren (children, cb = ch => ch) {
|
||||
// return React.Children.map(children, child => {
|
||||
return Array.from(children).map(child => {
|
||||
const newChild = cb(child)
|
||||
if (newChild && newChild.props && newChild.props.children) {
|
||||
return cloneElement(newChild, {
|
||||
children: recursiveCloneChildren(newChild.props.children, cb),
|
||||
})
|
||||
}
|
||||
return newChild
|
||||
})
|
||||
}
|
||||
// const newChildren = recursiveCloneChildren(children, child => {
|
||||
// const extraProps = {};
|
||||
// if (child && child.type && child.type.xxx) {
|
||||
// extraProps._prop = true;
|
||||
// return React.cloneElement(child, extraProps);
|
||||
// }
|
||||
// return child;
|
||||
// });
|
||||
|
||||
function recursiveGen (children, level = 0) {
|
||||
return children.map((child, index) => {
|
||||
const pos = `${level}-${index}`
|
||||
const props = getAllProps(child)
|
||||
const { title, label, value, ...rest } = props
|
||||
const { children: subChildren } = child.componentOptions
|
||||
const o = {
|
||||
...rest,
|
||||
title,
|
||||
label: label || title,
|
||||
value,
|
||||
key: child.key,
|
||||
_pos: pos,
|
||||
}
|
||||
if (subChildren) {
|
||||
o.children = recursiveGen(subChildren, pos)
|
||||
}
|
||||
return o
|
||||
})
|
||||
}
|
||||
|
||||
function recursive (children, cb) {
|
||||
children.forEach(item => {
|
||||
cb(item)
|
||||
if (item.children) {
|
||||
recursive(item.children, cb)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Get the tree's checkedNodes (todo: can merge to the `handleCheckState` function)
|
||||
// If one node checked, it's all children nodes checked.
|
||||
// If sibling nodes all checked, the parent checked.
|
||||
export function filterAllCheckedData (vs, treeNodes) {
|
||||
const vals = [...vs]
|
||||
if (!vals.length) {
|
||||
return vals
|
||||
}
|
||||
|
||||
const data = recursiveGen(treeNodes)
|
||||
const checkedNodesPositions = []
|
||||
|
||||
function checkChildren (children) {
|
||||
children.forEach(item => {
|
||||
if (item.__checked) {
|
||||
return
|
||||
}
|
||||
const ci = vals.indexOf(item.value)
|
||||
const childs = item.children
|
||||
if (ci > -1) {
|
||||
item.__checked = true
|
||||
checkedNodesPositions.push({ node: item, pos: item._pos })
|
||||
vals.splice(ci, 1)
|
||||
if (childs) {
|
||||
recursive(childs, child => {
|
||||
child.__checked = true
|
||||
checkedNodesPositions.push({ node: child, pos: child._pos })
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (childs) {
|
||||
checkChildren(childs)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function checkParent (children, parent = { root: true }) {
|
||||
let siblingChecked = 0
|
||||
children.forEach(item => {
|
||||
const childs = item.children
|
||||
if (childs && !item.__checked && !item.__halfChecked) {
|
||||
const p = checkParent(childs, item)
|
||||
if (p.__checked) {
|
||||
siblingChecked++
|
||||
} else if (p.__halfChecked) {
|
||||
siblingChecked += 0.5
|
||||
}
|
||||
} else if (item.__checked) {
|
||||
siblingChecked++
|
||||
} else if (item.__halfChecked) {
|
||||
siblingChecked += 0.5
|
||||
}
|
||||
})
|
||||
const len = children.length
|
||||
if (siblingChecked === len) {
|
||||
parent.__checked = true
|
||||
checkedNodesPositions.push({ node: parent, pos: parent._pos })
|
||||
} else if (siblingChecked < len && siblingChecked > 0) {
|
||||
parent.__halfChecked = true
|
||||
}
|
||||
if (parent.root) {
|
||||
return children
|
||||
}
|
||||
return parent
|
||||
}
|
||||
checkChildren(data)
|
||||
checkParent(data)
|
||||
|
||||
checkedNodesPositions.forEach((i, index) => {
|
||||
// clear private metadata
|
||||
delete checkedNodesPositions[index].node.__checked
|
||||
delete checkedNodesPositions[index].node._pos
|
||||
// create the same structure of `onCheck`'s return.
|
||||
checkedNodesPositions[index].node.props = {
|
||||
title: checkedNodesPositions[index].node.title,
|
||||
label: checkedNodesPositions[index].node.label || checkedNodesPositions[index].node.title,
|
||||
value: checkedNodesPositions[index].node.value,
|
||||
}
|
||||
if (checkedNodesPositions[index].node.children) {
|
||||
checkedNodesPositions[index].node.props.children = checkedNodesPositions[index].node.children
|
||||
}
|
||||
delete checkedNodesPositions[index].node.title
|
||||
delete checkedNodesPositions[index].node.label
|
||||
delete checkedNodesPositions[index].node.value
|
||||
delete checkedNodesPositions[index].node.children
|
||||
})
|
||||
return checkedNodesPositions
|
||||
}
|
||||
|
||||
export function processSimpleTreeData (treeData, format) {
|
||||
function unflatten2 (array, parent = { [format.id]: format.rootPId }) {
|
||||
const children = []
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
array[i] = { ...array[i] } // copy, can not corrupts original data
|
||||
if (array[i][format.pId] === parent[format.id]) {
|
||||
array[i].key = array[i][format.id]
|
||||
children.push(array[i])
|
||||
array.splice(i--, 1)
|
||||
}
|
||||
}
|
||||
if (children.length) {
|
||||
parent.children = children
|
||||
children.forEach(child => unflatten2(array, child))
|
||||
}
|
||||
if (parent[format.id] === format.rootPId) {
|
||||
return children
|
||||
}
|
||||
}
|
||||
return unflatten2(treeData)
|
||||
}
|
||||
|
||||
export function saveRef (instance, name) {
|
||||
if (!instance.saveRefs) {
|
||||
instance.saveRefs = {}
|
||||
}
|
||||
if (!instance.saveRefs[name]) {
|
||||
instance.saveRefs[name] = (node) => {
|
||||
instance[name] = node
|
||||
}
|
||||
}
|
||||
return instance.saveRefs[name]
|
||||
}
|
|
@ -558,7 +558,7 @@ const Tree = {
|
|||
tabIndex={focusable ? '0' : null}
|
||||
onKeydown={focusable ? this.onKeydown : () => {}}
|
||||
>
|
||||
{children.map(this.renderTreeNode)}
|
||||
{children.map((child, index) => this.renderTreeNode(child, index))}
|
||||
</ul>
|
||||
)
|
||||
},
|
||||
|
|
|
@ -21,6 +21,7 @@ let onlyTreeNodeWarned = false // Only accept TreeNode
|
|||
const TreeNode = {
|
||||
name: 'TreeNode',
|
||||
mixins: [BaseMixin],
|
||||
__ANT_TREE_NODE: true,
|
||||
props: initDefaultProps({
|
||||
eventKey: PropTypes.string, // Pass by parent `cloneElement`
|
||||
prefixCls: PropTypes.string,
|
||||
|
|
|
@ -332,6 +332,11 @@ export function calcCheckStateConduct (treeNodes, checkedKeys) {
|
|||
}
|
||||
}
|
||||
|
||||
// function keyListToString (keyList) {
|
||||
// if (!keyList) return keyList
|
||||
// return keyList.map(key => String(key))
|
||||
// }
|
||||
|
||||
/**
|
||||
* Calculate the value of checked and halfChecked keys.
|
||||
* This should be only run in init or props changed.
|
||||
|
@ -361,6 +366,9 @@ export function calcCheckedKeys (keys, props, children = []) {
|
|||
return null
|
||||
}
|
||||
|
||||
// keyProps.checkedKeys = keyListToString(keyProps.checkedKeys)
|
||||
// keyProps.halfCheckedKeys = keyListToString(keyProps.halfCheckedKeys)
|
||||
|
||||
// Do nothing if is checkStrictly mode
|
||||
if (checkStrictly) {
|
||||
return keyProps
|
||||
|
|
|
@ -45,7 +45,7 @@ import {
|
|||
Table,
|
||||
Transfer,
|
||||
Tree,
|
||||
// TreeSelect,
|
||||
TreeSelect,
|
||||
Tabs,
|
||||
Tag,
|
||||
TimePicker,
|
||||
|
@ -133,7 +133,8 @@ Vue.component(Table.ColumnGroup.name, Table.ColumnGroup)
|
|||
Vue.component(Transfer.name, Transfer)
|
||||
Vue.component(Tree.name, Tree)
|
||||
Vue.component(Tree.TreeNode.name, Tree.TreeNode)
|
||||
// Vue.component(TreeSelect.name, TreeSelect)
|
||||
Vue.component(TreeSelect.name, TreeSelect)
|
||||
Vue.component(TreeSelect.TreeNode.name, TreeSelect.TreeNode)
|
||||
Vue.component(Tabs.name, Tabs)
|
||||
Vue.component(Tabs.TabPane.name, Tabs.TabPane)
|
||||
Vue.component(Tag.name, Tag)
|
||||
|
|
|
@ -43,6 +43,7 @@ export { default as inputNumber } from 'antd/input-number/demo/index.vue'
|
|||
export { default as transfer } from 'antd/transfer/demo/index.vue'
|
||||
export { default as upload } from 'antd/upload/demo/index.vue'
|
||||
export { default as tree } from 'antd/tree/demo/index.vue'
|
||||
export { default as treeSelect } from 'antd/tree-select/demo/index.vue'
|
||||
export { default as layout } from 'antd/layout/demo/index.vue'
|
||||
export { default as form } from 'antd/form/demo/index.vue'
|
||||
export { default as anchor } from 'antd/anchor/demo/index.vue'
|
||||
|
|
|
@ -48,6 +48,7 @@ Array [
|
|||
"Table",
|
||||
"Transfer",
|
||||
"Tree",
|
||||
"TreeSelect",
|
||||
"Tabs",
|
||||
"Tag",
|
||||
"TimePicker",
|
||||
|
|
Loading…
Reference in New Issue