From f6add23da5ef9a4c912a163a17f3ae4ad6d3b994 Mon Sep 17 00:00:00 2001 From: chenos Date: Tue, 11 Oct 2022 08:44:16 +0800 Subject: [PATCH] feat(collection-manager): inverse fields can be configured (#883) * feat: inverse field * feat: improve code * feat: translations * fix: required (cherry picked from commit 306dca8e5ee1b74562d835a816b72f933465bfb6) # Conflicts: # packages/core/client/src/locale/zh_CN.ts --- .../Configuration/AddFieldAction.tsx | 58 +++++-------- .../Configuration/EditFieldAction.tsx | 77 +++++++++-------- .../src/collection-manager/interfaces/m2m.tsx | 3 +- .../src/collection-manager/interfaces/m2o.tsx | 3 +- .../src/collection-manager/interfaces/o2m.tsx | 3 +- .../src/collection-manager/interfaces/o2o.tsx | 22 +++-- .../interfaces/properties/index.ts | 83 +++++++++++++++++++ packages/core/client/src/locale/zh_CN.ts | 7 +- .../plugins/collection-manager/src/server.ts | 7 +- 9 files changed, 175 insertions(+), 88 deletions(-) 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'); + } } });