mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 05:29:26 +08:00
feat(custom-request): support variable in success message of custom request action (#6694)
* feat: support variable in success message of custom request action * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug
This commit is contained in:
parent
d1d8f4bbe7
commit
a9fb7ea56e
@ -227,8 +227,8 @@ export function useCollectValuesToSubmit() {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function interpolateVariables(str: string, scope: Record<string, any>): string {
|
export function interpolateVariables(str: string, scope: Record<string, any>): string {
|
||||||
return str.replace(/\{\{\s*([a-zA-Z0-9_$-.]+?)\s*\}\}/g, (_, key) => {
|
return str.replace(/\{\{\s*([a-zA-Z0-9_$.-]+?)\s*\}\}/g, (_, key) => {
|
||||||
return scope[key] !== undefined ? String(scope[key]) : '';
|
return scope[key] !== undefined ? String(scope[key]) : '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,9 @@ export const useAfterSuccessOptions = () => {
|
|||||||
}, [fieldsOptions, userFieldOptions]);
|
}, [fieldsOptions, userFieldOptions]);
|
||||||
const { settings: popupRecordSettings, shouldDisplayPopupRecord } = usePopupVariable();
|
const { settings: popupRecordSettings, shouldDisplayPopupRecord } = usePopupVariable();
|
||||||
const { currentRoleSettings } = useCurrentRoleVariable();
|
const { currentRoleSettings } = useCurrentRoleVariable();
|
||||||
const record = useCollectionRecordData();
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return [
|
return [
|
||||||
(record || form) && {
|
form && {
|
||||||
value: '$record',
|
value: '$record',
|
||||||
label: t('Response record', { ns: 'client' }),
|
label: t('Response record', { ns: 'client' }),
|
||||||
children: [...fields],
|
children: [...fields],
|
||||||
|
@ -31,3 +31,4 @@ export { default as useParseDataScopeFilter } from './hooks/useParseDataScopeFil
|
|||||||
export * from './isPatternDisabled';
|
export * from './isPatternDisabled';
|
||||||
export { SchemaSettingsPlugin } from './SchemaSettingsPlugin';
|
export { SchemaSettingsPlugin } from './SchemaSettingsPlugin';
|
||||||
export * from './VariableInput';
|
export * from './VariableInput';
|
||||||
|
export { replaceVariables } from './LinkageRules/bindLinkageRulesToFiled';
|
||||||
|
@ -18,6 +18,10 @@ import {
|
|||||||
useNavigateNoUpdate,
|
useNavigateNoUpdate,
|
||||||
useBlockRequestContext,
|
useBlockRequestContext,
|
||||||
useContextVariable,
|
useContextVariable,
|
||||||
|
useLocalVariables,
|
||||||
|
useVariables,
|
||||||
|
replaceVariables,
|
||||||
|
interpolateVariables,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { isURL } from '@nocobase/utils/client';
|
import { isURL } from '@nocobase/utils/client';
|
||||||
import { App } from 'antd';
|
import { App } from 'antd';
|
||||||
@ -38,12 +42,21 @@ export const useCustomizeRequestActionProps = () => {
|
|||||||
const { modal, message } = App.useApp();
|
const { modal, message } = App.useApp();
|
||||||
const dataSourceKey = useDataSourceKey();
|
const dataSourceKey = useDataSourceKey();
|
||||||
const { ctx } = useContextVariable();
|
const { ctx } = useContextVariable();
|
||||||
|
const localVariables = useLocalVariables();
|
||||||
|
const variables = useVariables();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async onClick(e?, callBack?) {
|
async onClick(e?, callBack?) {
|
||||||
const selectedRecord = field.data?.selectedRowData ? field.data?.selectedRowData : ctx;
|
const selectedRecord = field?.data?.selectedRowData ? field?.data?.selectedRowData : ctx;
|
||||||
const { skipValidator, onSuccess } = actionSchema?.['x-action-settings'] ?? {};
|
const { skipValidator, onSuccess } = actionSchema?.['x-action-settings'] ?? {};
|
||||||
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
|
const {
|
||||||
|
manualClose,
|
||||||
|
redirecting,
|
||||||
|
redirectTo,
|
||||||
|
successMessage: rawSuccessMessage,
|
||||||
|
actionAfterSuccess,
|
||||||
|
} = onSuccess || {};
|
||||||
|
let successMessage = rawSuccessMessage;
|
||||||
const xAction = actionSchema?.['x-action'];
|
const xAction = actionSchema?.['x-action'];
|
||||||
if (skipValidator !== true && xAction === 'customize:form:request') {
|
if (skipValidator !== true && xAction === 'customize:form:request') {
|
||||||
await form.submit();
|
await form.submit();
|
||||||
@ -71,6 +84,19 @@ export const useCustomizeRequestActionProps = () => {
|
|||||||
},
|
},
|
||||||
responseType: fieldSchema['x-response-type'] === 'stream' ? 'blob' : 'json',
|
responseType: fieldSchema['x-response-type'] === 'stream' ? 'blob' : 'json',
|
||||||
});
|
});
|
||||||
|
try {
|
||||||
|
const { exp, scope: expScope } = await replaceVariables(successMessage, {
|
||||||
|
variables,
|
||||||
|
localVariables: [
|
||||||
|
...localVariables,
|
||||||
|
{ name: '$nResponse', ctx: new Proxy({ ...res?.data, ...res?.data?.data }, {}) },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
successMessage = interpolateVariables(exp, expScope);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
if (res.headers['content-disposition']) {
|
if (res.headers['content-disposition']) {
|
||||||
const contentDisposition = res.headers['content-disposition'];
|
const contentDisposition = res.headers['content-disposition'];
|
||||||
const utf8Match = contentDisposition.match(/filename\*=utf-8''([^;]+)/i);
|
const utf8Match = contentDisposition.match(/filename\*=utf-8''([^;]+)/i);
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
/**
|
|
||||||
* This file is part of the NocoBase (R) project.
|
|
||||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
||||||
* Authors: NocoBase Team.
|
|
||||||
*
|
|
||||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useFieldSchema } from '@formily/react';
|
|
||||||
import {
|
|
||||||
AfterSuccess,
|
|
||||||
ButtonEditor,
|
|
||||||
RefreshDataBlockRequest,
|
|
||||||
RemoveButton,
|
|
||||||
SchemaSettings,
|
|
||||||
SchemaSettingsLinkageRules,
|
|
||||||
SecondConFirm,
|
|
||||||
useCollection,
|
|
||||||
useCollectionRecord,
|
|
||||||
useSchemaToolbar,
|
|
||||||
SchemaSettingAccessControl,
|
|
||||||
useDataBlockProps,
|
|
||||||
useCollectionManager_deprecated,
|
|
||||||
} from '@nocobase/client';
|
|
||||||
import { CustomRequestSettingsItem } from './components/CustomRequestActionDesigner';
|
|
||||||
|
|
||||||
export const customizeCustomRequestActionSettings = new SchemaSettings({
|
|
||||||
name: 'actionSettings:customRequest',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: 'editButton',
|
|
||||||
Component: ButtonEditor,
|
|
||||||
useComponentProps() {
|
|
||||||
const fieldSchema = useFieldSchema();
|
|
||||||
return {
|
|
||||||
isLink: fieldSchema['x-action'] === 'customize:table:request',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'linkageRules',
|
|
||||||
Component: SchemaSettingsLinkageRules,
|
|
||||||
useComponentProps() {
|
|
||||||
const { linkageRulesProps } = useSchemaToolbar();
|
|
||||||
return {
|
|
||||||
...linkageRulesProps,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'secondConFirm',
|
|
||||||
Component: SecondConFirm,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'afterSuccessfulSubmission',
|
|
||||||
Component: AfterSuccess,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'request settings',
|
|
||||||
Component: CustomRequestSettingsItem,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...SchemaSettingAccessControl,
|
|
||||||
useVisible() {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'refreshDataBlockRequest',
|
|
||||||
Component: RefreshDataBlockRequest,
|
|
||||||
useComponentProps() {
|
|
||||||
return {
|
|
||||||
isPopupAction: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
useVisible() {
|
|
||||||
const collection = useCollection();
|
|
||||||
return !!collection;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'delete',
|
|
||||||
sort: 100,
|
|
||||||
Component: RemoveButton as any,
|
|
||||||
useComponentProps() {
|
|
||||||
const { removeButtonProps } = useSchemaToolbar();
|
|
||||||
return removeButtonProps;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
@ -0,0 +1,242 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
ButtonEditor,
|
||||||
|
RefreshDataBlockRequest,
|
||||||
|
RemoveButton,
|
||||||
|
SchemaSettings,
|
||||||
|
SchemaSettingsLinkageRules,
|
||||||
|
SecondConFirm,
|
||||||
|
useCollection,
|
||||||
|
useSchemaToolbar,
|
||||||
|
SchemaSettingAccessControl,
|
||||||
|
useDesignable,
|
||||||
|
useGlobalVariable,
|
||||||
|
usePlugin,
|
||||||
|
SchemaSettingsModalItem,
|
||||||
|
useAfterSuccessOptions,
|
||||||
|
BlocksSelector,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
import { ISchema, useFieldSchema } from '@formily/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { CustomRequestSettingsItem } from './components/CustomRequestActionDesigner';
|
||||||
|
|
||||||
|
const useVariableOptions = () => {
|
||||||
|
const scopes = useAfterSuccessOptions();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: '$nResponse',
|
||||||
|
label: t('Response', { ns: 'client' }),
|
||||||
|
children: null,
|
||||||
|
},
|
||||||
|
...scopes.filter((v: any) => ['currentUser', 'currentTime', '$nRole'].includes(v.value)),
|
||||||
|
].filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useLinkVariableOptions = () => {
|
||||||
|
const scopes = useAfterSuccessOptions();
|
||||||
|
const environmentVariables = useGlobalVariable('$env');
|
||||||
|
return [...scopes.filter((v: any) => v.value !== '$record'), environmentVariables].filter(Boolean);
|
||||||
|
};
|
||||||
|
const useLinkVariableProps = () => {
|
||||||
|
const scope = useLinkVariableOptions();
|
||||||
|
return {
|
||||||
|
scope,
|
||||||
|
useTypedConstant: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export function AfterSuccess() {
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { onSuccess } = fieldSchema?.['x-action-settings'] || {};
|
||||||
|
const templatePlugin: any = usePlugin('@nocobase/plugin-block-template');
|
||||||
|
const isInBlockTemplateConfigPage = templatePlugin?.isInBlockTemplateConfigPage?.();
|
||||||
|
return (
|
||||||
|
<SchemaSettingsModalItem
|
||||||
|
dialogRootClassName="dialog-after-successful-submission"
|
||||||
|
width={700}
|
||||||
|
title={t('After successful submission')}
|
||||||
|
initialValues={
|
||||||
|
onSuccess
|
||||||
|
? {
|
||||||
|
actionAfterSuccess: onSuccess?.redirecting ? 'redirect' : 'previous',
|
||||||
|
...onSuccess,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
manualClose: false,
|
||||||
|
redirecting: false,
|
||||||
|
successMessage: '{{t("Saved successfully")}}',
|
||||||
|
actionAfterSuccess: 'previous',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schema={
|
||||||
|
{
|
||||||
|
type: 'object',
|
||||||
|
title: t('After successful submission'),
|
||||||
|
properties: {
|
||||||
|
successMessage: {
|
||||||
|
title: t('Popup message'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Variable.RawTextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
scope: useVariableOptions,
|
||||||
|
style: { minWidth: '220px' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
manualClose: {
|
||||||
|
title: t('Message popup close method'),
|
||||||
|
enum: [
|
||||||
|
{ label: t('Automatic close'), value: false },
|
||||||
|
{ label: t('Manually close'), value: true },
|
||||||
|
],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Radio.Group',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
redirecting: {
|
||||||
|
title: t('Then'),
|
||||||
|
'x-hidden': true,
|
||||||
|
enum: [
|
||||||
|
{ label: t('Stay on current page'), value: false },
|
||||||
|
{ label: t('Redirect to'), value: true },
|
||||||
|
],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Radio.Group',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-reactions': {
|
||||||
|
target: 'redirectTo',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: '{{!!$self.value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actionAfterSuccess: {
|
||||||
|
title: t('Action after successful submission'),
|
||||||
|
enum: [
|
||||||
|
{ label: t('Stay on the current popup or page'), value: 'stay' },
|
||||||
|
{ label: t('Return to the previous popup or page'), value: 'previous' },
|
||||||
|
{ label: t('Redirect to'), value: 'redirect' },
|
||||||
|
],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Radio.Group',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-reactions': {
|
||||||
|
target: 'redirectTo',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
visible: "{{$self.value==='redirect'}}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
redirectTo: {
|
||||||
|
title: t('Link'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Variable.TextArea',
|
||||||
|
'x-use-component-props': useLinkVariableProps,
|
||||||
|
},
|
||||||
|
blocksToRefresh: {
|
||||||
|
type: 'array',
|
||||||
|
title: t('Refresh data blocks'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-use-decorator-props': () => {
|
||||||
|
return {
|
||||||
|
tooltip: t('After successful submission, the selected data blocks will be automatically refreshed.'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'x-component': BlocksSelector,
|
||||||
|
'x-hidden': isInBlockTemplateConfigPage, // 模板配置页面暂不支持该配置
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as ISchema
|
||||||
|
}
|
||||||
|
onSubmit={(onSuccess) => {
|
||||||
|
fieldSchema['x-action-settings']['onSuccess'] = onSuccess;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
'x-action-settings': fieldSchema['x-action-settings'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const customizeCustomRequestActionSettings = new SchemaSettings({
|
||||||
|
name: 'actionSettings:customRequest',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'editButton',
|
||||||
|
Component: ButtonEditor,
|
||||||
|
useComponentProps() {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
return {
|
||||||
|
isLink: fieldSchema['x-action'] === 'customize:table:request',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'linkageRules',
|
||||||
|
Component: SchemaSettingsLinkageRules,
|
||||||
|
useComponentProps() {
|
||||||
|
const { linkageRulesProps } = useSchemaToolbar();
|
||||||
|
return {
|
||||||
|
...linkageRulesProps,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'secondConFirm',
|
||||||
|
Component: SecondConFirm,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'afterSuccessfulSubmission',
|
||||||
|
Component: AfterSuccess,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'request settings',
|
||||||
|
Component: CustomRequestSettingsItem,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...SchemaSettingAccessControl,
|
||||||
|
useVisible() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'refreshDataBlockRequest',
|
||||||
|
Component: RefreshDataBlockRequest,
|
||||||
|
useComponentProps() {
|
||||||
|
return {
|
||||||
|
isPopupAction: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
useVisible() {
|
||||||
|
const collection = useCollection();
|
||||||
|
return !!collection;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'delete',
|
||||||
|
sort: 100,
|
||||||
|
Component: RemoveButton as any,
|
||||||
|
useComponentProps() {
|
||||||
|
const { removeButtonProps } = useSchemaToolbar();
|
||||||
|
return removeButtonProps;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user