feat: improve form schema templates

This commit is contained in:
chenos 2022-04-13 17:29:25 +08:00
parent e4fffc2245
commit 5308210991
9 changed files with 269 additions and 26 deletions

View File

@ -287,5 +287,6 @@ export default {
"Template name": "模板名称", "Template name": "模板名称",
"Block type": "区块类型", "Block type": "区块类型",
"Action column": "操作列", "Action column": "操作列",
"Records per page": "每页显示数量" "Records per page": "每页显示数量",
"(Fields only)": "(仅字段)"
} }

View File

@ -75,7 +75,8 @@ export const FormDesigner = () => {
}); });
}} }}
/> />
<SchemaSettings.Template componentName={ctx?.action ? 'RecordForm' : 'CreateForm'} collectionName={name} /> {/* <SchemaSettings.Template componentName={'FormItem'} collectionName={name} /> */}
<SchemaSettings.FormItemTemplate componentName={'FormItem'} collectionName={name} />
<SchemaSettings.Divider /> <SchemaSettings.Divider />
<SchemaSettings.Remove <SchemaSettings.Remove
removeParentsIfNoChildren removeParentsIfNoChildren
@ -92,7 +93,12 @@ export const ReadPrettyFormDesigner = () => {
const template = useSchemaTemplate(); const template = useSchemaTemplate();
return ( return (
<GeneralSchemaDesigner template={template} title={title || name}> <GeneralSchemaDesigner template={template} title={title || name}>
<SchemaSettings.Template componentName={'ReadPrettyForm'} collectionName={name} /> {/* <SchemaSettings.Template componentName={'ReadPrettyForm'} collectionName={name} /> */}
<SchemaSettings.FormItemTemplate
insertAdjacentPosition={'beforeEnd'}
componentName={'ReadPrettyFormItem'}
collectionName={name}
/>
<SchemaSettings.Divider /> <SchemaSettings.Divider />
<SchemaSettings.Remove <SchemaSettings.Remove
removeParentsIfNoChildren removeParentsIfNoChildren

View File

@ -16,12 +16,22 @@ export interface RemoteSchemaComponentProps {
render?: any; render?: any;
hidden?: any; hidden?: any;
onlyRenderProperties?: boolean; onlyRenderProperties?: boolean;
noForm?: boolean;
} }
const defaultTransform = (s: Schema) => s; const defaultTransform = (s: Schema) => s;
const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) => { const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) => {
const { onlyRenderProperties, hidden, scope, uid, components, onSuccess, schemaTransform = defaultTransform } = props; const {
noForm,
onlyRenderProperties,
hidden,
scope,
uid,
components,
onSuccess,
schemaTransform = defaultTransform,
} = props;
const { reset } = useSchemaComponentContext(); const { reset } = useSchemaComponentContext();
const conf = { const conf = {
url: `/uiSchemas:${onlyRenderProperties ? 'getProperties' : 'getJsonSchema'}/${uid}`, url: `/uiSchemas:${onlyRenderProperties ? 'getProperties' : 'getJsonSchema'}/${uid}`,
@ -40,7 +50,9 @@ const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) =>
if (hidden) { if (hidden) {
return <Spin />; return <Spin />;
} }
return ( return noForm ? (
<SchemaComponent memoized components={components} scope={scope} schema={schemaTransform(data?.data || {})} />
) : (
<FormProvider form={form}> <FormProvider form={form}>
<SchemaComponent memoized components={components} scope={scope} schema={schemaTransform(data?.data || {})} /> <SchemaComponent memoized components={components} scope={scope} schema={schemaTransform(data?.data || {})} />
</FormProvider> </FormProvider>

View File

@ -60,7 +60,7 @@ export const ActionInitializer = (props) => {
}; };
export const DataBlockInitializer = (props) => { export const DataBlockInitializer = (props) => {
const { onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props; const { templateWrap, onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props;
const { getTemplateSchemaByMode } = useSchemaTemplateManager(); const { getTemplateSchemaByMode } = useSchemaTemplateManager();
return ( return (
<SchemaInitializer.Item <SchemaInitializer.Item
@ -69,7 +69,7 @@ export const DataBlockInitializer = (props) => {
onClick={async ({ item }) => { onClick={async ({ item }) => {
if (item.template) { if (item.template) {
const s = await getTemplateSchemaByMode(item); const s = await getTemplateSchemaByMode(item);
insert(s); templateWrap ? insert(templateWrap(s, { item })) : insert(s);
} else { } else {
if (onCreateBlockSchema) { if (onCreateBlockSchema) {
onCreateBlockSchema({ item }); onCreateBlockSchema({ item });
@ -105,7 +105,17 @@ export const FormBlockInitializer = (props) => {
<DataBlockInitializer <DataBlockInitializer
{...props} {...props}
icon={<FormOutlined />} icon={<FormOutlined />}
componentType={'CreateForm'} componentType={'FormItem'}
templateWrap={(templateSchema, { item }) => {
const s = createFormBlockSchema({
template: templateSchema,
collection: item.name,
});
if (item.template && item.mode === 'reference') {
s['x-template-key'] = item.template.key;
}
return s;
}}
createBlockSchema={createFormBlockSchema} createBlockSchema={createFormBlockSchema}
/> />
); );
@ -530,7 +540,20 @@ export const CreateFormBlockInitializer = (props) => {
onClick={async ({ item }) => { onClick={async ({ item }) => {
if (item.template) { if (item.template) {
const s = await getTemplateSchemaByMode(item); const s = await getTemplateSchemaByMode(item);
insert(s); if (item.template.componentName === 'FormItem') {
const blockSchema = createFormBlockSchema({
actionInitializers: 'CreateFormActionInitializers',
association,
collection: collection.name,
template: s,
});
if (item.mode === 'reference') {
blockSchema['x-template-key'] = item.template.key;
}
insert(blockSchema);
} else {
insert(s);
}
} else { } else {
insert( insert(
createFormBlockSchema({ createFormBlockSchema({
@ -541,7 +564,7 @@ export const CreateFormBlockInitializer = (props) => {
); );
} }
}} }}
items={useRecordCollectionDataSourceItems('CreateForm')} items={useRecordCollectionDataSourceItems('FormItem')}
/> />
); );
}; };
@ -558,7 +581,23 @@ export const RecordFormBlockInitializer = (props) => {
onClick={async ({ item }) => { onClick={async ({ item }) => {
if (item.template) { if (item.template) {
const s = await getTemplateSchemaByMode(item); const s = await getTemplateSchemaByMode(item);
insert(s); if (item.template.componentName === 'FormItem') {
const blockSchema = createFormBlockSchema({
association,
collection: collection.name,
action: 'get',
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
actionInitializers: 'UpdateFormActionInitializers',
template: s,
});
if (item.mode === 'reference') {
blockSchema['x-template-key'] = item.template.key;
}
insert(blockSchema);
} else {
insert(s);
}
} else { } else {
insert( insert(
createFormBlockSchema({ createFormBlockSchema({
@ -572,7 +611,7 @@ export const RecordFormBlockInitializer = (props) => {
); );
} }
}} }}
items={useRecordCollectionDataSourceItems('RecordForm')} items={useRecordCollectionDataSourceItems('FormItem')}
/> />
); );
}; };
@ -589,7 +628,22 @@ export const RecordReadPrettyFormBlockInitializer = (props) => {
onClick={async ({ item }) => { onClick={async ({ item }) => {
if (item.template) { if (item.template) {
const s = await getTemplateSchemaByMode(item); const s = await getTemplateSchemaByMode(item);
insert(s); if (item.template.componentName === 'ReadPrettyFormItem') {
const blockSchema = createReadPrettyFormBlockSchema({
association,
collection: collection.name,
action: 'get',
useSourceId: '{{ useSourceIdFromParentRecord }}',
useParams: '{{ useParamsFromRecord }}',
template: s,
});
if (item.mode === 'reference') {
blockSchema['x-template-key'] = item.template.key;
}
insert(blockSchema);
} else {
insert(s);
}
} else { } else {
insert( insert(
createReadPrettyFormBlockSchema({ createReadPrettyFormBlockSchema({
@ -602,7 +656,7 @@ export const RecordReadPrettyFormBlockInitializer = (props) => {
); );
} }
}} }}
items={useRecordCollectionDataSourceItems('ReadPrettyForm')} items={useRecordCollectionDataSourceItems('ReadPrettyFormItem')}
/> />
); );
}; };

View File

@ -149,6 +149,7 @@ export const useCurrentSchema = (action: string, key: string, find = findSchema,
}; };
export const useRecordCollectionDataSourceItems = (componentName) => { export const useRecordCollectionDataSourceItems = (componentName) => {
const { t } = useTranslation();
const collection = useCollection(); const collection = useCollection();
const { getTemplatesByCollection } = useSchemaTemplateManager(); const { getTemplatesByCollection } = useSchemaTemplateManager();
const templates = getTemplatesByCollection(collection.name).filter((template) => { const templates = getTemplatesByCollection(collection.name).filter((template) => {
@ -173,12 +174,14 @@ export const useRecordCollectionDataSourceItems = (componentName) => {
name: 'copy', name: 'copy',
title: '复制模板', title: '复制模板',
children: templates.map((template) => { children: templates.map((template) => {
const templateName =
template?.componentName === 'ReadPrettyFormItem' ? `${template?.name} ${t('(Fields only)')}` : template?.name;
return { return {
type: 'item', type: 'item',
mode: 'copy', mode: 'copy',
name: collection.name, name: collection.name,
template, template,
title: template.name || '未命名', title: templateName || '未命名',
}; };
}), }),
}, },
@ -188,12 +191,14 @@ export const useRecordCollectionDataSourceItems = (componentName) => {
name: 'ref', name: 'ref',
title: '引用模板', title: '引用模板',
children: templates.map((template) => { children: templates.map((template) => {
const templateName =
template?.componentName === 'ReadPrettyFormItem' ? `${template?.name} ${t('(Fields only)')}` : template?.name;
return { return {
type: 'item', type: 'item',
mode: 'reference', mode: 'reference',
name: collection.name, name: collection.name,
template, template,
title: template.name || '未命名', title: templateName || '未命名',
}; };
}), }),
}, },
@ -242,12 +247,14 @@ export const useCollectionDataSourceItems = (componentName) => {
name: 'copy', name: 'copy',
title: '复制模板', title: '复制模板',
children: templates.map((template) => { children: templates.map((template) => {
const templateName =
template?.componentName === 'FormItem' ? `${template?.name} ${t('(Fields only)')}` : template?.name;
return { return {
type: 'item', type: 'item',
mode: 'copy', mode: 'copy',
name: item.name, name: item.name,
template, template,
title: template.name || '未命名', title: templateName || '未命名',
}; };
}), }),
}, },
@ -257,12 +264,14 @@ export const useCollectionDataSourceItems = (componentName) => {
name: 'ref', name: 'ref',
title: '引用模板', title: '引用模板',
children: templates.map((template) => { children: templates.map((template) => {
const templateName =
template?.componentName === 'FormItem' ? `${template?.name} ${t('(Fields only)')}` : template?.name;
return { return {
type: 'item', type: 'item',
mode: 'reference', mode: 'reference',
name: item.name, name: item.name,
template, template,
title: template.name || '未命名', title: templateName || '未命名',
}; };
}), }),
}, },
@ -281,6 +290,7 @@ export const createFormBlockSchema = (options) => {
resource, resource,
association, association,
action, action,
template,
...others ...others
} = options; } = options;
const resourceName = resource || association || collection; const resourceName = resource || association || collection;
@ -310,7 +320,7 @@ export const createFormBlockSchema = (options) => {
useProps: '{{ useFormBlockProps }}', useProps: '{{ useFormBlockProps }}',
}, },
properties: { properties: {
grid: { grid: template || {
type: 'void', type: 'void',
'x-component': 'Grid', 'x-component': 'Grid',
'x-initializer': formItemInitializers, 'x-initializer': formItemInitializers,
@ -343,6 +353,7 @@ export const createReadPrettyFormBlockSchema = (options) => {
collection, collection,
association, association,
resource, resource,
template,
...others ...others
} = options; } = options;
const resourceName = resource || association || collection; const resourceName = resource || association || collection;
@ -381,7 +392,7 @@ export const createReadPrettyFormBlockSchema = (options) => {
}, },
properties: {}, properties: {},
}, },
grid: { grid: template || {
type: 'void', type: 'void',
'x-component': 'Grid', 'x-component': 'Grid',
'x-initializer': formItemInitializers, 'x-initializer': formItemInitializers,

View File

@ -4,6 +4,7 @@ import { useField, useFieldSchema } from '@formily/react';
import { Space } from 'antd'; import { Space } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next';
import { DragHandler, useCompile, useDesignable } from '../schema-component'; import { DragHandler, useCompile, useDesignable } from '../schema-component';
import { SchemaSettings } from './SchemaSettings'; import { SchemaSettings } from './SchemaSettings';
@ -33,6 +34,7 @@ export const GeneralSchemaDesigner = (props: any) => {
const { title, template, draggable = true } = props; const { title, template, draggable = true } = props;
const { dn, designable } = useDesignable(); const { dn, designable } = useDesignable();
const field = useField(); const field = useField();
const { t } = useTranslation();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const compile = useCompile(); const compile = useCompile();
const schemaSettingsProps = { const schemaSettingsProps = {
@ -43,13 +45,14 @@ export const GeneralSchemaDesigner = (props: any) => {
if (!designable) { if (!designable) {
return null; return null;
} }
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName) ? `${template?.name} ${t('(Fields only)')}` : template?.name;
return ( return (
<div className={'general-schema-designer'}> <div className={'general-schema-designer'}>
{title && ( {title && (
<div className={classNames('general-schema-designer-title', titleCss)}> <div className={classNames('general-schema-designer-title', titleCss)}>
<Space size={2}> <Space size={2}>
<span className={'title-tag'}>{compile(title)}</span> <span className={'title-tag'}>{compile(title)}</span>
{template && <span className={'title-tag'}>: {template?.name || '未命名'}</span>} {template && <span className={'title-tag'}>: {templateName || '未命名'}</span>}
</Space> </Space>
</div> </div>
)} )}

View File

@ -67,9 +67,9 @@ interface SchemaSettingsProviderProps {
export const SchemaSettingsProvider: React.FC<SchemaSettingsProviderProps> = (props) => { export const SchemaSettingsProvider: React.FC<SchemaSettingsProviderProps> = (props) => {
const { children, fieldSchema, ...others } = props; const { children, fieldSchema, ...others } = props;
const { getTemplateBySchemaId } = useSchemaTemplateManager(); const { getTemplateBySchema } = useSchemaTemplateManager();
const { name } = useCollection(); const { name } = useCollection();
const template = getTemplateBySchemaId(fieldSchema['x-uid']); const template = getTemplateBySchema(fieldSchema);
return ( return (
<SchemaSettingsContext.Provider value={{ collectionName: name, template, fieldSchema, ...others }}> <SchemaSettingsContext.Provider value={{ collectionName: name, template, fieldSchema, ...others }}>
{children} {children}
@ -181,6 +181,147 @@ SchemaSettings.Template = (props) => {
); );
}; };
const findGridSchema = (fieldSchema) => {
return fieldSchema.reduceProperties((buf, s) => {
if (s['x-component'] === 'FormV2') {
const f = s.reduceProperties((buf, s) => {
if (s['x-component'] === 'Grid') {
return s;
}
return buf;
}, null);
if (f) {
return f;
}
}
return buf;
}, null);
};
const findBlockTemplateSchema = (fieldSchema) => {
return fieldSchema.reduceProperties((buf, s) => {
if (s['x-component'] === 'FormV2') {
const f = s.reduceProperties((buf, s) => {
if (s['x-component'] === 'BlockTemplate') {
return s;
}
return buf;
}, null);
if (f) {
return f;
}
}
return buf;
}, null);
};
SchemaSettings.FormItemTemplate = (props) => {
const { insertAdjacentPosition = 'afterBegin', componentName, collectionName } = props;
const { t } = useTranslation();
const { dn, setVisible, template, fieldSchema } = useSchemaSettings();
const api = useAPIClient();
const { saveAsTemplate, copyTemplateSchema } = useSchemaTemplateManager();
if (!collectionName) {
return null;
}
if (template) {
return (
<SchemaSettings.Item
onClick={async () => {
const schema = await copyTemplateSchema(template);
const templateSchema = findBlockTemplateSchema(fieldSchema);
const sdn = createDesignable({
api,
refresh: dn.refresh.bind(dn),
current: templateSchema.parent,
});
sdn.loadAPIClientEvents();
sdn.removeWithoutEmit(templateSchema);
sdn.insertAdjacent(insertAdjacentPosition, schema, {
async onSuccess() {
await api.request({
url: `/uiSchemas:remove/${templateSchema['x-uid']}`,
});
},
});
fieldSchema['x-template-key'] = null;
await api.request({
url: `uiSchemas:patch`,
method: 'post',
data: {
'x-uid': fieldSchema['x-uid'],
'x-template-key': null,
},
});
dn.refresh();
}}
>
{t('Convert reference to duplicate')}
</SchemaSettings.Item>
);
}
return (
<SchemaSettings.Item
onClick={async () => {
setVisible(false);
const gridSchema = findGridSchema(fieldSchema);
console.log('gridSchema', gridSchema);
const values = await FormDialog('Save as template', () => {
return (
<FormLayout layout={'vertical'}>
<SchemaComponent
components={{ Input, FormItem }}
schema={{
type: 'object',
properties: {
name: {
title: '模板名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
}}
/>
</FormLayout>
);
}).open({});
const sdn = createDesignable({
api,
refresh: dn.refresh.bind(dn),
current: gridSchema.parent,
});
sdn.loadAPIClientEvents();
const { key } = await saveAsTemplate({
collectionName,
componentName,
name: values.name,
uid: gridSchema['x-uid'],
});
sdn.removeWithoutEmit(gridSchema);
sdn.insertAdjacent(insertAdjacentPosition, {
type: 'void',
'x-component': 'BlockTemplate',
'x-component-props': {
templateId: key,
},
});
fieldSchema['x-template-key'] = key;
await api.request({
url: `uiSchemas:patch`,
method: 'post',
data: {
'x-uid': fieldSchema['x-uid'],
'x-template-key': key,
},
});
}}
>
{t('Save as template')}
</SchemaSettings.Item>
);
};
SchemaSettings.Item = (props) => { SchemaSettings.Item = (props) => {
let { eventKey } = props; let { eventKey } = props;
return ( return (

View File

@ -19,7 +19,7 @@ export const BlockTemplate = observer((props: any) => {
return ( return (
<div> <div>
<BlockTemplateContext.Provider value={{ dn, field, fieldSchema, template }}> <BlockTemplateContext.Provider value={{ dn, field, fieldSchema, template }}>
<RemoteSchemaComponent uid={template?.uid} /> <RemoteSchemaComponent noForm uid={template?.uid} />
</BlockTemplateContext.Provider> </BlockTemplateContext.Provider>
</div> </div>
); );

View File

@ -46,10 +46,12 @@ const regenerateUid = (s: ISchema) => {
}; };
export const useSchemaTemplate = () => { export const useSchemaTemplate = () => {
const { getTemplateBySchemaId } = useSchemaTemplateManager(); const { getTemplateBySchema, templates } = useSchemaTemplateManager();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const schemaId = fieldSchema['x-uid']; const schemaId = fieldSchema['x-uid'];
return useMemo(() => getTemplateBySchemaId(schemaId), [schemaId]); const templateKey = fieldSchema['x-template-key'];
console.log('templateKey', { schemaId, templateKey })
return useMemo(() => getTemplateBySchema(fieldSchema), [schemaId, templateKey]);
}; };
export const useSchemaTemplateManager = () => { export const useSchemaTemplateManager = () => {
@ -98,7 +100,20 @@ export const useSchemaTemplateManager = () => {
await refresh(); await refresh();
return { key }; return { key };
}, },
getTemplateBySchema(schema) {
const templateKey = schema['x-template-key'];
if (templateKey) {
return templates?.find((template) => template.key === templateKey);
}
const schemaId = schema['x-uid'];
if (schemaId) {
return templates?.find((template) => template.uid === schemaId);
}
},
getTemplateBySchemaId(schemaId) { getTemplateBySchemaId(schemaId) {
if (!schemaId) {
return null;
}
return templates?.find((template) => template.uid === schemaId); return templates?.find((template) => template.uid === schemaId);
}, },
getTemplateById(key) { getTemplateById(key) {