forked from p96170835/amis
nested-select支持搜索,并优化展示
This commit is contained in:
parent
f53d02fd08
commit
db21de1c54
|
@ -2,18 +2,21 @@
|
||||||
|
|
||||||
树形结构选择框。
|
树形结构选择框。
|
||||||
|
|
||||||
- `type` 请设置成 `nested-select`
|
- `type` 请设置成 `nested-select`
|
||||||
- `options` 类似于 [select](./Select.md) 中 `options`, 并且支持通过 `children` 无限嵌套。
|
- `options` 类似于 [select](./Select.md) 中 `options`, 并且支持通过 `children` 无限嵌套。
|
||||||
- `source` Api 地址,如果选项不固定,可以通过配置 `source` 动态拉取。
|
- `source` Api 地址,如果选项不固定,可以通过配置 `source` 动态拉取。
|
||||||
- `multiple` 默认为 `false`, 设置成 `true` 表示可多选。
|
- `multiple` 默认为 `false`, 设置成 `true` 表示可多选。
|
||||||
- `joinValues` 默认为 `true`
|
- `searchable` 默认为 `false`, 是否可搜索
|
||||||
- 单选模式:当用户选中某个选项时,选项中的 value 将被作为该表单项的值提交,否则,整个选项对象都会作为该表单项的值提交。
|
- `joinValues` 默认为 `true`
|
||||||
- 多选模式:选中的多个选项的 `value` 会通过 `delimiter` 连接起来,否则直接将以数组的形式提交值。
|
- 单选模式:当用户选中某个选项时,选项中的 value 将被作为该表单项的值提交,否则,整个选项对象都会作为该表单项的值提交。
|
||||||
- `extractValue` 默认为 `false`, `joinValues`设置为`false`时生效, 开启后将选中的选项 value 的值封装为数组,作为当前表单项的值。
|
- 多选模式:选中的多个选项的 `value` 会通过 `delimiter` 连接起来,否则直接将以数组的形式提交值。
|
||||||
- `delimiter` 默认为 `,`
|
- `extractValue` 默认为 `false`, `joinValues`设置为`false`时生效, 开启后将选中的选项 value 的值封装为数组,作为当前表单项的值。
|
||||||
- `autoFill` 将当前已选中的选项的某个字段的值自动填充到表单中某个表单项中,只在单选时有效
|
- `delimiter` 默认为 `,`
|
||||||
- `autoFill`的格式为`{address: "${label}"}`,表示将选中项中的`label`的值,自动填充到当前表单项中`name` 为`address` 中
|
- `autoFill` 将当前已选中的选项的某个字段的值自动填充到表单中某个表单项中,只在单选时有效
|
||||||
- **还有更多通用配置请参考** [FormItem](./FormItem.md)
|
- `autoFill`的格式为`{address: "${label}"}`,表示将选中项中的`label`的值,自动填充到当前表单项中`name` 为`address` 中
|
||||||
|
- `cascade` 设置成 `true` 时当选中父节点时不自动选择子节点。
|
||||||
|
- `withChildren` 是指成 `true`,选中父节点时,值里面将包含子节点的值,否则只会保留父节点的值。
|
||||||
|
- **还有更多通用配置请参考** [FormItem](./FormItem.md)
|
||||||
|
|
||||||
```schema:height="300" scope="form-item"
|
```schema:height="300" scope="form-item"
|
||||||
{
|
{
|
||||||
|
|
|
@ -52,6 +52,12 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.#{$ns}Select-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: px2rem(10px);
|
||||||
|
}
|
||||||
|
|
||||||
&-clear {
|
&-clear {
|
||||||
padding: px2rem(3px);
|
padding: px2rem(3px);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -84,15 +90,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-menuOuter,
|
&-menuOuter {
|
||||||
&-childrenOuter {
|
display: flex;
|
||||||
z-index: 10;
|
}
|
||||||
position: absolute;
|
|
||||||
|
&-menu {
|
||||||
|
width: 160px;
|
||||||
|
max-width: 160px;
|
||||||
|
max-height: px2rem(300px);
|
||||||
background: $Form-select-menu-bg;
|
background: $Form-select-menu-bg;
|
||||||
color: $Form-select-menu-color;
|
color: $Form-select-menu-color;
|
||||||
border: $Form-select-outer-borderWidth solid
|
border: $Form-select-outer-borderWidth solid
|
||||||
$Form-input-onFocused-borderColor;
|
$Form-input-onFocused-borderColor;
|
||||||
box-shadow: $Form-select-outer-boxShadow;
|
box-shadow: $Form-select-outer-boxShadow;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.#{$ns}NestedSelect-option {
|
.#{$ns}NestedSelect-option {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -100,6 +116,12 @@
|
||||||
height: $Form-input-height;
|
height: $Form-input-height;
|
||||||
line-height: $Form-input-height;
|
line-height: $Form-input-height;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> label {
|
||||||
|
flex: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
&.is-active {
|
&.is-active {
|
||||||
color: $Form-select-menu-onActive-color;
|
color: $Form-select-menu-onActive-color;
|
||||||
|
@ -125,14 +147,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-childrenOuter {
|
|
||||||
display: none;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
left: 100%;
|
|
||||||
transform: translateY(-$Form-input-height);
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,17 @@ import Checkbox from '../../components/Checkbox';
|
||||||
import PopOver from '../../components/PopOver';
|
import PopOver from '../../components/PopOver';
|
||||||
import {RootCloseWrapper} from 'react-overlays';
|
import {RootCloseWrapper} from 'react-overlays';
|
||||||
import {Icon} from '../../components/icons';
|
import {Icon} from '../../components/icons';
|
||||||
import {autobind, flattenTree, isEmpty} from '../../utils/helper';
|
import {
|
||||||
|
autobind,
|
||||||
|
flattenTree,
|
||||||
|
isEmpty,
|
||||||
|
filterTreeWithChildren
|
||||||
|
} from '../../utils/helper';
|
||||||
import {dataMapping} from '../../utils/tpl-builtin';
|
import {dataMapping} from '../../utils/tpl-builtin';
|
||||||
|
|
||||||
import {OptionsControl, OptionsControlProps, Option} from '../Form/Options';
|
import {OptionsControl, OptionsControlProps, Option} from '../Form/Options';
|
||||||
|
import Input from '../../components/Input';
|
||||||
|
import {findDOMNode} from 'react-dom';
|
||||||
|
import {cloneDeep} from 'lodash';
|
||||||
|
|
||||||
export interface NestedSelectProps extends OptionsControlProps {
|
export interface NestedSelectProps extends OptionsControlProps {
|
||||||
cascade?: boolean;
|
cascade?: boolean;
|
||||||
|
@ -18,6 +25,9 @@ export interface NestedSelectProps extends OptionsControlProps {
|
||||||
|
|
||||||
export interface NestedSelectState {
|
export interface NestedSelectState {
|
||||||
isOpened?: boolean;
|
isOpened?: boolean;
|
||||||
|
isFocused?: boolean;
|
||||||
|
inputValue?: string;
|
||||||
|
stack?: Array<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class NestedSelectControl extends React.Component<
|
export default class NestedSelectControl extends React.Component<
|
||||||
|
@ -26,12 +36,17 @@ export default class NestedSelectControl extends React.Component<
|
||||||
> {
|
> {
|
||||||
static defaultProps: Partial<NestedSelectProps> = {
|
static defaultProps: Partial<NestedSelectProps> = {
|
||||||
cascade: false,
|
cascade: false,
|
||||||
withChildren: false
|
withChildren: false,
|
||||||
|
searchPromptText: '输入内容进行检索'
|
||||||
};
|
};
|
||||||
target: any;
|
target: any;
|
||||||
|
input: HTMLInputElement;
|
||||||
alteredOptions: any;
|
alteredOptions: any;
|
||||||
state = {
|
state = {
|
||||||
isOpened: false
|
isOpened: false,
|
||||||
|
isFocused: false,
|
||||||
|
inputValue: '',
|
||||||
|
stack: []
|
||||||
};
|
};
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
|
@ -41,9 +56,11 @@ export default class NestedSelectControl extends React.Component<
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
open() {
|
open() {
|
||||||
if (!this.props.disabled) {
|
const {options, disabled} = this.props;
|
||||||
|
if (!disabled) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isOpened: true
|
isOpened: true,
|
||||||
|
stack: [options]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +68,8 @@ export default class NestedSelectControl extends React.Component<
|
||||||
@autobind
|
@autobind
|
||||||
close() {
|
close() {
|
||||||
this.setState({
|
this.setState({
|
||||||
isOpened: false
|
isOpened: false,
|
||||||
|
stack: []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +119,10 @@ export default class NestedSelectControl extends React.Component<
|
||||||
onBulkChange
|
onBulkChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
if (multiple) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const sendTo =
|
const sendTo =
|
||||||
|
@ -120,7 +142,7 @@ export default class NestedSelectControl extends React.Component<
|
||||||
!multiple && this.close();
|
!multiple && this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCheck(option: any | Array<any>) {
|
handleCheck(option: any | Array<any>, index?: number) {
|
||||||
const {
|
const {
|
||||||
onChange,
|
onChange,
|
||||||
selectedOptions,
|
selectedOptions,
|
||||||
|
@ -129,8 +151,27 @@ export default class NestedSelectControl extends React.Component<
|
||||||
delimiter,
|
delimiter,
|
||||||
extractValue,
|
extractValue,
|
||||||
withChildren,
|
withChildren,
|
||||||
cascade
|
cascade,
|
||||||
|
multiple
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
const {stack} = this.state;
|
||||||
|
|
||||||
|
if (
|
||||||
|
option.children &&
|
||||||
|
option.children.length &&
|
||||||
|
typeof index === 'number'
|
||||||
|
) {
|
||||||
|
const checked = selectedOptions.some(o => o.value == option.value);
|
||||||
|
const uncheckable = cascade
|
||||||
|
? false
|
||||||
|
: option.uncheckable || (multiple && !checked);
|
||||||
|
const children = option.children.map(c => ({...c, uncheckable}));
|
||||||
|
if (stack[index]) {
|
||||||
|
stack.splice(index + 1, 1, children);
|
||||||
|
} else {
|
||||||
|
stack.push(children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const items = selectedOptions.concat();
|
const items = selectedOptions.concat();
|
||||||
let newValue;
|
let newValue;
|
||||||
|
@ -148,13 +189,18 @@ export default class NestedSelectControl extends React.Component<
|
||||||
newValue = xorBy(items, [option], valueField || 'value');
|
newValue = xorBy(items, [option], valueField || 'value');
|
||||||
} else if (withChildren) {
|
} else if (withChildren) {
|
||||||
option = flattenTree([option]);
|
option = flattenTree([option]);
|
||||||
const fn = option.every((opt: any) => !!~items.indexOf(opt))
|
const fn = option.every(
|
||||||
|
(opt: any) => !!~items.findIndex(item => item.value === opt.value)
|
||||||
|
)
|
||||||
? xorBy
|
? xorBy
|
||||||
: unionBy;
|
: unionBy;
|
||||||
newValue = fn(items, option, valueField || 'value');
|
newValue = fn(items, option, valueField || 'value');
|
||||||
} else {
|
} else {
|
||||||
newValue = items.filter(item => !~flattenTree([option]).indexOf(item));
|
newValue = items.filter(
|
||||||
!~items.indexOf(option) && newValue.push(option);
|
item => !~flattenTree([option], i => i.value).indexOf(item.value)
|
||||||
|
);
|
||||||
|
!~items.map(item => item.value).indexOf(option.value) &&
|
||||||
|
newValue.push(option);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newValue = xorBy(items, [option], valueField || 'value');
|
newValue = xorBy(items, [option], valueField || 'value');
|
||||||
|
@ -172,22 +218,24 @@ export default class NestedSelectControl extends React.Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
allChecked(options: Array<any>): boolean {
|
allChecked(options: Array<any>): boolean {
|
||||||
|
const {selectedOptions, withChildren} = this.props;
|
||||||
return options.every((option: any) => {
|
return options.every((option: any) => {
|
||||||
if (option.children) {
|
if (withChildren && option.children) {
|
||||||
return this.allChecked(option.children);
|
return this.allChecked(option.children);
|
||||||
}
|
}
|
||||||
return this.props.selectedOptions.some(
|
return selectedOptions.some(
|
||||||
selectedOption => selectedOption.value == option.value
|
selectedOption => selectedOption.value == option.value
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
partialChecked(options: Array<any>): boolean {
|
partialChecked(options: Array<any>): boolean {
|
||||||
|
const {selectedOptions, withChildren} = this.props;
|
||||||
return options.some((option: any) => {
|
return options.some((option: any) => {
|
||||||
if (option.children) {
|
if (withChildren && option.children) {
|
||||||
return this.partialChecked(option.children);
|
return this.partialChecked(option.children);
|
||||||
}
|
}
|
||||||
return this.props.selectedOptions.some(
|
return selectedOptions.some(
|
||||||
selectedOption => selectedOption.value == option.value
|
selectedOption => selectedOption.value == option.value
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -198,11 +246,79 @@ export default class NestedSelectControl extends React.Component<
|
||||||
reload && reload();
|
reload && reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderOptions(
|
@autobind
|
||||||
newOptions: Array<any>,
|
onFocus(e: any) {
|
||||||
isChildren: boolean,
|
this.props.disabled ||
|
||||||
uncheckable: boolean
|
this.state.isOpened ||
|
||||||
): any {
|
this.setState(
|
||||||
|
{
|
||||||
|
isFocused: true
|
||||||
|
},
|
||||||
|
this.focus
|
||||||
|
);
|
||||||
|
|
||||||
|
this.props.onFocus && this.props.onFocus(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
onBlur(e: any) {
|
||||||
|
this.setState({
|
||||||
|
isFocused: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.onBlur && this.props.onBlur(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
focus() {
|
||||||
|
this.input
|
||||||
|
? this.input.focus()
|
||||||
|
: this.getTarget() && this.getTarget().focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
blur() {
|
||||||
|
this.input
|
||||||
|
? this.input.blur()
|
||||||
|
: this.getTarget() && this.getTarget().blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
getTarget() {
|
||||||
|
if (!this.target) {
|
||||||
|
this.target = findDOMNode(this) as HTMLElement;
|
||||||
|
}
|
||||||
|
return this.target as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
inputRef(ref: HTMLInputElement) {
|
||||||
|
this.input = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleInputChange(evt: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
const inputValue = evt.currentTarget.value;
|
||||||
|
const {options, labelField, valueField} = this.props;
|
||||||
|
|
||||||
|
let filtedOptions =
|
||||||
|
inputValue && this.state.isOpened
|
||||||
|
? filterTreeWithChildren(cloneDeep(options), option => {
|
||||||
|
const reg = new RegExp(`${inputValue}`, 'i');
|
||||||
|
return (
|
||||||
|
reg.test(option[labelField || 'label']) ||
|
||||||
|
reg.test(option[valueField || 'value'])
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: options.concat();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
inputValue,
|
||||||
|
stack: [filtedOptions]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderOptions(): any {
|
||||||
const {
|
const {
|
||||||
multiple,
|
multiple,
|
||||||
selectedOptions,
|
selectedOptions,
|
||||||
|
@ -210,87 +326,119 @@ export default class NestedSelectControl extends React.Component<
|
||||||
value,
|
value,
|
||||||
options,
|
options,
|
||||||
disabled,
|
disabled,
|
||||||
cascade
|
searchable,
|
||||||
|
searchPromptText
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (multiple) {
|
const stack = this.state.stack;
|
||||||
let partialChecked = this.partialChecked(options);
|
|
||||||
let allChecked = this.allChecked(options);
|
|
||||||
|
|
||||||
return (
|
const searchInput = searchable ? (
|
||||||
<div className={cx({'NestedSelect-childrenOuter': isChildren})}>
|
<div
|
||||||
{!isChildren ? (
|
className={cx(`Select-input`, {
|
||||||
<div className={cx('NestedSelect-option', 'checkall')}>
|
'is-focused': this.state.isFocused
|
||||||
<Checkbox
|
})}
|
||||||
onChange={this.handleCheck.bind(this, options)}
|
>
|
||||||
checked={partialChecked}
|
<Icon icon="search" className="icon" />
|
||||||
partial={partialChecked && !allChecked}
|
<Input
|
||||||
>
|
value={this.state.inputValue}
|
||||||
全选
|
onFocus={this.onFocus}
|
||||||
</Checkbox>
|
onBlur={this.onBlur}
|
||||||
</div>
|
disabled={disabled}
|
||||||
) : null}
|
placeholder={searchPromptText}
|
||||||
{newOptions.map((option, idx) => {
|
onChange={this.handleInputChange}
|
||||||
const checked = selectedOptions.some(o => o.value == option.value);
|
ref={this.inputRef}
|
||||||
const selfChecked = !!uncheckable || checked;
|
/>
|
||||||
let nodeDisabled = !!uncheckable || !!disabled;
|
</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
return (
|
let partialChecked = this.partialChecked(options);
|
||||||
<div className={cx('NestedSelect-option')} key={idx}>
|
let allChecked = this.allChecked(options);
|
||||||
<Checkbox
|
|
||||||
onChange={this.handleCheck.bind(this, option)}
|
|
||||||
trueValue={option.value}
|
|
||||||
checked={selfChecked}
|
|
||||||
disabled={nodeDisabled}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</Checkbox>
|
|
||||||
{option.children ? (
|
|
||||||
<div className={cx('NestedSelect-optionArrowRight')}>
|
|
||||||
<Icon icon="right-arrow" className="icon" />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{option.children && option.children.length
|
|
||||||
? this.renderOptions(
|
|
||||||
option.children,
|
|
||||||
true,
|
|
||||||
cascade ? false : uncheckable || (multiple && checked)
|
|
||||||
)
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx({'NestedSelect-childrenOuter': isChildren})}>
|
<>
|
||||||
{newOptions.map((option, idx) => (
|
{stack.map((options, index) => (
|
||||||
<div
|
<div key={index} className={cx('NestedSelect-menu')}>
|
||||||
key={idx}
|
{index === 0 ? searchInput : null}
|
||||||
className={cx('NestedSelect-option', {
|
{multiple && index === 0 ? (
|
||||||
'is-active': value && value === option.value
|
<div className={cx('NestedSelect-option', 'checkall')}>
|
||||||
})}
|
<Checkbox
|
||||||
onClick={this.handleOptionClick.bind(this, option)}
|
onChange={this.handleCheck.bind(this, options)}
|
||||||
>
|
checked={partialChecked}
|
||||||
<span>{option.label}</span>
|
partial={partialChecked && !allChecked}
|
||||||
{option.children ? (
|
>
|
||||||
<div className={cx('NestedSelect-optionArrowRight')}>
|
全选
|
||||||
<Icon icon="right-arrow" className="icon" />
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{option.children && option.children.length
|
|
||||||
? this.renderOptions(option.children, true, false)
|
{options.map((option, idx) => {
|
||||||
: null}
|
const checked = selectedOptions.some(
|
||||||
|
o => o.value == option.value
|
||||||
|
);
|
||||||
|
const selfChecked = !!option.uncheckable || checked;
|
||||||
|
let nodeDisabled = !!option.uncheckable || !!disabled;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className={cx('NestedSelect-option', {
|
||||||
|
'is-active': value && value === option.value
|
||||||
|
})}
|
||||||
|
onClick={this.handleOptionClick.bind(this, option)}
|
||||||
|
onMouseEnter={this.onMouseEnter.bind(this, option, index)}
|
||||||
|
>
|
||||||
|
{multiple ? (
|
||||||
|
<Checkbox
|
||||||
|
onChange={this.handleCheck.bind(this, option, index)}
|
||||||
|
trueValue={option.value}
|
||||||
|
checked={selfChecked}
|
||||||
|
disabled={nodeDisabled}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</Checkbox>
|
||||||
|
) : (
|
||||||
|
<span>{option.label}</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{option.children && option.children.length ? (
|
||||||
|
<div className={cx('NestedSelect-optionArrowRight')}>
|
||||||
|
<Icon icon="right-arrow" className="icon" />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMouseEnter(option: Option, index: number, e: MouseEvent) {
|
||||||
|
let {stack} = this.state;
|
||||||
|
let {cascade, multiple, selectedOptions} = this.props;
|
||||||
|
index = index + 1;
|
||||||
|
|
||||||
|
if (option.children && option.children.length) {
|
||||||
|
const checked = selectedOptions.some(o => o.value == option.value);
|
||||||
|
const uncheckable = cascade
|
||||||
|
? false
|
||||||
|
: option.uncheckable || (multiple && checked);
|
||||||
|
const children = option.children.map(c => ({...c, uncheckable}));
|
||||||
|
if (stack[index]) {
|
||||||
|
stack.splice(index, 1, children);
|
||||||
|
} else {
|
||||||
|
stack.push(children);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stack[index] && stack.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({stack});
|
||||||
|
}
|
||||||
|
|
||||||
renderOuter() {
|
renderOuter() {
|
||||||
const {popOverContainer, options, classnames: cx} = this.props;
|
const {popOverContainer, classnames: cx} = this.props;
|
||||||
|
|
||||||
let body = (
|
let body = (
|
||||||
<RootCloseWrapper
|
<RootCloseWrapper
|
||||||
|
@ -301,24 +449,25 @@ export default class NestedSelectControl extends React.Component<
|
||||||
className={cx('NestedSelect-menuOuter')}
|
className={cx('NestedSelect-menuOuter')}
|
||||||
style={{minWidth: this.target.offsetWidth}}
|
style={{minWidth: this.target.offsetWidth}}
|
||||||
>
|
>
|
||||||
{this.renderOptions(options, false, false)}
|
{this.renderOptions()}
|
||||||
</div>
|
</div>
|
||||||
</RootCloseWrapper>
|
</RootCloseWrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (popOverContainer) {
|
return (
|
||||||
return (
|
<Overlay
|
||||||
<Overlay container={popOverContainer} target={() => this.target} show>
|
container={popOverContainer || this.getTarget}
|
||||||
<PopOver
|
target={this.getTarget}
|
||||||
className={cx('NestedSelect-popover')}
|
show
|
||||||
style={{minWidth: this.target.offsetWidth}}
|
>
|
||||||
>
|
<PopOver
|
||||||
{body}
|
className={cx('NestedSelect-popover')}
|
||||||
</PopOver>
|
style={{minWidth: this.target.offsetWidth}}
|
||||||
</Overlay>
|
>
|
||||||
);
|
{body}
|
||||||
}
|
</PopOver>
|
||||||
return body;
|
</Overlay>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -66,7 +66,7 @@ export default class Page extends React.Component<PageProps> {
|
||||||
|
|
||||||
static propsList: Array<string> = [
|
static propsList: Array<string> = [
|
||||||
'title',
|
'title',
|
||||||
'subtitle',
|
'subTitle',
|
||||||
'initApi',
|
'initApi',
|
||||||
'initFetchOn',
|
'initFetchOn',
|
||||||
'initFetch',
|
'initFetch',
|
||||||
|
|
|
@ -848,6 +848,36 @@ export function filterTree<T extends TreeItem>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这个和 filterTree 的区别是,这个会保留 children 也符合匹配的节点
|
||||||
|
*
|
||||||
|
* @param tree
|
||||||
|
* @param iterator
|
||||||
|
* @param level
|
||||||
|
*/
|
||||||
|
export function filterTreeWithChildren<T extends TreeItem>(
|
||||||
|
tree: Array<T>,
|
||||||
|
iterator: (item: T, key: number, level: number) => boolean,
|
||||||
|
level: number = 1
|
||||||
|
): Array<T> {
|
||||||
|
return tree.filter((item, index) => {
|
||||||
|
if (iterator(item, index, level)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.children && item.children.splice) {
|
||||||
|
item.children = filterTreeWithChildren(
|
||||||
|
item.children,
|
||||||
|
iterator,
|
||||||
|
level + 1
|
||||||
|
);
|
||||||
|
return item.children?.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断树中每个节点是否满足某个条件。
|
* 判断树中每个节点是否满足某个条件。
|
||||||
* @param tree
|
* @param tree
|
||||||
|
|
Loading…
Reference in New Issue