import Fuse from 'fuse.js'; interface FuzzyFilterOptions { /** 匹配关键字 */ keyword: string; /** 搜索字段 */ keys: Array>; /** 模糊程度 (0~1,越低越严格) */ threshold?: number; /** 最小匹配字符数 */ minMatchCharLength?: number; /** 当前语言 */ locale?: string; } /** 限定 keys 只能选择字符串字段 */ type FuzzyKeyOf = { [K in keyof T]: T[K] extends string ? K : never; }[keyof T]; export function fuzzyMatch( source: T[], options: FuzzyFilterOptions ): T[] { const { keyword, keys, threshold = 0.35, minMatchCharLength = 1, locale = 'auto', } = options; // --- 多语言比较器 --- const collator = new Intl.Collator(locale === 'auto' ? undefined : locale, { sensitivity: 'base', ignorePunctuation: true, }); // --- 文本标准化函数 --- const normalizeText = (text: string): string => text.normalize('NFKC').toLowerCase().trim(); // --- fallback 模糊匹配算法 --- const fallbackFuzzyMatch = (text: string, pattern: string): boolean => { const normalizedText = normalizeText(text); const normalizedPattern = normalizeText(pattern); let i = 0; for (const char of normalizedPattern) { i = normalizedText.indexOf(char, i); if (i === -1) return false; i++; } return true; }; const k = keyword.trim(); if (!k) { return source; } if (import.meta.client) { // 客户端使用Fuze.js进行模糊匹配 const fuse = new Fuse(source, { keys: keys as string[], threshold, minMatchCharLength, ignoreLocation: true, sortFn: (a, b) => { const itemA = a.item as T; const itemB = b.item as T; const key = keys[0]; const aValue = itemA[key]; const bValue = itemB[key]; if (typeof aValue === 'string' && typeof bValue === 'string') { return collator.compare(aValue, bValue); } return 0; }, }); return fuse.search(k).map((result) => result.item); } return source.filter((item) => keys.some((key) => { const value = item[key]; return typeof value === 'string' ? fallbackFuzzyMatch(value, k) : false; }) ); }