From 4efa3b7ac3ed25a016b9624823c44952ca8bda26 Mon Sep 17 00:00:00 2001 From: Jhonton Chen <504071088@qq.com> Date: Mon, 18 May 2026 17:18:22 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=84=E9=80=89=E6=8B=A9=E5=8A=A0=E4=B8=8A?= =?UTF-8?q?=E5=AF=B9=E7=BB=93=E6=9E=9C=E7=9A=84=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/locales/en_US/pages.ts | 3 + src/locales/zh_CN/pages.ts | 3 + src/pages/rules/components/GroupSelect.tsx | 112 +++++++++++++++++++-- src/services/typings.d.ts | 4 - 4 files changed, 109 insertions(+), 13 deletions(-) diff --git a/src/locales/en_US/pages.ts b/src/locales/en_US/pages.ts index 9d7998d..1cdbb8d 100644 --- a/src/locales/en_US/pages.ts +++ b/src/locales/en_US/pages.ts @@ -144,6 +144,9 @@ export default { 'pages.rules.allGroups': 'All Groups', 'pages.rules.allSymbols': 'All Symbols', 'pages.rules.allAssets': 'All Assets', + 'pages.rules.searchResults': 'Search results', + 'pages.rules.selectSearchResults': 'Select results', + 'pages.rules.deselectSearchResults': 'Deselect results', 'pages.rules.trigger': 'Triggered', 'pages.rules.triggeredCount': 'Times', 'pages.rules.disable': 'Disable', diff --git a/src/locales/zh_CN/pages.ts b/src/locales/zh_CN/pages.ts index ff555b1..103e822 100644 --- a/src/locales/zh_CN/pages.ts +++ b/src/locales/zh_CN/pages.ts @@ -142,6 +142,9 @@ export default { 'pages.rules.allGroups': '全部组', 'pages.rules.allSymbols': '全部产品', 'pages.rules.allAssets': '全部资产', + 'pages.rules.searchResults': '搜索结果', + 'pages.rules.selectSearchResults': '全选结果', + 'pages.rules.deselectSearchResults': '取消结果', 'pages.rules.trigger': '触发', 'pages.rules.triggeredCount': '次数', 'pages.rules.disable': '禁用', diff --git a/src/pages/rules/components/GroupSelect.tsx b/src/pages/rules/components/GroupSelect.tsx index 0e85ebd..dc7e364 100644 --- a/src/pages/rules/components/GroupSelect.tsx +++ b/src/pages/rules/components/GroupSelect.tsx @@ -1,9 +1,14 @@ 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 { getGroupTreeList } from '@/utils/dataCenter'; -import { generatePattern2, parsePatternToKeys2 } from '@/utils/groupUtil'; +import { + type GroupTreeNode, + generatePattern2, + parsePatternToKeys2, +} from '@/utils/groupUtil'; import { $t } from '@/utils/i18n'; interface GroupSelectProps { @@ -13,6 +18,31 @@ interface GroupSelectProps { disabled?: boolean; } +const normalizeSearchText = (value: string) => + value?.trim().toLowerCase() || ''; + +const isNodeTitleMatched = (node: GroupTreeNode, searchText: string) => + node.title.toLowerCase().includes(searchText); + +const collectSearchResultLeafKeys = ( + nodes: GroupTreeNode[], + searchText: string, + parentMatched = false, +): string[] => { + if (!searchText) return []; + + return nodes.flatMap((node) => { + const matched = parentMatched || isNodeTitleMatched(node, searchText); + const children = node.children || []; + + if (children.length === 0) { + return matched ? [node.key] : []; + } + + return collectSearchResultLeafKeys(children, searchText, matched); + }); +}; + const GroupSelect: React.FC = ({ dataSourceId, value, @@ -20,10 +50,21 @@ const GroupSelect: 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); @@ -57,20 +98,36 @@ const GroupSelect: 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 = generatePattern2(treeData, actualKeys); + const patternString = generatePattern2(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 ; @@ -84,10 +141,47 @@ const GroupSelect: React.FC = ({ listHeight: 410, treeData: treeData, treeCheckable: true, + showSearch: true, + searchValue, + onSearch: setSearchValue, + treeNodeFilterProp: 'title', 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', diff --git a/src/services/typings.d.ts b/src/services/typings.d.ts index 470f377..b4f7866 100644 --- a/src/services/typings.d.ts +++ b/src/services/typings.d.ts @@ -1,6 +1,3 @@ -// @ts-expect-error -/* eslint-disable */ - declare namespace API { // Response data format agreed with the backend interface BaseResponse { @@ -116,7 +113,6 @@ declare namespace API { fromName: string | null; mailHostConfigId: number | string | null; mailStatus: number | null; - special?: number; }; // "DataSource" Type