mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-02 03:58:07 +08:00
feat: 向导组件 事件&动作 配置
This commit is contained in:
parent
3eed053517
commit
521ad84f50
@ -32,6 +32,11 @@ order: 73
|
||||
"label": "邮箱",
|
||||
"type": "input-email",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "ajax",
|
||||
"label": "按钮",
|
||||
"type": "button"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
167
examples/components/EventAction/WizardEvent.jsx
Normal file
167
examples/components/EventAction/WizardEvent.jsx
Normal file
@ -0,0 +1,167 @@
|
||||
const wizardSchema = {
|
||||
type: 'wizard',
|
||||
steps: [
|
||||
{
|
||||
title: '第一步',
|
||||
body: [
|
||||
{
|
||||
name: 'website',
|
||||
label: '网址',
|
||||
type: 'input-url',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
label: '邮箱',
|
||||
type: 'input-email',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
api: '/api/mock2/form/saveForm?waitSeconds=2'
|
||||
},
|
||||
{
|
||||
title: '第二步',
|
||||
body: [
|
||||
{
|
||||
name: 'defaultEmail',
|
||||
type: 'input-email',
|
||||
label: 'Email',
|
||||
value: 'test@test.com'
|
||||
},
|
||||
{
|
||||
type: 'input-text',
|
||||
name: 'name'
|
||||
}
|
||||
],
|
||||
initApi: '/api/mock2/form/initForm',
|
||||
api: '/api/mock2/form/saveForm?waitSeconds=2'
|
||||
},
|
||||
{
|
||||
title: '确定',
|
||||
body: ['最后一步了,确认要提交吗?'],
|
||||
api: '/api/mock2/form/saveForm?waitSeconds=2'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function generateEventAcions(events) {
|
||||
const onEvent = {};
|
||||
events.forEach(event => {
|
||||
onEvent[event] = {
|
||||
actions: [{
|
||||
actionType: 'toast',
|
||||
msgType: 'info',
|
||||
msg: `派发 ${event} 事件`
|
||||
}]
|
||||
}
|
||||
});
|
||||
|
||||
console.log('onEvent', onEvent);
|
||||
|
||||
return onEvent;
|
||||
}
|
||||
|
||||
function generateActions(actions) {
|
||||
return actions.map(action => ({
|
||||
name: `wizard-${action.actionName}`,
|
||||
type: "button",
|
||||
label: action.label,
|
||||
level: actions.level || 'primary',
|
||||
className: 'mr-3 mb-3',
|
||||
onEvent: {
|
||||
click: {
|
||||
actions: [
|
||||
{
|
||||
actionType: action.actionName,
|
||||
componentId: 'wizard-receiver',
|
||||
description: action.label,
|
||||
args: action.actionValue
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
export default {
|
||||
type: 'page',
|
||||
title: '向导组件事件',
|
||||
regions: ['body', 'toolbar', 'header'],
|
||||
body: [
|
||||
// {
|
||||
// name: "wizard-next",
|
||||
// type: "button",
|
||||
// label: '跳转下一步',
|
||||
// level: 'primary',
|
||||
// onEvent: {
|
||||
// click: {
|
||||
// actions: [
|
||||
// {
|
||||
// actionType: 'next',
|
||||
// componentId: 'wizard-receiver',
|
||||
// description: '跳转下一步'
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// name: "wizard-prev",
|
||||
// type: "button",
|
||||
// label: '跳转上一步',
|
||||
// level: 'primary',
|
||||
// onEvent: {
|
||||
// click: {
|
||||
// actions: [
|
||||
// {
|
||||
// actionType: 'prev',
|
||||
// componentId: 'wizard-receiver',
|
||||
// description: '跳转上一步'
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
...generateActions([
|
||||
{
|
||||
actionName: 'prev',
|
||||
label: '跳转上一步'
|
||||
},
|
||||
{
|
||||
actionName: 'next',
|
||||
label: '跳转下一步'
|
||||
},
|
||||
{
|
||||
actionName: 'goto-step',
|
||||
label: '跳转第三步',
|
||||
actionValue: {
|
||||
step: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
actionName: 'step-submit',
|
||||
label: '本步骤提交'
|
||||
},
|
||||
{
|
||||
actionName: 'submit',
|
||||
label: '全部提交'
|
||||
}
|
||||
]),
|
||||
|
||||
{
|
||||
name: 'wizard',
|
||||
id: 'wizard-receiver',
|
||||
...wizardSchema,
|
||||
onEvent: generateEventAcions([
|
||||
'inited',
|
||||
'change',
|
||||
'stepChange',
|
||||
'finished',
|
||||
'submitSucc',
|
||||
'submitFail',
|
||||
'stepSubmitSucc',
|
||||
'stepSubmitFail'
|
||||
])
|
||||
}
|
||||
]
|
||||
};
|
@ -82,6 +82,7 @@ import SelectEventActionSchema from './EventAction/SelectEvent';
|
||||
import ButtonEventActionSchema from './EventAction/ButtonEvent';
|
||||
import InputRatingEventSchema from './EventAction/InputRatingEvent';
|
||||
import ExcelEventSchema from './EventAction/ExcelEvent';
|
||||
import WizardEventSchema from './EventAction/WizardEvent';
|
||||
import WizardSchema from './Wizard';
|
||||
import ChartSchema from './Chart';
|
||||
import EChartsEditorSchema from './ECharts';
|
||||
@ -580,7 +581,12 @@ export const examples = [
|
||||
label: 'excel',
|
||||
path: 'examples/event/excel',
|
||||
component: makeSchemaRenderer(ExcelEventSchema)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '向导组件',
|
||||
path: 'examples/event/wizard',
|
||||
component: makeSchemaRenderer(WizardEventSchema)
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -229,7 +229,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
initFinishedField,
|
||||
store,
|
||||
messages: {fetchSuccess, fetchFailed},
|
||||
onInit
|
||||
onInit // c
|
||||
} = this.props;
|
||||
|
||||
if (isEffectiveApi(initApi, store.data, initFetch)) {
|
||||
@ -253,7 +253,6 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
}
|
||||
})
|
||||
.then(value => {
|
||||
onInit && onInit(store.data);
|
||||
const state = {
|
||||
currentStep:
|
||||
typeof this.props.startStep === 'string'
|
||||
@ -291,7 +290,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
? parseInt(tokenize(this.props.startStep, this.props.data))
|
||||
: 1
|
||||
},
|
||||
() => onInit && onInit(store.data)
|
||||
() => this.handleInitEvent(store.data)
|
||||
);
|
||||
}
|
||||
|
||||
@ -336,6 +335,19 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
this.unSensor && this.unSensor();
|
||||
}
|
||||
|
||||
async dispatchEvent(action: string, value?: object) {
|
||||
const {dispatchEvent, data} = this.props;
|
||||
|
||||
const rendererEvent = await dispatchEvent(action, createObject(data, value ? {value} : {}));
|
||||
|
||||
return rendererEvent?.prevented ?? false;
|
||||
}
|
||||
|
||||
async handleInitEvent(data: any) {
|
||||
const {onInit} = this.props;
|
||||
(await this.dispatchEvent('inited', data)) && onInit && onInit(data);
|
||||
}
|
||||
|
||||
@autobind
|
||||
affixDetect() {
|
||||
if (
|
||||
@ -364,11 +376,15 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
affixed ? affixDom.classList.add('in') : affixDom.classList.remove('in');
|
||||
}
|
||||
|
||||
gotoStep(index: number) {
|
||||
async gotoStep(index: number) {
|
||||
const steps = this.props.steps || [];
|
||||
index = Math.max(Math.min(steps.length, index), 1);
|
||||
|
||||
if (index != this.state.currentStep) {
|
||||
if (await this.dispatchEvent('stepChange')) {
|
||||
return
|
||||
}
|
||||
|
||||
this.setState({
|
||||
currentStep: index,
|
||||
completeStep: Math.max(this.state.completeStep, index - 1)
|
||||
@ -517,9 +533,9 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
throwErrors: boolean = false,
|
||||
delegate?: IScopedContext
|
||||
) {
|
||||
const {onAction, store, env} = this.props;
|
||||
const {onAction, store, env, steps} = this.props;
|
||||
|
||||
if (action.actionType === 'next' || action.type === 'submit') {
|
||||
if (action.actionType === 'next' || action.type === 'submit' || action.actionType === 'step-submit') {
|
||||
this.form.doAction(
|
||||
{
|
||||
...action,
|
||||
@ -572,6 +588,14 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
});
|
||||
} else if (action.actionType === 'reload') {
|
||||
action.target && this.reloadTarget(action.target, data);
|
||||
} else if (action.actionType === 'goto-step') {
|
||||
const targetStep = (data as any).step;
|
||||
|
||||
if (targetStep !== undefined && targetStep <= steps.length && targetStep >= 0) {
|
||||
this.gotoStep((data as any).step);
|
||||
}
|
||||
} else if (action.actionType === 'submit') {
|
||||
this.finalSubmit()
|
||||
} else if (onAction) {
|
||||
onAction(e, action, data, throwErrors, delegate || this.context);
|
||||
}
|
||||
@ -601,9 +625,17 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleChange(values: object) {
|
||||
async handleChange(values: object) {
|
||||
const {store} = this.props;
|
||||
|
||||
const previous = store.data;
|
||||
const final = {...previous, ...values};
|
||||
|
||||
if (await this.dispatchEvent('change', final)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
store.updateData(values);
|
||||
}
|
||||
|
||||
@ -630,9 +662,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
store.updateData(reseted);
|
||||
}
|
||||
|
||||
// 接管里面 form 的提交,不能直接让 form 提交,因为 wizard 自己需要知道进度。
|
||||
@autobind
|
||||
handleSubmit(values: object, action: Action) {
|
||||
async finalSubmit(values: object = {}, action: Action = {type: 'submit'}) {
|
||||
const {
|
||||
store,
|
||||
steps,
|
||||
@ -643,13 +673,121 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
redirect,
|
||||
reload,
|
||||
env,
|
||||
onFinished
|
||||
onFinished // c
|
||||
} = this.props;
|
||||
|
||||
if (await this.dispatchEvent('finished', store.data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const step = steps[this.state.currentStep - 1];
|
||||
store.updateData(values);
|
||||
|
||||
|
||||
// 最后一步
|
||||
if (target) {
|
||||
this.submitToTarget(target, store.data);
|
||||
this.setState({completeStep: steps.length});
|
||||
} else if (action.api || step.api || api) {
|
||||
let finnalAsyncApi = action.asyncApi || step.asyncApi || asyncApi;
|
||||
|
||||
isEffectiveApi(finnalAsyncApi, store.data) &&
|
||||
store.updateData({
|
||||
[finishedField || 'finished']: false
|
||||
});
|
||||
|
||||
const formStore = this.form
|
||||
? (this.form.props.store as IFormStore)
|
||||
: store;
|
||||
store.markSaving(true);
|
||||
|
||||
formStore
|
||||
.saveRemote(action.api || step.api || api!, store.data, {
|
||||
onSuccess: () => {
|
||||
this.dispatchEvent('submitSucc', store.data);
|
||||
|
||||
if (
|
||||
!isEffectiveApi(finnalAsyncApi, store.data) ||
|
||||
store.data[finishedField || 'finished']
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return until(
|
||||
() => store.checkRemote(finnalAsyncApi as Api, store.data),
|
||||
(ret: any) => ret && ret[finishedField || 'finished'],
|
||||
cancel => (this.asyncCancel = cancel)
|
||||
);
|
||||
},
|
||||
onFailed: json => this.dispatchEvent('submitFail', json)
|
||||
})
|
||||
.then(async value => {
|
||||
const feedback = action.feedback;
|
||||
if (feedback && isVisible(feedback, value)) {
|
||||
const confirmed = await this.openFeedback(feedback, value);
|
||||
|
||||
// 如果 feedback 配置了,取消就跳过原有逻辑。
|
||||
if (feedback.skipRestOnCancel && !confirmed) {
|
||||
throw new SkipOperation();
|
||||
} else if (feedback.skipRestOnConfirm && confirmed) {
|
||||
throw new SkipOperation();
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({completeStep: steps.length});
|
||||
store.updateData({
|
||||
...store.data,
|
||||
...value
|
||||
});
|
||||
store.markSaving(false);
|
||||
|
||||
if (value && typeof value.step === 'number') {
|
||||
this.gotoStep(value.step);
|
||||
} else if (onFinished && onFinished(value, action) === false) {
|
||||
// 如果是 false 后面的操作就不执行
|
||||
return value;
|
||||
}
|
||||
|
||||
const finalRedirect =
|
||||
(action.redirect || step.redirect || redirect) &&
|
||||
filter(action.redirect || step.redirect || redirect, store.data);
|
||||
|
||||
if (finalRedirect) {
|
||||
env.jumpTo(finalRedirect, action);
|
||||
} else if (action.reload || step.reload || reload) {
|
||||
this.reloadTarget(
|
||||
action.reload || step.reload || reload!,
|
||||
store.data
|
||||
);
|
||||
}
|
||||
|
||||
return value;
|
||||
})
|
||||
.catch(error => {
|
||||
this.dispatchEvent('submitFail', error)
|
||||
store.markSaving(false);
|
||||
console.error(error);
|
||||
});
|
||||
} else {
|
||||
onFinished && onFinished(store.data, action);
|
||||
this.setState({completeStep: steps.length});
|
||||
}
|
||||
}
|
||||
|
||||
// 接管里面 form 的提交,不能直接让 form 提交,因为 wizard 自己需要知道进度。
|
||||
@autobind
|
||||
async handleSubmit(values: object, action: Action) {
|
||||
const {
|
||||
store,
|
||||
steps,
|
||||
finishedField
|
||||
} = this.props;
|
||||
|
||||
|
||||
if (this.state.currentStep < steps.length) {
|
||||
const step = steps[this.state.currentStep - 1];
|
||||
store.updateData(values);
|
||||
|
||||
let finnalAsyncApi = action.asyncApi || step.asyncApi;
|
||||
|
||||
isEffectiveApi(finnalAsyncApi, store.data) &&
|
||||
@ -661,6 +799,8 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
store
|
||||
.saveRemote(action.api || step.api!, store.data, {
|
||||
onSuccess: () => {
|
||||
this.dispatchEvent('stepSubmitSucc', store.data);
|
||||
|
||||
if (
|
||||
!isEffectiveApi(finnalAsyncApi, store.data) ||
|
||||
store.data[finishedField || 'finished']
|
||||
@ -675,6 +815,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
);
|
||||
},
|
||||
onFailed: json => {
|
||||
this.dispatchEvent('stepSubmitFail', json);
|
||||
if (json.status === 422 && json.errors && this.form) {
|
||||
this.form.props.store.setFormItemErrors(json.errors);
|
||||
}
|
||||
@ -700,6 +841,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
);
|
||||
})
|
||||
.catch(reason => {
|
||||
this.dispatchEvent('stepSubmitFail', reason);
|
||||
if (reason instanceof SkipOperation) {
|
||||
return;
|
||||
}
|
||||
@ -709,90 +851,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
this.gotoStep(this.state.currentStep + 1);
|
||||
}
|
||||
} else {
|
||||
// 最后一步
|
||||
if (target) {
|
||||
this.submitToTarget(target, store.data);
|
||||
this.setState({completeStep: steps.length});
|
||||
} else if (action.api || step.api || api) {
|
||||
let finnalAsyncApi = action.asyncApi || step.asyncApi || asyncApi;
|
||||
|
||||
isEffectiveApi(finnalAsyncApi, store.data) &&
|
||||
store.updateData({
|
||||
[finishedField || 'finished']: false
|
||||
});
|
||||
|
||||
const formStore = this.form
|
||||
? (this.form.props.store as IFormStore)
|
||||
: store;
|
||||
store.markSaving(true);
|
||||
|
||||
formStore
|
||||
.saveRemote(action.api || step.api || api!, store.data, {
|
||||
onSuccess: () => {
|
||||
if (
|
||||
!isEffectiveApi(finnalAsyncApi, store.data) ||
|
||||
store.data[finishedField || 'finished']
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return until(
|
||||
() => store.checkRemote(finnalAsyncApi as Api, store.data),
|
||||
(ret: any) => ret && ret[finishedField || 'finished'],
|
||||
cancel => (this.asyncCancel = cancel)
|
||||
);
|
||||
}
|
||||
})
|
||||
.then(async value => {
|
||||
const feedback = action.feedback;
|
||||
if (feedback && isVisible(feedback, value)) {
|
||||
const confirmed = await this.openFeedback(feedback, value);
|
||||
|
||||
// 如果 feedback 配置了,取消就跳过原有逻辑。
|
||||
if (feedback.skipRestOnCancel && !confirmed) {
|
||||
throw new SkipOperation();
|
||||
} else if (feedback.skipRestOnConfirm && confirmed) {
|
||||
throw new SkipOperation();
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({completeStep: steps.length});
|
||||
store.updateData({
|
||||
...store.data,
|
||||
...value
|
||||
});
|
||||
store.markSaving(false);
|
||||
|
||||
if (value && typeof value.step === 'number') {
|
||||
this.gotoStep(value.step);
|
||||
} else if (onFinished && onFinished(value, action) === false) {
|
||||
// 如果是 false 后面的操作就不执行
|
||||
return value;
|
||||
}
|
||||
|
||||
const finalRedirect =
|
||||
(action.redirect || step.redirect || redirect) &&
|
||||
filter(action.redirect || step.redirect || redirect, store.data);
|
||||
|
||||
if (finalRedirect) {
|
||||
env.jumpTo(finalRedirect, action);
|
||||
} else if (action.reload || step.reload || reload) {
|
||||
this.reloadTarget(
|
||||
action.reload || step.reload || reload!,
|
||||
store.data
|
||||
);
|
||||
}
|
||||
|
||||
return value;
|
||||
})
|
||||
.catch(e => {
|
||||
store.markSaving(false);
|
||||
console.error(e);
|
||||
});
|
||||
} else {
|
||||
onFinished && onFinished(store.data, action);
|
||||
this.setState({completeStep: steps.length});
|
||||
}
|
||||
this.finalSubmit(values, action);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -102,7 +102,9 @@ export interface Action extends Button {
|
||||
| 'reset-and-submit'
|
||||
| 'clear'
|
||||
| 'clear-and-submit'
|
||||
| 'toast';
|
||||
| 'toast'
|
||||
| 'goto-step'
|
||||
| 'step-submit';
|
||||
api?: Api;
|
||||
asyncApi?: Api;
|
||||
payload?: any;
|
||||
|
Loading…
Reference in New Issue
Block a user