From a00dcb78cbb70d74440ee73b690da3e3d8b352b2 Mon Sep 17 00:00:00 2001 From: Katherine Date: Fri, 24 Jan 2025 23:00:35 +0800 Subject: [PATCH] refactor: optimize filter component in filter form to match filterable settings (#6110) * fix: display checkbox field as select in filter form * fix: bug * chore: filterCollectionffield * refactor: code improve * fix: bug * fix: bug * fix: isTruly and isFalsy * fix: bug * fix: bug * fix: bug * fix: bug --------- Co-authored-by: chenos --- .../FilterFormBlockProvider.tsx | 39 +++--- .../interfaces/properties/operators.ts | 45 ++++++- .../filter-blocks/FilterCollectionField.tsx | 119 ++++++++++++++++++ .../Select/selectComponentFieldSettings.tsx | 8 +- .../schema-component/antd/select/Select.tsx | 4 +- .../core/database/src/operators/boolean.ts | 27 ++-- packages/core/test/src/e2e/templatesOfPage.ts | 6 +- 7 files changed, 216 insertions(+), 32 deletions(-) create mode 100644 packages/core/client/src/modules/blocks/filter-blocks/FilterCollectionField.tsx diff --git a/packages/core/client/src/block-provider/FilterFormBlockProvider.tsx b/packages/core/client/src/block-provider/FilterFormBlockProvider.tsx index 99d983810c..f6eacc4ede 100644 --- a/packages/core/client/src/block-provider/FilterFormBlockProvider.tsx +++ b/packages/core/client/src/block-provider/FilterFormBlockProvider.tsx @@ -10,10 +10,11 @@ import { useFieldSchema } from '@formily/react'; import React from 'react'; import { withDynamicSchemaProps } from '../hoc/withDynamicSchemaProps'; -import { DatePickerProvider, ActionBarProvider } from '../schema-component'; +import { DatePickerProvider, ActionBarProvider, SchemaComponentOptions } from '../schema-component'; import { DefaultValueProvider } from '../schema-settings'; import { CollectOperators } from './CollectOperators'; import { FormBlockProvider } from './FormBlockProvider'; +import { FilterCollectionField } from '../modules/blocks/filter-blocks/FilterCollectionField'; export const FilterFormBlockProvider = withDynamicSchemaProps((props) => { const filedSchema = useFieldSchema(); @@ -21,22 +22,24 @@ export const FilterFormBlockProvider = withDynamicSchemaProps((props) => { const deprecatedOperators = filedSchema['x-filter-operators'] || {}; return ( - - - - false}> - - - - - + + + + + false}> + + + + + + ); }); diff --git a/packages/core/client/src/collection-manager/interfaces/properties/operators.ts b/packages/core/client/src/collection-manager/interfaces/properties/operators.ts index a79128316b..ce0b6d411f 100644 --- a/packages/core/client/src/collection-manager/interfaces/properties/operators.ts +++ b/packages/core/client/src/collection-manager/interfaces/properties/operators.ts @@ -164,8 +164,49 @@ export const time = [ ]; export const boolean = [ - { label: '{{t("Yes")}}', value: '$isTruly', selected: true, noValue: true }, - { label: '{{t("No")}}', value: '$isFalsy', noValue: true }, + { + label: '{{t("Yes")}}', + value: '$isTruly', + selected: true, + noValue: true, + schema: { + 'x-component': 'Select', + 'x-component-props': { + multiple: false, + options: [ + { + label: '{{t("Yes")}}', + value: true, + }, + { + label: '{{t("No")}}', + value: false, + }, + ], + }, + }, + }, + { + label: '{{t("No")}}', + value: '$isFalsy', + noValue: true, + schema: { + 'x-component': 'Select', + 'x-component-props': { + multiple: false, + options: [ + { + label: '{{t("Yes")}}', + value: true, + }, + { + label: '{{t("No")}}', + value: false, + }, + ], + }, + }, + }, ]; export const tableoid = [ diff --git a/packages/core/client/src/modules/blocks/filter-blocks/FilterCollectionField.tsx b/packages/core/client/src/modules/blocks/filter-blocks/FilterCollectionField.tsx new file mode 100644 index 0000000000..28f47ad979 --- /dev/null +++ b/packages/core/client/src/modules/blocks/filter-blocks/FilterCollectionField.tsx @@ -0,0 +1,119 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Field } from '@formily/core'; +import { connect, Schema, useField, useFieldSchema } from '@formily/react'; +import { untracked } from '@formily/reactive'; +import { merge } from '@formily/shared'; +import { concat } from 'lodash'; +import React, { useEffect } from 'react'; +import { ErrorBoundary } from 'react-error-boundary'; +import { useFormBlockContext } from '../../../block-provider/FormBlockProvider'; +import { useDynamicComponentProps } from '../../../hoc/withDynamicSchemaProps'; +import { ErrorFallback, useCompile, useComponent } from '../../../schema-component'; +import { useIsAllowToSetDefaultValue } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue'; +import { useCollectionManager_deprecated } from '../../../'; +import { + CollectionFieldProvider, + useCollectionField, +} from '../../../data-source/collection-field/CollectionFieldProvider'; + +type Props = { + component: any; + children?: React.ReactNode; +}; + +const setFieldProps = (field: Field, key: string, value: any) => { + untracked(() => { + if (field[key] === undefined) { + field[key] = value; + } + }); +}; + +const setRequired = (field: Field, fieldSchema: Schema, uiSchema: Schema) => { + if (typeof fieldSchema['required'] === 'undefined') { + field.required = !!uiSchema['required']; + } +}; + +/** + * TODO: 初步适配 + * @internal + */ +export const FilterCollectionFieldInternalField: React.FC = (props: Props) => { + const compile = useCompile(); + const field = useField(); + const fieldSchema = useFieldSchema(); + const { getInterface } = useCollectionManager_deprecated(); + const { uiSchema: uiSchemaOrigin, defaultValue, interface: collectionInterface } = useCollectionField(); + const { isAllowToSetDefaultValue } = useIsAllowToSetDefaultValue(); + const targetInterface = getInterface(collectionInterface); + const operator = targetInterface?.filterable?.operators?.find( + (v, index) => v.value === fieldSchema['x-filter-operator'] || index === 0, + ); + const Component = useComponent( + operator?.schema?.['x-component'] || + fieldSchema['x-component-props']?.['component'] || + uiSchemaOrigin?.['x-component'] || + 'Input', + ); + const ctx = useFormBlockContext(); + const dynamicProps = useDynamicComponentProps(uiSchemaOrigin?.['x-use-component-props'], props); + // TODO: 初步适配 + useEffect(() => { + if (!uiSchemaOrigin) { + return; + } + const uiSchema = compile(uiSchemaOrigin); + setFieldProps(field, 'content', uiSchema['x-content']); + setFieldProps(field, 'title', uiSchema.title); + setFieldProps(field, 'description', uiSchema.description); + if (ctx?.form) { + const defaultVal = isAllowToSetDefaultValue() ? fieldSchema.default || defaultValue : undefined; + defaultVal !== null && defaultVal !== undefined && setFieldProps(field, 'initialValue', defaultVal); + } + + if (!field.validator && (uiSchema['x-validator'] || fieldSchema['x-validator'])) { + const concatSchema = concat([], uiSchema['x-validator'] || [], fieldSchema['x-validator'] || []); + field.validator = concatSchema; + } + if (fieldSchema['x-disabled'] === true) { + field.disabled = true; + } + if (fieldSchema['x-read-pretty'] === true) { + field.readPretty = true; + } + setRequired(field, fieldSchema, uiSchema); + // @ts-ignore + field.dataSource = uiSchema.enum; + const originalProps = + compile({ ...(operator?.schema?.['x-component-props'] || {}), ...(uiSchema['x-component-props'] || {}) }) || {}; + + field.componentProps = merge(originalProps, field.componentProps || {}, dynamicProps || {}); + }, [uiSchemaOrigin]); + + if (!uiSchemaOrigin) return null; + + return ; +}; + +export const FilterCollectionField = connect((props) => { + const fieldSchema = useFieldSchema(); + const field = useField(); + return ( + console.log(err)}> + + + + + ); +}); + +FilterCollectionField.displayName = 'FilterCollectionField'; diff --git a/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx index 41357ea3c4..8565ef3e6b 100644 --- a/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx @@ -421,7 +421,13 @@ export const filterSelectComponentFieldSettings = new SchemaSettings({ return isSelectFieldMode && !isFieldReadPretty; }, }, - getAllowMultiple({ title: 'Allow multiple selection' }), + { + ...getAllowMultiple({ title: 'Allow multiple selection' }), + useVisible() { + const field = useField(); + return field.componentProps.multiple !== false; + }, + }, { ...titleField, useVisible: useIsAssociationField, diff --git a/packages/core/client/src/schema-component/antd/select/Select.tsx b/packages/core/client/src/schema-component/antd/select/Select.tsx index 79292ce358..269beecddd 100644 --- a/packages/core/client/src/schema-component/antd/select/Select.tsx +++ b/packages/core/client/src/schema-component/antd/select/Select.tsx @@ -17,6 +17,7 @@ import React from 'react'; import { ReadPretty } from './ReadPretty'; import { FieldNames, defaultFieldNames, getCurrentOptions } from './utils'; import { BaseOptionType, DefaultOptionType } from 'antd/es/select'; +import { useCompile } from '../../'; export type SelectProps< ValueType = any, @@ -120,6 +121,7 @@ const filterOption = (input, option) => (option?.label ?? '').toLowerCase().incl const InternalSelect = connect( (props: SelectProps) => { const { objectValue, loading, value, rawOptions, defaultValue, ...others } = props; + const compile = useCompile(); let mode: any = props.multiple ? 'multiple' : props.mode; if (mode && !['multiple', 'tags'].includes(mode)) { mode = undefined; @@ -172,7 +174,7 @@ const InternalSelect = connect( ); }} - {...others} + {...compile(others)} onChange={(changed) => { props.onChange?.(changed === undefined ? null : changed); }} diff --git a/packages/core/database/src/operators/boolean.ts b/packages/core/database/src/operators/boolean.ts index 805ecf918d..b0130e5120 100644 --- a/packages/core/database/src/operators/boolean.ts +++ b/packages/core/database/src/operators/boolean.ts @@ -10,7 +10,26 @@ import { Op } from 'sequelize'; export default { - $isFalsy() { + $isFalsy(value) { + if (value === true || value === 'true') { + return { + [Op.or]: { + [Op.is]: null, + [Op.eq]: false, + }, + }; + } + return { + [Op.eq]: true, + }; + }, + + $isTruly(value) { + if (value === true || value === 'true') { + return { + [Op.eq]: true, + }; + } return { [Op.or]: { [Op.is]: null, @@ -18,10 +37,4 @@ export default { }, }; }, - - $isTruly() { - return { - [Op.eq]: true, - }; - }, } as Record; diff --git a/packages/core/test/src/e2e/templatesOfPage.ts b/packages/core/test/src/e2e/templatesOfPage.ts index 1831801496..9bcd9c1026 100644 --- a/packages/core/test/src/e2e/templatesOfPage.ts +++ b/packages/core/test/src/e2e/templatesOfPage.ts @@ -12515,7 +12515,7 @@ export const oneFilterFormBlockWithAllAssociationFieldsV1333Beta: PageConfig = { 'x-use-decorator-props': 'useFormItemProps', 'x-collection-field': 'general.oneToOneBelongsTo', 'x-component-props': { - multiple: false, + multiple: true, fieldNames: { label: 'id', value: 'id', @@ -12562,7 +12562,7 @@ export const oneFilterFormBlockWithAllAssociationFieldsV1333Beta: PageConfig = { 'x-use-decorator-props': 'useFormItemProps', 'x-collection-field': 'general.oneToOneHasOne', 'x-component-props': { - multiple: false, + multiple: true, fieldNames: { label: 'id', value: 'id', @@ -12656,7 +12656,7 @@ export const oneFilterFormBlockWithAllAssociationFieldsV1333Beta: PageConfig = { 'x-use-decorator-props': 'useFormItemProps', 'x-collection-field': 'general.manyToOne', 'x-component-props': { - multiple: false, + multiple: true, fieldNames: { label: 'id', value: 'id',