Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(tree): 优化Tree搜索功能,添加搜索高亮功能,优化样式表现 #1153

Merged
merged 1 commit into from
Sep 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 64 additions & 22 deletions src/components/Tree/src/Tree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -60,6 +60,7 @@

const searchState = reactive({
startSearch: false,
searchText: '',
searchData: [] as TreeItem[],
});

Expand Down Expand Up @@ -199,43 +200,69 @@
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) => {
const result = filterFn
? 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);
}
}

Expand All @@ -255,7 +282,6 @@

watchEffect(() => {
treeDataRef.value = props.treeData as TreeItem[];
handleSearch(unref(searchText));
});

onMounted(() => {
Expand Down Expand Up @@ -328,7 +354,7 @@
handleSearch(value);
},
getSearchValue: () => {
return searchText.value;
return searchState.searchText;
},
};

Expand Down Expand Up @@ -359,6 +385,8 @@
if (!data) {
return null;
}
const searchText = searchState.searchText;
const { highlight } = unref(props);
return data.map((item) => {
const {
title: titleField,
Expand All @@ -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 ? (
<span class={unref(getBindValues)?.blockNode ? `${prefixCls}__content` : ''}>
<span>{title.substr(0, searchIdx)}</span>
<span style={highlightStyle}>{searchText}</span>
<span>{title.substr(searchIdx + searchText.length)}</span>
</span>
) : (
title
);

return (
<Tree.TreeNode {...propsData} node={toRaw(item)} key={get(item, keyField)}>
{{
Expand All @@ -382,11 +427,8 @@
) : (
<>
{icon && <TreeIcon icon={icon} />}
<span
class={unref(getBindValues)?.blockNode ? `${prefixCls}__content` : ''}
>
{get(item, titleField)}
</span>
{titleDom}
{/*{get(item, titleField)}*/}
<span class={`${prefixCls}__actions`}>
{renderAction({ ...item, level })}
</span>
Expand Down Expand Up @@ -417,7 +459,7 @@
helpMessage={helpMessage}
onStrictlyChange={onStrictlyChange}
onSearch={handleSearch}
searchText={unref(searchText)}
searchText={searchState.searchText}
>
{extendSlots(slots)}
</TreeHeader>
Expand Down
25 changes: 20 additions & 5 deletions src/components/Tree/src/TreeHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
{{ title }}
</BasicTitle>

<div class="flex flex-1 justify-end items-center cursor-pointer" v-if="search || toolbar">
<div class="mr-1 w-2/3" v-if="search">
<div
class="flex flex-1 justify-self-stretch items-center cursor-pointer"
v-if="search || toolbar"
>
<div :class="getInputSearchCls" v-if="search">
<InputSearch
:placeholder="t('common.searchText')"
size="small"
Expand All @@ -31,7 +34,7 @@
</div>
</template>
<script lang="ts">
import type { PropType } from 'vue';
import { PropType } from 'vue';
import { defineComponent, computed, ref, watch } from 'vue';

import { Dropdown, Menu, Input } from 'ant-design-vue';
Expand Down Expand Up @@ -80,10 +83,22 @@
searchText: propTypes.string,
},
emits: ['strictly-change', 'search'],
setup(props, { emit }) {
setup(props, { emit, slots }) {
const { t } = useI18n();
const searchValue = ref('');

const getInputSearchCls = computed(() => {
const titleExists = slots.headerTitle || props.title;
return [
'mr-1',
'w-full',
// titleExists ? 'w-2/3' : 'w-full',
{
['ml-5']: titleExists,
},
];
});

const toolbarList = computed(() => {
const { checkable } = props;
const defaultToolbarList = [
Expand Down Expand Up @@ -157,7 +172,7 @@
// debounceEmitChange(e.target.value);
// }

return { t, toolbarList, handleMenuClick, searchValue };
return { t, toolbarList, handleMenuClick, searchValue, getInputSearchCls };
},
});
</script>
Expand Down
7 changes: 7 additions & 0 deletions src/components/Tree/src/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,17 @@ export const basicProps = {
>,
default: null,
},
// 高亮搜索值,仅高亮具体匹配值(通过title)值为true时使用默认色值,值为#xxx时使用此值替代且高亮开启
highlight: {
type: [Boolean, String] as PropType<Boolean | String>,
default: false,
},
// 搜索完成时自动展开结果
expandOnSearch: propTypes.bool.def(false),
// 搜索完成自动选中所有结果,当且仅当 checkable===true 时生效
checkOnSearch: propTypes.bool.def(false),
// 搜索完成自动select所有结果
selectedOnSearch: propTypes.bool.def(false),
};

export const treeNodeProps = {
Expand Down