Merge remote-tracking branch 'upstream/main' into fix_new_version

This commit is contained in:
sealday 2024-03-27 19:27:21 +08:00
commit 86ffb8f4a9
75 changed files with 2296 additions and 894 deletions

View File

@ -1,9 +1,14 @@
import React from 'react'; import React from 'react';
import { Plugin } from '../application/Plugin'; import { Plugin } from '../application/Plugin';
import { ActionSchemaToolbar } from '../modules/actions/ActionSchemaToolbar'; import { ActionSchemaToolbar } from '../modules/actions/ActionSchemaToolbar';
import { CollapseItemSchemaToolbar } from '../modules/blocks/filter-blocks/collapse/CollapseItemSchemaToolbar'; import { BlockSchemaToolbar } from '../modules/blocks/BlockSchemaToolbar';
import { FormItemSchemaToolbar } from '../modules/blocks/data-blocks/form/FormItemSchemaToolbar'; import { FormItemSchemaToolbar } from '../modules/blocks/data-blocks/form/FormItemSchemaToolbar';
import { useCreateFormBlockDecoratorProps } from '../modules/blocks/data-blocks/form/hooks/useCreateFormBlockDecoratorProps';
import { useCreateFormBlockProps } from '../modules/blocks/data-blocks/form/hooks/useCreateFormBlockProps';
import { useEditFormBlockDecoratorProps } from '../modules/blocks/data-blocks/form/hooks/useEditFormBlockDecoratorProps';
import { useEditFormBlockProps } from '../modules/blocks/data-blocks/form/hooks/useEditFormBlockProps';
import { TableColumnSchemaToolbar } from '../modules/blocks/data-blocks/table/TableColumnSchemaToolbar'; import { TableColumnSchemaToolbar } from '../modules/blocks/data-blocks/table/TableColumnSchemaToolbar';
import { CollapseItemSchemaToolbar } from '../modules/blocks/filter-blocks/collapse/CollapseItemSchemaToolbar';
import { SchemaComponentOptions } from '../schema-component'; import { SchemaComponentOptions } from '../schema-component';
import { RecordLink, useParamsFromRecord, useSourceIdFromParentRecord, useSourceIdFromRecord } from './BlockProvider'; import { RecordLink, useParamsFromRecord, useSourceIdFromParentRecord, useSourceIdFromRecord } from './BlockProvider';
import { DetailsBlockProvider, useDetailsBlockProps } from './DetailsBlockProvider'; import { DetailsBlockProvider, useDetailsBlockProps } from './DetailsBlockProvider';
@ -15,8 +20,10 @@ import { useTableBlockProps } from '../modules/blocks/data-blocks/table/hooks/us
import { TableFieldProvider, useTableFieldProps } from './TableFieldProvider'; import { TableFieldProvider, useTableFieldProps } from './TableFieldProvider';
import { TableSelectorProvider, useTableSelectorProps } from './TableSelectorProvider'; import { TableSelectorProvider, useTableSelectorProps } from './TableSelectorProvider';
import * as bp from './hooks'; import * as bp from './hooks';
import { BlockSchemaToolbar } from '../modules/blocks/BlockSchemaToolbar';
import { useTableBlockDecoratorProps } from '../modules/blocks/data-blocks/table/hooks/useTableBlockDecoratorProps'; import { useTableBlockDecoratorProps } from '../modules/blocks/data-blocks/table/hooks/useTableBlockDecoratorProps';
import { useListBlockDecoratorProps } from '../modules/blocks/data-blocks/list/hooks/useListBlockDecoratorProps';
import { useTableSelectorDecoratorProps } from '../modules/blocks/data-blocks/table-selector/hooks/useTableSelectorDecoratorProps';
import { useCollapseBlockDecoratorProps } from '../modules/blocks/filter-blocks/collapse/hooks/useCollapseBlockDecoratorProps';
// TODO: delete this, replaced by `BlockSchemaComponentPlugin` // TODO: delete this, replaced by `BlockSchemaComponentPlugin`
export const BlockSchemaComponentProvider: React.FC = (props) => { export const BlockSchemaComponentProvider: React.FC = (props) => {
@ -38,12 +45,19 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
useSourceIdFromParentRecord, useSourceIdFromParentRecord,
useParamsFromRecord, useParamsFromRecord,
useFormBlockProps, useFormBlockProps,
useCreateFormBlockProps,
useCreateFormBlockDecoratorProps,
useEditFormBlockDecoratorProps,
useEditFormBlockProps,
useFormFieldProps, useFormFieldProps,
useDetailsBlockProps, useDetailsBlockProps,
useTableFieldProps, useTableFieldProps,
useTableBlockProps, useTableBlockProps,
useTableSelectorProps, useTableSelectorProps,
useTableBlockDecoratorProps, useTableBlockDecoratorProps,
useListBlockDecoratorProps,
useTableSelectorDecoratorProps,
useCollapseBlockDecoratorProps,
}} }}
> >
{props.children} {props.children}
@ -82,12 +96,19 @@ export class BlockSchemaComponentPlugin extends Plugin {
useSourceIdFromParentRecord, useSourceIdFromParentRecord,
useParamsFromRecord, useParamsFromRecord,
useFormBlockProps, useFormBlockProps,
useCreateFormBlockProps,
useCreateFormBlockDecoratorProps,
useEditFormBlockDecoratorProps,
useEditFormBlockProps,
useFormFieldProps, useFormFieldProps,
useDetailsBlockProps, useDetailsBlockProps,
useTableFieldProps, useTableFieldProps,
useTableBlockProps, useTableBlockProps,
useTableSelectorProps, useTableSelectorProps,
useTableBlockDecoratorProps, useTableBlockDecoratorProps,
useListBlockDecoratorProps,
useTableSelectorDecoratorProps,
useCollapseBlockDecoratorProps,
}); });
} }
} }

View File

