mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
feat: improve openStepSettingsDialog
This commit is contained in:
parent
7561db0fa0
commit
3b39df9147
@ -278,6 +278,7 @@ export class Application {
|
|||||||
api: this.apiClient,
|
api: this.apiClient,
|
||||||
i18n: this.i18n,
|
i18n: this.i18n,
|
||||||
router: this.router.router,
|
router: this.router.router,
|
||||||
|
flowEngine: this.flowEngine,
|
||||||
});
|
});
|
||||||
this.use(FlowEngineProvider, { engine: this.flowEngine });
|
this.use(FlowEngineProvider, { engine: this.flowEngine });
|
||||||
this.use(FlowEngineGlobalsContextProvider);
|
this.use(FlowEngineGlobalsContextProvider);
|
||||||
|
@ -7,13 +7,15 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createSchemaField, ISchema } from '@formily/react';
|
import { FormButtonGroup } from '@formily/antd-v5';
|
||||||
import { message } from 'antd';
|
import { createForm } from '@formily/core';
|
||||||
|
import { createSchemaField, FormProvider, ISchema } from '@formily/react';
|
||||||
|
import { toJS } from '@formily/reactive';
|
||||||
|
import { Button, message, Space } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StepSettingsDialogProps } from '../../../../types';
|
import { StepSettingsDialogProps } from '../../../../types';
|
||||||
import { resolveDefaultParams, resolveUiSchema, compileUiSchema, getT } from '../../../../utils';
|
import { compileUiSchema, getT, resolveDefaultParams, resolveUiSchema } from '../../../../utils';
|
||||||
import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext';
|
import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext';
|
||||||
import { toJS } from '@formily/reactive';
|
|
||||||
|
|
||||||
const SchemaField = createSchemaField();
|
const SchemaField = createSchemaField();
|
||||||
|
|
||||||
@ -32,8 +34,10 @@ const openStepSettingsDialog = async ({
|
|||||||
stepKey,
|
stepKey,
|
||||||
dialogWidth = 600,
|
dialogWidth = 600,
|
||||||
dialogTitle,
|
dialogTitle,
|
||||||
|
mode = 'dialog',
|
||||||
}: StepSettingsDialogProps): Promise<any> => {
|
}: StepSettingsDialogProps): Promise<any> => {
|
||||||
const t = getT(model);
|
const t = getT(model);
|
||||||
|
const message = model.flowEngine.context.message;
|
||||||
|
|
||||||
if (!model) {
|
if (!model) {
|
||||||
message.error(t('Invalid model provided'));
|
message.error(t('Invalid model provided'));
|
||||||
@ -127,25 +131,47 @@ const openStepSettingsDialog = async ({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 动态导入FormDialog
|
const view = model.flowEngine.context[mode];
|
||||||
let FormDialog;
|
|
||||||
try {
|
|
||||||
({ FormDialog } = await import('@formily/antd-v5'));
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`${t('Failed to import FormDialog')}: ${error.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建FormDialog
|
const form = createForm({
|
||||||
const formDialog = FormDialog(
|
initialValues: compileUiSchema(scopes, initialValues),
|
||||||
{
|
});
|
||||||
title: dialogTitle || `${t(title)} - ${t('Configuration')}`,
|
|
||||||
width: dialogWidth,
|
const currentDialog = view.open({
|
||||||
okText: t('OK'),
|
title: dialogTitle || t(title),
|
||||||
cancelText: t('Cancel'),
|
width: dialogWidth,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
},
|
footer: (
|
||||||
(form) => {
|
<Space align="end">
|
||||||
// 创建上下文值
|
<Button
|
||||||
|
type="default"
|
||||||
|
onClick={() => {
|
||||||
|
currentDialog.close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
await form.submit();
|
||||||
|
const currentValues = form.values;
|
||||||
|
model.setStepParams(flowKey, stepKey, currentValues);
|
||||||
|
await model.save();
|
||||||
|
message.success(t('Configuration saved'));
|
||||||
|
currentDialog.close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(t('Error saving configuration'), ':', error);
|
||||||
|
message.error(t('Error saving configuration, please check console'));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('OK')}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
content: (currentDialog) => {
|
||||||
const contextValue: StepSettingContextType = {
|
const contextValue: StepSettingContextType = {
|
||||||
model,
|
model,
|
||||||
globals: model.flowEngine?.context || {},
|
globals: model.flowEngine?.context || {},
|
||||||
@ -155,45 +181,22 @@ const openStepSettingsDialog = async ({
|
|||||||
flowKey,
|
flowKey,
|
||||||
stepKey,
|
stepKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 编译 formSchema 中的表达式
|
// 编译 formSchema 中的表达式
|
||||||
const compiledFormSchema = compileUiSchema(scopes, formSchema);
|
const compiledFormSchema = compileUiSchema(scopes, formSchema);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StepSettingContextProvider value={contextValue}>
|
<FormProvider form={form}>
|
||||||
<SchemaField
|
<StepSettingContextProvider value={contextValue}>
|
||||||
schema={compiledFormSchema}
|
<SchemaField
|
||||||
components={{
|
schema={compiledFormSchema}
|
||||||
...flowEngine.flowSettings?.components,
|
components={{
|
||||||
}}
|
...flowEngine.flowSettings?.components,
|
||||||
scope={scopes}
|
}}
|
||||||
/>
|
scope={scopes}
|
||||||
</StepSettingContextProvider>
|
/>
|
||||||
|
</StepSettingContextProvider>
|
||||||
|
</FormProvider>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
|
||||||
|
|
||||||
// 设置保存回调
|
|
||||||
formDialog.forConfirm(async (payload, next) => {
|
|
||||||
try {
|
|
||||||
// 获取表单当前值
|
|
||||||
const currentValues = payload.values;
|
|
||||||
model.setStepParams(flowKey, stepKey, currentValues);
|
|
||||||
await model.save();
|
|
||||||
message.success(t('Configuration saved'));
|
|
||||||
next(payload);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(t('Error saving configuration'), ':', error);
|
|
||||||
message.error(t('Error saving configuration, please check console'));
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
formDialog.forCancel(async (payload, next) => next(payload));
|
|
||||||
|
|
||||||
// 打开对话框
|
|
||||||
return formDialog.open({
|
|
||||||
initialValues: compileUiSchema(scopes, initialValues),
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,17 +6,8 @@
|
|||||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createSchemaField, ISchema } from '@formily/react';
|
|
||||||
import { message, Button, Space } from 'antd';
|
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { StepSettingsDrawerProps } from '../../../../types';
|
import { StepSettingsDrawerProps } from '../../../../types';
|
||||||
import { resolveDefaultParams, resolveUiSchema, compileUiSchema, getT } from '../../../../utils';
|
import { openStepSettingsDialog } from './StepSettingsDialog';
|
||||||
import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext';
|
|
||||||
import { toJS } from '@formily/reactive';
|
|
||||||
|
|
||||||
const SchemaField = createSchemaField();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StepSettingsDrawer组件 - 使用自定义 Drawer 显示单个步骤的配置界面
|
* StepSettingsDrawer组件 - 使用自定义 Drawer 显示单个步骤的配置界面
|
||||||
@ -27,230 +18,8 @@ const SchemaField = createSchemaField();
|
|||||||
* @param props.drawerTitle 自定义抽屉标题,默认使用step的title
|
* @param props.drawerTitle 自定义抽屉标题,默认使用step的title
|
||||||
* @returns Promise<any> 返回表单提交的值
|
* @returns Promise<any> 返回表单提交的值
|
||||||
*/
|
*/
|
||||||
const openStepSettingsDrawer = async ({
|
const openStepSettingsDrawer = async (options: StepSettingsDrawerProps): Promise<any> => {
|
||||||
model,
|
return openStepSettingsDialog({ ...options, mode: 'drawer' });
|
||||||
flowKey,
|
|
||||||
stepKey,
|
|
||||||
drawerWidth = 600,
|
|
||||||
drawerTitle,
|
|
||||||
}: StepSettingsDrawerProps): Promise<any> => {
|
|
||||||
const t = getT(model);
|
|
||||||
|
|
||||||
if (!model) {
|
|
||||||
message.error(t('Invalid model provided'));
|
|
||||||
throw new Error(t('Invalid model provided'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取流程和步骤信息
|
|
||||||
const flow = model.getFlow(flowKey);
|
|
||||||
const step = flow?.steps?.[stepKey];
|
|
||||||
|
|
||||||
if (!flow) {
|
|
||||||
message.error(t('Flow with key {{flowKey}} not found', { flowKey }));
|
|
||||||
throw new Error(t('Flow with key {{flowKey}} not found', { flowKey }));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!step) {
|
|
||||||
message.error(t('Step with key {{stepKey}} not found', { stepKey }));
|
|
||||||
throw new Error(t('Step with key {{stepKey}} not found', { stepKey }));
|
|
||||||
}
|
|
||||||
|
|
||||||
let title = step.title;
|
|
||||||
|
|
||||||
// 创建参数解析上下文
|
|
||||||
const paramsContext = {
|
|
||||||
model,
|
|
||||||
globals: model.flowEngine?.context || {},
|
|
||||||
app: model.flowEngine?.context?.app,
|
|
||||||
};
|
|
||||||
|
|
||||||
const stepUiSchema = step.uiSchema || {};
|
|
||||||
let actionDefaultParams = {};
|
|
||||||
|
|
||||||
// 如果step使用了action,也获取action的uiSchema
|
|
||||||
let actionUiSchema = {};
|
|
||||||
if (step.use) {
|
|
||||||
const action = model.flowEngine?.getAction?.(step.use);
|
|
||||||
if (action && action.uiSchema) {
|
|
||||||
actionUiSchema = action.uiSchema;
|
|
||||||
}
|
|
||||||
actionDefaultParams = action.defaultParams || {};
|
|
||||||
title = title || action.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析动态 uiSchema
|
|
||||||
const resolvedActionUiSchema = await resolveUiSchema(actionUiSchema, paramsContext);
|
|
||||||
const resolvedStepUiSchema = await resolveUiSchema(stepUiSchema, paramsContext);
|
|
||||||
|
|
||||||
// 合并uiSchema,确保step的uiSchema优先级更高
|
|
||||||
const mergedUiSchema = { ...toJS(resolvedActionUiSchema) };
|
|
||||||
Object.entries(toJS(resolvedStepUiSchema)).forEach(([fieldKey, schema]) => {
|
|
||||||
if (mergedUiSchema[fieldKey]) {
|
|
||||||
mergedUiSchema[fieldKey] = { ...mergedUiSchema[fieldKey], ...schema };
|
|
||||||
} else {
|
|
||||||
mergedUiSchema[fieldKey] = schema;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 如果没有可配置的UI Schema,显示提示
|
|
||||||
if (Object.keys(mergedUiSchema).length === 0) {
|
|
||||||
message.info(t('This step has no configurable parameters'));
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取初始值
|
|
||||||
const stepParams = model.getStepParams(flowKey, stepKey) || {};
|
|
||||||
|
|
||||||
const flowEngine = model.flowEngine;
|
|
||||||
const scopes = {
|
|
||||||
useStepSettingContext,
|
|
||||||
...flowEngine.flowSettings?.scopes,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 解析 defaultParams
|
|
||||||
const resolvedDefaultParams = await resolveDefaultParams(step.defaultParams, paramsContext);
|
|
||||||
const resolveActionDefaultParams = await resolveDefaultParams(actionDefaultParams, paramsContext);
|
|
||||||
const initialValues = { ...toJS(resolveActionDefaultParams), ...toJS(resolvedDefaultParams), ...toJS(stepParams) };
|
|
||||||
|
|
||||||
// 构建表单Schema
|
|
||||||
const formSchema: ISchema = {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
layout: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'FormLayout',
|
|
||||||
'x-component-props': {
|
|
||||||
layout: 'vertical',
|
|
||||||
},
|
|
||||||
properties: mergedUiSchema,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// 动态导入Formily组件
|
|
||||||
let Form, createForm;
|
|
||||||
try {
|
|
||||||
({ Form } = await import('@formily/antd-v5'));
|
|
||||||
({ createForm } = await import('@formily/core'));
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`${t('Failed to import Formily components')}: ${error.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取drawer API
|
|
||||||
const drawer = model.flowEngine?.context?.drawer;
|
|
||||||
if (!drawer) {
|
|
||||||
throw new Error(t('Drawer API is not available, please ensure it is used within FlowEngineGlobalsContextProvider'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
// 用于跟踪Promise状态,避免重复调用resolve
|
|
||||||
let isResolved = false;
|
|
||||||
|
|
||||||
// 创建表单实例
|
|
||||||
const form = createForm({
|
|
||||||
initialValues: compileUiSchema(scopes, initialValues),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 创建抽屉内容组件
|
|
||||||
const DrawerContent: React.FC = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
// 先获取表单当前值,然后验证
|
|
||||||
const currentValues = form.values;
|
|
||||||
await form.validate();
|
|
||||||
|
|
||||||
// 保存配置
|
|
||||||
model.setStepParams(flowKey, stepKey, currentValues);
|
|
||||||
await model.save();
|
|
||||||
|
|
||||||
message.success(t('Configuration saved'));
|
|
||||||
isResolved = true;
|
|
||||||
drawerRef.destroy();
|
|
||||||
resolve(currentValues);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(t('Error saving configuration'), ':', error);
|
|
||||||
message.error(t('Error saving configuration, please check console'));
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
if (!isResolved) {
|
|
||||||
isResolved = true;
|
|
||||||
resolve(null);
|
|
||||||
}
|
|
||||||
drawerRef.destroy();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建上下文值
|
|
||||||
const contextValue: StepSettingContextType = {
|
|
||||||
model,
|
|
||||||
globals: model.flowEngine?.context || {},
|
|
||||||
app: model.flowEngine?.context?.app,
|
|
||||||
step,
|
|
||||||
flow,
|
|
||||||
flowKey,
|
|
||||||
stepKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 编译 formSchema 中的表达式
|
|
||||||
const compiledFormSchema = compileUiSchema(scopes, formSchema);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
||||||
<div style={{ flex: 1, overflow: 'auto', padding: '16px' }}>
|
|
||||||
<Form form={form} layout="vertical">
|
|
||||||
<StepSettingContextProvider value={contextValue}>
|
|
||||||
<SchemaField
|
|
||||||
schema={compiledFormSchema}
|
|
||||||
components={{
|
|
||||||
...flowEngine.flowSettings?.components,
|
|
||||||
}}
|
|
||||||
scope={scopes}
|
|
||||||
/>
|
|
||||||
</StepSettingContextProvider>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: '16px',
|
|
||||||
borderTop: '1px solid #f0f0f0',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
gap: '8px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Space>
|
|
||||||
<Button onClick={handleCancel}>{t('Cancel')}</Button>
|
|
||||||
<Button type="primary" loading={loading} onClick={handleSubmit}>
|
|
||||||
{t('OK')}
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 打开抽屉
|
|
||||||
const drawerRef = drawer.open({
|
|
||||||
title: drawerTitle || `${t(title)} - ${t('Configuration')}`,
|
|
||||||
width: drawerWidth,
|
|
||||||
content: <DrawerContent />,
|
|
||||||
onClose: () => {
|
|
||||||
// 只有在Promise还未被处理时才reject
|
|
||||||
if (!isResolved) {
|
|
||||||
isResolved = true;
|
|
||||||
resolve(null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { openStepSettingsDrawer };
|
export { openStepSettingsDrawer };
|
||||||
|
@ -263,6 +263,7 @@ export interface StepSettingsDialogProps {
|
|||||||
stepKey: string;
|
stepKey: string;
|
||||||
dialogWidth?: number | string;
|
dialogWidth?: number | string;
|
||||||
dialogTitle?: string;
|
dialogTitle?: string;
|
||||||
|
mode?: 'dialog' | 'drawer'; // 设置模式,默认为'dialog'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,6 +34,13 @@ const DrawerComponent = forwardRef<unknown, DrawerComponentProps>(({ afterClose,
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
config.onClose?.(e);
|
config.onClose?.(e);
|
||||||
}}
|
}}
|
||||||
|
styles={{
|
||||||
|
...config?.styles,
|
||||||
|
footer: {
|
||||||
|
textAlign: 'end',
|
||||||
|
...config?.styles?.footer,
|
||||||
|
},
|
||||||
|
}}
|
||||||
afterOpenChange={(open) => {
|
afterOpenChange={(open) => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
afterClose?.();
|
afterClose?.();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user