From 09169c26b2d61101a766c2d9b5482be98a3db1ae Mon Sep 17 00:00:00 2001 From: lvxiaojiao Date: Fri, 6 Jan 2023 16:37:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=8A=A8=E4=BD=9C=E4=B8=8A=E4=B8=8B?= =?UTF-8?q?=E6=96=87=E8=A1=A5=E5=85=85$$=E7=94=A8=E4=BA=8E=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=BB=84=E4=BB=B6=E5=BD=93=E5=89=8D=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=9F=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/zh-CN/concepts/event-action.md | 171 +++++++++++++++--- packages/amis-core/src/actions/Action.ts | 18 +- .../amis-core/src/utils/renderer-event.ts | 6 +- .../amis/src/renderers/DropDownButton.tsx | 17 +- 4 files changed, 182 insertions(+), 30 deletions(-) diff --git a/docs/zh-CN/concepts/event-action.md b/docs/zh-CN/concepts/event-action.md index f98f85e10..671bf5df9 100644 --- a/docs/zh-CN/concepts/event-action.md +++ b/docs/zh-CN/concepts/event-action.md @@ -12,26 +12,67 @@ order: 9 上一节我们介绍了如何实现联动,是比较基础和局限的,而事件动作是更简单、更灵活、更高级的用法,可以解决复杂的 UI 交互场景,支持渲染器事件监听和响应设计,无需关心组件层级关系。例如: -- 传递数据 - - 点击「按钮」,发送一个远程请求,返回的数据回填给一个表单 - - 弹窗提交后,将弹窗内表单数据回填给另一个表单 -- 联动刷新 - - 下拉选择不同项,联动刷新表单数据 -- 状态控制 - - 勾选不同按钮,控制相应组件的显示/隐藏 - - 勾选不同按钮,控制相应组件的启用/禁用 -- 多个组件监听同一个事件做出不同响应 - - 下拉选择不同项,组件 A 监听后发送一个远程请求,组件 B 监听后进行刷新 -- 逻辑编排 - - 针对监听到的事件,循环执行一些动作作为响应,还可以控制循环跳出或跳过 - - 针对监听到的事件,根据条件选择性的执行动作响应 - - 针对监听到的事件,并行执行多个动作作为响应 - - 执行完当前动作后,可以选择是否继续执行后续动作,是否关闭事件默认行为的执行 +- http 请求:发送 http 请求 +- 弹窗提示:执行弹窗、抽屉打开和 toast 提示 +- 页面跳转:页面链接跳转 +- 浏览器相关:回退、前进、后退、刷新 +- 刷新组件:联动刷新表单数据,即数据重新加载 +- 组件状态:控制指定组件的显示/隐藏、启用/禁用、展示态/编辑态 +- 组件特性动作:执行指定组件的专有动作,例如执行表单的提交动作 +- 组件数据:更新指定组件的数据域 +- 广播:多个组件监听同一个事件做出不同响应 +- JS 脚本:通过编写 JS 代码片段实现所需逻辑,同时支持 JS 代码内执行动作 +- 逻辑编排:条件、循环、排他、并行 ## 基本使用 +### onEvent + 通过`onEvent`属性实现渲染器事件与响应动作的绑定。`onEvent`内配置事件和动作映射关系,`actions`是事件对应的响应动作的集合。 +```json +{ + "type": "button", + "label": "尝试点击、鼠标移入/移出", + "level": "primary", + "onEvent": { + "click": { // 监听点击事件 + "actions": [ // 执行的动作列表 + { + "actionType": "toast", // 执行toast提示动作 + "args": { // 动作参数 + "msgType": "info", + "msg": "派发点击事件" + } + } + ] + }, + "mouseenter": {{ // 监听鼠标移入事件 + "actions": [ + { + "actionType": "toast", + "args": { + "msgType": "info", + "msg": "派发鼠标移入事件" + } + } + ] + }, + "mouseleave": {{ // 监听鼠标移出事件 + "actions": [ + { + "actionType": "toast", + "args": { + "msgType": "info", + "msg": "派发鼠标移出事件" + } + } + ] + } + } +} +``` + ```schema { type: 'page', @@ -80,6 +121,94 @@ order: 9 } ``` +### 上下文 + +执行动作时,可以通过`${event.data}`获取事件对象的数据、通过`${$$}`获取组件当前数据域,例如: + +```schema +{ + "type": "page", + data: { + p1: 'p1' + }, + "body": { + "type": "form", + debug: true, + "api": { + url: "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/form/saveForm", + method: "post", + data: { + "&": '$$', + job: 'coder' + } + }, + data: { + job: 'hr' + }, + "body": [ + { + type: 'alert', + "body": "监听姓名值变化,执行动作时读取输入的内容;监听年龄值变化,执行动作时读取input-text组件当前数据域(表单数据)", + "level": "info", + "className": "mb-1" + }, + { + "type": "input-text", + "name": "name", + "label": "姓名:", + onEvent: { + change: { + actions: [ + { + actionType: 'toast', + args: { + msg: '${name}' + } + } + ] + } + } + }, + { + "type": "input-text", + "name": "age", + "label": "年龄:", + onEvent: { + change: { + actions: [ + { + actionType: 'toast', + args: { + msg: '${$$|json}' + } + } + ] + } + } + } + ], + onEvent: { + submitSucc: { + actions: [ + { + actionType: 'toast', + args: { + msg: '${event.data|json}' + } + }, + { + actionType: 'toast', + args: { + msg: '${$$|json}' + } + } + ] + } + } + } +} +``` + ### 运行日志 可以在浏览器控制台查看运行日志,类似如下 @@ -92,9 +221,9 @@ run action ajax 代表运行了 ajax 动作,第二行是传递的参数和数据,第三行是执行完动作之后的 `event` 值,可以用做后续动作的参数。 -## 事件与动作 +## 事件与动作分类 -事件包含`渲染器事件`和`广播事件`,监听这些事件时,可以通过`event.data`来获取事件对象的数据。 +事件包含`渲染器事件`和`广播事件`。 - 渲染器事件,由具体的渲染器组件提供,每个渲染器组件暴露的事件可以查看具体的[组件文档的事件表](../../components/page#事件表); - 广播事件,即自定义事件,可以自定义派发的事件名称`eventName`,其他渲染器可以监听该自定义事件并配置响应动作。 @@ -1991,7 +2120,7 @@ registerAction('my-action', new MyAction()); 通过配置`actionType: 'for'`实现循环逻辑。 -**单层循环** +### 单层循环 ```schema { @@ -2043,7 +2172,7 @@ registerAction('my-action', new MyAction()); } ``` -**嵌套循环** +### 嵌套循环 ```schema { @@ -2152,7 +2281,7 @@ registerAction('my-action', new MyAction()); | -------- | ---------------------------------------------------- | ------ | ------------------------------------- | | children | Array<[动作](../../docs/concepts/event-action#动作)> | - | 子动作,可以通过`break动作`来跳出循环 | -## Break 动作 +### Break 动作 通过配置`actionType: 'for'`和`actionType: 'break'`实现循环跳出。 @@ -2219,7 +2348,7 @@ registerAction('my-action', new MyAction()); } ``` -## Continue 动作 +### Continue 动作 通过配置`actionType: 'for'`和`actionType: 'continue'`实现循环跳过。 diff --git a/packages/amis-core/src/actions/Action.ts b/packages/amis-core/src/actions/Action.ts index dab491fa7..814068c1d 100644 --- a/packages/amis-core/src/actions/Action.ts +++ b/packages/amis-core/src/actions/Action.ts @@ -181,19 +181,31 @@ export const runAction = async ( renderer: ListenerContext, event: any ) => { + // 追加数据 + let additional: any = { + event + }; + + // $$默认为renderer.props.data,兼容表单项值变化时的data读取 + if (!event.data.$$) { + additional = { + event, + $$: renderer.props.data // 部分组件交互后会有更新,如果想要获取那部分数据,可以通过事件数据获取 + }; + } + // 用户可能,需要用到事件数据和当前域的数据,因此merge事件数据和当前渲染器数据 // 需要保持渲染器数据链完整 // 注意:并行ajax请求结果必须通过event取值 const mergeData = createObject( createObject( renderer.props.data.__super - ? createObject(renderer.props.data.__super, {event}) - : {event}, + ? createObject(renderer.props.data.__super, additional) + : additional, renderer.props.data ), event.data ); - // 兼容一下1.9.0之前的版本 const expression = actionConfig.expression ?? actionConfig.execOn; diff --git a/packages/amis-core/src/utils/renderer-event.ts b/packages/amis-core/src/utils/renderer-event.ts index 67886b742..3fc600433 100644 --- a/packages/amis-core/src/utils/renderer-event.ts +++ b/packages/amis-core/src/utils/renderer-event.ts @@ -258,7 +258,11 @@ export const resolveEventData = (props: any, data: any, valueKey?: string) => { props.name && valueKey ? { ...data, - [props.name]: data[valueKey] + [props.name]: data[valueKey], + $$: { + ...props.data, + [props.name]: data[valueKey] + } } : data ); diff --git a/packages/amis/src/renderers/DropDownButton.tsx b/packages/amis/src/renderers/DropDownButton.tsx index 79496e4b1..2bd9ba33c 100644 --- a/packages/amis/src/renderers/DropDownButton.tsx +++ b/packages/amis/src/renderers/DropDownButton.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {Renderer, RendererProps} from 'amis-core'; +import {createObject, Renderer, RendererProps} from 'amis-core'; import {Overlay} from 'amis-core'; import {PopOver} from 'amis-core'; import {TooltipWrapper} from 'amis-ui'; @@ -180,9 +180,13 @@ export default class DropDownButton extends React.Component< } async open() { - await this.props.dispatchEvent('mouseenter', { - items: this.props.buttons // 为了保持名字统一 - }); + const {dispatchEvent, data, buttons} = this.props; + await dispatchEvent( + 'mouseenter', + createObject(data, { + items: buttons // 为了保持名字统一 + }) + ); this.setState({ isOpened: true }); @@ -190,7 +194,10 @@ export default class DropDownButton extends React.Component< close() { this.timer = setTimeout(() => { - this.props.dispatchEvent('mouseleave', {items: this.props.buttons}); + this.props.dispatchEvent( + 'mouseleave', + createObject(this.props.data, {items: this.props.buttons}) + ); this.setState({ isOpened: false });