diff --git a/src/components/Tree/src/Tree.vue b/src/components/Tree/src/Tree.vue index d026754e2ec..752aff74cdd 100644 --- a/src/components/Tree/src/Tree.vue +++ b/src/components/Tree/src/Tree.vue @@ -19,9 +19,9 @@ import { ScrollContainer } from '/@/components/Container'; import { omit, get, difference } from 'lodash-es'; - import { isArray, isBoolean, isFunction } from '/@/utils/is'; + import { isArray, isBoolean, isEmpty, isFunction } from '/@/utils/is'; import { extendSlots, getSlot } from '/@/utils/helper/tsxHelper'; - import { filter } from '/@/utils/helper/treeHelper'; + import { filter, treeToList } from '/@/utils/helper/treeHelper'; import { useTree } from './useTree'; import { useContextMenu } from '/@/hooks/web/useContextMenu'; @@ -60,6 +60,7 @@ const searchState = reactive({ startSearch: false, + searchText: '', searchData: [] as TreeItem[], }); @@ -199,23 +200,40 @@ state.checkStrictly = strictly; } - const searchText = ref(''); - watchEffect(() => { - if (props.searchValue !== searchText.value) searchText.value = props.searchValue; - }); + watch( + () => props.searchValue, + (val) => { + if (val !== searchState.searchText) { + searchState.searchText = val; + } + }, + { + immediate: true, + }, + ); + + watch( + () => props.treeData, + (val) => { + if (val) { + handleSearch(searchState.searchText); + } + }, + ); function handleSearch(searchValue: string) { - if (searchValue !== searchText.value) searchText.value = searchValue; + if (searchValue !== searchState.searchText) searchState.searchText = searchValue; emit('update:searchValue', searchValue); if (!searchValue) { searchState.startSearch = false; return; } - const { filterFn, checkable, expandOnSearch, checkOnSearch } = unref(props); + const { filterFn, checkable, expandOnSearch, checkOnSearch, selectedOnSearch } = + unref(props); searchState.startSearch = true; const { title: titleField, key: keyField } = unref(getReplaceFields); - const searchKeys: string[] = []; + const matchedKeys: string[] = []; searchState.searchData = filter( unref(treeDataRef), (node) => { @@ -223,19 +241,28 @@ ? filterFn(searchValue, node, unref(getReplaceFields)) : node[titleField]?.includes(searchValue) ?? false; if (result) { - searchKeys.push(node[keyField]); + matchedKeys.push(node[keyField]); } return result; }, unref(getReplaceFields), ); - if (expandOnSearch && searchKeys.length > 0) { - setExpandedKeys(searchKeys); + if (expandOnSearch) { + const expandKeys = treeToList(searchState.searchData).map((val) => { + return val[keyField]; + }); + if (expandKeys && expandKeys.length) { + setExpandedKeys(expandKeys); + } + } + + if (checkOnSearch && checkable && matchedKeys.length) { + setCheckedKeys(matchedKeys); } - if (checkOnSearch && checkable && searchKeys.length > 0) { - setCheckedKeys(searchKeys); + if (selectedOnSearch && matchedKeys.length) { + setSelectedKeys(matchedKeys); } } @@ -255,7 +282,6 @@ watchEffect(() => { treeDataRef.value = props.treeData as TreeItem[]; - handleSearch(unref(searchText)); }); onMounted(() => { @@ -328,7 +354,7 @@ handleSearch(value); }, getSearchValue: () => { - return searchText.value; + return searchState.searchText; }, }; @@ -359,6 +385,8 @@ if (!data) { return null; } + const searchText = searchState.searchText; + const { highlight } = unref(props); return data.map((item) => { const { title: titleField, @@ -369,6 +397,23 @@ const propsData = omit(item, 'title'); const icon = getIcon({ ...item, level }, item.icon); const children = get(item, childrenField) || []; + const title = get(item, titleField); + + const searchIdx = title.indexOf(searchText); + const isHighlight = + searchState.startSearch && !isEmpty(searchText) && highlight && searchIdx !== -1; + const highlightStyle = `color: ${isBoolean(highlight) ? '#f50' : highlight}`; + + const titleDom = isHighlight ? ( + + {title.substr(0, searchIdx)} + {searchText} + {title.substr(searchIdx + searchText.length)} + + ) : ( + title + ); + return ( {{ @@ -382,11 +427,8 @@ ) : ( <> {icon && } - - {get(item, titleField)} - + {titleDom} + {/*{get(item, titleField)}*/} {renderAction({ ...item, level })} @@ -417,7 +459,7 @@ helpMessage={helpMessage} onStrictlyChange={onStrictlyChange} onSearch={handleSearch} - searchText={unref(searchText)} + searchText={searchState.searchText} > {extendSlots(slots)} diff --git a/src/components/Tree/src/TreeHeader.vue b/src/components/Tree/src/TreeHeader.vue index 0eed80941d7..57739d50e6f 100644 --- a/src/components/Tree/src/TreeHeader.vue +++ b/src/components/Tree/src/TreeHeader.vue @@ -5,8 +5,11 @@ {{ title }} -
-
+
+
diff --git a/src/components/Tree/src/props.ts b/src/components/Tree/src/props.ts index 287d2f95816..faa5c03f788 100644 --- a/src/components/Tree/src/props.ts +++ b/src/components/Tree/src/props.ts @@ -80,10 +80,17 @@ export const basicProps = { >, default: null, }, + // 高亮搜索值,仅高亮具体匹配值(通过title)值为true时使用默认色值,值为#xxx时使用此值替代且高亮开启 + highlight: { + type: [Boolean, String] as PropType, + default: false, + }, // 搜索完成时自动展开结果 expandOnSearch: propTypes.bool.def(false), // 搜索完成自动选中所有结果,当且仅当 checkable===true 时生效 checkOnSearch: propTypes.bool.def(false), + // 搜索完成自动select所有结果 + selectedOnSearch: propTypes.bool.def(false), }; export const treeNodeProps = {