Fix product performance grouped JSON build
This commit is contained in:
+21
@@ -227,6 +227,7 @@ InitRoutes — FULL V3 (Method-aware) PERMISSION EDITION
|
|||||||
func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router {
|
func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router {
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
|
mountUploads(r)
|
||||||
mountSPA(r)
|
mountSPA(r)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1517,6 +1518,26 @@ func main() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mountUploads(r *mux.Router) {
|
||||||
|
root := strings.TrimSpace(os.Getenv("BLOB_ROOT"))
|
||||||
|
if root == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uploadsRoot := filepath.Join(root, "uploads")
|
||||||
|
if fi, err := os.Stat(uploadsRoot); err != nil || !fi.IsDir() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileServer := http.StripPrefix("/uploads/", http.FileServer(http.Dir(uploadsRoot)))
|
||||||
|
r.PathPrefix("/uploads/").Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||||
|
fileServer.ServeHTTP(w, r)
|
||||||
|
})).Methods(http.MethodGet, http.MethodHead, http.MethodOptions)
|
||||||
|
}
|
||||||
|
|
||||||
func mountSPA(r *mux.Router) {
|
func mountSPA(r *mux.Router) {
|
||||||
r.NotFoundHandler = http.HandlerFunc(spaIndex)
|
r.NotFoundHandler = http.HandlerFunc(spaIndex)
|
||||||
r.HandleFunc("/", spaIndex).Methods(http.MethodGet)
|
r.HandleFunc("/", spaIndex).Methods(http.MethodGet)
|
||||||
|
|||||||
@@ -6129,6 +6129,10 @@ func productPerformanceMapVariantKey(row map[string]any) string {
|
|||||||
|
|
||||||
func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string) {
|
func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string) {
|
||||||
normalizeProductPerformanceCostFields(out)
|
normalizeProductPerformanceCostFields(out)
|
||||||
|
isGroup := strings.TrimSpace(groupField) != ""
|
||||||
|
if isGroup {
|
||||||
|
out["group_field"] = groupField
|
||||||
|
}
|
||||||
preservedCustomerScores := map[string]float64{}
|
preservedCustomerScores := map[string]float64{}
|
||||||
preservedProductScores := map[string]float64{}
|
preservedProductScores := map[string]float64{}
|
||||||
for _, suffix := range productPerformancePeriodSuffixes() {
|
for _, suffix := range productPerformancePeriodSuffixes() {
|
||||||
@@ -6149,7 +6153,7 @@ func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string)
|
|||||||
qty := floatFromMap(out, "sales_qty_"+suffix)
|
qty := floatFromMap(out, "sales_qty_"+suffix)
|
||||||
stockQty := floatFromMap(out, "stock_qty")
|
stockQty := floatFromMap(out, "stock_qty")
|
||||||
turnoverBase := floatFromMap(out, "avg_stock_"+suffix)
|
turnoverBase := floatFromMap(out, "avg_stock_"+suffix)
|
||||||
if turnoverBase <= 0 {
|
if turnoverBase <= 0 && !isGroup {
|
||||||
turnoverBase = stockQty
|
turnoverBase = stockQty
|
||||||
}
|
}
|
||||||
days := productPerformancePeriodDays(out, suffix)
|
days := productPerformancePeriodDays(out, suffix)
|
||||||
@@ -6381,7 +6385,7 @@ func productPerformanceSalesPeriodScore(row map[string]any, suffix string) float
|
|||||||
stockTurnover := floatFromMap(row, "stock_turnover_"+suffix)
|
stockTurnover := floatFromMap(row, "stock_turnover_"+suffix)
|
||||||
if stockTurnover == 0 {
|
if stockTurnover == 0 {
|
||||||
avgStock := floatFromMap(row, "avg_stock_"+suffix)
|
avgStock := floatFromMap(row, "avg_stock_"+suffix)
|
||||||
if avgStock <= 0 {
|
if avgStock <= 0 && strings.TrimSpace(stringFromMap(row, "group_field")) == "" {
|
||||||
avgStock = floatFromMap(row, "stock_qty")
|
avgStock = floatFromMap(row, "stock_qty")
|
||||||
}
|
}
|
||||||
if avgStock > 0 {
|
if avgStock > 0 {
|
||||||
|
|||||||
@@ -1289,12 +1289,12 @@ function normalizeUploadsPath (storagePath) {
|
|||||||
|
|
||||||
function resolveProductImageUrl (item) {
|
function resolveProductImageUrl (item) {
|
||||||
if (!item || typeof item !== 'object') return ''
|
if (!item || typeof item !== 'object') return ''
|
||||||
|
const contentUrl = toText(item.content_url || item.ContentURL)
|
||||||
|
if (contentUrl) return contentUrl.startsWith('/api/') ? contentUrl : contentUrl
|
||||||
const thumbUrl = toText(item.thumb_url || item.thumbUrl)
|
const thumbUrl = toText(item.thumb_url || item.thumbUrl)
|
||||||
if (thumbUrl) return thumbUrl
|
if (thumbUrl) return thumbUrl
|
||||||
const fullUrl = toText(item.full_url || item.fullUrl)
|
const fullUrl = toText(item.full_url || item.fullUrl)
|
||||||
if (fullUrl) return fullUrl
|
if (fullUrl) return fullUrl
|
||||||
const contentUrl = toText(item.content_url || item.ContentURL)
|
|
||||||
if (contentUrl) return contentUrl.startsWith('/api/') ? contentUrl : contentUrl
|
|
||||||
const uploadsPath = normalizeUploadsPath(item.storage_path || item.storage)
|
const uploadsPath = normalizeUploadsPath(item.storage_path || item.storage)
|
||||||
if (uploadsPath) return uploadsPath
|
if (uploadsPath) return uploadsPath
|
||||||
const fileName = toText(item.file_name || item.FileName)
|
const fileName = toText(item.file_name || item.FileName)
|
||||||
|
|||||||
@@ -3535,6 +3535,7 @@ function weightFieldForMetric (field) {
|
|||||||
|
|
||||||
function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
|
function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
|
||||||
Object.assign(out, normalizeProductCostFields(out))
|
Object.assign(out, normalizeProductCostFields(out))
|
||||||
|
const isGroup = Boolean(String(groupField || '').trim())
|
||||||
const preservedCustomerScores = {}
|
const preservedCustomerScores = {}
|
||||||
const preservedProductScores = {}
|
const preservedProductScores = {}
|
||||||
for (const suffix of ['90d', '180d', '365d', 'total']) {
|
for (const suffix of ['90d', '180d', '365d', 'total']) {
|
||||||
@@ -3556,7 +3557,7 @@ function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
|
|||||||
const qty = Number(out[`sales_qty_${suffix}`] || 0)
|
const qty = Number(out[`sales_qty_${suffix}`] || 0)
|
||||||
const stockQty = Number(out.stock_qty || 0)
|
const stockQty = Number(out.stock_qty || 0)
|
||||||
const avgStock = Number(out[`avg_stock_${suffix}`] || 0)
|
const avgStock = Number(out[`avg_stock_${suffix}`] || 0)
|
||||||
const turnoverBase = avgStock > 0 ? avgStock : stockQty
|
const turnoverBase = avgStock > 0 ? avgStock : (isGroup ? 0 : stockQty)
|
||||||
const days = productPerformancePeriodDays(out, suffix)
|
const days = productPerformancePeriodDays(out, suffix)
|
||||||
const avgDaily = days > 0 ? qty / days : Number(out[`avg_daily_sales_${suffix}`] || 0)
|
const avgDaily = days > 0 ? qty / days : Number(out[`avg_daily_sales_${suffix}`] || 0)
|
||||||
if (days > 0) out[`avg_daily_sales_${suffix}`] = avgDaily
|
if (days > 0) out[`avg_daily_sales_${suffix}`] = avgDaily
|
||||||
@@ -3660,7 +3661,9 @@ function productPeriodMetricSource (row, suffix) {
|
|||||||
const salesIndex = Number(row?.[`sales_index_${suffix}`] || 0)
|
const salesIndex = Number(row?.[`sales_index_${suffix}`] || 0)
|
||||||
const qty = Number(row?.[`sales_qty_${suffix}`] || 0)
|
const qty = Number(row?.[`sales_qty_${suffix}`] || 0)
|
||||||
const stockQty = Number(row?.stock_qty || 0)
|
const stockQty = Number(row?.stock_qty || 0)
|
||||||
const avgStock = Number(row?.[`avg_stock_${suffix}`] || 0) || stockQty
|
const avgStock = Number(row?.[`avg_stock_${suffix}`] || 0)
|
||||||
|
const isGroup = Boolean(row?.__group || String(row?.group_field || '').trim())
|
||||||
|
const turnoverBase = avgStock > 0 ? avgStock : (isGroup ? 0 : stockQty)
|
||||||
const turnoverKey = `stock_turnover_${suffix}`
|
const turnoverKey = `stock_turnover_${suffix}`
|
||||||
const hasTurnover = Object.prototype.hasOwnProperty.call(row || {}, turnoverKey)
|
const hasTurnover = Object.prototype.hasOwnProperty.call(row || {}, turnoverKey)
|
||||||
return {
|
return {
|
||||||
@@ -3670,7 +3673,7 @@ function productPeriodMetricSource (row, suffix) {
|
|||||||
gross_margin_cost_90d: Number(row?.[`gross_margin_cost_${suffix}`] ?? 0),
|
gross_margin_cost_90d: Number(row?.[`gross_margin_cost_${suffix}`] ?? 0),
|
||||||
gross_margin_90d: Number(row?.[`gross_margin_${suffix}`] ?? 0),
|
gross_margin_90d: Number(row?.[`gross_margin_${suffix}`] ?? 0),
|
||||||
sales_qty_90d: qty,
|
sales_qty_90d: qty,
|
||||||
stock_turnover_90d: hasTurnover ? Number(row?.[turnoverKey] || 0) : annualizedStockTurnover(qty, avgStock, productPerformancePeriodDays(row, suffix)),
|
stock_turnover_90d: hasTurnover ? Number(row?.[turnoverKey] || 0) : annualizedStockTurnover(qty, turnoverBase, productPerformancePeriodDays(row, suffix)),
|
||||||
market_count_90d: periodCount(row, 'market_count', suffix),
|
market_count_90d: periodCount(row, 'market_count', suffix),
|
||||||
customer_count_90d: periodCount(row, 'customer_count', suffix),
|
customer_count_90d: periodCount(row, 'customer_count', suffix),
|
||||||
stock_qty: stockQty
|
stock_qty: stockQty
|
||||||
@@ -4344,11 +4347,15 @@ function existingOrStockTurnover (value, salesQty, stockQty, periodDays) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function groupedOrExistingStockTurnover (row, suffix, periodDays) {
|
function groupedOrExistingStockTurnover (row, suffix, periodDays) {
|
||||||
const stockBase = Number(row?.[`avg_stock_${suffix}`] || row?.stock_qty || 0)
|
|
||||||
const salesQty = Number(row?.[`sales_qty_${suffix}`] || 0)
|
const salesQty = Number(row?.[`sales_qty_${suffix}`] || 0)
|
||||||
|
const avgStock = Number(row?.[`avg_stock_${suffix}`] || 0)
|
||||||
|
const stockQty = Number(row?.stock_qty || 0)
|
||||||
if (row?.__group) {
|
if (row?.__group) {
|
||||||
return stockTurnover(salesQty, stockBase, periodDays)
|
if (avgStock > 0) return stockTurnover(salesQty, avgStock, periodDays)
|
||||||
|
const current = Number(row?.[`stock_turnover_${suffix}`])
|
||||||
|
return Number.isFinite(current) ? current : 0
|
||||||
}
|
}
|
||||||
|
const stockBase = avgStock > 0 ? avgStock : stockQty
|
||||||
return existingOrStockTurnover(row?.[`stock_turnover_${suffix}`], salesQty, stockBase, periodDays)
|
return existingOrStockTurnover(row?.[`stock_turnover_${suffix}`], salesQty, stockBase, periodDays)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -423,7 +423,6 @@ const productImageListByCode = ref({})
|
|||||||
const productImageListLoading = ref({})
|
const productImageListLoading = ref({})
|
||||||
const productImageFallbackByKey = ref({})
|
const productImageFallbackByKey = ref({})
|
||||||
const productImageContentLoading = ref({})
|
const productImageContentLoading = ref({})
|
||||||
const productImageBlobUrls = ref([])
|
|
||||||
const productImageListBlockedUntil = ref(0)
|
const productImageListBlockedUntil = ref(0)
|
||||||
const productCardDialog = ref(false)
|
const productCardDialog = ref(false)
|
||||||
const productCardData = ref({})
|
const productCardData = ref({})
|
||||||
@@ -702,24 +701,13 @@ function clearGalleryQueryIndex() {
|
|||||||
|
|
||||||
async function resolveProductImageUrlForCarousel(item) {
|
async function resolveProductImageUrlForCarousel(item) {
|
||||||
const resolved = resolveProductImageUrl(item)
|
const resolved = resolveProductImageUrl(item)
|
||||||
|
const contentUrl = String(resolved.contentUrl || '').trim()
|
||||||
|
if (contentUrl) return contentUrl
|
||||||
|
|
||||||
const directUrl = String(resolved.fullUrl || resolved.thumbUrl || resolved.publicUrl || '').trim()
|
const directUrl = String(resolved.fullUrl || resolved.thumbUrl || resolved.publicUrl || '').trim()
|
||||||
if (directUrl) return directUrl
|
if (directUrl) return directUrl
|
||||||
|
|
||||||
const contentUrl = String(resolved.contentUrl || '').trim()
|
return ''
|
||||||
if (contentUrl) {
|
|
||||||
try {
|
|
||||||
const blobRes = await api.get(contentUrl, { baseURL: '', responseType: 'blob' })
|
|
||||||
const blob = blobRes?.data
|
|
||||||
if (blob instanceof Blob) {
|
|
||||||
const objectUrl = URL.createObjectURL(blob)
|
|
||||||
productImageBlobUrls.value.push(objectUrl)
|
|
||||||
return objectUrl
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// fall through to public url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return contentUrl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
||||||
@@ -769,9 +757,9 @@ async function flushProductImageBatch() {
|
|||||||
const list = Array.isArray(item.images) ? item.images : []
|
const list = Array.isArray(item.images) ? item.images : []
|
||||||
const first = list[0] || null
|
const first = list[0] || null
|
||||||
const resolved = resolveProductImageUrl(first)
|
const resolved = resolveProductImageUrl(first)
|
||||||
const url = resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || resolved.contentUrl || ''
|
const url = resolved.contentUrl || resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || ''
|
||||||
productImageCache.value[key] = String(url || '').trim()
|
productImageCache.value[key] = String(url || '').trim()
|
||||||
productImageFallbackByKey.value[key] = resolved.fullUrl || resolved.publicUrl || resolved.contentUrl || ''
|
productImageFallbackByKey.value[key] = resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || ''
|
||||||
if (queued) productImageListByCode.value[queued.listKey] = list
|
if (queued) productImageListByCode.value[queued.listKey] = list
|
||||||
delete productImageLoading.value[key]
|
delete productImageLoading.value[key]
|
||||||
}
|
}
|
||||||
@@ -860,11 +848,11 @@ async function ensureProductImage(code, color, secondColor = '', dim1Id = '', di
|
|||||||
|
|
||||||
productImageCache.value[key] = String(
|
productImageCache.value[key] = String(
|
||||||
preferredCardUrl ||
|
preferredCardUrl ||
|
||||||
primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl || primaryResolved.contentUrl ||
|
primaryResolved.contentUrl || primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl ||
|
||||||
secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl || secondaryResolved.contentUrl ||
|
secondaryResolved.contentUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl ||
|
||||||
''
|
''
|
||||||
).trim()
|
).trim()
|
||||||
productImageFallbackByKey.value[key] = primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.contentUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.contentUrl || ''
|
productImageFallbackByKey.value[key] = primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl || ''
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[ProductStockByAttributes] product image fetch failed', { code, color, err })
|
console.warn('[ProductStockByAttributes] product image fetch failed', { code, color, err })
|
||||||
productImageCache.value[key] = ''
|
productImageCache.value[key] = ''
|
||||||
@@ -1576,10 +1564,6 @@ onUnmounted(() => {
|
|||||||
clearTimeout(filterOptionsDebounceTimer)
|
clearTimeout(filterOptionsDebounceTimer)
|
||||||
filterOptionsDebounceTimer = null
|
filterOptionsDebounceTimer = null
|
||||||
}
|
}
|
||||||
for (const url of productImageBlobUrls.value) {
|
|
||||||
try { URL.revokeObjectURL(url) } catch {}
|
|
||||||
}
|
|
||||||
productImageBlobUrls.value = []
|
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -390,7 +390,6 @@ const productImageListByCode = ref({})
|
|||||||
const productImageListLoading = ref({})
|
const productImageListLoading = ref({})
|
||||||
const productImageFallbackByKey = ref({})
|
const productImageFallbackByKey = ref({})
|
||||||
const productImageContentLoading = ref({})
|
const productImageContentLoading = ref({})
|
||||||
const productImageBlobUrls = ref([])
|
|
||||||
const productImageListBlockedUntil = ref(0)
|
const productImageListBlockedUntil = ref(0)
|
||||||
const productCardDialog = ref(false)
|
const productCardDialog = ref(false)
|
||||||
const productCardData = ref({})
|
const productCardData = ref({})
|
||||||
@@ -681,24 +680,13 @@ function clearGalleryQueryIndex() {
|
|||||||
|
|
||||||
async function resolveProductImageUrlForCarousel(item) {
|
async function resolveProductImageUrlForCarousel(item) {
|
||||||
const resolved = resolveProductImageUrl(item)
|
const resolved = resolveProductImageUrl(item)
|
||||||
|
const contentUrl = String(resolved.contentUrl || '').trim()
|
||||||
|
if (contentUrl) return contentUrl
|
||||||
|
|
||||||
const directUrl = String(resolved.fullUrl || resolved.thumbUrl || resolved.publicUrl || '').trim()
|
const directUrl = String(resolved.fullUrl || resolved.thumbUrl || resolved.publicUrl || '').trim()
|
||||||
if (directUrl) return directUrl
|
if (directUrl) return directUrl
|
||||||
|
|
||||||
const contentUrl = String(resolved.contentUrl || '').trim()
|
return ''
|
||||||
if (contentUrl) {
|
|
||||||
try {
|
|
||||||
const blobRes = await api.get(contentUrl, { baseURL: '', responseType: 'blob' })
|
|
||||||
const blob = blobRes?.data
|
|
||||||
if (blob instanceof Blob) {
|
|
||||||
const objectUrl = URL.createObjectURL(blob)
|
|
||||||
productImageBlobUrls.value.push(objectUrl)
|
|
||||||
return objectUrl
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// fall through to public url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return contentUrl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
||||||
@@ -748,9 +736,9 @@ async function flushProductImageBatch() {
|
|||||||
const list = Array.isArray(item.images) ? item.images : []
|
const list = Array.isArray(item.images) ? item.images : []
|
||||||
const first = list[0] || null
|
const first = list[0] || null
|
||||||
const resolved = resolveProductImageUrl(first)
|
const resolved = resolveProductImageUrl(first)
|
||||||
const url = resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || resolved.contentUrl || ''
|
const url = resolved.contentUrl || resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || ''
|
||||||
productImageCache.value[key] = String(url || '').trim()
|
productImageCache.value[key] = String(url || '').trim()
|
||||||
productImageFallbackByKey.value[key] = resolved.fullUrl || resolved.publicUrl || resolved.contentUrl || ''
|
productImageFallbackByKey.value[key] = resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || ''
|
||||||
if (queued) productImageListByCode.value[queued.listKey] = list
|
if (queued) productImageListByCode.value[queued.listKey] = list
|
||||||
delete productImageLoading.value[key]
|
delete productImageLoading.value[key]
|
||||||
}
|
}
|
||||||
@@ -836,11 +824,11 @@ async function ensureProductImage(code, color, secondColor = '', dim1Id = '', di
|
|||||||
const secondaryResolved = resolveProductImageUrl(secondaryItem)
|
const secondaryResolved = resolveProductImageUrl(secondaryItem)
|
||||||
productImageCache.value[key] = String(
|
productImageCache.value[key] = String(
|
||||||
preferredCardUrl ||
|
preferredCardUrl ||
|
||||||
primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl || primaryResolved.contentUrl ||
|
primaryResolved.contentUrl || primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl ||
|
||||||
secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl || secondaryResolved.contentUrl ||
|
secondaryResolved.contentUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl ||
|
||||||
''
|
''
|
||||||
).trim()
|
).trim()
|
||||||
productImageFallbackByKey.value[key] = primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.contentUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.contentUrl || ''
|
productImageFallbackByKey.value[key] = primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl || ''
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[ProductStockQuery] product image fetch failed', { code, color, err })
|
console.warn('[ProductStockQuery] product image fetch failed', { code, color, err })
|
||||||
productImageCache.value[key] = ''
|
productImageCache.value[key] = ''
|
||||||
@@ -1374,10 +1362,6 @@ function resetForm() {
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('mousemove', onFullscreenMouseMove)
|
window.removeEventListener('mousemove', onFullscreenMouseMove)
|
||||||
window.removeEventListener('mouseup', onFullscreenMouseUp)
|
window.removeEventListener('mouseup', onFullscreenMouseUp)
|
||||||
for (const url of productImageBlobUrls.value) {
|
|
||||||
try { URL.revokeObjectURL(url) } catch {}
|
|
||||||
}
|
|
||||||
productImageBlobUrls.value = []
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user