refactor(catalog): 废弃 LEWAN_ 镜像,统一英文 SKU 与积分价,商品图按 SKU 缓存

对外目录只保留英文主 sku;迁移硬删镜像并校准 1元=100积分;发货页/凭证优先使用 local {sku}.webp。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-09-14 20:00:26 +08:00
co-authored by Cursor
parent bf9614569e
commit df1a170d44
57 changed files with 1052 additions and 171 deletions
+47 -15
View File
@@ -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)}`
}
+4 -4
View File
@@ -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