diff --git a/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx b/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx
index f6bd28e6c..4c89f5282 100644
--- a/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx
+++ b/packages/core/client/src/collection-manager/Configuration/AddFieldAction.tsx
@@ -8,10 +8,11 @@ import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useRequest } from '../../api-client';
import { useRecord } from '../../record-provider';
-import { ActionContext, SchemaComponent, useCompile } from '../../schema-component';
+import { ActionContext, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
import { useCreateAction } from '../action-hooks';
import { useCollectionManager } from '../hooks';
import { IField } from '../interfaces/types';
+import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
import * as components from './components';
import { options } from './interfaces';
@@ -27,12 +28,15 @@ const getSchema = (schema: IField, record: any, compile): ISchema => {
properties['defaultValue']['title'] = compile('{{ t("Default value") }}');
properties['defaultValue']['x-decorator'] = 'FormItem';
}
-
- const initialValue = {
+
+ const initialValue: any = {
name: `f_${uid()}`,
...cloneDeep(schema.default),
interface: schema.name,
};
+ if (initialValue.reverseField) {
+ initialValue.reverseField.name = `f_${uid()}`;
+ }
// initialValue.uiSchema.title = schema.title;
return {
type: 'object',
@@ -95,44 +99,22 @@ const useCreateCollectionField = () => {
const { run } = useCreateAction();
const { refreshCM } = useCollectionManager();
const { title } = useRecord();
+ const ctx = useActionContext();
+ const { refresh } = useResourceActionContext();
+ const { resource } = useResourceContext();
return {
async run() {
await form.submit();
- // const options = form?.values?.uiSchema?.enum?.slice() || [];
- // if (options?.length) {
- // form.setValuesIn(
- // 'uiSchema.enum',
- // options.map((option) => {
- // return {
- // value: uid(),
- // ...option,
- // };
- // }),
- // );
- // }
- // function recursiveChildren(children = [], prefix = 'children') {
- // children.forEach((item, index) => {
- // const itemOptions = item.uiSchema?.enum?.slice() || [];
- // form.setValuesIn(
- // `${prefix}[${index}].uiSchema.enum`,
- // itemOptions.map((option) => {
- // return {
- // value: uid(),
- // ...option,
- // };
- // }),
- // );
- // recursiveChildren(item.children, `${prefix}[${index}].children`);
- // });
- // }
-
- // recursiveChildren(form?.values?.children);
-
- if (['obo', 'oho', 'o2o', 'o2m', 'm2o', 'm2m', 'linkTo'].includes(form?.values?.interface) && title) {
- form.setValuesIn('reverseField.uiSchema.title', title);
+ const values = cloneDeep(form.values);
+ if (values.autoCreateReverseField) {
+ } else {
+ delete values.reverseField;
}
-
- await run();
+ delete values.autoCreateReverseField;
+ await resource.create({ values });
+ ctx.setVisible(false);
+ await form.reset();
+ refresh();
await refreshCM();
},
};
@@ -183,7 +165,7 @@ export const AddFieldAction = () => {
);
diff --git a/packages/core/client/src/collection-manager/Configuration/EditFieldAction.tsx b/packages/core/client/src/collection-manager/Configuration/EditFieldAction.tsx
index 227b5a4da..70f464e4e 100644
--- a/packages/core/client/src/collection-manager/Configuration/EditFieldAction.tsx
+++ b/packages/core/client/src/collection-manager/Configuration/EditFieldAction.tsx
@@ -2,16 +2,17 @@ import { ArrayTable } from '@formily/antd';
import { ISchema, useForm } from '@formily/react';
import { uid } from '@formily/shared';
import cloneDeep from 'lodash/cloneDeep';
+import set from 'lodash/set';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useAPIClient, useRequest } from '../../api-client';
import { useRecord } from '../../record-provider';
-import { ActionContext, SchemaComponent, useCompile } from '../../schema-component';
+import { ActionContext, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
import { useUpdateAction } from '../action-hooks';
import { useCollectionManager } from '../hooks';
import { IField } from '../interfaces/types';
+import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
import * as components from './components';
-import { Typography } from 'antd';
const getSchema = (schema: IField, record: any, compile): ISchema => {
if (!schema) {
@@ -50,8 +51,8 @@ const getSchema = (schema: IField, record: any, compile): ISchema => {
type: 'void',
'x-component': 'FieldSummary',
'x-component-props': {
- schemaKey: schema.name
- }
+ schemaKey: schema.name,
+ },
},
// @ts-ignore
...properties,
@@ -86,39 +87,23 @@ const useUpdateCollectionField = () => {
const form = useForm();
const { run } = useUpdateAction();
const { refreshCM } = useCollectionManager();
+ const ctx = useActionContext();
+ const { refresh } = useResourceActionContext();
+ const { resource, targetKey } = useResourceContext();
+ const { [targetKey]: filterByTk } = useRecord();
return {
async run() {
await form.submit();
- const options = form?.values?.uiSchema?.enum?.slice() || [];
- form.setValuesIn(
- 'uiSchema.enum',
- options.map((option) => {
- return {
- value: uid(),
- ...option,
- };
- }),
- );
-
- function recursiveChildren(children = [], prefix = 'children') {
- children.forEach((item, index) => {
- const itemOptions = item.uiSchema?.enum?.slice() || [];
- form.setValuesIn(
- `${prefix}[${index}].uiSchema.enum`,
- itemOptions.map((option) => {
- return {
- value: uid(),
- ...option,
- };
- }),
- );
- recursiveChildren(item.children, `${prefix}[${index}].children`);
- });
+ const values = cloneDeep(form.values);
+ if (values.autoCreateReverseField) {
+ } else {
+ delete values.reverseField;
}
-
- recursiveChildren(form?.values?.children);
-
- await run();
+ delete values.autoCreateReverseField;
+ await resource.update({ filterByTk, values });
+ ctx.setVisible(false);
+ await form.reset();
+ refresh();
await refreshCM();
},
};
@@ -132,18 +117,32 @@ export const EditFieldAction = (props) => {
const api = useAPIClient();
const { t } = useTranslation();
const compile = useCompile();
+ const [data, setData] = useState({});
return (
{
const { data } = await api.resource('collections.fields', record.collectionName).get({
filterByTk: record.name,
- appends: record.interface === 'subTable' ? ['uiSchema', 'children'] : ['uiSchema'],
+ appends: ['uiSchema', 'reverseField'],
});
- const schema = getSchema({
- ...getInterface(record.interface),
- default: data?.data,
- }, record, compile);
+ setData(data?.data);
+ const interfaceConf = getInterface(record.interface);
+ const defaultValues: any = cloneDeep(data?.data) || {};
+ if (!defaultValues?.reverseField) {
+ defaultValues.autoCreateReverseField = false;
+ defaultValues.reverseField = interfaceConf.default?.reverseField;
+ set(defaultValues.reverseField, 'name', `f_${uid()}`);
+ set(defaultValues.reverseField, 'uiSchema.title', record.__parent.title);
+ }
+ const schema = getSchema(
+ {
+ ...interfaceConf,
+ default: defaultValues,
+ },
+ record,
+ compile,
+ );
setSchema(schema);
setVisible(true);
}}
@@ -153,7 +152,7 @@ export const EditFieldAction = (props) => {
);
diff --git a/packages/core/client/src/collection-manager/interfaces/m2m.tsx b/packages/core/client/src/collection-manager/interfaces/m2m.tsx
index e69cf45bc..f04c737e9 100644
--- a/packages/core/client/src/collection-manager/interfaces/m2m.tsx
+++ b/packages/core/client/src/collection-manager/interfaces/m2m.tsx
@@ -1,7 +1,7 @@
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
import { cloneDeep } from 'lodash';
-import { defaultProps, recordPickerSelector, recordPickerViewer, relationshipType } from './properties';
+import { defaultProps, recordPickerSelector, recordPickerViewer, relationshipType, reverseFieldProperties } from './properties';
import { IField } from './types';
export const m2m: IField = {
@@ -231,6 +231,7 @@ export const m2m: IField = {
},
},
},
+ ...reverseFieldProperties,
},
filterable: {
nested: true,
diff --git a/packages/core/client/src/collection-manager/interfaces/m2o.tsx b/packages/core/client/src/collection-manager/interfaces/m2o.tsx
index 98a7ff621..da547c4d4 100644
--- a/packages/core/client/src/collection-manager/interfaces/m2o.tsx
+++ b/packages/core/client/src/collection-manager/interfaces/m2o.tsx
@@ -1,6 +1,6 @@
import { ISchema } from '@formily/react';
import { cloneDeep } from 'lodash';
-import { recordPickerSelector, recordPickerViewer, relationshipType } from './properties';
+import { recordPickerSelector, recordPickerViewer, relationshipType, reverseFieldProperties } from './properties';
import { IField } from './types';
export const m2o: IField = {
@@ -203,6 +203,7 @@ export const m2o: IField = {
},
},
},
+ ...reverseFieldProperties,
},
filterable: {
nested: true,
diff --git a/packages/core/client/src/collection-manager/interfaces/o2m.tsx b/packages/core/client/src/collection-manager/interfaces/o2m.tsx
index 9af090a13..f2387e341 100644
--- a/packages/core/client/src/collection-manager/interfaces/o2m.tsx
+++ b/packages/core/client/src/collection-manager/interfaces/o2m.tsx
@@ -1,6 +1,6 @@
import { ISchema } from '@formily/react';
import { cloneDeep } from 'lodash';
-import { recordPickerSelector, recordPickerViewer, relationshipType } from './properties';
+import { recordPickerSelector, recordPickerViewer, relationshipType, reverseFieldProperties } from './properties';
import { IField } from './types';
export const o2m: IField = {
@@ -240,6 +240,7 @@ export const o2m: IField = {
},
},
},
+ ...reverseFieldProperties,
},
filterable: {
nested: true,
diff --git a/packages/core/client/src/collection-manager/interfaces/o2o.tsx b/packages/core/client/src/collection-manager/interfaces/o2o.tsx
index 64f9b3458..7ca51c319 100644
--- a/packages/core/client/src/collection-manager/interfaces/o2o.tsx
+++ b/packages/core/client/src/collection-manager/interfaces/o2o.tsx
@@ -1,6 +1,6 @@
import { ISchema } from '@formily/react';
import { cloneDeep } from 'lodash';
-import { recordPickerSelector, recordPickerViewer, relationshipType } from './properties';
+import { recordPickerSelector, recordPickerViewer, relationshipType, reverseFieldProperties } from './properties';
import { IField } from './types';
const internalSchameInitialize = (schema: ISchema, { field, block, readPretty, action }) => {
@@ -37,11 +37,11 @@ const internalSchameInitialize = (schema: ISchema, { field, block, readPretty, a
'x-initializer': 'FormItemInitializers',
properties: {},
},
- }
+ },
},
},
},
- }
+ };
} else {
schema.type = 'string';
if (block === 'Form') {
@@ -57,11 +57,11 @@ const internalSchameInitialize = (schema: ISchema, { field, block, readPretty, a
} else {
schema['properties'] = {
selector: cloneDeep(recordPickerSelector),
- }
+ };
}
}
}
-}
+};
export const o2o: IField = {
name: 'o2o',
@@ -207,6 +207,16 @@ export const o2o: IField = {
},
},
},
+ 'reverseField.name': {
+ type: 'string',
+ title: '{{t("Inverse field name")}}',
+ // required: true,
+ 'x-hidden': '{{ !createOnly }}',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ description:
+ "{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
+ },
},
filterable: {
nested: true,
@@ -372,6 +382,7 @@ export const oho: IField = {
},
},
},
+ ...reverseFieldProperties,
},
filterable: {
nested: true,
@@ -537,6 +548,7 @@ export const obo: IField = {
},
},
},
+ ...reverseFieldProperties,
},
filterable: {
nested: true,
diff --git a/packages/core/client/src/collection-manager/interfaces/properties/index.ts b/packages/core/client/src/collection-manager/interfaces/properties/index.ts
index 6c32c2360..d24801a09 100644
--- a/packages/core/client/src/collection-manager/interfaces/properties/index.ts
+++ b/packages/core/client/src/collection-manager/interfaces/properties/index.ts
@@ -53,6 +53,89 @@ export const relationshipType: ISchema = {
],
};
+export const reverseFieldProperties: Record = {
+ reverse: {
+ type: 'void',
+ 'x-component': 'div',
+ 'x-hidden': '{{ !showReverseFieldConfig }}',
+ properties: {
+ autoCreateReverseField: {
+ type: 'boolean',
+ default: true,
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Checkbox',
+ 'x-content': '{{t("Create inverse field in the target collection")}}',
+ 'x-reactions': [
+ {
+ target: 'reverseField.type',
+ when: '{{!!$self.value}}',
+ fulfill: {
+ state: {
+ hidden: false,
+ },
+ },
+ otherwise: {
+ state: {
+ hidden: true,
+ },
+ },
+ },
+ {
+ target: 'reverseField.uiSchema.title',
+ when: '{{!!$self.value}}',
+ fulfill: {
+ state: {
+ hidden: false,
+ },
+ },
+ otherwise: {
+ state: {
+ hidden: true,
+ },
+ },
+ },
+ {
+ target: 'reverseField.name',
+ when: '{{!!$self.value}}',
+ fulfill: {
+ state: {
+ hidden: false,
+ },
+ },
+ otherwise: {
+ state: {
+ hidden: true,
+ },
+ },
+ },
+ ],
+ },
+ 'reverseField.type': {
+ ...relationshipType,
+ title: '{{t("Inverse relationship type")}}',
+ },
+ 'reverseField.uiSchema.title': {
+ type: 'string',
+ title: '{{t("Inverse field display name")}}',
+ default: '{{record.title}}',
+ required: true,
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ },
+ 'reverseField.name': {
+ type: 'string',
+ title: '{{t("Inverse field name")}}',
+ required: true,
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ 'x-validator': 'uid',
+ description:
+ "{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}",
+ },
+ },
+ },
+};
+
export const dateTimeProps: { [key: string]: ISchema } = {
'uiSchema.x-component-props.dateFormat': {
type: 'string',
diff --git a/packages/core/client/src/locale/zh_CN.ts b/packages/core/client/src/locale/zh_CN.ts
index 8e47d5e4a..f5a6a6090 100644
--- a/packages/core/client/src/locale/zh_CN.ts
+++ b/packages/core/client/src/locale/zh_CN.ts
@@ -175,7 +175,8 @@ export default {
"Time format": "时间格式",
"12 hour": "12 小时制",
"24 hour": "24 小时制",
- "Relationship type": "关联类型",
+ "Relationship type": "关系类型",
+ "Inverse relationship type": "反向关系类型",
"Source collection": "源数据表",
"Source key": "源数据表字段标识",
"Target collection": "目标数据表",
@@ -683,7 +684,9 @@ export default {
'Marketplace': '插件市场',
'Coming soon...': '敬请期待...',
'Settings center': '配置中心',
-
'Bookmark': '书签',
'Manage all settings': '管理所有配置',
+ 'Create inverse field in the target collection': '在目标数据表里创建反向关系字段',
+ 'Inverse field name': '反向关系字段标识',
+ 'Inverse field display name': '反向关系字段名称',
}
diff --git a/packages/plugins/collection-manager/src/server.ts b/packages/plugins/collection-manager/src/server.ts
index e4ed074a7..1971373b3 100644
--- a/packages/plugins/collection-manager/src/server.ts
+++ b/packages/plugins/collection-manager/src/server.ts
@@ -41,7 +41,12 @@ export class CollectionManagerPlugin extends Plugin {
lodash.get(newValue, 'reverseField') &&
!lodash.get(newValue, 'reverseField.key')
) {
- throw new Error('cant update field without a reverseField key');
+ const field = await this.app.db
+ .getModel('fields')
+ .findByPk(model.get('reverseKey'), { transaction: options.transaction });
+ if (field) {
+ throw new Error('cant update field without a reverseField key');
+ }
}
});