From 270e516f9425918a78b93fb9249680aa2db4753d Mon Sep 17 00:00:00 2001 From: yeuimu <2197651308@qq.com> Date: Fri, 28 Aug 2026 11:22:53 +0800 Subject: [PATCH] fix(public): media images are objects ({id,url,sortOrder}) - handle both shapes in mergeMedia --- apps/api/src/public/public.service.spec.ts | 12 +++++--- apps/api/src/public/public.service.ts | 33 ++++++++++++++-------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/apps/api/src/public/public.service.spec.ts b/apps/api/src/public/public.service.spec.ts index 9a68d22..e755447 100644 --- a/apps/api/src/public/public.service.spec.ts +++ b/apps/api/src/public/public.service.spec.ts @@ -435,7 +435,7 @@ describe('PublicService', () => { sizeChart: { rows: [{ sizeName: 'S', measurements: [{ key: 'chest', cm: '94' }] }, { sizeName: 'M', measurements: [{ key: 'chest', cm: '100' }] }] }, packageSpecs: { rows: [{ sizeName: 'S' }] }, options: { sizes: [{ name: 'S' }, { name: 'M' }] }, - media: { images: ['http://img/pri-a', 'http://img/pri-b'], primaryImageUrl: 'http://img/pri-a' }, + media: { images: [{ id: 'i1', url: 'http://img/pri-a', sortOrder: 0 }, { id: 'i2', url: 'http://img/pri-b', sortOrder: 1 }], primaryImageUrl: 'http://img/pri-a' }, }, }); // Secondary: duplicate Black|S with a DIFFERENT price (must be dropped, @@ -452,7 +452,7 @@ describe('PublicService', () => { sizeChart: { rows: [{ sizeName: 'XXXL', measurements: [{ key: 'chest', cm: '120' }] }] }, packageSpecs: { rows: [{ sizeName: 'M' }, { sizeName: 'XXXL' }] }, options: { sizes: [{ name: 'XXXL' }] }, - media: { images: ['http://img/pri-a', 'http://img/sec-x'], primaryImageUrl: 'http://img/pri-a' }, + media: { images: [{ id: 'i1', url: 'http://img/pri-a', sortOrder: 0 }, { id: 'i9', url: 'http://img/sec-x', sortOrder: 0 }], primaryImageUrl: 'http://img/pri-a' }, }, }); const mergedGood = await prisma.good.create({ @@ -486,9 +486,13 @@ describe('PublicService', () => { const optSizes = (detail.options as any).sizes.map((s: any) => s.name).sort(); expect(optSizes).toEqual(['M', 'S', 'XXXL']); // Media gallery: primary images first, secondary-only URL appended, - // duplicate URL (pri-a) kept once. + // duplicate URL (pri-a) kept once. Entries keep their object shape. const media = detail.media as any; - expect(media.images).toEqual(['http://img/pri-a', 'http://img/pri-b', 'http://img/sec-x']); + expect(media.images.map((i: any) => i.url)).toEqual([ + 'http://img/pri-a', + 'http://img/pri-b', + 'http://img/sec-x', + ]); expect(media.primaryImageUrl).toBe('http://img/pri-a'); } finally { await prisma.goodOriginGood.deleteMany({ where: { goodId: mergedGood.id } }); diff --git a/apps/api/src/public/public.service.ts b/apps/api/src/public/public.service.ts index 24d226a..d525bc1 100644 --- a/apps/api/src/public/public.service.ts +++ b/apps/api/src/public/public.service.ts @@ -499,26 +499,35 @@ export class PublicService { /** * Merge the gallery `media` across details: primary images first, * secondary-only image URLs appended (URL-deduped). `primaryImageUrl` - * stays the primary's. + * stays the primary's. Image entries keep their original shape + * (`{id,url,sortOrder}` objects or plain strings). */ private mergeMedia( primary: PublicGoodRow['originGood']['detail'], secondaries: NonNullable[], ): Record | null { - const toImages = (media: unknown): string[] => { - const images = (media as { images?: unknown } | null)?.images; - return Array.isArray(images) ? images.filter((u): u is string => typeof u === 'string') : []; + const imageUrl = (img: unknown): string | null => { + if (typeof img === 'string') return img; + if (img && typeof img === 'object' && typeof (img as { url?: unknown }).url === 'string') { + return (img as { url: string }).url; + } + return null; }; const primaryMedia = (primary?.media as Record | null) ?? null; - if (!primaryMedia && secondaries.length === 0) return null; - const images = toImages(primaryMedia); - const seen = new Set(images); + const primaryImages = Array.isArray(primaryMedia?.images) + ? (primaryMedia!.images as unknown[]) + : null; + if (!primaryImages && secondaries.length === 0) return null; + const images: unknown[] = primaryImages ? [...primaryImages] : []; + const seen = new Set(images.map(imageUrl).filter((u): u is string => Boolean(u))); for (const sec of secondaries) { - for (const url of toImages(sec.media)) { - if (!seen.has(url)) { - seen.add(url); - images.push(url); - } + const secMedia = sec.media as { images?: unknown } | null; + if (!Array.isArray(secMedia?.images)) continue; + for (const img of secMedia!.images as unknown[]) { + const url = imageUrl(img); + if (!url || seen.has(url)) continue; + seen.add(url); + images.push(img); } } if (images.length === 0) return null;