@ -10,6 +10,7 @@ import { Templates as DataTemplateSelect } from '../schema-component/antd/form-v
import { BlockProvider, useBlockRequestContext } from './BlockProvider'; import { BlockProvider, useBlockRequestContext } from './BlockProvider';
import { TemplateBlockProvider } from './TemplateBlockProvider'; import { TemplateBlockProvider } from './TemplateBlockProvider';
import { FormActiveFieldsProvider } from './hooks/useFormActiveFields'; import { FormActiveFieldsProvider } from './hooks/useFormActiveFields';
import { withDynamicSchemaProps } from '../application/hoc/withDynamicSchemaProps';
export const FormBlockContext = createContext<{ export const FormBlockContext = createContext<{
form?: any; form?: any;
@ -29,7 +30,7 @@ FormBlockContext.displayName = 'FormBlockContext';
const InternalFormBlockProvider = (props) => { const InternalFormBlockProvider = (props) => {
const ctx = useFormBlockContext(); const ctx = useFormBlockContext();
const { action, readPretty, params, association, collection } = props; const { action, readPretty, params, collection } = props;
const field = useField(); const field = useField();
const form = useMemo( const form = useMemo(
() => () =>
@ -89,7 +90,7 @@ export const useIsDetailBlock = () => {
return ctx.type !== 'create' && fieldSchema?.['x-acl-action'] !== 'create' && fieldSchema?.['x-action'] !== 'create'; return ctx.type !== 'create' && fieldSchema?.['x-acl-action'] !== 'create' && fieldSchema?.['x-action'] !== 'create';
}; };
export const FormBlockProvider = (props) => { export const FormBlockProvider = withDynamicSchemaProps((props) => {
const record = useRecord(); const record = useRecord();
const parentRecordData = useCollectionParentRecordData(); const parentRecordData = useCollectionParentRecordData();
const { collection, isCusomeizeCreate } = props; const { collection, isCusomeizeCreate } = props;
@ -120,7 +121,7 @@ export const FormBlockProvider = (props) => {
</BlockProvider> </BlockProvider>
</TemplateBlockProvider> </TemplateBlockProvider>
); );
}; });
export const useFormBlockContext = () => { export const useFormBlockContext = () => {
return useContext(FormBlockContext); return useContext(FormBlockContext);

View File

@ -11,6 +11,7 @@ import { RecordProvider, useRecord } from '../record-provider';
import { SchemaComponentOptions } from '../schema-component'; import { SchemaComponentOptions } from '../schema-component';
import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider'; import { BlockProvider, RenderChildrenWithAssociationFilter, useBlockRequestContext } from './BlockProvider';
import { useParsedFilter } from './hooks'; import { useParsedFilter } from './hooks';
import { withDynamicSchemaProps } from '../application/hoc/withDynamicSchemaProps';
type Params = { type Params = {
filter?: any; filter?: any;
@ -141,7 +142,7 @@ const useAssociationNames = (collection) => {
); );
}; };
export const TableSelectorProvider = (props: TableSelectorProviderProps) => { export const TableSelectorProvider = withDynamicSchemaProps((props: TableSelectorProviderProps) => {
const parentParams = useTableSelectorParams(); const parentParams = useTableSelectorParams();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const { getCollectionJoinField, getCollectionFields } = useCollectionManager_deprecated(); const { getCollectionJoinField, getCollectionFields } = useCollectionManager_deprecated();
@ -263,7 +264,7 @@ export const TableSelectorProvider = (props: TableSelectorProviderProps) => {
</BlockProvider> </BlockProvider>
</SchemaComponentOptions> </SchemaComponentOptions>
); );
}; });
export const useTableSelectorContext = () => { export const useTableSelectorContext = () => {
return useContext(TableSelectorContext); return useContext(TableSelectorContext);

View File

@ -36,6 +36,7 @@ import { TableFieldResource } from '../TableFieldProvider';
export * from './useFormActiveFields'; export * from './useFormActiveFields';
export * from './useParsedFilter'; export * from './useParsedFilter';
export * from './useDataBlockSourceId';
export const usePickActionProps = () => { export const usePickActionProps = () => {
const form = useForm(); const form = useForm();

View File

@ -7,6 +7,7 @@ import {
} from '../..'; } from '../..';
/** /**
* @internal
* schema sourceId * schema sourceId
* recordData parentRecordData ; schema * recordData parentRecordData ; schema
* `通过点击关系字段按钮打开的弹窗中创建的非关系字段区块``关系字段区块`使 hook * `通过点击关系字段按钮打开的弹窗中创建的非关系字段区块``关系字段区块`使 hook

View File

@ -4,8 +4,9 @@ import React from 'react';
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../../../application'; import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
import { useBlockAssociationContext } from '../../../../block-provider'; import { useBlockAssociationContext } from '../../../../block-provider';
import { useCollection_deprecated } from '../../../../collection-manager'; import { useCollection_deprecated } from '../../../../collection-manager';
import { useRecordCollectionDataSourceItems } from '../../../../schema-initializer/utils';
import { useSchemaTemplateManager } from '../../../../schema-templates'; import { useSchemaTemplateManager } from '../../../../schema-templates';
import { createFormBlockSchema, useRecordCollectionDataSourceItems } from '../../../../schema-initializer/utils'; import { createCreateFormBlockUISchema } from './createCreateFormBlockUISchema';
// TODO: `SchemaInitializerItem` items // TODO: `SchemaInitializerItem` items
export const CreateFormBlockInitializer = () => { export const CreateFormBlockInitializer = () => {
@ -23,14 +24,19 @@ export const CreateFormBlockInitializer = () => {
if (item.template) { if (item.template) {
const s = await getTemplateSchemaByMode(item); const s = await getTemplateSchemaByMode(item);
if (item.template.componentName === 'FormItem') { if (item.template.componentName === 'FormItem') {
const blockSchema = createFormBlockSchema({ const blockSchema = createCreateFormBlockUISchema(
actionInitializers: 'createForm:configureActions', association
association, ? {
dataSource: collection.dataSource, association,
collection: collection.name, dataSource: collection.dataSource,
template: s, templateSchema: s,
settings: 'blockSettings:createForm', }
}); : {
collectionName: collection.name,
dataSource: collection.dataSource,
templateSchema: s,
},
);
if (item.mode === 'reference') { if (item.mode === 'reference') {
blockSchema['x-template-key'] = item.template.key; blockSchema['x-template-key'] = item.template.key;
} }
@ -40,13 +46,17 @@ export const CreateFormBlockInitializer = () => {
} }
} else { } else {
insert( insert(
createFormBlockSchema({ createCreateFormBlockUISchema(
actionInitializers: 'createForm:configureActions', association
association, ? {
dataSource: collection.dataSource, association,
collection: collection.name, dataSource: collection.dataSource,
settings: 'blockSettings:createForm', }
}), : {
collectionName: collection.name,
dataSource: collection.dataSource,
},
),
); );
} }
}} }}

View File

@ -1,9 +1,9 @@
import { FormOutlined } from '@ant-design/icons'; import { FormOutlined } from '@ant-design/icons';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application'; import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
import { createFormBlockSchema } from '../../../../schema-initializer/utils';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection'; import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { createCreateFormBlockUISchema } from './createCreateFormBlockUISchema';
export const FormBlockInitializer = ({ export const FormBlockInitializer = ({
filterCollections, filterCollections,
@ -44,11 +44,10 @@ export const FormBlockInitializer = ({
} }
insert( insert(
createFormBlockSchema({ createCreateFormBlockUISchema({
collection: item.collectionName || item.name, collectionName: item.collectionName || item.name,
dataSource: item.dataSource, dataSource: item.dataSource,
isCusomeizeCreate, isCusomeizeCreate,
settings: 'blockSettings:createForm',
}), }),
); );
}, },
@ -65,12 +64,11 @@ export const FormBlockInitializer = ({
return templateWrap(templateSchema, { item }); return templateWrap(templateSchema, { item });
} }
const schema = createFormBlockSchema({ const schema = createCreateFormBlockUISchema({
isCusomeizeCreate, isCusomeizeCreate,
dataSource: item.dataSource, dataSource: item.dataSource,
template: templateSchema, templateSchema: templateSchema,
collection: item.name, collectionName: item.name,
settings: 'blockSettings:createForm',
}); });
if (item.template && item.mode === 'reference') { if (item.template && item.mode === 'reference') {
schema['x-template-key'] = item.template.key; schema['x-template-key'] = item.template.key;

View File

@ -3,8 +3,9 @@ import React, { useCallback } from 'react';
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../../../application'; import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
import { useBlockAssociationContext } from '../../../../block-provider'; import { useBlockAssociationContext } from '../../../../block-provider';
import { useCollection_deprecated } from '../../../../collection-manager'; import { useCollection_deprecated } from '../../../../collection-manager';
import { useRecordCollectionDataSourceItems } from '../../../../schema-initializer/utils';
import { useSchemaTemplateManager } from '../../../../schema-templates'; import { useSchemaTemplateManager } from '../../../../schema-templates';
import { createFormBlockSchema, useRecordCollectionDataSourceItems } from '../../../../schema-initializer/utils'; import { createEditFormBlockUISchema } from './createEditFormBlockUISchema';
/** /**
* @deprecated * @deprecated
@ -43,16 +44,17 @@ export function useCreateEditFormBlock() {
const createEditFormBlock = useCallback( const createEditFormBlock = useCallback(
({ item }) => { ({ item }) => {
insert( insert(
createFormBlockSchema({ createEditFormBlockUISchema(
association, association
collection: item.collectionName || item.name, ? {
dataSource: item.dataSource, association,
action: 'get', dataSource: item.dataSource,
useSourceId: '{{ useSourceIdFromParentRecord }}', }
useParams: '{{ useParamsFromRecord }}', : {
actionInitializers: 'editForm:configureActions', collectionName: item.collectionName || item.name,
settings: 'blockSettings:editForm', dataSource: item.dataSource,
}), },
),
); );
}, },
[association, insert], [association, insert],
@ -61,17 +63,19 @@ export function useCreateEditFormBlock() {
const templateWrap = useCallback( const templateWrap = useCallback(
(templateSchema, { item }) => { (templateSchema, { item }) => {
if (item.template.componentName === 'FormItem') { if (item.template.componentName === 'FormItem') {
const blockSchema = createFormBlockSchema({ const blockSchema = createEditFormBlockUISchema(
association, association
collection: item.collectionName || item.name, ? {
dataSource: item.dataSource, association,
action: 'get', dataSource: item.dataSource,
useSourceId: '{{ useSourceIdFromParentRecord }}', templateSchema: templateSchema,
useParams: '{{ useParamsFromRecord }}', }
actionInitializers: 'editForm:configureActions', : {
template: templateSchema, collectionName: item.collectionName || item.name,
settings: 'blockSettings:editForm', dataSource: item.dataSource,
}); templateSchema: templateSchema,
},
);
if (item.mode === 'reference') { if (item.mode === 'reference') {
blockSchema['x-template-key'] = item.template.key; blockSchema['x-template-key'] = item.template.key;
} }

View File

@ -0,0 +1,64 @@
import { CreateFormBlockUISchemaOptions, createCreateFormBlockUISchema } from '../createCreateFormBlockUISchema';
vi.mock('@formily/shared', () => {
return {
uid: () => 'mocked-uid',
};
});
describe('createCreateFormBlockUISchema', () => {
it('should create a schema with all options', () => {
const options: CreateFormBlockUISchemaOptions = {
collectionName: 'users',
dataSource: 'userDataSource',
association: 'userAssociation',
templateSchema: { type: 'string' },
isCusomeizeCreate: true,
};
const result = createCreateFormBlockUISchema(options);
expect(result).toMatchInlineSnapshot(`
{
"properties": {
"mocked-uid": {
"properties": {
"grid": {
"type": "string",
},
"mocked-uid": {
"type": "void",
"x-component": "ActionBar",
"x-component-props": {
"layout": "one-column",
"style": {
"marginTop": 24,
},
},
"x-initializer": "createForm:configureActions",
},
},
"type": "void",
"x-component": "FormV2",
"x-use-component-props": "useCreateFormBlockProps",
},
},
"type": "void",
"x-acl-action": "userAssociation:create",
"x-acl-action-props": {
"skipScopeCheck": true,
},
"x-component": "CardItem",
"x-decorator": "FormBlockProvider",
"x-decorator-props": {
"association": "userAssociation",
"collection": "users",
"dataSource": "userDataSource",
"isCusomeizeCreate": true,
},
"x-settings": "blockSettings:createForm",
"x-toolbar": "BlockSchemaToolbar",
"x-use-decorator-props": "useCreateFormBlockDecoratorProps",
}
`);
});
});

View File

@ -0,0 +1,62 @@
import { ISchema } from '@formily/react';
import { vi } from 'vitest';
import { createEditFormBlockUISchema } from '../createEditFormBlockUISchema';
vi.mock('@formily/shared', () => ({
uid: () => 'uniqueId',
}));
describe('createEditFormBlockUISchema', () => {
it('should create a schema with all options', () => {
const schema = createEditFormBlockUISchema({
collectionName: 'users',
dataSource: 'UserDataSource',
association: 'users:update',
templateSchema: { type: 'string' },
});
expect(schema).toMatchInlineSnapshot(`
{
"properties": {
"uniqueId": {
"properties": {
"grid": {
"type": "string",
},
"uniqueId": {
"type": "void",
"x-component": "ActionBar",
"x-component-props": {
"layout": "one-column",
"style": {
"marginTop": 24,
},
},
"x-initializer": "editForm:configureActions",
},
},
"type": "void",
"x-component": "FormV2",
"x-use-component-props": "useEditFormBlockProps",
},
},
"type": "void",
"x-acl-action": "users:update:update",
"x-acl-action-props": {
"skipScopeCheck": false,
},
"x-component": "CardItem",
"x-decorator": "FormBlockProvider",
"x-decorator-props": {
"action": "get",
"association": "users:update",
"collection": "users",
"dataSource": "UserDataSource",
},
"x-settings": "blockSettings:editForm",
"x-toolbar": "BlockSchemaToolbar",
"x-use-decorator-props": "useEditFormBlockDecoratorProps",
}
`);
});
});

View File

@ -0,0 +1,70 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
export interface CreateFormBlockUISchemaOptions {
dataSource: string;
/** 如果传了 association 就不需要再传 collectionName 了 */
collectionName?: string;
association?: string;
templateSchema?: ISchema;
isCusomeizeCreate?: boolean;
}
/**
* UI Schema
* @returns
*/
export function createCreateFormBlockUISchema(options: CreateFormBlockUISchemaOptions): ISchema {
const { collectionName, association, dataSource, templateSchema, isCusomeizeCreate } = options;
const resourceName = association || collectionName;
if (!dataSource) {
throw new Error('dataSource are required');
}
const schema: ISchema = {
type: 'void',
'x-acl-action-props': {
skipScopeCheck: true,
},
'x-acl-action': `${resourceName}:create`,
'x-decorator': 'FormBlockProvider',
'x-use-decorator-props': 'useCreateFormBlockDecoratorProps',
'x-decorator-props': {
dataSource,
collection: collectionName,
association,
isCusomeizeCreate,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:createForm',
'x-component': 'CardItem',
properties: {
[uid()]: {
type: 'void',
'x-component': 'FormV2',
'x-use-component-props': 'useCreateFormBlockProps',
properties: {
grid: templateSchema || {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'form:configureFields',
properties: {},
},
[uid()]: {
type: 'void',
'x-initializer': 'createForm:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
style: {
marginTop: 24,
},
},
},
},
},
},
};
return schema;
}

View File

@ -0,0 +1,64 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
interface EditFormBlockOptions {
dataSource: string;
/** 如果传了 association 就不需要再传 collectionName 了 */
collectionName?: string;
association?: string;
templateSchema?: ISchema;
}
export function createEditFormBlockUISchema(options: EditFormBlockOptions): 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-props': {
skipScopeCheck: false,
},
'x-acl-action': `${resourceName}:update`,
'x-decorator': 'FormBlockProvider',
'x-use-decorator-props': 'useEditFormBlockDecoratorProps',
'x-decorator-props': {
action: 'get',
dataSource,
collection: collectionName,
association,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:editForm',
'x-component': 'CardItem',
properties: {
[uid()]: {
type: 'void',
'x-component': 'FormV2',
'x-use-component-props': 'useEditFormBlockProps',
properties: {
grid: templateSchema || {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'form:configureFields',
properties: {},
},
[uid()]: {
type: 'void',
'x-initializer': 'editForm:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
style: {
marginTop: 24,
},
},
},
},
},
},
};
}

View File

@ -0,0 +1,15 @@
import { useFormBlockSourceId } from './useFormBlockSourceId';
export function useCreateFormBlockDecoratorProps(props) {
let sourceId;
// association 的值是固定不变的,所以这里可以使用 hooks
if (props.association) {
// eslint-disable-next-line react-hooks/rules-of-hooks
sourceId = useFormBlockSourceId(props);
}
return {
sourceId,
};
}

View File

@ -0,0 +1,5 @@
import { useFormBlockProps } from '../../../../../block-provider/FormBlockProvider';
export function useCreateFormBlockProps() {
return useFormBlockProps();
}

View File

@ -0,0 +1,22 @@
import { useParamsFromRecord } from '../../../../../block-provider/BlockProvider';
import { useFormBlockSourceId } from './useFormBlockSourceId';
export function useEditFormBlockDecoratorProps(props) {
const params = useFormBlockParams();
let sourceId;
// association 的值是固定不变的,所以这里可以使用 hooks
if (props.association) {
// eslint-disable-next-line react-hooks/rules-of-hooks
sourceId = useFormBlockSourceId(props);
}
return {
params,
sourceId,
};
}
function useFormBlockParams() {
return useParamsFromRecord();
}

View File

@ -0,0 +1,5 @@
import { useFormBlockProps } from '../../../../../block-provider/FormBlockProvider';
export function useEditFormBlockProps() {
return useFormBlockProps();
}

View File

@ -0,0 +1,5 @@
import { useDataBlockSourceId } from '../../../../../block-provider/hooks/useDataBlockSourceId';
export function useFormBlockSourceId(props) {
return useDataBlockSourceId(props);
}

View File

@ -1,11 +1,12 @@
export * from './createFormBlockSettings';
export * from './CreateFormBlockInitializer'; export * from './CreateFormBlockInitializer';
export * from './FormBlockInitializer'; export * from './FormBlockInitializer';
export * from './FormItemSchemaToolbar'; export * from './FormItemSchemaToolbar';
export * from './RecordFormBlockInitializer'; export * from './RecordFormBlockInitializer';
export * from './editFormBlockSettings'; export * from './createCreateFormBlockUISchema';
export * from './createFormActionInitializers'; export * from './createFormActionInitializers';
export * from './createFormBlockSettings';
export * from './editFormBlockSettings';
export * from './fieldSettingsFormItem';
export * from './formActionInitializers'; export * from './formActionInitializers';
export * from './formItemInitializers'; export * from './formItemInitializers';
export * from './fieldSettingsFormItem';
export * from './updateFormActionInitializers'; export * from './updateFormActionInitializers';

View File

@ -2,7 +2,7 @@ import { OrderedListOutlined } from '@ant-design/icons';
import React from 'react'; import React from 'react';
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application'; import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
import { useCollectionManager_deprecated } from '../../../../collection-manager'; import { useCollectionManager_deprecated } from '../../../../collection-manager';
import { createListBlockSchema } from '../../../../schema-initializer/utils'; import { createListBlockSchema } from './createListBlockSchema';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer'; import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection'; import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection';
@ -48,10 +48,9 @@ export const ListBlockInitializer = ({
const collection = getCollection(item.name, item.dataSource); const collection = getCollection(item.name, item.dataSource);
const schema = createListBlockSchema({ const schema = createListBlockSchema({
collection: item.name, collectionName: item.name,
dataSource: item.dataSource, dataSource: item.dataSource,
rowKey: collection.filterTargetKey || 'id', rowKey: collection.filterTargetKey || 'id',
settings: 'blockSettings:list',
}); });
insert(schema); insert(schema);
}} }}

View File

@ -0,0 +1,97 @@
import { createListBlockSchema } from '../createListBlockSchema';
describe('createListBlockSchema', () => {
test('should return the correct schema', () => {
const options = {
collectionName: 'users',
dataSource: 'users',
association: 'user',
templateSchema: {
type: 'object',
properties: {
name: {
type: 'string',
},
age: {
type: 'number',
},
},
},
rowKey: 'id',
};
const schema = createListBlockSchema(options);
expect(schema).toMatchInlineSnapshot(`
{
"properties": {
"actionBar": {
"type": "void",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)",
},
},
"x-initializer": "list:configureActions",
},
"list": {
"properties": {
"item": {
"properties": {
"actionBar": {
"type": "void",
"x-align": "left",
"x-component": "ActionBar",
"x-component-props": {
"layout": "one-column",
},
"x-initializer": "list:configureItemActions",
"x-use-component-props": "useListActionBarProps",
},
"grid": {
"properties": {
"age": {
"type": "number",
},
"name": {
"type": "string",
},
},
"type": "object",
},
},
"type": "object",
"x-component": "List.Item",
"x-read-pretty": true,
"x-use-component-props": "useListItemProps",
},
},
"type": "array",
"x-component": "List",
"x-use-component-props": "useListBlockProps",
},
},
"type": "void",
"x-acl-action": "user:view",
"x-component": "CardItem",
"x-decorator": "List.Decorator",
"x-decorator-props": {
"action": "list",
"association": "user",
"collection": "users",
"dataSource": "users",
"params": {
"pageSize": 10,
},
"readPretty": true,
"rowKey": "id",
"runWhenParamsChanged": true,
},
"x-settings": "blockSettings:list",
"x-toolbar": "BlockSchemaToolbar",
"x-use-decorator-props": "useListBlockDecoratorProps",
}
`);
});
});

View File

@ -0,0 +1,76 @@
import { ISchema } from '@formily/react';
export const createListBlockSchema = (options: {
dataSource: string;
collectionName?: string;
association?: string;
templateSchema?: ISchema;
rowKey?: string;
}): ISchema => {
const { collectionName, dataSource, association, templateSchema, rowKey } = options;
const resourceName = association || collectionName;
return {
type: 'void',
'x-acl-action': `${resourceName}:view`,
'x-decorator': 'List.Decorator',
'x-use-decorator-props': 'useListBlockDecoratorProps',
'x-decorator-props': {
collection: collectionName,
dataSource,
association,
readPretty: true,
action: 'list',
params: {
pageSize: 10,
},
runWhenParamsChanged: true,
rowKey,
},
'x-component': 'CardItem',
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:list',
properties: {
actionBar: {
type: 'void',
'x-initializer': 'list:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
},
list: {
type: 'array',
'x-component': 'List',
'x-use-component-props': 'useListBlockProps',
properties: {
item: {
type: 'object',
'x-component': 'List.Item',
'x-read-pretty': true,
'x-use-component-props': 'useListItemProps',
properties: {
grid: templateSchema || {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'details:configureFields',
},
actionBar: {
type: 'void',
'x-align': 'left',
'x-initializer': 'list:configureItemActions',
'x-component': 'ActionBar',
'x-use-component-props': 'useListActionBarProps',
'x-component-props': {
layout: 'one-column',
},
},
},
},
},
},
},
};
};

View File

@ -0,0 +1,15 @@
import { useDataBlockSourceId } from '../../../../../block-provider/hooks/useDataBlockSourceId';
export function useListBlockDecoratorProps(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

@ -2,7 +2,7 @@ import { FormOutlined } from '@ant-design/icons';
import React from 'react'; import React from 'react';
import { useCollection_deprecated } from '../../../../collection-manager'; import { useCollection_deprecated } from '../../../../collection-manager';
import { createTableSelectorSchema } from '../../../../schema-initializer/utils'; import { createTableSelectorUISchema } from './createTableSelectorUISchema';
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../../../application'; import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
export const TableSelectorInitializer = () => { export const TableSelectorInitializer = () => {
@ -16,11 +16,10 @@ export const TableSelectorInitializer = () => {
{...others} {...others}
onClick={async () => { onClick={async () => {
insert( insert(
createTableSelectorSchema({ createTableSelectorUISchema({
rowKey: collection.filterTargetKey, rowKey: collection.filterTargetKey,
collection: collection.name, collectionName: collection.name,
dataSource: collection.dataSource, dataSource: collection.dataSource,
resource: collection.name,
}), }),
); );
}} }}

View File

@ -2,7 +2,7 @@ import { Page, expect, test } from '@nocobase/test/e2e';
import { createTable } from './utils'; import { createTable } from './utils';
test.describe('where table data selector can be added', () => { test.describe('where table data selector can be added', () => {
test('popup', async ({ page, mockPage, mockRecord }) => { test('popup', async ({ page, mockPage }) => {
await createTable({ page, mockPage, fieldName: 'manyToOne' }); await createTable({ page, mockPage, fieldName: 'manyToOne' });
// 选中一行数据之后,弹窗自动关闭,且数据被填充到关联字段中 // 选中一行数据之后,弹窗自动关闭,且数据被填充到关联字段中

View File

@ -0,0 +1,61 @@
import { createTableSelectorUISchema } from '../createTableSelectorUISchema';
vi.mock('@formily/shared', () => {
return {
uid: () => 'mocked-uid',
};
});
describe('createTableSelectorSchema', () => {
test('should return the correct schema', () => {
const schema = createTableSelectorUISchema({
collectionName: 'example',
dataSource: 'example',
rowKey: 'id',
});
expect(schema).toMatchInlineSnapshot(`
{
"properties": {
"mocked-uid": {
"type": "void",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)",
},
},
"x-initializer": "table:configureActions",
},
"value": {
"type": "array",
"x-component": "TableV2.Selector",
"x-component-props": {
"rowSelection": {
"type": "checkbox",
},
},
"x-initializer": "table:configureColumns",
"x-use-component-props": "useTableSelectorProps",
},
},
"type": "void",
"x-acl-action": "example:list",
"x-component": "CardItem",
"x-decorator": "TableSelectorProvider",
"x-decorator-props": {
"action": "list",
"collection": "example",
"dataSource": "example",
"params": {
"pageSize": 20,
},
"rowKey": "id",
},
"x-settings": "blockSettings:tableSelector",
"x-toolbar": "BlockSchemaToolbar",
"x-use-decorator-props": "useTableSelectorDecoratorProps",
}
`);
});
});

View File

@ -0,0 +1,56 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
export const createTableSelectorUISchema = (options: {
collectionName: string;
dataSource: string;
rowKey: string;
}): ISchema => {
const { collectionName, dataSource, rowKey } = options;
if (!collectionName || !dataSource || !rowKey) {
throw new Error('collectionName, dataSource, rowKey is required');
}
return {
type: 'void',
'x-acl-action': `${collectionName}:list`,
'x-decorator': 'TableSelectorProvider',
'x-use-decorator-props': 'useTableSelectorDecoratorProps',
'x-decorator-props': {
collection: collectionName,
dataSource,
action: 'list',
params: {
pageSize: 20,
},
rowKey,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:tableSelector',
'x-component': 'CardItem',
properties: {
[uid()]: {
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
},
value: {
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2.Selector',
'x-use-component-props': 'useTableSelectorProps',
'x-component-props': {
rowSelection: {
type: 'checkbox',
},
},
},
},
};
};

View File

@ -0,0 +1 @@
export function useTableSelectorDecoratorProps() {}

View File

@ -2,7 +2,7 @@ import { TableOutlined } from '@ant-design/icons';
import React from 'react'; import React from 'react';
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application'; import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
import { createCollapseBlockSchema } from '../../../../schema-initializer/utils'; import { createCollapseBlockSchema } from './createFilterCollapseBlockSchema';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer'; import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { Collection, CollectionFieldOptions } from '../../../../data-source'; import { Collection, CollectionFieldOptions } from '../../../../data-source';
@ -27,7 +27,7 @@ export const FilterCollapseBlockInitializer = ({
onCreateBlockSchema={async ({ item }) => { onCreateBlockSchema={async ({ item }) => {
const schema = createCollapseBlockSchema({ const schema = createCollapseBlockSchema({
dataSource: item.dataSource, dataSource: item.dataSource,
collection: item.collectionName || item.name, collectionName: item.collectionName || item.name,
// 与数据区块做区分 // 与数据区块做区分
blockType: 'filter', blockType: 'filter',
}); });

View File

@ -0,0 +1,44 @@
import { createCollapseBlockSchema } from '../createFilterCollapseBlockSchema';
vi.mock('@formily/shared', () => ({
uid: vi.fn().mockReturnValue('mocked-uid'),
}));
describe('createCollapseBlockSchema', () => {
it('should return the correct schema', () => {
const options = {
collectionName: 'testCollection',
dataSource: 'testDataSource',
blockType: 'testBlockType',
};
const schema = createCollapseBlockSchema(options);
expect(schema).toEqual({
type: 'void',
'x-decorator': 'AssociationFilter.Provider',
'x-use-decorator-props': 'useCollapseBlockDecoratorProps',
'x-decorator-props': {
collection: 'testCollection',
dataSource: 'testDataSource',
blockType: 'testBlockType',
associationFilterStyle: {
width: '100%',
},
name: 'filter-collapse',
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:filterCollapse',
'x-component': 'CardItem',
'x-filter-targets': [],
properties: {
'mocked-uid': {
type: 'void',
'x-action': 'associateFilter',
'x-initializer': 'filterCollapse:configureFields',
'x-component': 'AssociationFilter',
},
},
});
});
});

View File

@ -0,0 +1,37 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
export const createCollapseBlockSchema = (options: {
collectionName: string;
dataSource: string;
blockType: string;
}): ISchema => {
const { collectionName, dataSource, blockType } = options;
return {
type: 'void',
'x-decorator': 'AssociationFilter.Provider',
'x-use-decorator-props': 'useCollapseBlockDecoratorProps',
'x-decorator-props': {
collection: collectionName,
dataSource,
blockType,
associationFilterStyle: {
width: '100%',
},
name: 'filter-collapse',
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:filterCollapse',
'x-component': 'CardItem',
'x-filter-targets': [],
properties: {
[uid()]: {
type: 'void',
'x-action': 'associateFilter',
'x-initializer': 'filterCollapse:configureFields',
'x-component': 'AssociationFilter',
},
},
};
};

View File

@ -0,0 +1 @@
export function useCollapseBlockDecoratorProps() {}

View File

@ -2,11 +2,7 @@ import { useCollection_deprecated } from '../../../..';
import { CompatibleSchemaInitializer } from '../../../../application/schema-initializer/CompatibleSchemaInitializer'; import { CompatibleSchemaInitializer } from '../../../../application/schema-initializer/CompatibleSchemaInitializer';
import { gridRowColWrap } from '../../../../schema-initializer/utils'; import { gridRowColWrap } from '../../../../schema-initializer/utils';
/** const commonOptions = {
* @deprecated
*/
export const tableSelectorInitializers_deprecated = new CompatibleSchemaInitializer({
name: 'TableSelectorInitializers',
wrap: gridRowColWrap, wrap: gridRowColWrap,
title: '{{t("Add block")}}', title: '{{t("Add block")}}',
icon: 'PlusOutlined', icon: 'PlusOutlined',
@ -84,88 +80,20 @@ export const tableSelectorInitializers_deprecated = new CompatibleSchemaInitiali
], ],
}, },
], ],
};
/**
* @deprecated
*/
export const tableSelectorInitializers_deprecated = new CompatibleSchemaInitializer({
name: 'TableSelectorInitializers',
...commonOptions,
}); });
export const tableSelectorInitializers = new CompatibleSchemaInitializer( export const tableSelectorInitializers = new CompatibleSchemaInitializer(
{ {
name: 'popup:tableSelector:addBlock', name: 'popup:tableSelector:addBlock',
wrap: gridRowColWrap, ...commonOptions,
title: '{{t("Add block")}}',
icon: 'PlusOutlined',
items: [
{
type: 'itemGroup',
title: '{{t("Selector")}}',
name: 'selector',
children: [
{
name: 'title',
title: 'Table',
Component: 'TableSelectorInitializer',
},
],
},
{
type: 'itemGroup',
title: '{{t("Filter blocks")}}',
name: 'filterBlocks',
useChildren() {
const { name, dataSource } = useCollection_deprecated();
return [
{
name: 'filterFormBlockInTableSelector',
title: '{{t("Form")}}',
Component: 'FilterFormBlockInitializer',
componentProps: {
filterCollections() {
return false;
},
onlyCurrentDataSource: true,
},
collectionName: name,
dataSource,
},
{
name: 'filterCollapseBlockInTableSelector',
title: '{{t("Collapse")}}',
Component: 'FilterCollapseBlockInitializer',
componentProps: {
filterCollections() {
return false;
},
onlyCurrentDataSource: true,
},
collectionName: name,
dataSource,
},
];
},
},
{
type: 'itemGroup',
title: '{{t("Other blocks")}}',
name: 'otherBlocks',
children: [
{
title: '{{t("Add text")}}',
Component: 'BlockItemInitializer',
name: 'addText',
schema: {
type: 'void',
'x-editable': false,
'x-decorator': 'BlockItem',
// 'x-designer': 'Markdown.Void.Designer',
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:markdown',
'x-component': 'Markdown.Void',
'x-component-props': {
content: '{{t("This is a demo text, **supports Markdown syntax**.")}}',
},
},
},
],
},
],
}, },
tableSelectorInitializers_deprecated, tableSelectorInitializers_deprecated,
); );

View File

@ -6,6 +6,7 @@ import { createPortal } from 'react-dom';
import { DndContext } from '../../common'; import { DndContext } from '../../common';
import { useDesignable, useProps } from '../../hooks'; import { useDesignable, useProps } from '../../hooks';
import { useSchemaInitializerRender } from '../../../application'; import { useSchemaInitializerRender } from '../../../application';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
interface ActionBarContextForceProps { interface ActionBarContextForceProps {
layout?: 'one-column' | 'tow-columns'; layout?: 'one-column' | 'tow-columns';
@ -46,10 +47,13 @@ const Portal: React.FC = (props) => {
); );
}; };
export const ActionBar = observer( export const ActionBar = withDynamicSchemaProps(
(props: any) => { observer((props: any) => {
const { forceProps = {} } = useActionBarContext(); const { forceProps = {} } = useActionBarContext();
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { layout = 'tow-columns', style, spaceProps, ...others } = { ...useProps(props), ...forceProps } as any; const { layout = 'tow-columns', style, spaceProps, ...others } = { ...useProps(props), ...forceProps } as any;
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']); const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']);
const { designable } = useDesignable(); const { designable } = useDesignable();
@ -128,6 +132,6 @@ export const ActionBar = observer(
{render()} {render()}
</div> </div>
); );
}, }),
{ displayName: 'ActionBar' }, { displayName: 'ActionBar' },
); );

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { FormV2 } from '../form-v2'; import { FormV2 } from '../form-v2';
import { useDetailsBlockContext } from '../../../block-provider/DetailsBlockProvider';
import _ from 'lodash'; import _ from 'lodash';
import { Empty } from 'antd'; import { Empty } from 'antd';
import { useDataBlockRequest } from '../../../data-source'; import { useDataBlockRequest } from '../../../data-source';

View File

@ -18,6 +18,7 @@ import { isVariable, REGEX_OF_VARIABLE } from '../../../variables/utils/isVariab
import { getInnermostKeyAndValue, getTargetField } from '../../common/utils/uitls'; import { getInnermostKeyAndValue, getTargetField } from '../../common/utils/uitls';
import { useProps } from '../../hooks/useProps'; import { useProps } from '../../hooks/useProps';
import { collectFieldStateOfLinkageRules, getTempFieldState } from './utils'; import { collectFieldStateOfLinkageRules, getTempFieldState } from './utils';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
export interface FormProps { export interface FormProps {
[key: string]: any; [key: string]: any;
@ -197,14 +198,18 @@ export const Form: React.FC<FormProps> & {
FilterDesigner?: any; FilterDesigner?: any;
ReadPrettyDesigner?: any; ReadPrettyDesigner?: any;
Templates?: any; Templates?: any;
} = observer( } = withDynamicSchemaProps(
(props) => { observer((props) => {
const field = useField<Field>(); const field = useField<Field>();
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { form, disabled, ...others } = useProps(props); const { form, disabled, ...others } = useProps(props);
const formDisabled = disabled || field.disabled; const formDisabled = disabled || field.disabled;
return ( return (
<ConfigProvider componentDisabled={formDisabled}> <ConfigProvider componentDisabled={formDisabled}>
<form <form
onSubmit={(e) => e.preventDefault()}
className={css` className={css`
.ant-formily-item-feedback-layout-loose { .ant-formily-item-feedback-layout-loose {
margin-bottom: 12px; margin-bottom: 12px;
@ -221,7 +226,7 @@ export const Form: React.FC<FormProps> & {
</form> </form>
</ConfigProvider> </ConfigProvider>
); );
}, }),
{ displayName: 'Form' }, { displayName: 'Form' },
); );

View File

@ -5,6 +5,7 @@ import { FormContext, useField } from '@formily/react';
import _ from 'lodash'; import _ from 'lodash';
import React, { createContext, useContext, useEffect, useMemo } from 'react'; import React, { createContext, useContext, useEffect, useMemo } from 'react';
import { BlockProvider, useBlockRequestContext, useParsedFilter } from '../../../block-provider'; import { BlockProvider, useBlockRequestContext, useParsedFilter } from '../../../block-provider';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
export const ListBlockContext = createContext<any>({}); export const ListBlockContext = createContext<any>({});
ListBlockContext.displayName = 'ListBlockContext'; ListBlockContext.displayName = 'ListBlockContext';
@ -52,7 +53,7 @@ const InternalListBlockProvider = (props) => {
); );
}; };
export const ListBlockProvider = (props) => { export const ListBlockProvider = withDynamicSchemaProps((props) => {
const { params } = props; const { params } = props;
const { filter: parsedFilter } = useParsedFilter({ const { filter: parsedFilter } = useParsedFilter({
filterOption: params?.filter, filterOption: params?.filter,
@ -75,7 +76,7 @@ export const ListBlockProvider = (props) => {
<InternalListBlockProvider {...props} /> <InternalListBlockProvider {...props} />
</BlockProvider> </BlockProvider>
); );
}; });
export const useListBlockContext = () => { export const useListBlockContext = () => {
return useContext(ListBlockContext); return useContext(ListBlockContext);

View File

@ -7,8 +7,9 @@ import { useDesignable } from '../../hooks';
import { useCollectionParentRecordData } from '../../../data-source/collection-record/CollectionRecordProvider'; import { useCollectionParentRecordData } from '../../../data-source/collection-record/CollectionRecordProvider';
import { RecordProvider } from '../../../record-provider'; import { RecordProvider } from '../../../record-provider';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
export const ListItem = (props) => { export const ListItem = withDynamicSchemaProps((props) => {
const field = useField<ObjectField>(); const field = useField<ObjectField>();
const { designable } = useDesignable(); const { designable } = useDesignable();
const parentRecordData = useCollectionParentRecordData(); const parentRecordData = useCollectionParentRecordData();
@ -29,4 +30,4 @@ export const ListItem = (props) => {
</RecordProvider> </RecordProvider>
</div> </div>
); );
}; });

View File

@ -11,6 +11,7 @@ import { ListDesigner } from './List.Designer';
import { ListItem } from './List.Item'; import { ListItem } from './List.Item';
import useStyles from './List.style'; import useStyles from './List.style';
import { useListActionBarProps } from './hooks'; import { useListActionBarProps } from './hooks';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
const InternalList = (props) => { const InternalList = (props) => {
const { service } = useListBlockContext(); const { service } = useListBlockContext();
@ -93,7 +94,7 @@ const InternalList = (props) => {
); );
}; };
export const List = InternalList as typeof InternalList & { export const List = withDynamicSchemaProps(InternalList) as typeof InternalList & {
Item: typeof ListItem; Item: typeof ListItem;
Designer: typeof ListDesigner; Designer: typeof ListDesigner;
Decorator: typeof ListBlockProvider; Decorator: typeof ListBlockProvider;

View File

@ -8,6 +8,12 @@ interface Options {
} }
const useDef = () => ({}); const useDef = () => ({});
/**
* UISchema1.0 useProps UISchema
* @param originalProps
* @returns
*/
export const useProps = (originalProps: any = {}) => { export const useProps = (originalProps: any = {}) => {
const { useProps: useDynamicHook = useDef, ...others } = originalProps; const { useProps: useDynamicHook = useDef, ...others } = originalProps;
let useDynamicProps = useDynamicHook; let useDynamicProps = useDynamicHook;

View File

@ -1,10 +1,11 @@
import React, { useCallback, useMemo } from 'react';
import { FormOutlined } from '@ant-design/icons'; import { FormOutlined } from '@ant-design/icons';
import React, { useCallback, useMemo } from 'react';
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application'; import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
import { useSchemaTemplateManager } from '../../schema-templates';
import { createFormBlockSchema, useRecordCollectionDataSourceItems } from '../utils';
import { useCollectionManager_deprecated } from '../../collection-manager'; import { useCollectionManager_deprecated } from '../../collection-manager';
import { createCreateFormBlockUISchema } from '../../modules/blocks/data-blocks/form/createCreateFormBlockUISchema';
import { useSchemaTemplateManager } from '../../schema-templates';
import { useRecordCollectionDataSourceItems } from '../utils';
/** /**
* @deprecated * @deprecated
@ -18,31 +19,18 @@ export const RecordAssociationFormBlockInitializer = () => {
const field = itemConfig.field; const field = itemConfig.field;
const collectionName = field.target; const collectionName = field.target;
const collection = useMemo(() => getCollection(collectionName), [collectionName]); const collection = useMemo(() => getCollection(collectionName), [collectionName]);
const resource = `${field.collectionName}.${field.name}`;
return ( return (
<SchemaInitializerItem <SchemaInitializerItem
icon={<FormOutlined />} icon={<FormOutlined />}
{...others} {...others}
onClick={async ({ item }) => { onClick={async ({ item }) => {
const action = ['hasOne', 'belongsTo'].includes(field.type) ? 'get' : null;
const actionInitializers = ['hasOne', 'belongsTo'].includes(field.type)
? 'editForm:configureActions'
: 'createForm:configureActions';
if (item.template) { if (item.template) {
const template = await getTemplateSchemaByMode(item); const template = await getTemplateSchemaByMode(item);
if (item.template.componentName === 'FormItem') { if (item.template.componentName === 'FormItem') {
const blockSchema = createFormBlockSchema({ const blockSchema = createCreateFormBlockUISchema({
collection: collectionName,
dataSource: collection.dataSource, dataSource: collection.dataSource,
resource, association: `${field.collectionName}.${field.name}`,
association: resource, templateSchema: template,
action,
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
actionInitializers,
template: template,
settings: 'blockSettings:createForm',
}); });
if (item.mode === 'reference') { if (item.mode === 'reference') {
blockSchema['x-template-key'] = item.template.key; blockSchema['x-template-key'] = item.template.key;
@ -53,21 +41,19 @@ export const RecordAssociationFormBlockInitializer = () => {
} }
} else { } else {
insert( insert(
createFormBlockSchema({ createCreateFormBlockUISchema({
collection: collectionName,
resource,
dataSource: collection.dataSource, dataSource: collection.dataSource,
association: resource, association: `${field.collectionName}.${field.name}`,
action,
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
actionInitializers,
settings: 'blockSettings:createForm',
}), }),
); );
} }
}} }}
items={useRecordCollectionDataSourceItems('FormItem', itemConfig, collection, resource)} items={useRecordCollectionDataSourceItems(
'FormItem',
itemConfig,
collection,
`${field.collectionName}.${field.name}`,
)}
/> />
); );
}; };
@ -80,21 +66,11 @@ export function useCreateAssociationFormBlock() {
({ item }) => { ({ item }) => {
const field = item.associationField; const field = item.associationField;
const collection = getCollection(field.target); const collection = getCollection(field.target);
const action = ['hasOne', 'belongsTo'].includes(field.type) ? 'get' : null;
const actionInitializers = ['hasOne', 'belongsTo'].includes(field.type)
? 'editForm:configureActions'
: 'createForm:configureActions';
insert( insert(
createFormBlockSchema({ createCreateFormBlockUISchema({
collection: field.target,
dataSource: collection.dataSource, dataSource: collection.dataSource,
association: `${field.collectionName}.${field.name}`, association: `${field.collectionName}.${field.name}`,
action,
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
actionInitializers,
settings: 'blockSettings:createForm',
}), }),
); );
}, },
@ -104,23 +80,13 @@ export function useCreateAssociationFormBlock() {
const templateWrap = useCallback( const templateWrap = useCallback(
(templateSchema, { item }) => { (templateSchema, { item }) => {
const field = item.associationField; const field = item.associationField;
const action = ['hasOne', 'belongsTo'].includes(field.type) ? 'get' : null;
const collection = getCollection(field.target); const collection = getCollection(field.target);
const actionInitializers = ['hasOne', 'belongsTo'].includes(field.type)
? 'editForm:configureActions'
: 'createForm:configureActions';
if (item.template.componentName === 'FormItem') { if (item.template.componentName === 'FormItem') {
const blockSchema = createFormBlockSchema({ const blockSchema = createCreateFormBlockUISchema({
collection: field.target,
dataSource: collection.dataSource, dataSource: collection.dataSource,
association: `${field.collectionName}.${field.name}`, association: `${field.collectionName}.${field.name}`,
action, templateSchema: templateSchema,
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
actionInitializers,
template: templateSchema,
settings: 'blockSettings:createForm',
}); });
if (item.mode === 'reference') { if (item.mode === 'reference') {
blockSchema['x-template-key'] = item.template.key; blockSchema['x-template-key'] = item.template.key;

View File

@ -4,7 +4,8 @@ import React, { useCallback } from 'react';
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application'; import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
import { useCollectionManager_deprecated } from '../../collection-manager'; import { useCollectionManager_deprecated } from '../../collection-manager';
import { useSchemaTemplateManager } from '../../schema-templates'; import { useSchemaTemplateManager } from '../../schema-templates';
import { createListBlockSchema, useRecordCollectionDataSourceItems } from '../utils'; import { useRecordCollectionDataSourceItems } from '../utils';
import { createListBlockSchema } from '../../modules/blocks/data-blocks/list/createListBlockSchema';
export const RecordAssociationListBlockInitializer = () => { export const RecordAssociationListBlockInitializer = () => {
const itemConfig = useSchemaInitializerItem(); const itemConfig = useSchemaInitializerItem();
@ -28,11 +29,8 @@ export const RecordAssociationListBlockInitializer = () => {
insert( insert(
createListBlockSchema({ createListBlockSchema({
rowKey: collection.filterTargetKey, rowKey: collection.filterTargetKey,
collection: field.target,
resource,
dataSource: collection.dataSource, dataSource: collection.dataSource,
association: resource, association: resource,
settings: 'blockSettings:list',
}), }),
); );
} }
@ -54,10 +52,8 @@ export function useCreateAssociationListBlock() {
insert( insert(
createListBlockSchema({ createListBlockSchema({
rowKey: collection.filterTargetKey, rowKey: collection.filterTargetKey,
collection: field.target,
dataSource: collection.dataSource, dataSource: collection.dataSource,
association: `${field.collectionName}.${field.name}`, association: `${field.collectionName}.${field.name}`,
settings: 'blockSettings:list',
}), }),
); );
}, },

View File

@ -1,6 +1,7 @@
import { Field, Form } from '@formily/core'; import { Field, Form } from '@formily/core';
import { ISchema, Schema, useFieldSchema, useForm } from '@formily/react'; import { ISchema, Schema, useFieldSchema, useForm } from '@formily/react';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import _ from 'lodash';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@ -12,13 +13,12 @@ import {
useFormActiveFields, useFormActiveFields,
useFormBlockContext, useFormBlockContext,
} from '../'; } from '../';
import { FieldOptions, useCollection_deprecated, useCollectionManager_deprecated } from '../collection-manager'; import { FieldOptions, useCollectionManager_deprecated, useCollection_deprecated } from '../collection-manager';
import { Collection, CollectionFieldOptions } from '../data-source/collection/Collection';
import { useDataSourceManager } from '../data-source/data-source/DataSourceManagerProvider';
import { isAssocField } from '../filter-provider/utils'; import { isAssocField } from '../filter-provider/utils';
import { useActionContext, useCompile, useDesignable } from '../schema-component'; import { useActionContext, useCompile, useDesignable } from '../schema-component';
import { useSchemaTemplateManager } from '../schema-templates'; import { useSchemaTemplateManager } from '../schema-templates';
import { Collection, CollectionFieldOptions } from '../data-source/collection/Collection';
import { useDataSourceManager } from '../data-source/data-source/DataSourceManagerProvider';
import _ from 'lodash';
export const itemsMerge = (items1) => { export const itemsMerge = (items1) => {
return items1; return items1;
@ -972,94 +972,6 @@ export const createDetailsBlockSchema = (options) => {
return schema; return schema;
}; };
export const createListBlockSchema = (options) => {
const {
formItemInitializers = 'details:configureFields',
actionInitializers = 'list:configureActions',
itemActionInitializers = 'list:configureItemActions',
collection,
dataSource,
association,
template,
settings,
...others
} = options;
const resourceName = association || collection;
const schema: ISchema = {
type: 'void',
'x-acl-action': `${resourceName}:view`,
'x-decorator': 'List.Decorator',
'x-decorator-props': {
collection,
dataSource,
association,
readPretty: true,
action: 'list',
params: {
pageSize: 10,
},
runWhenParamsChanged: true,
...others,
},
'x-component': 'CardItem',
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': settings,
properties: {
actionBar: {
type: 'void',
'x-initializer': actionInitializers,
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
properties: {},
},
list: {
type: 'array',
'x-component': 'List',
'x-component-props': {
props: '{{ useListBlockProps }}',
},
properties: {
item: {
type: 'object',
'x-component': 'List.Item',
'x-read-pretty': true,
'x-component-props': {
useProps: '{{ useListItemProps }}',
},
properties: {
grid: template || {
type: 'void',
'x-component': 'Grid',
'x-initializer': formItemInitializers,
'x-initializer-props': {
useProps: '{{ useListItemInitializerProps }}',
},
properties: {},
},
actionBar: {
type: 'void',
'x-align': 'left',
'x-initializer': itemActionInitializers,
'x-component': 'ActionBar',
'x-component-props': {
useProps: '{{ useListActionBarProps }}',
layout: 'one-column',
},
properties: {},
},
},
},
},
},
},
};
return schema;
};
export const createGridCardBlockSchema = (options) => { export const createGridCardBlockSchema = (options) => {
const { const {
formItemInitializers = 'details:configureFields', formItemInitializers = 'details:configureFields',
@ -1150,7 +1062,28 @@ export const createGridCardBlockSchema = (options) => {
}; };
return schema; return schema;
}; };
export const createFormBlockSchema = (options) => {
/**
* @deprecated
* 使 createCreateFormBlockUISchema createEditFormBlockUISchema
* @param options
* @returns
*/
export const createFormBlockSchema = (options: {
formItemInitializers?: string;
actionInitializers?: string;
collection: string;
resource?: string;
dataSource?: string;
association?: string;
action?: string;
actions?: Record<string, any>;
template?: any;
title?: string;
settings?: any;
'x-designer'?: string;
[key: string]: any;
}) => {
const { const {
formItemInitializers = 'form:configureFields', formItemInitializers = 'form:configureFields',
actionInitializers = 'createForm:configureActions', actionInitializers = 'createForm:configureActions',
@ -1453,87 +1386,6 @@ export const createTableBlockSchema = (options) => {
return schema; return schema;
}; };
export const createCollapseBlockSchema = (options) => {
const { collection, dataSource, blockType } = options;
const schema: ISchema = {
type: 'void',
'x-decorator': 'AssociationFilter.Provider',
'x-decorator-props': {
collection,
dataSource,
blockType,
associationFilterStyle: {
width: '100%',
},
name: 'filter-collapse',
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:filterCollapse',
'x-component': 'CardItem',
'x-filter-targets': [],
properties: {
[uid()]: {
type: 'void',
'x-action': 'associateFilter',
'x-initializer': 'filterCollapse:configureFields',
'x-component': 'AssociationFilter',
properties: {},
},
},
};
return schema;
};
export const createTableSelectorSchema = (options) => {
const { collection, dataSource, resource, rowKey, ...others } = options;
const schema: ISchema = {
type: 'void',
'x-acl-action': `${resource || collection}:list`,
'x-decorator': 'TableSelectorProvider',
'x-decorator-props': {
collection,
resource: resource || collection,
dataSource,
action: 'list',
params: {
pageSize: 20,
},
rowKey,
...others,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:tableSelector',
'x-component': 'CardItem',
properties: {
[uid()]: {
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
properties: {},
},
value: {
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2.Selector',
'x-component-props': {
rowSelection: {
type: 'checkbox',
},
useProps: '{{ useTableSelectorProps }}',
},
properties: {},
},
},
};
return schema;
};
const getChildren = ({ const getChildren = ({
collections, collections,
dataSource, dataSource,

View File

@ -1,15 +1,16 @@
import { FormOutlined } from '@ant-design/icons'; import { FormOutlined } from '@ant-design/icons';
import React from 'react';
import { import {
SchemaInitializerItem,
createCreateFormBlockUISchema,
useBlockAssociationContext, useBlockAssociationContext,
useCollection_deprecated, useCollection_deprecated,
useSchemaTemplateManager,
createFormBlockSchema,
useRecordCollectionDataSourceItems, useRecordCollectionDataSourceItems,
SchemaInitializerItem,
useSchemaInitializer, useSchemaInitializer,
useSchemaInitializerItem, useSchemaInitializerItem,
useSchemaTemplateManager,
} from '@nocobase/client'; } from '@nocobase/client';
import React from 'react';
import { createBulkEditBlockUISchema } from './createBulkEditBlockUISchema';
export const CreateFormBulkEditBlockInitializer = () => { export const CreateFormBulkEditBlockInitializer = () => {
const itemConfig = useSchemaInitializerItem(); const itemConfig = useSchemaInitializerItem();
@ -26,13 +27,19 @@ export const CreateFormBulkEditBlockInitializer = () => {
if (item.template) { if (item.template) {
const s = await getTemplateSchemaByMode(item); const s = await getTemplateSchemaByMode(item);
if (item.template.componentName === 'FormItem') { if (item.template.componentName === 'FormItem') {
const blockSchema = createFormBlockSchema({ const blockSchema = createCreateFormBlockUISchema(
actionInitializers: 'createForm:configureActions', association
association, ? {
collection: collection.name, association,
dataSource: collection.dataSource, dataSource: collection.dataSource,
template: s, templateSchema: s,
}); }
: {
collectionName: collection.name,
dataSource: collection.dataSource,
templateSchema: s,
},
);
if (item.mode === 'reference') { if (item.mode === 'reference') {
blockSchema['x-template-key'] = item.template.key; blockSchema['x-template-key'] = item.template.key;
} }
@ -42,11 +49,9 @@ export const CreateFormBulkEditBlockInitializer = () => {
} }
} else { } else {
insert( insert(
createFormBlockSchema({ createBulkEditBlockUISchema({
formItemInitializers: 'bulkEditForm:configureFields',
actionInitializers: 'bulkEditForm:configureActions',
association, association,
collection: collection.name, collectionName: collection.name,
dataSource: collection.dataSource, dataSource: collection.dataSource,
}), }),
); );

View File

@ -0,0 +1,65 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
/**
* UI Schema
* @returns
*/
export function createBulkEditBlockUISchema(options: {
collectionName: string;
dataSource: string;
association?: string;
}): ISchema {
const { collectionName, association, dataSource } = options;
const resourceName = association || collectionName;
if (!collectionName || !dataSource) {
throw new Error('collectionName and dataSource are required');
}
const schema: ISchema = {
type: 'void',
'x-acl-action-props': {
skipScopeCheck: true,
},
'x-acl-action': `${resourceName}:create`,
'x-decorator': 'FormBlockProvider',
'x-decorator-props': {
dataSource,
collection: collectionName,
association,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:createForm',
'x-component': 'CardItem',
properties: {
[uid()]: {
type: 'void',
'x-component': 'FormV2',
'x-component-props': {
useProps: '{{ useCreateFormBlockProps }}',
},
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'bulkEditForm:configureFields',
properties: {},
},
[uid()]: {
type: 'void',
'x-initializer': 'bulkEditForm:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
style: {
marginTop: 24,
},
},
},
},
},
},
};
return schema;
}

View File

@ -0,0 +1,35 @@
import { test, expect } from '@nocobase/test/e2e';
import { emptyPageWithCalendarCollection, oneTableWithCalendarCollection } from './templates';
test.describe('where can be added', () => {
test('page', async ({ page, mockPage }) => {
await mockPage(emptyPageWithCalendarCollection).goto();
await page.getByLabel('schema-initializer-Grid-page:').hover();
await page.getByRole('menuitem', { name: 'form Calendar right' }).hover();
await page.getByRole('menuitem', { name: 'calendar', exact: true }).click();
await page.getByLabel('block-item-Select-Title field').getByTestId('select-single').click();
await page.getByRole('option', { name: 'Repeats' }).click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
await expect(page.getByLabel('block-item-CardItem-calendar-').getByText('Sun', { exact: true })).toBeVisible();
});
test('association block in popup', async ({ page, mockPage, mockRecord }) => {
await mockPage(oneTableWithCalendarCollection).goto();
await mockRecord('toManyCalendar');
// 打开弹窗
await page.getByLabel('action-Action.Link-View-view-').first().click();
await page.getByLabel('schema-initializer-Grid-popup').hover();
await page.getByRole('menuitem', { name: 'form Calendar right' }).hover();
await page.getByRole('menuitem', { name: 'manyToMany -> calendar' }).click();
await page.getByLabel('block-item-Select-Title field').getByTestId('select-single').click();
await page.getByRole('option', { name: 'Repeats' }).click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
await expect(page.getByLabel('block-item-CardItem-calendar-').getByText('Sun', { exact: true })).toBeVisible();
});
});

View File

@ -0,0 +1,238 @@
import { PageConfig } from '@nocobase/test/e2e';
const calendarCollection = {
name: 'calendar',
template: 'calendar',
};
export const emptyPageWithCalendarCollection: PageConfig = {
collections: [calendarCollection],
};
export const oneTableWithCalendarCollection: PageConfig = {
collections: [
calendarCollection,
{
name: 'toManyCalendar',
fields: [
{
name: 'manyToMany',
interface: 'm2m',
target: 'calendar',
},
],
},
],
pageSchema: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Page',
properties: {
lqs2pzl6li1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
properties: {
viawniezd4p: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
properties: {
s0nef2zgi5m: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
properties: {
ibwqgtls50q: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'toManyCalendar:list',
'x-use-decorator-props': 'useTableBlockDecoratorProps',
'x-decorator-props': {
collection: 'toManyCalendar',
dataSource: 'main',
action: 'list',
params: {
pageSize: 20,
},
rowKey: 'id',
showIndex: true,
dragSort: false,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:table',
'x-component': 'CardItem',
'x-filter-targets': [],
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
'x-uid': 'xi12fv3arso',
'x-async': false,
'x-index': 1,
},
v931jk5mpmg: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-use-component-props': 'useTableBlockProps',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
},
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-designer': 'TableV2.ActionColumnDesigner',
'x-initializer': 'table:configureItemActions',
properties: {
w4rc8u7s5q0: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {
hd95fsevokf: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("View") }}',
'x-action': 'view',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:view',
'x-component': 'Action.Link',
'x-component-props': {
openMode: 'drawer',
},
'x-decorator': 'ACLActionProvider',
'x-designer-props': {
linkageAction: true,
},
properties: {
drawer: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("View record") }}',
'x-component': 'Action.Container',
'x-component-props': {
className: 'nb-action-popup',
},
properties: {
tabs: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'TabPaneInitializers',
properties: {
tab1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Details")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:common:addBlock',
'x-uid': 'wfpwj2q55xi',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'tvwvyrlvpv6',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'bys1tnlre1o',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'ml2scl3y6se',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'q9gwy03bevt',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'f8i1npjyidq',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'h26hp47w83k',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'bjbs9yvab1k',
'x-async': false,
'x-index': 2,
},
},
'x-uid': '5i6112zy12n',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '6vh9ncefvs2',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'u4b441tpuzq',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'mworqfx7jaf',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'kgor3l32s12',
'x-async': true,
'x-index': 1,
},
};

View File

@ -0,0 +1,116 @@
import { createCalendarBlockUISchema } from '../schema-initializer/createCalendarBlockUISchema';
vi.mock('@formily/shared', async () => {
const actual = await vi.importActual('@formily/shared');
return {
...actual,
uid: () => 'mocked-uid',
};
});
describe('createCalendarBlockSchema', () => {
it('should return the correct schema', () => {
const options = {
collectionName: 'users',
dataSource: 'events',
fieldNames: {
title: 'title',
startDate: 'start_date',
endDate: 'end_date',
},
association: 'users.roles',
};
const schema = createCalendarBlockUISchema(options);
expect(schema).toMatchInlineSnapshot(`
{
"properties": {
"mocked-uid": {
"properties": {
"event": {
"properties": {
"drawer": {
"properties": {
"tabs": {
"properties": {
"tab1": {
"properties": {
"grid": {
"type": "void",
"x-component": "Grid",
"x-initializer": "popup:common:addBlock",
"x-initializer-props": {
"actionInitializers": "details:configureActions",
},
},
},
"title": "{{t('Details', { ns: 'calendar' })}}",
"type": "void",
"x-component": "Tabs.TabPane",
"x-component-props": {},
"x-designer": "Tabs.Designer",
},
},
"type": "void",
"x-component": "Tabs",
"x-component-props": {},
"x-initializer": "TabPaneInitializers",
"x-initializer-props": {
"gridInitializer": "popup:common:addBlock",
},
},
},
"title": "{{t('View record', { ns: 'calendar' })}}",
"type": "void",
"x-component": "Action.Drawer",
"x-component-props": {
"className": "nb-action-popup",
},
},
},
"type": "void",
"x-component": "CalendarV2.Event",
},
"toolBar": {
"type": "void",
"x-component": "CalendarV2.ActionBar",
"x-component-props": {
"style": {
"marginBottom": 24,
},
},
"x-initializer": "calendar:configureActions",
},
},
"type": "void",
"x-component": "CalendarV2",
"x-use-component-props": "useCalendarBlockProps",
},
},
"type": "void",
"x-acl-action": "users.roles:list",
"x-component": "CardItem",
"x-decorator": "CalendarBlockProvider",
"x-decorator-props": {
"action": "list",
"association": "users.roles",
"collection": "users",
"dataSource": "events",
"fieldNames": {
"endDate": "end_date",
"id": "id",
"startDate": "start_date",
"title": "title",
},
"params": {
"paginate": false,
},
},
"x-settings": "blockSettings:calendar",
"x-toolbar": "BlockSchemaToolbar",
"x-use-decorator-props": "useCalendarBlockDecoratorProps",
}
`);
});
});

View File

@ -1,6 +1,12 @@
import { LeftOutlined, RightOutlined } from '@ant-design/icons'; import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import { RecursionField, Schema, observer, useFieldSchema } from '@formily/react'; import { RecursionField, Schema, observer, useFieldSchema } from '@formily/react';
import { ActionContextProvider, RecordProvider, useCollectionParentRecordData, useProps } from '@nocobase/client'; import {
ActionContextProvider,
RecordProvider,
useCollectionParentRecordData,
useProps,
withDynamicSchemaProps,
} from '@nocobase/client';
import { parseExpression } from 'cron-parser'; import { parseExpression } from 'cron-parser';
import type { Dayjs } from 'dayjs'; import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -171,100 +177,104 @@ const CalendarRecordViewer = (props) => {
); );
}; };
export const Calendar: any = observer( export const Calendar: any = withDynamicSchemaProps(
(props: any) => { observer(
const { dataSource, fieldNames, showLunar, fixedBlock } = useProps(props); (props: any) => {
const [date, setDate] = useState<Date>(new Date()); // 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const [view, setView] = useState<View>('month'); const { dataSource, fieldNames, showLunar, fixedBlock } = useProps(props);
const events = useEvents(dataSource, fieldNames, date, view);
const [visible, setVisible] = useState(false);
const [record, setRecord] = useState<any>({});
const { wrapSSR, hashId, componentCls: containerClassName } = useStyle();
const components = useMemo(() => { const [date, setDate] = useState<Date>(new Date());
return { const [view, setView] = useState<View>('month');
toolbar: (props) => <Toolbar {...props} showLunar={showLunar}></Toolbar>, const events = useEvents(dataSource, fieldNames, date, view);
// week: { const [visible, setVisible] = useState(false);
// header: (props) => <Header {...props} type="week" showLunar={showLunar}></Header>, const [record, setRecord] = useState<any>({});
// }, const { wrapSSR, hashId, componentCls: containerClassName } = useStyle();
month: {
dateHeader: (props) => <Header {...props} showLunar={showLunar}></Header>, const components = useMemo(() => {
}, return {
toolbar: (props) => <Toolbar {...props} showLunar={showLunar}></Toolbar>,
// week: {
// header: (props) => <Header {...props} type="week" showLunar={showLunar}></Header>,
// },
month: {
dateHeader: (props) => <Header {...props} showLunar={showLunar}></Header>,
},
};
}, [showLunar]);
const messages: any = {
allDay: '',
previous: (
<div>
<LeftOutlined />
</div>
),
next: (
<div>
<RightOutlined />
</div>
),
today: i18nt('Today'),
month: i18nt('Month'),
week: i18nt('Week'),
work_week: i18nt('Work week'),
day: i18nt('Day'),
agenda: i18nt('Agenda'),
date: i18nt('Date'),
time: i18nt('Time'),
event: i18nt('Event'),
noEventsInRange: i18nt('None'),
showMore: (count) => i18nt('{{count}} more items', { count }),
}; };
}, [showLunar]); return wrapSSR(
<div className={`${hashId} ${containerClassName}`} style={{ height: fixedBlock ? '100%' : 700 }}>
const messages: any = { <GlobalStyle />
allDay: '', <CalendarRecordViewer visible={visible} setVisible={setVisible} record={record} />
previous: ( <BigCalendar
<div> popup
<LeftOutlined /> selectable
</div> events={events}
), view={view}
next: ( views={Weeks}
<div> date={date}
<RightOutlined /> step={60}
</div> showMultiDayTimes
), messages={messages}
today: i18nt('Today'), onNavigate={setDate}
month: i18nt('Month'), onView={setView}
week: i18nt('Week'), onSelectSlot={(slotInfo) => {
work_week: i18nt('Work week'), console.log('onSelectSlot', slotInfo);
day: i18nt('Day'), }}
agenda: i18nt('Agenda'), onDoubleClickEvent={() => {
date: i18nt('Date'), console.log('onDoubleClickEvent');
time: i18nt('Time'), }}
event: i18nt('Event'), onSelectEvent={(event) => {
noEventsInRange: i18nt('None'), const record = dataSource?.find((item) => item[fieldNames.id] === event.id);
showMore: (count) => i18nt('{{count}} more items', { count }), if (!record) {
}; return;
return wrapSSR(
<div className={`${hashId} ${containerClassName}`} style={{ height: fixedBlock ? '100%' : 700 }}>
<GlobalStyle />
<CalendarRecordViewer visible={visible} setVisible={setVisible} record={record} />
<BigCalendar
popup
selectable
events={events}
view={view}
views={Weeks}
date={date}
step={60}
showMultiDayTimes
messages={messages}
onNavigate={setDate}
onView={setView}
onSelectSlot={(slotInfo) => {
console.log('onSelectSlot', slotInfo);
}}
onDoubleClickEvent={() => {
console.log('onDoubleClickEvent');
}}
onSelectEvent={(event) => {
const record = dataSource?.find((item) => item[fieldNames.id] === event.id);
if (!record) {
return;
}
record.__event = { ...event, start: formatDate(dayjs(event.start)), end: formatDate(dayjs(event.end)) };
setRecord(record);
setVisible(true);
}}
formats={{
monthHeaderFormat: 'YYYY-M',
agendaDateFormat: 'M-DD',
dayHeaderFormat: 'YYYY-M-DD',
dayRangeHeaderFormat: ({ start, end }, culture, local) => {
if (dates.eq(start, end, 'month')) {
return local.format(start, 'YYYY-M', culture);
} }
return `${local.format(start, 'YYYY-M', culture)} - ${local.format(end, 'YYYY-M', culture)}`; record.__event = { ...event, start: formatDate(dayjs(event.start)), end: formatDate(dayjs(event.end)) };
},
}} setRecord(record);
components={components} setVisible(true);
localizer={localizer} }}
/> formats={{
</div>, monthHeaderFormat: 'YYYY-M',
); agendaDateFormat: 'M-DD',
}, dayHeaderFormat: 'YYYY-M-DD',
{ displayName: 'Calendar' }, dayRangeHeaderFormat: ({ start, end }, culture, local) => {
if (dates.eq(start, end, 'month')) {
return local.format(start, 'YYYY-M', culture);
}
return `${local.format(start, 'YYYY-M', culture)} - ${local.format(end, 'YYYY-M', culture)}`;
},
}}
components={components}
localizer={localizer}
/>
</div>,
);
},
{ displayName: 'Calendar' },
),
); );

View File

@ -0,0 +1,18 @@
import { useDataBlockSourceId } from '@nocobase/client';
import { useCalendarBlockParams } from './useCalendarBlockParams';
export function useCalendarBlockDecoratorProps(props) {
const params = useCalendarBlockParams(props);
let sourceId: string;
// 因为 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,22 @@
import { useMemo } from 'react';
export function useCalendarBlockParams(props) {
const appends = useMemo(() => {
const arr: string[] = [];
const start = props.fieldNames?.start;
const end = props.fieldNames?.end;
if (Array.isArray(start) && start.length >= 2) {
arr.push(start[0]);
}
if (Array.isArray(end) && end.length >= 2) {
arr.push(end[0]);
}
return arr;
}, [props.fieldNames]);
return useMemo(() => {
return { ...props.params, appends: [...appends, ...(props.params.appends || [])], paginate: false };
}, [appends, props.params]);
}

View File

@ -16,6 +16,7 @@ import {
useCreateAssociationCalendarBlock, useCreateAssociationCalendarBlock,
} from './schema-initializer/items'; } from './schema-initializer/items';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useCalendarBlockDecoratorProps } from './hooks/useCalendarBlockDecoratorProps';
export class PluginCalendarClient extends Plugin { export class PluginCalendarClient extends Plugin {
async load() { async load() {
@ -60,7 +61,7 @@ export class PluginCalendarClient extends Plugin {
RecordAssociationCalendarBlockInitializer, RecordAssociationCalendarBlockInitializer,
CalendarV2, CalendarV2,
}); });
this.app.addScopes({ useCalendarBlockProps }); this.app.addScopes({ useCalendarBlockProps, useCalendarBlockDecoratorProps });
this.schemaSettingsManager.add(calendarBlockSettings); this.schemaSettingsManager.add(calendarBlockSettings);
this.app.schemaInitializerManager.add(CalendarActionInitializers_deprecated); this.app.schemaInitializerManager.add(CalendarActionInitializers_deprecated);
this.app.schemaInitializerManager.add(calendarActionInitializers); this.app.schemaInitializerManager.add(calendarActionInitializers);

View File

@ -1,8 +1,15 @@
import { ArrayField } from '@formily/core'; import { ArrayField } from '@formily/core';
import { useField } from '@formily/react'; import { useField, useFieldSchema } from '@formily/react';
import { BlockProvider, FixedBlockWrapper, useBlockRequestContext, useParsedFilter } from '@nocobase/client'; import {
BlockProvider,
FixedBlockWrapper,
useBlockRequestContext,
useParsedFilter,
withDynamicSchemaProps,
} from '@nocobase/client';
import _ from 'lodash'; import _ from 'lodash';
import React, { createContext, useContext, useEffect, useMemo } from 'react'; import React, { createContext, useContext, useEffect, useMemo } from 'react';
import { useCalendarBlockParams } from '../hooks/useCalendarBlockParams';
export const CalendarBlockContext = createContext<any>({}); export const CalendarBlockContext = createContext<any>({});
CalendarBlockContext.displayName = 'CalendarBlockContext'; CalendarBlockContext.displayName = 'CalendarBlockContext';
@ -38,31 +45,26 @@ const InternalCalendarBlockProvider = (props) => {
); );
}; };
export const CalendarBlockProvider = (props) => { const useCompatCalendarBlockParams = (props) => {
const appends = useMemo(() => { const fieldSchema = useFieldSchema();
const arr: string[] = [];
const start = props.fieldNames?.start;
const end = props.fieldNames?.end;
if (Array.isArray(start) && start.length >= 2) { // 因为 x-use-decorator-props 的值是固定不变的,所以可以在条件中使用 hooks
arr.push(start[0]); if (fieldSchema['x-use-decorator-props']) {
} return props.params;
if (Array.isArray(end) && end.length >= 2) { } else {
arr.push(end[0]); // eslint-disable-next-line react-hooks/rules-of-hooks
} return useCalendarBlockParams(props);
}
};
return arr; export const CalendarBlockProvider = withDynamicSchemaProps((props) => {
}, [props.fieldNames]); const params = useCompatCalendarBlockParams(props);
return ( return (
<BlockProvider <BlockProvider name="calendar" {...props} params={params}>
name="calendar"
{...props}
params={{ ...props.params, appends: [...appends, ...(props.params.appends || [])], paginate: false }}
>
<InternalCalendarBlockProvider {...props} /> <InternalCalendarBlockProvider {...props} />
</BlockProvider> </BlockProvider>
); );
}; });
export const useCalendarBlockContext = () => { export const useCalendarBlockContext = () => {
return useContext(CalendarBlockContext); return useContext(CalendarBlockContext);

View File

@ -2,16 +2,23 @@ import { ISchema } from '@formily/react';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import { generateNTemplate } from '../../locale'; import { generateNTemplate } from '../../locale';
export const createCalendarBlockSchema = (options) => { export const createCalendarBlockUISchema = (options: {
const { collection, dataSource, resource, fieldNames, settings, ...others } = options; dataSource: string;
const schema: ISchema = { fieldNames: object;
collectionName?: string;
association?: string;
}): ISchema => {
const { collectionName, dataSource, fieldNames, association } = options;
return {
type: 'void', type: 'void',
'x-acl-action': `${resource || collection}:list`, 'x-acl-action': `${association || collectionName}:list`,
'x-decorator': 'CalendarBlockProvider', 'x-decorator': 'CalendarBlockProvider',
'x-use-decorator-props': 'useCalendarBlockDecoratorProps',
'x-decorator-props': { 'x-decorator-props': {
collection: collection, collection: collectionName,
dataSource, dataSource,
resource: resource || collection, association,
action: 'list', action: 'list',
fieldNames: { fieldNames: {
id: 'id', id: 'id',
@ -20,18 +27,15 @@ export const createCalendarBlockSchema = (options) => {
params: { params: {
paginate: false, paginate: false,
}, },
...others,
}, },
'x-toolbar': 'BlockSchemaToolbar', 'x-toolbar': 'BlockSchemaToolbar',
'x-settings': settings, 'x-settings': 'blockSettings:calendar',
'x-component': 'CardItem', 'x-component': 'CardItem',
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',
'x-component': 'CalendarV2', 'x-component': 'CalendarV2',
'x-component-props': { 'x-use-component-props': 'useCalendarBlockProps',
useProps: '{{ useCalendarBlockProps }}',
},
properties: { properties: {
toolBar: { toolBar: {
type: 'void', type: 'void',
@ -42,7 +46,6 @@ export const createCalendarBlockSchema = (options) => {
}, },
}, },
'x-initializer': 'calendar:configureActions', 'x-initializer': 'calendar:configureActions',
properties: {},
}, },
event: { event: {
type: 'void', type: 'void',
@ -79,7 +82,6 @@ export const createCalendarBlockSchema = (options) => {
actionInitializers: 'details:configureActions', actionInitializers: 'details:configureActions',
}, },
'x-initializer': 'popup:common:addBlock', 'x-initializer': 'popup:common:addBlock',
properties: {},
}, },
}, },
}, },
@ -93,6 +95,4 @@ export const createCalendarBlockSchema = (options) => {
}, },
}, },
}; };
return schema;
}; };

View File

@ -14,7 +14,7 @@ import {
useSchemaInitializerItem, useSchemaInitializerItem,
} from '@nocobase/client'; } from '@nocobase/client';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { createCalendarBlockSchema } from '../utils'; import { createCalendarBlockUISchema } from '../createCalendarBlockUISchema';
import { useTranslation } from '../../../locale'; import { useTranslation } from '../../../locale';
export const CalendarBlockInitializer = ({ export const CalendarBlockInitializer = ({
@ -95,13 +95,12 @@ export const CalendarBlockInitializer = ({
initialValues: {}, initialValues: {},
}); });
insert( insert(
createCalendarBlockSchema({ createCalendarBlockUISchema({
collection: item.name, collectionName: item.name,
dataSource: item.dataSource, dataSource: item.dataSource,
fieldNames: { fieldNames: {
...values, ...values,
}, },
settings: 'blockSettings:calendar',
}), }),
); );
}} }}

View File

@ -15,7 +15,7 @@ import {
useSchemaInitializer, useSchemaInitializer,
SchemaInitializerItem, SchemaInitializerItem,
} from '@nocobase/client'; } from '@nocobase/client';
import { createCalendarBlockSchema } from '../utils'; import { createCalendarBlockUISchema } from '../createCalendarBlockUISchema';
import { useTranslation } from '../../../locale'; import { useTranslation } from '../../../locale';
export const RecordAssociationCalendarBlockInitializer = () => { export const RecordAssociationCalendarBlockInitializer = () => {
@ -98,10 +98,9 @@ export const RecordAssociationCalendarBlockInitializer = () => {
initialValues: {}, initialValues: {},
}); });
insert( insert(
createCalendarBlockSchema({ createCalendarBlockUISchema({
collection: field.target,
resource,
association: resource, association: resource,
dataSource: item.dataSource,
fieldNames: { fieldNames: {
...values, ...values,
}, },
@ -183,13 +182,12 @@ export function useCreateAssociationCalendarBlock() {
initialValues: {}, initialValues: {},
}); });
insert( insert(
createCalendarBlockSchema({ createCalendarBlockUISchema({
collection: field.target,
association: `${field.collectionName}.${field.name}`, association: `${field.collectionName}.${field.name}`,
dataSource: item.dataSource,
fieldNames: { fieldNames: {
...values, ...values,
}, },
settings: 'blockSettings:calendar',
}), }),
); );
}; };

View File

@ -14,7 +14,7 @@ import {
DataBlockInitializer, DataBlockInitializer,
SchemaComponentOptions, SchemaComponentOptions,
} from '@nocobase/client'; } from '@nocobase/client';
import { createGanttBlockSchema } from './utils'; import { createGanttBlockUISchema } from './createGanttBlockUISchema';
export const GanttBlockInitializer = () => { export const GanttBlockInitializer = () => {
const { insert } = useSchemaInitializer(); const { insert } = useSchemaInitializer();
@ -120,8 +120,8 @@ export const GanttBlockInitializer = () => {
initialValues: {}, initialValues: {},
}); });
insert( insert(
createGanttBlockSchema({ createGanttBlockUISchema({
collection: item.name, collectionName: item.name,
dataSource: item.dataSource, dataSource: item.dataSource,
fieldNames: { fieldNames: {
...values, ...values,

View File

@ -0,0 +1,143 @@
import { createGanttBlockUISchema } from '../createGanttBlockUISchema';
vi.mock('@formily/shared', () => {
return {
uid: () => 'mocked-uid',
};
});
describe('createGanttBlockSchema', () => {
it('should generate schema correctly', () => {
const options = {
collectionName: 'TestCollection',
fieldNames: {
label: 'field1',
value: 'field2',
},
dataSource: 'TestDataSource',
};
const schema = createGanttBlockUISchema(options);
expect(schema).toMatchInlineSnapshot(`
{
"properties": {
"mocked-uid": {
"properties": {
"detail": {
"properties": {
"drawer": {
"properties": {
"tabs": {
"properties": {
"tab1": {
"properties": {
"grid": {
"type": "void",
"x-component": "Grid",
"x-initializer": "popup:common:addBlock",
},
},
"title": "{{t("Details")}}",
"type": "void",
"x-component": "Tabs.TabPane",
"x-component-props": {},
"x-designer": "Tabs.Designer",
},
},
"type": "void",
"x-component": "Tabs",
"x-component-props": {},
"x-initializer": "TabPaneInitializers",
},
},
"title": "{{ t("View record") }}",
"type": "void",
"x-component": "Action.Drawer",
"x-component-props": {
"className": "nb-action-popup",
},
},
},
"type": "void",
"x-component": "Gantt.Event",
},
"table": {
"properties": {
"actions": {
"properties": {
"actions": {
"type": "void",
"x-component": "Space",
"x-component-props": {
"split": "|",
},
"x-decorator": "DndContext",
},
},
"title": "{{ t("Actions") }}",
"type": "void",
"x-action-column": "actions",
"x-component": "TableV2.Column",
"x-decorator": "TableV2.Column.ActionBar",
"x-designer": "TableV2.ActionColumnDesigner",
"x-initializer": "table:configureItemActions",
},
},
"type": "array",
"x-component": "TableV2",
"x-component-props": {
"pagination": false,
"rowKey": "id",
"rowSelection": {
"type": "checkbox",
},
},
"x-decorator": "div",
"x-decorator-props": {
"style": {
"float": "left",
"maxWidth": "35%",
},
},
"x-initializer": "table:configureColumns",
"x-use-component-props": "useTableBlockProps",
},
"toolBar": {
"properties": {},
"type": "void",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": 24,
},
},
"x-initializer": "gantt:configureActions",
},
},
"type": "void",
"x-component": "Gantt",
"x-use-component-props": "useGanttBlockProps",
},
},
"type": "void",
"x-acl-action": "TestCollection:list",
"x-component": "CardItem",
"x-decorator": "GanttBlockProvider",
"x-decorator-props": {
"action": "list",
"collection": "TestCollection",
"dataSource": "TestDataSource",
"fieldNames": {
"label": "field1",
"value": "field2",
},
"params": {
"paginate": false,
},
},
"x-settings": "blockSettings:gantt",
"x-toolbar": "BlockSchemaToolbar",
}
`);
});
});

View File

@ -9,6 +9,8 @@ import {
useCollectionParentRecordData, useCollectionParentRecordData,
useTableBlockContext, useTableBlockContext,
useToken, useToken,
withDynamicSchemaProps,
useProps,
} from '@nocobase/client'; } from '@nocobase/client';
import { message } from 'antd'; import { message } from 'antd';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
@ -81,7 +83,8 @@ const debounceHandleProcessChange = debounce(async (task: Task, resource, fieldN
message.success(t('Saved successfully')); message.success(t('Saved successfully'));
await service?.refresh(); await service?.refresh();
}, 300); }, 300);
export const Gantt: any = (props: any) => {
export const Gantt: any = withDynamicSchemaProps((props: any) => {
const { styles } = useStyles(); const { styles } = useStyles();
const { token } = useToken(); const { token } = useToken();
const api = useAPIClient(); const api = useAPIClient();
@ -116,12 +119,13 @@ export const Gantt: any = (props: any) => {
viewDate, viewDate,
TooltipContent = StandardTooltipContent, TooltipContent = StandardTooltipContent,
onDoubleClick, onDoubleClick,
onClick,
onDelete, onDelete,
onSelect, onSelect,
useProps, onExpanderClick,
} = props; tasks,
const { onExpanderClick, tasks, expandAndCollapseAll } = useProps(); expandAndCollapseAll,
fieldNames,
} = useProps(props); // 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const ctx = useGanttBlockContext(); const ctx = useGanttBlockContext();
const appInfo = useCurrentAppInfo(); const appInfo = useCurrentAppInfo();
const { t } = useTranslation(); const { t } = useTranslation();
@ -129,7 +133,6 @@ export const Gantt: any = (props: any) => {
const tableCtx = useTableBlockContext(); const tableCtx = useTableBlockContext();
const { resource, service } = useBlockRequestContext(); const { resource, service } = useBlockRequestContext();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const { fieldNames } = useProps(props);
const viewMode = fieldNames.range || 'day'; const viewMode = fieldNames.range || 'day';
const wrapperRef = useRef<HTMLDivElement>(null); const wrapperRef = useRef<HTMLDivElement>(null);
const taskListRef = useRef<HTMLDivElement>(null); const taskListRef = useRef<HTMLDivElement>(null);
@ -559,4 +562,4 @@ export const Gantt: any = (props: any) => {
</div> </div>
</div> </div>
); );
}; });

View File

@ -0,0 +1,129 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
export const createGanttBlockUISchema = (options: {
collectionName: string;
fieldNames: object;
dataSource: string;
}): ISchema => {
const { collectionName, fieldNames, dataSource } = options;
return {
type: 'void',
'x-acl-action': `${collectionName}:list`,
'x-decorator': 'GanttBlockProvider',
'x-decorator-props': {
collection: collectionName,
dataSource,
action: 'list',
fieldNames,
params: {
paginate: false,
},
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:gantt',
// 'x-designer': 'Gantt.Designer',
'x-component': 'CardItem',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Gantt',
'x-use-component-props': 'useGanttBlockProps',
properties: {
toolBar: {
type: 'void',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 24,
},
},
'x-initializer': 'gantt:configureActions',
properties: {},
},
table: {
type: 'array',
'x-decorator': 'div',
'x-decorator-props': {
style: {
float: 'left',
maxWidth: '35%',
},
},
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-use-component-props': 'useTableBlockProps',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
pagination: false,
},
properties: {
actions: {
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-designer': 'TableV2.ActionColumnDesigner',
'x-initializer': 'table:configureItemActions',
properties: {
actions: {
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
},
},
},
},
},
detail: {
type: 'void',
'x-component': 'Gantt.Event',
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-component-props': {
className: 'nb-action-popup',
},
title: '{{ t("View record") }}',
properties: {
tabs: {
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'TabPaneInitializers',
properties: {
tab1: {
type: 'void',
title: '{{t("Details")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:common:addBlock',
},
},
},
},
},
},
},
},
},
},
},
},
};
};

View File

@ -41,6 +41,10 @@ export class GanttPlugin extends Plugin {
title: "{{t('Gantt')}}", title: "{{t('Gantt')}}",
Component: 'GanttBlockInitializer', Component: 'GanttBlockInitializer',
}); });
this.app.addScopes({
useGanttBlockProps,
});
} }
} }

View File

@ -1,5 +1,3 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
import { useCollection_deprecated, useCompile } from '@nocobase/client'; import { useCollection_deprecated, useCompile } from '@nocobase/client';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -19,131 +17,3 @@ export const useOptions = (type = 'string') => {
}); });
return options; return options;
}; };
export const createGanttBlockSchema = (options) => {
const { collection, resource, fieldNames, ...others } = options;
const schema: ISchema = {
type: 'void',
'x-acl-action': `${resource || collection}:list`,
'x-decorator': 'GanttBlockProvider',
'x-decorator-props': {
collection: collection,
resource: resource || collection,
action: 'list',
fieldNames: {
...fieldNames,
},
params: {
paginate: false,
},
...others,
},
'x-designer': 'Gantt.Designer',
'x-component': 'CardItem',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Gantt',
'x-component-props': {
useProps: '{{ useGanttBlockProps }}',
},
properties: {
toolBar: {
type: 'void',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 24,
},
},
'x-initializer': 'gantt:configureActions',
properties: {},
},
table: {
type: 'array',
'x-decorator': 'div',
'x-decorator-props': {
style: {
float: 'left',
maxWidth: '35%',
},
},
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
useProps: '{{ useTableBlockProps }}',
pagination: false,
},
properties: {
actions: {
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-designer': 'TableV2.ActionColumnDesigner',
'x-initializer': 'table:configureItemActions',
properties: {
actions: {
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {},
},
},
},
},
},
detail: {
type: 'void',
'x-component': 'Gantt.Event',
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-component-props': {
className: 'nb-action-popup',
},
title: '{{ t("View record") }}',
properties: {
tabs: {
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'TabPaneInitializers',
properties: {
tab1: {
type: 'void',
title: '{{t("Details")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:common:addBlock',
properties: {},
},
},
},
},
},
},
},
},
},
},
},
},
};
return schema;
};

