mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
parent
2af0c423e4
commit
bc788a9045
@ -495,7 +495,7 @@ Form 支持轮询初始化接口,步骤如下:
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"initApi": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/saveForm",
|
||||
"api": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/saveForm",
|
||||
"title": "用户信息",
|
||||
"controls": [
|
||||
{
|
||||
@ -607,6 +607,39 @@ Form 支持轮询初始化接口,步骤如下:
|
||||
|
||||
如果决定结束轮询的标识字段名不是 `finished`,请设置`finishedField`属性,比如:`"finishedField": "is_success"`
|
||||
|
||||
## 表单校验
|
||||
|
||||
一般可以通过在[表单项格式校验](./formitem#%E6%A0%BC%E5%BC%8F%E6%A0%A1%E9%AA%8C)中,配置校验规则完成校验,但是有时候,我们需要组合多个表单项实现一些校验,这时可以通过配置 `rules` 来实现组合校验。
|
||||
|
||||
例如下例,我们想校验 `a` 和 `b` 表单项不可以同时有值,否则就报错,则可以进行如下配置:
|
||||
|
||||
```schema:scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"api": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/saveForm",
|
||||
"rules": [
|
||||
{
|
||||
"rule": "!(data.a && data.b)",
|
||||
"message": "a 和 b 不能同时有值"
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "a",
|
||||
"label": "A"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "b",
|
||||
"label": "B"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> `rule` 编写使用 [表达式](../../concepts/expression)
|
||||
|
||||
## 重置表单
|
||||
|
||||
配置`"type": "reset"`或者`"actionType": "reset"`的按钮,可以实现点击重置表单项值。
|
||||
@ -827,47 +860,48 @@ Form 支持轮询初始化接口,步骤如下:
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| --------------------------- | ---------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| type | `string` | | `"form"` 指定为 Form 渲染器 |
|
||||
| name | `string` | | 设置一个名字后,方便其他组件与其通信 |
|
||||
| mode | `string` | `normal` | 表单展示方式,可以是:`normal`、`horizontal` 或者 `inline` |
|
||||
| horizontal | `Object` | `{"left":"col-sm-2", "right":"col-sm-10", "offset":"col-sm-offset-2"}` | 当 mode 为 `horizontal` 时有用,用来控制 label |
|
||||
| title | `string` | `"表单"` | Form 的标题 |
|
||||
| submitText | `String` | `"提交"` | 默认的提交按钮名称,如果设置成空,则可以把默认按钮去掉。 |
|
||||
| className | `string` | | 外层 Dom 的类名 |
|
||||
| controls | Array<[表单项](./formitem)> | | Form 表单项集合 |
|
||||
| actions | Array<[行为按钮](../action)> | | Form 提交按钮,成员为 Action |
|
||||
| messages | `Object` | | 消息提示覆写,默认消息读取的是 API 返回的消息,但是在此可以覆写它。 |
|
||||
| messages.fetchSuccess | `string` | | 获取成功时提示 |
|
||||
| messages.fetchFailed | `string` | | 获取失败时提示 |
|
||||
| messages.saveSuccess | `string` | | 保存成功时提示 |
|
||||
| messages.saveFailed | `string` | | 保存失败时提示 |
|
||||
| wrapWithPanel | `boolean` | `true` | 是否让 Form 用 panel 包起来,设置为 false 后,actions 将无效。 |
|
||||
| panelClassName | `string` | | 外层 panel 的类名 |
|
||||
| api | [API](../../types/api) | | Form 用来保存数据的 api。 |
|
||||
| initApi | [API](../../types/api) | | Form 用来获取初始数据的 api。 |
|
||||
| interval | `number` | `3000` | 刷新时间(最低 3000) |
|
||||
| silentPolling | `boolean` | `false` | 配置刷新时是否显示加载动画 |
|
||||
| stopAutoRefreshWhen | `string` | `""` | 通过[表达式](./Types.md#表达式) 来配置停止刷新的条件 |
|
||||
| initAsyncApi | [API](../../types/api) | | Form 用来获取初始数据的 api,与 initApi 不同的是,会一直轮询请求该接口,直到返回 finished 属性为 true 才 结束。 |
|
||||
| initFetch | `boolean` | `true` | 设置了 initApi 或者 initAsyncApi 后,默认会开始就发请求,设置为 false 后就不会起始就请求接口 |
|
||||
| initFetchOn | `string` | | 用表达式来配置 |
|
||||
| initFinishedField | `string` | `finished` | 设置了 initAsyncApi 后,默认会从返回数据的 data.finished 来判断是否完成,也可以设置成其他的 xxx,就会从 data.xxx 中获取 |
|
||||
| initCheckInterval | `number` | `3000` | 设置了 initAsyncApi 以后,默认拉取的时间间隔 |
|
||||
| asyncApi | [API](../../types/api) | | 设置此属性后,表单提交发送保存接口后,还会继续轮询请求该接口,直到返回 `finished` 属性为 `true` 才 结束。 |
|
||||
| checkInterval | `number` | 3000 | 轮询请求的时间间隔,默认为 3 秒。设置 `asyncApi` 才有效 |
|
||||
| finishedField | `string` | `"finished"` | 如果决定结束的字段名不是 `finished` 请设置此属性,比如 `is_success` |
|
||||
| submitOnChange | `boolean` | `false` | 表单修改即提交 |
|
||||
| submitOnInit | `boolean` | `false` | 初始就提交一次 |
|
||||
| resetAfterSubmit | `boolean` | `false` | 提交后是否重置表单 |
|
||||
| primaryField | `string` | `"id"` | 设置主键 id, 当设置后,检测表单是否完成时(asyncApi),只会携带此数据。 |
|
||||
| target | `string` | | 默认表单提交自己会通过发送 api 保存数据,但是也可以设定另外一个 form 的 name 值,或者另外一个 `CRUD` 模型的 name 值。 如果 target 目标是一个 `Form` ,则目标 `Form` 会重新触发 `initApi`,api 可以拿到当前 form 数据。如果目标是一个 `CRUD` 模型,则目标模型会重新触发搜索,参数为当前 Form 数据。当目标是 `window` 时,会把当前表单的数据附带到页面地址上。 |
|
||||
| redirect | `string` | | 设置此属性后,Form 保存成功后,自动跳转到指定页面。支持相对地址,和绝对地址(相对于组内的)。 |
|
||||
| reload | `string` | | 操作完后刷新目标对象。请填写目标组件设置的 name 值,如果填写为 `window` 则让当前页面整体刷新。 |
|
||||
| autoFocus | `boolean` | `false` | 是否自动聚焦。 |
|
||||
| canAccessSuperData | `boolean` | `true` | 指定是否可以自动获取上层的数据并映射到表单项上 |
|
||||
| persistData | `boolean` | `true` | 指定表单是否开启本地缓存 |
|
||||
| clearPersistDataAfterSubmit | `boolean` | `true` | 指定表单提交成功后是否清除本地缓存 |
|
||||
| trimValues | `boolean` | `false` | trim 当前表单项的每一个值 |
|
||||
| promptPageLeave | `boolean` | `false` | form 还没保存,即将离开页面前是否弹框确认。 |
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| --------------------------- | ----------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| type | `string` | | `"form"` 指定为 Form 渲染器 |
|
||||
| name | `string` | | 设置一个名字后,方便其他组件与其通信 |
|
||||
| mode | `string` | `normal` | 表单展示方式,可以是:`normal`、`horizontal` 或者 `inline` |
|
||||
| horizontal | `Object` | `{"left":"col-sm-2", "right":"col-sm-10", "offset":"col-sm-offset-2"}` | 当 mode 为 `horizontal` 时有用,用来控制 label |
|
||||
| title | `string` | `"表单"` | Form 的标题 |
|
||||
| submitText | `String` | `"提交"` | 默认的提交按钮名称,如果设置成空,则可以把默认按钮去掉。 |
|
||||
| className | `string` | | 外层 Dom 的类名 |
|
||||
| controls | Array<[表单项](./formitem)> | | Form 表单项集合 |
|
||||
| actions | Array<[行为按钮](../action)> | | Form 提交按钮,成员为 Action |
|
||||
| messages | `Object` | | 消息提示覆写,默认消息读取的是 API 返回的消息,但是在此可以覆写它。 |
|
||||
| messages.fetchSuccess | `string` | | 获取成功时提示 |
|
||||
| messages.fetchFailed | `string` | | 获取失败时提示 |
|
||||
| messages.saveSuccess | `string` | | 保存成功时提示 |
|
||||
| messages.saveFailed | `string` | | 保存失败时提示 |
|
||||
| wrapWithPanel | `boolean` | `true` | 是否让 Form 用 panel 包起来,设置为 false 后,actions 将无效。 |
|
||||
| panelClassName | `string` | | 外层 panel 的类名 |
|
||||
| api | [API](../../types/api) | | Form 用来保存数据的 api。 |
|
||||
| initApi | [API](../../types/api) | | Form 用来获取初始数据的 api。 |
|
||||
| rules | Array<{rule:string;message:string}> | | 表单组合校验规则 |
|
||||
| interval | `number` | `3000` | 刷新时间(最低 3000) |
|
||||
| silentPolling | `boolean` | `false` | 配置刷新时是否显示加载动画 |
|
||||
| stopAutoRefreshWhen | `string` | `""` | 通过[表达式](./Types.md#表达式) 来配置停止刷新的条件 |
|
||||
| initAsyncApi | [API](../../types/api) | | Form 用来获取初始数据的 api,与 initApi 不同的是,会一直轮询请求该接口,直到返回 finished 属性为 true 才 结束。 |
|
||||
| initFetch | `boolean` | `true` | 设置了 initApi 或者 initAsyncApi 后,默认会开始就发请求,设置为 false 后就不会起始就请求接口 |
|
||||
| initFetchOn | `string` | | 用表达式来配置 |
|
||||
| initFinishedField | `string` | `finished` | 设置了 initAsyncApi 后,默认会从返回数据的 data.finished 来判断是否完成,也可以设置成其他的 xxx,就会从 data.xxx 中获取 |
|
||||
| initCheckInterval | `number` | `3000` | 设置了 initAsyncApi 以后,默认拉取的时间间隔 |
|
||||
| asyncApi | [API](../../types/api) | | 设置此属性后,表单提交发送保存接口后,还会继续轮询请求该接口,直到返回 `finished` 属性为 `true` 才 结束。 |
|
||||
| checkInterval | `number` | 3000 | 轮询请求的时间间隔,默认为 3 秒。设置 `asyncApi` 才有效 |
|
||||
| finishedField | `string` | `"finished"` | 如果决定结束的字段名不是 `finished` 请设置此属性,比如 `is_success` |
|
||||
| submitOnChange | `boolean` | `false` | 表单修改即提交 |
|
||||
| submitOnInit | `boolean` | `false` | 初始就提交一次 |
|
||||
| resetAfterSubmit | `boolean` | `false` | 提交后是否重置表单 |
|
||||
| primaryField | `string` | `"id"` | 设置主键 id, 当设置后,检测表单是否完成时(asyncApi),只会携带此数据。 |
|
||||
| target | `string` | | 默认表单提交自己会通过发送 api 保存数据,但是也可以设定另外一个 form 的 name 值,或者另外一个 `CRUD` 模型的 name 值。 如果 target 目标是一个 `Form` ,则目标 `Form` 会重新触发 `initApi`,api 可以拿到当前 form 数据。如果目标是一个 `CRUD` 模型,则目标模型会重新触发搜索,参数为当前 Form 数据。当目标是 `window` 时,会把当前表单的数据附带到页面地址上。 |
|
||||
| redirect | `string` | | 设置此属性后,Form 保存成功后,自动跳转到指定页面。支持相对地址,和绝对地址(相对于组内的)。 |
|
||||
| reload | `string` | | 操作完后刷新目标对象。请填写目标组件设置的 name 值,如果填写为 `window` 则让当前页面整体刷新。 |
|
||||
| autoFocus | `boolean` | `false` | 是否自动聚焦。 |
|
||||
| canAccessSuperData | `boolean` | `true` | 指定是否可以自动获取上层的数据并映射到表单项上 |
|
||||
| persistData | `boolean` | `true` | 指定表单是否开启本地缓存 |
|
||||
| clearPersistDataAfterSubmit | `boolean` | `true` | 指定表单提交成功后是否清除本地缓存 |
|
||||
| trimValues | `boolean` | `false` | trim 当前表单项的每一个值 |
|
||||
| promptPageLeave | `boolean` | `false` | form 还没保存,即将离开页面前是否弹框确认。 |
|
||||
|
@ -274,6 +274,14 @@ export interface FormSchema extends BaseSchema {
|
||||
* 具体的提示信息,选填。
|
||||
*/
|
||||
promptPageLeaveMessage?: string;
|
||||
|
||||
/**
|
||||
* 组合校验规则,选填
|
||||
*/
|
||||
rules: Array<{
|
||||
rule: string;
|
||||
message: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export type FormGroup = FormSchema & {
|
||||
@ -307,6 +315,10 @@ export interface FormProps extends RendererProps, Omit<FormSchema, 'mode'> {
|
||||
saveFailed?: string;
|
||||
validateFailed?: string;
|
||||
};
|
||||
rules: Array<{
|
||||
rule: string;
|
||||
message: string;
|
||||
}>;
|
||||
lazyChange?: boolean; // 表单项的
|
||||
formLazyChange?: boolean; // 表单的
|
||||
}
|
||||
@ -373,6 +385,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
} = {};
|
||||
asyncCancel: () => void;
|
||||
disposeOnValidate: () => void;
|
||||
disposeRulesValidate: () => void;
|
||||
shouldLoadInitApi: boolean = false;
|
||||
timer: NodeJS.Timeout;
|
||||
mounted: boolean;
|
||||
@ -441,7 +454,8 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
messages: {fetchSuccess, fetchFailed},
|
||||
onValidate,
|
||||
promptPageLeave,
|
||||
env
|
||||
env,
|
||||
rules
|
||||
} = this.props;
|
||||
|
||||
this.mounted = true;
|
||||
@ -461,23 +475,38 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
return;
|
||||
}
|
||||
|
||||
// 在setError之前,提前把残留的error信息清除掉,否则每次onValidate后都会一直把报错 append 上去
|
||||
items.forEach(item => item.clearError());
|
||||
|
||||
if (msg) {
|
||||
msg = Array.isArray(msg) ? msg : [msg];
|
||||
items.forEach(item => item.addError(msg));
|
||||
} else {
|
||||
items.forEach(item => item.clearError());
|
||||
}
|
||||
|
||||
delete result[key];
|
||||
});
|
||||
|
||||
isEmpty(result)
|
||||
? store.clearRestErrors()
|
||||
: store.setRestErrors(result);
|
||||
? store.clearRestError()
|
||||
: store.setRestError(Object.keys(result).map(key => result[key]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(rules) && rules.length) {
|
||||
this.disposeRulesValidate = this.addHook(() => {
|
||||
if (!store.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
rules.forEach(
|
||||
item =>
|
||||
!evalExpression(item.rule, store.data) &&
|
||||
store.addRestError(item.message)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (isEffectiveApi(initApi, store.data, initFetch, initFetchOn)) {
|
||||
store
|
||||
.fetchInitData(initApi as any, store.data, {
|
||||
@ -543,6 +572,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
this.lazyHandleChange.cancel();
|
||||
this.asyncCancel && this.asyncCancel();
|
||||
this.disposeOnValidate && this.disposeOnValidate();
|
||||
this.disposeRulesValidate && this.disposeRulesValidate();
|
||||
this.componentCache.dispose();
|
||||
window.removeEventListener('beforeunload', this.beforePageUnload);
|
||||
this.unBlockRouting?.();
|
||||
@ -767,7 +797,7 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
onChange &&
|
||||
onChange(store.data, difference(store.data, store.pristine), this.props);
|
||||
|
||||
store.clearRestErrors();
|
||||
store.clearRestError();
|
||||
|
||||
(submit || submitOnChange) &&
|
||||
this.handleAction(
|
||||
@ -1341,6 +1371,8 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
render
|
||||
} = this.props;
|
||||
|
||||
const {restError} = store;
|
||||
|
||||
const WrapperComponent =
|
||||
this.props.wrapperComponent ||
|
||||
(/(?:\/|^)form\//.test($path as string) ? 'div' : 'form');
|
||||
@ -1365,11 +1397,11 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
controls
|
||||
})}
|
||||
|
||||
{/* 显示接口返回的 errors 中没有映射上的 */}
|
||||
{store.restErrors ? (
|
||||
<ul className={cx('Form-restErrors', 'Form-feedback')}>
|
||||
{Object.keys(store.restErrors).map(key => (
|
||||
<li key={key}>{store.restErrors[key]}</li>
|
||||
{/* 显示没有映射上的 errors */}
|
||||
{restError && restError.length ? (
|
||||
<ul className={cx('Form-restError', 'Form-feedback')}>
|
||||
{restError.map((item, idx) => (
|
||||
<li key={idx}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
|
@ -41,7 +41,7 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
itemsRef: types.optional(types.array(types.string), []),
|
||||
canAccessSuperData: true,
|
||||
persistData: false,
|
||||
restErrors: types.frozen() // 没有映射到表达项上的 errors
|
||||
restError: types.optional(types.array(types.string), []) // 没有映射到表达项上的 errors
|
||||
})
|
||||
.views(self => {
|
||||
function getItems() {
|
||||
@ -94,7 +94,10 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
},
|
||||
|
||||
get valid() {
|
||||
return getItems().every(item => item.valid);
|
||||
return (
|
||||
getItems().every(item => item.valid) &&
|
||||
(!self.restError || !self.restError.length)
|
||||
);
|
||||
},
|
||||
|
||||
get isPristine() {
|
||||
@ -226,12 +229,19 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
}
|
||||
);
|
||||
|
||||
function setRestErrors(errors: any) {
|
||||
self.restErrors = errors;
|
||||
function setRestError(errors: string[]) {
|
||||
self.restError.replace(errors);
|
||||
}
|
||||
|
||||
function clearRestErrors() {
|
||||
setRestErrors(null);
|
||||
function addRestError(msg: string | Array<string>) {
|
||||
const msgs: Array<string> = Array.isArray(msg) ? msg : [msg];
|
||||
msgs.forEach(msg => {
|
||||
self.restError.push(msg);
|
||||
});
|
||||
}
|
||||
|
||||
function clearRestError() {
|
||||
setRestError([]);
|
||||
}
|
||||
|
||||
const saveRemote: (
|
||||
@ -243,7 +253,7 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
data: object,
|
||||
options: fetchOptions = {}
|
||||
) {
|
||||
clearRestErrors();
|
||||
clearRestError();
|
||||
|
||||
try {
|
||||
options = {
|
||||
@ -308,7 +318,8 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
});
|
||||
|
||||
// 没有映射上的error信息加在msg后显示出来
|
||||
!isEmpty(errors) && setRestErrors(errors);
|
||||
!isEmpty(errors) &&
|
||||
setRestError(Object.keys(errors).map(key => errors[key]));
|
||||
|
||||
self.updateMessage(
|
||||
json.msg ??
|
||||
@ -413,7 +424,7 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
try {
|
||||
let valid = yield validate(hooks);
|
||||
|
||||
if (!valid || self.restErrors) {
|
||||
if (!valid) {
|
||||
const msg = failedMessage ?? self.__('Form.validateFailed');
|
||||
msg && getEnv(self).notify('error', msg);
|
||||
throw new Error(self.__('Form.validateFailed'));
|
||||
@ -581,8 +592,9 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
onChildStoreDispose,
|
||||
updateSavedData,
|
||||
getItemsByPath,
|
||||
setRestErrors,
|
||||
clearRestErrors,
|
||||
setRestError,
|
||||
addRestError,
|
||||
clearRestError,
|
||||
beforeDestroy() {
|
||||
syncOptions.cancel();
|
||||
setPersistData.cancel();
|
||||
|
Loading…
Reference in New Issue
Block a user