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:
parent
c722ca41d9
commit
e61568ac57
@ -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();
|
||||||
|
@ -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>
|
||||||
|
@ -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) {
|
||||||
|
@ -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,49 +58,51 @@ const InternalRemoteSelect = connect(
|
|||||||
const mapOptionsToTags = useCallback(
|
const mapOptionsToTags = useCallback(
|
||||||
(options) => {
|
(options) => {
|
||||||
try {
|
try {
|
||||||
return options.map((option) => {
|
return options
|
||||||
let label = option[fieldNames.label];
|
.map((option) => {
|
||||||
|
let label = option[fieldNames.label];
|
||||||
|
|
||||||
if (targetField?.uiSchema?.enum) {
|
if (targetField?.uiSchema?.enum) {
|
||||||
if (Array.isArray(label)) {
|
if (Array.isArray(label)) {
|
||||||
label = label
|
label = label
|
||||||
.map((item, index) => {
|
.map((item, index) => {
|
||||||
const option = targetField.uiSchema.enum.find((i) => i.value === item);
|
const option = targetField.uiSchema.enum.find((i) => i.value === item);
|
||||||
if (option) {
|
if (option) {
|
||||||
return (
|
return (
|
||||||
<Tag key={index} color={option.color} style={{ marginRight: 3 }}>
|
<Tag key={index} color={option.color} style={{ marginRight: 3 }}>
|
||||||
{option?.label || item}
|
{option?.label || item}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return <Tag>{item}</Tag>;
|
return <Tag key={item}>{item}</Tag>;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.reverse();
|
.reverse();
|
||||||
} else {
|
} else {
|
||||||
const item = targetField.uiSchema.enum.find((i) => i.value === label);
|
const item = targetField.uiSchema.enum.find((i) => i.value === label);
|
||||||
if (item) {
|
if (item) {
|
||||||
label = <Tag color={item.color}>{item.label}</Tag>;
|
label = <Tag color={item.color}>{item.label}</Tag>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (targetField?.type === 'date') {
|
if (targetField?.type === 'date') {
|
||||||
label = moment(label).format('YYYY-MM-DD');
|
label = moment(label).format('YYYY-MM-DD');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mapOptions) {
|
if (mapOptions) {
|
||||||
return mapOptions({
|
return mapOptions({
|
||||||
[fieldNames.label]: label,
|
[fieldNames.label]: label || EMPTY,
|
||||||
|
[fieldNames.value]: option[fieldNames.value],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...option,
|
||||||
|
[fieldNames.label]: label || EMPTY,
|
||||||
[fieldNames.value]: option[fieldNames.value],
|
[fieldNames.value]: option[fieldNames.value],
|
||||||
});
|
};
|
||||||
}
|
})
|
||||||
return {
|
.filter(Boolean);
|
||||||
...option,
|
|
||||||
[fieldNames.label]: label || option[fieldNames.value],
|
|
||||||
[fieldNames.value]: option[fieldNames.value],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return options;
|
return options;
|
||||||
|
@ -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', {
|
||||||
|
@ -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 && (
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -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) => {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 '..';
|
||||||
|
Loading…
Reference in New Issue
Block a user