View File

@ -6,6 +6,7 @@ import {
useCreateActionProps as useCAP, useCreateActionProps as useCAP,
useCollectionParentRecordData, useCollectionParentRecordData,
useProps, useProps,
withDynamicSchemaProps,
} from '@nocobase/client'; } from '@nocobase/client';
import { Spin, Tag } from 'antd'; import { Spin, Tag } from 'antd';
import React, { useContext, useMemo, useState } from 'react'; import React, { useContext, useMemo, useState } from 'react';
@ -56,100 +57,105 @@ export const toColumns = (groupField: any, dataSource: Array<any> = [], primaryK
return Object.values(columns); return Object.values(columns);
}; };
export const Kanban: any = observer( export const Kanban: any = withDynamicSchemaProps(
(props: any) => { observer(
const { styles } = useStyles(); (props: any) => {
const { groupField, onCardDragEnd, dataSource, setDataSource, ...restProps } = useProps(props); const { styles } = useStyles();
const parentRecordData = useCollectionParentRecordData();
const field = useField<ArrayField>(); // 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const fieldSchema = useFieldSchema(); const { groupField, onCardDragEnd, dataSource, setDataSource, ...restProps } = useProps(props);
const [disableCardDrag, setDisableCardDrag] = useState(false);
const schemas = useMemo( const parentRecordData = useCollectionParentRecordData();
() => const field = useField<ArrayField>();
fieldSchema.reduceProperties( const fieldSchema = useFieldSchema();
(buf, current) => { const [disableCardDrag, setDisableCardDrag] = useState(false);
if (current['x-component'].endsWith('.Card')) { const schemas = useMemo(
buf.card = current; () =>
} else if (current['x-component'].endsWith('.CardAdder')) { fieldSchema.reduceProperties(
buf.cardAdder = current; (buf, current) => {
} else if (current['x-component'].endsWith('.CardViewer')) { if (current['x-component'].endsWith('.Card')) {
buf.cardViewer = current; buf.card = current;
} } else if (current['x-component'].endsWith('.CardAdder')) {
return buf; buf.cardAdder = current;
}, } else if (current['x-component'].endsWith('.CardViewer')) {
{ card: null, cardAdder: null, cardViewer: null }, buf.cardViewer = current;
), }
[], return buf;
); },
const handleCardRemove = (card, column) => { { card: null, cardAdder: null, cardViewer: null },
const updatedBoard = Board.removeCard({ columns: field.value }, column, card); ),
field.value = updatedBoard.columns; [],
setDataSource(updatedBoard.columns); );
}; const handleCardRemove = (card, column) => {
const handleCardDragEnd = (card, fromColumn, toColumn) => { const updatedBoard = Board.removeCard({ columns: field.value }, column, card);
onCardDragEnd?.({ columns: field.value, groupField }, fromColumn, toColumn); field.value = updatedBoard.columns;
const updatedBoard = Board.moveCard({ columns: field.value }, fromColumn, toColumn); setDataSource(updatedBoard.columns);
field.value = updatedBoard.columns; };
setDataSource(updatedBoard.columns); const handleCardDragEnd = (card, fromColumn, toColumn) => {
}; onCardDragEnd?.({ columns: field.value, groupField }, fromColumn, toColumn);
return ( const updatedBoard = Board.moveCard({ columns: field.value }, fromColumn, toColumn);
<Spin wrapperClassName={styles.nbKanban} spinning={field.loading || false}> field.value = updatedBoard.columns;
<Board setDataSource(updatedBoard.columns);
{...restProps} };
allowAddCard={!!schemas.cardAdder} return (
disableColumnDrag <Spin wrapperClassName={styles.nbKanban} spinning={field.loading || false}>
cardAdderPosition={'bottom'} <Board
disableCardDrag={restProps.disableCardDrag || disableCardDrag} {...restProps}
onCardRemove={handleCardRemove} allowAddCard={!!schemas.cardAdder}
onCardDragEnd={handleCardDragEnd} disableColumnDrag
renderColumnHeader={({ title, color }) => ( cardAdderPosition={'bottom'}
<div className={'react-kanban-column-header'}> disableCardDrag={restProps.disableCardDrag || disableCardDrag}
<Tag color={color}>{title}</Tag> onCardRemove={handleCardRemove}
</div> onCardDragEnd={handleCardDragEnd}
)} renderColumnHeader={({ title, color }) => (
renderCard={(card, { column, dragging }) => { <div className={'react-kanban-column-header'}>
const columnIndex = dataSource?.indexOf(column); <Tag color={color}>{title}</Tag>
const cardIndex = column?.cards?.indexOf(card); </div>
return ( )}
schemas.card && ( renderCard={(card, { column, dragging }) => {
<RecordProvider record={card} parent={parentRecordData}> const columnIndex = dataSource?.indexOf(column);
<KanbanCardContext.Provider const cardIndex = column?.cards?.indexOf(card);
value={{ return (
setDisableCardDrag, schemas.card && (
cardViewerSchema: schemas.cardViewer, <RecordProvider record={card} parent={parentRecordData}>
cardField: field, <KanbanCardContext.Provider
card, value={{
column, setDisableCardDrag,
dragging, cardViewerSchema: schemas.cardViewer,
columnIndex, cardField: field,
cardIndex, card,
}} column,
> dragging,
<RecursionField name={schemas.card.name} schema={schemas.card} /> columnIndex,
</KanbanCardContext.Provider> cardIndex,
</RecordProvider> }}
) >
); <RecursionField name={schemas.card.name} schema={schemas.card} />
}} </KanbanCardContext.Provider>
renderCardAdder={({ column }) => { </RecordProvider>
if (!schemas.cardAdder) { )
return null; );
} }}
return ( renderCardAdder={({ column }) => {
<KanbanColumnContext.Provider value={{ column, groupField }}> if (!schemas.cardAdder) {
<SchemaComponentOptions scope={{ useCreateActionProps }}> return null;
<RecursionField name={schemas.cardAdder.name} schema={schemas.cardAdder} /> }
</SchemaComponentOptions> return (
</KanbanColumnContext.Provider> <KanbanColumnContext.Provider value={{ column, groupField }}>
); <SchemaComponentOptions scope={{ useCreateActionProps }}>
}} <RecursionField name={schemas.cardAdder.name} schema={schemas.cardAdder} />
> </SchemaComponentOptions>
{{ </KanbanColumnContext.Provider>
columns: dataSource || [], );
}} }}
</Board> >
</Spin> {{
); columns: dataSource || [],
}, }}
{ displayName: 'Kanban' }, </Board>
</Spin>
);
},
{ displayName: 'Kanban' },
),
); );

