From c9b5b1fad9ad6b4694671f302afe15c4121d71f8 Mon Sep 17 00:00:00 2001 From: R2m1liA <15258427350@163.com> Date: Fri, 5 Dec 2025 14:56:24 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E4=B8=BA=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E9=A1=B5=E6=A0=8F=E7=9B=AE=E6=B7=BB=E5=8A=A0=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E7=BC=A9=E7=95=A5=E5=9B=BE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 图片预览:产品与解决方案栏目添加缩略图功能 - 组件提取:在搜索结果页,将单个搜索结果单独提取为组件SearchResultCard --- .../pages/search/SearchResultCard.vue | 72 +++++++++++++++++++ app/components/pages/search/SearchResults.vue | 11 +-- server/utils/search-converters.ts | 2 + shared/types/meilisearch/meili-index.ts | 6 ++ shared/types/views/search-item-view.ts | 3 + 5 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 app/components/pages/search/SearchResultCard.vue diff --git a/app/components/pages/search/SearchResultCard.vue b/app/components/pages/search/SearchResultCard.vue new file mode 100644 index 0000000..3aa76a3 --- /dev/null +++ b/app/components/pages/search/SearchResultCard.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/app/components/pages/search/SearchResults.vue b/app/components/pages/search/SearchResults.vue index 816f469..1cde91c 100644 --- a/app/components/pages/search/SearchResults.vue +++ b/app/components/pages/search/SearchResults.vue @@ -6,16 +6,7 @@ :key="`${hit.type}-${hit.id}`" :to="localePath(resolveHitLink(hit))" > - -

{{ hit.title }}

-

- {{ hit.summary }} -

-

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

