diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index 6fe4700860..dfea313bb6 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -1,7 +1,7 @@ import { Schema, SchemaExpressionScopeContext, useField, useFieldSchema, useForm } from '@formily/react'; import { parse } from '@nocobase/utils/client'; import { Modal, message } from 'antd'; -import { cloneDeep, isEqual, uniq, omitBy } from 'lodash'; +import { cloneDeep, uniq } from 'lodash'; import get from 'lodash/get'; import omit from 'lodash/omit'; import { ChangeEvent, useContext, useEffect } from 'react'; @@ -16,12 +16,12 @@ import { transformToFilter } from '../../filter-provider/utils'; import { useRecord } from '../../record-provider'; import { removeNullCondition, useActionContext, useCompile } from '../../schema-component'; import { BulkEditFormItemValueType } from '../../schema-initializer/components'; +import { useSchemaTemplateManager } from '../../schema-templates'; import { useCurrentUserContext } from '../../user'; import { useBlockRequestContext, useFilterByTk } from '../BlockProvider'; import { useDetailsBlockContext } from '../DetailsBlockProvider'; import { mergeFilter } from '../SharedFilterProvider'; import { TableFieldResource } from '../TableFieldProvider'; -import { useSchemaTemplateManager } from '../../schema-templates'; export const usePickActionProps = () => { const form = useForm(); @@ -158,8 +158,8 @@ export const useCreateActionProps = () => { if (!skipValidator) { await form.submit(); } - const formValues = getFormValues(filterByTk, field, form, fieldNames, getField, resource); - const values = omitBy(formValues, (value) => isEqual(JSON.stringify(value), '[{}]')); + const values = getFormValues(filterByTk, field, form, fieldNames, getField, resource); + // const values = omitBy(formValues, (value) => isEqual(JSON.stringify(value), '[{}]')); if (addChild) { const treeParentField = getTreeParentField(); values[treeParentField?.name ?? 'parent'] = currentRecord; diff --git a/packages/core/client/src/collection-manager/interfaces/m2m.tsx b/packages/core/client/src/collection-manager/interfaces/m2m.tsx index 1998730ec8..9b51f0ba1c 100644 --- a/packages/core/client/src/collection-manager/interfaces/m2m.tsx +++ b/packages/core/client/src/collection-manager/interfaces/m2m.tsx @@ -1,12 +1,9 @@ import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; -import { cloneDeep } from 'lodash'; import { defaultProps, - recordPickerSelector, - recordPickerViewer, relationshipType, - reverseFieldProperties, + reverseFieldProperties } from './properties'; import { IField } from './types'; @@ -53,7 +50,7 @@ export const m2m: IField = { }, availableTypes: ['belongsToMany'], schemaInitialize(schema: ISchema, { readPretty, block, targetCollection }) { - schema['type'] = 'array'; + // schema['type'] = 'array'; if (targetCollection?.titleField && schema['x-component-props']) { schema['x-component-props'].fieldNames = schema['x-component-props'].fieldNames || { value: 'id' }; schema['x-component-props'].fieldNames.label = targetCollection.titleField; diff --git a/packages/core/client/src/collection-manager/interfaces/m2o.tsx b/packages/core/client/src/collection-manager/interfaces/m2o.tsx index 69c28a5a0a..2ac8dc1e62 100644 --- a/packages/core/client/src/collection-manager/interfaces/m2o.tsx +++ b/packages/core/client/src/collection-manager/interfaces/m2o.tsx @@ -1,11 +1,8 @@ import { ISchema } from '@formily/react'; -import { cloneDeep } from 'lodash'; import { constraintsProps, - recordPickerSelector, - recordPickerViewer, relationshipType, - reverseFieldProperties, + reverseFieldProperties } from './properties'; import { IField } from './types'; @@ -52,7 +49,7 @@ export const m2o: IField = { }, availableTypes: ['belongsTo'], schemaInitialize(schema: ISchema, { block, readPretty, targetCollection }) { - schema['type'] = 'object'; + // schema['type'] = 'object'; if (targetCollection?.titleField && schema['x-component-props']) { schema['x-component-props'].fieldNames = schema['x-component-props'].fieldNames || { value: 'id' }; schema['x-component-props'].fieldNames.label = targetCollection.titleField; diff --git a/packages/core/client/src/collection-manager/interfaces/o2m.tsx b/packages/core/client/src/collection-manager/interfaces/o2m.tsx index 4267cca521..800e075021 100644 --- a/packages/core/client/src/collection-manager/interfaces/o2m.tsx +++ b/packages/core/client/src/collection-manager/interfaces/o2m.tsx @@ -1,12 +1,5 @@ import { ISchema } from '@formily/react'; -import { cloneDeep } from 'lodash'; -import { - constraintsProps, - recordPickerSelector, - recordPickerViewer, - relationshipType, - reverseFieldProperties, -} from './properties'; +import { constraintsProps, relationshipType, reverseFieldProperties } from './properties'; import { IField } from './types'; export const o2m: IField = { @@ -52,7 +45,7 @@ export const o2m: IField = { }, availableTypes: ['hasMany'], schemaInitialize(schema: ISchema, { field, block, readPretty, targetCollection }) { - schema['type'] = 'array'; + // schema['type'] = 'array'; if (targetCollection?.titleField && schema['x-component-props']) { schema['x-component-props'].fieldNames = schema['x-component-props'].fieldNames || { value: 'id' }; schema['x-component-props'].fieldNames.label = targetCollection.titleField; diff --git a/packages/core/client/src/collection-manager/interfaces/o2o.tsx b/packages/core/client/src/collection-manager/interfaces/o2o.tsx index f72aa926e6..82610e2da4 100644 --- a/packages/core/client/src/collection-manager/interfaces/o2o.tsx +++ b/packages/core/client/src/collection-manager/interfaces/o2o.tsx @@ -1,12 +1,5 @@ import { ISchema } from '@formily/react'; -import { cloneDeep } from 'lodash'; -import { - constraintsProps, - recordPickerSelector, - recordPickerViewer, - relationshipType, - reverseFieldProperties, -} from './properties'; +import { constraintsProps, relationshipType, reverseFieldProperties } from './properties'; import { IField } from './types'; export const o2o: IField = { @@ -231,7 +224,7 @@ export const oho: IField = { }, }, schemaInitialize(schema: ISchema, { field, block, readPretty, action }) { - schema['type'] = 'object'; + // schema['type'] = 'object'; if (['Table', 'Kanban'].includes(block)) { schema['x-component-props'] = schema['x-component-props'] || {}; schema['x-component-props']['ellipsis'] = true; @@ -401,7 +394,7 @@ export const obo: IField = { }, }, schemaInitialize(schema: ISchema, { field, block, readPretty, action, targetCollection }) { - schema['type'] = 'object'; + // schema['type'] = 'object'; if (['Table', 'Kanban'].includes(block)) { schema['x-component-props'] = schema['x-component-props'] || {}; schema['x-component-props']['ellipsis'] = true; diff --git a/packages/core/client/src/schema-component/antd/association-field/AssociationFieldProvider.tsx b/packages/core/client/src/schema-component/antd/association-field/AssociationFieldProvider.tsx index d8e09342ae..33fdbecc96 100644 --- a/packages/core/client/src/schema-component/antd/association-field/AssociationFieldProvider.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/AssociationFieldProvider.tsx @@ -1,10 +1,11 @@ +import { Field } from '@formily/core'; import { observer, useField, useFieldSchema } from '@formily/react'; import React, { useMemo } from 'react'; import { useCollectionManager } from '../../../collection-manager'; import { AssociationFieldContext } from './context'; export const AssociationFieldProvider = observer((props) => { - const field = useField(); + const field = useField(); const { getCollectionJoinField, getCollection } = useCollectionManager(); const fieldSchema = useFieldSchema(); @@ -20,6 +21,7 @@ export const AssociationFieldProvider = observer((props) => { () => fieldSchema['x-component-props']?.mode || (isFileCollection ? 'FileManager' : 'Select'), [fieldSchema['x-component-props']?.mode], ); + return collectionField ? ( {props.children} diff --git a/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx b/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx index e33b47fee8..2e9a25aad0 100644 --- a/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx @@ -1,8 +1,7 @@ import { LoadingOutlined } from '@ant-design/icons'; import { RecursionField, connect, mapProps, observer, useField, useFieldSchema } from '@formily/react'; import { Input } from 'antd'; -import React, { useCallback, useEffect, useMemo } from 'react'; -import { useFieldTitle } from '../../hooks'; +import React from 'react'; import { RemoteSelect, RemoteSelectProps } from '../remote-select'; import useServiceOptions from './hooks'; @@ -17,28 +16,8 @@ const InternalAssociationSelect = observer((props: AssociationSelectProps) => { const fieldSchema = useFieldSchema(); const service = useServiceOptions(props); const isAllowAddNew = fieldSchema['x-add-new']; - const normalizeValues = useCallback( - (obj) => { - if (!objectValue && typeof obj === 'object') { - return obj[fieldNames?.value]; - } - return obj; - }, - [objectValue, fieldNames?.value], - ); - const value = useMemo(() => { - if (props.value === undefined || props.value === null || !Object.keys(props.value).length) { - return; - } - if (Array.isArray(props.value)) { - return props.value; - } else { - return props.value; - } - }, [props.value, normalizeValues]); - useEffect(() => { - field.value = value; - }, []); + const value = Array.isArray(props.value) ? props.value.filter(Boolean) : props.value; + return (
@@ -75,9 +54,8 @@ export const AssociationSelect = InternalAssociationSelect as unknown as Associa export const AssociationSelectReadPretty = connect( (props: any) => { + const service = useServiceOptions(props); if (props.fieldNames) { - const service = useServiceOptions(props); - useFieldTitle(); return ; } return null; diff --git a/packages/core/client/src/schema-component/antd/association-field/Editable.tsx b/packages/core/client/src/schema-component/antd/association-field/Editable.tsx index 692518081a..d85f516a09 100644 --- a/packages/core/client/src/schema-component/antd/association-field/Editable.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/Editable.tsx @@ -1,5 +1,5 @@ import { observer, useField, useFieldSchema, useForm } from '@formily/react'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { SchemaComponentOptions } from '../../'; import { useAssociationCreateActionProps as useCAP } from '../../../block-provider/hooks'; import { AssociationFieldProvider } from './AssociationFieldProvider'; @@ -17,6 +17,31 @@ const EditableAssociationField = observer((props: any) => { const form = useForm(); const fieldSchema = useFieldSchema(); const { options: collectionField, currentMode } = useAssociationFieldContext(); + const [loading, setLoading] = useState(true); + + useEffect(() => { + setLoading(true); + if (!collectionField) { + setLoading(false); + return; + } + if (field.value !== null && field.value !== undefined) { + setLoading(false); + return; + } + if (currentMode === 'Nester') { + if (['belongsTo', 'hasOne'].includes(collectionField.type)) { + field.value = {}; + } else if (['belongsToMany', 'hasMany'].includes(collectionField.type)) { + field.value = [null]; + } + } + setLoading(false); + }, [currentMode, collectionField, field.value]); + + if (loading) { + return null; + } const useCreateActionProps = () => { const { onClick } = useCAP(); diff --git a/packages/core/client/src/schema-component/antd/association-field/InternalPicker.tsx b/packages/core/client/src/schema-component/antd/association-field/InternalPicker.tsx index 5c5d723949..663b22fa31 100644 --- a/packages/core/client/src/schema-component/antd/association-field/InternalPicker.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/InternalPicker.tsx @@ -1,7 +1,7 @@ import { RecursionField, observer, useField, useFieldSchema } from '@formily/react'; import { Input, Select } from 'antd'; import { differenceBy, unionBy } from 'lodash'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect, useMemo, useState } from 'react'; import { FormProvider, RecordPickerContext, @@ -71,7 +71,20 @@ export const InternalPicker = observer((props: any) => { const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label'); const isAllowAddNew = fieldSchema['x-add-new']; const [selectedRows, setSelectedRows] = useState([]); - const [options, setOptions] = useState([]); + const options = useMemo(() => { + if (value && Object.keys(value).length > 0) { + const opts = (Array.isArray(value) ? value : value ? [value] : []).filter(Boolean).map((option) => { + const label = option?.[fieldNames.label]; + return { + ...option, + [fieldNames.label]: getLabelFormatValue(compile(labelUiSchema), compile(label)), + }; + }); + return opts; + } + return []; + }, [value, fieldNames?.label]); + const pickerProps = { size: 'small', fieldNames, @@ -85,24 +98,11 @@ export const InternalPicker = observer((props: any) => { setSelectedRows, collectionField, }; - useEffect(() => { - if (value && Object.keys(value).length > 0) { - const opts = (Array.isArray(value) ? value : value ? [value] : []).map((option) => { - const label = option[fieldNames.label]; - return { - ...option, - [fieldNames.label]: getLabelFormatValue(compile(labelUiSchema), compile(label)), - }; - }); - setOptions(opts); - } - }, [value, fieldNames?.label]); const getValue = () => { if (multiple == null) return null; - return Array.isArray(value) ? value?.map((v) => v[fieldNames.value]) : value?.[fieldNames.value]; + return Array.isArray(value) ? value.filter(Boolean)?.map((v) => v?.[fieldNames.value]) : value?.[fieldNames.value]; }; - const getFilter = () => { const targetKey = collectionField?.targetKey || 'id'; const list = options.map((option) => option[targetKey]).filter(Boolean); diff --git a/packages/core/client/src/schema-component/antd/association-field/Nester.tsx b/packages/core/client/src/schema-component/antd/association-field/Nester.tsx index e2b3e61fa0..4ad58961ee 100644 --- a/packages/core/client/src/schema-component/antd/association-field/Nester.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/Nester.tsx @@ -2,7 +2,7 @@ import { CloseCircleOutlined } from '@ant-design/icons'; import { ArrayField } from '@formily/core'; import { RecursionField, observer, useFieldSchema } from '@formily/react'; import { Button, Card, Divider } from 'antd'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext } from 'react'; import { useTranslation } from 'react-i18next'; import { AssociationFieldContext } from './context'; import { useAssociationFieldContext } from './hooks'; @@ -22,28 +22,13 @@ const ToOneNester = (props) => { return {props.children}; }; -const toArr = (value, isReadpretty) => { - if (!value) { - return isReadpretty ? [] : [{}]; - } - if (Array.isArray(value)) { - return value.length > 0 ? value : isReadpretty ? [] : [{}]; - } - return [value]; -}; - const ToManyNester = observer((props) => { const fieldSchema = useFieldSchema(); const { field } = useAssociationFieldContext(); - const [values, setValues] = useState([]); - useEffect(() => { - const values = toArr(field.value, field.readPretty); - setValues(values); - }, []); const { t } = useTranslation(); return ( - {values.map((value, index) => { + {(field.value || []).map((value, index) => { return ( <> {!field.readPretty && ( @@ -51,9 +36,9 @@ const ToManyNester = observer((props) => { { - const data = values.concat(); - data.splice(index, 1); - setValues(data); + const result = field.value; + result.splice(index, 1); + field.value = result; }} />
@@ -68,9 +53,9 @@ const ToManyNester = observer((props) => { type={'dashed'} block onClick={() => { - const data = values.concat(); - data.push({}); - setValues(data); + const result = field.value; + result.push({}); + field.value = result; }} > {t('Add new')} diff --git a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx index 3664964b81..872127498e 100644 --- a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx +++ b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx @@ -172,7 +172,7 @@ FormItem.Designer = function Designer() { }; }); - const fieldSchemaWithoutRequired = _.omit(fieldSchema,'required') + const fieldSchemaWithoutRequired = _.omit(fieldSchema, 'required'); return ( @@ -574,10 +574,10 @@ FormItem.Designer = function Designer() { schema['x-component-props'] = fieldSchema['x-component-props']; field.componentProps = field.componentProps || {}; field.componentProps.mode = mode; - if (mode === 'Nester') { - const initValue = ['hasMany', 'belongsToMany'].includes(collectionField?.type) ? [] : {}; - field.value = field.value || initValue; - } + // if (mode === 'Nester') { + // const initValue = ['hasMany', 'belongsToMany'].includes(collectionField?.type) ? [{}] : {}; + // field.value = field.value || initValue; + // } dn.emit('patch', { schema, }); diff --git a/packages/core/database/src/update-guard.ts b/packages/core/database/src/update-guard.ts index 7f696266de..bc5192609e 100644 --- a/packages/core/database/src/update-guard.ts +++ b/packages/core/database/src/update-guard.ts @@ -111,7 +111,7 @@ export class UpdateGuard { if (Array.isArray(associationValues)) { associationValues = associationValues.map((value) => { - if (typeof value == 'string' || typeof value == 'number') { + if (value === undefined || value === null || typeof value == 'string' || typeof value == 'number') { return value; } else { return sanitizeValue(value); diff --git a/packages/plugins/ui-schema-storage/src/migrations/20230522231231-association-field.ts b/packages/plugins/ui-schema-storage/src/migrations/20230522231231-association-field.ts new file mode 100644 index 0000000000..e60f8fb7e8 --- /dev/null +++ b/packages/plugins/ui-schema-storage/src/migrations/20230522231231-association-field.ts @@ -0,0 +1,45 @@ +import { Migration } from '@nocobase/server'; +import UiSchemaRepository from '../repository'; + +export default class extends Migration { + async up() { + const result = await this.app.version.satisfies('<0.9.3-alpha.2'); + + if (!result) { + return; + } + + const r = this.db.getRepository('uiSchemas'); + const items = await r.find({ + filter: { + 'schema.x-component': 'CollectionField', + }, + }); + console.log(items?.length); + await this.db.sequelize.transaction(async (transaction) => { + for (const item of items) { + const schema = item.schema; + if (!schema['x-collection-field']) { + continue; + } + if (schema['type'] === 'string') { + continue; + } + const field = this.db.getFieldByPath(schema['x-collection-field']); + if (!field) { + continue; + } + console.log(schema['x-collection-field'], schema['type']); + if (['hasOne', 'belongsTo'].includes(field.type)) { + schema['type'] = 'string'; + } else if (['hasMany', 'belongsToMany'].includes(field.type)) { + schema['type'] = 'string'; + } else { + continue; + } + item.set('schema', schema); + await item.save({ transaction }); + } + }); + } +}