Files
jinshen-website/app/components/SearchResults.vue
R2m1liA 265bc5370a feat: 搜索页添加分类功能
- 使用Element Plus的tab组件搜索页添加了分类功能
- 重构搜索页代码,将搜索结果作为单独的组件
- 调整代码格式,去除部分无用代码
2025-09-22 16:44:01 +08:00

221 lines
5.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div v-if="hasResults">
<div class="search-results">
<el-card
v-for="(hit, hitIndex) in paginatedHits"
:key="`${getHitIdentifier(hit.content, hitIndex)}`"
class="result-card"
@click="navigateTo(resolveHitLink(hit.content))"
>
<h3 class="result-title">{{ getHitTitle(hit.content) }}</h3>
<p v-if="getHitSummary(hit.content)" class="result-summary">
{{ getHitSummary(hit.content) }}
</p>
<p v-if="hit.type" class="result-type">
<span>内容类型: </span>
<span class="result-type-name">{{ getIndexLabel(hit.type) }}</span>
</p>
</el-card>
</div>
<!-- 分页组件 -->
<div class="pagination-container text-align-center mt-12">
<el-pagination
class="justify-center"
layout="prev, pager, next"
hide-on-single-page
:current-page="currentPage"
:page-size="pageSize"
:total="filteredHits.length"
@current-change="handleCurrentChange"
/>
</div>
</div>
<div v-else>
<el-empty
:description="
route.query.query
? $t('search.no-results', { query: route.query?.query })
: $t('search.no-query')
"
/>
</div>
</template>
<script setup lang="ts">
interface HitItem {
content: SearchHit;
type: string;
}
const props = defineProps<{
hitItems: HitItem[];
category?: string;
}>();
// i18n相关
const { t } = useI18n();
// 路由相关
const localePath = useLocalePath();
const route = useRoute();
// 分页相关
const currentPage = ref(1);
const pageSize = ref(5);
// 搜索相关
const hits = props.hitItems;
const filteredHits = computed(() => {
if (props.category) {
return hits.filter((hit) => hit.type === props.category);
} else {
return hits;
}
});
const paginatedHits = computed(() => {
const start = (currentPage.value - 1) * pageSize.value;
const end = currentPage.value * pageSize.value;
return filteredHits.value.slice(start, end);
});
const indexLabels = computed<Record<string, string>>(() => ({
production: t('search.sections.production'),
solution: t('search.sections.solution'),
support: t('search.sections.support'),
default: t('search.sections.default'),
}));
/**
* 根据indexUid获取标签名
* @param indexUid 搜索条目的IndexUid
*/
const getIndexLabel = (indexUid: string) =>
indexLabels.value[indexUid] || indexLabels.value.default;
const hasResults = computed(() => {
return filteredHits.value.length > 0;
});
/**
* 获取搜索条目的唯一标识符
* 尝试根据搜索条目的相关词条获取唯一标识符
* 若未找到则fallback至给定的index
* @param hit 搜索条目
* @param index 条目索引
*/
const getHitIdentifier = (hit: SearchHit, index: number) => {
const candidate = [hit.objectID, hit.documentId, hit.id, hit.slug].find(
(value) =>
['string', 'number'].includes(typeof value) && String(value).length > 0
);
return candidate != null ? String(candidate) : String(index);
};
/**
* 获取搜索条目的标题
* @param hit 搜索条目
*/
const getHitTitle = (hit: SearchHit) => {
const candidate = [
hit.title,
hit.name,
hit.heading,
hit.documentTitle,
].find((value) => typeof value === 'string' && value.trim().length > 0);
return candidate ? String(candidate) : t('search.untitled');
};
/**
* 获取搜索条目的摘要
* @param hit 搜索条目
*/
const getHitSummary = (hit: SearchHit) => {
const candidate = [
hit.summary,
hit.description,
hit.snippet,
hit.content,
hit.text,
].find((value) => typeof value === 'string' && value.trim().length > 0);
return candidate ? String(candidate) : '';
};
/**
* 解析条目链接
* 根据条目类型返回正确的跳转链接
* @param hit 搜索条目
*/
const resolveHitLink = (hit: SearchHit) => {
if (typeof hit.route === 'string' && hit.route.trim().length > 0) {
return localePath(hit.route);
}
const slugCandidate = [hit.slug, hit.documentId, hit.id, hit.objectID].find(
(value) =>
['string', 'number'].includes(typeof value) && String(value).length > 0
);
if (!slugCandidate) {
return null;
}
const slug = String(slugCandidate);
if (hit.indexUid === 'production') {
return localePath({ path: `/productions/${slug}` });
}
if (hit.indexUid === 'solution') {
return localePath({ path: `/solutions/${slug}` });
}
return null;
};
const handleCurrentChange = (page: number) => {
currentPage.value = page;
};
</script>
<style scoped>
.search-results {
display: grid;
gap: 1.5rem;
padding: 0.5rem;
}
.result-card {
border-radius: 12px;
transition: box-shadow 0.2s ease;
}
.result-card:hover {
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.06);
}
.result-title {
font-size: 1.2rem;
font-weight: 600;
color: var(--el-color-primary);
display: inline-block;
}
.result-summary {
font-size: 0.95rem;
color: var(--el-text-color-regular);
margin-bottom: 0.5rem;
line-height: 1.6;
}
.result-type {
font-size: 0.8rem;
color: var(--el-text-color-secondary);
}
.result-type-name {
margin-left: 4px;
color: var(--el-color-primary);
}
</style>