cherry-pick: Select组件开启虚拟列表后显示问题#4548 #4516 (#4572)

This commit is contained in:
RUNZE LU 2022-06-14 14:07:18 +08:00 committed by GitHub
parent eaa62a253e
commit 641f35ae37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 113 additions and 59 deletions

View File

@ -1405,14 +1405,32 @@ order: 2
> 1.10.0 及以上版本 > 1.10.0 及以上版本
下拉框在数据量较大时(超过 200可以通过 `virtualThreshold` 控制)会自动切换到虚拟渲染模式,如果选项的内容较长会导致内容重叠,这时需要设置 `itemHeight` 来避免。 下拉框在数据量较大时(超过 100可以通过 `virtualThreshold` 控制)会自动切换到虚拟渲染模式,如果选项的内容较长会导致内容重叠,这时需要设置 `itemHeight` 来避免。
```schema: scope="body"
{
"type": "form",
"api": "/api/mock2/form/saveForm",
"debug": true,
"body": [
{
"type": "select",
"label": "虚拟列表选择",
"name": "virtual-select",
"clearable": true,
"searchable": true,
"source": "/api/mock2/form/getOptions?waitSeconds=1&size=200"
}
]
}
```
## 属性表 ## 属性表
除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置 除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
| 属性名 | 类型 | 默认值 | 说明 | | 属性名 | 类型 | 默认值 | 说明 |
| ---------------- | --------------------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------- | | ---------------- | --------------------------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------- |
| options | `Array<object>`或`Array<string>` | | 选项组,供用户选择 | | options | `Array<object>`或`Array<string>` | | 选项组,供用户选择 |
| source | [API](../../../docs/types/api) 或 [数据映射](../../../docs/concepts/data-mapping) | | 选项组源,可通过数据映射获取当前数据域变量、或者配置 API 对象 | | source | [API](../../../docs/types/api) 或 [数据映射](../../../docs/concepts/data-mapping) | | 选项组源,可通过数据映射获取当前数据域变量、或者配置 API 对象 |
| multiple | `boolean` | `false` | 是否支持多选 | | multiple | `boolean` | `false` | 是否支持多选 |
@ -1420,6 +1438,6 @@ order: 2
| valueField | `boolean` | `"value"` | 标识选项中哪个字段是`value`值 | | valueField | `boolean` | `"value"` | 标识选项中哪个字段是`value`值 |
| joinValues | `boolean` | `true` | 是否拼接`value`值 | | joinValues | `boolean` | `true` | 是否拼接`value`值 |
| extractValue | `boolean` | `false` | 是否将`value`值抽取出来组成新的数组,只有在`joinValues`是`false`是生效 | | extractValue | `boolean` | `false` | 是否将`value`值抽取出来组成新的数组,只有在`joinValues`是`false`是生效 |
| itemHeight | `number` | | 每个选项的高度,用于虚拟渲染 | | itemHeight | `number` | `32` | 每个选项的高度,用于虚拟渲染 |
| virtualThreshold | `number` | | 在选项数量超过多少时开启虚拟渲染 | | virtualThreshold | `number` | `100` | 在选项数量超过多少时开启虚拟渲染 |
| valuesNoWrap | `boolean` | `false` | 默认情况下多选所有选项都会显示,通过这个可以最多显示一行,超出的部分变成 ... | | valuesNoWrap | `boolean` | `false` | 默认情况下多选所有选项都会显示,通过这个可以最多显示一行,超出的部分变成 ... |

View File

@ -0,0 +1,42 @@
module.exports = function (req, res) {
const size =
req.query.size && typeof Number(req.query.size) === 'number'
? Math.ceil(req.query.size)
: 0;
const customOptions =
size > 0
? Array.from({length: size}, (item, index) => ({
label: 'Option ' + index,
value: index.toString()
}))
: [];
const defaultOptions = [
{label: 'Option A', value: 'a'},
{label: 'Option B', value: 'b'},
{label: 'Option C', value: 'c'},
{label: 'Option D', value: 'd'},
{label: 'Option E', value: 'e'},
{label: 'Option F', value: 'f'},
{label: 'Option G', value: 'g'},
{label: 'Option H', value: 'h'},
{label: 'Option I', value: 'i'},
{label: 'Option J', value: 'j'},
{label: 'Option K', value: 'k'},
{label: 'Option L', value: 'l'},
{label: 'Option M', value: 'm'},
{label: 'Option N', value: 'n'},
{label: 'Option O', value: 'o'},
{label: 'Option P', value: 'p'},
{label: 'Option Q', value: 'q'}
];
res.json({
status: 0,
msg: 'ok',
data: {
options: customOptions.length > 0 ? customOptions : defaultOptions
}
});
};

