基类化
This commit is contained in:
parent
35928a6144
commit
dae1b2704f
|
|
@ -126,14 +126,14 @@ export async function GET(request: Request) {
|
|||
const { searchParams } = new URL(request.url)
|
||||
const medusaId = searchParams.get('medusaId')
|
||||
const payloadId = searchParams.get('payloadId')
|
||||
const collection = searchParams.get('collection')
|
||||
const collection = searchParams.get('collection') as 'products' | 'preorder-products' | null
|
||||
const forceUpdate = searchParams.get('forceUpdate') === 'true'
|
||||
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
// 同步单个商品(通过 Medusa ID)
|
||||
if (medusaId) {
|
||||
const result = await syncSingleProductByMedusaId(payload, medusaId, forceUpdate)
|
||||
const result = await syncSingleProductByMedusaId(payload, medusaId, forceUpdate, collection || undefined)
|
||||
return NextResponse.json(result)
|
||||
}
|
||||
|
||||
|
|
@ -166,13 +166,22 @@ export async function GET(request: Request) {
|
|||
/**
|
||||
* 通过 Medusa ID 同步单个商品
|
||||
*/
|
||||
async function syncSingleProductByMedusaId(payload: any, medusaId: string, forceUpdate: boolean) {
|
||||
async function syncSingleProductByMedusaId(
|
||||
payload: any,
|
||||
medusaId: string,
|
||||
forceUpdate: boolean,
|
||||
preferredCollection?: 'products' | 'preorder-products'
|
||||
) {
|
||||
console.log(`[Sync API] 🔄 开始同步产品: ${medusaId}`)
|
||||
console.log(`[Sync API] ⚙️ forceUpdate: ${forceUpdate}, preferredCollection: ${preferredCollection || 'auto'}`)
|
||||
|
||||
try {
|
||||
// 从 Medusa 获取商品数据
|
||||
const medusaProducts = await getAllMedusaProducts()
|
||||
const medusaProduct = medusaProducts.find((p) => p.id === medusaId)
|
||||
|
||||
if (!medusaProduct) {
|
||||
console.error(`[Sync API] ❌ Medusa 中未找到商品: ${medusaId}`)
|
||||
return {
|
||||
success: false,
|
||||
action: 'not_found',
|
||||
|
|
@ -180,20 +189,27 @@ async function syncSingleProductByMedusaId(payload: any, medusaId: string, force
|
|||
}
|
||||
}
|
||||
|
||||
// 确定应该同步到哪个 collection
|
||||
const targetCollection = getProductCollection(medusaProduct)
|
||||
console.log(`[Sync API] ✅ 找到 Medusa 产品: ${medusaProduct.title}`)
|
||||
|
||||
// 确定应该同步到哪个 collection(优先使用传入的 collection,否则自动判断)
|
||||
const targetCollection = preferredCollection || getProductCollection(medusaProduct)
|
||||
console.log(`[Sync API] 🎯 目标 collection: ${targetCollection}${preferredCollection ? ' (指定)' : ' (自动判断)'}`)
|
||||
|
||||
const otherCollection =
|
||||
targetCollection === 'preorder-products' ? 'products' : 'preorder-products'
|
||||
|
||||
// 转换数据
|
||||
const productData = transformMedusaProductToPayload(medusaProduct)
|
||||
const seedId = productData.seedId
|
||||
console.log(`[Sync API] 📦 产品数据: title=${productData.title}, seedId=${seedId}`)
|
||||
|
||||
// 使用新的查找函数(优先 seedId)
|
||||
const found = await findProductBySeedIdOrMedusaId(payload, seedId, medusaId)
|
||||
|
||||
// 如果在另一个 collection 中找到,需要移动
|
||||
if (found && found.collection !== targetCollection) {
|
||||
console.log(`[Sync API] 🚚 需要移动: ${found.collection} -> ${targetCollection}`)
|
||||
|
||||
await payload.delete({
|
||||
collection: found.collection,
|
||||
id: found.product.id,
|
||||
|
|
@ -204,6 +220,7 @@ async function syncSingleProductByMedusaId(payload: any, medusaId: string, force
|
|||
data: productData,
|
||||
})
|
||||
|
||||
console.log(`[Sync API] ✅ 移动成功, 新 ID: ${created.id}`)
|
||||
return {
|
||||
success: true,
|
||||
action: 'moved',
|
||||
|
|
@ -215,16 +232,19 @@ async function syncSingleProductByMedusaId(payload: any, medusaId: string, force
|
|||
|
||||
// 如果在目标 collection 中找到
|
||||
if (found) {
|
||||
console.log(`[Sync API] 🔎 在 ${found.collection} 中找到现有产品, ID: ${found.product.id}`)
|
||||
const existingProduct = found.product
|
||||
|
||||
// 如果存在且不强制更新,只更新空字段
|
||||
if (!forceUpdate) {
|
||||
// 合并数据(只更新空字段)
|
||||
console.log(`[Sync API] 🔄 模式: 只填充空字段`)
|
||||
// 合并数据(只曹新空字段)
|
||||
const mergedData = mergeProductData(existingProduct, productData, false)
|
||||
|
||||
// 如果没有需要更新的字段,跳过
|
||||
if (Object.keys(mergedData).length <= 2) {
|
||||
// 只有 lastSyncedAt 和 medusaId
|
||||
console.log(`[Sync API] ⏭️ 跳过: 所有字段都有值`)
|
||||
return {
|
||||
success: true,
|
||||
action: 'skipped',
|
||||
|
|
@ -234,6 +254,7 @@ async function syncSingleProductByMedusaId(payload: any, medusaId: string, force
|
|||
}
|
||||
}
|
||||
|
||||
console.log(`[Sync API] 📝 更新字段: ${Object.keys(mergedData).join(', ')}`)
|
||||
// 更新(只更新空字段)
|
||||
const updated = await payload.update({
|
||||
collection: targetCollection,
|
||||
|
|
@ -241,6 +262,7 @@ async function syncSingleProductByMedusaId(payload: any, medusaId: string, force
|
|||
data: mergedData,
|
||||
})
|
||||
|
||||
console.log(`[Sync API] ✅ 部分更新成功`)
|
||||
return {
|
||||
success: true,
|
||||
action: 'updated_partial',
|
||||
|
|
@ -250,6 +272,7 @@ async function syncSingleProductByMedusaId(payload: any, medusaId: string, force
|
|||
}
|
||||
}
|
||||
|
||||
console.log(`[Sync API] ⚡ 模式: 强制更新所有字段`)
|
||||
// 强制更新所有字段
|
||||
const updated = await payload.update({
|
||||
collection: targetCollection,
|
||||
|
|
@ -257,6 +280,7 @@ async function syncSingleProductByMedusaId(payload: any, medusaId: string, force
|
|||
data: productData,
|
||||
})
|
||||
|
||||
console.log(`[Sync API] ✅ 强制更新成功`)
|
||||
return {
|
||||
success: true,
|
||||
action: 'updated',
|
||||
|
|
@ -266,12 +290,14 @@ async function syncSingleProductByMedusaId(payload: any, medusaId: string, force
|
|||
}
|
||||
}
|
||||
|
||||
console.log(`[Sync API] ✨ 创建新产品`)
|
||||
// 不存在,创建新商品
|
||||
const created = await payload.create({
|
||||
collection: targetCollection,
|
||||
data: productData,
|
||||
})
|
||||
|
||||
console.log(`[Sync API] ✅ 创建成功, ID: ${created.id}`)
|
||||
return {
|
||||
success: true,
|
||||
action: 'created',
|
||||
|
|
@ -280,7 +306,7 @@ async function syncSingleProductByMedusaId(payload: any, medusaId: string, force
|
|||
collection: targetCollection,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error syncing product ${medusaId}:`, error)
|
||||
console.error(`[Sync API] ❌ 同步失败:`, error)
|
||||
return {
|
||||
success: false,
|
||||
action: 'error',
|
||||
|
|
@ -344,8 +370,8 @@ async function syncSingleProductByPayloadId(
|
|||
}
|
||||
}
|
||||
|
||||
// 使用 medusaId 同步
|
||||
return await syncSingleProductByMedusaId(payload, product.medusaId, forceUpdate)
|
||||
// 使用 medusaId 同步(保持在当前 collection)
|
||||
return await syncSingleProductByMedusaId(payload, product.medusaId, forceUpdate, foundCollection as 'products' | 'preorder-products')
|
||||
} catch (error) {
|
||||
console.error(`Error syncing product by Payload ID ${payloadId}:`, error)
|
||||
return {
|
||||
|
|
@ -545,7 +571,7 @@ export async function POST(request: Request) {
|
|||
const { medusaId, payloadId, collection = '', forceUpdate = true } = body
|
||||
|
||||
if (medusaId) {
|
||||
const result = await syncSingleProductByMedusaId(payload, medusaId, forceUpdate)
|
||||
const result = await syncSingleProductByMedusaId(payload, medusaId, forceUpdate, collection || undefined)
|
||||
return NextResponse.json(result)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { logAfterChange, logAfterDelete } from '../hooks/logAction'
|
||||
import { cacheAfterChange, cacheAfterDelete } from '../hooks/cacheInvalidation'
|
||||
import { ProductBaseFields, RelatedProductsField, TaobaoLinksField, MedusaAttributesTab } from './base/ProductBase'
|
||||
import {
|
||||
AlignFeature,
|
||||
BlocksFeature,
|
||||
|
|
@ -53,78 +54,7 @@ export const PreorderProducts: CollectionConfig = {
|
|||
{
|
||||
label: 'ℹ️ 基本信息',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'medusaId',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: {
|
||||
description: 'Medusa 商品 ID',
|
||||
readOnly: true,
|
||||
width: '60%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
required: true,
|
||||
defaultValue: 'draft',
|
||||
options: [
|
||||
{ label: '草稿', value: 'draft' },
|
||||
{ label: '已发布', value: 'published' },
|
||||
],
|
||||
admin: {
|
||||
description: '商品详情状态',
|
||||
width: '40%',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'seedId',
|
||||
type: 'text',
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: {
|
||||
description: 'Seed ID (从 Medusa 同步,用于数据绑定)',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
description: '商品标题(从 Medusa 同步)',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'thumbnail',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: '商品封面 URL(支持上传或输入 URL)',
|
||||
components: {
|
||||
Cell: '/components/cells/ThumbnailCell#ThumbnailCell',
|
||||
Field: '/components/fields/ThumbnailField#ThumbnailField',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'lastSyncedAt',
|
||||
type: 'date',
|
||||
admin: {
|
||||
description: '上次同步时间',
|
||||
readOnly: true,
|
||||
date: {
|
||||
displayFormat: 'yyyy-MM-dd HH:mm:ss',
|
||||
},
|
||||
},
|
||||
},
|
||||
...ProductBaseFields,
|
||||
{
|
||||
name: 'progress',
|
||||
type: 'ui',
|
||||
|
|
@ -170,15 +100,32 @@ export const PreorderProducts: CollectionConfig = {
|
|||
],
|
||||
},
|
||||
{
|
||||
name: 'preorderEndDate',
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'preorderStartDate',
|
||||
type: 'date',
|
||||
admin: {
|
||||
description: '预购结束日期(可选)',
|
||||
description: '预购开始日期(可选)',
|
||||
width: '50%',
|
||||
date: {
|
||||
displayFormat: 'yyyy-MM-dd HH:mm',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'preorderEndDate',
|
||||
type: 'date',
|
||||
admin: {
|
||||
description: '预购结束日期(可选)',
|
||||
width: '50%',
|
||||
date: {
|
||||
displayFormat: 'yyyy-MM-dd HH:mm',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'ui',
|
||||
name: 'refreshOrderCount',
|
||||
|
|
@ -276,31 +223,7 @@ export const PreorderProducts: CollectionConfig = {
|
|||
},
|
||||
{
|
||||
label: '🔗 相关商品',
|
||||
fields: [
|
||||
{
|
||||
name: 'relatedProducts',
|
||||
type: 'relationship',
|
||||
relationTo: ['preorder-products', 'products'],
|
||||
hasMany: true,
|
||||
admin: {
|
||||
description: '推荐的相关商品',
|
||||
components: {
|
||||
Field: '/components/fields/RelatedProductsField#RelatedProductsField',
|
||||
},
|
||||
},
|
||||
filterOptions: ({ relationTo, data }) => {
|
||||
// 过滤掉当前商品本身,避免自引用
|
||||
if (data?.id) {
|
||||
return {
|
||||
id: {
|
||||
not_equals: data.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
],
|
||||
fields: [RelatedProductsField],
|
||||
},
|
||||
{
|
||||
label: '📦 订单信息',
|
||||
|
|
@ -316,69 +239,10 @@ export const PreorderProducts: CollectionConfig = {
|
|||
},
|
||||
],
|
||||
},
|
||||
MedusaAttributesTab,
|
||||
{
|
||||
label: '🛒 淘宝链接',
|
||||
fields: [
|
||||
{
|
||||
name: 'taobaoLinks',
|
||||
type: 'array',
|
||||
label: '淘宝采购链接列表',
|
||||
admin: {
|
||||
description: '💡 管理淘宝采购链接(仅后台显示,不通过 API 暴露)',
|
||||
initCollapsed: false,
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => !!user,
|
||||
update: ({ req: { user } }) => !!user,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: '🔗 淘宝链接',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'https://item.taobao.com/...',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: '📝 标题',
|
||||
admin: {
|
||||
placeholder: '链接标题或商品名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'thumbnail',
|
||||
type: 'text',
|
||||
label: '🖼️ 缩略图 URL',
|
||||
admin: {
|
||||
placeholder: 'https://...',
|
||||
description: '淘宝商品图片地址',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'note',
|
||||
type: 'textarea',
|
||||
label: '📄 备注',
|
||||
admin: {
|
||||
placeholder: '其他备注信息...',
|
||||
rows: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ui',
|
||||
name: 'linkPreview',
|
||||
admin: {
|
||||
components: {
|
||||
Field: '/components/fields/TaobaoLinkPreview#TaobaoLinkPreview',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
fields: [TaobaoLinksField],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,26 +1,15 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { logAfterChange, logAfterDelete } from '../hooks/logAction'
|
||||
import { cacheAfterChange, cacheAfterDelete } from '../hooks/cacheInvalidation'
|
||||
import { ProductBaseFields, RelatedProductsField, TaobaoLinksField, MedusaAttributesTab } from './base/ProductBase'
|
||||
import {
|
||||
AlignFeature,
|
||||
BlocksFeature,
|
||||
BoldFeature,
|
||||
ChecklistFeature,
|
||||
HeadingFeature,
|
||||
IndentFeature,
|
||||
InlineCodeFeature,
|
||||
ItalicFeature,
|
||||
lexicalEditor,
|
||||
LinkFeature,
|
||||
OrderedListFeature,
|
||||
ParagraphFeature,
|
||||
RelationshipFeature,
|
||||
UnorderedListFeature,
|
||||
UploadFeature,
|
||||
FixedToolbarFeature,
|
||||
InlineToolbarFeature,
|
||||
HorizontalRuleFeature,
|
||||
BlockquoteFeature,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
export const Products: CollectionConfig = {
|
||||
|
|
@ -49,86 +38,7 @@ export const Products: CollectionConfig = {
|
|||
tabs: [
|
||||
{
|
||||
label: 'ℹ️ 基本信息',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'medusaId',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: {
|
||||
description: 'Medusa 商品 ID',
|
||||
readOnly: true,
|
||||
width: '60%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
required: true,
|
||||
defaultValue: 'draft',
|
||||
options: [
|
||||
{
|
||||
label: '草稿',
|
||||
value: 'draft',
|
||||
},
|
||||
{
|
||||
label: '已发布',
|
||||
value: 'published',
|
||||
},
|
||||
],
|
||||
admin: {
|
||||
description: '商品详情状态',
|
||||
width: '40%',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'seedId',
|
||||
type: 'text',
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: {
|
||||
description: 'Seed ID (恥从 Medusa 同步,用于数据绑定)',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
description: '商品标题(从 Medusa 同步)',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'thumbnail',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: '商品封面 URL(支持上传或输入 URL)',
|
||||
components: {
|
||||
Cell: '/components/cells/ThumbnailCell#ThumbnailCell',
|
||||
Field: '/components/fields/ThumbnailField#ThumbnailField',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'lastSyncedAt',
|
||||
type: 'date',
|
||||
admin: {
|
||||
description: '最后同步时间',
|
||||
readOnly: true,
|
||||
date: {
|
||||
displayFormat: 'yyyy-MM-dd HH:mm:ss',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
fields: ProductBaseFields,
|
||||
},
|
||||
{
|
||||
label: '📄 商品详情',
|
||||
|
|
@ -184,95 +94,12 @@ export const Products: CollectionConfig = {
|
|||
},
|
||||
{
|
||||
label: '🔗 关联信息',
|
||||
fields: [
|
||||
{
|
||||
name: 'relatedProducts',
|
||||
type: 'relationship',
|
||||
relationTo: ['products', 'preorder-products'],
|
||||
hasMany: true,
|
||||
admin: {
|
||||
description: '相关商品,支持搜索联想',
|
||||
components: {
|
||||
Field: '/components/fields/RelatedProductsField#RelatedProductsField',
|
||||
},
|
||||
},
|
||||
filterOptions: ({ relationTo, data }) => {
|
||||
// 过滤掉当前商品本身,避免自引用
|
||||
if (data?.id) {
|
||||
return {
|
||||
id: {
|
||||
not_equals: data.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
],
|
||||
fields: [RelatedProductsField],
|
||||
},
|
||||
MedusaAttributesTab,
|
||||
{
|
||||
label: '🛒 淘宝链接',
|
||||
fields: [
|
||||
{
|
||||
name: 'taobaoLinks',
|
||||
type: 'array',
|
||||
label: '淘宝采购链接列表',
|
||||
admin: {
|
||||
description: '💡 管理淘宝采购链接(仅后台显示,不通过 API 暴露)',
|
||||
initCollapsed: false,
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => !!user,
|
||||
update: ({ req: { user } }) => !!user,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: '🔗 淘宝链接',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'https://item.taobao.com/...',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: '📝 标题',
|
||||
admin: {
|
||||
placeholder: '链接标题或商品名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'thumbnail',
|
||||
type: 'text',
|
||||
label: '🖼️ 缩略图 URL',
|
||||
admin: {
|
||||
placeholder: 'https://...',
|
||||
description: '淘宝商品图片地址',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'note',
|
||||
type: 'textarea',
|
||||
label: '📄 备注',
|
||||
admin: {
|
||||
placeholder: '其他备注信息...',
|
||||
rows: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ui',
|
||||
name: 'linkPreview',
|
||||
admin: {
|
||||
components: {
|
||||
Field: '/components/fields/TaobaoLinkPreview#TaobaoLinkPreview',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
fields: [TaobaoLinksField],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,311 @@
|
|||
import type { Field, Tab } from 'payload'
|
||||
import {
|
||||
HeadingFeature,
|
||||
lexicalEditor,
|
||||
LinkFeature,
|
||||
UploadFeature,
|
||||
FixedToolbarFeature,
|
||||
InlineToolbarFeature,
|
||||
HorizontalRuleFeature,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
/**
|
||||
* 产品集合的共同字段基类
|
||||
* 用于 Products 和 PreorderProducts 集合
|
||||
*/
|
||||
export const ProductBaseFields: Field[] = [
|
||||
// ========== 基本信息字段 ==========
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'medusaId',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: {
|
||||
description: 'Medusa 商品 ID',
|
||||
readOnly: true,
|
||||
width: '60%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
required: true,
|
||||
defaultValue: 'draft',
|
||||
options: [
|
||||
{ label: '草稿', value: 'draft' },
|
||||
{ label: '已发布', value: 'published' },
|
||||
],
|
||||
admin: {
|
||||
description: '商品详情状态',
|
||||
width: '40%',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'seedId',
|
||||
type: 'text',
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: {
|
||||
description: 'Seed ID (从 Medusa 同步,用于数据绑定)',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
description: '商品标题(从 Medusa 同步)',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'thumbnail',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: '商品封面 URL(支持上传或输入 URL)',
|
||||
components: {
|
||||
Cell: '/components/cells/ThumbnailCell#ThumbnailCell',
|
||||
Field: '/components/fields/ThumbnailField#ThumbnailField',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'lastSyncedAt',
|
||||
type: 'date',
|
||||
admin: {
|
||||
description: '最后同步时间',
|
||||
readOnly: true,
|
||||
date: {
|
||||
displayFormat: 'yyyy-MM-dd HH:mm:ss',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Medusa 默认属性字段
|
||||
* 对应 Medusa 中的标签、类型、系列、分类、物理属性等
|
||||
*/
|
||||
export const MedusaAttributesFields: Field[] = [
|
||||
{
|
||||
name: 'tags',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: '产品标签(逗号分隔)',
|
||||
placeholder: '例如: 热门, 新品, 限量',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: '产品类型',
|
||||
placeholder: '例如: 外壳, PCB, 工具',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'collection',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: '产品系列',
|
||||
placeholder: '例如: Shell, Cartridge',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: '产品分类',
|
||||
placeholder: '例如: GBA, GBC, GB',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: '物理属性',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'height',
|
||||
type: 'number',
|
||||
admin: {
|
||||
description: '高度 (cm)',
|
||||
width: '25%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'width',
|
||||
type: 'number',
|
||||
admin: {
|
||||
description: '宽度 (cm)',
|
||||
width: '25%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'length',
|
||||
type: 'number',
|
||||
admin: {
|
||||
description: '长度 (cm)',
|
||||
width: '25%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'weight',
|
||||
type: 'number',
|
||||
admin: {
|
||||
description: '重量 (g)',
|
||||
width: '25%',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: '海关与物流',
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'midCode',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: 'MID 代码(制造商识别码)',
|
||||
placeholder: '例如: 1234567890',
|
||||
width: '33%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'hsCode',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: 'HS 代码(海关编码)',
|
||||
placeholder: '例如: 8523.49.00',
|
||||
width: '33%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'countryOfOrigin',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: '原产国',
|
||||
placeholder: '例如: CN, US, JP',
|
||||
width: '34%',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Medusa 属性 Tab
|
||||
* 包含所有 Medusa 默认的产品属性字段
|
||||
*/
|
||||
export const MedusaAttributesTab: Tab = {
|
||||
label: '🏷️ 属性',
|
||||
fields: MedusaAttributesFields,
|
||||
}
|
||||
|
||||
/**
|
||||
* 相关产品字段配置
|
||||
* 支持跨集合关联 (products 和 preorder-products)
|
||||
*/
|
||||
export const RelatedProductsField: Field = {
|
||||
name: 'relatedProducts',
|
||||
type: 'relationship',
|
||||
relationTo: ['products', 'preorder-products'],
|
||||
hasMany: true,
|
||||
admin: {
|
||||
description: '相关商品,支持搜索联想',
|
||||
components: {
|
||||
Field: '/components/fields/RelatedProductsField#RelatedProductsField',
|
||||
},
|
||||
},
|
||||
filterOptions: ({ relationTo, data }) => {
|
||||
// 过滤掉当前商品本身,避免自引用
|
||||
if (data?.id) {
|
||||
return {
|
||||
id: {
|
||||
not_equals: data.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 淘宝链接字段配置
|
||||
* 仅后台管理员可见,不通过 API 暴露
|
||||
*/
|
||||
export const TaobaoLinksField: Field = {
|
||||
name: 'taobaoLinks',
|
||||
type: 'array',
|
||||
label: '淘宝采购链接列表',
|
||||
admin: {
|
||||
description: '💡 管理淘宝采购链接(仅后台显示,不通过 API 暴露)',
|
||||
initCollapsed: false,
|
||||
},
|
||||
access: {
|
||||
read: ({ req: { user } }) => !!user,
|
||||
update: ({ req: { user } }) => !!user,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: '🔗 淘宝链接',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'https://item.taobao.com/...',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: '📝 标题',
|
||||
admin: {
|
||||
placeholder: '链接标题或商品名称',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'thumbnail',
|
||||
type: 'text',
|
||||
label: '🖼️ 缩略图 URL',
|
||||
admin: {
|
||||
placeholder: 'https://...',
|
||||
description: '淘宝商品图片地址',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'note',
|
||||
type: 'textarea',
|
||||
label: '📄 备注',
|
||||
admin: {
|
||||
placeholder: '其他备注信息...',
|
||||
rows: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ui',
|
||||
name: 'linkPreview',
|
||||
admin: {
|
||||
components: {
|
||||
Field: '/components/fields/TaobaoLinkPreview#TaobaoLinkPreview',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -1,19 +1,21 @@
|
|||
'use client'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
export const ThumbnailCell = (props: any) => {
|
||||
const pathname = usePathname()
|
||||
|
||||
// 从 URL 路径中提取 collection slug
|
||||
const collectionSlug = pathname?.includes('/preorder-products')
|
||||
? 'preorder-products'
|
||||
: 'products'
|
||||
|
||||
// 尝试从不同的 props 路径获取值
|
||||
const value = props.value || props.cellData || props.data
|
||||
const rowData = props.rowData || props.row
|
||||
|
||||
// 优先从 props 中获取 collection 信息(Payload Cell API)
|
||||
let collectionSlug = props.collectionConfig?.slug || props.field?.relationTo || props.collection
|
||||
|
||||
// 如果没有从 props 获取到,通过检查预购特有字段自动判断
|
||||
if (!collectionSlug) {
|
||||
// PreorderProducts 有 orderCount, preorderType 等特有字段
|
||||
const isPreorder = rowData?.orderCount !== undefined || rowData?.preorderType !== undefined
|
||||
collectionSlug = isPreorder ? 'preorder-products' : 'products'
|
||||
}
|
||||
|
||||
const isImage = typeof value === 'string' && value.match(/^https?:\/\/.+/)
|
||||
const editUrl = `/admin/collections/${collectionSlug}/${rowData?.id || ''}`
|
||||
|
||||
|
|
|
|||
|
|
@ -211,6 +211,9 @@ export function transformMedusaProductToPayload(product: MedusaProduct) {
|
|||
thumbnailUrl = product.images[0].url
|
||||
}
|
||||
|
||||
// 提取 tags(逗号分隔)
|
||||
const tags = product.tags?.map(tag => tag.value).join(', ') || null
|
||||
|
||||
return {
|
||||
medusaId: product.id,
|
||||
seedId: product.metadata?.seed_id || null,
|
||||
|
|
@ -219,6 +222,23 @@ export function transformMedusaProductToPayload(product: MedusaProduct) {
|
|||
thumbnail: thumbnailUrl || null,
|
||||
status: 'draft',
|
||||
lastSyncedAt: new Date().toISOString(),
|
||||
|
||||
// Medusa 默认属性
|
||||
tags: tags,
|
||||
type: product.type_id || null,
|
||||
collection: product.collection_id || null,
|
||||
category: product.metadata?.category || null,
|
||||
|
||||
// 物理属性(从 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,
|
||||
|
||||
// 海关与物流
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ export interface Product {
|
|||
*/
|
||||
status: 'draft' | 'published';
|
||||
/**
|
||||
* Seed ID (恥从 Medusa 同步,用于数据绑定)
|
||||
* Seed ID (从 Medusa 同步,用于数据绑定)
|
||||
*/
|
||||
seedId?: string | null;
|
||||
/**
|
||||
|
|
@ -254,6 +254,50 @@ export interface Product {
|
|||
}
|
||||
)[]
|
||||
| null;
|
||||
/**
|
||||
* 产品标签(逗号分隔)
|
||||
*/
|
||||
tags?: string | null;
|
||||
/**
|
||||
* 产品类型
|
||||
*/
|
||||
type?: string | null;
|
||||
/**
|
||||
* 产品系列
|
||||
*/
|
||||
collection?: string | null;
|
||||
/**
|
||||
* 产品分类
|
||||
*/
|
||||
category?: string | null;
|
||||
/**
|
||||
* 高度 (cm)
|
||||
*/
|
||||
height?: number | null;
|
||||
/**
|
||||
* 宽度 (cm)
|
||||
*/
|
||||
width?: number | null;
|
||||
/**
|
||||
* 长度 (cm)
|
||||
*/
|
||||
length?: number | null;
|
||||
/**
|
||||
* 重量 (g)
|
||||
*/
|
||||
weight?: number | null;
|
||||
/**
|
||||
* MID 代码(制造商识别码)
|
||||
*/
|
||||
midCode?: string | null;
|
||||
/**
|
||||
* HS 代码(海关编码)
|
||||
*/
|
||||
hsCode?: string | null;
|
||||
/**
|
||||
* 原产国
|
||||
*/
|
||||
countryOfOrigin?: string | null;
|
||||
/**
|
||||
* 💡 管理淘宝采购链接(仅后台显示,不通过 API 暴露)
|
||||
*/
|
||||
|
|
@ -301,7 +345,7 @@ export interface PreorderProduct {
|
|||
*/
|
||||
thumbnail?: string | null;
|
||||
/**
|
||||
* 上次同步时间
|
||||
* 最后同步时间
|
||||
*/
|
||||
lastSyncedAt?: string | null;
|
||||
/**
|
||||
|
|
@ -312,6 +356,10 @@ export interface PreorderProduct {
|
|||
* 众筹目标数量(0 表示以变体 max_orders 总和为准)
|
||||
*/
|
||||
fundingGoal: number;
|
||||
/**
|
||||
* 预购开始日期(可选)
|
||||
*/
|
||||
preorderStartDate?: string | null;
|
||||
/**
|
||||
* 预购结束日期(可选)
|
||||
*/
|
||||
|
|
@ -343,20 +391,64 @@ export interface PreorderProduct {
|
|||
[k: string]: unknown;
|
||||
} | null;
|
||||
/**
|
||||
* 推荐的相关商品
|
||||
* 相关商品,支持搜索联想
|
||||
*/
|
||||
relatedProducts?:
|
||||
| (
|
||||
| {
|
||||
relationTo: 'preorder-products';
|
||||
value: number | PreorderProduct;
|
||||
}
|
||||
| {
|
||||
relationTo: 'products';
|
||||
value: number | Product;
|
||||
}
|
||||
| {
|
||||
relationTo: 'preorder-products';
|
||||
value: number | PreorderProduct;
|
||||
}
|
||||
)[]
|
||||
| null;
|
||||
/**
|
||||
* 产品标签(逗号分隔)
|
||||
*/
|
||||
tags?: string | null;
|
||||
/**
|
||||
* 产品类型
|
||||
*/
|
||||
type?: string | null;
|
||||
/**
|
||||
* 产品系列
|
||||
*/
|
||||
collection?: string | null;
|
||||
/**
|
||||
* 产品分类
|
||||
*/
|
||||
category?: string | null;
|
||||
/**
|
||||
* 高度 (cm)
|
||||
*/
|
||||
height?: number | null;
|
||||
/**
|
||||
* 宽度 (cm)
|
||||
*/
|
||||
width?: number | null;
|
||||
/**
|
||||
* 长度 (cm)
|
||||
*/
|
||||
length?: number | null;
|
||||
/**
|
||||
* 重量 (g)
|
||||
*/
|
||||
weight?: number | null;
|
||||
/**
|
||||
* MID 代码(制造商识别码)
|
||||
*/
|
||||
midCode?: string | null;
|
||||
/**
|
||||
* HS 代码(海关编码)
|
||||
*/
|
||||
hsCode?: string | null;
|
||||
/**
|
||||
* 原产国
|
||||
*/
|
||||
countryOfOrigin?: string | null;
|
||||
/**
|
||||
* 💡 管理淘宝采购链接(仅后台显示,不通过 API 暴露)
|
||||
*/
|
||||
|
|
@ -821,6 +913,17 @@ export interface ProductsSelect<T extends boolean = true> {
|
|||
lastSyncedAt?: T;
|
||||
content?: T;
|
||||
relatedProducts?: T;
|
||||
tags?: T;
|
||||
type?: T;
|
||||
collection?: T;
|
||||
category?: T;
|
||||
height?: T;
|
||||
width?: T;
|
||||
length?: T;
|
||||
weight?: T;
|
||||
midCode?: T;
|
||||
hsCode?: T;
|
||||
countryOfOrigin?: T;
|
||||
taobaoLinks?:
|
||||
| T
|
||||
| {
|
||||
|
|
@ -846,11 +949,23 @@ export interface PreorderProductsSelect<T extends boolean = true> {
|
|||
lastSyncedAt?: T;
|
||||
preorderType?: T;
|
||||
fundingGoal?: T;
|
||||
preorderStartDate?: T;
|
||||
preorderEndDate?: T;
|
||||
orderCount?: T;
|
||||
fakeOrderCount?: T;
|
||||
description?: T;
|
||||
relatedProducts?: T;
|
||||
tags?: T;
|
||||
type?: T;
|
||||
collection?: T;
|
||||
category?: T;
|
||||
height?: T;
|
||||
width?: T;
|
||||
length?: T;
|
||||
weight?: T;
|
||||
midCode?: T;
|
||||
hsCode?: T;
|
||||
countryOfOrigin?: T;
|
||||
taobaoLinks?:
|
||||
| T
|
||||
| {
|
||||
|
|
|
|||
Loading…
Reference in New Issue