From ff3a1cb00bc50ba58e4a30275254d1e0a008984c Mon Sep 17 00:00:00 2001 From: adonis Date: Tue, 25 Aug 2026 20:49:53 +0330 Subject: [PATCH] fix(products): absolutize image URLs and preserve them on SSE merge Relative full_src now uses node_server, and empty live-list file payloads no longer wipe catalog images. Co-authored-by: Cursor --- src/utils/productImage.ts | 11 +++++- .../composables/useProductsPageData.ts | 37 +++++++++++++++---- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/utils/productImage.ts b/src/utils/productImage.ts index 64af457..5789f05 100644 --- a/src/utils/productImage.ts +++ b/src/utils/productImage.ts @@ -4,6 +4,7 @@ import mokaabImg from '@/assets/img/mokaabImg.svg' import sekkeImg from '@/assets/img/sekkeImg.svg' import parsianImg from '@/assets/img/parsianImg.svg' import venusImg from '@/assets/img/venousImg.svg' +import { getConfigApp } from '@/utils/config' export type ProductImageSource = { file?: { id?: number | null; src?: string | null; full_src?: string | null; url?: string | null } | null @@ -18,6 +19,14 @@ function isUsableImageSrc(src?: string | null): boolean { return value !== '' && value !== 'null' && value !== 'undefined' } +function absolutizeImageUrl(url: string): string { + if (/^https?:\/\//i.test(url)) return url + const base = String(getConfigApp('node_server') || '').replace(/\/$/, '') + const path = url.replace(/^\//, '') + if (!base) return url.startsWith('/') ? url : `/${url}` + return `${base}/${path}` +} + export function getProductFallbackImage(product?: ProductImageSource): string { const type = (product?.group_type ?? '').trim() if (!type) return defaultImg @@ -47,7 +56,7 @@ export function getProductFallbackImage(product?: ProductImageSource): string { export function resolveProductImageSrc(product?: ProductImageSource): string { const remoteSrc = product?.file?.full_src || product?.file?.url || product?.file?.src if (isUsableImageSrc(remoteSrc)) { - const url = remoteSrc!.trim() + const url = absolutizeImageUrl(remoteSrc!.trim()) const version = product?.file_id ?? product?.file?.id if (version != null && version !== '' && Number(version) > 0 && /^https?:\/\//i.test(url) && !/[?&]v=/.test(url)) { return `${url}${url.includes('?') ? '&' : '?'}v=${encodeURIComponent(String(version))}` diff --git a/src/views/base/products/composables/useProductsPageData.ts b/src/views/base/products/composables/useProductsPageData.ts index 206cfa3..e997ac2 100644 --- a/src/views/base/products/composables/useProductsPageData.ts +++ b/src/views/base/products/composables/useProductsPageData.ts @@ -57,22 +57,45 @@ function productFileId(product: Product): number { return Number(product.file_id ?? (product.file as { id?: number } | undefined)?.id ?? 0) } +function productHasDisplayImage(product: Product): boolean { + const file = product.file as { full_src?: string; url?: string; src?: string } | null | undefined + if (file && (file.full_src || file.url || file.src)) { + return true + } + return !!(product.img && /^https?:\/\//i.test(product.img)) +} + +/** استریم گاهی file خالی با همان file_id می‌فرستد — عکس خوب HTTP را پاک نکن. */ function mergeLiveProductList(incoming: Product[], current?: Product[]): Product[] { if (!current?.length) return incoming const currentById = new Map(current.map((item) => [item.id, item])) return incoming.map((item) => { const prev = currentById.get(item.id) - if (!prev || productFileId(prev) <= productFileId(item)) { - return item + if (!prev) return item + + const prevId = productFileId(prev) + const nextId = productFileId(item) + + if (!productHasDisplayImage(item) && productHasDisplayImage(prev)) { + return { + ...item, + file: prev.file, + file_id: prev.file_id ?? (prev.file as { id?: number } | undefined)?.id ?? item.file_id, + img: prev.img, + } } - return { - ...item, - file: prev.file, - file_id: prev.file_id, - img: prev.img, + if (prevId > nextId) { + return { + ...item, + file: prev.file, + file_id: prev.file_id, + img: prev.img, + } } + + return item }) }