View File

@ -1,25 +0,0 @@
{
"status": 0,
"msg": "ok",
"data": {
"options": [
{"label": "Option A", "value": "a"},
{"label": "Option B", "value": "b"},
{"label": "Option C", "value": "c"},
{"label": "Option D", "value": "d"},
{"label": "Option E", "value": "e"},
{"label": "Option F", "value": "f"},
{"label": "Option G", "value": "g"},
{"label": "Option H", "value": "h"},
{"label": "Option I", "value": "i"},
{"label": "Option J", "value": "j"},
{"label": "Option K", "value": "k"},
{"label": "Option L", "value": "l"},
{"label": "Option M", "value": "m"},
{"label": "Option N", "value": "n"},
{"label": "Option O", "value": "o"},
{"label": "Option P", "value": "p"},
{"label": "Option Q", "value": "q"}
]
}
}

View File

@ -1,5 +1,5 @@
import React = require('react'); import React = require('react');
import {NotFound} from 'amis-ui'; import NotFound from '../src/components/404';
import * as renderer from 'react-test-renderer'; import * as renderer from 'react-test-renderer';
import {render, fireEvent, cleanup} from '@testing-library/react'; import {render, fireEvent, cleanup} from '@testing-library/react';

View File

@ -81,6 +81,8 @@
.#{$ns}PopOver.#{$ns}Select-popover { .#{$ns}PopOver.#{$ns}Select-popover {
.#{$ns}Select-menu { .#{$ns}Select-menu {
overflow-x: hidden;
.#{$ns}Select-option { .#{$ns}Select-option {
height: px2rem(32px); height: px2rem(32px);
line-height: px2rem(22px); line-height: px2rem(22px);

View File

