refactor(DataBlock): details block (#3776)

* refactor: remove useless code related 'TableField'

* refactor: add details block UI schema and hooks

* refactor: update details block schema and create new hook for details block without paging props

* refactor: add useDetailsBlockWithoutPagingDecoratorProps and useDetailsBlockWithPagingDecoratorProps

* test: add unit tests

* chore: fix build

* chore: fix import path for withDynamicSchemaProps

* fix: fix import typo in createDetailsBlockWithoutPagingUISchema test

* chore: fix unit test

* refactor: should not get collection on getting association in UISchema

* chore: fix unit test

* refactor: use x-use-component-props instead of useProps

* chore: fix Pagination

* chore: skip weird tests

* chore: remove weird test

* refactor: update details block initializers and associated schemas

* refactor: rename to useDetailsWithPaginationProps

* refactor: rename to useDetailsWithPaginationDecoratorProps

* refactor: rename to createDetailsWithPaginationUISchema

* chore: fix unit test

* refactor: rename to createDetailsUISchema

* refactor: rename to useDetailsDecoratorProps

* refactor: rename to useDetailsProps

* refactor: rename to 'blockSettings:details'

* chore: fix e2e
This commit is contained in:
Zeke Zhang 2024-03-28 08:48:58 +08:00 committed by GitHub
parent 7566a7b357
commit 2468caeeb9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 698 additions and 372 deletions

View File

@ -27,6 +27,10 @@ import { useCollapseBlockDecoratorProps } from '../modules/blocks/filter-blocks/
import { useFilterFormBlockProps } from '../modules/blocks/filter-blocks/form/hooks/useFilterFormBlockProps';
import { useFilterFormBlockDecoratorProps } from '../modules/blocks/filter-blocks/form/hooks/useFilterFormBlockDecoratorProps';
import { useGridCardBlockDecoratorProps } from '../modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps';
import { useDetailsProps } from '../modules/blocks/data-blocks/details-single/hooks/useDetailsProps';
import { useDetailsWithPaginationProps } from '../modules/blocks/data-blocks/details-multi/hooks/useDetailsWithPaginationProps';
import { useDetailsDecoratorProps } from '../modules/blocks/data-blocks/details-single/hooks/useDetailsDecoratorProps';
import { useDetailsWithPaginationDecoratorProps } from '../modules/blocks/data-blocks/details-multi/hooks/useDetailsWithPaginationDecoratorProps';
// TODO: delete this, replaced by `BlockSchemaComponentPlugin`
export const BlockSchemaComponentProvider: React.FC = (props) => {
@ -54,6 +58,10 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
useEditFormBlockProps,
useFormFieldProps,
useDetailsBlockProps,
useDetailsProps,
useDetailsWithPaginationProps,
useDetailsDecoratorProps,
useDetailsWithPaginationDecoratorProps,
useTableFieldProps,
useTableBlockProps,
useTableSelectorProps,
@ -108,6 +116,10 @@ export class BlockSchemaComponentPlugin extends Plugin {
useEditFormBlockProps,
useFormFieldProps,
useDetailsBlockProps,
useDetailsProps,
useDetailsWithPaginationProps,
useDetailsDecoratorProps,
useDetailsWithPaginationDecoratorProps,
useTableFieldProps,
useTableBlockProps,
useTableSelectorProps,

View File

@ -7,6 +7,7 @@ import { useCollectionParentRecord } from '../data-source/collection-record/Coll
import { RecordProvider } from '../record-provider';
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
import { useParsedFilter } from './hooks';
import { withDynamicSchemaProps } from '../application/hoc/withDynamicSchemaProps';
export const DetailsBlockContext = createContext<any>({});
DetailsBlockContext.displayName = 'DetailsBlockContext';
@ -57,13 +58,13 @@ const InternalDetailsBlockProvider = (props) => {
);
};
export const DetailsBlockProvider = (props) => {
export const DetailsBlockProvider = withDynamicSchemaProps((props) => {
return (
<BlockProvider name="details" {...props}>
<InternalDetailsBlockProvider {...props} />
</BlockProvider>
);
};
});
/**
* @deprecated
@ -72,6 +73,11 @@ export const useDetailsBlockContext = () => {
return useContext(DetailsBlockContext);
};
/**
* @deprecated
* useDetailsWithPaginationProps useDetailsProps
* @returns
*/
export const useDetailsBlockProps = () => {
const ctx = useDetailsBlockContext();
useEffect(() => {

View File

@ -53,6 +53,10 @@ export const detailsActionInitializers_deprecated = new CompatibleSchemaInitiali
],
});
/**
* @deprecated
* 使 readPrettyFormActionInitializers
*/
export const detailsActionInitializers = new CompatibleSchemaInitializer(
{
name: 'detailsWithPaging:configureActions',

View File

@ -2,9 +2,9 @@ import { TableOutlined } from '@ant-design/icons';
import React from 'react';
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
import { useCollectionManager_deprecated } from '../../../../collection-manager';
import { createDetailsBlockSchema } from '../../../../schema-initializer/utils';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection';
import { createDetailsWithPaginationUISchema } from './createDetailsWithPaginationUISchema';
export const DetailsBlockInitializer = ({
filterCollections,
@ -51,15 +51,14 @@ export const DetailsBlockInitializer = ({
const { item } = options;
const collection = getCollection(item.name, item.dataSource);
const schema = createDetailsBlockSchema({
collection: item.name,
const schema = createDetailsWithPaginationUISchema({
collectionName: item.name,
dataSource: item.dataSource,
rowKey: collection.filterTargetKey || 'id',
actionInitializers:
hideActionInitializer: !(
(collection.template !== 'view' || collection?.writableView) &&
collection.template !== 'sql' &&
'detailsWithPaging:configureActions',
settings: 'blockSettings:multiDataDetails',
collection.template !== 'sql'
),
});
insert(schema);
}}

View File

@ -0,0 +1,69 @@
import { createDetailsWithPaginationUISchema } from '../createDetailsWithPaginationUISchema';
vi.mock('@formily/shared', () => ({
uid: vi.fn().mockReturnValue('mocked-uid'),
}));
describe('createDetailsBlockWithPaginationUISchema', () => {
it('should return the correct schema', () => {
const schema = createDetailsWithPaginationUISchema({
collectionName: 'users',
dataSource: 'users',
rowKey: 'id',
});
expect(schema).toMatchInlineSnapshot(`
{
"properties": {
"mocked-uid": {
"properties": {
"grid": {
"properties": {},
"type": "void",
"x-component": "Grid",
"x-initializer": "details:configureFields",
},
"mocked-uid": {
"properties": {},
"type": "void",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": 24,
},
},
"x-initializer": "details:configureActions",
},
"pagination": {
"type": "void",
"x-component": "Pagination",
"x-use-component-props": "useDetailsPaginationProps",
},
},
"type": "void",
"x-component": "Details",
"x-read-pretty": true,
"x-use-component-props": "useDetailsWithPaginationProps",
},
},
"type": "void",
"x-acl-action": "users:view",
"x-component": "CardItem",
"x-decorator": "DetailsBlockProvider",
"x-decorator-props": {
"action": "list",
"association": undefined,
"collection": "users",
"dataSource": "users",
"params": {
"pageSize": 1,
},
"readPretty": true,
},
"x-settings": "blockSettings:detailsWithPagination",
"x-toolbar": "BlockSchemaToolbar",
"x-use-decorator-props": "useDetailsWithPaginationDecoratorProps",
}
`);
});
});

View File

@ -0,0 +1,70 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
export function createDetailsWithPaginationUISchema(options: {
dataSource: string;
rowKey: string;
collectionName?: string;
association?: string;
templateSchema?: ISchema;
hideActionInitializer?: boolean;
}): ISchema {
const { collectionName, dataSource, association, templateSchema, hideActionInitializer, rowKey } = options;
const resourceName = association || collectionName;
if (!dataSource || !rowKey) {
throw new Error('dataSource and rowKey are required');
}
return {
type: 'void',
'x-acl-action': `${resourceName}:view`,
'x-decorator': 'DetailsBlockProvider',
'x-use-decorator-props': 'useDetailsWithPaginationDecoratorProps',
'x-decorator-props': {
dataSource,
collection: collectionName,
association,
readPretty: true,
action: 'list',
params: {
pageSize: 1,
},
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:detailsWithPagination',
'x-component': 'CardItem',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Details',
'x-read-pretty': true,
'x-use-component-props': 'useDetailsWithPaginationProps',
properties: {
[uid()]: {
type: 'void',
'x-initializer': hideActionInitializer ? undefined : 'details:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 24,
},
},
properties: {},
},
grid: templateSchema || {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'details:configureFields',
properties: {},
},
pagination: {
type: 'void',
'x-component': 'Pagination',
'x-use-component-props': 'useDetailsPaginationProps',
},
},
},
},
};
}

View File

@ -0,0 +1,216 @@
import { ArrayItems } from '@formily/antd-v5';
import { ISchema, useField, useFieldSchema } from '@formily/react';
import { useTranslation } from 'react-i18next';
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
import { useFormBlockContext } from '../../../../block-provider';
import { useDetailsBlockContext } from '../../../../block-provider/DetailsBlockProvider';
import { useCollection_deprecated, useSortFields } from '../../../../collection-manager';
import { removeNullCondition, useDesignable } from '../../../../schema-component';
import { SchemaSettingsBlockTitleItem, SchemaSettingsTemplate } from '../../../../schema-settings';
import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope';
import { setDataLoadingModeSettingsItem, useDataLoadingMode } from './setDataLoadingModeSettingsItem';
import { SchemaSettingsItemType } from '../../../../application/schema-settings/types';
const commonItems: SchemaSettingsItemType[] = [
{
name: 'title',
Component: SchemaSettingsBlockTitleItem,
},
{
name: 'dataScope',
Component: SchemaSettingsDataScope,
useComponentProps() {
const { name } = useCollection_deprecated();
const fieldSchema = useFieldSchema();
const { form } = useFormBlockContext();
const field = useField();
const { service } = useDetailsBlockContext();
const { dn } = useDesignable();
const dataLoadingMode = useDataLoadingMode();
return {
collectionName: name,
defaultFilter: fieldSchema?.['x-decorator-props']?.params?.filter || {},
form,
onSubmit: ({ filter }) => {
filter = removeNullCondition(filter);
const params = field.decoratorProps.params || {};
params.filter = filter;
field.decoratorProps.params = params;
fieldSchema['x-decorator-props']['params'] = params;
if (dataLoadingMode === 'auto') {
service.run({ ...service.params?.[0], filter });
}
dn.emit('patch', {
schema: {
['x-uid']: fieldSchema['x-uid'],
'x-decorator-props': fieldSchema['x-decorator-props'],
},
});
},
};
},
},
{
name: 'sortingRules',
type: 'modal',
useComponentProps() {
const { name } = useCollection_deprecated();
const { t } = useTranslation();
const fieldSchema = useFieldSchema();
const field = useField();
const { service } = useDetailsBlockContext();
const { dn } = useDesignable();
const sortFields = useSortFields(name);
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
const sort = defaultSort?.map((item: string) => {
return item.startsWith('-')
? {
field: item.substring(1),
direction: 'desc',
}
: {
field: item,
direction: 'asc',
};
});
return {
title: t('Set default sorting rules'),
components: {
ArrayItems,
},
schema: {
type: 'object',
title: t('Set default sorting rules'),
properties: {
sort: {
type: 'array',
default: sort,
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
items: {
type: 'object',
properties: {
space: {
type: 'void',
'x-component': 'Space',
properties: {
sort: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.SortHandle',
},
field: {
type: 'string',
enum: sortFields,
required: true,
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
style: {
width: 260,
},
},
},
direction: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Radio.Group',
'x-component-props': {
optionType: 'button',
},
enum: [
{
label: t('ASC'),
value: 'asc',
},
{
label: t('DESC'),
value: 'desc',
},
],
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
},
},
properties: {
add: {
type: 'void',
title: t('Add sort field'),
'x-component': 'ArrayItems.Addition',
},
},
},
},
} as ISchema,
onSubmit({ sort }) {
const sortArr = sort.map((item) => {
return item.direction === 'desc' ? `-${item.field}` : item.field;
});
const params = field.decoratorProps.params || {};
params.sort = sortArr;
field.decoratorProps.params = params;
fieldSchema['x-decorator-props']['params'] = params;
dn.emit('patch', {
schema: {
['x-uid']: fieldSchema['x-uid'],
'x-decorator-props': fieldSchema['x-decorator-props'],
},
});
service.run({ ...service.params?.[0], sort: sortArr });
},
};
},
},
setDataLoadingModeSettingsItem,
{
name: 'template',
Component: SchemaSettingsTemplate,
useComponentProps() {
const { name } = useCollection_deprecated();
const fieldSchema = useFieldSchema();
const defaultResource =
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
return {
componentName: 'Details',
collectionName: name,
resourceName: defaultResource,
};
},
},
{
name: 'divider',
type: 'divider',
},
{
name: 'remove',
type: 'remove',
componentProps: {
removeParentsIfNoChildren: true,
breakRemoveOn: {
'x-component': 'Grid',
},
},
},
];
/**
* @deprecated
* 使 detailsWithPaginationSettings
*/
export const multiDataDetailsBlockSettings = new SchemaSettings({
name: 'blockSettings:multiDataDetails',
items: commonItems,
});
export const detailsWithPaginationSettings = new SchemaSettings({
name: 'blockSettings:detailsWithPagination',
items: commonItems,
});

View File

@ -0,0 +1,15 @@
import { useDataBlockSourceId } from '../../../../../block-provider/hooks/useDataBlockSourceId';
export function useDetailsWithPaginationDecoratorProps(props) {
let sourceId;
// association 的值是固定不变的,所以可以在条件中使用 hooks
if (props.association) {
// eslint-disable-next-line react-hooks/rules-of-hooks
sourceId = useDataBlockSourceId({ association: props.association });
}
return {
sourceId,
};
}

View File

@ -0,0 +1,5 @@
import { useDetailsBlockProps } from '../../../../../block-provider/DetailsBlockProvider';
export function useDetailsWithPaginationProps() {
return useDetailsBlockProps();
}

View File

@ -1,204 +0,0 @@
import { ArrayItems } from '@formily/antd-v5';
import { ISchema, useField, useFieldSchema } from '@formily/react';
import { useTranslation } from 'react-i18next';
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
import { useFormBlockContext } from '../../../../block-provider';
import { useDetailsBlockContext } from '../../../../block-provider/DetailsBlockProvider';
import { useCollection_deprecated, useSortFields } from '../../../../collection-manager';
import { removeNullCondition, useDesignable } from '../../../../schema-component';
import { SchemaSettingsBlockTitleItem, SchemaSettingsTemplate } from '../../../../schema-settings';
import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope';
import { setDataLoadingModeSettingsItem, useDataLoadingMode } from './setDataLoadingModeSettingsItem';
export const multiDataDetailsBlockSettings = new SchemaSettings({
name: 'blockSettings:multiDataDetails',
items: [
{
name: 'title',
Component: SchemaSettingsBlockTitleItem,
},
{
name: 'dataScope',
Component: SchemaSettingsDataScope,
useComponentProps() {
const { name } = useCollection_deprecated();
const fieldSchema = useFieldSchema();
const { form } = useFormBlockContext();
const field = useField();
const { service } = useDetailsBlockContext();
const { dn } = useDesignable();
const dataLoadingMode = useDataLoadingMode();
return {
collectionName: name,
defaultFilter: fieldSchema?.['x-decorator-props']?.params?.filter || {},
form,
onSubmit: ({ filter }) => {
filter = removeNullCondition(filter);
const params = field.decoratorProps.params || {};
params.filter = filter;
field.decoratorProps.params = params;
fieldSchema['x-decorator-props']['params'] = params;
if (dataLoadingMode === 'auto') {
service.run({ ...service.params?.[0], filter });
}
dn.emit('patch', {
schema: {
['x-uid']: fieldSchema['x-uid'],
'x-decorator-props': fieldSchema['x-decorator-props'],
},
});
},
};
},
},
{
name: 'sortingRules',
type: 'modal',
useComponentProps() {
const { name } = useCollection_deprecated();
const { t } = useTranslation();
const fieldSchema = useFieldSchema();
const field = useField();
const { service } = useDetailsBlockContext();
const { dn } = useDesignable();
const sortFields = useSortFields(name);
const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
const sort = defaultSort?.map((item: string) => {
return item.startsWith('-')
? {
field: item.substring(1),
direction: 'desc',
}
: {
field: item,
direction: 'asc',
};
});
return {
title: t('Set default sorting rules'),
components: {
ArrayItems,
},
schema: {
type: 'object',
title: t('Set default sorting rules'),
properties: {
sort: {
type: 'array',
default: sort,
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
items: {
type: 'object',
properties: {
space: {
type: 'void',
'x-component': 'Space',
properties: {
sort: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.SortHandle',
},
field: {
type: 'string',
enum: sortFields,
required: true,
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-component-props': {
style: {
width: 260,
},
},
},
direction: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Radio.Group',
'x-component-props': {
optionType: 'button',
},
enum: [
{
label: t('ASC'),
value: 'asc',
},
{
label: t('DESC'),
value: 'desc',
},
],
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
},
},
properties: {
add: {
type: 'void',
title: t('Add sort field'),
'x-component': 'ArrayItems.Addition',
},
},
},
},
} as ISchema,
onSubmit({ sort }) {
const sortArr = sort.map((item) => {
return item.direction === 'desc' ? `-${item.field}` : item.field;
});
const params = field.decoratorProps.params || {};
params.sort = sortArr;
field.decoratorProps.params = params;
fieldSchema['x-decorator-props']['params'] = params;
dn.emit('patch', {
schema: {
['x-uid']: fieldSchema['x-uid'],
'x-decorator-props': fieldSchema['x-decorator-props'],
},
});
service.run({ ...service.params?.[0], sort: sortArr });
},
};
},
},
setDataLoadingModeSettingsItem,
{
name: 'template',
Component: SchemaSettingsTemplate,
useComponentProps() {
const { name } = useCollection_deprecated();
const fieldSchema = useFieldSchema();
const defaultResource =
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
return {
componentName: 'Details',
collectionName: name,
resourceName: defaultResource,
};
},
},
{
name: 'divider',
type: 'divider',
},
{
name: 'remove',
type: 'remove',
componentProps: {
removeParentsIfNoChildren: true,
breakRemoveOn: {
'x-component': 'Grid',
},
},
},
],
});

View File

@ -47,6 +47,7 @@ const AssociatedFields = () => {
/**
* @deprecated
* 使 readPrettyFormItemInitializers
*/
export const readPrettyFormItemInitializers_deprecated = new CompatibleSchemaInitializer({
name: 'ReadPrettyFormItemInitializers',

View File

@ -1,10 +1,11 @@
import { FormOutlined } from '@ant-design/icons';
import React, { useCallback } from 'react';
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
import { useBlockAssociationContext, useBlockRequestContext } from '../../../../block-provider';
import { useBlockAssociationContext } from '../../../../block-provider';
import { useCollection_deprecated } from '../../../../collection-manager';
import { useSchemaTemplateManager } from '../../../../schema-templates';
import { createDetailsBlockSchema, useRecordCollectionDataSourceItems } from '../../../../schema-initializer/utils';
import { useRecordCollectionDataSourceItems } from '../../../../schema-initializer/utils';
import { createDetailsUISchema } from './createDetailsUISchema';
export const RecordReadPrettyFormBlockInitializer = () => {
const itemConfig = useSchemaInitializerItem();
@ -24,29 +25,27 @@ export const RecordReadPrettyFormBlockInitializer = () => {
};
export function useCreateSingleDetailsSchema() {
const itemConfig = useSchemaInitializerItem();
const { insert } = useSchemaInitializer();
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
const association = useBlockAssociationContext();
const { block } = useBlockRequestContext();
const actionInitializers =
block !== 'TableField' ? itemConfig.actionInitializers || 'details:configureActions' : null;
const templateWrap = useCallback(
(templateSchema, options) => {
const { item } = options;
if (item.template.componentName === 'ReadPrettyFormItem') {
const blockSchema = createDetailsBlockSchema({
actionInitializers,
association,
collection: item.collectionName || item.name,
dataSource: item.dataSource,
action: 'get',
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
template: templateSchema,
settings: 'blockSettings:singleDataDetails',
});
const blockSchema = createDetailsUISchema(
association
? {
association,
dataSource: item.dataSource,
templateSchema: templateSchema,
}
: {
collectionName: item.collectionName || item.name,
dataSource: item.dataSource,
templateSchema: templateSchema,
},
);
if (item.mode === 'reference') {
blockSchema['x-template-key'] = item.template.key;
}
@ -55,7 +54,7 @@ export function useCreateSingleDetailsSchema() {
return templateSchema;
}
},
[actionInitializers, association],
[association],
);
const createSingleDetailsSchema = useCallback(
@ -65,20 +64,21 @@ export function useCreateSingleDetailsSchema() {
insert(templateWrap(template, { item }));
} else {
insert(
createDetailsBlockSchema({
actionInitializers,
association,
collection: item.collectionName || item.name,
dataSource: item.dataSource,
action: 'get',
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
settings: 'blockSettings:singleDataDetails',
}),
createDetailsUISchema(
association
? {
association,
dataSource: item.dataSource,
}
: {
collectionName: item.collectionName || item.name,
dataSource: item.dataSource,
},
),
);
}
},
[actionInitializers, association, getTemplateSchemaByMode, insert, templateWrap],
[association, getTemplateSchemaByMode, insert, templateWrap],
);
return { createSingleDetailsSchema, templateWrap };

View File

@ -0,0 +1,62 @@
import { createDetailsUISchema } from '../createDetailsUISchema';
// Mock uid to always return a fixed value
vi.mock('@formily/shared', () => ({
uid: () => 'fixed-uid',
}));
describe('createDetailsBlockWithoutPagingUISchema', () => {
it('should create a valid schema with mandatory fields', () => {
const options = {
collectionName: 'users',
dataSource: 'usersDataSource',
};
const result = createDetailsUISchema(options);
expect(result).toMatchInlineSnapshot(`
{
"properties": {
"fixed-uid": {
"properties": {
"fixed-uid": {
"properties": {},
"type": "void",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": 24,
},
},
"x-initializer": "details:configureActions",
},
"grid": {
"properties": {},
"type": "void",
"x-component": "Grid",
"x-initializer": "details:configureFields",
},
},
"type": "void",
"x-component": "Details",
"x-read-pretty": true,
"x-use-component-props": "useDetailsProps",
},
},
"type": "void",
"x-acl-action": "users:get",
"x-component": "CardItem",
"x-decorator": "DetailsBlockProvider",
"x-decorator-props": {
"action": "get",
"association": undefined,
"collection": "users",
"dataSource": "usersDataSource",
"readPretty": true,
},
"x-settings": "blockSettings:details",
"x-toolbar": "BlockSchemaToolbar",
"x-use-decorator-props": "useDetailsDecoratorProps",
}
`);
});
});

View File

@ -0,0 +1,60 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
export function createDetailsUISchema(options: {
dataSource: string;
collectionName?: string;
association?: string;
templateSchema?: ISchema;
}): ISchema {
const { collectionName, dataSource, association, templateSchema } = options;
const resourceName = association || collectionName;
if (!dataSource) {
throw new Error('dataSource are required');
}
return {
type: 'void',
'x-acl-action': `${resourceName}:get`,
'x-decorator': 'DetailsBlockProvider',
'x-use-decorator-props': 'useDetailsDecoratorProps',
'x-decorator-props': {
dataSource,
collection: collectionName,
association,
readPretty: true,
action: 'get',
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:details',
'x-component': 'CardItem',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Details',
'x-read-pretty': true,
'x-use-component-props': 'useDetailsProps',
properties: {
[uid()]: {
type: 'void',
'x-initializer': 'details:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 24,
},
},
properties: {},
},
grid: templateSchema || {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'details:configureFields',
properties: {},
},
},
},
},
};
}

View File

@ -0,0 +1,56 @@
import { useFieldSchema } from '@formily/react';
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
import { useCollection_deprecated } from '../../../../collection-manager';
import { SchemaSettingsBlockTitleItem, SchemaSettingsFormItemTemplate } from '../../../../schema-settings';
import { SchemaSettingsItemType } from '../../../../application/schema-settings/types';
const commonItems: SchemaSettingsItemType[] = [
{
name: 'title',
Component: SchemaSettingsBlockTitleItem,
},
{
name: 'formItemTemplate',
Component: SchemaSettingsFormItemTemplate,
useComponentProps() {
const { name } = useCollection_deprecated();
const fieldSchema = useFieldSchema();
const defaultResource =
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
return {
insertAdjacentPosition: 'beforeEnd',
componentName: 'ReadPrettyFormItem',
collectionName: name,
resourceName: defaultResource,
};
},
},
{
name: 'divider',
type: 'divider',
},
{
name: 'remove',
type: 'remove',
componentProps: {
removeParentsIfNoChildren: true,
breakRemoveOn: {
'x-component': 'Grid',
},
},
},
];
/**
* @deprecated
* 使 detailsBlockSettings
*/
export const singleDataDetailsBlockSettings = new SchemaSettings({
name: 'blockSettings:singleDataDetails',
items: commonItems,
});
export const detailsBlockSettings = new SchemaSettings({
name: 'blockSettings:details',
items: commonItems,
});

View File

@ -0,0 +1,18 @@
import { useParamsFromRecord } from '../../../../../block-provider/BlockProvider';
import { useDataBlockSourceId } from '../../../../../block-provider/hooks/useDataBlockSourceId';
export function useDetailsDecoratorProps(props) {
const params = useParamsFromRecord();
let sourceId;
// association 的值是固定不变的,所以可以在条件中使用 hooks
if (props.association) {
// eslint-disable-next-line react-hooks/rules-of-hooks
sourceId = useDataBlockSourceId({ association: props.association });
}
return {
params,
sourceId,
};
}

View File

@ -0,0 +1,5 @@
import { useDetailsBlockProps } from '../../../../../block-provider/DetailsBlockProvider';
export function useDetailsProps() {
return useDetailsBlockProps();
}

View File

@ -1,44 +0,0 @@
import { useFieldSchema } from '@formily/react';
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
import { useCollection_deprecated } from '../../../../collection-manager';
import { SchemaSettingsBlockTitleItem, SchemaSettingsFormItemTemplate } from '../../../../schema-settings';
export const singleDataDetailsBlockSettings = new SchemaSettings({
name: 'blockSettings:singleDataDetails',
items: [
{
name: 'title',
Component: SchemaSettingsBlockTitleItem,
},
{
name: 'formItemTemplate',
Component: SchemaSettingsFormItemTemplate,
useComponentProps() {
const { name } = useCollection_deprecated();
const fieldSchema = useFieldSchema();
const defaultResource =
fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association;
return {
insertAdjacentPosition: 'beforeEnd',
componentName: 'ReadPrettyFormItem',
collectionName: name,
resourceName: defaultResource,
};
},
},
{
name: 'divider',
type: 'divider',
},
{
name: 'remove',
type: 'remove',
componentProps: {
removeParentsIfNoChildren: true,
breakRemoveOn: {
'x-component': 'Grid',
},
},
},
],
});

View File

@ -88,7 +88,7 @@ test.describe('where to open a popup and what can be added to it', () => {
// 删除已创建的 blocks腾出页面空间
// delete details block
await page.getByText('GeneralConfigure actionsConfigure fields').hover();
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:singleDataDetails-general').hover();
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:details-general').hover();
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
// delete form block

View File

@ -3,8 +3,9 @@ import { FormV2 } from '../form-v2';
import _ from 'lodash';
import { Empty } from 'antd';
import { useDataBlockRequest } from '../../../data-source';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
export const Details = (props) => {
export const Details = withDynamicSchemaProps((props) => {
const request = useDataBlockRequest();
if (!request?.loading && _.isEmpty(request?.data?.data)) {
@ -12,4 +13,4 @@ export const Details = (props) => {
}
return <FormV2 {...props} />;
};
});

View File

@ -1,15 +0,0 @@
import { render } from '@nocobase/test/client';
import React from 'react';
import { Pagination } from '../index';
describe('Pagination', () => {
it('should render correctly', () => {
render(<Pagination />);
expect(document.querySelector('.ant-pagination')).toBeInTheDocument();
});
it('hidden', () => {
render(<Pagination hidden />);
expect(document.querySelector('.ant-pagination')).not.toBeInTheDocument();
});
});

View File

@ -2,24 +2,29 @@ import { observer } from '@formily/react';
import { Pagination as AntdPagination } from 'antd';
import React, { KeyboardEventHandler } from 'react';
import { useProps } from '../../hooks/useProps';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
export const Pagination = observer(
(props: any) => {
const { hidden, ...others } = useProps(props);
if (hidden) {
return null;
}
const onKeypress: KeyboardEventHandler<HTMLDivElement> = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
export const Pagination = withDynamicSchemaProps(
observer(
(props: any) => {
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { hidden, ...others } = useProps(props);
if (hidden) {
return null;
}
};
const onKeypress: KeyboardEventHandler<HTMLDivElement> = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
}
};
return (
<div onKeyPress={onKeypress}>
<AntdPagination {...others} />
</div>
);
},
{ displayName: 'Pagination' },
return (
<div onKeyPress={onKeypress}>
<AntdPagination {...others} />
</div>
);
},
{ displayName: 'Pagination' },
),
);

View File

@ -4,7 +4,8 @@ import React, { useCallback } from 'react';
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
import { useCollectionManager_deprecated } from '../../collection-manager';
import { useSchemaTemplateManager } from '../../schema-templates';
import { createDetailsBlockSchema, useRecordCollectionDataSourceItems } from '../utils';
import { useRecordCollectionDataSourceItems } from '../utils';
import { createDetailsWithPaginationUISchema } from '../../modules/blocks/data-blocks/details-multi/createDetailsWithPaginationUISchema';
export const RecordAssociationDetailsBlockInitializer = () => {
const itemConfig = useSchemaInitializerItem();
@ -25,13 +26,10 @@ export const RecordAssociationDetailsBlockInitializer = () => {
insert(s);
} else {
insert(
createDetailsBlockSchema({
collection: field.target,
resource,
createDetailsWithPaginationUISchema({
dataSource: collection.dataSource,
association: resource,
rowKey: collection.filterTargetKey || 'id',
settings: 'blockSettings:multiDataDetails',
}),
);
}
@ -51,12 +49,10 @@ export function useCreateAssociationDetailsBlock() {
const collection = getCollection(field.target);
insert(
createDetailsBlockSchema({
collection: field.target,
createDetailsWithPaginationUISchema({
dataSource: collection.dataSource,
association: `${field.collectionName}.${field.name}`,
rowKey: collection.filterTargetKey || 'id',
settings: 'blockSettings:multiDataDetails',
}),
);
},

View File

@ -2,10 +2,10 @@ import { FormOutlined } from '@ant-design/icons';
import React, { useCallback } from 'react';
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
import { useBlockRequestContext } from '../../block-provider';
import { useSchemaTemplateManager } from '../../schema-templates';
import { createDetailsBlockSchema, useRecordCollectionDataSourceItems } from '../utils';
import { useRecordCollectionDataSourceItems } from '../utils';
import { useCollectionManager_deprecated } from '../../collection-manager';
import { createDetailsUISchema } from '../../modules/blocks/data-blocks/details-single/createDetailsUISchema';
/**
* @deprecated
@ -22,8 +22,6 @@ export const RecordReadPrettyAssociationFormBlockInitializer = () => {
const collection = getCollection(collectionName);
const resource = `${field.collectionName}.${field.name}`;
const { block } = useBlockRequestContext();
const actionInitializers = block !== 'TableField' ? 'details:configureActions' : null;
return (
<SchemaInitializerItem
@ -33,17 +31,10 @@ export const RecordReadPrettyAssociationFormBlockInitializer = () => {
if (item.template) {
const s = await getTemplateSchemaByMode(item);
if (item.template.componentName === 'ReadPrettyFormItem') {
const blockSchema = createDetailsBlockSchema({
actionInitializers,
collection: collectionName,
const blockSchema = createDetailsUISchema({
dataSource: collection.dataSource,
resource,
association: resource,
action: 'get',
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
template: s,
settings: 'blockSettings:singleDataDetails',
templateSchema: s,
});
if (item.mode === 'reference') {
blockSchema['x-template-key'] = item.template.key;
@ -54,16 +45,9 @@ export const RecordReadPrettyAssociationFormBlockInitializer = () => {
}
} else {
insert(
createDetailsBlockSchema({
actionInitializers,
collection: collectionName,
resource,
createDetailsUISchema({
association: resource,
dataSource: collection.dataSource,
action: 'get',
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
settings: 'blockSettings:singleDataDetails',
}),
);
}
@ -76,8 +60,6 @@ export const RecordReadPrettyAssociationFormBlockInitializer = () => {
export function useCreateAssociationDetailsWithoutPagination() {
const { insert } = useSchemaInitializer();
const { getCollection } = useCollectionManager_deprecated();
const { block } = useBlockRequestContext();
const actionInitializers = block !== 'TableField' ? 'details:configureActions' : null;
const createAssociationDetailsWithoutPagination = useCallback(
({ item }) => {
@ -85,19 +67,13 @@ export function useCreateAssociationDetailsWithoutPagination() {
const collection = getCollection(field.target);
insert(
createDetailsBlockSchema({
actionInitializers,
collection: field.target,
createDetailsUISchema({
dataSource: collection.dataSource,
association: `${field.collectionName}.${field.name}`,
action: 'get',
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
settings: 'blockSettings:singleDataDetails',
}),
);
},
[actionInitializers, getCollection, insert],
[getCollection, insert],
);
const templateWrap = useCallback(
@ -106,16 +82,10 @@ export function useCreateAssociationDetailsWithoutPagination() {
const collection = getCollection(field.target);
if (item.template.componentName === 'ReadPrettyFormItem') {
const blockSchema = createDetailsBlockSchema({
actionInitializers,
collection: field.target,
const blockSchema = createDetailsUISchema({
dataSource: collection.dataSource,
association: `${field.collectionName}.${field.name}`,
action: 'get',
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
template: templateSchema,
settings: 'blockSettings:singleDataDetails',
templateSchema: templateSchema,
});
if (item.mode === 'reference') {
blockSchema['x-template-key'] = item.template.key;
@ -125,7 +95,7 @@ export function useCreateAssociationDetailsWithoutPagination() {
return templateSchema;
}
},
[actionInitializers, getCollection],
[getCollection],
);
return { createAssociationDetailsWithoutPagination, templateWrap };

View File

@ -893,7 +893,18 @@ export const useCollectionDataSourceItems = ({
return res;
};
export const createDetailsBlockSchema = (options) => {
export const createDetailsBlockSchema = (options: {
collection: string;
dataSource: string;
rowKey?: string;
formItemInitializers?: string;
actionInitializers?: string;
association?: string;
template?: any;
settings?: string;
action?: string;
[key: string]: any;
}) => {
const {
formItemInitializers = 'details:configureFields',
actionInitializers = 'detailsWithPaging:configureActions',

View File

@ -15,8 +15,14 @@ import { customizeUpdateRecordActionSettings } from '../modules/actions/update-r
import { customizePopupActionSettings } from '../modules/actions/view-edit-popup/customizePopupActionSettings';
import { editActionSettings } from '../modules/actions/view-edit-popup/editActionSettings';
import { viewActionSettings } from '../modules/actions/view-edit-popup/viewActionSettings';
import { multiDataDetailsBlockSettings } from '../modules/blocks/data-blocks/details-multi/multiDataDetailsBlockSettings';
import { singleDataDetailsBlockSettings } from '../modules/blocks/data-blocks/details-single/singleDataDetailsBlockSettings';
import {
detailsWithPaginationSettings,
multiDataDetailsBlockSettings,
} from '../modules/blocks/data-blocks/details-multi/detailsWithPaginationSettings';
import {
detailsBlockSettings,
singleDataDetailsBlockSettings,
} from '../modules/blocks/data-blocks/details-single/detailsBlockSettings';
import {
createFormBlockSettings,
creationFormBlockSettings,
@ -56,7 +62,9 @@ export class SchemaSettingsPlugin extends Plugin {
this.schemaSettingsManager.add(filterFormBlockSettings);
this.schemaSettingsManager.add(filterFormItemFieldSettings);
this.schemaSettingsManager.add(multiDataDetailsBlockSettings);
this.schemaSettingsManager.add(detailsWithPaginationSettings);
this.schemaSettingsManager.add(singleDataDetailsBlockSettings);
this.schemaSettingsManager.add(detailsBlockSettings);
this.schemaSettingsManager.add(tableSelectorBlockSettings);
this.schemaSettingsManager.add(listBlockSettings);
this.schemaSettingsManager.add(gridCardBlockSettings);