refactor: initialization when switching field components (#1915)

* fix: toManyNester only display one data

* fix: sub-nester

* fix: initialValue

* fix: [null]

* fix: record picker initValue

* fix: initValue filter

* fix: association select  initialValue filter

* fix: association select  initialValue filter

* fix: [null]

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
katherinehhh 2023-05-24 10:36:29 +08:00 committed by GitHub
parent a0dd58e7e3
commit e299f5452c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 121 additions and 106 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<Field>();
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 ? (
<AssociationFieldContext.Provider value={{ options: collectionField, field, currentMode }}>
{props.children}

View File

@ -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 (
<div key={fieldSchema.name}>
<Input.Group compact style={{ display: 'flex' }}>
@ -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 <RemoteSelect.ReadPretty {...props} service={service}></RemoteSelect.ReadPretty>;
}
return null;

View File

@ -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();

View File

@ -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);

View File

@ -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 <Card bordered={true}>{props.children}</Card>;
};
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<ArrayField>();
const [values, setValues] = useState([]);
useEffect(() => {
const values = toArr(field.value, field.readPretty);
setValues(values);
}, []);
const { t } = useTranslation();
return (
<Card bordered={true} style={{ position: 'relative' }}>
{values.map((value, index) => {
{(field.value || []).map((value, index) => {
return (
<>
{!field.readPretty && (
@ -51,9 +36,9 @@ const ToManyNester = observer((props) => {
<CloseCircleOutlined
style={{ zIndex: 1000, position: 'absolute', color: '#a8a3a3' }}
onClick={() => {
const data = values.concat();
data.splice(index, 1);
setValues(data);
const result = field.value;
result.splice(index, 1);
field.value = result;
}}
/>
</div>
@ -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')}

View File

@ -172,7 +172,7 @@ FormItem.Designer = function Designer() {
};
});
const fieldSchemaWithoutRequired = _.omit(fieldSchema,'required')
const fieldSchemaWithoutRequired = _.omit(fieldSchema, 'required');
return (
<GeneralSchemaDesigner>
@ -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,
});

View File

@ -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);

View File

@ -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<UiSchemaRepository>('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 });
}
});
}
}