mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
feat(custom-request): support selected table records in custom request (#6647)
* feat: support selected table records in custom request * fix: test * fix: bug * fix: bug * fix: bug * fix: bug * fix: merge bug * fix: e2e test * fix: e2e test
This commit is contained in:
parent
1c343cad66
commit
f5fb2844da
@ -57,6 +57,7 @@ test.describe('add blocks to the popup', () => {
|
|||||||
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
await page.getByRole('menuitem', { name: 'Details right' }).hover();
|
||||||
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
await page.getByRole('menuitem', { name: 'Associated records' }).last().hover();
|
||||||
await page.getByRole('menuitem', { name: 'Roles' }).click();
|
await page.getByRole('menuitem', { name: 'Roles' }).click();
|
||||||
|
await page.mouse.move(300, 0);
|
||||||
await page.getByLabel('schema-initializer-Grid-details:configureFields-roles').hover();
|
await page.getByLabel('schema-initializer-Grid-details:configureFields-roles').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Role UID' }).click();
|
await page.getByRole('menuitem', { name: 'Role UID' }).click();
|
||||||
await page.mouse.move(300, 0);
|
await page.mouse.move(300, 0);
|
||||||
|
@ -71,7 +71,7 @@ test.describe('sub page', () => {
|
|||||||
expect(page.url()).not.toContain('/popups/');
|
expect(page.url()).not.toContain('/popups/');
|
||||||
|
|
||||||
// 确认是否回到了主页面
|
// 确认是否回到了主页面
|
||||||
await page.getByText('Users单层子页面Configure').hover();
|
// await page.getByText('Users单层子页面Configure').hover();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'designer-schema-settings-CardItem-blockSettings:table-users' }),
|
page.getByRole('button', { name: 'designer-schema-settings-CardItem-blockSettings:table-users' }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
@ -57,7 +57,7 @@ import { useAllDataBlocks } from '../page/AllDataBlocksProvider';
|
|||||||
|
|
||||||
const useA = () => {
|
const useA = () => {
|
||||||
return {
|
return {
|
||||||
async run() { },
|
async run() {},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -139,23 +139,26 @@ export const Action: ComposedAction = withDynamicSchemaProps(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleClick = useMemo(() => {
|
const handleClick = useMemo(() => {
|
||||||
return onClick && (async (e, callback) => {
|
return (
|
||||||
await onClick?.(e, callback);
|
onClick &&
|
||||||
|
(async (e, callback) => {
|
||||||
|
await onClick?.(e, callback);
|
||||||
|
|
||||||
// 执行完 onClick 之后,刷新数据区块
|
// 执行完 onClick 之后,刷新数据区块
|
||||||
const blocksToRefresh = fieldSchema['x-action-settings']?.onSuccess?.blocksToRefresh || []
|
const blocksToRefresh = fieldSchema['x-action-settings']?.onSuccess?.blocksToRefresh || [];
|
||||||
if (blocksToRefresh.length > 0) {
|
if (blocksToRefresh.length > 0) {
|
||||||
getAllDataBlocks().forEach((block) => {
|
getAllDataBlocks().forEach((block) => {
|
||||||
if (blocksToRefresh.includes(block.uid)) {
|
if (blocksToRefresh.includes(block.uid)) {
|
||||||
try {
|
try {
|
||||||
block.service?.refresh();
|
block.service?.refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to refresh block:', block.uid, error);
|
console.error('Failed to refresh block:', block.uid, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
})
|
||||||
});
|
);
|
||||||
}, [onClick, fieldSchema, getAllDataBlocks]);
|
}, [onClick, fieldSchema, getAllDataBlocks]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -365,8 +365,8 @@ export const SchemaSettingsFormItemTemplate = function FormItemTemplate(props) {
|
|||||||
required: true,
|
required: true,
|
||||||
default: collection
|
default: collection
|
||||||
? `${compile(collection?.title || collection?.name)}_${t(
|
? `${compile(collection?.title || collection?.name)}_${t(
|
||||||
componentTitle[componentName] || componentName,
|
componentTitle[componentName] || componentName,
|
||||||
)}`
|
)}`
|
||||||
: t(componentTitle[componentName] || componentName),
|
: t(componentTitle[componentName] || componentName),
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'Input',
|
'x-component': 'Input',
|
||||||
@ -567,7 +567,7 @@ export const SchemaSettingsRemove: FC<SchemaSettingsRemoveProps> = (props) => {
|
|||||||
|
|
||||||
export interface SchemaSettingsSelectItemProps
|
export interface SchemaSettingsSelectItemProps
|
||||||
extends Omit<SchemaSettingsItemProps, 'onChange' | 'onClick'>,
|
extends Omit<SchemaSettingsItemProps, 'onChange' | 'onClick'>,
|
||||||
Omit<SelectWithTitleProps, 'title' | 'defaultValue'> {
|
Omit<SelectWithTitleProps, 'title' | 'defaultValue'> {
|
||||||
value?: SelectWithTitleProps['defaultValue'];
|
value?: SelectWithTitleProps['defaultValue'];
|
||||||
optionRender?: (option: any, info: { index: number }) => React.ReactNode;
|
optionRender?: (option: any, info: { index: number }) => React.ReactNode;
|
||||||
}
|
}
|
||||||
@ -900,26 +900,32 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
|
|||||||
>
|
>
|
||||||
<LocationSearchContext.Provider value={locationSearch}>
|
<LocationSearchContext.Provider value={locationSearch}>
|
||||||
<BlockRequestContext_deprecated.Provider value={ctx}>
|
<BlockRequestContext_deprecated.Provider value={ctx}>
|
||||||
<DataSourceApplicationProvider dataSourceManager={dm} dataSource={dataSourceKey}>
|
<DataSourceApplicationProvider
|
||||||
|
dataSourceManager={dm}
|
||||||
|
dataSource={dataSourceKey}
|
||||||
|
>
|
||||||
<AssociationOrCollectionProvider
|
<AssociationOrCollectionProvider
|
||||||
allowNull
|
allowNull
|
||||||
collection={collection?.name}
|
collection={collection?.name}
|
||||||
association={association}
|
association={association}
|
||||||
>
|
>
|
||||||
<SchemaComponentOptions scope={options.scope} components={options.components}>
|
<SchemaComponentOptions
|
||||||
|
scope={options.scope}
|
||||||
|
components={options.components}
|
||||||
|
>
|
||||||
<FormLayout
|
<FormLayout
|
||||||
layout={'vertical'}
|
layout={'vertical'}
|
||||||
className={css`
|
className={css`
|
||||||
// screen > 576px
|
// screen > 576px
|
||||||
@media (min-width: 576px) {
|
@media (min-width: 576px) {
|
||||||
min-width: 520px;
|
min-width: 520px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// screen <= 576px
|
// screen <= 576px
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<ApplicationContext.Provider value={app}>
|
<ApplicationContext.Provider value={app}>
|
||||||
<APIClientProvider apiClient={apiClient}>
|
<APIClientProvider apiClient={apiClient}>
|
||||||
@ -984,13 +990,13 @@ export const SchemaSettingsDefaultSortingRules = function DefaultSortingRules(pr
|
|||||||
const sort = defaultSort?.map((item: string) => {
|
const sort = defaultSort?.map((item: string) => {
|
||||||
return item.startsWith('-')
|
return item.startsWith('-')
|
||||||
? {
|
? {
|
||||||
field: item.substring(1),
|
field: item.substring(1),
|
||||||
direction: 'desc',
|
direction: 'desc',
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
field: item,
|
field: item,
|
||||||
direction: 'asc',
|
direction: 'asc',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const sortFields = useSortFields(props.name || collection?.name);
|
const sortFields = useSortFields(props.name || collection?.name);
|
||||||
|
|
||||||
|
@ -15,3 +15,4 @@ export * from './useURLSearchParamsVariable';
|
|||||||
export * from './useUserVariable';
|
export * from './useUserVariable';
|
||||||
export * from './useVariableOptions';
|
export * from './useVariableOptions';
|
||||||
export * from './usePopupVariable';
|
export * from './usePopupVariable';
|
||||||
|
export * from './useContextAssociationFields';
|
||||||
|
@ -46,7 +46,9 @@ const getChildren = (
|
|||||||
return {
|
return {
|
||||||
key: option.name,
|
key: option.name,
|
||||||
value: option.name,
|
value: option.name,
|
||||||
|
name: option.name,
|
||||||
label: compile(option.title),
|
label: compile(option.title),
|
||||||
|
title: compile(option.title),
|
||||||
disabled: disabled,
|
disabled: disabled,
|
||||||
isLeaf: true,
|
isLeaf: true,
|
||||||
depth,
|
depth,
|
||||||
@ -59,7 +61,9 @@ const getChildren = (
|
|||||||
return {
|
return {
|
||||||
key: option.name,
|
key: option.name,
|
||||||
value: option.name,
|
value: option.name,
|
||||||
|
name: option.name,
|
||||||
label: compile(option.title),
|
label: compile(option.title),
|
||||||
|
title: compile(option.title),
|
||||||
disabled: disabled,
|
disabled: disabled,
|
||||||
isLeaf: true,
|
isLeaf: true,
|
||||||
field: option,
|
field: option,
|
||||||
@ -77,10 +81,10 @@ export const useContextAssociationFields = ({
|
|||||||
contextCollectionName,
|
contextCollectionName,
|
||||||
collectionField,
|
collectionField,
|
||||||
}: {
|
}: {
|
||||||
schema: any;
|
schema?: any;
|
||||||
maxDepth?: number;
|
maxDepth?: number;
|
||||||
contextCollectionName: string;
|
contextCollectionName: string;
|
||||||
collectionField: CollectionFieldOptions_deprecated;
|
collectionField?: CollectionFieldOptions_deprecated;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
@ -101,10 +105,17 @@ export const useContextAssociationFields = ({
|
|||||||
const children =
|
const children =
|
||||||
getChildren(
|
getChildren(
|
||||||
getFilterOptions(collectionName).filter((v) => {
|
getFilterOptions(collectionName).filter((v) => {
|
||||||
const isAssociationField = ['hasOne', 'hasMany', 'belongsTo', 'belongsToMany', 'belongsToArray'].includes(
|
if (collectionField) {
|
||||||
v.type,
|
const isAssociationField = [
|
||||||
);
|
'hasOne',
|
||||||
return isAssociationField;
|
'hasMany',
|
||||||
|
'belongsTo',
|
||||||
|
'belongsToMany',
|
||||||
|
'belongsToArray',
|
||||||
|
].includes(v.type);
|
||||||
|
return isAssociationField;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
schema,
|
schema,
|
||||||
|
@ -78,7 +78,7 @@ function AfterSuccess() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaSettingsModalItem
|
<SchemaSettingsModalItem
|
||||||
dialogRootClassName='dialog-after-successful-submission'
|
dialogRootClassName="dialog-after-successful-submission"
|
||||||
width={700}
|
width={700}
|
||||||
title={t('After successful submission')}
|
title={t('After successful submission')}
|
||||||
initialValues={fieldSchema?.['x-action-settings']?.['onSuccess']}
|
initialValues={fieldSchema?.['x-action-settings']?.['onSuccess']}
|
||||||
|
@ -15,11 +15,28 @@ import {
|
|||||||
useCollectionRecordData,
|
useCollectionRecordData,
|
||||||
useCompile,
|
useCompile,
|
||||||
useGlobalVariable,
|
useGlobalVariable,
|
||||||
|
useContextAssociationFields,
|
||||||
|
useTableBlockContext,
|
||||||
|
useCurrentPopupContext,
|
||||||
|
getStoredPopupContext,
|
||||||
useFormBlockContext,
|
useFormBlockContext,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
import { useTranslation } from '../locale';
|
import { useTranslation } from '../locale';
|
||||||
|
|
||||||
|
const useIsShowTableSelectRecord = () => {
|
||||||
|
const { params } = useCurrentPopupContext();
|
||||||
|
const recordData = useCollectionRecordData();
|
||||||
|
const tableBlockContextBasicValue = useTableBlockContext();
|
||||||
|
if (recordData) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const popupTableBlockContext = getStoredPopupContext(params?.popupuid)?.tableBlockContext;
|
||||||
|
return !isEmpty(popupTableBlockContext) || !isEmpty(tableBlockContextBasicValue);
|
||||||
|
};
|
||||||
|
|
||||||
export const useCustomRequestVariableOptions = () => {
|
export const useCustomRequestVariableOptions = () => {
|
||||||
const collection = useCollection_deprecated();
|
const collection = useCollection_deprecated();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -33,6 +50,8 @@ export const useCustomRequestVariableOptions = () => {
|
|||||||
return [compile(fieldsOptions), compile(userFieldOptions)];
|
return [compile(fieldsOptions), compile(userFieldOptions)];
|
||||||
}, [fieldsOptions, userFieldOptions]);
|
}, [fieldsOptions, userFieldOptions]);
|
||||||
const environmentVariables = useGlobalVariable('$env');
|
const environmentVariables = useGlobalVariable('$env');
|
||||||
|
const contextVariable = useContextAssociationFields({ maxDepth: 2, contextCollectionName: collection.name });
|
||||||
|
const shouldShowTableSelectVariable = useIsShowTableSelectRecord();
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return [
|
return [
|
||||||
environmentVariables,
|
environmentVariables,
|
||||||
@ -61,6 +80,7 @@ export const useCustomRequestVariableOptions = () => {
|
|||||||
title: 'API token',
|
title: 'API token',
|
||||||
children: null,
|
children: null,
|
||||||
},
|
},
|
||||||
|
shouldShowTableSelectVariable && { ...contextVariable, name: '$nSelectedRecord', title: contextVariable.label },
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
}, [recordData, t, fields, blockType, userFields]);
|
}, [recordData, t, fields, blockType, userFields, shouldShowTableSelectVariable]);
|
||||||
};
|
};
|
||||||
|
@ -16,6 +16,8 @@ import {
|
|||||||
useCompile,
|
useCompile,
|
||||||
useDataSourceKey,
|
useDataSourceKey,
|
||||||
useNavigateNoUpdate,
|
useNavigateNoUpdate,
|
||||||
|
useBlockRequestContext,
|
||||||
|
useContextVariable,
|
||||||
} 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';
|
||||||
@ -25,18 +27,21 @@ export const useCustomizeRequestActionProps = () => {
|
|||||||
const apiClient = useAPIClient();
|
const apiClient = useAPIClient();
|
||||||
const navigate = useNavigateNoUpdate();
|
const navigate = useNavigateNoUpdate();
|
||||||
const actionSchema = useFieldSchema();
|
const actionSchema = useFieldSchema();
|
||||||
|
const { field } = useBlockRequestContext();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
const { name: blockType } = useBlockContext() || {};
|
const { name: blockType } = useBlockContext() || {};
|
||||||
// const { getPrimaryKey } = useCollection_deprecated();
|
|
||||||
const recordData = useCollectionRecordData();
|
const recordData = useCollectionRecordData();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const actionField = useField();
|
const actionField = useField();
|
||||||
const { setVisible } = useActionContext();
|
const { setVisible } = useActionContext();
|
||||||
const { modal, message } = App.useApp();
|
const { modal, message } = App.useApp();
|
||||||
const dataSourceKey = useDataSourceKey();
|
const dataSourceKey = useDataSourceKey();
|
||||||
|
const { ctx } = useContextVariable();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async onClick(e?, callBack?) {
|
async onClick(e?, callBack?) {
|
||||||
|
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, actionAfterSuccess } = onSuccess || {};
|
||||||
const xAction = actionSchema?.['x-action'];
|
const xAction = actionSchema?.['x-action'];
|
||||||
@ -58,12 +63,11 @@ export const useCustomizeRequestActionProps = () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
currentRecord: {
|
currentRecord: {
|
||||||
// id: record[getPrimaryKey()],
|
|
||||||
// appends: result.params[0]?.appends,
|
|
||||||
dataSourceKey,
|
dataSourceKey,
|
||||||
data: currentRecordData,
|
data: currentRecordData,
|
||||||
},
|
},
|
||||||
$nForm: blockType === 'form' ? form.values : undefined,
|
$nForm: blockType === 'form' ? form.values : undefined,
|
||||||
|
$nSelectedRecord: selectedRecord,
|
||||||
},
|
},
|
||||||
responseType: fieldSchema['x-response-type'] === 'stream' ? 'blob' : 'json',
|
responseType: fieldSchema['x-response-type'] === 'stream' ? 'blob' : 'json',
|
||||||
});
|
});
|
||||||
|
@ -73,6 +73,7 @@ export async function send(this: CustomRequestPlugin, ctx: Context, next: Next)
|
|||||||
data: {},
|
data: {},
|
||||||
},
|
},
|
||||||
$nForm,
|
$nForm,
|
||||||
|
$nSelectedRecord,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
// root role has all permissions
|
// root role has all permissions
|
||||||
@ -154,6 +155,7 @@ export async function send(this: CustomRequestPlugin, ctx: Context, next: Next)
|
|||||||
$nToken: ctx.getBearerToken(),
|
$nToken: ctx.getBearerToken(),
|
||||||
$nForm,
|
$nForm,
|
||||||
$env: ctx.app.environment.getVariables(),
|
$env: ctx.app.environment.getVariables(),
|
||||||
|
$nSelectedRecord,
|
||||||
};
|
};
|
||||||
|
|
||||||
const axiosRequestConfig = {
|
const axiosRequestConfig = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user