mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-02 03:48:13 +08:00
parent
47f6747213
commit
2b5efd478e
@ -10,6 +10,8 @@ order: 45
|
||||
|
||||
## 基本用法
|
||||
|
||||
通过 name 属性指定要循环的数组,items 属性指定循环的内容。
|
||||
|
||||
```schema: scope="page"
|
||||
{
|
||||
"type": "page",
|
||||
@ -29,7 +31,9 @@ order: 45
|
||||
|
||||
### 如果是对象数组
|
||||
|
||||
如果数组中的数据是对象,可以直接使用 data.xxx 来获取,另外能用 data.index 来获取数组索引,但如果对象本身也有名字为 index 的字段就会覆盖到,获取不到索引了。
|
||||
如果数组中的数据是对象,可以直接使用内部变量 xxx 来获取,或者通过 `item.xxxx`。另外能用 index 来获取数组索引。
|
||||
|
||||
> 如果成员对象本身也有名字为 index 的字段就会覆盖到,获取不到索引了,请查看「循环嵌套」的章节解决
|
||||
|
||||
```schema:height="160" scope="page"
|
||||
{
|
||||
@ -42,12 +46,49 @@ order: 45
|
||||
"name": "arr",
|
||||
"items": {
|
||||
"type": "tpl",
|
||||
"tpl": "<span class='label label-default m-l-sm'><%= data.name %>:<%= data.index %></span> "
|
||||
"tpl": "<span class='label label-default m-l-sm'>${name}:${index}</span> "
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 循环嵌套
|
||||
|
||||
如果存在嵌套使用,通过默认的 `item` 或者 `index` 始终是拿的最里面那层的信息,想要获取上层 each 的信息,则需要自定义 `itemKeyName` 和 `indexKeyName` 来指定字段名。
|
||||
|
||||
```schema:height="160" scope="page"
|
||||
{
|
||||
"type": "page",
|
||||
"data": {
|
||||
"arr": [{"name": "a", "subList": ["a1", "a2"]}, {"name": "b", "subList": ["b1", "b2"]}, {"name": "c", "subList": ["c1", "c2"]}]
|
||||
},
|
||||
"body": {
|
||||
"type": "each",
|
||||
"name": "arr",
|
||||
"itemKeyName": "itemOutter",
|
||||
"indexKeyName": "indexOutter",
|
||||
"items": [
|
||||
{
|
||||
"type": "tpl",
|
||||
"inline": false,
|
||||
"tpl": "<span class='label label-default m-l-sm'>${name}:${index}</span> "
|
||||
},
|
||||
|
||||
{
|
||||
"type": "each",
|
||||
"name": "subList",
|
||||
"items": [
|
||||
{
|
||||
"type": "tpl",
|
||||
"tpl": "<span class='label label-default m-l-sm'>${itemOutter.name}-${item}:${indexOutter}-${index}</span> "
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 用作 Field 时
|
||||
|
||||
当用在 Table 的列配置 Column、List 的内容、Card 卡片的内容和表单的 Static-XXX 中时,可以设置`name`属性,映射同名变量,然后用可以通过 `item` 变量获取单项值
|
||||
@ -138,13 +179,47 @@ List 的内容、Card 卡片的内容配置同上
|
||||
|
||||
`name` 的优先级会比 `source` 更高
|
||||
|
||||
## 动态表单项
|
||||
|
||||
> 3.5.0 版本开始支持
|
||||
|
||||
表单项支持通过表达式配置动态表单项,可结合 `each` 组件一起使用。
|
||||
|
||||
```schema: scope="page"
|
||||
{
|
||||
"type": "page",
|
||||
"data": {
|
||||
"arr": ["A", "B", "C"]
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
type: "form",
|
||||
debug: true,
|
||||
body: [
|
||||
{
|
||||
"type": "each",
|
||||
"source": "${arr}",
|
||||
"items": {
|
||||
"type": "input-text",
|
||||
"label": "Input${item}",
|
||||
"name": "text${index}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ----------- | -------- | -------- | -------------------------------------------------------------------- |
|
||||
| type | `string` | `"each"` | 指定为 Each 组件 |
|
||||
| value | `array` | `[]` | 用于循环的值 |
|
||||
| name | `string` | | 获取数据域中变量 |
|
||||
| source | `string` | | 获取数据域中变量, 支持 [数据映射](../../docs/concepts/data-mapping) |
|
||||
| items | `object` | | 使用`value`中的数据,循环输出渲染器。 |
|
||||
| placeholder | `string` | | 当 `value` 值不存在或为空数组时的占位文本 |
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------------ | -------- | -------- | -------------------------------------------------------------------- |
|
||||
| type | `string` | `"each"` | 指定为 Each 组件 |
|
||||
| value | `array` | `[]` | 用于循环的值 |
|
||||
| name | `string` | | 获取数据域中变量 |
|
||||
| source | `string` | | 获取数据域中变量, 支持 [数据映射](../../docs/concepts/data-mapping) |
|
||||
| items | `object` | | 使用`value`中的数据,循环输出渲染器。 |
|
||||
| placeholder | `string` | | 当 `value` 值不存在或为空数组时的占位文本 |
|
||||
| itemKeyName | `string` | `item` | 获取循环当前数组成员 |
|
||||
| indexKeyName | `string` | `index` | 获取循环当前索引 |
|
||||
|
@ -31,7 +31,7 @@ import {FormBaseControl, FormItemWrap} from './Item';
|
||||
import {Api} from '../types';
|
||||
import {TableStore} from '../store/table';
|
||||
import pick from 'lodash/pick';
|
||||
import {callStrFunction, changedEffect} from '../utils';
|
||||
import {callStrFunction, changedEffect, tokenize} from '../utils';
|
||||
|
||||
export interface ControlOutterProps extends RendererProps {
|
||||
formStore?: IFormStore;
|
||||
@ -129,7 +129,6 @@ export function wrapControl<
|
||||
colIndex,
|
||||
rowIndex,
|
||||
$schema: {
|
||||
name,
|
||||
id,
|
||||
type,
|
||||
required,
|
||||
@ -163,6 +162,13 @@ export function wrapControl<
|
||||
this.handleBlur = this.handleBlur.bind(this);
|
||||
this.validate = this.validate.bind(this);
|
||||
this.flushChange = this.flushChange.bind(this);
|
||||
let name = this.props.$schema.name;
|
||||
|
||||
// 如果 name 是表达式
|
||||
// 扩充 each 用法
|
||||
if (isExpression(name)) {
|
||||
name = tokenize(name, data);
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
// 一般情况下这些表单项都是需要 name 的,提示一下
|
||||
@ -308,7 +314,7 @@ export function wrapControl<
|
||||
const {
|
||||
store,
|
||||
formStore: form,
|
||||
$schema: {name, validate},
|
||||
$schema: {validate},
|
||||
addHook
|
||||
} = this.props;
|
||||
|
||||
@ -326,7 +332,7 @@ export function wrapControl<
|
||||
return finalValidate(
|
||||
this.props.data,
|
||||
this.getValue(),
|
||||
name
|
||||
formItem.name
|
||||
).then((ret: any) => {
|
||||
if ((typeof ret === 'string' || Array.isArray(ret)) && ret) {
|
||||
formItem.addError(ret, 'control:valdiate');
|
||||
@ -516,12 +522,7 @@ export function wrapControl<
|
||||
}
|
||||
|
||||
controlRef(control: any) {
|
||||
const {
|
||||
addHook,
|
||||
removeHook,
|
||||
formStore: form,
|
||||
$schema: {name}
|
||||
} = this.props;
|
||||
const {addHook, removeHook, formStore: form} = this.props;
|
||||
|
||||
// 因为 control 有可能被 n 层 hoc 包裹。
|
||||
while (control && control.getWrappedInstance) {
|
||||
@ -534,16 +535,15 @@ export function wrapControl<
|
||||
this.hook = () => {
|
||||
formItem.clearError('component:valdiate');
|
||||
|
||||
return validate(this.props.data, this.getValue(), name).then(
|
||||
ret => {
|
||||
if (
|
||||
(typeof ret === 'string' || Array.isArray(ret)) &&
|
||||
ret
|
||||
) {
|
||||
formItem.setError(ret, 'component:valdiate');
|
||||
}
|
||||
return validate(
|
||||
this.props.data,
|
||||
this.getValue(),
|
||||
formItem.name
|
||||
).then(ret => {
|
||||
if ((typeof ret === 'string' || Array.isArray(ret)) && ret) {
|
||||
formItem.setError(ret, 'component:valdiate');
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
addHook?.(this.hook);
|
||||
} else if (!control && this.hook) {
|
||||
@ -675,7 +675,6 @@ export function wrapControl<
|
||||
formStore: form,
|
||||
onChange,
|
||||
$schema: {
|
||||
name,
|
||||
id,
|
||||
label,
|
||||
type,
|
||||
@ -713,7 +712,7 @@ export function wrapControl<
|
||||
eventType: 'formItemChange',
|
||||
eventData: {
|
||||
id,
|
||||
name,
|
||||
name: model.name,
|
||||
label,
|
||||
type,
|
||||
value
|
||||
@ -737,10 +736,10 @@ export function wrapControl<
|
||||
|
||||
if (model.extraName) {
|
||||
const values = model.splitExtraValue(value);
|
||||
onChange?.(values[0], name!);
|
||||
onChange?.(values[0], model.name);
|
||||
onChange?.(values[1], model.extraName, submitOnChange === true);
|
||||
} else {
|
||||
onChange?.(value, name!, submitOnChange === true);
|
||||
onChange?.(value, model.name, submitOnChange === true);
|
||||
}
|
||||
this.checkValidate();
|
||||
}
|
||||
@ -766,7 +765,6 @@ export function wrapControl<
|
||||
const model = this.model;
|
||||
const {
|
||||
formStore: form,
|
||||
name,
|
||||
$schema: {pipeOut},
|
||||
onChange,
|
||||
value: oldValue,
|
||||
@ -786,10 +784,10 @@ export function wrapControl<
|
||||
|
||||
if (model.extraName) {
|
||||
const values = model.splitExtraValue(value);
|
||||
onChange?.(values[0], name!, false, true);
|
||||
onChange?.(values[0], model.name!, false, true);
|
||||
onChange?.(values[1], model.extraName!, false, true);
|
||||
} else {
|
||||
onChange?.(value, name!, false, true);
|
||||
onChange?.(value, model.name!, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -812,12 +810,9 @@ export function wrapControl<
|
||||
|
||||
// 兼容老版本用法,新版本直接用 onChange 就可以。
|
||||
setValue(value: any, key?: string) {
|
||||
const {
|
||||
$schema: {name},
|
||||
onBulkChange
|
||||
} = this.props;
|
||||
const {onBulkChange} = this.props;
|
||||
|
||||
if (!key || key === name) {
|
||||
if (!key || (this.model && key === this.model.name)) {
|
||||
this.handleChange(value);
|
||||
} else {
|
||||
onBulkChange &&
|
||||
@ -854,6 +849,7 @@ export function wrapControl<
|
||||
formMode: control.mode || formMode,
|
||||
ref: this.controlRef,
|
||||
data: data || store?.data,
|
||||
name: model?.name ?? control.name,
|
||||
value,
|
||||
changeMotivation: model?.changeMotivation,
|
||||
defaultValue: control.value,
|
||||
|
@ -244,3 +244,49 @@ test('Renderer:FormItem:extraName', async () => {
|
||||
end: `${moment().format('YYYY-MM')}-16`
|
||||
});
|
||||
});
|
||||
|
||||
test('Renderer:FormItem:dynamicName', async () => {
|
||||
const onSubmit = jest.fn();
|
||||
|
||||
const {container, getByText} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'form',
|
||||
id: 'theform',
|
||||
submitText: 'Submit',
|
||||
body: [
|
||||
{
|
||||
type: 'input-text',
|
||||
name: '${a}',
|
||||
label: 'Label'
|
||||
}
|
||||
],
|
||||
title: 'The form'
|
||||
},
|
||||
{
|
||||
onSubmit,
|
||||
data: {
|
||||
a: 'abc'
|
||||
}
|
||||
},
|
||||
makeEnv({})
|
||||
)
|
||||
);
|
||||
|
||||
const input = container.querySelector('input[name=abc]');
|
||||
expect(input).toBeTruthy();
|
||||
fireEvent.change(input!, {
|
||||
target: {
|
||||
value: '123'
|
||||
}
|
||||
});
|
||||
await wait(500); // 有 250 秒左右的节流
|
||||
fireEvent.click(getByText('Submit'));
|
||||
await wait(300);
|
||||
|
||||
expect(onSubmit).toHaveBeenCalled();
|
||||
|
||||
expect(onSubmit.mock.calls[0][0]).toMatchObject({
|
||||
abc: '123'
|
||||
});
|
||||
});
|
||||
|
@ -5,6 +5,34 @@ import {resolveVariable, resolveVariableAndFilter} from 'amis-core';
|
||||
import {createObject, getPropValue, isObject} from 'amis-core';
|
||||
import {BaseSchema, SchemaCollection} from '../Schema';
|
||||
|
||||
export interface EachExtraProps extends RendererProps {
|
||||
items: any;
|
||||
item: any;
|
||||
index: number;
|
||||
itemKeyName: string;
|
||||
indexKeyName: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
function EachItem(props: EachExtraProps) {
|
||||
const {render, data, items, item, name, index, itemKeyName, indexKeyName} =
|
||||
props;
|
||||
const ctx = React.useMemo(
|
||||
() =>
|
||||
createObject(data, {
|
||||
...(isObject(item) ? item : {}),
|
||||
[name]: item,
|
||||
[itemKeyName || 'item']: item,
|
||||
[indexKeyName || 'index']: index
|
||||
}),
|
||||
[item, data, name, index, itemKeyName, indexKeyName]
|
||||
);
|
||||
|
||||
return render(`item/${index}`, items, {
|
||||
data: ctx
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Each 循环功能渲染器。
|
||||
* 文档:https://aisuda.bce.baidu.com/amis/zh-CN/components/each
|
||||
@ -25,6 +53,20 @@ export interface EachSchema extends BaseSchema {
|
||||
*/
|
||||
source?: string;
|
||||
|
||||
/**
|
||||
* 用来控制通过什么字段读取成员数据,考虑到可能多层嵌套
|
||||
* 如果名字一样会读取不到上层变量,所以这里可以指定一下
|
||||
* @default item
|
||||
*/
|
||||
itemKeyName?: string;
|
||||
|
||||
/**
|
||||
* 用来控制通过什么字段读取序号,考虑到可能多层嵌套
|
||||
* 如果名字一样会读取不到上层变量,所以这里可以指定一下
|
||||
* @default index
|
||||
*/
|
||||
indexKeyName?: string;
|
||||
|
||||
items?: SchemaCollection;
|
||||
|
||||
placeholder?: string;
|
||||
@ -50,6 +92,8 @@ export default class Each extends React.Component<EachProps> {
|
||||
style,
|
||||
render,
|
||||
items,
|
||||
itemKeyName,
|
||||
indexKeyName,
|
||||
placeholder,
|
||||
classnames: cx,
|
||||
translate: __
|
||||
@ -82,17 +126,19 @@ export default class Each extends React.Component<EachProps> {
|
||||
return (
|
||||
<div className={cx('Each', className)} style={buildStyle(style, data)}>
|
||||
{Array.isArray(arr) && arr.length && items ? (
|
||||
arr.map((item: any, index: number) =>
|
||||
render(`item/${index}`, items, {
|
||||
data: createObject(
|
||||
data,
|
||||
isObject(item)
|
||||
? {index, ...item}
|
||||
: {[name]: item, item: item, index}
|
||||
),
|
||||
key: index
|
||||
})
|
||||
)
|
||||
arr.map((item: any, index: number) => (
|
||||
<EachItem
|
||||
{...this.props}
|
||||
items={items}
|
||||
key={index}
|
||||
index={index}
|
||||
data={data}
|
||||
item={item}
|
||||
name={name}
|
||||
itemKeyName={itemKeyName}
|
||||
indexKeyName={indexKeyName}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className={cx('Each-placeholder')}>
|
||||
{render('placeholder', __(placeholder))}
|
||||
|
Loading…
Reference in New Issue
Block a user