fix: select 各种模式支持 checkAll (#5941)

This commit is contained in:
sansiro 2023-03-03 15:33:26 +08:00 committed by GitHub
parent 3e5bc113ab
commit ab3cbd7d1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 269 additions and 29 deletions

View File

@ -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);

View File

@ -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}
/>
)
) : (

View File

@ -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)}

View File

@ -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))}
</>
);
}

View File

@ -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)) {

View File

@ -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}
/>
);
}

View File

@ -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() {

View File

@ -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}
/>