mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-02 03:58:07 +08:00
feat:扩充Scoped根据ID查找组件&补充几个通用动作 (#3380)
* feat:扩充Scoped根据ID查找组件&补充几个通用动作 * feat:扩充Scoped根据ID查找组件&补充几个通用动作
This commit is contained in:
parent
afaa9384d2
commit
fd3bc997df
@ -5,6 +5,7 @@ export default {
|
||||
body: [
|
||||
{
|
||||
type: 'button',
|
||||
id: 'b_001',
|
||||
label: '发送广播事件1-表单1/2/3都在监听',
|
||||
actionType: 'reload',
|
||||
dialog: {
|
||||
@ -15,9 +16,20 @@ export default {
|
||||
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',
|
||||
eventName: 'broadcast_1dddd',
|
||||
args: {
|
||||
name: 'lvxj',
|
||||
age: 18,
|
||||
@ -40,6 +52,7 @@ export default {
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
id: 'b_002',
|
||||
label: '发送广播事件2-表单3在监听',
|
||||
className: 'ml-2',
|
||||
actionType: 'reload',
|
||||
@ -65,6 +78,7 @@ export default {
|
||||
},
|
||||
{
|
||||
type: 'form',
|
||||
id: 'form_001',
|
||||
title: '表单1(我的权重最低)-刷新',
|
||||
name: 'form1',
|
||||
debug: true,
|
||||
@ -74,12 +88,14 @@ export default {
|
||||
body: [
|
||||
{
|
||||
type: 'form',
|
||||
id: 'form_001_form_01',
|
||||
title: '表单1(我的权重最低)-刷新',
|
||||
name: 'sub-form1',
|
||||
debug: true,
|
||||
body: [
|
||||
{
|
||||
type: 'input-text',
|
||||
id: 'form_001_form_01_text_01',
|
||||
label: '名称',
|
||||
name: 'name',
|
||||
disabled: false,
|
||||
@ -87,6 +103,7 @@ export default {
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
id: 'form_001_form_01_text_02',
|
||||
label: '等级',
|
||||
name: 'level',
|
||||
disabled: false,
|
||||
@ -94,6 +111,7 @@ export default {
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
id: 'form_001_form_01_text_03',
|
||||
label: '昵称',
|
||||
name: 'myname',
|
||||
disabled: false,
|
||||
@ -122,11 +140,13 @@ export default {
|
||||
{
|
||||
type: 'form',
|
||||
name: 'form2',
|
||||
id: 'form_002',
|
||||
title: '表单2(权重2)-刷新+发Ajax',
|
||||
debug: true,
|
||||
body: [
|
||||
{
|
||||
type: 'input-text',
|
||||
id: 'form_001_text_01',
|
||||
label: '年龄',
|
||||
name: 'age',
|
||||
disabled: false,
|
||||
@ -176,11 +196,13 @@ export default {
|
||||
{
|
||||
type: 'form',
|
||||
name: 'form3',
|
||||
id: 'form_003',
|
||||
title: '表单3(权重3)-逻辑编排',
|
||||
debug: true,
|
||||
body: [
|
||||
{
|
||||
type: 'input-text',
|
||||
id: 'form_003_text_01',
|
||||
label: '职业',
|
||||
name: 'job',
|
||||
disabled: false,
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
} from './factory';
|
||||
import {asFormItem} from './renderers/Form/Item';
|
||||
import {renderChild, renderChildren} from './Root';
|
||||
import {IScopedContext, ScopedContext} from './Scoped';
|
||||
import {Schema, SchemaNode} from './types';
|
||||
import {DebugWrapper, enableAMISDebug} from './utils/debug';
|
||||
import getExprProperties from './utils/filter-schema';
|
||||
@ -60,8 +61,9 @@ const componentCache: SimpleMap = new SimpleMap();
|
||||
class BroadcastCmpt extends React.Component<BroadcastCmptProps> {
|
||||
ref: any;
|
||||
unbindEvent: (() => void) | undefined = undefined;
|
||||
static contextType = ScopedContext;
|
||||
|
||||
constructor(props: BroadcastCmptProps) {
|
||||
constructor(props: BroadcastCmptProps, context: IScopedContext) {
|
||||
super(props);
|
||||
this.triggerEvent = this.triggerEvent.bind(this);
|
||||
}
|
||||
@ -101,6 +103,7 @@ class BroadcastCmpt extends React.Component<BroadcastCmptProps> {
|
||||
<Component
|
||||
ref={this.childRef}
|
||||
{...rest}
|
||||
scoped={this.context}
|
||||
dispatchEvent={this.triggerEvent}
|
||||
/>
|
||||
);
|
||||
|
@ -8,7 +8,15 @@ import find from 'lodash/find';
|
||||
import hoistNonReactStatic from 'hoist-non-react-statics';
|
||||
import {dataMapping} from './utils/tpl-builtin';
|
||||
import {RendererEnv, RendererProps} from './factory';
|
||||
import {noop, autobind, qsstringify, qsparse} from './utils/helper';
|
||||
import {
|
||||
noop,
|
||||
autobind,
|
||||
qsstringify,
|
||||
qsparse,
|
||||
createObject,
|
||||
findTree,
|
||||
TreeItem
|
||||
} from './utils/helper';
|
||||
import {RendererData, Action} from './types';
|
||||
|
||||
export interface ScopedComponentType extends React.Component<RendererProps> {
|
||||
@ -29,9 +37,11 @@ export interface ScopedComponentType extends React.Component<RendererProps> {
|
||||
|
||||
export interface IScopedContext {
|
||||
parent?: AliasIScopedContext;
|
||||
children?: AliasIScopedContext[];
|
||||
registerComponent: (component: ScopedComponentType) => void;
|
||||
unRegisterComponent: (component: ScopedComponentType) => void;
|
||||
getComponentByName: (name: string) => ScopedComponentType;
|
||||
getComponentById: (id: string) => ScopedComponentType | undefined;
|
||||
getComponents: () => Array<ScopedComponentType>;
|
||||
reload: (target: string, ctx: RendererData) => void;
|
||||
send: (target: string, ctx: RendererData) => void;
|
||||
@ -46,8 +56,7 @@ function createScopedTools(
|
||||
env?: RendererEnv
|
||||
): IScopedContext {
|
||||
const components: Array<ScopedComponentType> = [];
|
||||
|
||||
return {
|
||||
const self = {
|
||||
parent,
|
||||
registerComponent(component: ScopedComponentType) {
|
||||
// 不要把自己注册在自己的 Scoped 上,自己的 Scoped 是给子节点们注册的。
|
||||
@ -80,7 +89,7 @@ function createScopedTools(
|
||||
|
||||
return paths.reduce((scope, name, idx) => {
|
||||
if (scope && scope.getComponentByName) {
|
||||
const result = scope.getComponentByName(name);
|
||||
const result: ScopedComponentType = scope.getComponentByName(name);
|
||||
return result && idx < len - 1 ? result.context : result;
|
||||
}
|
||||
|
||||
@ -96,6 +105,27 @@ function createScopedTools(
|
||||
return resolved || (parent && parent.getComponentByName(name));
|
||||
},
|
||||
|
||||
getComponentById(id: string) {
|
||||
let root: AliasIScopedContext = this;
|
||||
// 找到顶端scoped
|
||||
while (root.parent) {
|
||||
root = root.parent;
|
||||
}
|
||||
|
||||
// 向下查找
|
||||
let component = undefined;
|
||||
findTree([root], (item: TreeItem) =>
|
||||
item.getComponents().find((cmpt: ScopedComponentType) => {
|
||||
if (cmpt.props.id === id) {
|
||||
component = cmpt;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
) as ScopedComponentType | undefined;
|
||||
return component;
|
||||
},
|
||||
|
||||
getComponents() {
|
||||
return components.concat();
|
||||
},
|
||||
@ -208,6 +238,17 @@ function createScopedTools(
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!parent) {
|
||||
return self;
|
||||
}
|
||||
|
||||
!parent.children && (parent.children = []);
|
||||
|
||||
// 把孩子带上
|
||||
parent.children!.push(self);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function closeDialog(component: ScopedComponentType) {
|
||||
@ -257,6 +298,7 @@ export function HocScoped<
|
||||
context,
|
||||
this.props.env
|
||||
);
|
||||
|
||||
const scopeRef = props.scopeRef;
|
||||
scopeRef && scopeRef(this.scoped);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ export interface ListenerAction {
|
||||
actionType: 'broadcast' | LogicActionType | 'custom' | string; // 动作类型 逻辑动作|自定义(脚本支撑)|reload|url|ajax|dialog|drawer 其他扩充的组件动作
|
||||
eventName?: string; // 事件名称,actionType: broadcast
|
||||
description?: string; // 事件描述,actionType: broadcast
|
||||
componentId?: string; // 组件ID,用于直接执行指定组件的动作
|
||||
args?: any; // 参数,可以配置数据映射
|
||||
preventDefault?: boolean; // 阻止原有组件的动作行为
|
||||
stopPropagation?: boolean; // 阻止后续的事件处理器执行
|
||||
@ -70,7 +71,19 @@ export const runActions = async (
|
||||
for (const actionConfig of actions) {
|
||||
let actionInstrance = getActionByType(actionConfig.actionType);
|
||||
|
||||
// 找不到就通过组件动作完成
|
||||
// 如果存在指定组件ID,说明是组件专有动作
|
||||
if (actionConfig.componentId) {
|
||||
actionInstrance = getActionByType('component');
|
||||
} else if (
|
||||
actionConfig.actionType === 'url' ||
|
||||
actionConfig.actionType === 'link' ||
|
||||
actionConfig.actionType === 'jump'
|
||||
) {
|
||||
// 打开页面动作
|
||||
actionInstrance = getActionByType('openpage');
|
||||
}
|
||||
|
||||
// 找不到就通过组件专有动作完成
|
||||
if (!actionInstrance) {
|
||||
actionInstrance = getActionByType('component');
|
||||
}
|
||||
|
58
src/actions/AjaxAction.ts
Normal file
58
src/actions/AjaxAction.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import {IRootStore} from '../store/root';
|
||||
import {isVisible} from '../utils/helper';
|
||||
import {RendererEvent} from '../utils/renderer-event';
|
||||
import {filter} from '../utils/tpl';
|
||||
import {
|
||||
Action,
|
||||
ListenerAction,
|
||||
ListenerContext,
|
||||
registerAction
|
||||
} from './Action';
|
||||
|
||||
/**
|
||||
* 发送请求动作
|
||||
*
|
||||
* @export
|
||||
* @class AjaxAction
|
||||
* @implements {Action}
|
||||
*/
|
||||
export class AjaxAction implements Action {
|
||||
async run(
|
||||
action: ListenerAction,
|
||||
renderer: ListenerContext,
|
||||
event: RendererEvent<any>
|
||||
) {
|
||||
const store = renderer.props.store;
|
||||
|
||||
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 redirect = action.redirect && filter(action.redirect, store.data);
|
||||
redirect && renderer.env.jumpTo(redirect, action);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerAction('ajax', new AjaxAction());
|
@ -21,8 +21,15 @@ export class CmptAction implements Action {
|
||||
renderer: ListenerContext,
|
||||
event: RendererEvent<any>
|
||||
) {
|
||||
// 根据唯一ID查找指定组件
|
||||
const component =
|
||||
renderer.props.$schema.id !== action.componentId
|
||||
? renderer.props.scoped?.getComponentById(action.componentId)
|
||||
: renderer;
|
||||
|
||||
// 执行组件动作
|
||||
await renderer.doAction?.(action, action.args);
|
||||
(await component.props.onAction?.(event, action, action.args)) ||
|
||||
component.doAction?.(action, action.args);
|
||||
}
|
||||
}
|
||||
|
||||
|
41
src/actions/CopyAction.ts
Normal file
41
src/actions/CopyAction.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import {RendererEvent} from '../utils/renderer-event';
|
||||
import {dataMapping} from '../utils/tpl-builtin';
|
||||
import {filter} from '../utils/tpl';
|
||||
import pick from 'lodash/pick';
|
||||
import mapValues from 'lodash/mapValues';
|
||||
import qs from 'qs';
|
||||
import {
|
||||
Action,
|
||||
ListenerAction,
|
||||
ListenerContext,
|
||||
LoopStatus,
|
||||
registerAction
|
||||
} from './Action';
|
||||
import {isVisible} from '../utils/helper';
|
||||
|
||||
/**
|
||||
* 复制动作
|
||||
*
|
||||
* @export
|
||||
* @class CopyAction
|
||||
* @implements {Action}
|
||||
*/
|
||||
export class CopyAction implements Action {
|
||||
async run(
|
||||
action: ListenerAction,
|
||||
renderer: ListenerContext,
|
||||
event: RendererEvent<any>
|
||||
) {
|
||||
debugger;
|
||||
if (action.content || action.copy) {
|
||||
renderer.props.env.copy?.(
|
||||
filter(action.content || action.copy, action.args, '| raw'),
|
||||
{
|
||||
format: action.copyFormat
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerAction('copy', new CopyAction());
|
28
src/actions/DialogAction.ts
Normal file
28
src/actions/DialogAction.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import {RendererEvent} from '../utils/renderer-event';
|
||||
import {
|
||||
Action,
|
||||
ListenerAction,
|
||||
ListenerContext,
|
||||
registerAction
|
||||
} from './Action';
|
||||
|
||||
/**
|
||||
* 打开弹窗动作
|
||||
*
|
||||
* @export
|
||||
* @class DialogAction
|
||||
* @implements {Action}
|
||||
*/
|
||||
export class DialogAction implements Action {
|
||||
async run(
|
||||
action: ListenerAction,
|
||||
renderer: ListenerContext,
|
||||
event: RendererEvent<any>
|
||||
) {
|
||||
const store = renderer.props.store;
|
||||
store.setCurrentAction(action);
|
||||
store.openDialog(action.args);
|
||||
}
|
||||
}
|
||||
|
||||
registerAction('dialog', new DialogAction());
|
28
src/actions/DrawerAction.ts
Normal file
28
src/actions/DrawerAction.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import {RendererEvent} from '../utils/renderer-event';
|
||||
import {
|
||||
Action,
|
||||
ListenerAction,
|
||||
ListenerContext,
|
||||
registerAction
|
||||
} from './Action';
|
||||
|
||||
/**
|
||||
* 打开抽屉动作
|
||||
*
|
||||
* @export
|
||||
* @class DrawerAction
|
||||
* @implements {Action}
|
||||
*/
|
||||
export class DrawerAction implements Action {
|
||||
async run(
|
||||
action: ListenerAction,
|
||||
renderer: ListenerContext,
|
||||
event: RendererEvent<any>
|
||||
) {
|
||||
const store = renderer.props.store;
|
||||
store.setCurrentAction(action);
|
||||
store.openDrawer(action.args);
|
||||
}
|
||||
}
|
||||
|
||||
registerAction('drawer', new DrawerAction());
|
38
src/actions/EmailAction.ts
Normal file
38
src/actions/EmailAction.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import {RendererEvent} from '../utils/renderer-event';
|
||||
import {filter} from '../utils/tpl';
|
||||
import pick from 'lodash/pick';
|
||||
import mapValues from 'lodash/mapValues';
|
||||
import qs from 'qs';
|
||||
import {
|
||||
Action,
|
||||
ListenerAction,
|
||||
ListenerContext,
|
||||
registerAction
|
||||
} from './Action';
|
||||
|
||||
/**
|
||||
* 邮件动作
|
||||
*
|
||||
* @export
|
||||
* @class EmailAction
|
||||
* @implements {Action}
|
||||
*/
|
||||
export class EmailAction implements Action {
|
||||
async run(
|
||||
action: ListenerAction,
|
||||
renderer: ListenerContext,
|
||||
event: RendererEvent<any>
|
||||
) {
|
||||
const mailTo = filter(action.to, action.args);
|
||||
const mailInfo = mapValues(
|
||||
pick(action, 'to', 'cc', 'bcc', 'subject', 'body'),
|
||||
val => filter(val, action.args)
|
||||
);
|
||||
const mailStr = qs.stringify(mailInfo);
|
||||
const mailto = `mailto:${mailTo}?${mailStr}`;
|
||||
|
||||
window.open(mailto);
|
||||
}
|
||||
}
|
||||
|
||||
registerAction('email', new EmailAction());
|
39
src/actions/OpenPageAction.ts
Normal file
39
src/actions/OpenPageAction.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import {RendererEvent} from '../utils/renderer-event';
|
||||
import {filter} from '../utils/tpl';
|
||||
import {
|
||||
Action,
|
||||
ListenerAction,
|
||||
ListenerContext,
|
||||
registerAction
|
||||
} from './Action';
|
||||
|
||||
/**
|
||||
* 打开页面动作
|
||||
*
|
||||
* @export
|
||||
* @class OpenPageAction
|
||||
* @implements {Action}
|
||||
*/
|
||||
export class OpenPageAction implements Action {
|
||||
async run(
|
||||
action: ListenerAction,
|
||||
renderer: ListenerContext,
|
||||
event: RendererEvent<any>
|
||||
) {
|
||||
if (!renderer.props.env?.jumpTo) {
|
||||
throw new Error('env.jumpTo is required!');
|
||||
}
|
||||
|
||||
renderer.props.env.jumpTo(
|
||||
filter(
|
||||
(action.to || action.url || action.link) as string,
|
||||
action.args,
|
||||
'| raw'
|
||||
),
|
||||
action,
|
||||
action.args
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
registerAction('openpage', new OpenPageAction());
|
@ -10,5 +10,11 @@ import './ParallelAction';
|
||||
import './CustomAction';
|
||||
import './BroadcastAction';
|
||||
import './CmptAction';
|
||||
import './AjaxAction';
|
||||
import './CopyAction';
|
||||
import './DialogAction';
|
||||
import './DrawerAction';
|
||||
import './EmailAction';
|
||||
import './OpenPageAction';
|
||||
|
||||
export * from './Action';
|
||||
|
12
src/env.tsx
12
src/env.tsx
@ -61,7 +61,17 @@ export interface RendererEnv {
|
||||
useMobileUI?: boolean;
|
||||
bindEvent: (context: any) => (() => void) | undefined;
|
||||
dispatchEvent: (
|
||||
e: string | React.MouseEvent<any>,
|
||||
e:
|
||||
| string
|
||||
| React.ClipboardEvent<any>
|
||||
| React.DragEvent<any>
|
||||
| React.ChangeEvent<any>
|
||||
| React.KeyboardEvent<any>
|
||||
| React.TouchEvent<any>
|
||||
| React.WheelEvent<any>
|
||||
| React.AnimationEvent<any>
|
||||
| React.TransitionEvent<any>
|
||||
| React.MouseEvent<any>,
|
||||
context: any,
|
||||
data: any
|
||||
) => Promise<RendererEvent<any> | undefined>;
|
||||
|
Loading…
Reference in New Issue
Block a user