From bd894d6f2e83e916c24cca5c744fd2e5a2e30892 Mon Sep 17 00:00:00 2001 From: R2m1liA <15258427350@163.com> Date: Sat, 8 Nov 2025 15:34:42 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E4=BA=A7=E5=93=81=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E9=A1=B5=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为ProductType添加sort字段用于排序 - 产品列表页项目按照sort升序排序 --- app/composables/directus/useProductList.ts | 1 + app/models/mappers/productMapper.test.ts | 14 +++++++-- app/models/mappers/productMapper.ts | 25 ++++++++++++--- app/models/views/ProductListView.ts | 12 +++++++- app/pages/products/index.vue | 36 ++++++++++------------ shared/types/directus/my-schema.ts | 2 ++ 6 files changed, 64 insertions(+), 26 deletions(-) diff --git a/app/composables/directus/useProductList.ts b/app/composables/directus/useProductList.ts index 273b5d6..989c88d 100644 --- a/app/composables/directus/useProductList.ts +++ b/app/composables/directus/useProductList.ts @@ -19,6 +19,7 @@ export const useProductList = () => { { translations: ['id', 'name'], }, + 'sort', ], }, ], diff --git a/app/models/mappers/productMapper.test.ts b/app/models/mappers/productMapper.test.ts index 177641a..1e996f0 100644 --- a/app/models/mappers/productMapper.test.ts +++ b/app/models/mappers/productMapper.test.ts @@ -14,6 +14,7 @@ describe('toProductListView', () => { product_type: { id: 1, translations: [{ id: 20, name: 'Type Name' }], + sort: 1, }, }; @@ -22,7 +23,11 @@ describe('toProductListView', () => { name: 'Product Name', summary: 'Product Summary', cover: 'cover-file-uuid-1234', - product_type: 'Type Name', + product_type: { + id: 1, + name: 'Type Name', + sort: 1, + }, }); }); @@ -34,6 +39,7 @@ describe('toProductListView', () => { product_type: { id: 20, translations: [], + sort: 1, }, }; @@ -42,7 +48,11 @@ describe('toProductListView', () => { name: '', summary: '', cover: 'cover-file-uuid-1234', - product_type: '', + product_type: { + id: 20, + name: '', + sort: 1, + }, }); }); }); diff --git a/app/models/mappers/productMapper.ts b/app/models/mappers/productMapper.ts index 9597c49..724faff 100644 --- a/app/models/mappers/productMapper.ts +++ b/app/models/mappers/productMapper.ts @@ -1,3 +1,22 @@ +/** + * 将Directus返回的ProductType数据转换为ProductTypeView视图模型 + * + * @param raw: 原始的ProductType数据 + * @returns 转换后的ProductTypeView对象 + * + * @example + * const view = toProductTypeView(rawProductType); + */ +export function toProductTypeView(raw: ProductType): ProductTypeView { + const trans = raw.translations?.[0] ?? { name: '' }; + + return { + id: raw.id, + name: trans.name, + sort: raw?.sort ?? 999, + }; +} + /** * 将 Directus返回的 Product 数据转换为 ProductListView 视图模型 * @@ -11,10 +30,8 @@ export function toProductListView(raw: Product): ProductListView { const trans = raw.translations?.[0] ?? { name: '', summary: '' }; const type = isObject(raw.product_type) - ? raw.product_type.translations.length === 0 - ? '' - : raw.product_type.translations[0].name - : ''; + ? toProductTypeView(raw.product_type) + : undefined; return { id: raw.id, diff --git a/app/models/views/ProductListView.ts b/app/models/views/ProductListView.ts index 14912fe..eec1b6f 100644 --- a/app/models/views/ProductListView.ts +++ b/app/models/views/ProductListView.ts @@ -1,3 +1,13 @@ +/** + * 产品类型视图模型 + * 用于产品列表页的section渲染与排序 + */ +export interface ProductTypeView { + id: number; + name: string; + sort: number; +} + /** * 产品列表视图模型 * 用于产品列表(/products)渲染的数据结构 @@ -13,7 +23,7 @@ export interface ProductListView { summary: string; /** 产品类型 **/ - product_type: string; + product_type: ProductTypeView; /** 产品封面(图片的id) **/ cover: string; diff --git a/app/pages/products/index.vue b/app/pages/products/index.vue index 28babe7..667cb7e 100644 --- a/app/pages/products/index.vue +++ b/app/pages/products/index.vue @@ -8,14 +8,14 @@
toProductListView(item)) ); + logger.debug('products: ', products.value); + // 按类型分组 - // 兼容 product_type 既可能为对象也可能为字符串 const groupedProducts = computed(() => { - const groups: Record = {}; + const groups: Record = + {}; for (const prod of products.value) { - let typeKey = ''; - if (typeof prod.product_type === 'string') { - typeKey = prod.product_type; - } else if ( - prod.product_type && - typeof prod.product_type === 'object' && - 'name' in prod.product_type - ) { - typeKey = prod.product_type || ''; + const typeKey = prod.product_type?.name ?? ''; + if (!groups[typeKey]) { + groups[typeKey] = { data: [], sort: prod.product_type?.sort ?? 999 }; } - if (!groups[typeKey]) groups[typeKey] = []; - groups[typeKey]?.push(prod); + groups[typeKey]?.data.push(prod); } - return groups; + const sortedGroups = Object.entries(groups) + .sort(([, a], [, b]) => a.sort - b.sort) + .map(([key, value]) => [key, value]); + return sortedGroups; }); watch(groupedProducts, () => { diff --git a/shared/types/directus/my-schema.ts b/shared/types/directus/my-schema.ts index e7c279d..0d4c1b0 100644 --- a/shared/types/directus/my-schema.ts +++ b/shared/types/directus/my-schema.ts @@ -142,6 +142,7 @@ export interface ProductType { id: number; /** @description 当前产品条目的状态 */ status?: 'published' | 'draft' | 'archived'; + sort?: number | null; /** @description i18n文本 */ translations?: ProductTypesTranslation[] | null; } @@ -231,6 +232,7 @@ export interface SolutionType { /** @primaryKey */ id: number; status?: 'published' | 'draft' | 'archived'; + sort?: number | null; /** @description i18n字段 */ translations?: SolutionTypesTranslation[] | null; } -- 2.49.0 From a34cfaff6f979fd5cdb1debb261392601d06bd42 Mon Sep 17 00:00:00 2001 From: R2m1liA <15258427350@163.com> Date: Sat, 8 Nov 2025 15:42:03 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3product-list?= =?UTF-8?q?=E7=9A=84=E9=81=8D=E5=8E=86=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/pages/products/index.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/pages/products/index.vue b/app/pages/products/index.vue index 667cb7e..57cfa31 100644 --- a/app/pages/products/index.vue +++ b/app/pages/products/index.vue @@ -8,7 +8,7 @@
a.sort - b.sort) - .map(([key, value]) => [key, value]); + const sortedGroups = Object.fromEntries( + Object.entries(groups).sort(([, a], [, b]) => a.sort - b.sort) + ); return sortedGroups; }); -- 2.49.0 From 308a080ea4e1d9b4acff825b5ce2cdc730393bbd Mon Sep 17 00:00:00 2001 From: R2m1liA <15258427350@163.com> Date: Sat, 8 Nov 2025 15:42:40 +0800 Subject: [PATCH 3/4] =?UTF-8?q?docs:=20=E4=B8=BAProductTypeView=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/views/ProductListView.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/views/ProductListView.ts b/app/models/views/ProductListView.ts index eec1b6f..b8511da 100644 --- a/app/models/views/ProductListView.ts +++ b/app/models/views/ProductListView.ts @@ -3,8 +3,13 @@ * 用于产品列表页的section渲染与排序 */ export interface ProductTypeView { + /** 唯一标识符 **/ id: number; + + /** 类型名 **/ name: string; + + /** 排序字段 **/ sort: number; } -- 2.49.0 From 0363a887853086be759b99e0159c1a2941ed361b Mon Sep 17 00:00:00 2001 From: R2m1liA <15258427350@163.com> Date: Sat, 8 Nov 2025 15:46:37 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=E8=A7=A3=E5=86=B3=E6=96=B9?= =?UTF-8?q?=E6=A1=88=E5=88=97=E8=A1=A8=E9=A1=B5=E6=A0=87=E7=AD=BE=E6=8E=92?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为SolutionType添加Sort字段用于排序 - 解决方案列表按照sort升序排序 --- app/composables/directus/useSolutionList.ts | 2 +- app/models/mappers/solutionMapper.test.ts | 14 +++++++-- app/models/mappers/solutionMapper.ts | 27 ++++++++++++++--- app/models/views/SolutionListView.ts | 17 ++++++++++- app/pages/solutions/index.vue | 33 ++++++++++----------- 5 files changed, 67 insertions(+), 26 deletions(-) diff --git a/app/composables/directus/useSolutionList.ts b/app/composables/directus/useSolutionList.ts index 030e272..8fe659f 100644 --- a/app/composables/directus/useSolutionList.ts +++ b/app/composables/directus/useSolutionList.ts @@ -12,7 +12,7 @@ export const useSolutionList = () => { 'id', 'cover', { - type: ['id', { translations: ['id', 'name'] }], + type: ['id', { translations: ['id', 'name'] }, 'sort'], }, { translations: ['id', 'title', 'summary'], diff --git a/app/models/mappers/solutionMapper.test.ts b/app/models/mappers/solutionMapper.test.ts index 1850d47..743414f 100644 --- a/app/models/mappers/solutionMapper.test.ts +++ b/app/models/mappers/solutionMapper.test.ts @@ -13,6 +13,7 @@ describe('toSolutionListView', () => { type: { id: 1, translations: [{ id: 1, name: 'Type Name' }], + sort: 1, }, }; @@ -20,7 +21,11 @@ describe('toSolutionListView', () => { id: 1, title: 'Solution Title', summary: 'Solution Summary', - solution_type: 'Type Name', + solution_type: { + id: 1, + name: 'Type Name', + sort: 1, + }, }); }); @@ -31,6 +36,7 @@ describe('toSolutionListView', () => { type: { id: 1, translations: [], + sort: null, }, }; @@ -38,7 +44,11 @@ describe('toSolutionListView', () => { id: 1, title: '', summary: '', - solution_type: '', + solution_type: { + id: 1, + name: '', + sort: 999, + }, }); }); }); diff --git a/app/models/mappers/solutionMapper.ts b/app/models/mappers/solutionMapper.ts index baf1694..d869024 100644 --- a/app/models/mappers/solutionMapper.ts +++ b/app/models/mappers/solutionMapper.ts @@ -1,3 +1,24 @@ +/** + * 将 Directus 返回的 SolutionType 数据转换为 SolutionTypeView 视图模型 + * + * @param raw: 原始的 SolutionType 数据 + * @returns 转换后的 SolutionTypeView 对象 + * + * --- + * + * @example + * const view = toSolutionTypeView(rawSolutionType); + */ +export function toSolutionTypeView(raw: SolutionType): SolutionTypeView { + const trans = raw.translations?.[0] ?? { name: '' }; + + return { + id: raw.id, + name: trans.name, + sort: raw?.sort ?? 999, + }; +} + /** * 将 Directus 返回的 Solution 数据转换为 SolutionListView 视图模型 * @@ -16,10 +37,8 @@ export function toSolutionListView(raw: Solution): SolutionListView { }; const type = isObject(raw.type) - ? raw.type.translations.length === 0 - ? '' - : raw.type.translations[0].name - : ''; + ? toSolutionTypeView(raw.type) + : undefined; return { id: raw.id, diff --git a/app/models/views/SolutionListView.ts b/app/models/views/SolutionListView.ts index fa41cfe..b688271 100644 --- a/app/models/views/SolutionListView.ts +++ b/app/models/views/SolutionListView.ts @@ -1,3 +1,18 @@ +/** + * 解决方案类型视图模型 + * 用于解决方案列表页标签栏的渲染与排序 + */ +export interface SolutionTypeView { + /** 唯一标识符 **/ + id: number; + + /** 类型名 **/ + name: string; + + /** 排序字段 **/ + sort: number; +} + /** * 解决方案列表模型 * 用于解决方案列表(/solutions)渲染的数据结构 @@ -13,7 +28,7 @@ export interface SolutionListView { summary: string; /** 解决方案类型 **/ - solution_type: string; + solution_type: SolutionTypeView; /** 解决方案封面(图片id) **/ cover: string; diff --git a/app/pages/solutions/index.vue b/app/pages/solutions/index.vue index 24f293d..33bf637 100644 --- a/app/pages/solutions/index.vue +++ b/app/pages/solutions/index.vue @@ -19,14 +19,14 @@
{ - const gourps: Record = {}; + const groups: Record = + {}; for (const sol of solutions.value) { - let typeKey = ''; - if (typeof sol.solution_type === 'string') { - typeKey = sol.solution_type; - } else if ( - sol.solution_type && - typeof sol.solution_type === 'object' && - 'type' in sol.solution_type - ) { - typeKey = sol.solution_type || ''; + const typeKey = sol.solution_type?.name ?? ''; + if (!groups[typeKey]) { + groups[typeKey] = { data: [], sort: sol.solution_type?.sort ?? 999 }; } - if (!gourps[typeKey]) gourps[typeKey] = []; - gourps[typeKey]?.push(sol); + groups[typeKey]?.data.push(sol); } - return gourps; + const sortedGroups = Object.fromEntries( + Object.entries(groups).sort(([, a], [, b]) => a.sort - b.sort) + ); + return sortedGroups; }); watch(error, (value) => { -- 2.49.0