mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-02 03:48:13 +08:00
Merge pull request #4395 from lurunze1226/feat-picker-event
feat: Picker组件支持事件动作
This commit is contained in:
commit
a2b852c31e
@ -510,3 +510,9 @@ order: 35
|
||||
| modalMode | `string` | `"dialog"` | 设置 `dialog` 或者 `drawer`,用来配置弹出方式。 |
|
||||
| pickerSchema | `string` | `{mode: 'list', listItem: {title: '${label}'}}` | 即用 List 类型的渲染,来展示列表信息。更多配置参考 [CRUD](../crud) |
|
||||
| embed | `boolean` | `false` | 是否使用内嵌模式 |
|
||||
|
||||
## 事件表
|
||||
|
||||
| 事件名称 | 事件参数 | 说明 |
|
||||
| -------- | --------------------------------- | ------ |
|
||||
| change | `{value: string; option: Option}` | 值变化 |
|
||||
|
@ -174,6 +174,305 @@ export default {
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'alert',
|
||||
body: 'Picker选择后将数据回填到form的其他组件中',
|
||||
level: 'info',
|
||||
className: 'mt-2 mb-1'
|
||||
},
|
||||
{
|
||||
type: 'form',
|
||||
api: '/api/mock2/form/saveForm',
|
||||
debug: true,
|
||||
body: [
|
||||
{
|
||||
type: 'picker',
|
||||
name: 'picker',
|
||||
id: 'picker',
|
||||
joinValues: true,
|
||||
valueField: 'id',
|
||||
labelField: 'id',
|
||||
label: '多选',
|
||||
source: '/api/mock2/sample',
|
||||
size: 'lg',
|
||||
multiple: false,
|
||||
description: '选择一项内容,自动填入Form其他组件,并触发按钮动作',
|
||||
pickerSchema: {
|
||||
mode: 'table',
|
||||
name: 'thelist',
|
||||
quickSaveApi: '/api/mock2/sample/bulkUpdate',
|
||||
quickSaveItemApi: '/api/mock2/sample/$id',
|
||||
draggable: true,
|
||||
headerToolbar: {
|
||||
wrapWithPanel: false,
|
||||
type: 'form',
|
||||
className: 'text-right',
|
||||
target: 'thelist',
|
||||
mode: 'inline',
|
||||
body: [
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'keywords',
|
||||
addOn: {
|
||||
type: 'submit',
|
||||
label: '搜索',
|
||||
level: 'primary',
|
||||
icon: 'fa fa-search pull-left'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
name: 'engine',
|
||||
label: 'Rendering engine',
|
||||
sortable: true,
|
||||
searchable: true,
|
||||
type: 'text',
|
||||
toggled: true
|
||||
},
|
||||
{
|
||||
name: 'browser',
|
||||
label: 'Browser',
|
||||
sortable: true,
|
||||
type: 'text',
|
||||
toggled: true
|
||||
},
|
||||
{
|
||||
name: 'platform',
|
||||
label: 'Platform(s)',
|
||||
sortable: true,
|
||||
type: 'text',
|
||||
toggled: true
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
label: 'Engine version',
|
||||
quickEdit: true,
|
||||
type: 'text',
|
||||
toggled: true
|
||||
},
|
||||
{
|
||||
name: 'grade',
|
||||
label: 'CSS grade',
|
||||
quickEdit: {
|
||||
mode: 'inline',
|
||||
type: 'select',
|
||||
options: ['A', 'B', 'C', 'D', 'X'],
|
||||
saveImmediately: true
|
||||
},
|
||||
type: 'text',
|
||||
toggled: true
|
||||
},
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 100,
|
||||
buttons: [
|
||||
{
|
||||
type: 'button',
|
||||
icon: 'fa fa-eye',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
title: '查看',
|
||||
body: {
|
||||
type: 'form',
|
||||
body: [
|
||||
{
|
||||
type: 'static',
|
||||
name: 'engine',
|
||||
label: 'Engine'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'static',
|
||||
name: 'browser',
|
||||
label: 'Browser'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'static',
|
||||
name: 'platform',
|
||||
label: 'Platform(s)'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'static',
|
||||
name: 'version',
|
||||
label: 'Engine version'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'static',
|
||||
name: 'grade',
|
||||
label: 'CSS grade'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'html',
|
||||
html: '<p>添加其他 <span>Html 片段</span> 需要支持变量替换(todo).</p>'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
icon: 'fa fa-pencil',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
position: 'left',
|
||||
size: 'lg',
|
||||
title: '编辑',
|
||||
body: {
|
||||
type: 'form',
|
||||
name: 'sample-edit-form',
|
||||
api: '/api/mock2/sample/$id',
|
||||
body: [
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'engine',
|
||||
label: 'Engine',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'browser',
|
||||
label: 'Browser',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'platform',
|
||||
label: 'Platform(s)',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'version',
|
||||
label: 'Engine version'
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'grade',
|
||||
label: 'CSS grade',
|
||||
options: ['A', 'B', 'C', 'D', 'X']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
icon: 'fa fa-times text-danger',
|
||||
actionType: 'ajax',
|
||||
confirmText: '您确认要删除?',
|
||||
api: 'delete:/api/mock2/sample/$id'
|
||||
}
|
||||
],
|
||||
toggled: true
|
||||
}
|
||||
]
|
||||
},
|
||||
onEvent: {
|
||||
change: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'setValue',
|
||||
componentId: 'id',
|
||||
args: {
|
||||
value: '${event.data.value}'
|
||||
}
|
||||
},
|
||||
{
|
||||
actionType: 'setValue',
|
||||
componentId: 'platform',
|
||||
args: {
|
||||
value: '${event.data.option.platform}'
|
||||
}
|
||||
},
|
||||
{
|
||||
actionType: 'click',
|
||||
componentId: 'dialog-action',
|
||||
args: {
|
||||
browser: '${event.data.option.browser}'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'id',
|
||||
name: 'id',
|
||||
id: 'id',
|
||||
type: 'input-text'
|
||||
},
|
||||
{
|
||||
label: 'platform',
|
||||
name: 'platform',
|
||||
id: 'platform',
|
||||
type: 'input-text'
|
||||
},
|
||||
{
|
||||
type: 'action',
|
||||
id: 'dialog-action',
|
||||
label: '自动触发的弹窗',
|
||||
level: 'primary',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
type: 'dialog',
|
||||
title: 'browser',
|
||||
id: 'browser-dialog',
|
||||
body: [
|
||||
{
|
||||
type: 'form',
|
||||
debug: true,
|
||||
id: 'browser-form',
|
||||
body: [
|
||||
{
|
||||
type: 'input-text',
|
||||
id: 'browser',
|
||||
name: 'browser',
|
||||
label: 'browser'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -1,10 +1,15 @@
|
||||
import React from 'react';
|
||||
import hotkeys from 'hotkeys-js';
|
||||
import omit from 'lodash/omit';
|
||||
import pick from 'lodash/pick';
|
||||
import {Renderer, RendererProps} from '../factory';
|
||||
import {filter} from '../utils/tpl';
|
||||
import Button from '../components/Button';
|
||||
import pick from 'lodash/pick';
|
||||
import omit from 'lodash/omit';
|
||||
import {ScopedContext} from '../Scoped';
|
||||
import {isObject} from '../utils/helper';
|
||||
|
||||
import type {IScopedContext} from '../Scoped';
|
||||
import type {Action as ICmptAction} from '../types';
|
||||
|
||||
export interface ButtonSchema extends BaseSchema {
|
||||
/**
|
||||
@ -431,7 +436,6 @@ import {generateIcon} from '../utils/icon';
|
||||
import {BadgeSchema, withBadge} from '../components/Badge';
|
||||
import {normalizeApi, str2AsyncFunction} from '../utils/api';
|
||||
import {TooltipWrapper} from '../components/TooltipWrapper';
|
||||
import handleAction from '../utils/handleAction';
|
||||
|
||||
// 构造一个假的 React 事件避免可能的报错,主要用于快捷键功能
|
||||
// 来自 https://stackoverflow.com/questions/27062455/reactjs-can-i-create-my-own-syntheticevent
|
||||
@ -851,28 +855,68 @@ export class Action extends React.Component<ActionProps, ActionState> {
|
||||
|
||||
export default themeable(Action);
|
||||
|
||||
export type ActionRendererProps = RendererProps &
|
||||
Omit<ActionProps, 'onAction' | 'isCurrentUrl' | 'tooltipContainer'> & {
|
||||
onAction: (
|
||||
e: React.MouseEvent<any> | string | void | null,
|
||||
action: object,
|
||||
data: any
|
||||
) => void;
|
||||
btnDisabled?: boolean;
|
||||
};
|
||||
|
||||
@Renderer({
|
||||
type: 'action'
|
||||
})
|
||||
// @ts-ignore 类型没搞定
|
||||
@withBadge
|
||||
export class ActionRenderer extends React.Component<
|
||||
RendererProps &
|
||||
Omit<ActionProps, 'onAction' | 'isCurrentUrl' | 'tooltipContainer'> & {
|
||||
onAction: (
|
||||
e: React.MouseEvent<any> | void | null,
|
||||
action: object,
|
||||
data: any
|
||||
) => void;
|
||||
btnDisabled?: boolean;
|
||||
export class ActionRenderer extends React.Component<ActionRendererProps> {
|
||||
static contextType = ScopedContext;
|
||||
|
||||
constructor(props: ActionRendererProps, scoped: IScopedContext) {
|
||||
super(props);
|
||||
|
||||
scoped.registerComponent(this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const scoped = this.context as IScopedContext;
|
||||
scoped.unRegisterComponent(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 动作处理
|
||||
*/
|
||||
doAction(
|
||||
action: ICmptAction,
|
||||
args: {
|
||||
value?: string | {[key: string]: string};
|
||||
}
|
||||
> {
|
||||
) {
|
||||
const actionType = action?.actionType;
|
||||
|
||||
if (actionType === 'click') {
|
||||
this.handleAction(actionType, action);
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
async handleAction(e: React.MouseEvent<any> | void | null, action: any) {
|
||||
async handleAction(
|
||||
e: React.MouseEvent<any> | string | void | null,
|
||||
action: any
|
||||
) {
|
||||
const {env, onAction, data, ignoreConfirm, dispatchEvent} = this.props;
|
||||
let mergedData = data;
|
||||
|
||||
if (action?.actionType === 'click' && isObject(action?.args)) {
|
||||
mergedData = createObject(data, action.args);
|
||||
}
|
||||
|
||||
// 触发渲染器事件
|
||||
const rendererEvent = await dispatchEvent(e as React.MouseEvent<any>, data);
|
||||
const rendererEvent = await dispatchEvent(
|
||||
e as React.MouseEvent<any> | string,
|
||||
mergedData
|
||||
);
|
||||
|
||||
// 阻止原有动作执行
|
||||
if (rendererEvent?.prevented) {
|
||||
@ -880,14 +924,14 @@ export class ActionRenderer extends React.Component<
|
||||
}
|
||||
|
||||
if (!ignoreConfirm && action.confirmText && env.confirm) {
|
||||
let confirmed = await env.confirm(filter(action.confirmText, data));
|
||||
let confirmed = await env.confirm(filter(action.confirmText, mergedData));
|
||||
if (confirmed) {
|
||||
await onAction(e, action, data);
|
||||
await onAction(e, action, mergedData);
|
||||
} else if (action.countDown) {
|
||||
throw new Error('cancel');
|
||||
}
|
||||
} else {
|
||||
await onAction(e, action, data);
|
||||
await onAction(e, action, mergedData);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,7 +247,7 @@ export default class PickerControl extends React.PureComponent<
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleChange(items: Array<any>) {
|
||||
async handleChange(items: Array<any>) {
|
||||
const {
|
||||
joinValues,
|
||||
valueField,
|
||||
@ -255,6 +255,8 @@ export default class PickerControl extends React.PureComponent<
|
||||
extractValue,
|
||||
multiple,
|
||||
options,
|
||||
data,
|
||||
dispatchEvent,
|
||||
setOptions,
|
||||
onChange
|
||||
} = this.props;
|
||||
@ -286,6 +288,14 @@ export default class PickerControl extends React.PureComponent<
|
||||
});
|
||||
|
||||
additionalOptions.length && setOptions(options.concat(additionalOptions));
|
||||
const rendererEvent = await dispatchEvent(
|
||||
'change',
|
||||
createObject(data, {value, option: items[0]})
|
||||
);
|
||||
if (rendererEvent?.prevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(value);
|
||||
}
|
||||
|
||||
|
@ -653,7 +653,6 @@ export class ServiceRenderer extends Service {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
const scoped = this.context as IScopedContext;
|
||||
scoped.unRegisterComponent(this as ScopedComponentType);
|
||||
}
|
||||
|
@ -113,7 +113,8 @@ export interface Action extends Button {
|
||||
| 'collapse'
|
||||
| 'step-submit'
|
||||
| 'selectAll'
|
||||
| 'changeTabKey';
|
||||
| 'changeTabKey'
|
||||
| 'click';
|
||||
api?: Api;
|
||||
asyncApi?: Api;
|
||||
payload?: any;
|
||||
|
Loading…
Reference in New Issue
Block a user