Merge pull request #4395 from lurunze1226/feat-picker-event

feat: Picker组件支持事件动作
This commit is contained in:
hsm-lv 2022-05-23 15:03:55 +08:00 committed by GitHub
commit a2b852c31e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 380 additions and 21 deletions

View File

@ -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}` | 值变化 |

View File

@ -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'
}
]
}
]
}
}
]
}
}
}
]
}
]
};

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -653,7 +653,6 @@ export class ServiceRenderer extends Service {
}
componentWillUnmount() {
super.componentWillUnmount();
const scoped = this.context as IScopedContext;
scoped.unRegisterComponent(this as ScopedComponentType);
}

View File

@ -113,7 +113,8 @@ export interface Action extends Button {
| 'collapse'
| 'step-submit'
| 'selectAll'
| 'changeTabKey';
| 'changeTabKey'
| 'click';
api?: Api;
asyncApi?: Api;
payload?: any;