diff --git a/app/pages/support/documents.vue b/app/pages/support/documents.vue index 949302b..c838dc8 100644 --- a/app/pages/support/documents.vue +++ b/app/pages/support/documents.vue @@ -77,7 +77,7 @@ const fuzzyMatchedDocuments = fuzzyMatch(documents.value, { keyword: filters.keyword, keys: ['title'], - threshold: 0.2, + threshold: 0.3, }); return fuzzyMatchedDocuments.filter((doc: DocumentListView) => { const matchProduct = filters.selectedProduct diff --git a/app/pages/support/faq.vue b/app/pages/support/faq.vue index 7a1c9fa..89ed0e9 100644 --- a/app/pages/support/faq.vue +++ b/app/pages/support/faq.vue @@ -77,7 +77,7 @@ const fuzzyMatchedQuestions = fuzzyMatch(questions.value, { keyword: filters.keyword, keys: ['title', 'content'], - threshold: 0.2, + threshold: 0.3, }); return fuzzyMatchedQuestions.filter((question: QuestionListView) => { const matchProduct = filters.selectedProduct diff --git a/app/utils/fuzzyFilter.ts b/app/utils/fuzzyFilter.ts index 41e8225..3a88391 100644 --- a/app/utils/fuzzyFilter.ts +++ b/app/utils/fuzzyFilter.ts @@ -41,8 +41,30 @@ export function fuzzyMatch( }); // --- 文本标准化函数 --- - const normalizeText = (text: string): string => - text.normalize('NFKC').toLowerCase().trim(); + const normalizeText = (text: string): string => { + const normalizedText = text.normalize('NFKC').toLowerCase().trim(); + const translit = transliterateText(normalizedText); + return `${normalizedText} ${translit}`; + }; + + /** + * 类型安全的对象取值函数 + */ + function getPropertyByPath( + obj: T, + path: string | string[] + ): string | undefined { + const keys = Array.isArray(path) ? path : path.split('.'); + let value: unknown = obj; + for (const key of keys) { + if (value && typeof value === 'object' && key in value) { + value = (value as Record)[key]; + } else { + return undefined; + } + } + return typeof value === 'string' ? value : undefined; + } // --- fallback 模糊匹配算法 --- const fallbackFuzzyMatch = (text: string, pattern: string): boolean => { @@ -70,6 +92,23 @@ export function fuzzyMatch( threshold, minMatchCharLength, ignoreLocation: true, + findAllMatches: true, + isCaseSensitive: false, + getFn: (obj, path) => { + const value = getPropertyByPath(obj, path); + if (typeof value === 'string') { + const normalized = value + .normalize('NFKC') + .replace(/\s+/g, '') + .toLowerCase() + .trim(); + const translit = transliterateText(normalized); + logger.debug(`${normalized} => ${translit}`); + // 拼接原文 + 转写,提升中外文混合匹配效果 + return `${normalized} ${translit}`; + } + return value; + }, sortFn: (a, b) => { const itemA = a.item as T; const itemB = b.item as T; @@ -83,6 +122,8 @@ export function fuzzyMatch( }, }); + logger.debug('Fuzzy search options:', options); + logger.debug('Fuzzy search keyword:', k); return fuse.search(k).map((result) => result.item); } diff --git a/app/utils/transliterateText.ts b/app/utils/transliterateText.ts new file mode 100644 index 0000000..56d69ea --- /dev/null +++ b/app/utils/transliterateText.ts @@ -0,0 +1,17 @@ +import { pinyin } from 'pinyin-pro'; + +/** + * 将汉语文本转换为拼音形式 + */ +export function transliterateText(input: string): string { + if (!input) return ''; + const text = input.normalize('NFKC').trim(); + + // 检测是否包含中文字符 + if (/[\u4e00-\u9fa5]/.test(text)) { + return pinyin(text, { toneType: 'none', type: 'array' }).join(''); + } + + // 否则返回原文本 + return text; +} diff --git a/package.json b/package.json index 00f7eb4..9440f07 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "meilisearch": "^0.53.0", "nuxt": "^4.0.3", "nuxt-directus": "5.7.0", + "pinyin-pro": "^3.27.0", "sass": "^1.90.0", "sharp": "^0.34.3", "vue": "^3.5.18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eae14c2..19778d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,9 @@ importers: nuxt-directus: specifier: 5.7.0 version: 5.7.0(magicast@0.3.5) + pinyin-pro: + specifier: ^3.27.0 + version: 3.27.0 sass: specifier: ^1.90.0 version: 1.92.1 @@ -4399,6 +4402,9 @@ packages: typescript: optional: true + pinyin-pro@3.27.0: + resolution: {integrity: sha512-Osdgjwe7Rm17N2paDMM47yW+jUIUH3+0RGo8QP39ZTLpTaJVDK0T58hOLaMQJbcMmAebVuK2ePunTEVEx1clNQ==} + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -10618,6 +10624,8 @@ snapshots: optionalDependencies: typescript: 5.9.2 + pinyin-pro@3.27.0: {} + pkg-types@1.3.1: dependencies: confbox: 0.1.8