mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
feat: Transfer组件支持分页 (#8512)
This commit is contained in:
parent
a9eafda7a3
commit
729f599573
@ -878,12 +878,95 @@ icon:
|
||||
}
|
||||
```
|
||||
|
||||
## 分页
|
||||
|
||||
> `3.6.0`及以上版本
|
||||
|
||||
当数据量庞大时,可以开启数据源分页,此时左侧列表底部会出现分页控件,相关配置参考属性表。通常在提交表单中使用分页场景,处理数据量较大的数据源。如果需要在表单中回显已选值,建议同时设置`{"joinValues": false, "extractValue": false}`,因为已选数据可能位于不同的分页,如果仅使用value值作为提交值,可能会导致右侧结果区无法正确渲染。
|
||||
|
||||
> 仅列表(list)和表格(table)展示模式支持分页,接口的数据结构参考[CRUD数据源接口格式](../crud#数据结构)
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"debug": true,
|
||||
"body": [
|
||||
{
|
||||
"label": "默认",
|
||||
"type": "transfer",
|
||||
"name": "transfer",
|
||||
"joinValues": false,
|
||||
"extractValue": false,
|
||||
"source": "/api/mock2/options/transfer?page=${page}&perPage=${perPage}",
|
||||
"pagination": {
|
||||
"enable": true,
|
||||
"layout": ["pager", "perpage", "total"],
|
||||
"popOverContainerSelector": ".cxd-Panel--form"
|
||||
},
|
||||
"value": [
|
||||
{"label": "Laura Lewis", "value": "1", "id": 1},
|
||||
{"label": "Christopher Rodriguez", "value": "3", "id": 3},
|
||||
{"label": "Laura Miller", "value": "12", "id": 12},
|
||||
{"label": "Patricia Robinson", "value": "14", "id": 14}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 前端分页
|
||||
|
||||
> `3.6.0`及以上版本
|
||||
|
||||
当使用数据域变量作为数据源时,支持实现前端一次性加载并分页
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"debug": true,
|
||||
"body": [
|
||||
{
|
||||
"type": "service",
|
||||
"api": {
|
||||
"url": "/api/mock2/options/loadDataOnce",
|
||||
"method": "get",
|
||||
"responseData": {
|
||||
"transferOptions": "${items}"
|
||||
}
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"label": "默认",
|
||||
"type": "transfer",
|
||||
"name": "transfer",
|
||||
"joinValues": false,
|
||||
"extractValue": false,
|
||||
"source": "${transferOptions}",
|
||||
"pagination": {
|
||||
"enable": true,
|
||||
"layout": ["pager", "perpage", "total"],
|
||||
"popOverContainerSelector": ".cxd-Panel--form"
|
||||
},
|
||||
"value": [
|
||||
{"label": "Laura Lewis", "value": "1", "id": 1},
|
||||
{"label": "Christopher Rodriguez", "value": "3", "id": 3},
|
||||
{"label": "Laura Miller", "value": "12", "id": 12},
|
||||
{"label": "Patricia Robinson", "value": "14", "id": 14}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 属性表
|
||||
|
||||
除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| -------------------------- | ----------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
|
||||
| -------------------------- | ----------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
|
||||
| options | `Array<object>`或`Array<string>` | | [选项组](./options#%E9%9D%99%E6%80%81%E9%80%89%E9%A1%B9%E7%BB%84-options) |
|
||||
| source | `string`或 [API](../../../docs/types/api) | | [动态选项组](./options#%E5%8A%A8%E6%80%81%E9%80%89%E9%A1%B9%E7%BB%84-source) |
|
||||
| delimeter | `string` | `false` | [拼接符](./options#%E6%8B%BC%E6%8E%A5%E7%AC%A6-delimiter) |
|
||||
@ -909,6 +992,13 @@ icon:
|
||||
| valueTpl | `string` \| [SchemaNode](../../docs/types/schemanode) | | 用来自定义值的展示 |
|
||||
| itemHeight | `number` | `32` | 每个选项的高度,用于虚拟渲染 |
|
||||
| virtualThreshold | `number` | `100` | 在选项数量超过多少时开启虚拟渲染 |
|
||||
| pagination | `object` | | 分页配置 | `3.6.0` |
|
||||
| pagination.className | `string` | | 分页控件CSS类名 | `3.6.0` |
|
||||
| pagination.enable | `boolean` | | 是否开启分页 | `3.6.0` |
|
||||
| pagination.layout | `string` \| `string[]` | `["pager"]` | 通过控制 layout 属性的顺序,调整分页结构布局 | `3.6.0` |
|
||||
| pagination.perPageAvailable | `number[]` | `[10, 20, 50, 100]` | 指定每页可以显示多少条 | `3.6.0` |
|
||||
| pagination.maxButtons | `number` | `5` | 最多显示多少个分页按钮,最小为 5 | `3.6.0` |
|
||||
| pagination.popOverContainerSelector | `string` | | 切换每页条数的控件挂载点 | `3.6.0` |
|
||||
|
||||
## 事件表
|
||||
|
||||
|
238
mock/cfc/mock/options/loadDataOnce.js
Normal file
238
mock/cfc/mock/options/loadDataOnce.js
Normal file
@ -0,0 +1,238 @@
|
||||
/** 前端分页的接口 */
|
||||
module.exports = function (req, res) {
|
||||
res.json({
|
||||
status: 0,
|
||||
msg: 'ok',
|
||||
data: {
|
||||
count: data.length,
|
||||
items: data
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const data = [
|
||||
{
|
||||
"label": "Laura Lewis",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"label": "David Gonzalez",
|
||||
"value": "2"
|
||||
},
|
||||
{
|
||||
"label": "Christopher Rodriguez",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"label": "Sarah Young",
|
||||
"value": "4"
|
||||
},
|
||||
{
|
||||
"label": "James Jones",
|
||||
"value": "5"
|
||||
},
|
||||
{
|
||||
"label": "Larry Robinson",
|
||||
"value": "6"
|
||||
},
|
||||
{
|
||||
"label": "Christopher Perez",
|
||||
"value": "7"
|
||||
},
|
||||
{
|
||||
"label": "Sharon Davis",
|
||||
"value": "8"
|
||||
},
|
||||
{
|
||||
"label": "Kenneth Anderson",
|
||||
"value": "9"
|
||||
},
|
||||
{
|
||||
"label": "Deborah Lewis",
|
||||
"value": "10"
|
||||
},
|
||||
{
|
||||
"label": "Jennifer Lewis",
|
||||
"value": "11"
|
||||
},
|
||||
{
|
||||
"label": "Laura Miller",
|
||||
"value": "12"
|
||||
},
|
||||
{
|
||||
"label": "Larry Harris",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"label": "Patricia Robinson",
|
||||
"value": "14"
|
||||
},
|
||||
{
|
||||
"label": "Mark Davis",
|
||||
"value": "15"
|
||||
},
|
||||
{
|
||||
"label": "Jessica Harris",
|
||||
"value": "16"
|
||||
},
|
||||
{
|
||||
"label": "Anna Brown",
|
||||
"value": "17"
|
||||
},
|
||||
{
|
||||
"label": "Lisa Young",
|
||||
"value": "18"
|
||||
},
|
||||
{
|
||||
"label": "Donna Williams",
|
||||
"value": "19"
|
||||
},
|
||||
{
|
||||
"label": "Shirley Davis",
|
||||
"value": "20"
|
||||
},
|
||||
{
|
||||
"label": "Richard Clark",
|
||||
"value": "21"
|
||||
},
|
||||
{
|
||||
"label": "Cynthia Martinez",
|
||||
"value": "22"
|
||||
},
|
||||
{
|
||||
"label": "Kimberly Walker",
|
||||
"value": "23"
|
||||
},
|
||||
{
|
||||
"label": "Timothy Anderson",
|
||||
"value": "24"
|
||||
},
|
||||
{
|
||||
"label": "Betty Lee",
|
||||
"value": "25"
|
||||
},
|
||||
{
|
||||
"label": "Jeffrey Allen",
|
||||
"value": "26"
|
||||
},
|
||||
{
|
||||
"label": "Karen Martinez",
|
||||
"value": "27"
|
||||
},
|
||||
{
|
||||
"label": "Anna Lopez",
|
||||
"value": "28"
|
||||
},
|
||||
{
|
||||
"label": "Dorothy Anderson",
|
||||
"value": "29"
|
||||
},
|
||||
{
|
||||
"label": "David Perez",
|
||||
"value": "30"
|
||||
},
|
||||
{
|
||||
"label": "Dorothy Martin",
|
||||
"value": "31"
|
||||
},
|
||||
{
|
||||
"label": "George Johnson",
|
||||
"value": "32"
|
||||
},
|
||||
{
|
||||
"label": "Donald Jackson",
|
||||
"value": "33"
|
||||
},
|
||||
{
|
||||
"label": "Mary Brown",
|
||||
"value": "34"
|
||||
},
|
||||
{
|
||||
"label": "Deborah Martinez",
|
||||
"value": "35"
|
||||
},
|
||||
{
|
||||
"label": "Donald Jackson",
|
||||
"value": "36"
|
||||
},
|
||||
{
|
||||
"label": "Lisa Robinson",
|
||||
"value": "37"
|
||||
},
|
||||
{
|
||||
"label": "Laura Martinez",
|
||||
"value": "38"
|
||||
},
|
||||
{
|
||||
"label": "Timothy Taylor",
|
||||
"value": "39"
|
||||
},
|
||||
{
|
||||
"label": "Joseph Martinez",
|
||||
"value": "40"
|
||||
},
|
||||
{
|
||||
"label": "Karen Wilson",
|
||||
"value": "41"
|
||||
},
|
||||
{
|
||||
"label": "Karen Walker",
|
||||
"value": "42"
|
||||
},
|
||||
{
|
||||
"label": "William Martinez",
|
||||
"value": "43"
|
||||
},
|
||||
{
|
||||
"label": "Linda Brown",
|
||||
"value": "44"
|
||||
},
|
||||
{
|
||||
"label": "Elizabeth Brown",
|
||||
"value": "45"
|
||||
},
|
||||
{
|
||||
"label": "Anna Moore",
|
||||
"value": "46"
|
||||
},
|
||||
{
|
||||
"label": "Robert Martinez",
|
||||
"value": "47"
|
||||
},
|
||||
{
|
||||
"label": "Edward Hernandez",
|
||||
"value": "48"
|
||||
},
|
||||
{
|
||||
"label": "Elizabeth Hall",
|
||||
"value": "49"
|
||||
},
|
||||
{
|
||||
"label": "Linda Jackson",
|
||||
"value": "50"
|
||||
},
|
||||
{
|
||||
"label": "Brian Jones",
|
||||
"value": "51"
|
||||
},
|
||||
{
|
||||
"label": "Amy Thompson",
|
||||
"value": "52"
|
||||
},
|
||||
{
|
||||
"label": "Kimberly Wilson",
|
||||
"value": "53"
|
||||
},
|
||||
{
|
||||
"label": "Nancy Garcia",
|
||||
"value": "54"
|
||||
},
|
||||
{
|
||||
"label": "Mary Thompson",
|
||||
"value": "55"
|
||||
}
|
||||
].map(function (item, index) {
|
||||
return Object.assign({}, item, {
|
||||
id: index + 1
|
||||
});
|
||||
});
|
242
mock/cfc/mock/options/transfer.js
Normal file
242
mock/cfc/mock/options/transfer.js
Normal file
@ -0,0 +1,242 @@
|
||||
/** Transfer分页接口 */
|
||||
module.exports = function (req, res) {
|
||||
const perPage = Number(req.query.perPage || 10);
|
||||
const page = Number(req.query.page || 1);
|
||||
|
||||
res.json({
|
||||
status: 0,
|
||||
msg: 'ok',
|
||||
data: {
|
||||
count: data.length,
|
||||
page: page,
|
||||
items: data.concat().splice((page - 1) * perPage, perPage)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const data = [
|
||||
{
|
||||
"label": "Laura Lewis",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"label": "David Gonzalez",
|
||||
"value": "2"
|
||||
},
|
||||
{
|
||||
"label": "Christopher Rodriguez",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"label": "Sarah Young",
|
||||
"value": "4"
|
||||
},
|
||||
{
|
||||
"label": "James Jones",
|
||||
"value": "5"
|
||||
},
|
||||
{
|
||||
"label": "Larry Robinson",
|
||||
"value": "6"
|
||||
},
|
||||
{
|
||||
"label": "Christopher Perez",
|
||||
"value": "7"
|
||||
},
|
||||
{
|
||||
"label": "Sharon Davis",
|
||||
"value": "8"
|
||||
},
|
||||
{
|
||||
"label": "Kenneth Anderson",
|
||||
"value": "9"
|
||||
},
|
||||
{
|
||||
"label": "Deborah Lewis",
|
||||
"value": "10"
|
||||
},
|
||||
{
|
||||
"label": "Jennifer Lewis",
|
||||
"value": "11"
|
||||
},
|
||||
{
|
||||
"label": "Laura Miller",
|
||||
"value": "12"
|
||||
},
|
||||
{
|
||||
"label": "Larry Harris",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"label": "Patricia Robinson",
|
||||
"value": "14"
|
||||
},
|
||||
{
|
||||
"label": "Mark Davis",
|
||||
"value": "15"
|
||||
},
|
||||
{
|
||||
"label": "Jessica Harris",
|
||||
"value": "16"
|
||||
},
|
||||
{
|
||||
"label": "Anna Brown",
|
||||
"value": "17"
|
||||
},
|
||||
{
|
||||
"label": "Lisa Young",
|
||||
"value": "18"
|
||||
},
|
||||
{
|
||||
"label": "Donna Williams",
|
||||
"value": "19"
|
||||
},
|
||||
{
|
||||
"label": "Shirley Davis",
|
||||
"value": "20"
|
||||
},
|
||||
{
|
||||
"label": "Richard Clark",
|
||||
"value": "21"
|
||||
},
|
||||
{
|
||||
"label": "Cynthia Martinez",
|
||||
"value": "22"
|
||||
},
|
||||
{
|
||||
"label": "Kimberly Walker",
|
||||
"value": "23"
|
||||
},
|
||||
{
|
||||
"label": "Timothy Anderson",
|
||||
"value": "24"
|
||||
},
|
||||
{
|
||||
"label": "Betty Lee",
|
||||
"value": "25"
|
||||
},
|
||||
{
|
||||
"label": "Jeffrey Allen",
|
||||
"value": "26"
|
||||
},
|
||||
{
|
||||
"label": "Karen Martinez",
|
||||
"value": "27"
|
||||
},
|
||||
{
|
||||
"label": "Anna Lopez",
|
||||
"value": "28"
|
||||
},
|
||||
{
|
||||
"label": "Dorothy Anderson",
|
||||
"value": "29"
|
||||
},
|
||||
{
|
||||
"label": "David Perez",
|
||||
"value": "30"
|
||||
},
|
||||
{
|
||||
"label": "Dorothy Martin",
|
||||
"value": "31"
|
||||
},
|
||||
{
|
||||
"label": "George Johnson",
|
||||
"value": "32"
|
||||
},
|
||||
{
|
||||
"label": "Donald Jackson",
|
||||
"value": "33"
|
||||
},
|
||||
{
|
||||
"label": "Mary Brown",
|
||||
"value": "34"
|
||||
},
|
||||
{
|
||||
"label": "Deborah Martinez",
|
||||
"value": "35"
|
||||
},
|
||||
{
|
||||
"label": "Donald Jackson",
|
||||
"value": "36"
|
||||
},
|
||||
{
|
||||
"label": "Lisa Robinson",
|
||||
"value": "37"
|
||||
},
|
||||
{
|
||||
"label": "Laura Martinez",
|
||||
"value": "38"
|
||||
},
|
||||
{
|
||||
"label": "Timothy Taylor",
|
||||
"value": "39"
|
||||
},
|
||||
{
|
||||
"label": "Joseph Martinez",
|
||||
"value": "40"
|
||||
},
|
||||
{
|
||||
"label": "Karen Wilson",
|
||||
"value": "41"
|
||||
},
|
||||
{
|
||||
"label": "Karen Walker",
|
||||
"value": "42"
|
||||
},
|
||||
{
|
||||
"label": "William Martinez",
|
||||
"value": "43"
|
||||
},
|
||||
{
|
||||
"label": "Linda Brown",
|
||||
"value": "44"
|
||||
},
|
||||
{
|
||||
"label": "Elizabeth Brown",
|
||||
"value": "45"
|
||||
},
|
||||
{
|
||||
"label": "Anna Moore",
|
||||
"value": "46"
|
||||
},
|
||||
{
|
||||
"label": "Robert Martinez",
|
||||
"value": "47"
|
||||
},
|
||||
{
|
||||
"label": "Edward Hernandez",
|
||||
"value": "48"
|
||||
},
|
||||
{
|
||||
"label": "Elizabeth Hall",
|
||||
"value": "49"
|
||||
},
|
||||
{
|
||||
"label": "Linda Jackson",
|
||||
"value": "50"
|
||||
},
|
||||
{
|
||||
"label": "Brian Jones",
|
||||
"value": "51"
|
||||
},
|
||||
{
|
||||
"label": "Amy Thompson",
|
||||
"value": "52"
|
||||
},
|
||||
{
|
||||
"label": "Kimberly Wilson",
|
||||
"value": "53"
|
||||
},
|
||||
{
|
||||
"label": "Nancy Garcia",
|
||||
"value": "54"
|
||||
},
|
||||
{
|
||||
"label": "Mary Thompson",
|
||||
"value": "55"
|
||||
}
|
||||
].map(function (item, index) {
|
||||
return Object.assign({}, item, {
|
||||
id: index + 1
|
||||
});
|
||||
});
|
@ -37,6 +37,7 @@ import {
|
||||
FormBaseControl
|
||||
} from './Item';
|
||||
import {IFormItemStore} from '../store/formItem';
|
||||
import {isObject} from 'amis-core';
|
||||
|
||||
export type OptionsControlComponent = React.ComponentType<FormControlProps>;
|
||||
|
||||
@ -230,7 +231,11 @@ export interface OptionsControlProps
|
||||
selectedOptions: Array<Option>;
|
||||
setOptions: (value: Array<any>, skipNormalize?: boolean) => void;
|
||||
setLoading: (value: boolean) => void;
|
||||
reloadOptions: (setError?: boolean) => void;
|
||||
reloadOptions: (
|
||||
setError?: boolean,
|
||||
isInit?: boolean,
|
||||
data?: Record<string, any>
|
||||
) => void;
|
||||
deferLoad: (option: Option) => void;
|
||||
leftDeferLoad: (option: Option, leftOptions: Option) => void;
|
||||
expandTreeOptions: (nodePathArr: any[]) => void;
|
||||
@ -443,15 +448,12 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||
);
|
||||
|
||||
if (prevOptions !== options) {
|
||||
formItem.setOptions(
|
||||
normalizeOptions(
|
||||
options || [],
|
||||
undefined,
|
||||
props.valueField || 'value'
|
||||
),
|
||||
this.changeOptionValue,
|
||||
props.data
|
||||
formItem.loadOptionsFromDataScope(
|
||||
props.source as string,
|
||||
props.data,
|
||||
this.changeOptionValue
|
||||
);
|
||||
|
||||
this.normalizeValue();
|
||||
}
|
||||
} else if (
|
||||
@ -792,20 +794,16 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||
}
|
||||
|
||||
@autobind
|
||||
reloadOptions(setError?: boolean, isInit = false) {
|
||||
const {source, formItem, data, onChange, setPrinstineValue, valueField} =
|
||||
reloadOptions(setError?: boolean, isInit = false, data = this.props.data) {
|
||||
const {source, formItem, onChange, setPrinstineValue, valueField} =
|
||||
this.props;
|
||||
|
||||
if (formItem && isPureVariable(source as string)) {
|
||||
isAlive(formItem) &&
|
||||
formItem.setOptions(
|
||||
normalizeOptions(
|
||||
resolveVariableAndFilter(source as string, data, '| raw') || [],
|
||||
undefined,
|
||||
valueField
|
||||
),
|
||||
this.changeOptionValue,
|
||||
data
|
||||
formItem.loadOptionsFromDataScope(
|
||||
source as string,
|
||||
data,
|
||||
this.changeOptionValue
|
||||
);
|
||||
return;
|
||||
} else if (!formItem || !isEffectiveApi(source, data)) {
|
||||
|
@ -156,7 +156,8 @@ export function wrapControl<
|
||||
minLength,
|
||||
maxLength,
|
||||
validateOnChange,
|
||||
label
|
||||
label,
|
||||
pagination
|
||||
}
|
||||
} = this.props;
|
||||
|
||||
@ -230,7 +231,8 @@ export function wrapControl<
|
||||
validateOnChange,
|
||||
label,
|
||||
inputGroupControl,
|
||||
extraName
|
||||
extraName,
|
||||
pagination
|
||||
});
|
||||
|
||||
// issue 这个逻辑应该在 combo 里面自己实现。
|
||||
@ -380,7 +382,8 @@ export function wrapControl<
|
||||
'minLength',
|
||||
'maxLength',
|
||||
'label',
|
||||
'extraName'
|
||||
'extraName',
|
||||
'pagination'
|
||||
],
|
||||
prevProps.$schema,
|
||||
props.$schema,
|
||||
|
@ -7,11 +7,13 @@ import {
|
||||
Instance
|
||||
} from 'mobx-state-tree';
|
||||
import isEqualWith from 'lodash/isEqualWith';
|
||||
import uniqWith from 'lodash/uniqWith';
|
||||
import {FormStore, IFormStore} from './form';
|
||||
import {str2rules, validate as doValidate} from '../utils/validations';
|
||||
import {Api, Payload, fetchOptions, ApiObject} from '../types';
|
||||
import {ComboStore, IComboStore, IUniqueGroup} from './combo';
|
||||
import {evalExpression} from '../utils/tpl';
|
||||
import {resolveVariableAndFilter} from '../utils/tpl-builtin';
|
||||
import {buildApi, isEffectiveApi} from '../utils/api';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import {
|
||||
@ -98,6 +100,7 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
joinValues: true,
|
||||
extractValue: false,
|
||||
options: types.optional(types.frozen<Array<any>>(), []),
|
||||
optionsRaw: types.optional(types.frozen<Array<any>>(), []),
|
||||
expressionsInOptions: false,
|
||||
selectFirst: false,
|
||||
autoFill: types.frozen(),
|
||||
@ -113,7 +116,18 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
/** 当前表单项所属的InputGroup父元素, 用于收集InputGroup的子元素 */
|
||||
inputGroupControl: types.optional(types.frozen(), {}),
|
||||
colIndex: types.frozen(),
|
||||
rowIndex: types.frozen()
|
||||
rowIndex: types.frozen(),
|
||||
/** Transfer组件分页模式 */
|
||||
pagination: types.optional(types.frozen(), {
|
||||
enable: false,
|
||||
/** 当前页数 */
|
||||
page: 1,
|
||||
/** 每页显示条数 */
|
||||
perPage: 10,
|
||||
/** 总条数 */
|
||||
total: 0
|
||||
}),
|
||||
accumulatedOptions: types.optional(types.frozen<Array<any>>(), [])
|
||||
})
|
||||
.views(self => {
|
||||
function getForm(): any {
|
||||
@ -175,6 +189,26 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
return getLastOptionValue();
|
||||
},
|
||||
|
||||
/** 数据源接口数据是否开启分页 */
|
||||
get enableSourcePagination(): boolean {
|
||||
return !!self.pagination.enable;
|
||||
},
|
||||
|
||||
/** 数据源接口开启分页时当前页码 */
|
||||
get sourcePageNum(): number {
|
||||
return self.pagination.page ?? 1;
|
||||
},
|
||||
|
||||
/** 数据源接口开启分页时每页显示条数 */
|
||||
get sourcePerPageNum(): number {
|
||||
return self.pagination.perPage ?? 10;
|
||||
},
|
||||
|
||||
/** 数据源接口开启分页时数据总条数 */
|
||||
get sourceTotalNum(): number {
|
||||
return self.pagination.total ?? 0;
|
||||
},
|
||||
|
||||
getSelectedOptions: (
|
||||
value: any = self.tmpValue,
|
||||
nodeValueArray?: any[] | undefined
|
||||
@ -308,7 +342,8 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
minLength,
|
||||
validateOnChange,
|
||||
label,
|
||||
inputGroupControl
|
||||
inputGroupControl,
|
||||
pagination
|
||||
}: {
|
||||
extraName?: string;
|
||||
required?: boolean;
|
||||
@ -338,6 +373,11 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
path: string;
|
||||
[propsName: string]: any;
|
||||
};
|
||||
pagination?: {
|
||||
enable?: boolean;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
};
|
||||
}) {
|
||||
if (typeof rules === 'string') {
|
||||
rules = str2rules(rules);
|
||||
@ -372,6 +412,15 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
inputGroupControl?.name != null &&
|
||||
(self.inputGroupControl = inputGroupControl);
|
||||
|
||||
if (pagination && isObject(pagination) && !!pagination.enable) {
|
||||
self.pagination = {
|
||||
enable: true,
|
||||
page: pagination.page ? pagination.page || 1 : 1,
|
||||
perPage: pagination.perPage ? pagination.perPage || 10 : 10,
|
||||
total: 0
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
typeof rules !== 'undefined' ||
|
||||
typeof required !== 'undefined' ||
|
||||
@ -556,6 +605,23 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
}
|
||||
}
|
||||
|
||||
function setPagination(params: {
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
total?: number;
|
||||
}) {
|
||||
const {page, perPage, total} = params || {};
|
||||
|
||||
if (self.enableSourcePagination) {
|
||||
self.pagination = {
|
||||
...self.pagination,
|
||||
...(page != null && typeof page === 'number' ? {page} : {}),
|
||||
...(perPage != null && typeof perPage === 'number' ? {perPage} : {}),
|
||||
...(total != null && typeof total === 'number' ? {total} : {})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function setOptions(
|
||||
options: Array<object>,
|
||||
onChange?: (value: any) => void,
|
||||
@ -567,6 +633,15 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
options = filterTree(options, item => item);
|
||||
const originOptions = self.options.concat();
|
||||
self.options = options;
|
||||
/** 开启分页后当前选项内容需要累加 */
|
||||
self.accumulatedOptions = self.enableSourcePagination
|
||||
? uniqWith(
|
||||
[...originOptions, ...options],
|
||||
(lhs, rhs) =>
|
||||
lhs[self.valueField ?? 'value'] ===
|
||||
rhs[self.valueField ?? 'value']
|
||||
)
|
||||
: options;
|
||||
syncOptions(originOptions, data);
|
||||
let selectedOptions;
|
||||
|
||||
@ -722,6 +797,14 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
|
||||
options = normalizeOptions(options as any, undefined, self.valueField);
|
||||
|
||||
if (self.enableSourcePagination) {
|
||||
self.pagination = {
|
||||
...self.pagination,
|
||||
page: parseInt(json.data?.page, 10) || 1,
|
||||
total: parseInt(json.data?.total ?? json.data?.count, 10) || 0
|
||||
};
|
||||
}
|
||||
|
||||
if (config?.extendsOptions && self.selectedOptions.length > 0) {
|
||||
self.selectedOptions.forEach((item: any) => {
|
||||
const exited = findTree(
|
||||
@ -752,6 +835,41 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
return json;
|
||||
});
|
||||
|
||||
/**
|
||||
* 从数据域加载选项数据源,注意这里默认source变量解析后是全量的数据源
|
||||
*/
|
||||
function loadOptionsFromDataScope(
|
||||
source: string,
|
||||
ctx: Record<string, any>,
|
||||
onChange?: (value: any) => void
|
||||
) {
|
||||
let options: any[] = resolveVariableAndFilter(source, ctx, '| raw');
|
||||
|
||||
if (!Array.isArray(options)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
options = normalizeOptions(options, undefined, self.valueField);
|
||||
|
||||
if (self.enableSourcePagination) {
|
||||
self.pagination = {
|
||||
...self.pagination,
|
||||
...(ctx?.page ? {page: ctx?.page} : {}),
|
||||
...(ctx?.perPage ? {perPage: ctx?.perPage} : {}),
|
||||
total: options.length
|
||||
};
|
||||
}
|
||||
|
||||
options = options.slice(
|
||||
(self.pagination.page - 1) * self.pagination.perPage,
|
||||
self.pagination.page * self.pagination.perPage
|
||||
);
|
||||
|
||||
setOptions(options, onChange, ctx);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
const loadAutoUpdateData: (
|
||||
api: Api,
|
||||
data?: object,
|
||||
@ -1377,8 +1495,10 @@ export const FormItemStore = StoreNode.named('FormItemStore')
|
||||
setError,
|
||||
addError,
|
||||
clearError,
|
||||
setPagination,
|
||||
setOptions,
|
||||
loadOptions,
|
||||
loadOptionsFromDataScope,
|
||||
deferLoadOptions,
|
||||
deferLoadLeftOptions,
|
||||
expandTreeOptions,
|
||||
|
@ -786,6 +786,7 @@
|
||||
--transfer-base-header-paddingBottom: var(--sizes-size-5);
|
||||
--transfer-base-header-paddingLeft: var(--sizes-size-8);
|
||||
--transfer-base-header-paddingRight: var(--sizes-size-8);
|
||||
--transfer-base-footer-border-color: var(--colors-neutral-line-8);
|
||||
--transfer-base-body-paddingTop: var(--sizes-size-0);
|
||||
--transfer-base-body-paddingBottom: var(--sizes-size-0);
|
||||
--transfer-base-body-paddingLeft: var(--sizes-size-0);
|
||||
|
@ -39,6 +39,42 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-footer {
|
||||
border-top: 1px solid var(--transfer-base-footer-border-color);
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: flex-end;
|
||||
padding: var(--gap-sm);
|
||||
|
||||
/* 底部空间较小,让Pagination紧凑一些 */
|
||||
&-pagination {
|
||||
& > ul {
|
||||
&.#{$ns}Pagination-item {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
& > li {
|
||||
--Pagination-minWidth: #{px2rem(22px)};
|
||||
--Pagination-height: #{px2rem(22px)};
|
||||
--Pagination-padding: 0 #{px2rem(6px)};
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}Pagination-perpage {
|
||||
--select-base-default-paddingTop: 0;
|
||||
--select-base-default-paddingBottom: 0;
|
||||
--select-base-default-paddingLeft: #{px2rem(6px)};
|
||||
--select-base-default-paddingRight: #{px2rem(6px)};
|
||||
|
||||
margin-left: 0;
|
||||
|
||||
.#{$ns}Select-valueWrap {
|
||||
line-height: #{px2rem(22px)};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-select,
|
||||
&-result {
|
||||
overflow: hidden;
|
||||
@ -64,6 +100,10 @@
|
||||
var(--transfer-base-top-right-border-radius)
|
||||
var(--transfer-base-bottom-right-border-radius)
|
||||
var(--transfer-base-bottom-left-border-radius);
|
||||
|
||||
&--pagination {
|
||||
max-height: px2rem(475px);
|
||||
}
|
||||
}
|
||||
|
||||
&-select > &-selection,
|
||||
|
@ -4,7 +4,6 @@ import includes from 'lodash/includes';
|
||||
import debounce from 'lodash/debounce';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import unionWith from 'lodash/unionWith';
|
||||
|
||||
import {ThemeProps, themeable, findTree, differenceFromAll} from 'amis-core';
|
||||
import {BaseSelectionProps, BaseSelection, ItemRenderStates} from './Selection';
|
||||
import {Options, Option} from './Select';
|
||||
@ -24,6 +23,7 @@ import {ItemRenderStates as ResultItemRenderStates} from './ResultList';
|
||||
import ResultTableList from './ResultTableList';
|
||||
import ResultTreeList from './ResultTreeList';
|
||||
import {SpinnerExtraProps} from './Spinner';
|
||||
import Pagination from './Pagination';
|
||||
|
||||
export type SelectMode =
|
||||
| 'table'
|
||||
@ -113,6 +113,44 @@ export interface TransferProps
|
||||
checkAllLabel?: string;
|
||||
/** 树形模式下,给 tree 的属性 */
|
||||
onlyChildren?: boolean;
|
||||
/** 分页模式下累积的选项值,用于右侧回显 */
|
||||
accumulatedOptions?: Option[];
|
||||
/** 分页配置 */
|
||||
pagination?: {
|
||||
/** 是否开启分页 */
|
||||
enable: boolean;
|
||||
/** 分页组件CSS类名 */
|
||||
className?: string;
|
||||
/**
|
||||
* 通过控制layout属性的顺序,调整分页结构 total,perPage,pager,go
|
||||
* @default 'pager'
|
||||
*/
|
||||
layout?: string | Array<string>;
|
||||
|
||||
/**
|
||||
* 指定每页可以显示多少条
|
||||
* @default [10, 20, 50, 100]
|
||||
*/
|
||||
perPageAvailable?: Array<number>;
|
||||
|
||||
/**
|
||||
* 最多显示多少个分页按钮。
|
||||
*
|
||||
* @default 5
|
||||
*/
|
||||
maxButtons?: number;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
total?: number;
|
||||
popOverContainer?: any;
|
||||
popOverContainerSelector?: string;
|
||||
};
|
||||
/** 切换分页事件 */
|
||||
onPageChange?: (
|
||||
page: number,
|
||||
perPage?: number,
|
||||
direction?: 'forward' | 'backward'
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface TransferState {
|
||||
@ -549,10 +587,33 @@ export class Transfer<
|
||||
{this.state.searchResult !== null
|
||||
? this.renderSearchResult(props)
|
||||
: this.renderOptions(props)}
|
||||
|
||||
{this.renderFooter()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderFooter() {
|
||||
const {classnames: cx, pagination, onPageChange} = this.props;
|
||||
|
||||
return pagination?.enable ? (
|
||||
<div className={cx('Transfer-footer')}>
|
||||
<Pagination
|
||||
className={cx('Transfer-footer-pagination', pagination.className)}
|
||||
activePage={pagination.page}
|
||||
perPage={pagination.perPage}
|
||||
total={pagination.total}
|
||||
layout={pagination.layout}
|
||||
maxButtons={pagination.maxButtons}
|
||||
perPageAvailable={pagination.perPageAvailable}
|
||||
popOverContainer={pagination.popOverContainer}
|
||||
popOverContainerSelector={pagination.popOverContainerSelector}
|
||||
onPageChange={onPageChange}
|
||||
/>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
renderSearchResult(props: TransferProps) {
|
||||
const {
|
||||
searchResultMode,
|
||||
@ -827,9 +888,10 @@ export class Transfer<
|
||||
virtualThreshold,
|
||||
itemHeight,
|
||||
loadingConfig,
|
||||
showInvalidMatch
|
||||
showInvalidMatch,
|
||||
pagination,
|
||||
accumulatedOptions
|
||||
} = this.props;
|
||||
|
||||
const {resultSelectMode, isTreeDeferLoad} = this.state;
|
||||
const searchable = !isTreeDeferLoad && resultSearchable;
|
||||
|
||||
@ -840,7 +902,7 @@ export class Transfer<
|
||||
ref={this.domResultRef}
|
||||
classnames={cx}
|
||||
columns={columns!}
|
||||
options={options || []}
|
||||
options={(pagination?.enable ? accumulatedOptions : options) || []}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
option2value={option2value}
|
||||
@ -862,7 +924,7 @@ export class Transfer<
|
||||
loadingConfig={loadingConfig}
|
||||
classnames={cx}
|
||||
className={cx('Transfer-value')}
|
||||
options={options}
|
||||
options={(pagination?.enable ? accumulatedOptions : options) || []}
|
||||
valueField={'value'}
|
||||
value={value || []}
|
||||
onChange={onChange!}
|
||||
@ -915,7 +977,8 @@ export class Transfer<
|
||||
selectMode = 'list',
|
||||
translate: __,
|
||||
valueField = 'value',
|
||||
mobileUI
|
||||
mobileUI,
|
||||
pagination
|
||||
} = this.props as any;
|
||||
const {searchResult} = this.state;
|
||||
|
||||
@ -939,7 +1002,11 @@ export class Transfer<
|
||||
<div
|
||||
className={cx('Transfer', className, inline ? 'Transfer--inline' : '')}
|
||||
>
|
||||
<div className={cx('Transfer-select')}>
|
||||
<div
|
||||
className={cx('Transfer-select', {
|
||||
'Transfer-select--pagination': !!pagination?.enable
|
||||
})}
|
||||
>
|
||||
{this.renderSelect(this.props)}
|
||||
</div>
|
||||
<div className={cx('Transfer-mid', {'is-mobile': mobileUI})}>
|
||||
@ -949,7 +1016,12 @@ export class Transfer<
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={cx('Transfer-result', {'is-mobile': mobileUI})}>
|
||||
<div
|
||||
className={cx('Transfer-result', {
|
||||
'is-mobile': mobileUI,
|
||||
'Transfer-select--pagination': !!pagination?.enable
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
'Transfer-title',
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1392,7 +1392,6 @@ test('Renderer:transfer search highlight', async () => {
|
||||
});
|
||||
|
||||
test('Renderer:transfer tree search', async () => {
|
||||
|
||||
const onSubmit = jest.fn();
|
||||
const {container, findByText, getByText} = render(
|
||||
amisRender(
|
||||
@ -1486,7 +1485,7 @@ test('Renderer:transfer tree search', async () => {
|
||||
});
|
||||
|
||||
await(300);
|
||||
|
||||
|
||||
const libai = getByText('李白');
|
||||
expect(libai).not.toBeNull();
|
||||
fireEvent.click(libai);
|
||||
@ -1501,4 +1500,310 @@ test('Renderer:transfer tree search', async () => {
|
||||
expect(onSubmit.mock.calls[0][0]).toEqual({
|
||||
transfer: "caocao,libai"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Renderer:Transfer with pagination', async () => {
|
||||
const mockData = [
|
||||
{
|
||||
"label": "Laura Lewis",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"label": "David Gonzalez",
|
||||
"value": "2"
|
||||
},
|
||||
{
|
||||
"label": "Christopher Rodriguez",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"label": "Sarah Young",
|
||||
"value": "4"
|
||||
},
|
||||
{
|
||||
"label": "James Jones",
|
||||
"value": "5"
|
||||
},
|
||||
{
|
||||
"label": "Larry Robinson",
|
||||
"value": "6"
|
||||
},
|
||||
{
|
||||
"label": "Christopher Perez",
|
||||
"value": "7"
|
||||
},
|
||||
{
|
||||
"label": "Sharon Davis",
|
||||
"value": "8"
|
||||
},
|
||||
{
|
||||
"label": "Kenneth Anderson",
|
||||
"value": "9"
|
||||
},
|
||||
{
|
||||
"label": "Deborah Lewis",
|
||||
"value": "10"
|
||||
},
|
||||
{
|
||||
"label": "Jennifer Lewis",
|
||||
"value": "11"
|
||||
},
|
||||
{
|
||||
"label": "Laura Miller",
|
||||
"value": "12"
|
||||
},
|
||||
{
|
||||
"label": "Larry Harris",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"label": "Patricia Robinson",
|
||||
"value": "14"
|
||||
},
|
||||
{
|
||||
"label": "Mark Davis",
|
||||
"value": "15"
|
||||
},
|
||||
{
|
||||
"label": "Jessica Harris",
|
||||
"value": "16"
|
||||
},
|
||||
{
|
||||
"label": "Anna Brown",
|
||||
"value": "17"
|
||||
},
|
||||
{
|
||||
"label": "Lisa Young",
|
||||
"value": "18"
|
||||
},
|
||||
{
|
||||
"label": "Donna Williams",
|
||||
"value": "19"
|
||||
},
|
||||
{
|
||||
"label": "Shirley Davis",
|
||||
"value": "20"
|
||||
}
|
||||
];
|
||||
const fetcher = jest.fn().mockImplementation((api) => {
|
||||
const perPage = 10; /** 锁死10个方便测试 */
|
||||
const page = Number(api.query.page || 1);
|
||||
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
status: 0,
|
||||
msg: 'ok',
|
||||
data: {
|
||||
count: mockData.length,
|
||||
page: page,
|
||||
items: mockData.concat().splice((page - 1) * perPage, perPage)
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
"type": "form",
|
||||
"debug": true,
|
||||
"body": [
|
||||
{
|
||||
"label": "默认",
|
||||
"type": "transfer",
|
||||
"name": "transfer",
|
||||
"joinValues": false,
|
||||
"extractValue": false,
|
||||
"source": "/api/mock2/options/transfer?page=${page}&perPage=${perPage}",
|
||||
"pagination": {
|
||||
"enable": true,
|
||||
"layout": ["pager", "perpage", "total"],
|
||||
"popOverContainerSelector": ".cxd-Panel--form"
|
||||
},
|
||||
"value": [
|
||||
{"label": "Laura Lewis", "value": "1", id: 1},
|
||||
{"label": "Christopher Rodriguez", "value": "3", id: 3},
|
||||
{"label": "Laura Miller", "value": "12", id: 12},
|
||||
{"label": "Patricia Robinson", "value": "14", id: 14}
|
||||
]
|
||||
}
|
||||
]
|
||||
}, {}, makeEnv({fetcher})));
|
||||
|
||||
await wait(500);
|
||||
expect(container.querySelector('.cxd-Transfer-footer-pagination')).toBeInTheDocument();
|
||||
|
||||
const checkboxes = container.querySelectorAll('input[type=checkbox]')!;
|
||||
expect(checkboxes.length).toEqual(11); /** 包括顶部全选 */
|
||||
expect((checkboxes[1] as HTMLInputElement)?.checked).toEqual(true);
|
||||
expect((checkboxes[2] as HTMLInputElement)?.checked).toEqual(false);
|
||||
expect((checkboxes[3] as HTMLInputElement)?.checked).toEqual(true);
|
||||
expect((checkboxes[4] as HTMLInputElement)?.checked).toEqual(false);
|
||||
|
||||
const nextBtn = container.querySelector('.cxd-Pagination-next')!;
|
||||
fireEvent.click(nextBtn);
|
||||
await wait(500);
|
||||
|
||||
const checkboxes2 = container.querySelectorAll('input[type=checkbox]')!;
|
||||
expect(checkboxes2.length).toEqual(11);
|
||||
expect((checkboxes2[1] as HTMLInputElement)?.checked).toEqual(false);
|
||||
expect((checkboxes2[2] as HTMLInputElement)?.checked).toEqual(true);
|
||||
expect((checkboxes2[3] as HTMLInputElement)?.checked).toEqual(false);
|
||||
expect((checkboxes2[4] as HTMLInputElement)?.checked).toEqual(true);
|
||||
})
|
||||
|
||||
test.only('Renderer:Transfer with pagination and data source from data scope', async () => {
|
||||
const mockData = [
|
||||
{
|
||||
"label": "Laura Lewis",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"label": "David Gonzalez",
|
||||
"value": "2"
|
||||
},
|
||||
{
|
||||
"label": "Christopher Rodriguez",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"label": "Sarah Young",
|
||||
"value": "4"
|
||||
},
|
||||
{
|
||||
"label": "James Jones",
|
||||
"value": "5"
|
||||
},
|
||||
{
|
||||
"label": "Larry Robinson",
|
||||
"value": "6"
|
||||
},
|
||||
{
|
||||
"label": "Christopher Perez",
|
||||
"value": "7"
|
||||
},
|
||||
{
|
||||
"label": "Sharon Davis",
|
||||
"value": "8"
|
||||
},
|
||||
{
|
||||
"label": "Kenneth Anderson",
|
||||
"value": "9"
|
||||
},
|
||||
{
|
||||
"label": "Deborah Lewis",
|
||||
"value": "10"
|
||||
},
|
||||
{
|
||||
"label": "Jennifer Lewis",
|
||||
"value": "11"
|
||||
},
|
||||
{
|
||||
"label": "Laura Miller",
|
||||
"value": "12"
|
||||
},
|
||||
{
|
||||
"label": "Larry Harris",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"label": "Patricia Robinson",
|
||||
"value": "14"
|
||||
},
|
||||
{
|
||||
"label": "Mark Davis",
|
||||
"value": "15"
|
||||
},
|
||||
{
|
||||
"label": "Jessica Harris",
|
||||
"value": "16"
|
||||
},
|
||||
{
|
||||
"label": "Anna Brown",
|
||||
"value": "17"
|
||||
},
|
||||
{
|
||||
"label": "Lisa Young",
|
||||
"value": "18"
|
||||
},
|
||||
{
|
||||
"label": "Donna Williams",
|
||||
"value": "19"
|
||||
},
|
||||
{
|
||||
"label": "Shirley Davis",
|
||||
"value": "20"
|
||||
}
|
||||
];
|
||||
const fetcher = jest.fn().mockImplementation((api) => {
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
status: 0,
|
||||
msg: 'ok',
|
||||
data: {
|
||||
count: mockData.length,
|
||||
items: mockData
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
"type": "form",
|
||||
"debug": true,
|
||||
"body": [
|
||||
{
|
||||
"type": "service",
|
||||
"api": {
|
||||
"url": "/api/mock2/options/loadDataOnce",
|
||||
"method": "get",
|
||||
"responseData": {
|
||||
"transferOptions": "${items}"
|
||||
}
|
||||
},
|
||||
body: [
|
||||
{
|
||||
"label": "默认",
|
||||
"type": "transfer",
|
||||
"name": "transfer",
|
||||
"joinValues": false,
|
||||
"extractValue": false,
|
||||
"source": "${transferOptions}",
|
||||
"pagination": {
|
||||
"enable": true,
|
||||
"layout": ["pager", "perpage", "total"],
|
||||
"popOverContainerSelector": ".cxd-Panel--form"
|
||||
},
|
||||
"value": [
|
||||
{"label": "Laura Lewis", "value": "1", id: 1},
|
||||
{"label": "Christopher Rodriguez", "value": "3", id: 3},
|
||||
{"label": "Laura Miller", "value": "12", id: 12},
|
||||
{"label": "Patricia Robinson", "value": "14", id: 14}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}, {}, makeEnv({fetcher})));
|
||||
|
||||
await wait(500);
|
||||
expect(container.querySelector('.cxd-Transfer-footer-pagination')).toBeInTheDocument();
|
||||
|
||||
const checkboxes = container.querySelectorAll('input[type=checkbox]')!;
|
||||
expect(checkboxes.length).toEqual(11); /** 包括顶部全选 */
|
||||
expect((checkboxes[1] as HTMLInputElement)?.checked).toEqual(true);
|
||||
expect((checkboxes[2] as HTMLInputElement)?.checked).toEqual(false);
|
||||
expect((checkboxes[3] as HTMLInputElement)?.checked).toEqual(true);
|
||||
expect((checkboxes[4] as HTMLInputElement)?.checked).toEqual(false);
|
||||
|
||||
const nextBtn = container.querySelector('.cxd-Pagination-next')!;
|
||||
fireEvent.click(nextBtn);
|
||||
await wait(500);
|
||||
|
||||
const checkboxes2 = container.querySelectorAll('input[type=checkbox]')!;
|
||||
expect(checkboxes2.length).toEqual(11);
|
||||
expect((checkboxes2[1] as HTMLInputElement)?.checked).toEqual(false);
|
||||
expect((checkboxes2[2] as HTMLInputElement)?.checked).toEqual(true);
|
||||
expect((checkboxes2[3] as HTMLInputElement)?.checked).toEqual(false);
|
||||
expect((checkboxes2[4] as HTMLInputElement)?.checked).toEqual(true);
|
||||
})
|
||||
|
@ -1,17 +1,17 @@
|
||||
import React from 'react';
|
||||
import find from 'lodash/find';
|
||||
|
||||
import pick from 'lodash/pick';
|
||||
import {isAlive} from 'mobx-state-tree';
|
||||
import {matchSorter} from 'match-sorter';
|
||||
import {
|
||||
OptionsControlProps,
|
||||
OptionsControl,
|
||||
FormOptionsControl,
|
||||
resolveEventData,
|
||||
str2function,
|
||||
getOptionValueBindField
|
||||
} from 'amis-core';
|
||||
import {SpinnerExtraProps, Transfer} from 'amis-ui';
|
||||
import type {Option} from 'amis-core';
|
||||
import {
|
||||
getOptionValueBindField,
|
||||
isEffectiveApi,
|
||||
isPureVariable,
|
||||
resolveVariableAndFilter,
|
||||
autobind,
|
||||
filterTree,
|
||||
string2regExp,
|
||||
@ -20,18 +20,25 @@ import {
|
||||
findTreeIndex,
|
||||
getTree,
|
||||
spliceTree,
|
||||
mapTree
|
||||
mapTree,
|
||||
optionValueCompare,
|
||||
resolveVariable,
|
||||
ActionObject,
|
||||
toNumber
|
||||
} from 'amis-core';
|
||||
import {Spinner} from 'amis-ui';
|
||||
import {optionValueCompare} from 'amis-core';
|
||||
import {resolveVariable} from 'amis-core';
|
||||
import {FormOptionsSchema, SchemaApi, SchemaObject} from '../../Schema';
|
||||
import {Selection as BaseSelection} from 'amis-ui';
|
||||
import {ResultList} from 'amis-ui';
|
||||
import {ActionObject, toNumber} from 'amis-core';
|
||||
import type {ItemRenderStates} from 'amis-ui/lib/components/Selection';
|
||||
import {SpinnerExtraProps, Transfer, Spinner, ResultList} from 'amis-ui';
|
||||
import {
|
||||
FormOptionsSchema,
|
||||
SchemaApi,
|
||||
SchemaObject,
|
||||
SchemaExpression,
|
||||
SchemaClassName
|
||||
} from '../../Schema';
|
||||
import {supportStatic} from './StaticHoc';
|
||||
import {matchSorter} from 'match-sorter';
|
||||
|
||||
import type {ItemRenderStates} from 'amis-ui/lib/components/Selection';
|
||||
import type {Option} from 'amis-core';
|
||||
import type {PaginationSchema} from '../Pagination';
|
||||
|
||||
/**
|
||||
* Transfer
|
||||
@ -161,6 +168,22 @@ export interface TransferControlSchema
|
||||
* 树形模式下,仅选中子节点
|
||||
*/
|
||||
onlyChildren?: boolean;
|
||||
|
||||
/**
|
||||
* 分页配置,selectMode为默认和table才会生效
|
||||
* @since 3.6.0
|
||||
*/
|
||||
pagination?: {
|
||||
/** 是否左侧选项分页,默认不开启 */
|
||||
enable: SchemaExpression;
|
||||
/** 分页组件CSS类名 */
|
||||
className?: SchemaClassName;
|
||||
/** 是否开启前端分页 */
|
||||
loadDataOnce?: boolean;
|
||||
} & Pick<
|
||||
PaginationSchema,
|
||||
'layout' | 'maxButtons' | 'perPageAvailable' | 'popOverContainerSelector'
|
||||
>;
|
||||
}
|
||||
|
||||
export interface BaseTransferProps
|
||||
@ -427,6 +450,30 @@ export class BaseTransferRenderer<
|
||||
return regexp.test(labelTest) || regexp.test(valueTest);
|
||||
}
|
||||
|
||||
@autobind
|
||||
handlePageChange(
|
||||
page: number,
|
||||
perPage?: number,
|
||||
direction?: 'forward' | 'backward'
|
||||
) {
|
||||
const {source, data, formItem, onChange} = this.props;
|
||||
const ctx = createObject(data, {
|
||||
page: page ?? 1,
|
||||
perPage: perPage ?? 10,
|
||||
...(direction ? {pageDir: direction} : {})
|
||||
});
|
||||
|
||||
if (!formItem || !isAlive(formItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPureVariable(source)) {
|
||||
formItem.loadOptionsFromDataScope(source, ctx, onChange);
|
||||
} else if (isEffectiveApi(source, ctx)) {
|
||||
formItem.loadOptions(source, ctx, undefined, false, onChange, false);
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
optionItemRender(option: Option, states: ItemRenderStates) {
|
||||
const {menuTpl, render, data} = this.props;
|
||||
@ -544,7 +591,11 @@ export class BaseTransferRenderer<
|
||||
showInvalidMatch,
|
||||
onlyChildren,
|
||||
mobileUI,
|
||||
noResultsText
|
||||
noResultsText,
|
||||
pagination,
|
||||
formItem,
|
||||
env,
|
||||
popOverContainer
|
||||
} = this.props;
|
||||
|
||||
// 目前 LeftOptions 没有接口可以动态加载
|
||||
@ -570,6 +621,7 @@ export class BaseTransferRenderer<
|
||||
onlyChildren={onlyChildren}
|
||||
value={selectedOptions}
|
||||
options={options}
|
||||
accumulatedOptions={formItem?.accumulatedOptions ?? []}
|
||||
disabled={disabled}
|
||||
onChange={this.handleChange}
|
||||
option2value={this.option2value}
|
||||
@ -607,6 +659,28 @@ export class BaseTransferRenderer<
|
||||
showInvalidMatch={showInvalidMatch}
|
||||
mobileUI={mobileUI}
|
||||
noResultsText={noResultsText}
|
||||
pagination={{
|
||||
...pick(pagination, [
|
||||
'className',
|
||||
'layout',
|
||||
'perPageAvailable',
|
||||
'popOverContainerSelector'
|
||||
]),
|
||||
enable:
|
||||
!!formItem?.enableSourcePagination &&
|
||||
(!selectMode ||
|
||||
selectMode === 'list' ||
|
||||
selectMode === 'table') &&
|
||||
options.length > 0,
|
||||
maxButtons: Number.isInteger(pagination?.maxButtons)
|
||||
? pagination.maxButtons
|
||||
: 5,
|
||||
page: formItem?.sourcePageNum,
|
||||
perPage: formItem?.sourcePerPageNum,
|
||||
total: formItem?.sourceTotalNum,
|
||||
popOverContainer: popOverContainer ?? env?.getModalContainer
|
||||
}}
|
||||
onPageChange={this.handlePageChange}
|
||||
/>
|
||||
|
||||
<Spinner
|
||||
|
Loading…
Reference in New Issue
Block a user