diff --git a/packages/core/client/src/application/__tests__/schema-initializer/hooks/useGetSchemaInitializerMenuItems.test.tsx b/packages/core/client/src/application/__tests__/schema-initializer/hooks/useGetSchemaInitializerMenuItems.test.tsx index 57a9e9d496..7ead4902f6 100644 --- a/packages/core/client/src/application/__tests__/schema-initializer/hooks/useGetSchemaInitializerMenuItems.test.tsx +++ b/packages/core/client/src/application/__tests__/schema-initializer/hooks/useGetSchemaInitializerMenuItems.test.tsx @@ -71,11 +71,13 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "parent-2-item1-0", "label": "item1", "onClick": [Function], + "style": undefined, }, { "key": "parent-2-item2-1", "label": "item2", "onClick": [Function], + "style": undefined, }, { "associationField": "a.b", @@ -139,6 +141,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "group-0-Item 1-0", "label": "Item 1", "onClick": [Function], + "style": undefined, }, ], "key": "group-0", @@ -151,6 +154,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "parent-item-group-1-Item 1-0", "label": "Item 1", "onClick": [Function], + "style": undefined, }, ], "key": "parent-item-group-1", @@ -204,6 +208,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "submenu-1-SubItem 1-0", "label": "SubItem 1", "onClick": [Function], + "style": undefined, }, ], "key": "submenu-1", @@ -215,6 +220,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "submenu-2-SubItem 1-0", "label": "SubItem 1", "onClick": [Function], + "style": undefined, }, ], "key": "submenu-2", @@ -226,6 +232,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "submenu-3-SubItem 1-0", "label": "SubItem 1", "onClick": [Function], + "style": undefined, }, ], "key": "submenu-3", @@ -289,11 +296,13 @@ describe('useSchemaInitializerMenuItems', () => { "key": 1, "label": "item1", "onClick": [Function], + "style": undefined, }, { "key": 2, "label": "item2", "onClick": [Function], + "style": undefined, }, ] `); diff --git a/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemGroup.tsx b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemGroup.tsx index 9181354869..2163255598 100644 --- a/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemGroup.tsx +++ b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemGroup.tsx @@ -15,7 +15,7 @@ import { SchemaInitializerOptions } from '../types'; import { SchemaInitializerChildren } from './SchemaInitializerChildren'; import { SchemaInitializerDivider } from './SchemaInitializerDivider'; import { useSchemaInitializerStyles } from './style'; - +import { useMenuSearch } from './SchemaInitializerItemSearchFields'; export interface SchemaInitializerItemGroupProps { title: string; children?: SchemaInitializerOptions['items']; @@ -45,6 +45,8 @@ export const SchemaInitializerItemGroup: FC = ( * @internal */ export const SchemaInitializerItemGroupInternal = () => { - const itemConfig = useSchemaInitializerItem(); - return ; + const itemConfig: any = useSchemaInitializerItem(); + const searchedChildren = useMenuSearch(itemConfig); + /* eslint-disable react/no-children-prop */ + return ; }; diff --git a/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemSearchFields.tsx b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemSearchFields.tsx new file mode 100644 index 0000000000..3f8866461e --- /dev/null +++ b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemSearchFields.tsx @@ -0,0 +1,180 @@ +/** + * 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 { uid } from '@formily/shared'; +import { Divider, Empty, Input, MenuProps } from 'antd'; +import React, { useEffect, useMemo, useState, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; + +function getPrefixAndCompare(a, b) { + const prefixA = a.replace(/-displayCollectionFields$/, ''); + const prefixB = b.replace(/-displayCollectionFields$/, ''); + + // 判断 a 是否包含 b,如果包含则返回 false,否则返回 true + return !prefixA.includes(prefixB); +} + +export const SearchFields = ({ value: outValue, onChange, name }) => { + const { t } = useTranslation(); + const [value, setValue] = useState(outValue); + const inputRef = useRef(''); + + // 生成唯一的ID用于区分不同层级的SearchFields + const uniqueId = useRef(`${name || Math.random().toString(10).substr(2, 9)}`); + + useEffect(() => { + setValue(outValue); + }, [outValue]); + + useEffect(() => { + const focusInput = () => { + if ( + document.activeElement?.id !== inputRef.current.input.id && + getPrefixAndCompare(document.activeElement?.id, inputRef.current.input.id) + ) { + inputRef.current?.focus(); + } + }; + + // 观察当前元素是否在视图中 + const observer = new IntersectionObserver((entries) => { + if (entries.some((v) => v.isIntersecting)) { + focusInput(); + } + }); + if (inputRef.current?.input) { + inputRef.current.input.id = uniqueId.current; // 设置唯一ID + observer.observe(inputRef.current.input); + } + + return () => { + observer.disconnect(); + }; + }, []); + + const compositionRef = useRef(false); + + const handleChange = (e: React.ChangeEvent) => { + if (!compositionRef.current) { + onChange(e.target.value); + setValue(e.target.value); + } + }; + const Composition = (e: React.CompositionEvent | any) => { + if (e.type === 'compositionend') { + compositionRef.current = false; + handleChange(e); + } else { + compositionRef.current = true; + } + }; + return ( +
e.stopPropagation()}> + { + e.stopPropagation(); + }} + onChange={handleChange} + onCompositionStart={Composition} + onCompositionEnd={Composition} + onCompositionUpdate={Composition} + /> + +
+ ); +}; + +export const useMenuSearch = (props: { children: any[]; showType?: boolean; hideSearch?: boolean; name?: string }) => { + const { children, showType, hideSearch, name } = props; + const items = children?.concat?.() || []; + const [searchValue, setSearchValue] = useState(null); + + // 处理搜索逻辑 + const limitedSearchedItems = useMemo(() => { + if (!searchValue || searchValue === '') { + return items; + } + const lowerSearchValue = searchValue.toLocaleLowerCase(); + return items.filter( + (item) => + (item.label || item.title) && + String(item.label || item.title) + .toLocaleLowerCase() + .includes(lowerSearchValue), + ); + }, [searchValue, items]); + + // 最终结果项 + const resultItems = useMemo(() => { + const res = []; + if (!hideSearch && (items.length > 10 || searchValue)) { + res.push({ + key: `search-${uid()}`, + Component: () => ( + { + setSearchValue(val); + }} + /> + ), + onClick({ domEvent }) { + domEvent.stopPropagation(); + }, + ...(showType ? { isMenuType: true } : {}), + }); + } + + if (limitedSearchedItems.length > 0) { + res.push(...limitedSearchedItems); + } else { + res.push({ + key: 'empty', + style: { + height: 150, + }, + Component: () => ( +
e.stopPropagation()}> + +
+ ), + ...(showType ? { isMenuType: true } : {}), + }); + } + + return res; + }, [hideSearch, limitedSearchedItems, searchValue, showType]); + + const result = processedResult(resultItems, showType, hideSearch, name); + + return children ? result : undefined; +}; + +// 处理嵌套子菜单 +const processedResult = (resultItems, showType, hideSearch, name) => { + return resultItems.map((item: any) => { + if (['subMenu', 'itemGroup'].includes(item.type)) { + const childItems = useMenuSearch({ + children: item.children, + showType, + hideSearch, + name: item.name, + }); + return { ...item, children: childItems }; + } + return item; + }); +}; diff --git a/packages/core/client/src/application/schema-initializer/hooks/useGetSchemaInitializerMenuItems.tsx b/packages/core/client/src/application/schema-initializer/hooks/useGetSchemaInitializerMenuItems.tsx index 10573983f5..6d374ba82d 100644 --- a/packages/core/client/src/application/schema-initializer/hooks/useGetSchemaInitializerMenuItems.tsx +++ b/packages/core/client/src/application/schema-initializer/hooks/useGetSchemaInitializerMenuItems.tsx @@ -101,6 +101,7 @@ export function useGetSchemaInitializerMenuItems(onClick?: (args: any) => void) onClick: handleClick, } : { + style: item.style, key, label, onClick: handleClick, diff --git a/packages/plugins/@nocobase/plugin-acl/src/server/server.ts b/packages/plugins/@nocobase/plugin-acl/src/server/server.ts index 3b4fd98414..151b89ab83 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/server/server.ts @@ -547,7 +547,7 @@ export class PluginACLServer extends Plugin { const hasFilterByTk = (params) => { return JSON.stringify(params).includes('filterByTk'); - } + }; if (!hasFilterByTk(ctx.permission.mergedParams) || !hasFilterByTk(ctx.permission.rawParams)) { await next(); @@ -574,7 +574,6 @@ export class PluginACLServer extends Plugin { }, ); - const withACLMeta = createWithACLMetaMiddleware(); // append allowedActions to list & get response