diff --git a/src/pages/rules/components/SymbolSelect.tsx b/src/pages/rules/components/SymbolSelect.tsx index b54543a..7dd5b36 100644 --- a/src/pages/rules/components/SymbolSelect.tsx +++ b/src/pages/rules/components/SymbolSelect.tsx @@ -1,5 +1,6 @@ import { ProFormTreeSelect } from '@ant-design/pro-components'; -import React, { useEffect, useState } from 'react'; +import { Button, Space, Typography } from 'antd'; +import React, { useEffect, useMemo, useState } from 'react'; import useUserInfo from '@/hooks/useUserInfo'; import Loading from '@/loading'; import { getSymbolTreeList } from '@/utils/dataCenter'; @@ -13,6 +14,37 @@ interface SymbolSelectProps { disabled?: boolean; } +const normalizeSearchText = (value: string) => + value?.trim().toLowerCase() || ''; + +const isNodeDefaultSearchMatched = ( + node: API.SymbolTreeNode, + searchText: string, +) => + String(node.value || '') + .toLowerCase() + .includes(searchText); + +const collectSearchResultLeafKeys = ( + nodes: API.SymbolTreeNode[], + searchText: string, + parentMatched = false, +): string[] => { + if (!searchText) return []; + + return nodes.flatMap((node) => { + const matched = + parentMatched || isNodeDefaultSearchMatched(node, searchText); + const children = node.children || []; + + if (children.length === 0) { + return matched ? [node.key] : []; + } + + return collectSearchResultLeafKeys(children, searchText, matched); + }); +}; + const SymbolSelect: React.FC = ({ dataSourceId, value, @@ -20,17 +52,29 @@ const SymbolSelect: React.FC = ({ }) => { const { userInfo } = useUserInfo(); const companyInfo = userInfo?.company; - const [treeData, setTreeData] = useState([]); + const [treeData, setTreeData] = useState([]); const [loading, setLoading] = useState(false); // Internally maintains a keys array for TreeSelect display const [checkedKeys, setCheckedKeys] = useState([]); + const [searchValue, setSearchValue] = useState(''); + + const normalizedSearchValue = useMemo( + () => normalizeSearchText(searchValue), + [searchValue], + ); + + const searchResultKeys = useMemo( + () => collectSearchResultLeafKeys(treeData, normalizedSearchValue), + [treeData, normalizedSearchValue], + ); const loadData = async (force: boolean = false) => { setLoading(true); try { const data = await getSymbolTreeList(companyInfo, dataSourceId, force); setCheckedKeys([]); + setSearchValue(''); setTreeData(data); } finally { setLoading(false); @@ -56,20 +100,36 @@ const SymbolSelect: React.FC = ({ }, [value, treeData]); // 3. When users manually check/uncheck items, convert internal keys back to string and trigger onChange - const handleTreeChange = (keys: any) => { - // Handle the object format that antd tree may return - const actualKeys = Array.isArray(keys) ? keys : keys.checked; - + const updateCheckedKeys = (keys: string[]) => { // Update internal UI state - setCheckedKeys(actualKeys); + setCheckedKeys(keys); // Generate string to pass to external Form if (onChange) { - const patternString = generatePattern(treeData, actualKeys); + const patternString = generatePattern(treeData, keys); onChange(patternString); } }; + const handleTreeChange = (keys: any) => { + // Handle the object format that antd tree may return + const actualKeys = Array.isArray(keys) ? keys : keys?.checked || []; + updateCheckedKeys(actualKeys); + }; + + const handleSelectSearchResults = () => { + updateCheckedKeys( + Array.from(new Set([...checkedKeys, ...searchResultKeys])), + ); + }; + + const handleDeselectSearchResults = () => { + const searchResultKeySet = new Set(searchResultKeys); + updateCheckedKeys( + checkedKeys.filter((checkedKey) => !searchResultKeySet.has(checkedKey)), + ); + }; + if (!dataSourceId) return null; if (loading) return ; @@ -83,10 +143,44 @@ const SymbolSelect: React.FC = ({ listHeight: 410, treeData: treeData, treeCheckable: true, + onSearch: setSearchValue, value: checkedKeys, // Use the internally parsed array onChange: handleTreeChange, maxTagCount: 'responsive', virtual: true, + popupRender: (originNode) => + normalizedSearchValue && searchResultKeys.length > 0 ? ( + <> +
event.preventDefault()} + > + + + {$t('pages.rules.searchResults')}:{' '} + {searchResultKeys.length} + +
+ + +
+
+
+ {originNode} + + ) : ( + originNode + ), fieldNames: { label: 'title', value: 'key',