diff --git a/app/components/pages/search/SearchResultCard.vue b/app/components/pages/search/SearchResultCard.vue new file mode 100644 index 0000000..02be23c --- /dev/null +++ b/app/components/pages/search/SearchResultCard.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/app/components/pages/search/SearchResults.vue b/app/components/pages/search/SearchResults.vue index 816f469..0414f7b 100644 --- a/app/components/pages/search/SearchResults.vue +++ b/app/components/pages/search/SearchResults.vue @@ -3,19 +3,13 @@
- -

{{ hit.title }}

-

- {{ hit.summary }} -

-

- {{ $t('search.section') }}: - {{ getIndexLabel(hit.type) }} -

-
+
@@ -72,7 +66,7 @@ const items = props.searchItems; const filteredHits = computed(() => { if (props.category) { - return items.filter((item) => item.type === props.category); + return items.filter((item) => item.sectionType === props.category); } else { return items; } @@ -116,19 +110,19 @@ const slug = String(slugCandidate); - if (item.type === 'product') { + if (item.sectionType === 'product') { return localePath({ path: `/products/${slug}` }); } - if (item.type === 'solution') { + if (item.sectionType === 'solution') { return localePath({ path: `/solutions/${slug}` }); } - if (item.type === 'document') { + if (item.sectionType === 'document') { return localePath({ path: `/download/${slug}` }); } - if (item.type === 'question') { + if (item.sectionType === 'question') { return localePath({ path: `/support/faq`, query: { focus: slug } }); } diff --git a/app/components/pages/search/SearchTabs.vue b/app/components/pages/search/SearchTabs.vue index 6ed3e25..1d9e4b5 100644 --- a/app/components/pages/search/SearchTabs.vue +++ b/app/components/pages/search/SearchTabs.vue @@ -32,7 +32,7 @@ const resultCount = computed(() => { const map: Record = { all: props.searchItems.length }; for (const item of props.searchItems) { - map[item.type] = (map[item.type] ?? 0) + 1; + map[item.sectionType] = (map[item.sectionType] ?? 0) + 1; } return map; }); diff --git a/app/components/shared/DocumentList.vue b/app/components/shared/DocumentList.vue index de8669d..36d08fa 100644 --- a/app/components/shared/DocumentList.vue +++ b/app/components/shared/DocumentList.vue @@ -7,7 +7,17 @@ @click="handleClick(doc.fileId)" >
-

{{ doc.title }}

+
+

+ {{ doc.title }} + + | + + + {{ doc.type.name }} + +

+
{{ $t('document-meta.size') }}: {{ formatFileSize(doc.size) }} @@ -28,6 +38,10 @@ type: Array as () => Array, default: () => [], }, + showCategory: { + type: Boolean, + default: true, + }, }); const localePath = useLocalePath(); @@ -63,6 +77,15 @@ color: var(--el-text-color-secondary); } + .document-title { + margin-bottom: 0.5rem; + } + + .document-category { + font-size: 0.75rem; + color: var(--el-text-color-secondary); + } + .download-button { margin-left: auto; } diff --git a/app/pages/support/documents.vue b/app/pages/support/documents.vue index 8c0db6f..5bf8fd3 100644 --- a/app/pages/support/documents.vue +++ b/app/pages/support/documents.vue @@ -17,7 +17,13 @@ :document-type-options="documentTypeOptions" /> - + { filesize: 1000, filename_download: 'doc1.pdf', }, + type: { + id: 1, + translations: [ + { + id: 1, + name: 'manual', + }, + ], + }, translations: [ { id: 1, @@ -363,6 +372,10 @@ describe('toProductDocumentView', () => { fileId: 'rand-om__-uuid-1234', filename: 'doc1.pdf', title: 'Document Title 1', + type: { + id: '1', + name: 'manual', + }, size: 1000, url: '/api/assets/rand-om__-uuid-1234', }, @@ -391,6 +404,10 @@ describe('toProductDocumentView', () => { fileId: 'rand-om__-uuid-1234', filename: 'doc1.pdf', title: '', + type: { + id: '-1', + name: '', + }, size: 1000, url: '/api/assets/rand-om__-uuid-1234', }, @@ -413,6 +430,10 @@ describe('toProductDocumentView', () => { filename: '', title: '', size: 0, + type: { + id: '-1', + name: '', + }, url: '', }, { @@ -421,6 +442,10 @@ describe('toProductDocumentView', () => { filename: '', title: '', size: 0, + type: { + id: '-1', + name: '', + }, url: '', }, ]); diff --git a/server/mappers/productMapper.ts b/server/mappers/productMapper.ts index 2896d96..453b85a 100644 --- a/server/mappers/productMapper.ts +++ b/server/mappers/productMapper.ts @@ -1,4 +1,5 @@ import { isObject } from '../../server/utils/object'; +import { toDocumentTypeView } from './documentMapper'; /** * 将 Directus 返回的 ProductImage 数据转换为 ProductImageView 视图模型 @@ -161,6 +162,10 @@ export function toProductDocumentView( size: 0, title: '', url: '', + type: { + id: '-1', + name: '', + }, } satisfies ProductDocumentView; } @@ -173,6 +178,10 @@ export function toProductDocumentView( size: 0, title: '', url: '', + type: { + id: '-1', + name: '', + }, } satisfies ProductDocumentView; } @@ -184,6 +193,8 @@ export function toProductDocumentView( const trans = document.translations?.[0]; + const typeView = toDocumentTypeView(document.type ?? null); + return { id: item.id.toString(), fileId: file?.id ?? '', @@ -191,6 +202,7 @@ export function toProductDocumentView( size: file?.filesize ?? 0, title: trans?.title ?? '', url: url, + type: typeView, } satisfies ProductDocumentView; }); } diff --git a/server/utils/search-converters.test.ts b/server/utils/search-converters.test.ts index 95ad2e2..db52eeb 100644 --- a/server/utils/search-converters.test.ts +++ b/server/utils/search-converters.test.ts @@ -12,13 +12,16 @@ describe('converters', () => { summary: 'High efficiency', description: 'Detailed description', type: 'pump', + cover: 'rand-om__-uuid-1234', }; const result = converters.products(item); expect(result).toEqual({ id: 1, - type: 'product', + sectionType: 'product', title: 'Hydraulic Pump', summary: 'High efficiency', + type: 'pump', + thumbnail: '/api/assets/rand-om__-uuid-1234', }); }); @@ -29,13 +32,16 @@ describe('converters', () => { summary: 'Effective solution', content: 'Detailed content', type: 'Type A', + cover: 'rand-om__-uuid-5678', }; const result = converters.solutions(item); expect(result).toEqual({ id: 1, - type: 'solution', + sectionType: 'solution', title: 'Solution A', summary: 'Effective solution', + type: 'Type A', + thumbnail: '/api/assets/rand-om__-uuid-5678', }); }); @@ -47,13 +53,15 @@ describe('converters', () => { 'This is a detailed explanation of how to use the product effectively.', products: ['Product A'], product_types: ['Type A'], + type: 'Question Type', }; const result = converters.questions(item); expect(result).toEqual({ id: 1, title: 'How to use product?', - summary: '', - type: 'question', + summary: undefined, + type: 'Question Type', + sectionType: 'question', }); }); @@ -64,13 +72,15 @@ describe('converters', () => { products: ['Product A'], product_types: ['Type A'], fileUUID: 'TEST-UUID', + type: 'manual', }; const result = converters.product_documents(item); expect(result).toEqual({ id: 'TEST-UUID', title: 'User Manual', - summary: '', - type: 'document', + summary: undefined, + sectionType: 'document', + type: 'manual', }); }); }); diff --git a/server/utils/search-converters.ts b/server/utils/search-converters.ts index 2cc6f97..6560048 100644 --- a/server/utils/search-converters.ts +++ b/server/utils/search-converters.ts @@ -6,31 +6,35 @@ export const converters: { } = { products: (item: MeiliIndexMap['products']): SearchItemView => ({ id: item.id, - type: 'product', + sectionType: 'product', title: item.name, - summary: item.summary, + summary: item?.summary, + type: item?.type, + thumbnail: item?.cover ? `/api/assets/${item.cover}` : undefined, }), solutions: (item: MeiliIndexMap['solutions']): SearchItemView => ({ id: item.id, - type: 'solution', + sectionType: 'solution', title: item.title, - summary: item.summary, + summary: item?.summary, + type: item?.type, + thumbnail: item?.cover ? `/api/assets/${item.cover}` : undefined, }), questions: (item: MeiliIndexMap['questions']): SearchItemView => ({ id: item.id, - type: 'question', + sectionType: 'question', title: item.title, - summary: '', + type: item?.type, }), product_documents: ( item: MeiliIndexMap['product_documents'] ): SearchItemView => ({ id: item.fileUUID || item.id, - type: 'document', + sectionType: 'document', title: item.title, - summary: '', + type: item?.type, }), }; diff --git a/shared/types/meilisearch/meili-index.ts b/shared/types/meilisearch/meili-index.ts index 7b34473..3b6e93d 100644 --- a/shared/types/meilisearch/meili-index.ts +++ b/shared/types/meilisearch/meili-index.ts @@ -9,13 +9,16 @@ export interface MeiliProductIndex { name: string; /** 产品简介 **/ - summary: string; + summary?: string; /** 产品详情 **/ - description: string; + description?: string; /** 产品类型 **/ - type: string; + type?: string; + + /** 产品缩略图 **/ + cover?: string; } /** @@ -29,13 +32,16 @@ export interface MeiliSolutionIndex { title: string; /** 解决方案摘要 **/ - summary: string; + summary?: string; /** 解决方案内容 **/ - content: string; + content?: string; /** 解决方案类型 **/ - type: string; + type?: string; + + /** 解决方案缩略图 **/ + cover?: string; } /** @@ -49,7 +55,10 @@ export interface MeiliQuestionIndex { title: string; /** 问题内容 **/ - content: string; + content?: string; + + /** 问题类型 **/ + type?: string; /** 相关产品 **/ products: string[]; @@ -68,6 +77,9 @@ export interface MeiliProductDocumentIndex { /** 文档标题 **/ title: string; + /** 文档类型 **/ + type?: string; + /** 相关产品 **/ products: string[]; diff --git a/shared/types/views/product-view.ts b/shared/types/views/product-view.ts index 7407ddc..0c2a08a 100644 --- a/shared/types/views/product-view.ts +++ b/shared/types/views/product-view.ts @@ -1,3 +1,5 @@ +import type { DocumentTypeView } from './document-list-view'; + /** * 产品图片视图模型 * 用于产品详情页(/products/[slug])中的产品图片数据结构 @@ -73,6 +75,9 @@ export interface ProductDocumentView { /** 文档大小 **/ size: number; + /** 文档类型 **/ + type: DocumentTypeView; + /** 文档链接 **/ url: string; } diff --git a/shared/types/views/search-item-view.ts b/shared/types/views/search-item-view.ts index e9070f0..1910a03 100644 --- a/shared/types/views/search-item-view.ts +++ b/shared/types/views/search-item-view.ts @@ -3,11 +3,17 @@ export interface SearchItemView { id: number | string; /** 条目类型 **/ - type: 'product' | 'solution' | 'question' | 'document'; + sectionType: 'product' | 'solution' | 'question' | 'document'; /** 条目标题 **/ title: string; /** 条目摘要 **/ - summary: string; + summary?: string; + + /** 条目分类 **/ + type?: string; + + /** 条目预览图 **/ + thumbnail?: string; }