mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
fix: select 各种模式支持 checkAll (#5941)
This commit is contained in:
parent
3e5bc113ab
commit
ab3cbd7d1a
@ -270,11 +270,13 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 100%;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&-col {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
min-width: 150px;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
&-col:not(:last-child) {
|
||||
@ -287,6 +289,10 @@
|
||||
color: var(--text--muted-color);
|
||||
}
|
||||
|
||||
&-checkAll {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
// height: var(--Form-input-height);
|
||||
@ -326,6 +332,9 @@
|
||||
|
||||
&-itemLabel {
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
span {
|
||||
margin-left: px2rem(10px);
|
||||
|
@ -126,7 +126,9 @@ export class AssociatedSelection extends BaseSelection<
|
||||
labelField,
|
||||
virtualThreshold,
|
||||
itemHeight,
|
||||
loadingConfig
|
||||
loadingConfig,
|
||||
checkAll,
|
||||
checkAllLabel
|
||||
} = this.props;
|
||||
|
||||
const selectdOption = BaseSelection.resolveSelected(
|
||||
@ -214,6 +216,8 @@ export class AssociatedSelection extends BaseSelection<
|
||||
virtualThreshold={virtualThreshold}
|
||||
itemHeight={itemHeight}
|
||||
loadingConfig={loadingConfig}
|
||||
checkAllLabel={checkAllLabel}
|
||||
checkAll={checkAll}
|
||||
/>
|
||||
) : rightMode === 'chained' ? (
|
||||
<ChainedSelection
|
||||
@ -228,6 +232,8 @@ export class AssociatedSelection extends BaseSelection<
|
||||
virtualThreshold={virtualThreshold}
|
||||
itemHeight={itemHeight}
|
||||
loadingConfig={loadingConfig}
|
||||
checkAllLabel={checkAllLabel}
|
||||
checkAll={checkAll}
|
||||
/>
|
||||
) : (
|
||||
<GroupedSelection
|
||||
@ -241,6 +247,8 @@ export class AssociatedSelection extends BaseSelection<
|
||||
labelField={labelField}
|
||||
virtualThreshold={virtualThreshold}
|
||||
itemHeight={itemHeight}
|
||||
checkAllLabel={checkAllLabel}
|
||||
checkAll={checkAll}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
|
@ -166,6 +166,54 @@ export class ChainedSelection extends BaseSelection<
|
||||
return this.renderItem(option, index, depth, id, styles);
|
||||
}
|
||||
|
||||
renderCheckAll() {
|
||||
const {
|
||||
multiple,
|
||||
checkAll,
|
||||
checkAllLabel,
|
||||
classnames: cx,
|
||||
translate: __,
|
||||
labelClassName,
|
||||
itemClassName
|
||||
} = this.props;
|
||||
|
||||
if (!multiple || !checkAll) {
|
||||
return null;
|
||||
}
|
||||
const availableOptions = this.getAvailableOptions();
|
||||
|
||||
const valueArray = this.valueArray;
|
||||
|
||||
const checkedAll = availableOptions.every(
|
||||
option => valueArray.indexOf(option) > -1
|
||||
);
|
||||
const checkedPartial = availableOptions.some(
|
||||
option => valueArray.indexOf(option) > -1
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'ChainedSelection-item',
|
||||
'ChainedSelection-checkAll',
|
||||
itemClassName
|
||||
)}
|
||||
onClick={this.toggleAll}
|
||||
>
|
||||
<Checkbox
|
||||
checked={checkedPartial}
|
||||
partial={checkedPartial && !checkedAll}
|
||||
size="sm"
|
||||
labelClassName={labelClassName}
|
||||
/>
|
||||
|
||||
<div className={cx('ChainedSelection-itemLabel')}>
|
||||
<span>{__(checkAllLabel)}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
@ -330,7 +378,10 @@ export class ChainedSelection extends BaseSelection<
|
||||
return (
|
||||
<div className={cx('ChainedSelection', className)}>
|
||||
{body && body.length ? (
|
||||
body
|
||||
<>
|
||||
{this.renderCheckAll()}
|
||||
{body}
|
||||
</>
|
||||
) : (
|
||||
<div className={cx('ChainedSelection-placeholder')}>
|
||||
{__(placeholder)}
|
||||
|
@ -168,6 +168,50 @@ export class GroupedSelection extends BaseSelection<BaseSelectionProps> {
|
||||
);
|
||||
}
|
||||
|
||||
renderCheckAll() {
|
||||
const {
|
||||
multiple,
|
||||
checkAll,
|
||||
checkAllLabel,
|
||||
classnames: cx,
|
||||
translate: __,
|
||||
labelClassName,
|
||||
itemClassName
|
||||
} = this.props;
|
||||
|
||||
if (!multiple || !checkAll) {
|
||||
return null;
|
||||
}
|
||||
const availableOptions = this.getAvailableOptions();
|
||||
|
||||
const valueArray = this.valueArray;
|
||||
|
||||
const checkedAll = availableOptions.every(
|
||||
option => valueArray.indexOf(option) > -1
|
||||
);
|
||||
const checkedPartial = availableOptions.some(
|
||||
option => valueArray.indexOf(option) > -1
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx('GroupedSelection-item', itemClassName)}
|
||||
onClick={this.toggleAll}
|
||||
>
|
||||
<Checkbox
|
||||
checked={checkedPartial}
|
||||
partial={checkedPartial && !checkedAll}
|
||||
size="sm"
|
||||
labelClassName={labelClassName}
|
||||
/>
|
||||
|
||||
<div className={cx('GroupedSelection-itemLabel')}>
|
||||
{__(checkAllLabel)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
@ -206,6 +250,7 @@ export class GroupedSelection extends BaseSelection<BaseSelectionProps> {
|
||||
height={height}
|
||||
itemCount={flattendOptions.length}
|
||||
itemSize={itemHeight}
|
||||
prefix={this.renderCheckAll()}
|
||||
renderItem={({
|
||||
index,
|
||||
style
|
||||
@ -227,7 +272,10 @@ export class GroupedSelection extends BaseSelection<BaseSelectionProps> {
|
||||
)}
|
||||
</AutoSizer>
|
||||
) : (
|
||||
options.map((option, key) => this.renderOption(option, key))
|
||||
<>
|
||||
{this.renderCheckAll()}
|
||||
{options.map((option, key) => this.renderOption(option, key))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,8 @@ import {
|
||||
themeable,
|
||||
ThemeProps,
|
||||
autobind,
|
||||
findTree
|
||||
findTree,
|
||||
flattenTree
|
||||
} from 'amis-core';
|
||||
import Checkbox from './Checkbox';
|
||||
import {Option, Options} from './Select';
|
||||
@ -41,6 +42,8 @@ export interface BaseSelectionProps extends ThemeProps, LocaleProps {
|
||||
disabled?: boolean;
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
placeholderRender?: (props: any) => JSX.Element | null;
|
||||
checkAll?: boolean;
|
||||
checkAllLabel?: string;
|
||||
}
|
||||
|
||||
export interface ItemRenderStates {
|
||||
@ -153,13 +156,22 @@ export class BaseSelection<
|
||||
onChange && onChange(multiple ? newValue : newValue[0]);
|
||||
}
|
||||
|
||||
getAvailableOptions() {
|
||||
const {options} = this.props;
|
||||
const flattendOptions = flattenTree(options, item =>
|
||||
item.children ? null : item
|
||||
).filter(a => a && !a.disabled);
|
||||
|
||||
return flattendOptions as Option[];
|
||||
}
|
||||
|
||||
@autobind
|
||||
toggleAll() {
|
||||
const {value, onChange, option2value, options} = this.props;
|
||||
|
||||
let valueArray: Array<Option> = [];
|
||||
|
||||
const availableOptions = options.filter(option => !option.disabled);
|
||||
const availableOptions = this.getAvailableOptions();
|
||||
const intersectOptions = this.intersectArray(value, availableOptions);
|
||||
|
||||
if (!Array.isArray(value)) {
|
||||
|
@ -111,6 +111,8 @@ export interface TransferProps
|
||||
virtualThreshold?: number; // 数据量多大的时候开启虚拟渲染`
|
||||
virtualListHeight?: number; // 虚拟渲染时,列表高度
|
||||
showInvalidMatch?: boolean;
|
||||
checkAll?: boolean;
|
||||
checkAllLabel?: string;
|
||||
}
|
||||
|
||||
export interface TransferState {
|
||||
@ -130,12 +132,14 @@ export class Transfer<
|
||||
| 'selectMode'
|
||||
| 'statistics'
|
||||
| 'virtualThreshold'
|
||||
| 'checkAllLabel'
|
||||
> = {
|
||||
multiple: true,
|
||||
resultListModeFollowSelect: false,
|
||||
selectMode: 'list',
|
||||
statistics: true,
|
||||
virtualThreshold: 100
|
||||
virtualThreshold: 100,
|
||||
checkAllLabel: 'Select.checkAll'
|
||||
};
|
||||
|
||||
state: TransferState = {
|
||||
@ -473,7 +477,9 @@ export class Transfer<
|
||||
labelField,
|
||||
virtualThreshold,
|
||||
itemHeight,
|
||||
virtualListHeight
|
||||
virtualListHeight,
|
||||
checkAll,
|
||||
checkAllLabel
|
||||
} = props;
|
||||
const {isTreeDeferLoad, searchResult} = this.state;
|
||||
const options = searchResult ?? [];
|
||||
@ -517,6 +523,8 @@ export class Transfer<
|
||||
labelField={labelField}
|
||||
virtualThreshold={virtualThreshold}
|
||||
itemHeight={itemHeight}
|
||||
checkAllLabel={checkAllLabel}
|
||||
checkAll={checkAll}
|
||||
/>
|
||||
) : mode === 'chained' ? (
|
||||
<ChainedSelection
|
||||
@ -533,6 +541,8 @@ export class Transfer<
|
||||
virtualThreshold={virtualThreshold}
|
||||
itemHeight={itemHeight}
|
||||
virtualListHeight={virtualListHeight}
|
||||
checkAllLabel={checkAllLabel}
|
||||
checkAll={checkAll}
|
||||
/>
|
||||
) : (
|
||||
<GroupedSelection
|
||||
@ -549,6 +559,8 @@ export class Transfer<
|
||||
virtualThreshold={virtualThreshold}
|
||||
itemHeight={itemHeight}
|
||||
virtualListHeight={virtualListHeight}
|
||||
checkAllLabel={checkAllLabel}
|
||||
checkAll={checkAll}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -576,7 +588,9 @@ export class Transfer<
|
||||
virtualThreshold,
|
||||
itemHeight,
|
||||
virtualListHeight,
|
||||
loadingConfig
|
||||
loadingConfig,
|
||||
checkAll,
|
||||
checkAllLabel
|
||||
} = props;
|
||||
|
||||
return selectMode === 'table' ? (
|
||||
@ -594,6 +608,8 @@ export class Transfer<
|
||||
virtualThreshold={virtualThreshold}
|
||||
itemHeight={itemHeight}
|
||||
virtualListHeight={virtualListHeight}
|
||||
checkAllLabel={checkAllLabel}
|
||||
checkAll={checkAll}
|
||||
/>
|
||||
) : selectMode === 'tree' ? (
|
||||
<Tree
|
||||
@ -614,6 +630,8 @@ export class Transfer<
|
||||
virtualThreshold={virtualThreshold}
|
||||
itemHeight={itemHeight}
|
||||
loadingConfig={loadingConfig}
|
||||
checkAllLabel={checkAllLabel}
|
||||
checkAll={checkAll}
|
||||
/>
|
||||
) : selectMode === 'chained' ? (
|
||||
<ChainedSelection
|
||||
@ -631,6 +649,8 @@ export class Transfer<
|
||||
itemHeight={itemHeight}
|
||||
virtualListHeight={virtualListHeight}
|
||||
loadingConfig={loadingConfig}
|
||||
checkAllLabel={checkAllLabel}
|
||||
checkAll={checkAll}
|
||||
/>
|
||||
) : selectMode === 'associated' ? (
|
||||
<AssociatedSelection
|
||||
@ -653,6 +673,8 @@ export class Transfer<
|
||||
itemHeight={itemHeight}
|
||||
virtualListHeight={virtualListHeight}
|
||||
loadingConfig={loadingConfig}
|
||||
checkAllLabel={checkAllLabel}
|
||||
checkAll={checkAll}
|
||||
/>
|
||||
) : (
|
||||
<GroupedSelection
|
||||
@ -669,6 +691,8 @@ export class Transfer<
|
||||
virtualThreshold={virtualThreshold}
|
||||
itemHeight={itemHeight}
|
||||
virtualListHeight={virtualListHeight}
|
||||
checkAllLabel={checkAllLabel}
|
||||
checkAll={checkAll}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -25,7 +25,8 @@ import {
|
||||
findTreeIndex,
|
||||
hasAbility,
|
||||
getTreeParent,
|
||||
getTreeAncestors
|
||||
getTreeAncestors,
|
||||
flattenTree
|
||||
} from 'amis-core';
|
||||
import {Option, Options, value2array} from './Select';
|
||||
import {themeable, ThemeProps, highlight} from 'amis-core';
|
||||
@ -143,6 +144,10 @@ interface TreeSelectorProps extends ThemeProps, LocaleProps, SpinnerExtraProps {
|
||||
draggable?: boolean;
|
||||
onMove?: (dropInfo: IDropInfo) => void;
|
||||
itemRender?: (option: Option, states: ItemRenderStates) => JSX.Element;
|
||||
// 是否允许全选
|
||||
checkAll?: boolean;
|
||||
// 全选按钮文案
|
||||
checkAllLabel?: string;
|
||||
enableDefaultIcon?: boolean;
|
||||
}
|
||||
|
||||
@ -558,26 +563,28 @@ export class TreeSelector extends React.Component<
|
||||
{
|
||||
value
|
||||
},
|
||||
() => {
|
||||
const {
|
||||
joinValues,
|
||||
extractValue,
|
||||
valueField,
|
||||
delimiter,
|
||||
onChange,
|
||||
enableNodePath
|
||||
} = props;
|
||||
() => this.fireChange(value)
|
||||
);
|
||||
}
|
||||
|
||||
onChange(
|
||||
enableNodePath
|
||||
? this.transform2NodePath(value)
|
||||
: joinValues
|
||||
? value.map(item => item[valueField as string]).join(delimiter)
|
||||
: extractValue
|
||||
? value.map(item => item[valueField as string])
|
||||
: value
|
||||
);
|
||||
}
|
||||
fireChange(value: Option[]) {
|
||||
const {
|
||||
joinValues,
|
||||
extractValue,
|
||||
valueField,
|
||||
delimiter,
|
||||
onChange,
|
||||
enableNodePath
|
||||
} = this.props;
|
||||
|
||||
onChange(
|
||||
enableNodePath
|
||||
? this.transform2NodePath(value)
|
||||
: joinValues
|
||||
? value.map(item => item[valueField as string]).join(delimiter)
|
||||
: extractValue
|
||||
? value.map(item => item[valueField as string])
|
||||
: value
|
||||
);
|
||||
}
|
||||
|
||||
@ -1253,6 +1260,77 @@ export class TreeSelector extends React.Component<
|
||||
);
|
||||
}
|
||||
|
||||
isEmptyOrNotExist(obj: any) {
|
||||
return obj === '' || obj === undefined || obj === null;
|
||||
}
|
||||
|
||||
getAvailableOptions() {
|
||||
const {options, onlyChildren, valueField} = this.props;
|
||||
const flattendOptions = flattenTree(options, item =>
|
||||
onlyChildren
|
||||
? item.children
|
||||
? null
|
||||
: item
|
||||
: this.isEmptyOrNotExist(item[valueField || 'value'])
|
||||
? null
|
||||
: item
|
||||
).filter(a => a && !a.disabled);
|
||||
|
||||
return flattendOptions as Option[];
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleCheckAll(availableOptions: Option[], checkedAll: boolean) {
|
||||
this.setState(
|
||||
{
|
||||
value: checkedAll ? [] : availableOptions
|
||||
},
|
||||
() => this.fireChange(checkedAll ? [] : availableOptions)
|
||||
);
|
||||
}
|
||||
|
||||
renderCheckAll() {
|
||||
const {
|
||||
multiple,
|
||||
checkAll,
|
||||
checkAllLabel,
|
||||
classnames: cx,
|
||||
translate: __,
|
||||
disabled
|
||||
} = this.props;
|
||||
|
||||
if (!multiple || !checkAll) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const availableOptions = this.getAvailableOptions();
|
||||
|
||||
const checkedAll = availableOptions.every(option =>
|
||||
this.isItemChecked(option)
|
||||
);
|
||||
const checkedPartial = availableOptions.some(option =>
|
||||
this.isItemChecked(option)
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx('Tree-itemLabel')}
|
||||
onClick={() => this.handleCheckAll(availableOptions, checkedAll)}
|
||||
>
|
||||
<Checkbox
|
||||
size="sm"
|
||||
disabled={disabled}
|
||||
checked={checkedPartial}
|
||||
partial={checkedPartial && !checkedAll}
|
||||
/>
|
||||
|
||||
<div className={cx('Tree-itemLabel-item')}>
|
||||
<span className={cx('Tree-itemText')}>{__(checkAllLabel)}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@autobind
|
||||
renderList(list: Options, value: any[]) {
|
||||
const {virtualThreshold, itemHeight = 32} = this.props;
|
||||
@ -1261,6 +1339,7 @@ export class TreeSelector extends React.Component<
|
||||
<VirtualList
|
||||
height={list.length > 8 ? 266 : list.length * itemHeight}
|
||||
itemCount={list.length}
|
||||
prefix={this.renderCheckAll()}
|
||||
itemSize={itemHeight}
|
||||
//! hack: 让 VirtualList 重新渲染
|
||||
renderItem={this.renderItem.bind(this)}
|
||||
@ -1268,7 +1347,12 @@ export class TreeSelector extends React.Component<
|
||||
);
|
||||
}
|
||||
|
||||
return list.map((item, index) => this.renderItem({index}));
|
||||
return (
|
||||
<>
|
||||
{this.renderCheckAll()}
|
||||
{list.map((item, index) => this.renderItem({index}))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -571,6 +571,8 @@ class TransferDropdownRenderer extends BaseTransferRenderer<TransferDropDownProp
|
||||
loadingConfig,
|
||||
labelField,
|
||||
showInvalidMatch,
|
||||
checkAll,
|
||||
checkAllLabel,
|
||||
overlay
|
||||
} = this.props;
|
||||
|
||||
@ -624,6 +626,8 @@ class TransferDropdownRenderer extends BaseTransferRenderer<TransferDropDownProp
|
||||
virtualListHeight={266}
|
||||
labelField={labelField}
|
||||
showInvalidMatch={showInvalidMatch}
|
||||
checkAllLabel={checkAllLabel}
|
||||
checkAll={checkAll}
|
||||
overlay={overlay}
|
||||
/>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user