feat: support add new in block for inheritance collection (#1518)

* feat: support adding inherited child collection in blocks

* feat: support adding inherited child collection in blocks

* refactor: create form block display on demand when using

* feat:  support  add new  in-block for  inheritance collection

* fix: action open mode support configuration when adding

* feat: support the configuration of detail and form for inherited collections in block (#1521)

* feat: support the configuration of detail and form for inherited collections in the block

* fix: form and detail is not support current collection

* fix: inherited blocks within blocks only display their own

* style: style improve

* style: style improve

* refactor: detail and form block to determine whether there are inherited collection

* fix: repeated display of child collection

* feat:   add new for  inherited collection, judge the permissions

* feat: support child collection add new configure

* style: style improve

* style: style improve

* fix: child collectio repeatable configuration adding new

* style: style improve

* style: style improve
This commit is contained in:
katherinehhh 2023-03-04 18:54:25 +08:00 committed by GitHub
parent 37998d03ad
commit 555378c342
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 509 additions and 115 deletions

View File

@ -213,7 +213,7 @@ export const ACLActionProvider = (props) => {
if (!actionPath && resource && schema['x-action']) { if (!actionPath && resource && schema['x-action']) {
actionPath = `${resource}:${schema['x-action']}`; actionPath = `${resource}:${schema['x-action']}`;
} }
if (!actionPath.includes(':')) { if (!actionPath?.includes(':')) {
actionPath = `${resource}:${actionPath}`; actionPath = `${resource}:${actionPath}`;
} }
if (!actionPath) { if (!actionPath) {

View File

@ -3,7 +3,7 @@ import { useField } from '@formily/react';
import { Spin } from 'antd'; import { Spin } from 'antd';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react'; import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react';
import { useCollectionManager } from '../collection-manager'; import { useCollection } from '../collection-manager';
import { RecordProvider, useRecord } from '../record-provider'; import { RecordProvider, useRecord } from '../record-provider';
import { useDesignable } from '../schema-component'; import { useDesignable } from '../schema-component';
import { BlockProvider, useBlockRequestContext } from './BlockProvider'; import { BlockProvider, useBlockRequestContext } from './BlockProvider';
@ -51,14 +51,14 @@ const InternalFormBlockProvider = (props) => {
export const FormBlockProvider = (props) => { export const FormBlockProvider = (props) => {
const record = useRecord(); const record = useRecord();
const { __tableName } = record; const { collection } = props;
const { getInheritCollections } = useCollectionManager(); const { __collection } = record;
const inheritCollections = getInheritCollections(__tableName); const currentCollection = useCollection();
const { designable } = useDesignable(); const { designable } = useDesignable();
const flag = const detailFlag = (Object.keys(record).length > 0 && designable) || __collection === collection;
!designable && __tableName && !inheritCollections.includes(props.collection) && __tableName !== props.collection; const createFlag = (currentCollection.name === collection && !Object.keys(record).length) || !currentCollection.name;
return ( return (
!flag && ( (detailFlag || createFlag) && (
<BlockProvider {...props} block={'form'}> <BlockProvider {...props} block={'form'}>
<InternalFormBlockProvider {...props} /> <InternalFormBlockProvider {...props} />
</BlockProvider> </BlockProvider>

View File

@ -94,6 +94,17 @@ export const useSortFields = (collectionName: string) => {
}); });
}; };
export const useChildrenCollections = (collectionName: string) => {
const { getChildrenCollections } = useCollectionManager();
const childrenCollections = getChildrenCollections(collectionName);
return childrenCollections.map((collection: any) => {
return {
value: collection.name,
label: collection?.title || collection.name,
};
});
};
export const useCollectionFilterOptions = (collectionName: string) => { export const useCollectionFilterOptions = (collectionName: string) => {
const { getCollectionFields, getInterface } = useCollectionManager(); const { getCollectionFields, getInterface } = useCollectionManager();
const fields = getCollectionFields(collectionName); const fields = getCollectionFields(collectionName);

View File

@ -1,6 +1,6 @@
import { clone } from '@formily/shared'; import { clone } from '@formily/shared';
import { CascaderProps } from 'antd'; import { CascaderProps } from 'antd';
import { reduce, unionBy, uniq } from 'lodash'; import { reduce, unionBy, uniq, uniqBy } from 'lodash';
import { useContext } from 'react'; import { useContext } from 'react';
import { useCompile } from '../../schema-component'; import { useCompile } from '../../schema-component';
import { CollectionManagerContext } from '../context'; import { CollectionManagerContext } from '../context';
@ -8,7 +8,7 @@ import { CollectionFieldOptions } from '../types';
export const useCollectionManager = () => { export const useCollectionManager = () => {
const { refreshCM, service, interfaces, collections, templates } = useContext(CollectionManagerContext); const { refreshCM, service, interfaces, collections, templates } = useContext(CollectionManagerContext);
const compile = useCompile() const compile = useCompile();
const getInheritedFields = (name) => { const getInheritedFields = (name) => {
const inheritKeys = getInheritCollections(name); const inheritKeys = getInheritCollections(name);
const inheritedFields = reduce( const inheritedFields = reduce(
@ -72,7 +72,7 @@ export const useCollectionManager = () => {
children.push(v); children.push(v);
return getChildren(collectionKey); return getChildren(collectionKey);
}); });
return children; return uniqBy(children, 'key');
}; };
return getChildren(name); return getChildren(name);
}; };
@ -81,14 +81,17 @@ export const useCollectionManager = () => {
return collection?.fields || []; return collection?.fields || [];
}; };
const getCollectionFieldsOptions = (
const getCollectionFieldsOptions = (collectionName: string, type: string | string[] = 'string', opts?: { collectionName: string,
/** type: string | string[] = 'string',
* true opts?: {
* Array<string> /**
*/ * true
association?: boolean | string[]; * Array<string>
}) => { */
association?: boolean | string[];
},
) => {
const { association = false } = opts || {}; const { association = false } = opts || {};
if (typeof type === 'string') { if (typeof type === 'string') {
type = [type]; type = [type];
@ -98,9 +101,10 @@ export const useCollectionManager = () => {
?.filter( ?.filter(
(field) => (field) =>
field.interface && field.interface &&
(type.includes(field.type) || (association && field.target && field.target !== collectionName && (type.includes(field.type) ||
Array.isArray(association) ? association.includes(field.interface) : false (association && field.target && field.target !== collectionName && Array.isArray(association)
)), ? association.includes(field.interface)
: false)),
) )
?.map((field) => { ?.map((field) => {
const result: CascaderProps<any>['options'][0] = { const result: CascaderProps<any>['options'][0] = {
@ -110,13 +114,13 @@ export const useCollectionManager = () => {
if (association && field.target) { if (association && field.target) {
result.children = getCollectionFieldsOptions(field.target, type, opts); result.children = getCollectionFieldsOptions(field.target, type, opts);
if (!result.children?.length) { if (!result.children?.length) {
return null return null;
} }
} }
return result; return result;
}) })
// 过滤 map 产生为 null 的数据 // 过滤 map 产生为 null 的数据
.filter(Boolean) .filter(Boolean);
return options; return options;
}; };

View File

@ -5,7 +5,7 @@ import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDesignable } from '../..'; import { useDesignable } from '../..';
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
import { useCollection } from '../../../collection-manager'; import { useCollection, useCollectionManager } from '../../../collection-manager';
import { useRecord } from '../../../record-provider'; import { useRecord } from '../../../record-provider';
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider'; import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
@ -41,6 +41,7 @@ export const ActionDesigner = (props) => {
const field = useField(); const field = useField();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const { name } = useCollection(); const { name } = useCollection();
const { getChildrenCollections } = useCollectionManager();
const { dn } = useDesignable(); const { dn } = useDesignable();
const { t } = useTranslation(); const { t } = useTranslation();
const isPopupAction = ['create', 'update', 'view', 'customize:popup'].includes(fieldSchema['x-action'] || ''); const isPopupAction = ['create', 'update', 'view', 'customize:popup'].includes(fieldSchema['x-action'] || '');
@ -48,7 +49,7 @@ export const ActionDesigner = (props) => {
const [initialSchema, setInitialSchema] = useState<ISchema>(); const [initialSchema, setInitialSchema] = useState<ISchema>();
const actionType = fieldSchema['x-action'] ?? ''; const actionType = fieldSchema['x-action'] ?? '';
const isLinkageAction = Object.keys(useFormBlockContext()).length > 0 && Object.keys(useRecord()).length > 0; const isLinkageAction = Object.keys(useFormBlockContext()).length > 0 && Object.keys(useRecord()).length > 0;
const isChildCollectionAction = getChildrenCollections(name).length > 0 && fieldSchema['x-action'] === 'create';
useEffect(() => { useEffect(() => {
const schemaUid = uid(); const schemaUid = uid();
const schema: ISchema = { const schema: ISchema = {
@ -379,6 +380,8 @@ export const ActionDesigner = (props) => {
}} }}
/> />
)} )}
{isChildCollectionAction && <SchemaSettings.EnableChildCollections collectionName={name} />}
<SchemaSettings.Divider /> <SchemaSettings.Divider />
<SchemaSettings.Remove <SchemaSettings.Remove
removeParentsIfNoChildren removeParentsIfNoChildren

View File

@ -6,7 +6,7 @@ import React, { useState, useEffect } from 'react';
import { useActionContext } from '../..'; import { useActionContext } from '../..';
import { Icon } from '../../../icon'; import { Icon } from '../../../icon';
import { SortableItem } from '../../common'; import { SortableItem } from '../../common';
import { useCompile, useDesigner } from '../../hooks'; import { useCompile, useComponent, useDesigner } from '../../hooks';
import { useProps } from '../../hooks/useProps'; import { useProps } from '../../hooks/useProps';
import { useRecord } from '../../../record-provider'; import { useRecord } from '../../../record-provider';
import ActionContainer from './Action.Container'; import ActionContainer from './Action.Container';
@ -96,7 +96,8 @@ export const Action: ComposedAction = observer((props: any) => {
const disabled = form.disabled || field.disabled; const disabled = form.disabled || field.disabled;
const openSize = fieldSchema?.['x-component-props']?.['openSize']; const openSize = fieldSchema?.['x-component-props']?.['openSize'];
const linkageRules = fieldSchema?.['x-linkage-rules'] || []; const linkageRules = fieldSchema?.['x-linkage-rules'] || [];
const { designable } = useDesignable(); const { designable, } = useDesignable();
const tarComponent=useComponent(component)||component;
useEffect(() => { useEffect(() => {
linkageRules.map((v) => { linkageRules.map((v) => {
return v.actions?.map((h) => { return v.actions?.map((h) => {
@ -130,7 +131,7 @@ export const Action: ComposedAction = observer((props: any) => {
} }
} }
}} }}
component={component || Button} component={tarComponent || Button}
className={classnames(className, actionDesignerCss)} className={classnames(className, actionDesignerCss)}
> >
{title || compile(fieldSchema.title)} {title || compile(fieldSchema.title)}

View File

@ -1,7 +1,7 @@
import { Schema, useFieldSchema } from '@formily/react'; import { Schema, useFieldSchema } from '@formily/react';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SchemaInitializer, useCollection, useCollectionManager } from '../..'; import { SchemaInitializer, useCollection, useCollectionManager, SchemaInitializerItemOptions } from '../..';
import { gridRowColWrap } from '../utils'; import { gridRowColWrap } from '../utils';
const recursiveParent = (schema: Schema) => { const recursiveParent = (schema: Schema) => {
@ -104,27 +104,70 @@ const useRelationFields = () => {
return relationFields; return relationFields;
}; };
const useInheritFields = (props) => { const useDetailCollections = (props) => {
const { actionInitializers } = props; const { actionInitializers, childrenCollections, collection } = props;
const collection = useCollection(); const detailCollections = [
const { getChildrenCollections } = useCollectionManager(); {
const childrenCollections = getChildrenCollections(collection.name); key: collection.name,
return childrenCollections.map((c) => {
return {
key: c.key,
type: 'item', type: 'item',
title: c?.title || c.name, title: collection?.title || collection.name,
component: 'RecordReadPrettyFormBlockInitializer', component: 'RecordReadPrettyFormBlockInitializer',
icon: false, icon: false,
targetCollection: c, targetCollection: collection,
actionInitializers, actionInitializers,
}; },
}); ].concat(
childrenCollections.map((c) => {
return {
key: c.name,
type: 'item',
title: c?.title || c.name,
component: 'RecordReadPrettyFormBlockInitializer',
icon: false,
targetCollection: c,
actionInitializers,
};
}),
) as SchemaInitializerItemOptions[];
return detailCollections;
};
const useFormCollections = (props) => {
const { actionInitializers, childrenCollections, collection } = props;
const formCollections = [
{
key: collection.name,
type: 'item',
title: collection?.title || collection.name,
component: 'RecordFormBlockInitializer',
icon: false,
targetCollection: collection,
actionInitializers,
},
].concat(
childrenCollections.map((c) => {
return {
key: c.name,
type: 'item',
title: c?.title || c.name,
component: 'RecordFormBlockInitializer',
icon: false,
targetCollection: c,
actionInitializers,
};
}),
) as SchemaInitializerItemOptions[];
return formCollections;
}; };
export const RecordBlockInitializers = (props: any) => { export const RecordBlockInitializers = (props: any) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { insertPosition, component, actionInitializers } = props; const { insertPosition, component, actionInitializers } = props;
const collection = useCollection();
const { getChildrenCollections } = useCollectionManager();
const childrenCollections = getChildrenCollections(collection.name);
const hasChildCollection = childrenCollections?.length > 0;
return ( return (
<SchemaInitializer.Button <SchemaInitializer.Button
wrap={gridRowColWrap} wrap={gridRowColWrap}
@ -136,26 +179,36 @@ export const RecordBlockInitializers = (props: any) => {
{ {
type: 'itemGroup', type: 'itemGroup',
title: '{{t("Current record blocks")}}', title: '{{t("Current record blocks")}}',
children: [ children: hasChildCollection
{ ? [
key: 'details', {
type: 'item', key: 'details',
title: '{{t("Details")}}', type: 'subMenu',
component: 'RecordReadPrettyFormBlockInitializer', title: '{{t("Details")}}',
actionInitializers, children: useDetailCollections({ ...props, childrenCollections, collection }),
}, },
{ {
key: 'form', key: 'form',
type: 'item', type: 'subMenu',
title: '{{t("Form")}}', title: '{{t("Form")}}',
component: 'RecordFormBlockInitializer', children: useFormCollections({ ...props, childrenCollections, collection }),
}, },
], ]
}, : [
{ {
type: 'itemGroup', key: 'details',
title: '{{t("Children collection blocks")}}', type: 'item',
children: useInheritFields(props), title: '{{t("Details")}}',
component: 'RecordReadPrettyFormBlockInitializer',
actionInitializers,
},
{
key: 'form',
type: 'item',
title: '{{t("Form")}}',
component: 'RecordFormBlockInitializer',
},
],
}, },
{ {
type: 'itemGroup', type: 'itemGroup',

View File

@ -0,0 +1,166 @@
import React, { useState } from 'react';
import { DownOutlined, PlusOutlined } from '@ant-design/icons';
import { RecursionField, useFieldSchema, useField } from '@formily/react';
import { Dropdown, Menu, Button } from 'antd';
import { css } from '@emotion/css';
import { observer } from '@formily/react';
import { useCollectionManager, useCollection, CollectionProvider } from '../../collection-manager';
import { ActionContext, useCompile, useActionContext } from '../../schema-component';
import { useRecordPkValue, useACLRolesCheck } from '../../acl/ACLProvider';
export const actionDesignerCss = css`
position: relative;
&:hover {
.general-schema-designer {
display: block;
}
}
.general-schema-designer {
position: absolute;
z-index: 999;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: none;
background: rgba(241, 139, 98, 0.06);
border: 0;
top: 0;
bottom: 0;
left: 0;
right: 0;
pointer-events: none;
> .general-schema-designer-icons {
position: absolute;
right: 2px;
top: 2px;
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: #f18b62;
color: #fff;
line-height: 16px;
width: 16px;
padding-left: 1px;
}
}
}
`;
const actionAclCheck = (actionPath) => {
const { data, inResources, getResourceActionParams, getStrategyActionParams } = useACLRolesCheck();
const recordPkValue = useRecordPkValue();
const collection = useCollection();
const resource = collection.resource;
const parseAction = (actionPath: string, options: any = {}) => {
const [resourceName] = actionPath.split(':');
if (data?.allowAll) {
return {};
}
if (inResources(resourceName)) {
return getResourceActionParams(actionPath);
}
return getStrategyActionParams(actionPath);
};
if (!actionPath && resource) {
actionPath = `${resource}:create}`;
}
if (!actionPath?.includes(':')) {
actionPath = `${resource}:${actionPath}`;
}
if (!actionPath) {
return true;
}
const params = parseAction(actionPath, { recordPkValue });
if (!params) {
return false;
}
return true;
};
export const CreateRecordAction = observer((props) => {
const [visible, setVisible] = useState(false);
const collection = useCollection();
const fieldSchema = useFieldSchema();
const enableChildren = fieldSchema['x-enable-children'] || [];
const field = useField();
const { getChildrenCollections } = useCollectionManager();
const totalChildCollections = getChildrenCollections(collection.name);
const inheritsCollections = enableChildren
.map((k) => {
const childCollection = totalChildCollections.find((j) => j.name === k.collection);
return {
...childCollection,
title: k.title||childCollection.title,
};
})
.filter((v) => {
return actionAclCheck(`${v.name}:create`);
});
const [currentCollection, setCurrentCollection] = useState(collection.name);
const ctx = useActionContext();
const compile = useCompile();
const menu = (
<Menu>
{inheritsCollections.map((option) => {
return (
<Menu.Item
key={option.name}
onClick={(info) => {
setVisible(true);
setCurrentCollection(option.name);
}}
>
{compile(option.title)}
</Menu.Item>
);
})}
</Menu>
);
return (
<div className={actionDesignerCss}>
<ActionContext.Provider value={{ ...ctx, visible, setVisible }}>
{inheritsCollections?.length > 0 ? (
<Dropdown.Button
type="primary"
icon={<DownOutlined />}
buttonsRender={([leftButton, rightButton]) => [
leftButton,
React.cloneElement(rightButton as React.ReactElement<any, string>, { loading: false }),
]}
overlay={menu}
onClick={(info) => {
setVisible(true);
setCurrentCollection(collection.name);
}}
>
<PlusOutlined />
{props.children}
</Dropdown.Button>
) : (
<Button
type={'primary'}
icon={<PlusOutlined />}
onClick={(info) => {
setVisible(true);
setCurrentCollection(collection.name);
}}
>
{props.children}
</Button>
)}
<CollectionProvider name={currentCollection}>
<RecursionField schema={fieldSchema} basePath={field.address} onlyRenderProperties />
</CollectionProvider>
</ActionContext.Provider>
</div>
);
});
// export const CreateRecordAction = observer((props: any) => {
// return (
// <Action {...props} component={CreateAction}>
// {props.children}
// </Action>
// );
// });

View File

@ -1,2 +1,3 @@
export * from './assigned-field'; export * from './assigned-field';
export * from './BulkEditField'; export * from './BulkEditField';
export * from './CreateRecordAction'

View File

@ -4,14 +4,14 @@ import { ActionInitializer } from './ActionInitializer';
export const CreateActionInitializer = (props) => { export const CreateActionInitializer = (props) => {
const schema = { const schema = {
type: 'void', type: 'void',
title: '{{ t("Add new") }}',
'x-action': 'create', 'x-action': 'create',
title: "{{t('Add new')}}",
'x-designer': 'Action.Designer', 'x-designer': 'Action.Designer',
'x-component': 'Action', 'x-component': 'Action',
'x-decorator': 'ACLActionProvider',
'x-component-props': { 'x-component-props': {
icon: 'PlusOutlined',
openMode: 'drawer', openMode: 'drawer',
type: 'primary', component: 'CreateRecordAction',
}, },
properties: { properties: {
drawer: { drawer: {

View File

@ -1,55 +1,54 @@
import React from "react"; import React from 'react';
import { FormOutlined } from '@ant-design/icons'; import { FormOutlined } from '@ant-design/icons';
import { useBlockAssociationContext } from "../../block-provider"; import { useBlockAssociationContext } from '../../block-provider';
import { useCollection } from "../../collection-manager"; import { useCollection } from '../../collection-manager';
import { useSchemaTemplateManager } from "../../schema-templates"; import { useSchemaTemplateManager } from '../../schema-templates';
import { SchemaInitializer } from "../SchemaInitializer"; import { SchemaInitializer } from '../SchemaInitializer';
import { createFormBlockSchema, useRecordCollectionDataSourceItems } from "../utils"; import { createFormBlockSchema, useRecordCollectionDataSourceItems } from '../utils';
export const RecordFormBlockInitializer = (props) => { export const RecordFormBlockInitializer = (props) => {
const { onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props; const { onCreateBlockSchema, componentType, createBlockSchema, insert, targetCollection, ...others } = props;
const { getTemplateSchemaByMode } = useSchemaTemplateManager(); const { getTemplateSchemaByMode } = useSchemaTemplateManager();
const collection = useCollection(); const collection = targetCollection || useCollection();
const association = useBlockAssociationContext(); const association = useBlockAssociationContext();
console.log('RecordFormBlockInitializer', collection, association); return (
return ( <SchemaInitializer.Item
<SchemaInitializer.Item icon={<FormOutlined />}
icon={<FormOutlined />} {...others}
{...others} onClick={async ({ item }) => {
onClick={async ({ item }) => { 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 = createFormBlockSchema({ association,
association, collection: collection.name,
collection: collection.name, action: 'get',
action: 'get', useSourceId: '{{ useSourceIdFromParentRecord }}',
useSourceId: '{{ useSourceIdFromParentRecord }}', useParams: '{{ useParamsFromRecord }}',
useParams: '{{ useParamsFromRecord }}', actionInitializers: 'UpdateFormActionInitializers',
actionInitializers: 'UpdateFormActionInitializers', template: s,
template: s, });
}); if (item.mode === 'reference') {
if (item.mode === 'reference') { blockSchema['x-template-key'] = item.template.key;
blockSchema['x-template-key'] = item.template.key;
}
insert(blockSchema);
} else {
insert(s);
} }
insert(blockSchema);
} else { } else {
insert( insert(s);
createFormBlockSchema({
association,
collection: collection.name,
action: 'get',
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
actionInitializers: 'UpdateFormActionInitializers',
}),
);
} }
}} } else {
items={useRecordCollectionDataSourceItems('FormItem')} insert(
/> createFormBlockSchema({
); association,
}; collection: collection.name,
action: 'get',
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
actionInitializers: 'UpdateFormActionInitializers',
}),
);
}
}}
items={useRecordCollectionDataSourceItems('FormItem')}
/>
);
};

View File

@ -0,0 +1,106 @@
import { observer, useForm } from '@formily/react';
import React from 'react';
import { action } from '@formily/reactive';
import { SchemaComponent, useCompile } from '../../schema-component';
import { useCollectionManager } from '../../collection-manager';
export const EnableChildCollections = observer((props: any) => {
const { useProps } = props;
const { defaultValues, collectionName } = useProps();
const form = useForm();
const compile = useCompile();
const { getChildrenCollections } = useCollectionManager();
const childrenCollections = getChildrenCollections(collectionName);
const useAsyncDataSource = (service: any) => {
return (field: any, options?: any) => {
field.loading = true;
service(field, options).then(
action.bound((data: any) => {
field.dataSource = data;
field.loading = false;
if (field.initialValue) {
field.disabled = true;
}
}),
);
};
};
const loadData = async (field) => {
const { childrenCollections: childCollections } = form.values?.enableChildren;
return childrenCollections
.filter((v) => {
return !childCollections.find((k) => k.collection === v.name) || field.initialValue || v.name === field.value;
})
?.map((collection: any) => ({
label: compile(collection.title),
value: collection.name,
}));
};
return (
<SchemaComponent
schema={{
type: 'object',
properties: {
childrenCollections: {
type: 'array',
default: defaultValues,
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
items: {
type: 'object',
properties: {
space: {
type: 'void',
'x-component': 'Space',
properties: {
sort: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.SortHandle',
},
collection: {
type: 'string',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Select',
'x-component-props': {
style: {
width: 260,
},
},
'x-reactions': ['{{useAsyncDataSource(loadData)}}'],
},
title: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
style: {
width: 235,
},
},
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
},
},
properties: {
add: {
type: 'void',
title: '{{ t("Add collection") }}',
'x-component': 'ArrayItems.Addition',
},
},
},
},
}}
scope={{ useAsyncDataSource, loadData }}
/>
);
});

View File

@ -1,5 +1,5 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { FormDialog, FormItem, FormLayout, Input, ArrayCollapse } from '@formily/antd'; import { FormDialog, FormItem, FormLayout, Input, ArrayCollapse, ArrayItems } from '@formily/antd';
import { createForm, Field, GeneralField } from '@formily/core'; import { createForm, Field, GeneralField } from '@formily/core';
import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@formily/react'; import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@formily/react';
import _ from 'lodash'; import _ from 'lodash';
@ -43,6 +43,7 @@ import { useSchemaTemplateManager } from '../schema-templates';
import { useBlockTemplateContext } from '../schema-templates/BlockTemplate'; import { useBlockTemplateContext } from '../schema-templates/BlockTemplate';
import { FormLinkageRules } from './LinkageRules'; import { FormLinkageRules } from './LinkageRules';
import { useLinkageCollectionFieldOptions } from './LinkageRules/action-hooks'; import { useLinkageCollectionFieldOptions } from './LinkageRules/action-hooks';
import { EnableChildCollections } from './EnableChildCollections';
interface SchemaSettingsProps { interface SchemaSettingsProps {
title?: any; title?: any;
@ -770,3 +771,52 @@ SchemaSettings.LinkageRules = (props) => {
/> />
); );
}; };
SchemaSettings.EnableChildCollections = (props) => {
const { collectionName } = props;
const fieldSchema = useFieldSchema();
const { dn } = useDesignable();
const { t } = useTranslation();
return (
<SchemaSettings.ModalItem
title={t('Enable child collections')}
components={{ ArrayItems, FormLayout }}
width={600}
schema={
{
type: 'object',
title: t('Enable child collections'),
properties: {
enableChildren: {
'x-component': EnableChildCollections,
'x-component-props': {
useProps: () => {
return {
defaultValues: fieldSchema?.['x-enable-children'],
collectionName,
};
},
},
},
},
} as ISchema
}
onSubmit={(v) => {
const enableChildren = [];
for (const item of v.enableChildren.childrenCollections) {
enableChildren.push(_.pickBy(item, _.identity));
}
const uid = fieldSchema['x-uid'];
const schema = {
['x-uid']: uid,
};
fieldSchema['x-enable-children'] = enableChildren;
schema['x-enable-children'] = enableChildren;
dn.emit('patch', {
schema,
});
dn.refresh();
}}
/>
);
};