From 265bc5370a3f97c4237a7bfa6981679031b9e92d Mon Sep 17 00:00:00 2001 From: R2m1liA <15258427350@163.com> Date: Mon, 22 Sep 2025 16:44:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=90=9C=E7=B4=A2=E9=A1=B5=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=88=86=E7=B1=BB=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用Element Plus的tab组件搜索页添加了分类功能 - 重构搜索页代码,将搜索结果作为单独的组件 - 调整代码格式,去除部分无用代码 --- app/components/SearchResults.vue | 220 ++++++++++++++++++++++ app/pages/search.vue | 310 ++++++------------------------- 2 files changed, 280 insertions(+), 250 deletions(-) create mode 100644 app/components/SearchResults.vue diff --git a/app/components/SearchResults.vue b/app/components/SearchResults.vue new file mode 100644 index 0000000..1839be9 --- /dev/null +++ b/app/components/SearchResults.vue @@ -0,0 +1,220 @@ + + + + + diff --git a/app/pages/search.vue b/app/pages/search.vue index 8311224..c26f759 100644 --- a/app/pages/search.vue +++ b/app/pages/search.vue @@ -20,84 +20,41 @@ {{ $t('search.search-button') }} -

- {{ $t('search.results-for', { query: route.query?.query }) }} -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- + + + + - - {{ getHitTitle(hit.content) }} - -

{{ getHitTitle(hit.content) }}

-

- {{ getHitSummary(hit.content) }} -

-

- 内容类型: - {{ getIndexLabel(hit.type) }} -

-
-
- - -
- -
+ + + + + + + + + + + +
import { Search } from '@element-plus/icons-vue'; - import type { SearchHit, SearchSection } from '~/composables/useMeilisearch'; + // i18n相关 const { t } = useI18n(); + const { getStrapiLocale } = useLocalizations(); + const strapiLocale = getStrapiLocale(); + + // 路由相关 const route = useRoute(); const router = useRouter(); const localePath = useLocalePath(); - const { getStrapiLocale } = useLocalizations(); - const strapiLocale = getStrapiLocale(); - - const { search } = useMeilisearch(); - + // 搜索相关 const loading = ref(true); + const { search } = useMeilisearch(); const keyword = ref(''); - const currentPage = ref(1); - const pageSize = ref(5); + const activeRequestId = ref(0); + const sections = ref([]); - const localeSections = computed(() => - sections.value.map((section) => ({ - ...section, - hits: section.hits.filter( - (hit) => - !hit.locale || - String(hit.locale).toLowerCase() === strapiLocale.toLowerCase() - ), - })) - ); + // 本地化+空Section过滤 const filteredSections = computed(() => - localeSections.value.filter((section) => section.hits.length > 0) + sections.value + .map((section) => ({ + ...section, + hits: section.hits.filter( + (hit) => + !hit.locale || + String(hit.locale).toLowerCase() === strapiLocale.toLowerCase() + ), + })) + .filter((section) => section.hits.length > 0) ); + // 展平hits const hits = computed(() => filteredSections.value.flatMap((item) => item.hits.map((content) => ({ content, type: item.indexUid })) ) ); - const paginatedHits = computed(() => { - const start = (currentPage.value - 1) * pageSize.value; - const end = currentPage.value * pageSize.value; - return hits.value.slice(start, end); + + // 分类控制 + const activeTab = ref('all'); + const resultCount = computed(() => { + const map: Record = { all: hits.value.length }; + for (const hit of hits.value) { + map[hit.type] = (map[hit.type] ?? 0) + 1; + } + return map; }); - const activeRequestId = ref(0); - - const indexLabels = computed>(() => ({ - production: t('search.sections.production'), - solution: t('search.sections.solution'), - support: t('search.sections.support'), - default: t('search.sections.default'), - })); - - const getIndexLabel = (indexUid: string) => - indexLabels.value[indexUid] || indexLabels.value.default; const hasResults = computed(() => - localeSections.value.some((section) => section.hits.length > 0) + filteredSections.value.some((section) => section.hits.length > 0) ); - 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); - }; - - 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'); - }; - - 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) : ''; - }; - - 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 navigateToQuery = (value: string) => { const trimmed = value.trim(); if (!trimmed) return; @@ -253,7 +150,7 @@ sections.value = results; } console.log('hits:', hits.value); - console.log('paginatedHits:', paginatedHits.value); + console.log(resultCount.value); } catch (error) { console.error('Failed to perform search', error); if (requestId === activeRequestId.value) { @@ -266,32 +163,12 @@ } }; - function debounce void>( - fn: T, - delay: number - ): (...args: Parameters) => void { - let timer: ReturnType | null = null; - - return function (this: never, ...args: Parameters) { - if (timer) { - clearTimeout(timer); - } - timer = setTimeout(() => { - fn.apply(this, args); - }, delay); - }; - } - const handleClear = () => { keyword.value = ''; sections.value = []; router.replace(localePath({ path: '/search' })); }; - const handleCurrentChange = (page: number) => { - currentPage.value = page; - }; - watch( () => route.query.query, (newQuery) => { @@ -368,73 +245,6 @@ padding: 3rem 0; } - .search-results { - display: flex; - flex-direction: column; - gap: 2.5rem; - } - - .search-section { - display: flex; - flex-direction: column; - gap: 1.5rem; - } - - .section-header { - display: flex; - align-items: baseline; - gap: 0.75rem; - } - - .section-title { - font-size: 1.5rem; - font-weight: 600; - color: var(--el-text-color-primary); - } - - .section-count { - font-size: 0.9rem; - color: var(--el-text-color-secondary); - } - - .section-results { - display: grid; - gap: 1.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); - } - @media (max-width: 640px) { .search-page { padding: 2rem 1rem;