From 8cc8283ded82e2806d55014d7a1bd38aeeaeb5b9 Mon Sep 17 00:00:00 2001 From: hsm-lv <80095014+hsm-lv@users.noreply.github.com> Date: Wed, 26 Jan 2022 09:18:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E8=A1=A5=E5=85=85=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E9=80=9A=E7=94=A8=E5=8A=A8=E4=BD=9C&=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E5=8A=A8=E4=BD=9C=E6=9C=BA=E5=88=B6=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B=20(#3495)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:补充一些通用动作&完善事件动作机制示例 * feat:补充一些通用动作&完善事件动作机制示例 --- .../Event.jsx => EventAction/Broadcast.jsx} | 114 +------ examples/components/EventAction/Cmpt.jsx | 91 ++++++ examples/components/EventAction/Common.jsx | 293 ++++++++++++++++++ examples/components/EventAction/Custom.jsx | 23 ++ examples/components/EventAction/DataFlow.jsx | 111 +++++++ examples/components/EventAction/Logic.jsx | 191 ++++++++++++ examples/components/EventAction/Stop.jsx | 140 +++++++++ examples/components/Example.jsx | 51 ++- src/SchemaRenderer.tsx | 8 +- src/actions/Action.ts | 5 +- src/actions/AjaxAction.ts | 96 ++++-- src/actions/BroadcastAction.ts | 1 + src/actions/CmptAction.ts | 11 +- src/actions/CopyAction.ts | 1 - src/actions/CustomAction.ts | 6 +- src/actions/DialogAction.ts | 69 +++++ src/actions/DrawerAction.ts | 33 +- .../{OpenPageAction.ts => LinkAction.ts} | 6 +- src/actions/ToastAction.ts | 22 ++ src/actions/index.ts | 3 +- src/factory.tsx | 2 +- 21 files changed, 1096 insertions(+), 181 deletions(-) rename examples/components/{Linkage/Event.jsx => EventAction/Broadcast.jsx} (62%) create mode 100644 examples/components/EventAction/Cmpt.jsx create mode 100644 examples/components/EventAction/Common.jsx create mode 100644 examples/components/EventAction/Custom.jsx create mode 100644 examples/components/EventAction/DataFlow.jsx create mode 100644 examples/components/EventAction/Logic.jsx create mode 100644 examples/components/EventAction/Stop.jsx rename src/actions/{OpenPageAction.ts => LinkAction.ts} (84%) create mode 100644 src/actions/ToastAction.ts diff --git a/examples/components/Linkage/Event.jsx b/examples/components/EventAction/Broadcast.jsx similarity index 62% rename from examples/components/Linkage/Event.jsx rename to examples/components/EventAction/Broadcast.jsx index 81b75de54..e60e75373 100644 --- a/examples/components/Linkage/Event.jsx +++ b/examples/components/EventAction/Broadcast.jsx @@ -1,6 +1,6 @@ export default { type: 'page', - title: '广播事件', + title: '广播(自定义事件)', regions: ['body', 'toolbar', 'header'], body: [ { @@ -12,21 +12,9 @@ export default { title: '系统提示', body: '对你点击了' }, - // target: 'form?name=lvxj', onEvent: { click: { actions: [ - { - actionType: 'reload', - args: { - name: 'lvxj', - age: 18 - }, - preventDefault: true, - stopPropagation: false, - componentId: 'form_001' - // componentId: 'form_001_form_01_text_01' - }, { actionType: 'broadcast', eventName: 'broadcast_1', @@ -60,7 +48,6 @@ export default { title: '系统提示', body: '对你点击了' }, - // target: 'form?name=lvxj', onEvent: { click: { actions: [ @@ -231,105 +218,6 @@ export default { actionType: 'custom', script: "doAction({actionType: 'ajax',api: 'https://api/form/form3-custom-ajax-1'});\n //event.stopPropagation();" - }, - { - actionType: 'parallel', - args: { - level: 3 - }, - children: [ - { - actionType: 'ajax', - api: 'https://api/form/form3-parallel-ajax-1', - preventDefault: false - // stopPropagation: true - }, - { - actionType: 'ajax', - api: 'https://api/form/form3-parallel-ajax-2' - } - ] - }, - { - actionType: 'switch', - preventDefault: false, - stopPropagation: false, - children: [ - { - actionType: 'ajax', - api: 'https://api/form/form3-branch-ajax-1', - expression: 'this.branchCont > 19', - preventDefault: false, - stopPropagation: true // 这里无效,因为条件不成立 - }, - { - actionType: 'ajax', - api: 'https://api/form/form3-branch-ajax-2', - expression: 'this.branchCont > 17', - preventDefault: false, - stopPropagation: false - }, - { - actionType: 'ajax', - api: 'https://api/form/form3-branch-ajax-3', - expression: 'this.branchCont > 16' - } - ] - }, - { - actionType: 'loop', - loopName: 'loopData', - preventDefault: false, - stopPropagation: false, - args: { - level: 3 - }, - children: [ - { - actionType: 'reload', - preventDefault: false, - stopPropagation: false - }, - { - actionType: 'ajax', - api: 'https://api/form/form3-loop-ajax-1?name=${name}', - preventDefault: false, - stopPropagation: false - }, - // { - // actionType: 'break' - // }, - { - actionType: 'ajax', - api: 'https://api/form/form3-loop-ajax-2?age=${age}' - }, - { - actionType: 'loop', - loopName: 'loopData', - args: { - level: 3 - }, - children: [ - { - actionType: 'ajax', - api: 'https://api/form/form3-loop-loop-ajax-1' - }, - { - actionType: 'ajax', - api: 'https://api/form/form3-loop-loop-ajax-2?age=${age}', - preventDefault: false, - stopPropagation: false - }, - { - actionType: 'continue' - }, - { - actionType: 'ajax', - api: 'https://api/form/form3-loop-loop-ajax-3' - } - ] - } - ] } ] }, diff --git a/examples/components/EventAction/Cmpt.jsx b/examples/components/EventAction/Cmpt.jsx new file mode 100644 index 000000000..1036db949 --- /dev/null +++ b/examples/components/EventAction/Cmpt.jsx @@ -0,0 +1,91 @@ +export default { + type: 'page', + title: '触发其他组件动作', + regions: ['body', 'toolbar', 'header'], + body: [ + { + type: 'button', + id: 'b_001', + label: '触发表单1的刷新动作', + onEvent: { + click: { + actions: [ + { + actionType: 'reload', + args: { + name: 'lvxj', + age: 18 + }, + preventDefault: true, + stopPropagation: false, + componentId: 'form_001' + } + ] + } + } + }, + { + type: 'button', + id: 'b_002', + label: '触发表单1的子表单1的输入框【名字】的清空动作', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'clear', + preventDefault: true, + stopPropagation: false, + componentId: 'form_001_form_01_text_01' + } + ] + } + } + }, + { + type: 'form', + id: 'form_001', + title: '表单1', + name: 'form1', + debug: true, + data: { + selfname: 'selfname' // 测下数据链 + }, + body: [ + { + type: 'form', + id: 'form_001_form_01', + title: '表单1的子表单01', + name: 'sub-form1', + debug: true, + body: [ + { + type: 'input-text', + id: 'form_001_form_01_text_01', + label: '名称', + name: 'name', + disabled: false, + mode: 'horizontal' + }, + { + type: 'input-text', + id: 'form_001_form_01_text_02', + label: '等级', + name: 'level', + disabled: false, + mode: 'horizontal' + }, + { + type: 'input-text', + id: 'form_001_form_01_text_03', + label: '昵称', + name: 'myname', + disabled: false, + mode: 'horizontal' + } + ] + } + ] + } + ] +}; diff --git a/examples/components/EventAction/Common.jsx b/examples/components/EventAction/Common.jsx new file mode 100644 index 000000000..0d210881a --- /dev/null +++ b/examples/components/EventAction/Common.jsx @@ -0,0 +1,293 @@ +export default { + type: 'page', + title: '触发通用动作', + regions: ['body', 'toolbar', 'header'], + body: [ + { + type: 'button', + id: 'b_001', + label: '发送Ajax请求', + actionType: 'reload', + dialog: { + title: '系统提示', + body: '对你点击了' + }, + onEvent: { + click: { + actions: [ + { + actionType: 'ajax', + api: 'https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/form/saveForm', + messages: { + success: '成功了!欧耶', + failed: '失败了呢。。' + } + } + ] + } + } + }, + { + type: 'button', + id: 'b_002', + className: 'ml-2', + label: '打开一个弹窗', + actionType: 'reload', + dialog: { + title: '系统提示', + body: '对你点击了' + }, + onEvent: { + click: { + actions: [ + { + actionType: 'dialog', + args: { + id: '${event.data.ajax1.id}' + }, + dialog: { + type: 'dialog', + id: 'dialog_1', + title: '弹框标题1', + data: { + id: '${id}' + }, + body: [ + { + type: 'form', + debug: true, + body: [ + { + type: 'tpl', + tpl: '

对,你刚刚点击了111${id}

', + inline: false + } + ] + } + ] + } + } + ] + } + } + }, + { + type: 'button', + id: 'b_003', + label: '关闭一个弹窗', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'dialog', + dialog: { + type: 'dialog', + id: 'dialog_1', + title: '弹框标题1', + body: [ + { + type: 'tpl', + tpl: '

对,你刚刚点击了111

', + inline: false + }, + { + type: 'button', + label: '打开一个子弹窗,然后关闭它的父亲', + onEvent: { + click: { + actions: [ + { + actionType: 'dialog', + dialog: { + type: 'dialog', + title: '弹框标题3', + body: [ + { + type: 'button', + label: '关闭指定弹窗', + onEvent: { + click: { + actions: [ + { + actionType: 'closeDialog', + componentId: 'dialog_1' + } + ] + } + } + } + ] + } + } + ] + } + } + }, + { + type: 'button', + label: '关闭当前弹窗', + onEvent: { + click: { + actions: [ + { + actionType: 'closeDialog' + } + ] + } + } + }, + { + type: 'input-text', + label: '文本', + name: 'text', + disabled: false, + mode: 'horizontal' + } + ] + } + } + ] + } + } + }, + { + type: 'button', + id: 'b_004', + label: '打开页面', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'url', + url: 'http://www.baidu.com', + blank: true + } + ] + } + } + }, + { + type: 'button', + id: 'b_005', + label: '打开页面(单页模式)', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'link', + link: './broadcat' + } + ] + } + } + }, + { + type: 'button', + id: 'b_006', + label: '弹个提示对话框', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'alert', + msg: '该吃饭了~' + } + ] + } + } + }, + { + type: 'button', + id: 'b_007', + label: '弹个确认对话框', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'confirm', + title: '操作确认', + msg: '要删除它吗?' + } + ] + } + } + }, + { + type: 'button', + id: 'b_008', + label: '全局消息提示', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'toast', + msgType: 'warning', + msg: '我是一个全局警告消息', + options: { + position: 'top-right' + } + } + ] + } + } + }, + { + type: 'button', + id: 'b_009', + label: '复制一段文本', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'copy', + content: 'http://www.baidu.com' + } + ] + } + } + }, + { + type: 'button', + id: 'b_010', + label: '复制一段富文本', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'copy', + copyFormat: 'text/html', + content: "link bold" + } + ] + } + } + }, + { + type: 'button', + id: 'b_011', + label: '发送邮件', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'email', + to: 'amis@baidu.com', + cc: 'baidu@baidu.com', + subject: '这是邮件主题', + body: '这是邮件正文' + } + ] + } + } + } + ] +}; diff --git a/examples/components/EventAction/Custom.jsx b/examples/components/EventAction/Custom.jsx new file mode 100644 index 000000000..09e985085 --- /dev/null +++ b/examples/components/EventAction/Custom.jsx @@ -0,0 +1,23 @@ +export default { + type: 'page', + title: '自定义JS', + regions: ['body', 'toolbar', 'header'], + body: [ + { + type: 'button', + label: '发送个请求', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'custom', + script: + "doAction({actionType: 'ajax',api: 'https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/form/saveForm'});\n //event.stopPropagation();" + } + ] + } + } + } + ] +}; diff --git a/examples/components/EventAction/DataFlow.jsx b/examples/components/EventAction/DataFlow.jsx new file mode 100644 index 000000000..230abf16c --- /dev/null +++ b/examples/components/EventAction/DataFlow.jsx @@ -0,0 +1,111 @@ +export default { + type: 'page', + title: '数据传递', + regions: ['body', 'toolbar', 'header'], + body: [ + { + type: 'tpl', + tpl: '

从事件触发开始,整个数据流包含事件本身产生的事件数据和动作产生的动作数据,事件源头产生的数据在AMIS事件动作机制底层已经自动加入渲染器数据域,可以通过"event.data.xxx"直接获取,而部分动作产生的数据如何流动需要交互设计者进行介入,对于数据流动可以通过数据映射,将上一个动作产生的数据作为动作参数写入下一个动作。通过outputVar指定输出的变量名,通过args指定输入的参数数据

' + }, + { + type: 'button', + id: 'b_001', + label: '发一个广播,携带args参数', + onEvent: { + click: { + actions: [ + { + actionType: 'broadcast', + eventName: 'broadcast_1', + args: { + name: 'lvxj', + age: 18 + }, + description: '一个按钮的点击事件' + } + ] + } + } + }, + { + type: 'button', + id: 'b_001', + label: '发送Ajax请求,并把返回数据传给弹窗', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'ajax', + api: 'https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/form/saveForm', + messages: { + success: '成功了!欧耶', + failed: '失败了呢。。' + }, + outputVar: 'ajax1' + }, + { + actionType: 'dialog', + args: { + id: '${event.data.ajax1.id}' + }, + dialog: { + type: 'dialog', + id: 'dialog_1', + title: '弹框标题1', + data: { + id: '${id}' + }, + body: [ + { + type: 'form', + debug: true, + body: [ + { + type: 'tpl', + tpl: '

Ajax请求返回的id=${id}

', + inline: false + } + ] + } + ] + } + } + ] + } + } + }, + { + type: 'form', + name: 'form1', + id: 'form_001', + title: '表单1-监听广播并获取携带的参数', + debug: true, + body: [ + { + type: 'input-text', + id: 'form_001_text_01', + label: '年龄', + name: 'age', + disabled: false, + mode: 'horizontal' + } + ], + data: { + name: 'amis' + }, + onEvent: { + broadcast_1: { + actions: [ + { + actionType: 'reload', + args: { + age: '${event.data.age}' + } + } + ] + } + } + } + ] +}; diff --git a/examples/components/EventAction/Logic.jsx b/examples/components/EventAction/Logic.jsx new file mode 100644 index 000000000..91cc0043b --- /dev/null +++ b/examples/components/EventAction/Logic.jsx @@ -0,0 +1,191 @@ +export default { + type: 'page', + title: '逻辑编排', + regions: ['body', 'toolbar', 'header'], + body: { + type: 'form', + data: { + loopData: [ + { + name: 'lv', + age: '19' + }, + { + name: 'xj', + age: '21' + } + ], + branchCont: 18, + execOn: 'kkk' + }, + body: [ + { + type: 'button', + id: 'b_001', + label: '条件', + onEvent: { + click: { + actions: [ + { + actionType: 'ajax', + args: { + param: '0' + }, + api: 'https://api/form/ajax?param=${param}', // param=2,如果想要拿到当前域的数据需要通过args数据映射 + execOn: 'execOn === "kkk"' + }, + { + actionType: 'ajax', + args: { + param: '1' + }, + api: 'https://api/form/ajax?param=${param}', // param=2,如果想要拿到当前域的数据需要通过args数据映射 + execOn: 'execOn === "jjj"' + }, + { + actionType: 'ajax', + args: { + param: '2' + }, + api: 'https://api/form/ajax?param=${param}', // param=2,如果想要拿到当前域的数据需要通过args数据映射 + execOn: 'execOn === "kkk"' + } + ] + } + } + }, + { + type: 'button', + id: 'b_002', + label: '并行', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'parallel', + children: [ + { + actionType: 'ajax', + api: 'https://api/form/parallel-ajax-1', + preventDefault: false + // stopPropagation: true + }, + { + actionType: 'ajax', + api: 'https://api/form/parallel-ajax-2' + } + ] + } + ] + } + } + }, + { + type: 'button', + id: 'b_003', + label: '循环', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'loop', + loopName: 'loopData', + preventDefault: false, + stopPropagation: false, + args: { + level: 3 + }, + children: [ + { + actionType: 'reload', + preventDefault: false, + stopPropagation: false + }, + { + actionType: 'ajax', + api: 'https://api/form/loop-ajax-1?name=${name}', + preventDefault: false, + stopPropagation: false + }, + // { + // actionType: 'break' + // }, + { + actionType: 'ajax', + api: 'https://api/form/loop-ajax-2?age=${age}' + }, + { + actionType: 'loop', + loopName: 'loopData', + args: { + level: 3 + }, + children: [ + { + actionType: 'ajax', + api: 'https://api/form/loop-loop-ajax-1' + }, + { + actionType: 'ajax', + api: 'https://api/form/loop-loop-ajax-2?age=${age}', + preventDefault: false, + stopPropagation: false + }, + { + actionType: 'continue' + }, + { + actionType: 'ajax', + api: 'https://api/form/loop-loop-ajax-3' + } + ] + } + ] + } + ] + } + } + }, + { + type: 'button', + id: 'b_004', + label: 'switch排他', + className: 'ml-2', + onEvent: { + click: { + actions: [ + { + actionType: 'switch', + preventDefault: false, + stopPropagation: false, + children: [ + { + actionType: 'ajax', + api: 'https://api/form/branch-ajax-1', + expression: 'this.branchCont > 19', + preventDefault: false, + stopPropagation: true // 这里无效,因为条件不成立 + }, + { + actionType: 'ajax', + api: 'https://api/form/branch-ajax-2', + expression: 'this.branchCont > 17', + preventDefault: false, + stopPropagation: false + }, + { + actionType: 'ajax', + api: 'https://api/form/branch-ajax-3', + expression: 'this.branchCont > 16' + } + ] + } + ] + } + } + } + ] + } +}; diff --git a/examples/components/EventAction/Stop.jsx b/examples/components/EventAction/Stop.jsx new file mode 100644 index 000000000..84baf5608 --- /dev/null +++ b/examples/components/EventAction/Stop.jsx @@ -0,0 +1,140 @@ +export default { + type: 'page', + title: '事件/动作干预', + regions: ['body', 'toolbar', 'header'], + body: [ + { + type: 'button', + id: 'b_001', + label: '联动表单1(事件干预)', + actionType: 'dialog', + dialog: { + title: '系统提示', + body: '对你点击了' + }, + onEvent: { + click: { + actions: [ + { + actionType: 'broadcast', + eventName: 'broadcast_1', + args: { + name: 'lvxj', + age: 18 + }, + description: '一个按钮的点击事件' + } + ] + } + } + }, + { + type: 'button', + id: 'b_002', + label: '联动表单2(动作干预)', + className: 'ml-2', + actionType: 'dialog', + dialog: { + title: '系统提示', + body: '对你点击了' + }, + onEvent: { + click: { + actions: [ + { + actionType: 'broadcast', + eventName: 'broadcast_1', + args: { + name: 'lvxj', + age: 18 + }, + description: '一个按钮的点击事件' + } + ] + } + } + }, + + { + type: 'form', + name: 'form1', + id: 'form_001', + title: '表单1-刷新(刷新后,按钮的弹窗动作因为被干预,不会执行)', + debug: true, + body: [ + { + type: 'input-text', + id: 'form_001_text_01', + label: '年龄', + name: 'age', + disabled: false, + mode: 'horizontal' + } + ], + data: { + execOn: 'kkk', + param: '1' + }, + onEvent: { + broadcast_1: { + actions: [ + { + actionType: 'reload', + args: { + age: '${event.data.age}' + }, + preventDefault: true, // 阻止按钮的弹窗 + execOn: 'execOn === "kkk"' // or this.xxx + } + ] + } + } + }, + { + type: 'form', + name: 'form2', + id: 'form_002', + title: '表单2-刷新+发Ajax(只执行刷新,Ajax请求被干预,不会执行)', + debug: true, + body: [ + { + type: 'input-text', + id: 'form_001_text_01', + label: '年龄', + name: 'age', + disabled: false, + mode: 'horizontal' + } + ], + data: { + execOn: 'kkk', + param: '1' + }, + onEvent: { + broadcast_1: { + actions: [ + { + actionType: 'reload', + args: { + age: '${event.data.age}' + }, + preventDefault: false, // 阻止按钮的弹窗 + stopPropagation: true, + execOn: 'execOn === "kkk"' // or this.xxx + }, + { + actionType: 'ajax', + args: { + param: '2' + }, + api: 'https://api/form/ajax?param=${param}', // param=2,如果想要拿到当前域的数据需要通过args数据映射 + execOn: 'execOn === "kkk"', + preventDefault: false, + stopPropagation: false + } + ] + } + } + } + ] +}; diff --git a/examples/components/Example.jsx b/examples/components/Example.jsx index 155f986aa..c71716d6e 100644 --- a/examples/components/Example.jsx +++ b/examples/components/Example.jsx @@ -66,7 +66,13 @@ import CRUDLinkPageSchema from './Linkage/CRUD'; import OptionsPageSchema from './Linkage/Options'; import OptionsLocalPageSchema from './Linkage/OptionsLocal'; import FormSubmitSchema from './Linkage/FormSubmit'; -import EventsSchema from './Linkage/Event'; +import CommonEventActionSchema from './EventAction/Common'; +import BroadcastEventActionSchema from './EventAction/Broadcast'; +import CmptEventActionSchema from './EventAction/Cmpt'; +import CustomEventActionSchema from './EventAction/Custom'; +import LogicEventActionSchema from './EventAction/Logic'; +import StopEventActionSchema from './EventAction/Stop'; +import DataFlowEventActionSchema from './EventAction/DataFlow'; import WizardSchema from './Wizard'; import ChartSchema from './Chart'; import EChartsEditorSchema from './ECharts'; @@ -496,11 +502,48 @@ export const examples = [ label: '表单和列表联动', path: '/examples/linkpage/crud', component: makeSchemaRenderer(CRUDLinkPageSchema) + } + ] + }, + + { + label: '事件动作机制', + icon: 'fa fa-bolt', + children: [ + { + label: '执行通用动作', + path: '/examples/event-action/common', + component: makeSchemaRenderer(CommonEventActionSchema) }, { - label: '广播事件机制', - path: '/examples/linkpage/event', - component: makeSchemaRenderer(EventsSchema) + label: '广播(自定义事件)', + path: '/examples/event-action/broadcat', + component: makeSchemaRenderer(BroadcastEventActionSchema) + }, + { + label: '执行其他组件动作', + path: '/examples/event-action/cmpt', + component: makeSchemaRenderer(CmptEventActionSchema) + }, + { + label: '自定义JS', + path: '/examples/event-action/custom', + component: makeSchemaRenderer(CustomEventActionSchema) + }, + { + label: '执行逻辑编排动作', + path: '/examples/event-action/logic', + component: makeSchemaRenderer(LogicEventActionSchema) + }, + { + label: '事件/动作干预', + path: '/examples/event-action/stop', + component: makeSchemaRenderer(StopEventActionSchema) + }, + { + label: '动作间数据传递', + path: '/examples/event-action/dataflow', + component: makeSchemaRenderer(DataFlowEventActionSchema) } ] }, diff --git a/src/SchemaRenderer.tsx b/src/SchemaRenderer.tsx index 27acf595d..b9df83c1d 100644 --- a/src/SchemaRenderer.tsx +++ b/src/SchemaRenderer.tsx @@ -65,7 +65,7 @@ class BroadcastCmpt extends React.Component { constructor(props: BroadcastCmptProps, context: IScopedContext) { super(props); - this.triggerEvent = this.triggerEvent.bind(this); + this.dispatchEvent = this.dispatchEvent.bind(this); } componentDidMount() { @@ -81,7 +81,7 @@ class BroadcastCmpt extends React.Component { return this.ref; } - async triggerEvent( + async dispatchEvent( e: React.MouseEvent, data: any ): Promise | undefined> { @@ -108,10 +108,10 @@ class BroadcastCmpt extends React.Component { ) : ( - + ); } } diff --git a/src/actions/Action.ts b/src/actions/Action.ts index 8df516fb4..1b13982fe 100644 --- a/src/actions/Action.ts +++ b/src/actions/Action.ts @@ -20,6 +20,7 @@ export interface ListenerAction { description?: string; // 事件描述,actionType: broadcast componentId?: string; // 组件ID,用于直接执行指定组件的动作 args?: any; // 参数,可以配置数据映射 + outputVar?: any; // 输出数据变量名 preventDefault?: boolean; // 阻止原有组件的动作行为 stopPropagation?: boolean; // 阻止后续的事件处理器执行 execOn?: string; // 执行条件 @@ -72,7 +73,7 @@ export const runActions = async ( let actionInstrance = getActionByType(actionConfig.actionType); // 如果存在指定组件ID,说明是组件专有动作 - if (actionConfig.componentId) { + if (!actionInstrance && actionConfig.componentId) { actionInstrance = getActionByType('component'); } else if ( actionConfig.actionType === 'url' || @@ -80,7 +81,7 @@ export const runActions = async ( actionConfig.actionType === 'jump' ) { // 打开页面动作 - actionInstrance = getActionByType('openpage'); + actionInstrance = getActionByType('openlink'); } // 找不到就通过组件专有动作完成 diff --git a/src/actions/AjaxAction.ts b/src/actions/AjaxAction.ts index 4d9f71107..31e9e298f 100644 --- a/src/actions/AjaxAction.ts +++ b/src/actions/AjaxAction.ts @@ -1,5 +1,8 @@ import {IRootStore} from '../store/root'; -import {isVisible} from '../utils/helper'; +import {fetchOptions} from '../types'; +import {normalizeApiResponseData} from '../utils/api'; +import {ServerError} from '../utils/errors'; +import {createObject, isEmpty, isVisible} from '../utils/helper'; import {RendererEvent} from '../utils/renderer-event'; import {filter} from '../utils/tpl'; import { @@ -22,36 +25,73 @@ export class AjaxAction implements Action { renderer: ListenerContext, event: RendererEvent ) { - const store = renderer.props.store; + const env = event.context.env; + try { + const options = { + ...(action.options ?? {}), + method: action.method ?? 'post' + }; - store.setCurrentAction(action); - store - .saveRemote(action.api as string, action.args, { - successMessage: action.messages && action.messages.success, - errorMessage: action.messages && action.messages.failed - }) - .then(async () => { - if (action.feedback && isVisible(action.feedback, store.data)) { - await this.openFeedback(action.feedback, store); - } + const result = await env.fetcher( + action.api as string, + action.args, + options + ); - const redirect = action.redirect && filter(action.redirect, store.data); - redirect && renderer.env.jumpTo(redirect, action); - }) - .catch(() => {}); - } + if (!isEmpty(result.data) || result.ok) { + const responseData = normalizeApiResponseData(result.data); + // 记录请求返回的数据 + event.setData( + createObject( + event.data, + action.outputVar + ? { + [`${action.outputVar}`]: responseData + } + : responseData + ) + ); + } - openFeedback(dialog: any, store: IRootStore) { - return new Promise(resolve => { - store.setCurrentAction({ - type: 'button', - actionType: 'dialog', - dialog: dialog - }); - store.openDialog(store.data, undefined, confirmed => { - resolve(confirmed); - }); - }); + if (!result.ok) { + throw new ServerError( + (action.messages && action.messages.failed) ?? result.msg, + result + ); + } else { + env.notify( + 'success', + (action.messages && action.messages.success) ?? result.msg, + result.msgTimeout !== undefined + ? { + closeButton: true, + timeout: result.msgTimeout + } + : undefined + ); + } + + return result.data; + } catch (e) { + if (e.type === 'ServerError') { + const result = (e as ServerError).response; + env.notify( + 'error', + e.message, + result.msgTimeout !== undefined + ? { + closeButton: true, + timeout: result.msgTimeout + } + : undefined + ); + } else { + env.notify('error', e.message); + } + + // 不阻塞后面执行 + // throw e; + } } } diff --git a/src/actions/BroadcastAction.ts b/src/actions/BroadcastAction.ts index c3bdf0608..28cbedf09 100644 --- a/src/actions/BroadcastAction.ts +++ b/src/actions/BroadcastAction.ts @@ -32,6 +32,7 @@ export class BroadcastAction implements Action { return await event.context.env.dispatchEvent( action.eventName, renderer, + event.context.scoped, action.args, event ); diff --git a/src/actions/CmptAction.ts b/src/actions/CmptAction.ts index 6e7d3b007..1c75a54b2 100644 --- a/src/actions/CmptAction.ts +++ b/src/actions/CmptAction.ts @@ -23,13 +23,18 @@ export class CmptAction implements Action { ) { // 根据唯一ID查找指定组件 const component = - renderer.props.$schema.id !== action.componentId + action.componentId && renderer.props.$schema.id !== action.componentId ? event.context.scoped?.getComponentById(action.componentId) : renderer; // 执行组件动作 - (await component.props.onAction?.(event, action, action.args)) || - component.doAction?.(action, action.args); + return ( + (await component.props.onAction?.( + event.context.nativeEvent, + action, + action.args + )) || component.doAction?.(action, action.args) + ); } } diff --git a/src/actions/CopyAction.ts b/src/actions/CopyAction.ts index 067029c0f..83369bd37 100644 --- a/src/actions/CopyAction.ts +++ b/src/actions/CopyAction.ts @@ -26,7 +26,6 @@ export class CopyAction implements Action { renderer: ListenerContext, event: RendererEvent ) { - debugger; if (action.content || action.copy) { renderer.props.env.copy?.( filter(action.content || action.copy, action.args, '| raw'), diff --git a/src/actions/CustomAction.ts b/src/actions/CustomAction.ts index aa7b0e886..5eb478425 100644 --- a/src/actions/CustomAction.ts +++ b/src/actions/CustomAction.ts @@ -37,8 +37,10 @@ export class CustomAction implements Action { await (scriptFunc as any)?.call( null, renderer, - renderer.doAction.bind(renderer), - event + renderer.props.onAction?.bind(renderer, event.context.nativeEvent) || + renderer.doAction?.bind(renderer), + event, + action ); } } diff --git a/src/actions/DialogAction.ts b/src/actions/DialogAction.ts index fba425eb2..78015c65f 100644 --- a/src/actions/DialogAction.ts +++ b/src/actions/DialogAction.ts @@ -1,3 +1,7 @@ +import React from 'react'; +import {render} from 'react-dom'; +import {render as amisRender} from '../factory'; +import {uuid} from '../utils/helper'; import {RendererEvent} from '../utils/renderer-event'; import { Action, @@ -6,6 +10,12 @@ import { registerAction } from './Action'; +let dialogs: { + id: string; + action: any; + store: any; +}[] = []; + /** * 打开弹窗动作 * @@ -20,9 +30,68 @@ export class DialogAction implements Action { event: RendererEvent ) { const store = renderer.props.store; + // 记录弹窗 + dialogs.push({ + id: action.dialog.id || '', // id依赖用户的配置 + action, + store + }); + // 打开弹窗 store.setCurrentAction(action); store.openDialog(action.args); } } +/** + * 关闭弹窗动作 + * + * @export + * @class CloseDialogAction + * @implements {Action} + */ +export class CloseDialogAction implements Action { + async run( + action: ListenerAction, + renderer: ListenerContext, + event: RendererEvent + ) { + // 找到指定关闭的弹窗或者直接关闭当前 + const context = action.componentId + ? dialogs.find(item => item.id === action.componentId) + : dialogs[dialogs.length - 1]; + // 如果通过store去关闭,需要确保store是dialog的渲染载体 + // 但如果是弹窗里弹窗的话,关闭父弹窗会同时关闭子弹窗 + context?.store.closeDialog(false); + } +} + +/** + * alert提示动作 + */ +export class AlertAction implements Action { + async run( + action: ListenerAction, + renderer: ListenerContext, + event: RendererEvent + ) { + event.context.env.alert?.(action.msg); + } +} + +/** + * confirm确认提示动作 + */ +export class ConfirmAction implements Action { + async run( + action: ListenerAction, + renderer: ListenerContext, + event: RendererEvent + ) { + event.context.env.confirm?.(action.msg, action.title); + } +} + registerAction('dialog', new DialogAction()); +registerAction('closeDialog', new CloseDialogAction()); +registerAction('alert', new AlertAction()); +registerAction('confirm', new ConfirmAction()); diff --git a/src/actions/DrawerAction.ts b/src/actions/DrawerAction.ts index 7102ba9b4..760315639 100644 --- a/src/actions/DrawerAction.ts +++ b/src/actions/DrawerAction.ts @@ -1,28 +1,23 @@ -import {RendererEvent} from '../utils/renderer-event'; -import { - Action, - ListenerAction, - ListenerContext, - registerAction -} from './Action'; +import {registerAction} from './Action'; +import {CloseDialogAction, DialogAction} from './DialogAction'; /** * 打开抽屉动作 * * @export * @class DrawerAction - * @implements {Action} + * @extends {DialogAction} */ -export class DrawerAction implements Action { - async run( - action: ListenerAction, - renderer: ListenerContext, - event: RendererEvent - ) { - const store = renderer.props.store; - store.setCurrentAction(action); - store.openDrawer(action.args); - } -} +export class DrawerAction extends DialogAction {} + +/** + * 关闭抽屉动作 + * + * @export + * @class CloseDrawerAction + * @extends {CloseDialogAction} + */ +export class CloseDrawerAction extends CloseDialogAction {} registerAction('drawer', new DrawerAction()); +registerAction('closeDrawer', new CloseDrawerAction()); diff --git a/src/actions/OpenPageAction.ts b/src/actions/LinkAction.ts similarity index 84% rename from src/actions/OpenPageAction.ts rename to src/actions/LinkAction.ts index a1504384f..85d43c8c0 100644 --- a/src/actions/OpenPageAction.ts +++ b/src/actions/LinkAction.ts @@ -11,10 +11,10 @@ import { * 打开页面动作 * * @export - * @class OpenPageAction + * @class LinkAction * @implements {Action} */ -export class OpenPageAction implements Action { +export class LinkAction implements Action { async run( action: ListenerAction, renderer: ListenerContext, @@ -36,4 +36,4 @@ export class OpenPageAction implements Action { } } -registerAction('openpage', new OpenPageAction()); +registerAction('openlink', new LinkAction()); diff --git a/src/actions/ToastAction.ts b/src/actions/ToastAction.ts new file mode 100644 index 000000000..18355c881 --- /dev/null +++ b/src/actions/ToastAction.ts @@ -0,0 +1,22 @@ +import {RendererEvent} from '../utils/renderer-event'; +import { + Action, + ListenerAction, + ListenerContext, + registerAction +} from './Action'; + +/** + * 全局toast + */ +export class ToastAction implements Action { + async run( + action: ListenerAction, + renderer: ListenerContext, + event: RendererEvent + ) { + event.context.env.notify?.(action.msgType, action.msg, action.options); + } +} + +registerAction('toast', new ToastAction()); diff --git a/src/actions/index.ts b/src/actions/index.ts index 050131a16..0a7374c49 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -15,6 +15,7 @@ import './CopyAction'; import './DialogAction'; import './DrawerAction'; import './EmailAction'; -import './OpenPageAction'; +import './LinkAction'; +import './ToastAction'; export * from './Action'; diff --git a/src/factory.tsx b/src/factory.tsx index b1266baed..b637243e9 100644 --- a/src/factory.tsx +++ b/src/factory.tsx @@ -381,7 +381,7 @@ const defaultOptions: RenderOptions = { return () => { this.rendererEventListeners = this.rendererEventListeners.filter( - (item: RendererEventListener) => item.renderer === renderer + (item: RendererEventListener) => item.renderer !== renderer ); }; }