refactor(catalog): 废弃 LEWAN_ 镜像,统一英文 SKU 与积分价,商品图按 SKU 缓存
对外目录只保留英文主 sku;迁移硬删镜像并校准 1元=100积分;发货页/凭证优先使用 local {sku}.webp。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,24 +1,56 @@
|
||||
/**
|
||||
* 解析商品图片地址:
|
||||
* 1. 本地相对路径(以 / 开头)直接返回;
|
||||
* 2. 乐玩/官方商品 UUID 地址,优先使用已下载至本地的图片缓存;
|
||||
* 3. 其他外链图片走后端持久化缓存代理 (/api/delivery/v1/image-proxy),杜绝跨域拦截与画布污染。
|
||||
* 商品图统一解析:优先英文 SKU 本地缓存。
|
||||
*
|
||||
* 约定目录:frontend/public/images/products/{sku}.webp
|
||||
* 同步:cd backend && go run ./cmd/sync_product_images
|
||||
*/
|
||||
export function resolveProductImage(url?: string | null): string {
|
||||
if (!url) return ''
|
||||
const trimmed = url.trim()
|
||||
if (!trimmed) return ''
|
||||
|
||||
if (trimmed.startsWith('/')) {
|
||||
return trimmed
|
||||
export type ProductImageInput = {
|
||||
sku?: string | null
|
||||
/** 上游缩略图 URL(乐玩/DLC);仅在无本地 SKU 图时作回退 */
|
||||
url?: string | null
|
||||
}
|
||||
|
||||
function trim(value?: string | null): string {
|
||||
return String(value || '').trim()
|
||||
}
|
||||
|
||||
/** 按商户英文 SKU 取本地缓存图。 */
|
||||
export function resolveProductImageBySku(sku?: string | null): string {
|
||||
const normalized = trim(sku)
|
||||
if (!normalized) return ''
|
||||
return `/images/products/${normalized}.webp`
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析展示用商品图:
|
||||
* 1. 有 sku → `/images/products/{sku}.webp`
|
||||
* 2. 否则从 url 提取乐玩 UUID → `/images/products/{uuid}.webp`(历史缓存)
|
||||
* 3. 其它外链 → image-proxy(运行时落盘到 data/uploads/products,仅兜底)
|
||||
*/
|
||||
export function resolveProductImage(input?: string | ProductImageInput | null): string {
|
||||
if (input == null) return ''
|
||||
|
||||
let sku = ''
|
||||
let url = ''
|
||||
if (typeof input === 'string') {
|
||||
url = trim(input)
|
||||
} else {
|
||||
sku = trim(input.sku)
|
||||
url = trim(input.url)
|
||||
}
|
||||
|
||||
// 匹配乐玩官方商品 ID:/products/<uuid>/...
|
||||
const uuidMatch = trimmed.match(/\/products\/([a-zA-Z0-9-]+)(?:\/|\.|$)/)
|
||||
if (uuidMatch && uuidMatch[1]) {
|
||||
if (sku) {
|
||||
return resolveProductImageBySku(sku)
|
||||
}
|
||||
|
||||
if (!url) return ''
|
||||
if (url.startsWith('/')) return url
|
||||
|
||||
const uuidMatch = url.match(/\/products\/([a-zA-Z0-9-]+)(?:\/|\.|$)/)
|
||||
if (uuidMatch?.[1]) {
|
||||
return `/images/products/${uuidMatch[1]}.webp`
|
||||
}
|
||||
|
||||
// 其它外部地址统一经由代理及本地磁盘缓存返回
|
||||
return `/api/delivery/v1/image-proxy?url=${encodeURIComponent(trimmed)}`
|
||||
return `/api/delivery/v1/image-proxy?url=${encodeURIComponent(url)}`
|
||||
}
|
||||
|
||||
@@ -89,12 +89,12 @@ export async function generateDeliveryVoucher(order: DeliveryOrderInfo): Promise
|
||||
ctx.lineWidth = 1
|
||||
strokeRoundRect(ctx, cardX + 40, prodCardY, cardW - 80, prodCardH, 16)
|
||||
|
||||
// 尝试加载商品图片(优先命中本地下载缓存)
|
||||
// 尝试加载商品图片(优先 SKU 本地缓存)
|
||||
let productImgLoaded = false
|
||||
if (order.product?.image) {
|
||||
const productImgURL = resolveProductImage({ sku: order.product?.sku, url: order.product?.image })
|
||||
if (productImgURL) {
|
||||
try {
|
||||
const imgURL = resolveProductImage(order.product.image)
|
||||
const img = await loadImage(imgURL)
|
||||
const img = await loadImage(productImgURL)
|
||||
const imgSize = 146
|
||||
const imgX = cardX + 64
|
||||
const imgY = prodCardY + 32
|
||||
|
||||
Reference in New Issue
Block a user