All checks were successful
deploy to server / build-and-deploy (push) Successful in 3m49s
- 以搜索条目匹配程度为标准排序 - 移除拼音支持
109 lines
2.6 KiB
TypeScript
109 lines
2.6 KiB
TypeScript
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 } = options;
|
||
|
||
// --- 文本标准化函数 ---
|
||
const normalizeText = (text: string): string => {
|
||
const normalizedText = text.normalize('NFKC').toLowerCase().trim();
|
||
return normalizedText;
|
||
};
|
||
|
||
/**
|
||
* 类型安全的对象取值函数
|
||
*/
|
||
function getPropertyByPath<T>(
|
||
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<string, unknown>)[key];
|
||
} else {
|
||
return undefined;
|
||
}
|
||
}
|
||
return typeof value === 'string' ? value : undefined;
|
||
}
|
||
|
||
// --- 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,
|
||
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();
|
||
return normalized;
|
||
}
|
||
return value;
|
||
},
|
||
});
|
||
|
||
const result = fuse.search(k);
|
||
return result.map((result) => result.item);
|
||
}
|
||
|
||
return source.filter((item) =>
|
||
keys.some((key) => {
|
||
const value = item[key];
|
||
return typeof value === 'string' ? fallbackFuzzyMatch(value, k) : false;
|
||
})
|
||
);
|
||
}
|