-
+ diff --git a/server/utils/search-converters.ts b/server/utils/search-converters.ts index 2cc6f97..b5a66dc 100644 --- a/server/utils/search-converters.ts +++ b/server/utils/search-converters.ts @@ -9,6 +9,7 @@ export const converters: { type: 'product', title: item.name, summary: item.summary, + thumbnail: `/api/assets/${item.cover}`, }), solutions: (item: MeiliIndexMap['solutions']): SearchItemView => ({ @@ -16,6 +17,7 @@ export const converters: { type: 'solution', title: item.title, summary: item.summary, + thumbnail: `/api/assets/${item.cover}`, }), questions: (item: MeiliIndexMap['questions']): SearchItemView => ({ diff --git a/shared/types/meilisearch/meili-index.ts b/shared/types/meilisearch/meili-index.ts index 7b34473..d7a5810 100644 --- a/shared/types/meilisearch/meili-index.ts +++ b/shared/types/meilisearch/meili-index.ts @@ -16,6 +16,9 @@ export interface MeiliProductIndex { /** 产品类型 **/ type: string; + + /** 产品缩略图 **/ + cover: string; } /** @@ -36,6 +39,9 @@ export interface MeiliSolutionIndex { /** 解决方案类型 **/ type: string; + + /** 解决方案缩略图 **/ + cover: string; } /** diff --git a/shared/types/views/search-item-view.ts b/shared/types/views/search-item-view.ts index e9070f0..594caed 100644 --- a/shared/types/views/search-item-view.ts +++ b/shared/types/views/search-item-view.ts @@ -10,4 +10,7 @@ export interface SearchItemView { /** 条目摘要 **/ summary: string; + + /** 条目预览图 **/ + thumbnail?: string; } -- 2.49.0 From 36d24a4740ebb23ac06d70054864a565f0cb491e Mon Sep 17 00:00:00 2001 From: R2m1liA <15258427350@163.com> Date: Fri, 5 Dec 2025 16:25:57 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=E5=8F=AF=E9=80=89=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为搜索条目中的可选字段进行判断并处理 --- server/utils/search-converters.test.ts | 4 ++++ server/utils/search-converters.ts | 8 ++++---- shared/types/meilisearch/meili-index.ts | 18 +++++++++--------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/server/utils/search-converters.test.ts b/server/utils/search-converters.test.ts index 95ad2e2..a90c33d 100644 --- a/server/utils/search-converters.test.ts +++ b/server/utils/search-converters.test.ts @@ -12,6 +12,7 @@ describe('converters', () => { summary: 'High efficiency', description: 'Detailed description', type: 'pump', + cover: 'rand-om__-uuid-1234', }; const result = converters.products(item); expect(result).toEqual({ @@ -19,6 +20,7 @@ describe('converters', () => { type: 'product', title: 'Hydraulic Pump', summary: 'High efficiency', + thumbnail: '/api/assets/rand-om__-uuid-1234', }); }); @@ -29,6 +31,7 @@ describe('converters', () => { summary: 'Effective solution', content: 'Detailed content', type: 'Type A', + cover: 'rand-om__-uuid-5678', }; const result = converters.solutions(item); expect(result).toEqual({ @@ -36,6 +39,7 @@ describe('converters', () => { type: 'solution', title: 'Solution A', summary: 'Effective solution', + thumbnail: '/api/assets/rand-om__-uuid-5678', }); }); diff --git a/server/utils/search-converters.ts b/server/utils/search-converters.ts index b5a66dc..c334131 100644 --- a/server/utils/search-converters.ts +++ b/server/utils/search-converters.ts @@ -8,16 +8,16 @@ export const converters: { id: item.id, type: 'product', title: item.name, - summary: item.summary, - thumbnail: `/api/assets/${item.cover}`, + summary: item?.summary ?? '', + thumbnail: item?.cover ? `/api/assets/${item.cover}` : undefined, }), solutions: (item: MeiliIndexMap['solutions']): SearchItemView => ({ id: item.id, type: 'solution', title: item.title, - summary: item.summary, - thumbnail: `/api/assets/${item.cover}`, + summary: item?.summary ?? '', + thumbnail: item?.cover ? `/api/assets/${item.cover}` : undefined, }), questions: (item: MeiliIndexMap['questions']): SearchItemView => ({ diff --git a/shared/types/meilisearch/meili-index.ts b/shared/types/meilisearch/meili-index.ts index d7a5810..9555b64 100644 --- a/shared/types/meilisearch/meili-index.ts +++ b/shared/types/meilisearch/meili-index.ts @@ -9,16 +9,16 @@ export interface MeiliProductIndex { name: string; /** 产品简介 **/ - summary: string; + summary?: string; /** 产品详情 **/ - description: string; + description?: string; /** 产品类型 **/ - type: string; + type?: string; /** 产品缩略图 **/ - cover: string; + cover?: string; } /** @@ -32,16 +32,16 @@ export interface MeiliSolutionIndex { title: string; /** 解决方案摘要 **/ - summary: string; + summary?: string; /** 解决方案内容 **/ - content: string; + content?: string; /** 解决方案类型 **/ - type: string; + type?: string; /** 解决方案缩略图 **/ - cover: string; + cover?: string; } /** @@ -55,7 +55,7 @@ export interface MeiliQuestionIndex { title: string; /** 问题内容 **/ - content: string; + content?: string; /** 相关产品 **/ products: string[]; -- 2.49.0 From f1398a55457e8e6dc71374f5a6a276109a6f76d8 Mon Sep 17 00:00:00 2001 From: R2m1liA <15258427350@163.com> Date: Fri, 5 Dec 2025 16:49:09 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E4=B8=BA=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E6=9D=A1=E7=9B=AE=E6=B7=BB=E5=8A=A0=E7=BB=86=E5=88=86=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 类型细分:有原先的四大分类添加细分类型,例如产品(原纸分切机) - 接口调整:原先的type分类改为sectionType并将type作为细分类型使用 --- .../pages/search/SearchResultCard.vue | 5 ++++- app/components/pages/search/SearchResults.vue | 17 ++++++++++------- app/components/pages/search/SearchTabs.vue | 2 +- server/utils/search-converters.test.ts | 18 ++++++++++++------ server/utils/search-converters.ts | 18 ++++++++++-------- shared/types/meilisearch/meili-index.ts | 6 ++++++ shared/types/views/search-item-view.ts | 7 +++++-- 7 files changed, 48 insertions(+), 25 deletions(-) diff --git a/app/components/pages/search/SearchResultCard.vue b/app/components/pages/search/SearchResultCard.vue index 3aa76a3..02be23c 100644 --- a/app/components/pages/search/SearchResultCard.vue +++ b/app/components/pages/search/SearchResultCard.vue @@ -6,9 +6,12 @@

{{ item.summary }}

-

+

{{ $t('search.section') }}: {{ typeLabel }} + ({{ item.type }})

diff --git a/app/components/pages/search/SearchResults.vue b/app/components/pages/search/SearchResults.vue index 1cde91c..0414f7b 100644 --- a/app/components/pages/search/SearchResults.vue +++ b/app/components/pages/search/SearchResults.vue @@ -3,10 +3,13 @@
- +
@@ -63,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; } @@ -107,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/server/utils/search-converters.test.ts b/server/utils/search-converters.test.ts index a90c33d..db52eeb 100644 --- a/server/utils/search-converters.test.ts +++ b/server/utils/search-converters.test.ts @@ -17,9 +17,10 @@ describe('converters', () => { 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', }); }); @@ -36,9 +37,10 @@ describe('converters', () => { 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', }); }); @@ -51,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', }); }); @@ -68,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 c334131..6560048 100644 --- a/server/utils/search-converters.ts +++ b/server/utils/search-converters.ts @@ -6,33 +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 9555b64..3b6e93d 100644 --- a/shared/types/meilisearch/meili-index.ts +++ b/shared/types/meilisearch/meili-index.ts @@ -57,6 +57,9 @@ export interface MeiliQuestionIndex { /** 问题内容 **/ content?: string; + /** 问题类型 **/ + type?: string; + /** 相关产品 **/ products: string[]; @@ -74,6 +77,9 @@ export interface MeiliProductDocumentIndex { /** 文档标题 **/ title: string; + /** 文档类型 **/ + type?: string; + /** 相关产品 **/ products: string[]; diff --git a/shared/types/views/search-item-view.ts b/shared/types/views/search-item-view.ts index 594caed..1910a03 100644 --- a/shared/types/views/search-item-view.ts +++ b/shared/types/views/search-item-view.ts @@ -3,13 +3,16 @@ 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; -- 2.49.0 From 63cdff9c41f5ad88e535a376a824bf8640dc5cab Mon Sep 17 00:00:00 2001 From: R2m1liA <15258427350@163.com> Date: Fri, 5 Dec 2025 17:18:48 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=E4=B8=BA=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=BA=93=E6=B7=BB=E5=8A=A0=E6=96=87=E6=A1=A3=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 功能添加:在文档列表中,当未指定文档类型时,在标题右侧显示文档类型 - 查询更改:产品查询添加文档类型查询方法 - mapper更改:productDocumentView添加文档类型 --- app/components/shared/DocumentList.vue | 25 ++++++++++++++++++++++++- app/pages/support/documents.vue | 8 +++++++- server/assets/graphql/product.graphql | 7 +++++++ server/mappers/productMapper.test.ts | 25 +++++++++++++++++++++++++ server/mappers/productMapper.ts | 12 ++++++++++++ shared/types/views/product-view.ts | 5 +++++ 6 files changed, 80 insertions(+), 2 deletions(-) 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/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; } -- 2.49.0