View File

@ -16,7 +16,7 @@ import {
useSchemaInitializerItem, useSchemaInitializerItem,
useAPIClient, useAPIClient,
} from '@nocobase/client'; } from '@nocobase/client';
import { createKanbanBlockSchema } from './utils'; import { createKanbanBlockUISchema } from './createKanbanBlockUISchema';
import { CreateAndSelectSort } from './CreateAndSelectSort'; import { CreateAndSelectSort } from './CreateAndSelectSort';
import { NAMESPACE } from './locale'; import { NAMESPACE } from './locale';
@ -157,14 +157,13 @@ export const KanbanBlockInitializer = () => {
initialValues: {}, initialValues: {},
}); });
insert( insert(
createKanbanBlockSchema({ createKanbanBlockUISchema({
sortField: values.dragSortBy, sortField: values.dragSortBy,
groupField: values.groupField.value, groupField: values.groupField.value,
collection: item.name, collectionName: item.name,
dataSource: item.dataSource, dataSource: item.dataSource,
params: { params: {
sort: [values.dragSortBy], sort: [values.dragSortBy],
paginate: false,
}, },
}), }),
); );

View File

@ -0,0 +1,126 @@
import { createKanbanBlockUISchema } from '../createKanbanBlockUISchema';
vi.mock('@formily/shared', () => {
return {
uid: vi.fn(() => 'mocked-uid'),
};
});
test('createKanbanBlockSchema should return an object with expected properties', () => {
const options = {
collectionName: 'testCollection',
groupField: 'testGroupField',
sortField: 'testSortField',
dataSource: 'testDataSource',
params: { testParam: 'testValue' },
};
const result = createKanbanBlockUISchema(options);
expect(result).toMatchInlineSnapshot(`
{
"properties": {
"actions": {
"properties": {},
"type": "void",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)",
},
},
"x-initializer": "kanban:configureActions",
},
"mocked-uid": {
"properties": {
"card": {
"properties": {
"grid": {
"type": "void",
"x-component": "Grid",
"x-component-props": {
"dndContext": false,
},
},
},
"type": "void",
"x-component": "Kanban.Card",
"x-component-props": {
"openMode": "drawer",
},
"x-decorator": "BlockItem",
"x-designer": "Kanban.Card.Designer",
"x-label-disabled": true,
"x-read-pretty": true,
},
"cardViewer": {
"properties": {
"drawer": {
"properties": {
"tabs": {
"properties": {
"tab1": {
"properties": {
"grid": {
"properties": {},
"type": "void",
"x-component": "Grid",
"x-initializer": "popup:common:addBlock",
},
},
"title": "{{t("Details")}}",
"type": "void",
"x-component": "Tabs.TabPane",
"x-component-props": {},
"x-designer": "Tabs.Designer",
},
},
"type": "void",
"x-component": "Tabs",
"x-component-props": {},
"x-initializer": "TabPaneInitializers",
},
},
"title": "{{ t("View record") }}",
"type": "void",
"x-component": "Action.Container",
"x-component-props": {
"className": "nb-action-popup",
},
},
},
"title": "{{ t("View") }}",
"type": "void",
"x-action": "view",
"x-component": "Kanban.CardViewer",
"x-component-props": {
"openMode": "drawer",
},
"x-designer": "Action.Designer",
},
},
"type": "array",
"x-component": "Kanban",
"x-use-component-props": "useKanbanBlockProps",
},
},
"type": "void",
"x-acl-action": "testCollection:list",
"x-component": "CardItem",
"x-decorator": "KanbanBlockProvider",
"x-decorator-props": {
"action": "list",
"collection": "testCollection",
"dataSource": "testDataSource",
"groupField": "testGroupField",
"params": {
"paginate": false,
"testParam": "testValue",
},
"sortField": "testSortField",
},
"x-settings": "blockSettings:kanban",
"x-toolbar": "BlockSchemaToolbar",
}
`);
});

