mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 08:47:20 +00:00
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:
parent
a0dd58e7e3
commit
e299f5452c
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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')}
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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 });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user