diff --git a/src/components/Application/src/search/AppSearchModal.vue b/src/components/Application/src/search/AppSearchModal.vue
index 76cc0c31..f713a47f 100644
--- a/src/components/Application/src/search/AppSearchModal.vue
+++ b/src/components/Application/src/search/AppSearchModal.vue
@@ -43,7 +43,14 @@
- {{ item.name }}
+
+
+ {{ each.char }}
+
@@ -254,6 +261,13 @@
&-text {
flex: 1;
+
+ // 搜索结果包含的字符着色
+ & > span {
+ &.highlight {
+ color: lighten(@primary-color, 20%);
+ }
+ }
}
&-enter {
diff --git a/src/components/Application/src/search/useMenuSearch.ts b/src/components/Application/src/search/useMenuSearch.ts
index 299df271..b9875a8e 100644
--- a/src/components/Application/src/search/useMenuSearch.ts
+++ b/src/components/Application/src/search/useMenuSearch.ts
@@ -13,6 +13,8 @@ export interface SearchResult {
name: string;
path: string;
icon?: string;
+ // 搜索结果包含的字符着色
+ chars: { char: string; highlight: boolean }[];
}
// Translate special characters
@@ -68,11 +70,85 @@ export function useMenuSearch(refs: Ref, scrollWrap: Ref, emit: A
const { name, path, icon, children, hideMenu, meta } = item;
if (
!hideMenu &&
- reg.test(name?.toLowerCase()) &&
+ reg.test(name?.toLowerCase() ?? '') &&
(!children?.length || meta?.hideChildrenInMenu)
) {
+ const chars: { char: string; highlight: boolean }[] = [];
+
+ // 显示字符串
+ const label = (parent?.name ? `${parent.name} > ${name}` : name) ?? '';
+ const labelChars = label.split('');
+ let labelPointer = 0;
+
+ const keywordChars = keyword.value.split('');
+ const keywordLength = keywordChars.length;
+ let keywordPointer = 0;
+
+ // 用于查找完整关键词的匹配
+ let includePointer = 0;
+
+ // 优先查找完整关键词的匹配
+ if (label.toLowerCase().includes(keyword.value.toLowerCase())) {
+ while (includePointer < labelChars.length) {
+ if (
+ label.toLowerCase().slice(includePointer, includePointer + keywordLength) ===
+ keyword.value.toLowerCase()
+ ) {
+ chars.push(
+ ...label
+ .substring(labelPointer, includePointer)
+ .split('')
+ .map((v) => ({
+ char: v,
+ highlight: false,
+ })),
+ );
+ chars.push(
+ ...label
+ .slice(includePointer, includePointer + keywordLength)
+ .split('')
+ .map((v) => ({
+ char: v,
+ highlight: true,
+ })),
+ );
+ includePointer += keywordLength;
+ labelPointer = includePointer;
+ } else {
+ includePointer++;
+ }
+ }
+ }
+
+ // 查找满足关键词顺序的匹配
+ while (labelPointer < labelChars.length) {
+ keywordPointer = 0;
+ while (keywordPointer < keywordChars.length) {
+ if (keywordChars[keywordPointer] !== void 0 && labelChars[labelPointer] !== void 0) {
+ if (
+ keywordChars[keywordPointer].toLowerCase() ===
+ labelChars[labelPointer].toLowerCase()
+ ) {
+ chars.push({
+ char: labelChars[labelPointer],
+ highlight: true,
+ });
+ keywordPointer++;
+ } else {
+ chars.push({
+ char: labelChars[labelPointer],
+ highlight: false,
+ });
+ }
+ } else {
+ keywordPointer++;
+ }
+ labelPointer++;
+ }
+ }
ret.push({
- name: parent?.name ? `${parent.name} > ${name}` : name,
+ name: label,
+ chars,
path,
icon,
});
@@ -81,7 +157,36 @@ export function useMenuSearch(refs: Ref, scrollWrap: Ref, emit: A
ret.push(...handlerSearchResult(children, reg, item));
}
});
- return ret;
+
+ // 排序
+ return ret.sort((a, b) => {
+ if (
+ a.name.toLowerCase().includes(keyword.value.toLowerCase()) &&
+ b.name.toLowerCase().includes(keyword.value.toLowerCase())
+ ) {
+ // 两者都存在完整关键词的匹配
+
+ // 匹配数量
+ const ca =
+ a.name.toLowerCase().match(new RegExp(keyword.value.toLowerCase(), 'g'))?.length ?? 0;
+ const cb =
+ b.name.toLowerCase().match(new RegExp(keyword.value.toLowerCase(), 'g'))?.length ?? 0;
+
+ // 匹配数量越多的优先显示,数量相同的按字符串排序
+ return ca === cb ? a.name.toLowerCase().localeCompare(b.name.toLowerCase()) : cb - ca;
+ } else {
+ if (a.name.toLowerCase().includes(keyword.value.toLowerCase())) {
+ // 完整关键词的匹配优先
+ return -1;
+ } else if (b.name.toLowerCase().includes(keyword.value.toLowerCase())) {
+ // 完整关键词的匹配优先
+ return 1;
+ } else {
+ // 按字符串排序
+ return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
+ }
+ }
+ });
}
// Activate when the mouse moves to a certain line