mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 11:56:29 +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",
|
"key": "parent-2-item1-0",
|
||||||
"label": "item1",
|
"label": "item1",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
|
"style": undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "parent-2-item2-1",
|
"key": "parent-2-item2-1",
|
||||||
"label": "item2",
|
"label": "item2",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
|
"style": undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"associationField": "a.b",
|
"associationField": "a.b",
|
||||||
@ -139,6 +141,7 @@ describe('useGetSchemaInitializerMenuItems', () => {
|
|||||||
"key": "group-0-Item 1-0",
|
"key": "group-0-Item 1-0",
|
||||||
"label": "Item 1",
|
"label": "Item 1",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
|
"style": undefined,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"key": "group-0",
|
"key": "group-0",
|
||||||
@ -151,6 +154,7 @@ describe('useGetSchemaInitializerMenuItems', () => {
|
|||||||
"key": "parent-item-group-1-Item 1-0",
|
"key": "parent-item-group-1-Item 1-0",
|
||||||
"label": "Item 1",
|
"label": "Item 1",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
|
"style": undefined,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"key": "parent-item-group-1",
|
"key": "parent-item-group-1",
|
||||||
@ -204,6 +208,7 @@ describe('useGetSchemaInitializerMenuItems', () => {
|
|||||||
"key": "submenu-1-SubItem 1-0",
|
"key": "submenu-1-SubItem 1-0",
|
||||||
"label": "SubItem 1",
|
"label": "SubItem 1",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
|
"style": undefined,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"key": "submenu-1",
|
"key": "submenu-1",
|
||||||
@ -215,6 +220,7 @@ describe('useGetSchemaInitializerMenuItems', () => {
|
|||||||
"key": "submenu-2-SubItem 1-0",
|
"key": "submenu-2-SubItem 1-0",
|
||||||
"label": "SubItem 1",
|
"label": "SubItem 1",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
|
"style": undefined,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"key": "submenu-2",
|
"key": "submenu-2",
|
||||||
@ -226,6 +232,7 @@ describe('useGetSchemaInitializerMenuItems', () => {
|
|||||||
"key": "submenu-3-SubItem 1-0",
|
"key": "submenu-3-SubItem 1-0",
|
||||||
"label": "SubItem 1",
|
"label": "SubItem 1",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
|
"style": undefined,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"key": "submenu-3",
|
"key": "submenu-3",
|
||||||
@ -289,11 +296,13 @@ describe('useSchemaInitializerMenuItems', () => {
|
|||||||
"key": 1,
|
"key": 1,
|
||||||
"label": "item1",
|
"label": "item1",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
|
"style": undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": 2,
|
"key": 2,
|
||||||
"label": "item2",
|
"label": "item2",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
|
"style": undefined,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
|
@ -15,7 +15,7 @@ import { SchemaInitializerOptions } from '../types';
|
|||||||
import { SchemaInitializerChildren } from './SchemaInitializerChildren';
|
import { SchemaInitializerChildren } from './SchemaInitializerChildren';
|
||||||
import { SchemaInitializerDivider } from './SchemaInitializerDivider';
|
import { SchemaInitializerDivider } from './SchemaInitializerDivider';
|
||||||
import { useSchemaInitializerStyles } from './style';
|
import { useSchemaInitializerStyles } from './style';
|
||||||
|
import { useMenuSearch } from './SchemaInitializerItemSearchFields';
|
||||||
export interface SchemaInitializerItemGroupProps {
|
export interface SchemaInitializerItemGroupProps {
|
||||||
title: string;
|
title: string;
|
||||||
children?: SchemaInitializerOptions['items'];
|
children?: SchemaInitializerOptions['items'];
|
||||||
@ -45,6 +45,8 @@ export const SchemaInitializerItemGroup: FC<SchemaInitializerItemGroupProps> = (
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export const SchemaInitializerItemGroupInternal = () => {
|
export const SchemaInitializerItemGroupInternal = () => {
|
||||||
const itemConfig = useSchemaInitializerItem<SchemaInitializerItemGroupProps>();
|
const itemConfig: any = useSchemaInitializerItem<SchemaInitializerItemGroupProps>();
|
||||||
return <SchemaInitializerItemGroup {...itemConfig} />;
|
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,
|
onClick: handleClick,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
style: item.style,
|
||||||
key,
|
key,
|
||||||
label,
|
label,
|
||||||
onClick: handleClick,
|
onClick: handleClick,
|
||||||
|
@ -547,7 +547,7 @@ export class PluginACLServer extends Plugin {
|
|||||||
|
|
||||||
const hasFilterByTk = (params) => {
|
const hasFilterByTk = (params) => {
|
||||||
return JSON.stringify(params).includes('filterByTk');
|
return JSON.stringify(params).includes('filterByTk');
|
||||||
}
|
};
|
||||||
|
|
||||||
if (!hasFilterByTk(ctx.permission.mergedParams) || !hasFilterByTk(ctx.permission.rawParams)) {
|
if (!hasFilterByTk(ctx.permission.mergedParams) || !hasFilterByTk(ctx.permission.rawParams)) {
|
||||||
await next();
|
await next();
|
||||||
@ -574,7 +574,6 @@ export class PluginACLServer extends Plugin {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const withACLMeta = createWithACLMetaMiddleware();
|
const withACLMeta = createWithACLMetaMiddleware();
|
||||||
|
|
||||||
// append allowedActions to list & get response
|
// append allowedActions to list & get response
|
||||||
|
Loading…
Reference in New Issue
Block a user