diff --git a/docs/zh-CN/components/service.md b/docs/zh-CN/components/service.md index bae585d45..b7731aa9a 100755 --- a/docs/zh-CN/components/service.md +++ b/docs/zh-CN/components/service.md @@ -10,7 +10,7 @@ order: 63 amis 中部分组件,作为展示组件,自身没有**使用接口初始化数据域的能力**,例如:[Table](./table)、[Cards](./cards)、[List](./list)等,他们需要使用某些配置项,例如`source`,通过[数据映射](../../docs/concepts/data-mapping)功能,在当前的 **数据链** 中获取数据,并进行数据展示。 -而`Service`组件就是专门为该类组件而生,它的功能是::**配置初始化接口,进行数据域的初始化,然后在`Service`内容器中配置子组件,这些子组件通过数据链的方法,获取`Service`所拉取到的数据** +而`Service`组件就是专门为该类组件而生,它的功能是:**配置初始化接口,进行数据域的初始化,然后在`Service`内容器中配置子组件,这些子组件通过数据链的方法,获取`Service`所拉取到的数据** ## 基本使用 @@ -584,3 +584,18 @@ ws.on('connection', function connection(ws) { | interval | `number` | | 轮询时间间隔(最低 3000) | | silentPolling | `boolean` | `false` | 配置轮询时是否显示加载动画 | | stopAutoRefreshWhen | [表达式](../../docs/concepts/expression) | | 配置停止轮询的条件 | + +## 事件表 + +| 事件名称 | 事件参数 | 说明 | +| ----------------- | -------------------- | -------------------- | +| fetchInited | api 初始化数据 | api 初始化完成 | +| fetchSchemaInited | schemaApi 初始化数据 | schemaApi 初始化完成 | + +## 动作表 + +| 动作名称 | 动作配置 | 说明 | +| -------- | -------- | ----------------------------------------------- | +| reload | - | 重新加载,调用 api,刷新数据域数据 | +| rebuild | - | 重新构建,调用 schemaApi,重新构建容器内 Schema | +| setValue | - | 更新数据域数据 | diff --git a/examples/components/EventAction/FormEvent.jsx b/examples/components/EventAction/FormEvent.jsx index adfce8dff..995e2be62 100644 --- a/examples/components/EventAction/FormEvent.jsx +++ b/examples/components/EventAction/FormEvent.jsx @@ -27,7 +27,7 @@ export default { onEvent: { click: { actions: [ - { + { actionType: 'reset', componentId: 'form-action-receiver' } @@ -86,7 +86,7 @@ export default { { name: 'form-action-receiver', id: 'form-action-receiver', - title: "表单:用于接收上面按钮的动作,派发form本身的事件", + title: '表单:用于接收上面按钮的动作,派发form本身的事件', type: 'form', debug: true, api: '/api/mock2/form/saveForm', @@ -298,7 +298,7 @@ export default { type: 'form', debug: true, api: '/api/mock2/form/saveForm', - title: "表单:派发formItem的校验事件", + title: '表单:派发formItem的校验事件', data: { data1: '初始化数据1', data2: '初始化数据2' diff --git a/examples/components/EventAction/ServiceEvent.jsx b/examples/components/EventAction/ServiceEvent.jsx new file mode 100644 index 000000000..e2c9b5876 --- /dev/null +++ b/examples/components/EventAction/ServiceEvent.jsx @@ -0,0 +1,227 @@ +export default { + type: 'page', + title: 'Service功能型容器', + regions: ['body', 'toolbar', 'header'], + body: [ + { + type: 'tpl', + tpl: '事件', + inline: false, + wrapperComponent: 'h2' + }, + { + type: 'tpl', + tpl: 'fetchInited 事件', + inline: false, + wrapperComponent: 'h3' + }, + { + type: 'action', + level: 'success', + label: 'fetchInited', + actionType: 'dialog', + dialog: { + title: 'fetchInited', + body: [ + { + type: 'service', + name: 'service-api', + api: '/api/mock2/page/initData', + body: { + type: 'panel', + title: '$title', + body: '现在是:${date}' + }, + onEvent: { + fetchInited: { + actions: [ + { + actionType: 'toast', + msgType: 'success', + msg: 'API inited: ${date}' + } + ] + } + } + } + ] + } + }, + { + type: 'divider' + }, + { + type: 'tpl', + tpl: 'fetchSchemaInited 事件', + inline: false, + wrapperComponent: 'h3' + }, + { + type: 'action', + level: 'info', + label: 'fetchSchemaInited', + actionType: 'dialog', + dialog: { + title: 'fetchSchemaInited', + body: [ + { + type: 'service', + name: 'service-schema-api', + schemaApi: '/api/mock2/service/schema?type=form', + onEvent: { + fetchSchemaInited: { + actions: [ + { + actionType: 'toast', + msgType: 'info', + msg: 'SchemaAPI inited: title: ${title}' + } + ] + } + } + } + ] + } + }, + { + type: 'divider' + }, + { + type: 'tpl', + tpl: '动作', + inline: false, + wrapperComponent: 'h2' + }, + { + type: 'tpl', + tpl: 'reload 动作', + inline: false, + wrapperComponent: 'h3' + }, + { + type: 'action', + label: 'reload触发器', + level: 'primary', + className: 'mb-3', + onEvent: { + click: { + actions: [ + { + actionType: 'reload', + componentId: 'service-reload' + } + ] + } + } + }, + { + type: 'service', + id: 'service-reload', + name: 'service-reload', + api: '/api/mock2/number/random', + body: { + type: 'panel', + title: 'reload动作', + body: '现在是:${random}' + } + }, + { + type: 'divider' + }, + { + type: 'tpl', + tpl: 'rebuild 动作', + inline: false, + wrapperComponent: 'h3' + }, + { + type: 'alert', + body: '请选择一种构建方式生成组件', + level: 'info', + showIcon: true, + className: 'mb-3', + visibleOn: 'this.schemaType == null' + }, + { + type: 'button-group', + tiled: true, + className: 'mb-3', + buttons: ['form', 'tabs', 'crud'].map(schema => ({ + type: 'action', + label: `构建${schema}`, + icon: 'fa fa-hammer', + onEvent: { + click: { + actions: [ + { + actionType: 'rebuild', + componentId: 'service-rebuild', + args: { + schemaType: `${schema}` + } + } + ] + } + } + })) + }, + { + type: 'service', + id: 'service-rebuild', + name: 'service-rebuild', + schemaApi: { + url: '/api/mock2/service/schema?type=${schemaType}', + method: 'post', + sendOn: 'this.schemaType != null' + } + }, + { + type: 'divider' + }, + { + type: 'tpl', + tpl: 'setValue 动作', + inline: false, + wrapperComponent: 'h3' + }, + { + type: 'action', + label: 'setValue触发器', + level: 'primary', + className: 'mb-3', + onEvent: { + click: { + actions: [ + { + actionType: 'setValue', + componentId: 'service-setvalue', + value: {language: ['🇨🇳 中国']} + } + ] + } + } + }, + { + type: 'service', + id: 'service-setvalue', + name: 'service-setvalue', + data: { + language: ['🇺🇸 美国'] + }, + body: { + type: 'panel', + title: 'setValue动作', + body: [ + { + type: 'each', + name: 'language', + items: { + type: 'tpl', + tpl: "<%= data.item %> " + } + } + ] + } + } + ] +}; diff --git a/examples/components/Example.jsx b/examples/components/Example.jsx index 7b78bf946..1628a3d19 100644 --- a/examples/components/Example.jsx +++ b/examples/components/Example.jsx @@ -86,6 +86,7 @@ import InputTreeEventSchema from './EventAction/InputTreeEvent'; import treeSelectEventSchema from './EventAction/treeSelectEvent'; import FormEventActionSchema from './EventAction/FormEvent'; import TransferEventSchema from './EventAction/TransferEvent'; +import ServiceEventSchema from './EventAction/ServiceEvent'; import WizardSchema from './Wizard'; import ChartSchema from './Chart'; import EChartsEditorSchema from './ECharts'; @@ -608,6 +609,11 @@ export const examples = [ label: '穿梭框类组件', path: 'examples/event/transfer', component: makeSchemaRenderer(TransferEventSchema) + }, + { + label: 'Service组件', + path: 'examples/event/service', + component: makeSchemaRenderer(ServiceEventSchema) } ] }, diff --git a/examples/components/Services/Schema.jsx b/examples/components/Services/Schema.jsx index 0029dc7ca..a22896606 100644 --- a/examples/components/Services/Schema.jsx +++ b/examples/components/Services/Schema.jsx @@ -38,7 +38,6 @@ export default { } ] }, - { name: 'service1', type: 'service', diff --git a/mock/cfc/mock/number/random.js b/mock/cfc/mock/number/random.js new file mode 100644 index 000000000..9ed2f58f5 --- /dev/null +++ b/mock/cfc/mock/number/random.js @@ -0,0 +1,18 @@ +module.exports = function (req, res) { + const showError = req.query.error; + + if (showError) { + return res.json({ + status: 404, + msg: 'Not Found' + }); + } else { + return res.json({ + status: 0, + msg: '随机返回一个数字', + data: { + random: Math.random() * 1000 + } + }); + } +}; diff --git a/src/actions/ToastAction.ts b/src/actions/ToastAction.ts index e7b963daa..a75745e0f 100644 --- a/src/actions/ToastAction.ts +++ b/src/actions/ToastAction.ts @@ -5,6 +5,7 @@ import { ListenerContext, registerAction } from './Action'; +import {resolveVariableAndFilter} from '../utils/tpl-builtin'; export interface IToastAction extends ListenerAction { msg: string; @@ -31,7 +32,11 @@ export class ToastAction implements Action { renderer: ListenerContext, event: RendererEvent ) { - event.context.env.notify?.(action.msgType || 'info', action.msg, action); + event.context.env.notify?.( + action.msgType || 'info', + resolveVariableAndFilter(action.msg, event?.data, '| raw'), + action + ); } } diff --git a/src/renderers/Service.tsx b/src/renderers/Service.tsx index 32219927b..6d36b41c7 100644 --- a/src/renderers/Service.tsx +++ b/src/renderers/Service.tsx @@ -31,6 +31,9 @@ import { } from '../Schema'; import {IIRendererStore} from '../store'; +import type {ScopedComponentType} from '../Scoped'; +import type {ListenerAction} from '../actions/Action'; + /** * Service 服务类控件。 * 文档:https://baidu.gitee.io/amis/docs/components/service @@ -209,6 +212,32 @@ export default class Service extends React.Component { } } + doAction(action: ListenerAction, args: any) { + if (action?.actionType === 'rebuild') { + const { + schemaApi, + store, + dataProvider, + messages: {fetchSuccess, fetchFailed} + } = this.props; + + store.updateData(args); + clearTimeout(this.timer); + if (isEffectiveApi(schemaApi, store.data)) { + store + .fetchSchema(schemaApi, store.data, { + successMessage: fetchSuccess, + errorMessage: fetchFailed + }) + .then(this.afterSchemaFetch); + } + + if (dataProvider) { + this.runDataProvider(); + } + } + } + @autobind initFetch() { const { @@ -331,7 +360,10 @@ export default class Service extends React.Component { // 初始化接口返回的是整个 response, // 保存 ajax 请求的时候返回时数据部分。 const data = result?.hasOwnProperty('ok') ? result.data : result; - const {onBulkChange} = this.props; + const {onBulkChange, dispatchEvent} = this.props; + + dispatchEvent?.('fetchInited', data); + if (!isEmpty(data) && onBulkChange) { onBulkChange(data); } @@ -340,7 +372,10 @@ export default class Service extends React.Component { } afterSchemaFetch(schema: any) { - const {onBulkChange, formStore} = this.props; + const {onBulkChange, formStore, dispatchEvent} = this.props; + + dispatchEvent?.('fetchSchemaInited', schema); + if (formStore && schema?.data && onBulkChange) { onBulkChange && onBulkChange(schema.data); } @@ -586,7 +621,7 @@ export class ServiceRenderer extends Service { super(props); const scoped = context; - scoped.registerComponent(this); + scoped.registerComponent(this as ScopedComponentType); } reload(subpath?: string, query?: any, ctx?: any, silent?: boolean) { @@ -613,7 +648,7 @@ export class ServiceRenderer extends Service { componentWillUnmount() { super.componentWillUnmount(); const scoped = this.context as IScopedContext; - scoped.unRegisterComponent(this); + scoped.unRegisterComponent(this as ScopedComponentType); } reloadTarget(target: string, data?: any) { @@ -622,6 +657,6 @@ export class ServiceRenderer extends Service { } setData(values: object) { - return super.afterDataFetch(values); + return this.props.store.updateData(values); } }