feat: 产品筛选器的模糊搜索功能
- 引入纯前端依赖Fuse.js用于模糊匹配 - 新增Utils-fuzzyFilter封装Fuse的初始化与匹配 - 在前端将字符串匹配改为模糊匹配
This commit is contained in:
95
app/utils/fuzzyFilter.ts
Normal file
95
app/utils/fuzzyFilter.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
interface FuzzyFilterOptions<T> {
|
||||
/** 匹配关键字 */
|
||||
keyword: string;
|
||||
|
||||
/** 搜索字段 */
|
||||
keys: Array<FuzzyKeyOf<T>>;
|
||||
|
||||
/** 模糊程度 (0~1,越低越严格) */
|
||||
threshold?: number;
|
||||
|
||||
/** 最小匹配字符数 */
|
||||
minMatchCharLength?: number;
|
||||
|
||||
/** 当前语言 */
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
/** 限定 keys 只能选择字符串字段 */
|
||||
type FuzzyKeyOf<T> = {
|
||||
[K in keyof T]: T[K] extends string ? K : never;
|
||||
}[keyof T];
|
||||
|
||||
export function fuzzyMatch<T extends object>(
|
||||
source: T[],
|
||||
options: FuzzyFilterOptions<T>
|
||||
): 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<T>(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;
|
||||
})
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user