feat: 添加搜索功能

- 使用meilisearch进行搜索
- 增添搜索界面
This commit is contained in:
2025-09-16 16:02:00 +08:00
parent 92c5a3baab
commit dba2cdf366
6 changed files with 550 additions and 14 deletions

View File

@ -0,0 +1,128 @@
import { MeiliSearch } from "meilisearch";
import type { SearchParams, SearchResponse } from "meilisearch";
interface RawSearchSection {
indexUid: string;
response: SearchResponse<Record<string, unknown>>;
}
export interface SearchHit extends Record<string, unknown> {
indexUid: string;
objectID?: string | number;
}
export interface SearchSection {
indexUid: string;
hits: SearchHit[];
estimatedTotalHits: number;
processingTimeMs: number;
}
const parseIndexes = (indexes: string | string[] | undefined): string[] => {
if (!indexes) {
return [];
}
if (Array.isArray(indexes)) {
return indexes.map((item) => item.trim()).filter(Boolean);
}
return indexes
.split(",")
.map((item) => item.trim())
.filter(Boolean);
};
export const useMeilisearch = () => {
const runtimeConfig = useRuntimeConfig();
const indexes = computed(() =>
parseIndexes(runtimeConfig.public?.meili?.indexes)
);
let meiliClient: MeiliSearch | null = null;
const ensureClient = () => {
if(meiliClient) return meiliClient;
const host = runtimeConfig.public?.meili?.host;
if (!host) {
console.warn("Meilisearch host is not configured.");
return null;
}
const apiKey = runtimeConfig.public?.meili?.searchKey;
meiliClient = new MeiliSearch({
host,
apiKey: apiKey || undefined,
});
return meiliClient;
};
const search = async (
query: string,
params: SearchParams = {}
): Promise<SearchSection[]> => {
const trimmedQuery = query.trim();
if (!trimmedQuery) {
return [];
}
const client = ensureClient();
if (!client) {
return [];
}
const activeIndexes = indexes.value;
if (!activeIndexes.length) {
console.warn("No Meilisearch indexes configured.");
return [];
}
const requests = activeIndexes.map(async (indexUid) => {
const response = await client.index(indexUid).search(trimmedQuery, {
limit: params.limit ?? 10,
...params,
});
const safeResponse = JSON.parse(JSON.stringify(response));
return {
indexUid,
response: {
hits: safeResponse.hits,
estimatedTotalHits: safeResponse.estimatedTotalHits ?? safeResponse.hits.length,
processingTimeMs: safeResponse.processingTimeMs ?? 0,
query: safeResponse.query,
},
} satisfies RawSearchSection;
});
console.log((await requests[0])?.response.hits[0]?.locale);
const settled = await Promise.allSettled(requests);
settled
.filter((result): result is PromiseRejectedResult => result.status === "rejected")
.forEach((result) => {
console.error("Meilisearch query failed", result.reason);
});
return settled
.filter((result) => result.status === "fulfilled")
.map((result) => {
const fulfilled = result as PromiseFulfilledResult<RawSearchSection>;
return {
indexUid: fulfilled.value.indexUid,
hits: fulfilled.value.response.hits.map((hit) => ({
...hit,
indexUid: fulfilled.value.indexUid,
})),
estimatedTotalHits:
fulfilled.value.response.estimatedTotalHits ??
fulfilled.value.response.hits.length,
processingTimeMs: fulfilled.value.response.processingTimeMs ?? 0,
};
});
};
return {
search,
indexes,
};
};