From 4d709fa9e917fbfe9412bce12271503fee5aed77 Mon Sep 17 00:00:00 2001 From: Zeke Zhang <958414905@qq.com> Date: Sat, 2 Nov 2024 12:15:45 +0800 Subject: [PATCH] fix(filterBlocks): make e2e tests pass --- .../client/src/block-provider/hooks/index.ts | 6 +++- .../data-source/DataSourceManagerProvider.tsx | 22 ++++++++++++- .../src/filter-provider/FilterProvider.tsx | 4 +-- .../core/client/src/filter-provider/utils.ts | 17 ++-------- .../src/schema-settings/SchemaSettings.tsx | 32 +++++++++++++++---- .../SchemaSettingsConnectDataBlocks.tsx | 4 +-- 6 files changed, 58 insertions(+), 27 deletions(-) diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index 3a59c261cf..9ae800c4de 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -497,7 +497,11 @@ const useDoFilter = () => { // 这里的代码是为了实现:筛选表单的筛选操作在首次渲染时自动执行一次 useEffect(() => { - doFilter({ doNothingWhenFilterIsEmpty: true }); + // 使用 setTimeout 是为了等待筛选表单的变量解析完成,否则会因为获取的 filter 为空而导致筛选表单的筛选操作不执行。 + // 另外,如果不加 100 毫秒的延迟,会导致数据区块列表更新后,不触发筛选操作的问题。 + setTimeout(() => { + doFilter({ doNothingWhenFilterIsEmpty: true }); + }, 100); }, [getDataBlocks().length]); return { diff --git a/packages/core/client/src/data-source/data-source/DataSourceManagerProvider.tsx b/packages/core/client/src/data-source/data-source/DataSourceManagerProvider.tsx index 4b09ee5eb5..547c038e0e 100644 --- a/packages/core/client/src/data-source/data-source/DataSourceManagerProvider.tsx +++ b/packages/core/client/src/data-source/data-source/DataSourceManagerProvider.tsx @@ -7,7 +7,8 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import React, { FC, ReactNode, createContext, useContext } from 'react'; +import React, { FC, ReactNode, createContext, useCallback, useContext } from 'react'; +import { InheritanceCollectionMixin } from '../../collection-manager/mixins/InheritanceCollectionMixin'; import type { DataSourceManager } from './DataSourceManager'; export const DataSourceManagerContext = createContext(null); @@ -26,3 +27,22 @@ export function useDataSourceManager() { const context = useContext(DataSourceManagerContext); return context; } + +/** + * 获取当前 collection 继承链路上的所有 collection + * @returns + */ +export function useAllCollectionsInheritChainGetter() { + const dm = useDataSourceManager(); + const getAllCollectionsInheritChain = useCallback( + (collectionName: string, customDataSource?: string) => { + return dm + ?.getDataSource(customDataSource) + ?.collectionManager?.getCollection(collectionName) + ?.getAllCollectionsInheritChain(); + }, + [dm], + ); + + return { getAllCollectionsInheritChain }; +} diff --git a/packages/core/client/src/filter-provider/FilterProvider.tsx b/packages/core/client/src/filter-provider/FilterProvider.tsx index 955e6ee99d..d2647c14a7 100644 --- a/packages/core/client/src/filter-provider/FilterProvider.tsx +++ b/packages/core/client/src/filter-provider/FilterProvider.tsx @@ -18,7 +18,7 @@ import { removeNullCondition } from '../schema-component'; import { mergeFilter, useAssociatedFields } from './utils'; // @ts-ignore -import React, { createContext, useCallback, useEffect, useMemo, useRef } from 'react'; +import React, { createContext, useCallback, useLayoutEffect, useMemo, useRef } from 'react'; enum FILTER_OPERATOR { AND = '$and', @@ -177,7 +177,7 @@ export const DataBlockCollector = ({ getDataBlockRequest, ]); - useEffect(() => { + useLayoutEffect(() => { if (shouldApplyFilter) addBlockToDataBlocks(); }, [addBlockToDataBlocks, shouldApplyFilter]); diff --git a/packages/core/client/src/filter-provider/utils.ts b/packages/core/client/src/filter-provider/utils.ts index 9af9c7dead..028104540f 100644 --- a/packages/core/client/src/filter-provider/utils.ts +++ b/packages/core/client/src/filter-provider/utils.ts @@ -12,10 +12,10 @@ import { flatten, getValuesByPath } from '@nocobase/utils/client'; import _ from 'lodash'; import { useCallback, useEffect, useState } from 'react'; import { FilterTarget, findFilterTargets } from '../block-provider/hooks'; -import { CollectionFieldOptions_deprecated, FieldOptions, InheritanceCollectionMixin } from '../collection-manager'; +import { CollectionFieldOptions_deprecated, FieldOptions } from '../collection-manager'; import { Collection } from '../data-source/collection/Collection'; import { useCollection } from '../data-source/collection/CollectionProvider'; -import { useDataSourceManager } from '../data-source/data-source/DataSourceManagerProvider'; +import { useAllCollectionsInheritChainGetter } from '../data-source/data-source/DataSourceManagerProvider'; import { removeNullCondition } from '../schema-component'; import { DataBlock, useFilterBlock } from './FilterProvider'; @@ -61,21 +61,10 @@ export const getSupportFieldsByForeignKey = (filterBlockCollection: Collection, * @returns */ export const useSupportedBlocks = (filterBlockType: FilterBlockType) => { - const dm = useDataSourceManager(); const { getDataBlocks } = useFilterBlock(); const fieldSchema = useFieldSchema(); const collection = useCollection(); - - // 获取当前 collection 继承链路上的所有 collection - const getAllCollectionsInheritChain = useCallback( - (collectionName: string, customDataSource?: string) => { - return dm - ?.getDataSource(customDataSource) - ?.collectionManager?.getCollection(collectionName) - ?.getAllCollectionsInheritChain(); - }, - [dm], - ); + const { getAllCollectionsInheritChain } = useAllCollectionsInheritChainGetter(); // Form 和 Collapse 仅支持同表的数据区块 if (filterBlockType === FilterBlockType.FORM || filterBlockType === FilterBlockType.COLLAPSE) { diff --git a/packages/core/client/src/schema-settings/SchemaSettings.tsx b/packages/core/client/src/schema-settings/SchemaSettings.tsx index bdca426013..2304da8b31 100644 --- a/packages/core/client/src/schema-settings/SchemaSettings.tsx +++ b/packages/core/client/src/schema-settings/SchemaSettings.tsx @@ -33,12 +33,12 @@ import React, { FC, ReactNode, createContext, + // @ts-ignore + startTransition, useCallback, useContext, useEffect, useMemo, - // @ts-ignore - useTransition as useReactTransition, useState, } from 'react'; import { createPortal } from 'react-dom'; @@ -157,22 +157,40 @@ export const SchemaSettingsDropdown: React.FC = React.memo( const { title, dn, ...others } = props; const [visible, setVisible] = useState(false); const { Component, getMenuItems } = useMenuItem(); - const [, startTransition] = useReactTransition(); const dropdownMaxHeight = useNiceDropdownMaxHeight([visible]); + const [openDropdown, setOpenDropdown] = useState(false); - const changeMenu: DropdownProps['onOpenChange'] = useCallback((nextOpen: boolean, info) => { + const setDropdownVisible = (visible: boolean) => { + setVisible(visible); + + // 延迟 300ms 是为了避免对动画造成影响 + setTimeout(() => { + setOpenDropdown(visible); + }, 300); + }; + + const changeMenu: DropdownProps['onOpenChange'] = (nextOpen: boolean, info) => { if (info.source === 'trigger' || nextOpen) { // 当鼠标快速滑过时,终止菜单的渲染,防止卡顿 startTransition(() => { - setVisible(nextOpen); + setDropdownVisible(nextOpen); }); } - }, []); + }; + + // 从这里截断,可以保证每次显示时都是最新的菜单列表 + if (!openDropdown) { + return ( +
setOpenDropdown(true)} data-testid={props['data-testid']}> + {typeof title === 'string' ? {title} : title} +
+ ); + } const items = getMenuItems(() => props.children); return ( - +