Merge branch 'next' into develop

This commit is contained in:
nocobase[bot] 2025-03-25 13:19:53 +00:00
commit 7eb262670c
7 changed files with 166 additions and 12 deletions

View File

@ -203,6 +203,12 @@ export function useCollectValuesToSubmit() {
]);
}
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]) : '';
});
}
export const useCreateActionProps = () => {
const filterByTk = useFilterByTk();
const record = useCollectionRecord();
@ -219,11 +225,20 @@ export const useCreateActionProps = () => {
const collectValues = useCollectValuesToSubmit();
const action = record.isNew ? actionField.componentProps.saveMode || 'create' : 'update';
const filterKeys = actionField.componentProps.filterKeys?.checked || [];
const localVariables = useLocalVariables();
const variables = useVariables();
return {
async onClick() {
const { onSuccess, skipValidator, triggerWorkflows } = actionSchema?.['x-action-settings'] ?? {};
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
const {
manualClose,
redirecting,
redirectTo: rawRedirectTo,
successMessage,
actionAfterSuccess,
} = onSuccess || {};
if (!skipValidator) {
await form.submit();
}
@ -241,6 +256,15 @@ export const useCreateActionProps = () => {
: undefined,
updateAssociationValues,
});
let redirectTo = rawRedirectTo;
if (rawRedirectTo) {
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
variables,
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(data?.data?.data, {}) }],
});
redirectTo = interpolateVariables(exp, expScope);
}
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
setVisible?.(false);
}
@ -588,7 +612,13 @@ export const useCustomizeUpdateActionProps = () => {
skipValidator,
triggerWorkflows,
} = actionSchema?.['x-action-settings'] ?? {};
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
const {
manualClose,
redirecting,
redirectTo: rawRedirectTo,
successMessage,
actionAfterSuccess,
} = onSuccess || {};
const assignedValues = {};
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
const value = originalAssignedValues[key];
@ -614,7 +644,7 @@ export const useCustomizeUpdateActionProps = () => {
if (skipValidator === false) {
await form.submit();
}
await resource.update({
const result = await resource.update({
filterByTk,
values: { ...assignedValues },
// TODO(refactor): should change to inject by plugin
@ -622,6 +652,16 @@ export const useCustomizeUpdateActionProps = () => {
? triggerWorkflows.map((row) => [row.workflowKey, row.context].filter(Boolean).join('!')).join(',')
: undefined,
});
let redirectTo = rawRedirectTo;
if (rawRedirectTo) {
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
variables,
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(result?.data?.data?.[0], {}) }],
});
redirectTo = interpolateVariables(exp, expScope);
}
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
setVisible?.(false);
}
@ -913,7 +953,13 @@ export const useUpdateActionProps = () => {
skipValidator,
triggerWorkflows,
} = actionSchema?.['x-action-settings'] ?? {};
const { manualClose, redirecting, redirectTo, successMessage, actionAfterSuccess } = onSuccess || {};
const {
manualClose,
redirecting,
redirectTo: rawRedirectTo,
successMessage,
actionAfterSuccess,
} = onSuccess || {};
const assignedValues = {};
const waitList = Object.keys(originalAssignedValues).map(async (key) => {
const value = originalAssignedValues[key];
@ -952,7 +998,7 @@ export const useUpdateActionProps = () => {
actionField.data = field.data || {};
actionField.data.loading = true;
try {
await resource.update({
const result = await resource.update({
filterByTk,
values: {
...values,
@ -971,6 +1017,15 @@ export const useUpdateActionProps = () => {
if (callBack) {
callBack?.();
}
let redirectTo = rawRedirectTo;
if (rawRedirectTo) {
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
variables,
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(result?.data?.data?.[0], {}) }],
});
redirectTo = interpolateVariables(exp, expScope);
}
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
setVisible?.(false);
}

View File

@ -157,6 +157,8 @@ export const useCollectionFilterOptions = (collection: any, dataSource?: string)
const option = {
name: field.name,
title: field?.uiSchema?.title || field.name,
label: field?.uiSchema?.title || field.name,
value: field.name,
schema: field?.uiSchema,
operators:
operators?.filter?.((operator) => {

View File

@ -1094,5 +1094,6 @@
"Font Sizepx": "字体大小(像素)",
"Font Weight": "字体粗细",
"Font Style": "字体样式",
"Italic": "斜体"
"Italic": "斜体",
"Response record":"响应结果记录"
}

View File

@ -34,6 +34,8 @@ import {
import { DefaultValueProvider } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
import { useLinkageAction } from './hooks';
import { requestSettingsSchema } from './utils';
import { useAfterSuccessOptions } from './hooks/useGetAfterSuccessVariablesOptions';
import { useGlobalVariable } from '../../../application/hooks/useGlobalVariable';
const MenuGroup = (props) => {
return props.children;
@ -280,11 +282,24 @@ export function SkipValidation() {
);
}
const fieldNames = {
value: 'value',
label: 'label',
};
const useVariableProps = (environmentVariables) => {
const scope = useAfterSuccessOptions();
return {
scope: [environmentVariables, ...scope].filter(Boolean),
fieldNames,
};
};
export function AfterSuccess() {
const { dn } = useDesignable();
const { t } = useTranslation();
const fieldSchema = useFieldSchema();
const { onSuccess } = fieldSchema?.['x-action-settings'] || {};
const environmentVariables = useGlobalVariable('$env');
return (
<SchemaSettingsModalItem
title={t('After successful submission')}
@ -363,8 +378,9 @@ export function AfterSuccess() {
redirectTo: {
title: t('Link'),
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {},
'x-component': 'Variable.TextArea',
// eslint-disable-next-line react-hooks/rules-of-hooks
'x-use-component-props': () => useVariableProps(environmentVariables),
},
},
} as ISchema

View File

@ -0,0 +1,66 @@
/**
* 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 { useMemo } from 'react';
import { useCollection_deprecated, useCollectionFilterOptions } from '../../../../collection-manager';
import { useCollectionRecordData } from '../../../../data-source';
import { useTranslation } from 'react-i18next';
import { useCompile } from '../../../';
import { useBlockContext } from '../../../../block-provider/BlockProvider';
import { usePopupVariable } from '../../../../schema-settings/VariableInput/hooks';
import { useCurrentRoleVariable } from '../../../../schema-settings/VariableInput/hooks';
export const useAfterSuccessOptions = () => {
const collection = useCollection_deprecated();
const { t } = useTranslation();
const fieldsOptions = useCollectionFilterOptions(collection);
const userFieldOptions = useCollectionFilterOptions('users', 'main');
const compile = useCompile();
const recordData = useCollectionRecordData();
const { name: blockType } = useBlockContext() || {};
const [fields, userFields] = useMemo(() => {
return [compile(fieldsOptions), compile(userFieldOptions)];
}, [fieldsOptions, userFieldOptions]);
const { settings: popupRecordSettings, shouldDisplayPopupRecord } = usePopupVariable();
const { currentRoleSettings } = useCurrentRoleVariable();
const record = useCollectionRecordData();
return useMemo(() => {
return [
(record || blockType === 'form') && {
value: '$record',
label: t('Response record', { ns: 'client' }),
children: [...fields],
},
recordData && {
value: 'currentRecord',
label: t('Current record', { ns: 'client' }),
children: [...fields],
},
shouldDisplayPopupRecord && {
...popupRecordSettings,
},
{
value: 'currentUser',
label: t('Current user', { ns: 'client' }),
children: userFields,
},
currentRoleSettings,
{
value: 'currentTime',
label: t('Current time', { ns: 'client' }),
children: null,
},
{
value: '$nToken',
label: 'API token',
children: null,
},
].filter(Boolean);
}, [recordData, t, fields, blockType, userFields]);
};

View File

@ -16,5 +16,6 @@ export * from './hooks/useGetAriaLabelOfAction';
export * from './hooks/useGetAriaLabelOfDrawer';
export * from './hooks/useGetAriaLabelOfModal';
export * from './hooks/useGetAriaLabelOfPopover';
export * from './hooks/useGetAfterSuccessVariablesOptions';
export * from './types';
export * from './zIndexContext';

View File

@ -8,7 +8,6 @@
*/
import { ISchema, useFieldSchema } from '@formily/react';
import { isValid } from '@formily/shared';
import {
ActionDesigner,
SchemaSettings,
@ -19,6 +18,8 @@ import {
useDesignable,
useSchemaToolbar,
RefreshDataBlockRequest,
useAfterSuccessOptions,
useGlobalVariable,
} from '@nocobase/client';
import { useTranslation } from 'react-i18next';
import React from 'react';
@ -49,11 +50,22 @@ function UpdateMode() {
/>
);
}
const fieldNames = {
value: 'value',
label: 'label',
};
const useVariableProps = (environmentVariables) => {
const scope = useAfterSuccessOptions();
return {
scope: [environmentVariables, ...scope].filter(Boolean),
fieldNames,
};
};
function AfterSuccess() {
const { dn } = useDesignable();
const { t } = useTranslation();
const fieldSchema = useFieldSchema();
const environmentVariables = useGlobalVariable('$env');
return (
<SchemaSettingsModalItem
title={t('After successful submission')}
@ -100,8 +112,9 @@ function AfterSuccess() {
redirectTo: {
title: t('Link'),
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {},
'x-component': 'Variable.TextArea',
// eslint-disable-next-line react-hooks/rules-of-hooks
'x-use-component-props': () => useVariableProps(environmentVariables),
},
},
} as ISchema