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 <cursoragent@cursor.com>
This commit is contained in:
2026-08-25 20:49:53 +03:30
co-authored by Cursor
parent 29579f80d3
commit ff3a1cb00b
2 changed files with 40 additions and 8 deletions
+10 -1
View File
@@ -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))}`
@@ -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
})
}