feat(data-template): support to set data scope and title field (#1918)

* refactor: optimize

* feat(Data-template): support to set data scope and title field

* fix: fix error when no template data

* fix: fix error

* fix: operator

* chore: remove id

* fix: filter

* fix: should be global

* fix: submit

* fix: global sync

* fix: title field

* fix: fix filter

* fix: fix first created

* fix: collection

* fix: collection

* fix: collection

* chore: should show N/A

* Revert "chore: should show N/A"

This reverts commit 00c91d77612aea0801fec88b5f3f91e4be999439.

* chore: should show N/A

* chore: remove useless code
This commit is contained in:
被雨水过滤的空气-Rairn 2023-05-25 17:09:29 +08:00 committed by GitHub
parent c722ca41d9
commit e61568ac57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 354 additions and 90 deletions

View File

@ -74,7 +74,7 @@ const CurrentFields = (props) => {
const { resource, targetKey } = props.collectionResource || {}; const { resource, targetKey } = props.collectionResource || {};
const { [targetKey]: filterByTk, titleField } = useRecord(); const { [targetKey]: filterByTk, titleField } = useRecord();
const [loadingRecord, setLoadingRecord] = React.useState<any>(null); const [loadingRecord, setLoadingRecord] = React.useState<any>(null);
const { updateCollection, refreshCM } = useCollectionManager(); const { refreshCM } = useCollectionManager();
const columns: TableColumnProps<any>[] = [ const columns: TableColumnProps<any>[] = [
{ {
@ -180,7 +180,7 @@ const InheritFields = (props) => {
const { [targetKey]: filterByTk, titleField, name } = useRecord(); const { [targetKey]: filterByTk, titleField, name } = useRecord();
const [loadingRecord, setLoadingRecord] = React.useState(null); const [loadingRecord, setLoadingRecord] = React.useState(null);
const { t } = useTranslation(); const { t } = useTranslation();
const { updateCollection, refreshCM } = useCollectionManager(); const { refreshCM } = useCollectionManager();
const columns: TableColumnProps<any>[] = [ const columns: TableColumnProps<any>[] = [
{ {
@ -265,7 +265,7 @@ const InheritFields = (props) => {
); );
}; };
export const CollectionFields = (props) => { export const CollectionFields = () => {
const compile = useCompile(); const compile = useCompile();
const field = useField<Field>(); const field = useField<Field>();
const { name } = useRecord(); const { name } = useRecord();

View File

@ -160,7 +160,7 @@ AssociationSelect.Designer = function Designer() {
readOnlyMode = 'read-pretty'; readOnlyMode = 'read-pretty';
} }
const fieldSchemaWithoutRequired = _.omit(fieldSchema,'required') const fieldSchemaWithoutRequired = _.omit(fieldSchema, 'required');
return ( return (
<GeneralSchemaDesigner> <GeneralSchemaDesigner>

View File

@ -8,7 +8,15 @@ import { useAPIClient } from '../../../api-client';
import { findFormBlock } from '../../../block-provider'; import { findFormBlock } from '../../../block-provider';
import { useCollectionManager } from '../../../collection-manager'; import { useCollectionManager } from '../../../collection-manager';
interface ITemplate { export interface ITemplate {
config?: {
[key: string]: {
/** 设置的数据范围 */
filter?: any;
/** 设置的标题字段 */
titleField?: string;
};
};
items: { items: {
key: string; key: string;
title: string; title: string;
@ -31,7 +39,7 @@ const useDataTemplates = () => {
items.forEach((item) => { items.forEach((item) => {
try { try {
item.fields = item.fields item.fields = item.fields
.map((field) => { ?.map((field) => {
const joinField = getCollectionJoinField(`${item.collection}.${field}`); const joinField = getCollectionJoinField(`${item.collection}.${field}`);
if (joinField) { if (joinField) {
return field; return field;
@ -57,7 +65,7 @@ const useDataTemplates = () => {
templates, templates,
display, display,
defaultTemplate, defaultTemplate,
enabled: items.length > 0, enabled: items.length > 0 && items.every((item) => item.dataId !== undefined),
}; };
}; };
@ -68,7 +76,7 @@ export const Templates = ({ style = {}, form }) => {
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { useEffect(() => {
if (defaultTemplate) { if (enabled && defaultTemplate) {
fetchTemplateData(api, defaultTemplate, t) fetchTemplateData(api, defaultTemplate, t)
.then((data) => { .then((data) => {
if (form && data) { if (form && data) {

View File

@ -7,10 +7,11 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { ResourceActionOptions, useRequest } from '../../../api-client'; import { ResourceActionOptions, useRequest } from '../../../api-client';
import { mergeFilter } from '../../../block-provider/SharedFilterProvider'; import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
import { useCollection, useCollectionManager } from '../../../collection-manager'; import { useCollection, useCollectionManager } from '../../../collection-manager';
import { useCompile } from '../../hooks';
import { Select, defaultFieldNames } from '../select'; import { Select, defaultFieldNames } from '../select';
import { ReadPretty } from './ReadPretty'; import { ReadPretty } from './ReadPretty';
const EMPTY = 'N/A';
export type RemoteSelectProps<P = any> = SelectProps<P, any> & { export type RemoteSelectProps<P = any> = SelectProps<P, any> & {
objectValue?: boolean; objectValue?: boolean;
onChange?: (v: any) => void; onChange?: (v: any) => void;
@ -35,7 +36,6 @@ const InternalRemoteSelect = connect(
targetField: _targetField, targetField: _targetField,
...others ...others
} = props; } = props;
const compile = useCompile();
const firstRun = useRef(false); const firstRun = useRef(false);
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const field = useField(); const field = useField();
@ -58,7 +58,8 @@ const InternalRemoteSelect = connect(
const mapOptionsToTags = useCallback( const mapOptionsToTags = useCallback(
(options) => { (options) => {
try { try {
return options.map((option) => { return options
.map((option) => {
let label = option[fieldNames.label]; let label = option[fieldNames.label];
if (targetField?.uiSchema?.enum) { if (targetField?.uiSchema?.enum) {
@ -73,7 +74,7 @@ const InternalRemoteSelect = connect(
</Tag> </Tag>
); );
} else { } else {
return <Tag>{item}</Tag>; return <Tag key={item}>{item}</Tag>;
} }
}) })
.reverse(); .reverse();
@ -91,16 +92,17 @@ const InternalRemoteSelect = connect(
if (mapOptions) { if (mapOptions) {
return mapOptions({ return mapOptions({
[fieldNames.label]: label, [fieldNames.label]: label || EMPTY,
[fieldNames.value]: option[fieldNames.value], [fieldNames.value]: option[fieldNames.value],
}); });
} }
return { return {
...option, ...option,
[fieldNames.label]: label || option[fieldNames.value], [fieldNames.label]: label || EMPTY,
[fieldNames.value]: option[fieldNames.value], [fieldNames.value]: option[fieldNames.value],
}; };
}); })
.filter(Boolean);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
return options; return options;

View File

@ -35,7 +35,7 @@ export const SchemaComponentProvider: React.FC<ISchemaComponentProvider> = (prop
const { designable, components, children } = props; const { designable, components, children } = props;
const [, setUid] = useState(uid()); const [, setUid] = useState(uid());
const [formId, setFormId] = useState(uid()); const [formId, setFormId] = useState(uid());
const form = props.form || useMemo(() => createForm(), [formId]); const form = useMemo(() => props.form || createForm(), [formId]);
const { t } = useTranslation(); const { t } = useTranslation();
const scope = { ...props.scope, t, randomString }; const scope = { ...props.scope, t, randomString };
const [active, setActive] = useCookieState('useCookieDesignable', { const [active, setActive] = useCookieState('useCookieDesignable', {

View File

@ -1,9 +1,8 @@
import { Field } from '@formily/core'; import { Field } from '@formily/core';
import { ISchema, useField, useFieldSchema,observer } from '@formily/react'; import { ISchema, observer, useField, useFieldSchema } from '@formily/react';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
import React from 'react'; import React from 'react';
import { useCollectionManager, useCollection } from '../collection-manager'; import { useTranslation } from 'react-i18next';
import { useCollection, useCollectionManager } from '../collection-manager';
import { useDesignable } from '../schema-component'; import { useDesignable } from '../schema-component';
import { SchemaSettings } from '../schema-settings'; import { SchemaSettings } from '../schema-settings';
@ -18,7 +17,6 @@ export const GeneralSchemaItems: React.FC<{
const { t } = useTranslation(); const { t } = useTranslation();
const { dn, refresh } = useDesignable(); const { dn, refresh } = useDesignable();
const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']); const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
console.log(field,field.decoratorProps )
return ( return (
<> <>
{collectionField && ( {collectionField && (

View File

@ -1,11 +1,22 @@
import { Field } from '@formily/core';
import { connect, mapProps, observer } from '@formily/react'; import { connect, mapProps, observer } from '@formily/react';
import { observable } from '@formily/reactive';
import { Tree as AntdTree } from 'antd'; import { Tree as AntdTree } from 'antd';
import _ from 'lodash';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { mergeFilter } from '../../block-provider';
import { useCollectionManager } from '../../collection-manager'; import { useCollectionManager } from '../../collection-manager';
import { AssociationSelect, SchemaComponent } from '../../schema-component'; import {
AssociationSelect,
SchemaComponent,
SchemaComponentContext,
removeNullCondition,
} from '../../schema-component';
import { ITemplate } from '../../schema-component/antd/form-v2/Templates';
import { AsDefaultTemplate } from './components/AsDefaultTemplate'; import { AsDefaultTemplate } from './components/AsDefaultTemplate';
import { ArrayCollapse } from './components/DataTemplateTitle'; import { ArrayCollapse } from './components/DataTemplateTitle';
import { Designer, getSelectedIdFilter } from './components/Designer';
import { useCollectionState } from './hooks/useCollectionState'; import { useCollectionState } from './hooks/useCollectionState';
const Tree = connect( const Tree = connect(
@ -18,22 +29,60 @@ const Tree = connect(
); );
export const FormDataTemplates = observer((props: any) => { export const FormDataTemplates = observer((props: any) => {
const { useProps } = props; const { useProps, formSchema, designerCtx } = props;
const { defaultValues, collectionName } = useProps(); const { defaultValues, collectionName } = useProps();
const { collectionList, getEnableFieldTree, onLoadData, onCheck } = useCollectionState(collectionName); const { collectionList, getEnableFieldTree, onLoadData, onCheck } = useCollectionState(collectionName);
const { getCollection, getCollectionField } = useCollectionManager(); const { getCollection, getCollectionField } = useCollectionManager();
const collection = getCollection(collectionName);
const { t } = useTranslation(); const { t } = useTranslation();
const field = getCollectionField(`${collectionName}.${collection?.titleField || 'id'}`);
// 不要在后面的数组中依赖 defaultValues否则会因为 defaultValues 的变化导致 activeData 响应性丢失
const activeData = useMemo<ITemplate>(
() =>
observable(
defaultValues || { items: [], display: true, config: { [collectionName]: { titleField: '', filter: {} } } },
),
[],
);
const getTargetField = (collectionName: string) => {
const collection = getCollection(collectionName);
return getCollectionField(
`${collectionName}.${activeData?.config[collectionName]?.titleField || collection?.titleField || 'id'}`,
);
};
const getFieldNames = (collectionName: string) => {
const collection = getCollection(collectionName);
return {
label: getLabel(activeData.config?.[collectionName]?.titleField || collection?.titleField || 'id'),
value: 'id',
};
};
const getFilter = (collectionName: string, value: any) => {
const filter = activeData.config?.[collectionName]?.filter;
return _.isEmpty(filter) ? {} : removeNullCondition(mergeFilter([filter, getSelectedIdFilter(value)], '$or'));
};
const components = useMemo(() => ({ ArrayCollapse }), []); const components = useMemo(() => ({ ArrayCollapse }), []);
const scope = useMemo(() => ({ getEnableFieldTree }), []); const scope = useMemo(
() => ({
getEnableFieldTree,
getTargetField,
getFieldNames,
getFilter,
getResource,
collectionName,
}),
[],
);
const schema = useMemo( const schema = useMemo(
() => ({ () => ({
type: 'object', type: 'object',
properties: { properties: {
items: { items: {
type: 'array', type: 'array',
default: defaultValues?.items, default: activeData?.items,
'x-component': 'ArrayCollapse', 'x-component': 'ArrayCollapse',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component-props': { 'x-component-props': {
@ -58,7 +107,7 @@ export const FormDataTemplates = observer((props: any) => {
title: '{{ t("Collection") }}', title: '{{ t("Collection") }}',
required: true, required: true,
description: t('If collection inherits, choose inherited collections as templates'), description: t('If collection inherits, choose inherited collections as templates'),
default: collectionName, default: '{{ collectionName }}',
'x-display': collectionList.length > 1 ? 'visible' : 'hidden', 'x-display': collectionList.length > 1 ? 'visible' : 'hidden',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Select', 'x-component': 'Select',
@ -71,36 +120,27 @@ export const FormDataTemplates = observer((props: any) => {
title: '{{ t("Template Data") }}', title: '{{ t("Template Data") }}',
required: true, required: true,
description: t('Select an existing piece of data as the initialization data for the form'), description: t('Select an existing piece of data as the initialization data for the form'),
'x-designer': Designer,
'x-designer-props': {
formSchema,
data: activeData,
},
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': AssociationSelect, 'x-component': AssociationSelect,
'x-component-props': { 'x-component-props': {
service: { service: {
resource: collectionName, resource: '{{ $record.collection || collectionName }}',
params: {
filter: '{{ getFilter($self.componentProps.service.resource, $self.value) }}',
},
}, },
action: 'list', action: 'list',
multiple: false, multiple: false,
objectValue: false, objectValue: false,
manual: false, manual: false,
targetField: field, targetField: '{{ getTargetField($self.componentProps.service.resource) }}',
mapOptions(option) { mapOptions: getMapOptions(),
try { fieldNames: '{{ getFieldNames($self.componentProps.service.resource) }}',
const label = getLabel(collection);
option[label] = (
<>
#{option.id} {option[label]}
</>
);
return option;
} catch (error) {
console.error(error);
return option;
}
},
fieldNames: {
label: getLabel(collection),
value: 'id',
},
}, },
'x-reactions': [ 'x-reactions': [
{ {
@ -110,7 +150,7 @@ export const FormDataTemplates = observer((props: any) => {
disabled: '{{ !$deps[0] }}', disabled: '{{ !$deps[0] }}',
componentProps: { componentProps: {
service: { service: {
resource: '{{ $deps[0] }}', resource: '{{ getResource($deps[0], $self) }}',
}, },
}, },
}, },
@ -182,7 +222,7 @@ export const FormDataTemplates = observer((props: any) => {
display: { display: {
type: 'boolean', type: 'boolean',
'x-content': '{{ t("Display data template selector") }}', 'x-content': '{{ t("Display data template selector") }}',
default: defaultValues?.display !== false, default: activeData?.display !== false,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Checkbox', 'x-component': 'Checkbox',
}, },
@ -191,9 +231,30 @@ export const FormDataTemplates = observer((props: any) => {
[], [],
); );
return <SchemaComponent components={components} scope={scope} schema={schema} />; return (
<SchemaComponentContext.Provider value={{ ...designerCtx, designable: true }}>
<SchemaComponent components={components} scope={scope} schema={schema} />
</SchemaComponentContext.Provider>
);
}); });
function getLabel(collection: any) { export function getLabel(titleField) {
return !collection?.titleField || collection.titleField === 'id' ? 'label' : collection?.titleField; return titleField || 'label';
}
function getMapOptions() {
return (option) => {
if (option?.id === undefined) {
return null;
}
return option;
};
}
function getResource(resource: string, field: Field) {
if (resource !== field.componentProps.service.resource) {
// 切换 collection 后,之前选中的其它 collection 的数据就没有意义了,需要清空
field.value = undefined;
}
return resource;
} }

View File

@ -0,0 +1,182 @@
import { Field } from '@formily/core';
import { ISchema, observer, useField, useFieldSchema } from '@formily/react';
import { error } from '@nocobase/utils/client';
import { Select } from 'antd';
import _ from 'lodash';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { GeneralSchemaDesigner, SchemaSettings } from '../..';
import { mergeFilter } from '../../../block-provider';
import { useCollectionFilterOptions, useCollectionManager } from '../../../collection-manager';
import { isTitleField } from '../../../collection-manager/Configuration/CollectionFields';
import { removeNullCondition, useCompile, useDesignable } from '../../../schema-component';
import { ITemplate } from '../../../schema-component/antd/form-v2/Templates';
import { FilterDynamicComponent } from '../../../schema-component/antd/table-v2/FilterDynamicComponent';
export const Designer = observer(() => {
const { getCollectionFields, getCollectionField, getCollection } = useCollectionManager();
const field = useField<Field>();
const fieldSchema = useFieldSchema();
const { t } = useTranslation();
const { dn } = useDesignable();
const compile = useCompile();
const { formSchema, data } = fieldSchema['x-designer-props'] as {
formSchema: ISchema;
data?: ITemplate;
};
// 在这里读取 resource 的值,当 resource 变化时,会触发该组件的更新
const collectionName = field.componentProps.service.resource;
const collection = getCollection(collectionName);
const collectionFields = getCollectionFields(collectionName);
const dataSource = useCollectionFilterOptions(collectionName);
if (!data) {
error('data is required');
return null;
}
const getFilter = () => data.config?.[collectionName]?.filter || {};
const setFilter = (filter) => {
try {
_.set(data, `config.${collectionName}.filter`, removeNullCondition(filter));
} catch (err) {
error(err);
}
};
const getTitleFIeld = () => data.config?.[collectionName]?.titleField || collection?.titleField || 'id';
const setTitleField = (titleField) => {
try {
_.set(data, `config.${collectionName}.titleField`, titleField);
} catch (err) {
error(err);
}
};
const options = collectionFields
.filter((field) => isTitleField(field))
.map((field) => ({
value: field?.name,
label: compile(field?.uiSchema?.title) || field?.name,
}));
return (
<GeneralSchemaDesigner draggable={false}>
<SchemaSettings.ModalItem
title={t('Set the data scope')}
schema={
{
type: 'object',
title: t('Set the data scope'),
properties: {
filter: {
default: getFilter(),
// title: '数据范围',
enum: dataSource,
'x-component': 'Filter',
'x-component-props': {
dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
},
},
},
} as ISchema
}
onSubmit={({ filter }) => {
setFilter(filter);
try {
// 不仅更新当前模板,也更新同级的其它模板
field.query('fieldReaction.items.*.layout.dataId').forEach((item) => {
if (item.componentProps.service.resource !== collectionName) {
return;
}
item.componentProps.service.params = {
filter: _.isEmpty(filter)
? {}
: removeNullCondition(mergeFilter([filter, getSelectedIdFilter(field.value)], '$or')),
};
});
} catch (err) {
error(err);
}
formSchema['x-data-templates'] = data;
dn.emit('patch', {
schema: {
['x-uid']: formSchema['x-uid'],
['x-data-templates']: data,
},
});
dn.refresh();
}}
/>
<SelectItem
key="title-field"
title={t('Title field')}
options={options}
value={getTitleFIeld()}
onChange={(label) => {
setTitleField(label);
try {
// 不仅更新当前模板,也更新同级的其它模板
field.query('fieldReaction.items.*.layout.dataId').forEach((item) => {
if (item.componentProps.service.resource !== collectionName) {
return;
}
item.componentProps.fieldNames.label = label;
item.componentProps.targetField = getCollectionField(
`${collectionName}.${label || collection?.titleField || 'id'}`,
);
});
} catch (err) {
console.error(err);
}
formSchema['x-data-templates'] = data;
const schema = {
['x-uid']: formSchema['x-uid'],
['x-data-templates']: data,
};
dn.emit('patch', {
schema,
});
dn.refresh();
}}
/>
</GeneralSchemaDesigner>
);
});
export function getSelectedIdFilter(selectedId) {
return selectedId
? {
id: {
$eq: selectedId,
},
}
: null;
}
function SelectItem(props) {
const { title, options, value, onChange, ...others } = props;
return (
<SchemaSettings.Item {...others}>
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
{title}
<Select
bordered={false}
value={value}
onChange={onChange}
options={options}
style={{ textAlign: 'right', minWidth: 100 }}
{...others}
/>
</div>
</SchemaSettings.Item>
);
}

View File

@ -31,6 +31,7 @@ import {
FormProvider, FormProvider,
RemoteSchemaComponent, RemoteSchemaComponent,
SchemaComponent, SchemaComponent,
SchemaComponentContext,
SchemaComponentOptions, SchemaComponentOptions,
createDesignable, createDesignable,
findFormBlock, findFormBlock,
@ -1011,8 +1012,15 @@ SchemaSettings.LinkageRules = function LinkageRules(props) {
); );
}; };
export const useDataTemplates = () => { export const useDataTemplates = (schema?: Schema) => {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
if (schema) {
return {
templateData: _.cloneDeep(schema['x-data-templates']),
};
}
const formSchema = findFormBlock(fieldSchema) || fieldSchema; const formSchema = findFormBlock(fieldSchema) || fieldSchema;
return { return {
templateData: _.cloneDeep(formSchema?.['x-data-templates']), templateData: _.cloneDeep(formSchema?.['x-data-templates']),
@ -1020,6 +1028,7 @@ export const useDataTemplates = () => {
}; };
SchemaSettings.DataTemplates = function DataTemplates(props) { SchemaSettings.DataTemplates = function DataTemplates(props) {
const designerCtx = useContext(SchemaComponentContext);
const { collectionName } = props; const { collectionName } = props;
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const { dn } = useDesignable(); const { dn } = useDesignable();
@ -1035,6 +1044,8 @@ SchemaSettings.DataTemplates = function DataTemplates(props) {
fieldReaction: { fieldReaction: {
'x-component': FormDataTemplates, 'x-component': FormDataTemplates,
'x-component-props': { 'x-component-props': {
designerCtx,
formSchema,
useProps: () => { useProps: () => {
return { return {
defaultValues: templateData, defaultValues: templateData,
@ -1048,7 +1059,7 @@ SchemaSettings.DataTemplates = function DataTemplates(props) {
[templateData], [templateData],
); );
const onSubmit = useCallback((v) => { const onSubmit = useCallback((v) => {
const data = v.fieldReaction || {}; const data = { ...(formSchema['x-data-templates'] || {}), ...v.fieldReaction };
// 当 Tree 组件开启 checkStrictly 属性时,会导致 checkedKeys 的值是一个对象,而不是数组,所以这里需要转换一下以支持旧版本 // 当 Tree 组件开启 checkStrictly 属性时,会导致 checkedKeys 的值是一个对象,而不是数组,所以这里需要转换一下以支持旧版本
data.items.forEach((item) => { data.items.forEach((item) => {

View File

@ -9,7 +9,9 @@ export function useIsShowMultipleSwitch() {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const { getCollectionField } = useCollectionManager(); const { getCollectionField } = useCollectionManager();
const collectionField = getCollectionField(fieldSchema['x-collection-field']); const collectionField = fieldSchema['x-collection-field']
? getCollectionField(fieldSchema['x-collection-field'])
: null;
const uiSchema = collectionField?.uiSchema || fieldSchema; const uiSchema = collectionField?.uiSchema || fieldSchema;
const hasMultiple = uiSchema['x-component-props']?.multiple === true; const hasMultiple = uiSchema['x-component-props']?.multiple === true;

View File

@ -1,4 +1,4 @@
import { Input, PageHeader as AntdPageHeader, Spin } from 'antd'; import { PageHeader as AntdPageHeader, Input, Spin } from 'antd';
import React, { useContext, useState } from 'react'; import React, { useContext, useState } from 'react';
import { useHistory, useRouteMatch } from 'react-router-dom'; import { useHistory, useRouteMatch } from 'react-router-dom';
import { useAPIClient, useRequest, useSchemaTemplateManager } from '..'; import { useAPIClient, useRequest, useSchemaTemplateManager } from '..';