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": "模板名称",
"Block type": "区块类型",
"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.Remove
removeParentsIfNoChildren
@ -92,7 +93,12 @@ export const ReadPrettyFormDesigner = () => {
const template = useSchemaTemplate();
return (
<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.Remove
removeParentsIfNoChildren

View File

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

View File

@ -60,7 +60,7 @@ export const ActionInitializer = (props) => {
};
export const DataBlockInitializer = (props) => {
const { onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props;
const { templateWrap, onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props;
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
return (
<SchemaInitializer.Item
@ -69,7 +69,7 @@ export const DataBlockInitializer = (props) => {
onClick={async ({ item }) => {
if (item.template) {
const s = await getTemplateSchemaByMode(item);
insert(s);
templateWrap ? insert(templateWrap(s, { item })) : insert(s);
} else {
if (onCreateBlockSchema) {
onCreateBlockSchema({ item });
@ -105,7 +105,17 @@ export const FormBlockInitializer = (props) => {
<DataBlockInitializer
{...props}
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}
/>
);
@ -530,7 +540,20 @@ export const CreateFormBlockInitializer = (props) => {
onClick={async ({ item }) => {
if (item.template) {
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 {
insert(
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 }) => {
if (item.template) {
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 {
insert(
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 }) => {
if (item.template) {
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 {
insert(
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) => {
const { t } = useTranslation();
const collection = useCollection();
const { getTemplatesByCollection } = useSchemaTemplateManager();
const templates = getTemplatesByCollection(collection.name).filter((template) => {
@ -173,12 +174,14 @@ export const useRecordCollectionDataSourceItems = (componentName) => {
name: 'copy',
title: '复制模板',
children: templates.map((template) => {
const templateName =
template?.componentName === 'ReadPrettyFormItem' ? `${template?.name} ${t('(Fields only)')}` : template?.name;
return {
type: 'item',
mode: 'copy',
name: collection.name,
template,
title: template.name || '未命名',
title: templateName || '未命名',
};
}),
},
@ -188,12 +191,14 @@ export const useRecordCollectionDataSourceItems = (componentName) => {
name: 'ref',
title: '引用模板',
children: templates.map((template) => {
const templateName =
template?.componentName === 'ReadPrettyFormItem' ? `${template?.name} ${t('(Fields only)')}` : template?.name;
return {
type: 'item',
mode: 'reference',
name: collection.name,
template,
title: template.name || '未命名',
title: templateName || '未命名',
};
}),
},
@ -242,12 +247,14 @@ export const useCollectionDataSourceItems = (componentName) => {
name: 'copy',
title: '复制模板',
children: templates.map((template) => {
const templateName =
template?.componentName === 'FormItem' ? `${template?.name} ${t('(Fields only)')}` : template?.name;
return {
type: 'item',
mode: 'copy',
name: item.name,
template,
title: template.name || '未命名',
title: templateName || '未命名',
};
}),
},
@ -257,12 +264,14 @@ export const useCollectionDataSourceItems = (componentName) => {
name: 'ref',
title: '引用模板',
children: templates.map((template) => {
const templateName =
template?.componentName === 'FormItem' ? `${template?.name} ${t('(Fields only)')}` : template?.name;
return {
type: 'item',
mode: 'reference',
name: item.name,
template,
title: template.name || '未命名',
title: templateName || '未命名',
};
}),
},
@ -281,6 +290,7 @@ export const createFormBlockSchema = (options) => {
resource,
association,
action,
template,
...others
} = options;
const resourceName = resource || association || collection;
@ -310,7 +320,7 @@ export const createFormBlockSchema = (options) => {
useProps: '{{ useFormBlockProps }}',
},
properties: {
grid: {
grid: template || {
type: 'void',
'x-component': 'Grid',
'x-initializer': formItemInitializers,
@ -343,6 +353,7 @@ export const createReadPrettyFormBlockSchema = (options) => {
collection,
association,
resource,
template,
...others
} = options;
const resourceName = resource || association || collection;
@ -381,7 +392,7 @@ export const createReadPrettyFormBlockSchema = (options) => {
},
properties: {},
},
grid: {
grid: template || {
type: 'void',
'x-component': 'Grid',
'x-initializer': formItemInitializers,

View File

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

View File

@ -67,9 +67,9 @@ interface SchemaSettingsProviderProps {
export const SchemaSettingsProvider: React.FC<SchemaSettingsProviderProps> = (props) => {
const { children, fieldSchema, ...others } = props;
const { getTemplateBySchemaId } = useSchemaTemplateManager();
const { getTemplateBySchema } = useSchemaTemplateManager();
const { name } = useCollection();
const template = getTemplateBySchemaId(fieldSchema['x-uid']);
const template = getTemplateBySchema(fieldSchema);
return (
<SchemaSettingsContext.Provider value={{ collectionName: name, template, fieldSchema, ...others }}>
{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) => {
let { eventKey } = props;
return (

View File

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

View File

@ -46,10 +46,12 @@ const regenerateUid = (s: ISchema) => {
};
export const useSchemaTemplate = () => {
const { getTemplateBySchemaId } = useSchemaTemplateManager();
const { getTemplateBySchema, templates } = useSchemaTemplateManager();
const fieldSchema = useFieldSchema();
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 = () => {
@ -98,7 +100,20 @@ export const useSchemaTemplateManager = () => {
await refresh();
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) {
if (!schemaId) {
return null;
}
return templates?.find((template) => template.uid === schemaId);
},
getTemplateById(key) {