ui: update ProductPerformanceProfitability page to enhance table views and tabs management
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import api from 'src/services/api'
|
||||
|
||||
const GROUPED_TTL_MS = 2 * 60 * 1000
|
||||
const IMAGE_TTL_MS = 15 * 60 * 1000
|
||||
|
||||
function normalizeUploadsPath (storagePath) {
|
||||
const raw = String(storagePath || '').trim()
|
||||
if (!raw) return ''
|
||||
const normalized = raw.replace(/\\/g, '/')
|
||||
const idx = normalized.toLowerCase().indexOf('/uploads/')
|
||||
if (idx >= 0) return normalized.slice(idx)
|
||||
if (normalized.toLowerCase().startsWith('uploads/')) return `/${normalized}`
|
||||
return ''
|
||||
}
|
||||
|
||||
function resolveProductImageUrl (item) {
|
||||
if (!item || typeof item !== 'object') return ''
|
||||
|
||||
const contentURL = String(item.content_url || item.ContentURL || '').trim()
|
||||
if (contentURL.startsWith('/api/')) return contentURL
|
||||
if (contentURL.startsWith('/')) return `/api${contentURL}`
|
||||
|
||||
const imageId = Number(item.id || item.ID || 0)
|
||||
if (Number.isFinite(imageId) && imageId > 0) return `/api/product-images/${imageId}/content`
|
||||
|
||||
const thumbURL = String(item.thumb_url || item.thumbUrl || '').trim()
|
||||
if (thumbURL) return thumbURL
|
||||
|
||||
const fullURL = String(item.full_url || item.fullUrl || '').trim()
|
||||
if (fullURL) return fullURL
|
||||
|
||||
const uploadsPath = normalizeUploadsPath(item.storage_path || item.storage || '')
|
||||
if (uploadsPath) return uploadsPath
|
||||
|
||||
const fileName = String(item.file_name || item.FileName || '').trim()
|
||||
return fileName ? `/uploads/image/${fileName}` : ''
|
||||
}
|
||||
|
||||
function sortedList (value) {
|
||||
if (!Array.isArray(value)) return []
|
||||
return value.map(x => String(x || '').trim()).filter(Boolean).sort()
|
||||
}
|
||||
|
||||
export const useProductPerformanceStore = defineStore('product-performance-store', {
|
||||
state: () => ({
|
||||
groupedRowsByKey: {},
|
||||
groupedLoadedAtByKey: {},
|
||||
groupedLoadingByKey: {},
|
||||
groupedInFlightByKey: {},
|
||||
imageUrlByKey: {},
|
||||
imageListByKey: {},
|
||||
imageLoadedAtByKey: {}
|
||||
}),
|
||||
|
||||
getters: {
|
||||
groupedRows: state => key => state.groupedRowsByKey[key] || [],
|
||||
hasImageKey: state => key => Object.prototype.hasOwnProperty.call(state.imageListByKey, key),
|
||||
imageUrl: state => key => state.imageUrlByKey[key] || '',
|
||||
imageList: state => key => state.imageListByKey[key] || []
|
||||
},
|
||||
|
||||
actions: {
|
||||
groupedCacheKey (params = {}) {
|
||||
return JSON.stringify({
|
||||
mode: String(params.mode || ''),
|
||||
groupLevels: sortedList(params.groupLevels),
|
||||
expandedKeys: sortedList(params.expandedKeys),
|
||||
limit: Number(params.limit || 0)
|
||||
})
|
||||
},
|
||||
|
||||
async fetchGroupedRows (params = {}, options = {}) {
|
||||
const key = this.groupedCacheKey(params)
|
||||
const now = Date.now()
|
||||
const loadedAt = Number(this.groupedLoadedAtByKey[key] || 0)
|
||||
const cached = this.groupedRowsByKey[key]
|
||||
if (!options.force && Array.isArray(cached) && now - loadedAt < GROUPED_TTL_MS) return cached
|
||||
if (this.groupedInFlightByKey[key]) return this.groupedInFlightByKey[key]
|
||||
|
||||
this.groupedLoadingByKey = { ...this.groupedLoadingByKey, [key]: true }
|
||||
const request = api.get('/pricing/product-performance/grouped', {
|
||||
params: {
|
||||
mode: params.mode,
|
||||
group_levels: Array.isArray(params.groupLevels) ? params.groupLevels.join(',') : params.groupLevels,
|
||||
expanded_keys: Array.isArray(params.expandedKeys) ? params.expandedKeys.join(',') : params.expandedKeys,
|
||||
limit: params.limit
|
||||
},
|
||||
timeout: 90000
|
||||
}).then(resp => {
|
||||
const rows = Array.isArray(resp?.data) ? resp.data : []
|
||||
this.groupedRowsByKey = { ...this.groupedRowsByKey, [key]: rows }
|
||||
this.groupedLoadedAtByKey = { ...this.groupedLoadedAtByKey, [key]: Date.now() }
|
||||
return rows
|
||||
}).finally(() => {
|
||||
const nextLoading = { ...this.groupedLoadingByKey }
|
||||
const nextInFlight = { ...this.groupedInFlightByKey }
|
||||
delete nextLoading[key]
|
||||
delete nextInFlight[key]
|
||||
this.groupedLoadingByKey = nextLoading
|
||||
this.groupedInFlightByKey = nextInFlight
|
||||
})
|
||||
|
||||
this.groupedInFlightByKey = { ...this.groupedInFlightByKey, [key]: request }
|
||||
return request
|
||||
},
|
||||
|
||||
setImageCache (key, urls = []) {
|
||||
const normalizedKey = String(key || '').trim()
|
||||
if (!normalizedKey) return
|
||||
const list = Array.isArray(urls) ? urls.filter(Boolean) : []
|
||||
this.imageListByKey = { ...this.imageListByKey, [normalizedKey]: list }
|
||||
this.imageUrlByKey = { ...this.imageUrlByKey, [normalizedKey]: list[0] || '' }
|
||||
this.imageLoadedAtByKey = { ...this.imageLoadedAtByKey, [normalizedKey]: Date.now() }
|
||||
},
|
||||
|
||||
async fetchImageBatch (items = [], options = {}) {
|
||||
const now = Date.now()
|
||||
const pending = []
|
||||
const seen = new Set()
|
||||
for (const item of items) {
|
||||
const key = String(item?.key || '').trim()
|
||||
if (!key || seen.has(key)) continue
|
||||
seen.add(key)
|
||||
const loadedAt = Number(this.imageLoadedAtByKey[key] || 0)
|
||||
if (!options.force && Object.prototype.hasOwnProperty.call(this.imageListByKey, key) && now - loadedAt < IMAGE_TTL_MS) continue
|
||||
pending.push({
|
||||
key,
|
||||
code: String(item?.code || '').trim(),
|
||||
dim1: String(item?.dim1 || '').trim(),
|
||||
dim3: String(item?.dim3 || '').trim()
|
||||
})
|
||||
}
|
||||
|
||||
if (!pending.length) {
|
||||
const keys = Array.from(seen.values())
|
||||
return {
|
||||
lists: Object.fromEntries(keys.map(key => [key, this.imageListByKey[key] || []])),
|
||||
urls: Object.fromEntries(keys.map(key => [key, this.imageUrlByKey[key] || '']))
|
||||
}
|
||||
}
|
||||
|
||||
const resp = await api.post('/product-images/batch', { items: pending }, { timeout: 60000 })
|
||||
const returned = new Set()
|
||||
const lists = {}
|
||||
const urlsByKey = {}
|
||||
for (const item of Array.isArray(resp?.data) ? resp.data : []) {
|
||||
const key = String(item?.key || '').trim()
|
||||
if (!key) continue
|
||||
returned.add(key)
|
||||
const urls = (Array.isArray(item.images) ? item.images : []).map(resolveProductImageUrl).filter(Boolean)
|
||||
this.setImageCache(key, urls)
|
||||
lists[key] = urls
|
||||
urlsByKey[key] = urls[0] || ''
|
||||
}
|
||||
for (const item of pending) {
|
||||
if (returned.has(item.key)) continue
|
||||
this.setImageCache(item.key, [])
|
||||
lists[item.key] = []
|
||||
urlsByKey[item.key] = ''
|
||||
}
|
||||
|
||||
return { lists, urls: urlsByKey }
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user