View File

@ -1,24 +1,33 @@
import { ISchema } from '@formily/react'; import { ISchema } from '@formily/react';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
export const createKanbanBlockSchema = (options) => { export const createKanbanBlockUISchema = (options: {
const { collection, resource, groupField, sortField, ...others } = options; collectionName: string;
const schema: ISchema = { groupField: string;
sortField: string;
dataSource: string;
params?: Record<string, any>;
}): ISchema => {
const { collectionName, groupField, sortField, dataSource, params } = options;
return {
type: 'void', type: 'void',
'x-acl-action': `${resource || collection}:list`, 'x-acl-action': `${collectionName}:list`,
'x-decorator': 'KanbanBlockProvider', 'x-decorator': 'KanbanBlockProvider',
'x-decorator-props': { 'x-decorator-props': {
collection: collection, collection: collectionName,
resource: resource || collection,
action: 'list', action: 'list',
groupField, groupField,
sortField, sortField,
params: { params: {
paginate: false, paginate: false,
...params,
}, },
...others, dataSource,
}, },
'x-designer': 'Kanban.Designer', // 'x-designer': 'Kanban.Designer',
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:kanban',
'x-component': 'CardItem', 'x-component': 'CardItem',
properties: { properties: {
actions: { actions: {
@ -35,9 +44,7 @@ export const createKanbanBlockSchema = (options) => {
[uid()]: { [uid()]: {
type: 'array', type: 'array',
'x-component': 'Kanban', 'x-component': 'Kanban',
'x-component-props': { 'x-use-component-props': 'useKanbanBlockProps',
useProps: '{{ useKanbanBlockProps }}',
},
properties: { properties: {
card: { card: {
type: 'void', type: 'void',
@ -106,5 +113,4 @@ export const createKanbanBlockSchema = (options) => {
}, },
}, },
}; };
return schema;
}; };

View File

@ -0,0 +1,95 @@
import { createMapBlockUISchema } from '../../block/createMapBlockUISchema';
vi.mock('@formily/shared', () => {
return {
uid: () => 'mocked-uid',
};
});
test('createMapBlockSchema should return an object with expected properties', () => {
const options = {
collectionName: 'testCollection',
dataSource: 'testDataSource',
fieldNames: {
label: 'field1',
value: 'field2',
},
};
const result = createMapBlockUISchema(options);
expect(result).toMatchInlineSnapshot(`
{
"properties": {
"actions": {
"type": "void",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": 16,
},
},
"x-initializer": "map:configureActions",
},
"mocked-uid": {
"properties": {
"drawer": {
"properties": {
"tabs": {
"properties": {
"tab1": {
"properties": {
"grid": {
"type": "void",
"x-component": "Grid",
"x-initializer": "popup:common:addBlock",
},
},
"title": "{{t("Details")}}",
"type": "void",
"x-component": "Tabs.TabPane",
"x-component-props": {},
"x-designer": "Tabs.Designer",
},
},
"type": "void",
"x-component": "Tabs",
"x-component-props": {},
"x-initializer": "TabPaneInitializers",
},
},
"title": "{{ t("View record") }}",
"type": "void",
"x-component": "Action.Drawer",
"x-component-props": {
"className": "nb-action-popup",
},
},
},
"type": "void",
"x-component": "MapBlock",
"x-use-component-props": "useMapBlockProps",
},
},
"type": "void",
"x-acl-action": "testCollection:list",
"x-component": "CardItem",
"x-decorator": "MapBlockProvider",
"x-decorator-props": {
"action": "list",
"collection": "testCollection",
"dataSource": "testDataSource",
"fieldNames": {
"label": "field1",
"value": "field2",
},
"params": {
"paginate": false,
},
},
"x-filter-targets": [],
"x-settings": "blockSettings:map",
"x-toolbar": "BlockSchemaToolbar",
}
`);
});

View File

@ -1,9 +1,16 @@
import { useCollection_deprecated, useCollectionManager_deprecated, useProps } from '@nocobase/client'; import {
useCollection_deprecated,
useCollectionManager_deprecated,
useProps,
withDynamicSchemaProps,
} from '@nocobase/client';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { MapBlockComponent } from '../components'; import { MapBlockComponent } from '../components';
export const MapBlock = (props) => { export const MapBlock = withDynamicSchemaProps((props) => {
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { fieldNames } = useProps(props); const { fieldNames } = useProps(props);
const { getCollectionJoinField } = useCollectionManager_deprecated(); const { getCollectionJoinField } = useCollectionManager_deprecated();
const { name } = useCollection_deprecated(); const { name } = useCollection_deprecated();
const collectionField = useMemo(() => { const collectionField = useMemo(() => {
@ -12,4 +19,4 @@ export const MapBlock = (props) => {
const fieldComponentProps = collectionField?.uiSchema?.['x-component-props']; const fieldComponentProps = collectionField?.uiSchema?.['x-component-props'];
return <MapBlockComponent {...fieldComponentProps} {...props} collectionField={collectionField} />; return <MapBlockComponent {...fieldComponentProps} {...props} collectionField={collectionField} />;
}; });

View File

@ -13,7 +13,8 @@ import {
} from '@nocobase/client'; } from '@nocobase/client';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { useMapTranslation } from '../locale'; import { useMapTranslation } from '../locale';
import { createMapBlockSchema, findNestedOption } from './utils'; import { findNestedOption } from './utils';
import { createMapBlockUISchema } from './createMapBlockUISchema';
export const MapBlockInitializer = () => { export const MapBlockInitializer = () => {
const itemConfig = useSchemaInitializerItem(); const itemConfig = useSchemaInitializerItem();
@ -84,13 +85,12 @@ export const MapBlockInitializer = () => {
initialValues: {}, initialValues: {},
}); });
insert( insert(
createMapBlockSchema({ createMapBlockUISchema({
collection: item.name, collectionName: item.name,
dataSource: item.dataSource, dataSource: item.dataSource,
fieldNames: { fieldNames: {
...values, ...values,
}, },
settings: 'blockSettings:map',
}), }),
); );
}} }}

View File

@ -0,0 +1,81 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
export const createMapBlockUISchema = (options: {
collectionName: string;
dataSource: string;
fieldNames: object;
}): ISchema => {
const { collectionName, fieldNames, dataSource } = options;
return {
type: 'void',
'x-acl-action': `${collectionName}:list`,
'x-decorator': 'MapBlockProvider',
'x-decorator-props': {
collection: collectionName,
dataSource,
action: 'list',
fieldNames,
params: {
paginate: false,
},
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:map',
'x-component': 'CardItem',
// 保存当前筛选区块所能过滤的数据区块
'x-filter-targets': [],
properties: {
actions: {
type: 'void',
'x-initializer': 'map:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 16,
},
},
},
[uid()]: {
type: 'void',
'x-component': 'MapBlock',
'x-use-component-props': 'useMapBlockProps',
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-component-props': {
className: 'nb-action-popup',
},
title: '{{ t("View record") }}',
properties: {
tabs: {
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'TabPaneInitializers',
properties: {
tab1: {
type: 'void',
title: '{{t("Details")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:common:addBlock',
},
},
},
},
},
},
},
},
},
},
};
};

View File

@ -1,86 +1,3 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
export const createMapBlockSchema = (options) => {
const { collection, resource, fieldNames, settings, ...others } = options;
const schema: ISchema = {
type: 'void',
'x-acl-action': `${resource || collection}:list`,
'x-decorator': 'MapBlockProvider',
'x-decorator-props': {
collection: collection,
resource: resource || collection,
action: 'list',
fieldNames,
params: {
paginate: false,
},
...others,
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': settings,
'x-component': 'CardItem',
// 保存当前筛选区块所能过滤的数据区块
'x-filter-targets': [],
properties: {
actions: {
type: 'void',
'x-initializer': 'map:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 16,
},
},
properties: {},
},
[uid()]: {
type: 'void',
'x-component': 'MapBlock',
'x-component-props': {
useProps: '{{ useMapBlockProps }}',
},
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-component-props': {
className: 'nb-action-popup',
},
title: '{{ t("View record") }}',
properties: {
tabs: {
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'TabPaneInitializers',
properties: {
tab1: {
type: 'void',
title: '{{t("Details")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:common:addBlock',
properties: {},
},
},
},
},
},
},
},
},
},
},
};
return schema;
};
export const findNestedOption = (value: string[] | string, options = []) => { export const findNestedOption = (value: string[] | string, options = []) => {
if (typeof value === 'string') { if (typeof value === 'string') {
value = [value]; value = [value];

View File

@ -20,6 +20,7 @@ import { getSource } from '../../utils';
import { AMapComponent, AMapForwardedRefProps } from './Map'; import { AMapComponent, AMapForwardedRefProps } from './Map';
export const AMapBlock = (props) => { export const AMapBlock = (props) => {
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys, lineSort } = const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys, lineSort } =
useProps(props); useProps(props);
const { name, getPrimaryKey } = useCollection_deprecated(); const { name, getPrimaryKey } = useCollection_deprecated();

View File

@ -38,6 +38,7 @@ const pointClass = css`
`; `;
export const GoogleMapsBlock = (props) => { export const GoogleMapsBlock = (props) => {
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys, lineSort } = const { collectionField, fieldNames, dataSource, fixedBlock, zoom, setSelectedRecordKeys, lineSort } =
useProps(props); useProps(props);
const { getPrimaryKey } = useCollection_deprecated(); const { getPrimaryKey } = useCollection_deprecated();

View File

@ -6,6 +6,7 @@ import { mapBlockSettings } from './block/MapBlock.Settings';
import { Configuration, Map } from './components'; import { Configuration, Map } from './components';
import { fields } from './fields'; import { fields } from './fields';
import { NAMESPACE, generateNTemplate } from './locale'; import { NAMESPACE, generateNTemplate } from './locale';
import { useMapBlockProps } from './block/MapBlockProvider';
const MapProvider = React.memo((props) => { const MapProvider = React.memo((props) => {
return ( return (
<CurrentAppInfoProvider> <CurrentAppInfoProvider>
@ -44,6 +45,10 @@ export class MapPlugin extends Plugin {
Component: Configuration, Component: Configuration,
aclSnippet: 'pm.map.configuration', aclSnippet: 'pm.map.configuration',
}); });
this.app.addScopes({
useMapBlockProps,
});
} }
} }

View File

@ -4,7 +4,6 @@ import {
CollectionProvider_deprecated, CollectionProvider_deprecated,
SchemaInitializerItem, SchemaInitializerItem,
SchemaInitializerItemType, SchemaInitializerItemType,
createFormBlockSchema,
useRecordCollectionDataSourceItems, useRecordCollectionDataSourceItems,
useSchemaInitializer, useSchemaInitializer,
useSchemaInitializerItem, useSchemaInitializerItem,
@ -14,6 +13,7 @@ import {
import { JOB_STATUS, traverseSchema } from '@nocobase/plugin-workflow/client'; import { JOB_STATUS, traverseSchema } from '@nocobase/plugin-workflow/client';
import { NAMESPACE } from '../../locale'; import { NAMESPACE } from '../../locale';
import { createManualFormBlockUISchema } from './createManualFormBlockUISchema';
function InternalFormBlockInitializer({ schema, ...others }) { function InternalFormBlockInitializer({ schema, ...others }) {
const { getTemplateSchemaByMode } = useSchemaTemplateManager(); const { getTemplateSchemaByMode } = useSchemaTemplateManager();
@ -21,7 +21,7 @@ function InternalFormBlockInitializer({ schema, ...others }) {
const items = useRecordCollectionDataSourceItems('FormItem') as SchemaInitializerItemType[]; const items = useRecordCollectionDataSourceItems('FormItem') as SchemaInitializerItemType[];
async function onConfirm({ item }) { async function onConfirm({ item }) {
const template = item.template ? await getTemplateSchemaByMode(item) : null; const template = item.template ? await getTemplateSchemaByMode(item) : null;
const result = createFormBlockSchema({ const result = createManualFormBlockUISchema({
actionInitializers: 'workflowManual:form:configureActions', actionInitializers: 'workflowManual:form:configureActions',
actions: { actions: {
resolve: { resolve: {

View File

@ -0,0 +1,5 @@
import { createFormBlockSchema } from '@nocobase/client';
export function createManualFormBlockUISchema(options) {
return createFormBlockSchema(options);
}