Merge pull request #7855 from hsm-lv/chore-eventaction

chore:动作执行支持ignoreError,用于忽略执行错误继续执行动作列表
This commit is contained in:
hsm-lv 2023-08-17 14:27:21 +08:00 committed by GitHub
commit 8c93fe7631
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 5 deletions

View File

@ -3194,7 +3194,7 @@ http 请求动作执行结束后,后面的动作可以通过 `${responseResult
| id | 组件 ID即组件的 id 属性的值 | | id | 组件 ID即组件的 id 属性的值 |
| path | 数据路径,即数据变量的路径 | | path | 数据路径,即数据变量的路径 |
# 事件动作干预 # 干预动作执行
事件动作干预是指执行完当前动作后,干预所监听事件默认处理逻辑和后续其他动作的执行。通过`preventDefault`、`stopPropagation`分别阻止监听事件默认行为和停止下一个动作执行。 事件动作干预是指执行完当前动作后,干预所监听事件默认处理逻辑和后续其他动作的执行。通过`preventDefault`、`stopPropagation`分别阻止监听事件默认行为和停止下一个动作执行。
@ -3305,6 +3305,65 @@ http 请求动作执行结束后,后面的动作可以通过 `${responseResult
} }
``` ```
## 忽略动作报错继续执行
> `3.3.1` 及以上版本
默认情况下尝试执行一个不存在的目标组件动作、JS 脚本执行错误等程序错误都会导致动作执行终止,可以通过`ignoreError: true`来忽略动作报错继续执行后面的动作。
```schema
{
"type": "page",
"title": "第一个按钮发生错误直接阻塞执行,第二个按钮发生错误后仍然执行",
"body": [
{
type: 'button',
label: '无法弹出提示',
level: 'primary',
className: 'mr-2',
onEvent: {
click: {
actions: [
{
actionType: 'reload',
componentId: 'notfound'
},
{
actionType: 'toast',
args: {
msg: 'okk'
}
}
]
}
}
},
{
type: 'button',
label: '可以弹出提示',
level: 'primary',
onEvent: {
click: {
actions: [
{
actionType: 'reload',
componentId: 'notfound',
ignoreError: true
},
{
actionType: 'toast',
args: {
msg: 'okk'
}
}
]
}
}
}
]
}
```
# 自定义组件接入事件动作 # 自定义组件接入事件动作
需求场景主要是想要自定义组件的内部事件暴露出去,能够通过对事件的监听来执行所需动作,并希望自定义组件自身的动作能够被其他组件调用。接入方法是通过`props.dispatchEvent`派发自身的各种事件,使其具备更灵活的交互设计能力;通过实现`doAction`方法实现其他组件对其专属动作的调用,需要注意的是,此处依赖内部的 `Scoped Context`来实现自身的注册,可以参考 [组件间通信](../../docs/extend/custom-react#组件间通信)。 需求场景主要是想要自定义组件的内部事件暴露出去,能够通过对事件的监听来执行所需动作,并希望自定义组件自身的动作能够被其他组件调用。接入方法是通过`props.dispatchEvent`派发自身的各种事件,使其具备更灵活的交互设计能力;通过实现`doAction`方法实现其他组件对其专属动作的调用,需要注意的是,此处依赖内部的 `Scoped Context`来实现自身的注册,可以参考 [组件间通信](../../docs/extend/custom-react#组件间通信)。
@ -3321,3 +3380,4 @@ http 请求动作执行结束后,后面的动作可以通过 `${responseResult
| stopPropagation | `boolean`\|[表达式](../concepts/expression)\|[ConditionBuilder](../../components/form/condition-builder) | false | 停止后续动作执行,`> 1.10.0 及以上版本支持表达式,> 2.9.0 及以上版本支持ConditionBuilder` | | stopPropagation | `boolean`\|[表达式](../concepts/expression)\|[ConditionBuilder](../../components/form/condition-builder) | false | 停止后续动作执行,`> 1.10.0 及以上版本支持表达式,> 2.9.0 及以上版本支持ConditionBuilder` |
| expression | `boolean`\|[表达式](../concepts/expression)\|[ConditionBuilder](../../components/form/condition-builder) | - | 执行条件,不设置表示默认执行,`> 1.10.0 及以上版本支持表达式,> 2.9.0 及以上版本支持ConditionBuilder` | | expression | `boolean`\|[表达式](../concepts/expression)\|[ConditionBuilder](../../components/form/condition-builder) | - | 执行条件,不设置表示默认执行,`> 1.10.0 及以上版本支持表达式,> 2.9.0 及以上版本支持ConditionBuilder` |
| outputVar | `string` | - | 输出数据变量名 | | outputVar | `string` | - | 输出数据变量名 |
| ignoreError | `boolean` | false | 当动作执行出错后,是否忽略错误继续执行。`3.3.1 及以上版本支持` |

View File

@ -24,6 +24,7 @@ export interface ListenerAction {
description?: string; // 事件描述actionType: broadcast description?: string; // 事件描述actionType: broadcast
componentId?: string; // 组件ID用于直接执行指定组件的动作指定多个组件时使用英文逗号分隔 componentId?: string; // 组件ID用于直接执行指定组件的动作指定多个组件时使用英文逗号分隔
componentName?: string; // 组件Name用于直接执行指定组件的动作指定多个组件时使用英文逗号分隔 componentName?: string; // 组件Name用于直接执行指定组件的动作指定多个组件时使用英文逗号分隔
ignoreError?: boolean; // 当执行动作发生错误时,是否忽略并继续执行
args?: Record<string, any>; // 动作配置可以配置数据映射。注意存在schema配置的动作都不能放在args里面避免数据域不同导致的解析错误问题 args?: Record<string, any>; // 动作配置可以配置数据映射。注意存在schema配置的动作都不能放在args里面避免数据域不同导致的解析错误问题
data?: Record<string, any> | null; // 动作数据参数,可以配置数据映射 data?: Record<string, any> | null; // 动作数据参数,可以配置数据映射
dataMergeMode?: 'merge' | 'override'; // 参数模式,合并或者覆盖 dataMergeMode?: 'merge' | 'override'; // 参数模式,合并或者覆盖
@ -181,8 +182,19 @@ export const runActions = async (
actionInstrance = getActionByType('component'); actionInstrance = getActionByType('component');
} }
// 这些节点的子节点运行逻辑由节点内部实现 try {
await runAction(actionInstrance, actionConfig, renderer, event); // 这些节点的子节点运行逻辑由节点内部实现
await runAction(actionInstrance, actionConfig, renderer, event);
} catch (e) {
const ignore = actionConfig.ignoreError ?? false;
if (!ignore) {
throw Error(
`${actionConfig.actionType} 动作执行失败,原因:${
e.message || '未知'
}`
);
}
}
if (event.stoped) { if (event.stoped) {
break; break;

View File

@ -43,7 +43,9 @@ export class CmptAction implements RendererAction {
: null; : null;
if (key && !component) { if (key && !component) {
throw Error('目标组件没有找到请检查componentId或componentName是否正确'); throw Error(
'尝试执行一个不存在的目标组件动作请检查目标组件非隐藏状态且正确指定了componentId或componentName'
);
} }
if (action.actionType === 'setValue') { if (action.actionType === 'setValue') {

View File

@ -97,3 +97,70 @@ test('EventAction:prevent', async () => {
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
expect(fetcher).not.toHaveBeenCalled(); expect(fetcher).not.toHaveBeenCalled();
}); });
test('EventAction:ignoreError', async () => {
const notify = jest.fn();
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
status: 0,
msg: 'ok',
data: {
age: 18
}
}
})
);
const {getByText, container}: any = render(
amisRender(
{
type: 'page',
body: [
{
type: 'button',
label: '按钮',
level: 'primary',
onEvent: {
click: {
actions: [
{
actionType: 'reload',
componentId: 'notfound',
ignoreError: true
},
{
actionType: 'ajax',
api: '/api/test3'
},
{
actionType: 'custom',
script:
"const myMsg = '我是自定义JS';\nsdfsdfsdf();\ndoAction({\n actionType: 'toast',\n args: {\n msg: myMsg\n }\n});\n",
ignoreError: true
},
{
actionType: 'ajax',
api: '/api/test4'
}
]
}
}
}
]
},
{},
makeEnv({
getModalContainer: () => container,
notify,
fetcher
})
)
);
fireEvent.click(getByText('按钮'));
await waitFor(() => {
expect(fetcher).toHaveBeenCalledTimes(2);
expect(fetcher.mock.calls[0][0].url).toEqual('/api/test3');
expect(fetcher.mock.calls[1][0].url).toEqual('/api/test4');
});
});

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import type {IColumn, ITableStore} from 'amis-core/lib/store/table'; import type {IColumn, ITableStore} from 'amis-core';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
export function ColGroup({ export function ColGroup({