diff --git a/src/app/api/sync-medusa/route.ts b/src/app/api/sync-medusa/route.ts index be7bd7d..5f59604 100644 --- a/src/app/api/sync-medusa/route.ts +++ b/src/app/api/sync-medusa/route.ts @@ -89,7 +89,7 @@ function mergeProductData(existingProduct: any, newData: any, forceUpdate: boole mergedData.seedId = newData.seedId } - // 只在字段为空时更新 + // 只在字段为空时更新基础字段 if (!existingProduct.title) { mergedData.title = newData.title } @@ -103,6 +103,23 @@ function mergeProductData(existingProduct: any, newData: any, forceUpdate: boole mergedData.status = newData.status } + // Medusa 属性字段:总是更新(以 Medusa 为准) + mergedData.tags = newData.tags + mergedData.type = newData.type + mergedData.collection = newData.collection + mergedData.category = newData.category + + // 物理属性:总是更新 + mergedData.height = newData.height + mergedData.width = newData.width + mergedData.length = newData.length + mergedData.weight = newData.weight + + // 海关与物流:总是更新 + mergedData.midCode = newData.midCode + mergedData.hsCode = newData.hsCode + mergedData.countryOfOrigin = newData.countryOfOrigin + return mergedData } @@ -241,25 +258,12 @@ async function syncSingleProductByMedusaId( // 如果存在且不强制更新,只更新空字段 if (!forceUpdate) { - console.log(`[Sync API] 🔄 模式: 只填充空字段`) - // 合并数据(只曹新空字段) + console.log(`[Sync API] 🔄 模式: 只填充空字段,但 Medusa 属性总是更新`) + // 合并数据(只更新空字段,但 Medusa 属性总是更新) const mergedData = mergeProductData(existingProduct, productData, false) - // 如果没有需要更新的字段,跳过 - if (Object.keys(mergedData).length <= 2) { - // 只有 lastSyncedAt 和 medusaId - console.log(`[Sync API] ⏭️ 跳过: 所有字段都有值`) - return { - success: true, - action: 'skipped', - message: `商品 ${medusaId} 已存在于 ${targetCollection},且所有字段都有值`, - productId: existingProduct.id, - collection: targetCollection, - } - } - - console.log(`[Sync API] 📝 更新字段: ${Object.keys(mergedData).join(', ')}`) - // 更新(只更新空字段) + console.log(`[Sync API] 📝 更新字段(含 Medusa 属性): ${Object.keys(mergedData).join(', ')}`) + // 更新(只更新空字段 + Medusa 属性) const updated = await payload.update({ collection: targetCollection, id: existingProduct.id, @@ -395,25 +399,11 @@ async function syncAllProducts(payload: any, forceUpdate: boolean) { if (found) { const existingProduct = found.product - // 如果不强制更新,只更新空字段 + // 如果不强制更新,只更新空字段,但 Medusa 属性总是更新 if (!forceUpdate) { const mergedData = mergeProductData(existingProduct, productData, false) - // 如果没有需要更新的字段,跳过 - if (Object.keys(mergedData).length <= 2) { - // 只有 lastSyncedAt 和 medusaId - results.skipped++ - results.details.push({ - medusaId: medusaProduct.id, - seedId: seedId, - title: medusaProduct.title, - action: 'skipped', - collection: targetCollection, - }) - continue - } - - // 更新(只更新空字段) + // 更新(只更新空字段 + Medusa 属性) await payload.update({ collection: targetCollection, id: existingProduct.id, diff --git a/src/app/api/sync-product/route.ts b/src/app/api/sync-product/route.ts index 30d44fe..0c361d3 100644 --- a/src/app/api/sync-product/route.ts +++ b/src/app/api/sync-product/route.ts @@ -111,7 +111,7 @@ export async function POST(request: Request) { } } - let action: 'created' | 'updated' | 'updated_partial' | 'moved' | 'skipped' = 'created' + let action: 'created' | 'updated' | 'updated_partial' | 'moved' = 'created' let finalProduct: any // 如果在错误的 collection 中,需要移动 @@ -134,12 +134,13 @@ export async function POST(request: Request) { // 如果找到了并且在正确的 collection 中 else if (existingProduct) { if (!forceUpdate) { - // 只更新空字段 + // 只更新空字段,但 Medusa 属性字段总是更新 const mergedData: any = { lastSyncedAt: productData.lastSyncedAt, medusaId: productData.medusaId, } + // 基础字段:只更新空字段 if (!existingProduct.seedId && productData.seedId) { mergedData.seedId = productData.seedId } @@ -148,20 +149,30 @@ export async function POST(request: Request) { if (!existingProduct.thumbnail) mergedData.thumbnail = productData.thumbnail if (!existingProduct.status) mergedData.status = productData.status - // 如果没有需要更新的字段,跳过 - if (Object.keys(mergedData).length <= 2) { - console.log(`[Sync Product API] ⏭️ 跳过: 所有字段都有值`) - action = 'skipped' - finalProduct = existingProduct - } else { - console.log(`[Sync Product API] 📝 更新字段: ${Object.keys(mergedData).join(', ')}`) - finalProduct = await payload.update({ - collection: targetCollection, - id: existingProduct.id, - data: mergedData, - }) - action = 'updated_partial' - } + // Medusa 属性字段:总是更新(以 Medusa 为准) + mergedData.tags = productData.tags + mergedData.type = productData.type + mergedData.collection = productData.collection + mergedData.category = productData.category + + // 物理属性:总是更新 + mergedData.height = productData.height + mergedData.width = productData.width + mergedData.length = productData.length + mergedData.weight = productData.weight + + // 海关与物流:总是更新 + mergedData.midCode = productData.midCode + mergedData.hsCode = productData.hsCode + mergedData.countryOfOrigin = productData.countryOfOrigin + + console.log(`[Sync Product API] 📝 更新字段(含 Medusa 属性): ${Object.keys(mergedData).join(', ')}`) + finalProduct = await payload.update({ + collection: targetCollection, + id: existingProduct.id, + data: mergedData, + }) + action = 'updated_partial' } else { // 强制更新所有字段 console.log(`[Sync Product API] ⚡ 强制更新所有字段`) @@ -207,7 +218,7 @@ export async function POST(request: Request) { fakeOrderCount: finalProduct.fakeOrderCount, }), }, - message: `产品已${action === 'created' ? '创建' : action === 'moved' ? '移动' : action === 'skipped' ? '跳过' : '更新'}于 ${targetCollection}`, + message: `产品已${action === 'created' ? '创建' : action === 'moved' ? '移动' : '更新'}于 ${targetCollection}`, }) return addCorsHeaders(response, origin) diff --git a/src/collections/base/ProductBase.ts b/src/collections/base/ProductBase.ts index b0ae6c5..c9b7c40 100644 --- a/src/collections/base/ProductBase.ts +++ b/src/collections/base/ProductBase.ts @@ -92,43 +92,48 @@ export const ProductBaseFields: Field[] = [ /** * Medusa 默认属性字段 * 对应 Medusa 中的标签、类型、系列、分类、物理属性等 + * 这些字段从 Medusa 同步,在 Payload 中为只读 */ export const MedusaAttributesFields: Field[] = [ { name: 'tags', type: 'text', admin: { - description: '产品标签(逗号分隔)', + description: '产品标签(逗号分隔,从 Medusa 同步)', placeholder: '例如: 热门, 新品, 限量', + readOnly: true, }, }, { name: 'type', type: 'text', admin: { - description: '产品类型', + description: '产品类型(从 Medusa 同步)', placeholder: '例如: 外壳, PCB, 工具', + readOnly: true, }, }, { name: 'collection', type: 'text', admin: { - description: '产品系列', + description: '产品系列(从 Medusa 同步)', placeholder: '例如: Shell, Cartridge', + readOnly: true, }, }, { name: 'category', type: 'text', admin: { - description: '产品分类', + description: '产品分类(从 Medusa 同步)', placeholder: '例如: GBA, GBC, GB', + readOnly: true, }, }, { type: 'collapsible', - label: '物理属性', + label: '物理属性(只读)', fields: [ { type: 'row', @@ -137,32 +142,36 @@ export const MedusaAttributesFields: Field[] = [ name: 'height', type: 'number', admin: { - description: '高度 (cm)', + description: '高度 (cm),从 Medusa 同步', width: '25%', + readOnly: true, }, }, { name: 'width', type: 'number', admin: { - description: '宽度 (cm)', + description: '宽度 (cm),从 Medusa 同步', width: '25%', + readOnly: true, }, }, { name: 'length', type: 'number', admin: { - description: '长度 (cm)', + description: '长度 (cm),从 Medusa 同步', width: '25%', + readOnly: true, }, }, { name: 'weight', type: 'number', admin: { - description: '重量 (g)', + description: '重量 (g),从 Medusa 同步', width: '25%', + readOnly: true, }, }, ], @@ -171,7 +180,7 @@ export const MedusaAttributesFields: Field[] = [ }, { type: 'collapsible', - label: '海关与物流', + label: '海关与物流(只读)', fields: [ { type: 'row', @@ -180,27 +189,30 @@ export const MedusaAttributesFields: Field[] = [ name: 'midCode', type: 'text', admin: { - description: 'MID 代码(制造商识别码)', + description: 'MID 代码(制造商识别码,从 Medusa 同步)', placeholder: '例如: 1234567890', width: '33%', + readOnly: true, }, }, { name: 'hsCode', type: 'text', admin: { - description: 'HS 代码(海关编码)', + description: 'HS 代码(海关编码,从 Medusa 同步)', placeholder: '例如: 8523.49.00', width: '33%', + readOnly: true, }, }, { name: 'countryOfOrigin', type: 'text', admin: { - description: '原产国', + description: '原产国(从 Medusa 同步)', placeholder: '例如: CN, US, JP', width: '34%', + readOnly: true, }, }, ], diff --git a/src/lib/cors.ts b/src/lib/cors.ts index 8d06abe..12d6a38 100644 --- a/src/lib/cors.ts +++ b/src/lib/cors.ts @@ -6,8 +6,7 @@ import { NextResponse } from 'next/server' */ const ALLOWED_ORIGINS = [ 'http://localhost:9000', // Medusa 开发服务器 - 'http://localhost:7001', // Medusa Admin 默认端口 - 'http://localhost:7000', // Storefront 默认 端口 + 'http://localhost:8000', // Storefront 默认 端口 process.env.MEDUSA_URL, process.env.ADMIN_URL, ].filter(Boolean) as string[] diff --git a/src/lib/medusa.ts b/src/lib/medusa.ts index 6f1c92b..54f51d4 100644 --- a/src/lib/medusa.ts +++ b/src/lib/medusa.ts @@ -36,6 +36,31 @@ interface MedusaProduct { id: string value: string }> + // 完整的关联对象 + collection?: { + id: string + title: string + handle: string + } + type?: { + id: string + value: string + } + categories?: Array<{ + id: string + name: string + handle: string + }> + // 物理属性 + height?: number + width?: number + length?: number + weight?: number + // 海关与物流 + mid_code?: string + hs_code?: string + origin_country?: string + // 兼容旧字段 collection_id?: string type_id?: string } @@ -214,6 +239,15 @@ export function transformMedusaProductToPayload(product: MedusaProduct) { // 提取 tags(逗号分隔) const tags = product.tags?.map(tag => tag.value).join(', ') || null + // 提取 type(优先使用 type.value,否则使用 type_id 或 metadata) + const type = product.type?.value || product.type_id || product.metadata?.type || null + + // 提取 collection(优先使用 collection.title,否则使用 collection_id 或 metadata) + const collection = product.collection?.title || product.collection_id || product.metadata?.collection || null + + // 提取 category(从 categories 数组或 metadata) + const category = product.categories?.[0]?.name || product.metadata?.category || null + return { medusaId: product.id, seedId: product.metadata?.seed_id || null, @@ -225,20 +259,20 @@ export function transformMedusaProductToPayload(product: MedusaProduct) { // Medusa 默认属性 tags: tags, - type: product.type_id || null, - collection: product.collection_id || null, - category: product.metadata?.category || null, + type: type, + collection: collection, + category: category, - // 物理属性(从 metadata 中提取) - height: product.metadata?.height ? Number(product.metadata.height) : null, - width: product.metadata?.width ? Number(product.metadata.width) : null, - length: product.metadata?.length ? Number(product.metadata.length) : null, - weight: product.metadata?.weight ? Number(product.metadata.weight) : null, + // 物理属性(优先使用直接字段,否则从 metadata 中提取) + height: product.height || (product.metadata?.height ? Number(product.metadata.height) : null), + width: product.width || (product.metadata?.width ? Number(product.metadata.width) : null), + length: product.length || (product.metadata?.length ? Number(product.metadata.length) : null), + weight: product.weight || (product.metadata?.weight ? Number(product.metadata.weight) : null), - // 海关与物流 - midCode: product.metadata?.mid_code || product.metadata?.midCode || null, - hsCode: product.metadata?.hs_code || product.metadata?.hsCode || null, - countryOfOrigin: product.metadata?.country_of_origin || product.metadata?.countryOfOrigin || null, + // 海关与物流(优先使用直接字段,否则从 metadata 中提取) + midCode: product.mid_code || product.metadata?.mid_code || product.metadata?.midCode || null, + hsCode: product.hs_code || product.metadata?.hs_code || product.metadata?.hsCode || null, + countryOfOrigin: product.origin_country || product.metadata?.country_of_origin || product.metadata?.countryOfOrigin || null, } } diff --git a/src/payload-types.ts b/src/payload-types.ts index 3c49fb3..bef1a34 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -255,47 +255,47 @@ export interface Product { )[] | null; /** - * 产品标签(逗号分隔) + * 产品标签(逗号分隔,从 Medusa 同步) */ tags?: string | null; /** - * 产品类型 + * 产品类型(从 Medusa 同步) */ type?: string | null; /** - * 产品系列 + * 产品系列(从 Medusa 同步) */ collection?: string | null; /** - * 产品分类 + * 产品分类(从 Medusa 同步) */ category?: string | null; /** - * 高度 (cm) + * 高度 (cm),从 Medusa 同步 */ height?: number | null; /** - * 宽度 (cm) + * 宽度 (cm),从 Medusa 同步 */ width?: number | null; /** - * 长度 (cm) + * 长度 (cm),从 Medusa 同步 */ length?: number | null; /** - * 重量 (g) + * 重量 (g),从 Medusa 同步 */ weight?: number | null; /** - * MID 代码(制造商识别码) + * MID 代码(制造商识别码,从 Medusa 同步) */ midCode?: string | null; /** - * HS 代码(海关编码) + * HS 代码(海关编码,从 Medusa 同步) */ hsCode?: string | null; /** - * 原产国 + * 原产国(从 Medusa 同步) */ countryOfOrigin?: string | null; /** @@ -406,47 +406,47 @@ export interface PreorderProduct { )[] | null; /** - * 产品标签(逗号分隔) + * 产品标签(逗号分隔,从 Medusa 同步) */ tags?: string | null; /** - * 产品类型 + * 产品类型(从 Medusa 同步) */ type?: string | null; /** - * 产品系列 + * 产品系列(从 Medusa 同步) */ collection?: string | null; /** - * 产品分类 + * 产品分类(从 Medusa 同步) */ category?: string | null; /** - * 高度 (cm) + * 高度 (cm),从 Medusa 同步 */ height?: number | null; /** - * 宽度 (cm) + * 宽度 (cm),从 Medusa 同步 */ width?: number | null; /** - * 长度 (cm) + * 长度 (cm),从 Medusa 同步 */ length?: number | null; /** - * 重量 (g) + * 重量 (g),从 Medusa 同步 */ weight?: number | null; /** - * MID 代码(制造商识别码) + * MID 代码(制造商识别码,从 Medusa 同步) */ midCode?: string | null; /** - * HS 代码(海关编码) + * HS 代码(海关编码,从 Medusa 同步) */ hsCode?: string | null; /** - * 原产国 + * 原产国(从 Medusa 同步) */ countryOfOrigin?: string | null; /**