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 {
|
||||
return str.replace(/\{\{\s*([a-zA-Z0-9_$-.]+?)\s*\}\}/g, (_, key) => {
|
||||
export function interpolateVariables(str: string, scope: Record<string, any>): string {
|
||||
return str.replace(/\{\{\s*([a-zA-Z0-9_$.-]+?)\s*\}\}/g, (_, key) => {
|
||||
return scope[key] !== undefined ? String(scope[key]) : '';
|
||||
});
|
||||
}
|
||||
|
@ -29,10 +29,9 @@ export const useAfterSuccessOptions = () => {
|
||||
}, [fieldsOptions, userFieldOptions]);
|
||||
const { settings: popupRecordSettings, shouldDisplayPopupRecord } = usePopupVariable();
|
||||
const { currentRoleSettings } = useCurrentRoleVariable();
|
||||
const record = useCollectionRecordData();
|
||||
return useMemo(() => {
|
||||
return [
|
||||
(record || form) && {
|
||||
form && {
|
||||
value: '$record',
|
||||
label: t('Response record', { ns: 'client' }),
|
||||
children: [...fields],
|
||||
|
@ -31,3 +31,4 @@ export { default as useParseDataScopeFilter } from './hooks/useParseDataScopeFil
|
||||
export * from './isPatternDisabled';
|
||||
export { SchemaSettingsPlugin } from './SchemaSettingsPlugin';
|
||||
export * from './VariableInput';
|
||||
export { replaceVariables } from './LinkageRules/bindLinkageRulesToFiled';
|
||||
|
@ -18,6 +18,10 @@ import {
|
||||
useNavigateNoUpdate,
|
||||
useBlockRequestContext,
|
||||
useContextVariable,
|
||||
useLocalVariables,
|
||||
useVariables,
|
||||
replaceVariables,
|
||||
interpolateVariables,
|
||||
} from '@nocobase/client';
|
||||
import { isURL } from '@nocobase/utils/client';
|
||||
import { App } from 'antd';
|
||||
@ -38,12 +42,21 @@ export const useCustomizeRequestActionProps = () => {
|
||||
const { modal, message } = App.useApp();
|
||||
const dataSourceKey = useDataSourceKey();
|
||||
const { ctx } = useContextVariable();
|
||||
const localVariables = useLocalVariables();
|
||||
const variables = useVariables();
|
||||
|
||||
return {
|
||||
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 { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
|
||||
const {
|
||||
manualClose,
|
||||
redirecting,
|
||||
redirectTo,
|
||||
successMessage: rawSuccessMessage,
|
||||
actionAfterSuccess,
|
||||
} = onSuccess || {};
|
||||
let successMessage = rawSuccessMessage;
|
||||
const xAction = actionSchema?.['x-action'];
|
||||
if (skipValidator !== true && xAction === 'customize:form:request') {
|
||||
await form.submit();
|
||||
@ -71,6 +84,19 @@ export const useCustomizeRequestActionProps = () => {
|
||||
},
|
||||
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']) {
|
||||
const contentDisposition = res.headers['content-disposition'];
|
||||
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