@ -9,6 +9,7 @@ import {uncontrollable} from 'amis-core';
import React from 'react'; import React from 'react';
import isInteger from 'lodash/isInteger'; import isInteger from 'lodash/isInteger';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import merge from 'lodash/merge';
import VirtualList from './virtual-list'; import VirtualList from './virtual-list';
import Overlay from './Overlay'; import Overlay from './Overlay';
import PopOver from './PopOver'; import PopOver from './PopOver';
@ -413,7 +414,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
inputValue: '', inputValue: '',
highlightedIndex: -1, highlightedIndex: -1,
selection: value2array(props.value, props), selection: value2array(props.value, props),
itemHeight: 35, itemHeight: 32 /** Select选项高度保持一致 */,
pickerSelectItem: '' pickerSelectItem: ''
}; };
} }
@ -728,7 +729,9 @@ export class Select extends React.Component<SelectProps, SelectState> {
@autobind @autobind
menuItemRef(ref: any) { menuItemRef(ref: any) {
ref && this.setState({itemHeight: ref.offsetHeight}); if (ref && typeof ref.offsetHeight === 'number' && ref > 0) {
this.setState({itemHeight: ref.offsetHeight});
}
} }
renderValue({inputValue, isOpen}: ControllerStateAndHelpers<any>) { renderValue({inputValue, isOpen}: ControllerStateAndHelpers<any>) {
@ -946,7 +949,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
hideSelected, hideSelected,
renderMenu, renderMenu,
mobileClassName, mobileClassName,
virtualThreshold = 200, virtualThreshold = 100,
useMobileUI = false useMobileUI = false
} = this.props; } = this.props;
const {selection} = this.state; const {selection} = this.state;
@ -960,7 +963,8 @@ export class Select extends React.Component<SelectProps, SelectState> {
}) })
: options.concat() : options.concat()
).filter((option: Option) => !option.hidden && option.visible !== false); ).filter((option: Option) => !option.hidden && option.visible !== false);
const enableVirtualRender =
filtedOptions.length && filtedOptions.length > virtualThreshold;
const selectionValues = selection.map(select => select[valueField]); const selectionValues = selection.map(select => select[valueField]);
if (multiple && checkAll) { if (multiple && checkAll) {
const optionsValues = (checkAllBySearch ? filtedOptions : options).map( const optionsValues = (checkAllBySearch ? filtedOptions : options).map(
@ -1000,7 +1004,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
item, item,
disabled: item.disabled disabled: item.disabled
})} })}
style={style} style={merge(style, enableVirtualRender ? {width: '100%'} : {})}
className={cx(`Select-option`, { className={cx(`Select-option`, {
'is-disabled': item.disabled, 'is-disabled': item.disabled,
'is-highlight': highlightedIndex === index, 'is-highlight': highlightedIndex === index,
@ -1107,8 +1111,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
<div <div
ref={this.menu} ref={this.menu}
className={cx('Select-menu', { className={cx('Select-menu', {
'Select--longlist': 'Select--longlist': enableVirtualRender,
filtedOptions.length && filtedOptions.length > virtualThreshold,
'is-mobile': mobileUI 'is-mobile': mobileUI
})} })}
> >

View File

@ -1654,7 +1654,7 @@ exports[`Renderer:select virtual 1`] = `
</span> </span>
<div <div
class="cxd-PopOver cxd-Select-popover cxd-PopOver--leftBottomLeftTop" class="cxd-PopOver cxd-Select-popover cxd-PopOver--leftBottomLeftTop"
style="display: block; width: 0px; left: 0px; top: 0px; position: relative;" style="display: block; width: auto; left: 0px; top: 0px; position: relative;"
theme="cxd" theme="cxd"
> >
<div <div
@ -1674,14 +1674,14 @@ exports[`Renderer:select virtual 1`] = `
style="overflow: auto; will-change: transform; height: 266px; width: 100%;" style="overflow: auto; will-change: transform; height: 266px; width: 100%;"
> >
<div <div
style="position: relative; width: auto; white-space: nowrap; min-height: 100%; height: 7000px;" style="position: relative; width: auto; white-space: nowrap; min-height: 100%; height: 6400px;"
> >
<div <div
aria-selected="false" aria-selected="false"
class="cxd-Select-option" class="cxd-Select-option"
id="downshift-1-item-0" id="downshift-1-item-0"
role="option" role="option"
style="position: absolute; top: 0px; left: 0px; width: auto; height: 35px;" style="position: absolute; top: 0px; left: 0px; width: 100%; height: 32px;"
> >
<span <span
class="cxd-Select-option-content" class="cxd-Select-option-content"
@ -1695,7 +1695,7 @@ exports[`Renderer:select virtual 1`] = `
class="cxd-Select-option" class="cxd-Select-option"
id="downshift-1-item-1" id="downshift-1-item-1"
role="option" role="option"
style="position: absolute; top: 35px; left: 0px; width: auto; height: 35px;" style="position: absolute; top: 32px; left: 0px; width: 100%; height: 32px;"
> >
<span <span
class="cxd-Select-option-content" class="cxd-Select-option-content"
@ -1709,7 +1709,7 @@ exports[`Renderer:select virtual 1`] = `
class="cxd-Select-option" class="cxd-Select-option"
id="downshift-1-item-2" id="downshift-1-item-2"
role="option" role="option"
style="position: absolute; top: 70px; left: 0px; width: auto; height: 35px;" style="position: absolute; top: 64px; left: 0px; width: 100%; height: 32px;"
> >
<span <span
class="cxd-Select-option-content" class="cxd-Select-option-content"
@ -1723,7 +1723,7 @@ exports[`Renderer:select virtual 1`] = `
class="cxd-Select-option" class="cxd-Select-option"
id="downshift-1-item-3" id="downshift-1-item-3"
role="option" role="option"
style="position: absolute; top: 105px; left: 0px; width: auto; height: 35px;" style="position: absolute; top: 96px; left: 0px; width: 100%; height: 32px;"
> >
<span <span
class="cxd-Select-option-content" class="cxd-Select-option-content"
@ -1737,7 +1737,7 @@ exports[`Renderer:select virtual 1`] = `
class="cxd-Select-option" class="cxd-Select-option"
id="downshift-1-item-4" id="downshift-1-item-4"
role="option" role="option"
style="position: absolute; top: 140px; left: 0px; width: auto; height: 35px;" style="position: absolute; top: 128px; left: 0px; width: 100%; height: 32px;"
> >
<span <span
class="cxd-Select-option-content" class="cxd-Select-option-content"
@ -1751,7 +1751,7 @@ exports[`Renderer:select virtual 1`] = `
class="cxd-Select-option" class="cxd-Select-option"
id="downshift-1-item-5" id="downshift-1-item-5"
role="option" role="option"
style="position: absolute; top: 175px; left: 0px; width: auto; height: 35px;" style="position: absolute; top: 160px; left: 0px; width: 100%; height: 32px;"
> >
<span <span
class="cxd-Select-option-content" class="cxd-Select-option-content"
@ -1765,7 +1765,7 @@ exports[`Renderer:select virtual 1`] = `
class="cxd-Select-option" class="cxd-Select-option"
id="downshift-1-item-6" id="downshift-1-item-6"
role="option" role="option"
style="position: absolute; top: 210px; left: 0px; width: auto; height: 35px;" style="position: absolute; top: 192px; left: 0px; width: 100%; height: 32px;"
> >
<span <span
class="cxd-Select-option-content" class="cxd-Select-option-content"
@ -1779,7 +1779,7 @@ exports[`Renderer:select virtual 1`] = `
class="cxd-Select-option" class="cxd-Select-option"
id="downshift-1-item-7" id="downshift-1-item-7"
role="option" role="option"
style="position: absolute; top: 245px; left: 0px; width: auto; height: 35px;" style="position: absolute; top: 224px; left: 0px; width: 100%; height: 32px;"
> >
<span <span
class="cxd-Select-option-content" class="cxd-Select-option-content"
@ -1793,7 +1793,7 @@ exports[`Renderer:select virtual 1`] = `
class="cxd-Select-option" class="cxd-Select-option"
id="downshift-1-item-8" id="downshift-1-item-8"
role="option" role="option"
style="position: absolute; top: 280px; left: 0px; width: auto; height: 35px;" style="position: absolute; top: 256px; left: 0px; width: 100%; height: 32px;"
> >
<span <span
class="cxd-Select-option-content" class="cxd-Select-option-content"
@ -1807,7 +1807,7 @@ exports[`Renderer:select virtual 1`] = `
class="cxd-Select-option" class="cxd-Select-option"
id="downshift-1-item-9" id="downshift-1-item-9"
role="option" role="option"
style="position: absolute; top: 315px; left: 0px; width: auto; height: 35px;" style="position: absolute; top: 288px; left: 0px; width: 100%; height: 32px;"
> >
<span <span
class="cxd-Select-option-content" class="cxd-Select-option-content"
@ -1821,7 +1821,7 @@ exports[`Renderer:select virtual 1`] = `
class="cxd-Select-option" class="cxd-Select-option"
id="downshift-1-item-10" id="downshift-1-item-10"
role="option" role="option"
style="position: absolute; top: 350px; left: 0px; width: auto; height: 35px;" style="position: absolute; top: 320px; left: 0px; width: 100%; height: 32px;"
> >
<span <span
class="cxd-Select-option-content" class="cxd-Select-option-content"
@ -1830,6 +1830,20 @@ exports[`Renderer:select virtual 1`] = `
option10 option10
</span> </span>
</div> </div>
<div
aria-selected="false"
class="cxd-Select-option"
id="downshift-1-item-11"
role="option"
style="position: absolute; top: 352px; left: 0px; width: 100%; height: 32px;"
>
<span
class="cxd-Select-option-content"
title="option11"
>
option11
</span>
</div>
</div> </div>
</div> </div>
</div> </div>