mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 06:15:11 +00:00
feat: configure fields support serach and filter fields (#5471)
* feat: configure fields support serach and filter fields * feat: configure fields support serach and filter fields * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: test * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug
This commit is contained in:
parent
5685b545fe
commit
c639e7d403
@ -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,
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
@ -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<SchemaInitializerItemGroupProps> = (
|
||||
* @internal
|
||||
*/
|
||||
export const SchemaInitializerItemGroupInternal = () => {
|
||||
const itemConfig = useSchemaInitializerItem<SchemaInitializerItemGroupProps>();
|
||||
return <SchemaInitializerItemGroup {...itemConfig} />;
|
||||
const itemConfig: any = useSchemaInitializerItem<SchemaInitializerItemGroupProps>();
|
||||
const searchedChildren = useMenuSearch(itemConfig);
|
||||
/* eslint-disable react/no-children-prop */
|
||||
return <SchemaInitializerItemGroup {...itemConfig} children={searchedChildren} />;
|
||||
};
|
||||
|
@ -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<string>(outValue);
|
||||
const inputRef = useRef<any>('');
|
||||
|
||||
// 生成唯一的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<boolean>(false);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!compositionRef.current) {
|
||||
onChange(e.target.value);
|
||||
setValue(e.target.value);
|
||||
}
|
||||
};
|
||||
const Composition = (e: React.CompositionEvent<HTMLInputElement> | any) => {
|
||||
if (e.type === 'compositionend') {
|
||||
compositionRef.current = false;
|
||||
handleChange(e);
|
||||
} else {
|
||||
compositionRef.current = true;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
allowClear
|
||||
style={{ padding: '0 4px 6px 16px', boxShadow: 'none' }}
|
||||
bordered={false}
|
||||
placeholder={t('Search')}
|
||||
defaultValue={value}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onChange={handleChange}
|
||||
onCompositionStart={Composition}
|
||||
onCompositionEnd={Composition}
|
||||
onCompositionUpdate={Composition}
|
||||
/>
|
||||
<Divider style={{ margin: 0 }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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<MenuProps['items']>(() => {
|
||||
const res = [];
|
||||
if (!hideSearch && (items.length > 10 || searchValue)) {
|
||||
res.push({
|
||||
key: `search-${uid()}`,
|
||||
Component: () => (
|
||||
<SearchFields
|
||||
name={name}
|
||||
value={searchValue}
|
||||
onChange={(val: string) => {
|
||||
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: () => (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
</div>
|
||||
),
|
||||
...(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;
|
||||
});
|
||||
};
|
@ -101,6 +101,7 @@ export function useGetSchemaInitializerMenuItems(onClick?: (args: any) => void)
|
||||
onClick: handleClick,
|
||||
}
|
||||
: {
|
||||
style: item.style,
|
||||
key,
|
||||
label,
|
||||
onClick: handleClick,
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user