预购精简
This commit is contained in:
parent
af1023c3d7
commit
397dcb93ae
|
|
@ -28,6 +28,7 @@ import { RelatedProductsField as RelatedProductsField_f3e26ca26ab1ef52a2ee0f6932
|
||||||
import { SyncMedusaButton as SyncMedusaButton_31e6578e170fdd0bad7013c8202d6e08 } from '../../../components/sync/SyncMedusaButton'
|
import { SyncMedusaButton as SyncMedusaButton_31e6578e170fdd0bad7013c8202d6e08 } from '../../../components/sync/SyncMedusaButton'
|
||||||
import { default as default_c2e3814fe427263135b1f5931c37f6f2 } from '../../../components/list/ProductGridStyler'
|
import { default as default_c2e3814fe427263135b1f5931c37f6f2 } from '../../../components/list/ProductGridStyler'
|
||||||
import { ForceSyncButton as ForceSyncButton_28396efe36d6238add95cf44109e281c } from '../../../components/sync/ForceSyncButton'
|
import { ForceSyncButton as ForceSyncButton_28396efe36d6238add95cf44109e281c } from '../../../components/sync/ForceSyncButton'
|
||||||
|
import { BlocksFeatureClient as BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||||
import { default as default_767734c8b7b095ea28d54c32abcf46e4 } from '../../../components/views/AdminPanel'
|
import { default as default_767734c8b7b095ea28d54c32abcf46e4 } from '../../../components/views/AdminPanel'
|
||||||
import { default as default_a766ef013722c08f9bb937940272cb5f } from '../../../components/views/LogsManagerView'
|
import { default as default_a766ef013722c08f9bb937940272cb5f } from '../../../components/views/LogsManagerView'
|
||||||
import { S3ClientUploadHandler as S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24 } from '@payloadcms/storage-s3/client'
|
import { S3ClientUploadHandler as S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24 } from '@payloadcms/storage-s3/client'
|
||||||
|
|
@ -64,6 +65,7 @@ export const importMap = {
|
||||||
"/components/sync/SyncMedusaButton#SyncMedusaButton": SyncMedusaButton_31e6578e170fdd0bad7013c8202d6e08,
|
"/components/sync/SyncMedusaButton#SyncMedusaButton": SyncMedusaButton_31e6578e170fdd0bad7013c8202d6e08,
|
||||||
"/components/list/ProductGridStyler#default": default_c2e3814fe427263135b1f5931c37f6f2,
|
"/components/list/ProductGridStyler#default": default_c2e3814fe427263135b1f5931c37f6f2,
|
||||||
"/components/sync/ForceSyncButton#ForceSyncButton": ForceSyncButton_28396efe36d6238add95cf44109e281c,
|
"/components/sync/ForceSyncButton#ForceSyncButton": ForceSyncButton_28396efe36d6238add95cf44109e281c,
|
||||||
|
"@payloadcms/richtext-lexical/client#BlocksFeatureClient": BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
"/components/views/AdminPanel#default": default_767734c8b7b095ea28d54c32abcf46e4,
|
"/components/views/AdminPanel#default": default_767734c8b7b095ea28d54c32abcf46e4,
|
||||||
"/components/views/LogsManagerView#default": default_a766ef013722c08f9bb937940272cb5f,
|
"/components/views/LogsManagerView#default": default_a766ef013722c08f9bb937940272cb5f,
|
||||||
"@payloadcms/storage-s3/client#S3ClientUploadHandler": S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24,
|
"@payloadcms/storage-s3/client#S3ClientUploadHandler": S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24,
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,17 @@ import { getCache, setCache } from '@/lib/redis'
|
||||||
/**
|
/**
|
||||||
* GET /api/public/products/[id]
|
* GET /api/public/products/[id]
|
||||||
* 获取单个产品详情(带缓存)
|
* 获取单个产品详情(带缓存)
|
||||||
|
* 支持参数:
|
||||||
|
* - collection: 'preorder-products' | 'products' (可选,如不指定则自动搜索)
|
||||||
*/
|
*/
|
||||||
export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
|
export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
|
||||||
try {
|
try {
|
||||||
const { id } = params
|
const { id } = params
|
||||||
|
const searchParams = req.nextUrl.searchParams
|
||||||
|
const collection = searchParams.get('collection')
|
||||||
|
|
||||||
// 生成缓存 key
|
// 生成缓存 key
|
||||||
const cacheKey = `products:detail:${id}`
|
const cacheKey = `products:detail:${id}:collection=${collection || 'auto'}`
|
||||||
|
|
||||||
// 尝试从缓存获取
|
// 尝试从缓存获取
|
||||||
const cached = await getCache(cacheKey)
|
const cached = await getCache(cacheKey)
|
||||||
|
|
@ -24,13 +28,50 @@ export async function GET(req: NextRequest, { params }: { params: { id: string }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从数据库获取
|
|
||||||
const payload = await getPayload({ config })
|
const payload = await getPayload({ config })
|
||||||
const result = await payload.findByID({
|
let result: any = null
|
||||||
collection: 'products',
|
let foundCollection: string = ''
|
||||||
id,
|
|
||||||
depth: 2,
|
if (collection) {
|
||||||
})
|
// 如果指定了 collection,直接查询
|
||||||
|
try {
|
||||||
|
result = await payload.findByID({
|
||||||
|
collection: collection as any,
|
||||||
|
id,
|
||||||
|
depth: 2,
|
||||||
|
})
|
||||||
|
foundCollection = collection
|
||||||
|
} catch {
|
||||||
|
// 找不到
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 自动搜索各个 collection
|
||||||
|
const collections = ['preorder-products', 'products']
|
||||||
|
|
||||||
|
for (const col of collections) {
|
||||||
|
try {
|
||||||
|
result = await payload.findByID({
|
||||||
|
collection: col as any,
|
||||||
|
id,
|
||||||
|
depth: 2,
|
||||||
|
})
|
||||||
|
foundCollection = col
|
||||||
|
break
|
||||||
|
} catch {
|
||||||
|
// 继续尝试下一个
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: 'Product not found',
|
||||||
|
},
|
||||||
|
{ status: 404 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 只有已发布的产品才返回
|
// 只有已发布的产品才返回
|
||||||
if (result.status !== 'published') {
|
if (result.status !== 'published') {
|
||||||
|
|
@ -43,12 +84,18 @@ export async function GET(req: NextRequest, { params }: { params: { id: string }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加 collection 信息
|
||||||
|
const resultWithMeta = {
|
||||||
|
...result,
|
||||||
|
_collection: foundCollection,
|
||||||
|
}
|
||||||
|
|
||||||
// 缓存结果(1 小时)
|
// 缓存结果(1 小时)
|
||||||
await setCache(cacheKey, result, 3600)
|
await setCache(cacheKey, resultWithMeta, 3600)
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: result,
|
data: resultWithMeta,
|
||||||
cached: false,
|
cached: false,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,11 @@ import { getCache, setCache } from '@/lib/redis'
|
||||||
/**
|
/**
|
||||||
* GET /api/public/products
|
* GET /api/public/products
|
||||||
* 获取产品列表(带缓存)
|
* 获取产品列表(带缓存)
|
||||||
|
* 支持参数:
|
||||||
|
* - type: 'preorder' | 'order' | 'all' (默认 'all')
|
||||||
|
* - page: 页码
|
||||||
|
* - limit: 每页数量
|
||||||
|
* - status: 'draft' | 'published' (默认 'published')
|
||||||
*/
|
*/
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -13,9 +18,10 @@ export async function GET(req: NextRequest) {
|
||||||
const page = parseInt(searchParams.get('page') || '1', 10)
|
const page = parseInt(searchParams.get('page') || '1', 10)
|
||||||
const limit = parseInt(searchParams.get('limit') || '10', 10)
|
const limit = parseInt(searchParams.get('limit') || '10', 10)
|
||||||
const status = searchParams.get('status') || 'published'
|
const status = searchParams.get('status') || 'published'
|
||||||
|
const type = searchParams.get('type') || 'all'
|
||||||
|
|
||||||
// 生成缓存 key
|
// 生成缓存 key
|
||||||
const cacheKey = `products:list:page=${page}:limit=${limit}:status=${status}`
|
const cacheKey = `products:list:type=${type}:page=${page}:limit=${limit}:status=${status}`
|
||||||
|
|
||||||
// 尝试从缓存获取
|
// 尝试从缓存获取
|
||||||
const cached = await getCache(cacheKey)
|
const cached = await getCache(cacheKey)
|
||||||
|
|
@ -27,17 +33,74 @@ export async function GET(req: NextRequest) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从数据库获取
|
|
||||||
const payload = await getPayload({ config })
|
const payload = await getPayload({ config })
|
||||||
const result = await payload.find({
|
const where = { status: { equals: status } }
|
||||||
collection: 'products',
|
|
||||||
where: {
|
let result
|
||||||
status: { equals: status },
|
|
||||||
},
|
if (type === 'all') {
|
||||||
page,
|
// 查询所有类型
|
||||||
limit,
|
const [preorders, products] = await Promise.all([
|
||||||
depth: 1,
|
payload.find({
|
||||||
})
|
collection: 'preorder-products',
|
||||||
|
where,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
depth: 1,
|
||||||
|
}),
|
||||||
|
payload.find({
|
||||||
|
collection: 'products',
|
||||||
|
where,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
depth: 1,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
// 合并结果
|
||||||
|
result = {
|
||||||
|
docs: [
|
||||||
|
...preorders.docs.map((doc) => ({ ...doc, _type: 'preorder-products' })),
|
||||||
|
...products.docs.map((doc) => ({ ...doc, _type: 'products' })),
|
||||||
|
],
|
||||||
|
totalDocs: preorders.totalDocs + products.totalDocs,
|
||||||
|
limit,
|
||||||
|
page,
|
||||||
|
totalPages: Math.ceil(
|
||||||
|
(preorders.totalDocs + products.totalDocs) / limit,
|
||||||
|
),
|
||||||
|
hasNextPage:
|
||||||
|
page < Math.ceil((preorders.totalDocs + products.totalDocs) / limit),
|
||||||
|
hasPrevPage: page > 1,
|
||||||
|
}
|
||||||
|
} else if (type === 'preorder') {
|
||||||
|
// 只查询预售商品
|
||||||
|
result = await payload.find({
|
||||||
|
collection: 'preorder-products',
|
||||||
|
where,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
depth: 1,
|
||||||
|
})
|
||||||
|
} else if (type === 'order') {
|
||||||
|
// 只查询现货商品
|
||||||
|
result = await payload.find({
|
||||||
|
collection: 'products',
|
||||||
|
where,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
depth: 1,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 旧的 products collection
|
||||||
|
result = await payload.find({
|
||||||
|
collection: 'products',
|
||||||
|
where,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
depth: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 缓存结果(1 小时)
|
// 缓存结果(1 小时)
|
||||||
await setCache(cacheKey, result, 3600)
|
await setCache(cacheKey, result, 3600)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
getAllMedusaProducts,
|
getAllMedusaProducts,
|
||||||
transformMedusaProductToPayload,
|
transformMedusaProductToPayload,
|
||||||
getMedusaProductsPaginated,
|
getMedusaProductsPaginated,
|
||||||
|
getProductCollection,
|
||||||
} from '@/lib/medusa'
|
} from '@/lib/medusa'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -18,6 +19,7 @@ export async function GET(request: Request) {
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
const medusaId = searchParams.get('medusaId')
|
const medusaId = searchParams.get('medusaId')
|
||||||
const payloadId = searchParams.get('payloadId')
|
const payloadId = searchParams.get('payloadId')
|
||||||
|
const collection = searchParams.get('collection')
|
||||||
const forceUpdate = searchParams.get('forceUpdate') === 'true'
|
const forceUpdate = searchParams.get('forceUpdate') === 'true'
|
||||||
|
|
||||||
const payload = await getPayload({ config })
|
const payload = await getPayload({ config })
|
||||||
|
|
@ -30,7 +32,12 @@ export async function GET(request: Request) {
|
||||||
|
|
||||||
// 同步单个商品(通过 Payload ID)
|
// 同步单个商品(通过 Payload ID)
|
||||||
if (payloadId) {
|
if (payloadId) {
|
||||||
const result = await syncSingleProductByPayloadId(payload, payloadId, forceUpdate)
|
const result = await syncSingleProductByPayloadId(
|
||||||
|
payload,
|
||||||
|
payloadId,
|
||||||
|
collection || '',
|
||||||
|
forceUpdate,
|
||||||
|
)
|
||||||
return NextResponse.json(result)
|
return NextResponse.json(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,27 +61,6 @@ export async function GET(request: Request) {
|
||||||
*/
|
*/
|
||||||
async function syncSingleProductByMedusaId(payload: any, medusaId: string, forceUpdate: boolean) {
|
async function syncSingleProductByMedusaId(payload: any, medusaId: string, forceUpdate: boolean) {
|
||||||
try {
|
try {
|
||||||
// 检查商品是否已存在
|
|
||||||
const existing = await payload.find({
|
|
||||||
collection: 'products',
|
|
||||||
where: {
|
|
||||||
medusaId: { equals: medusaId },
|
|
||||||
},
|
|
||||||
limit: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
const existingProduct = existing.docs[0]
|
|
||||||
|
|
||||||
// 如果存在且不强制更新,跳过
|
|
||||||
if (existingProduct && !forceUpdate) {
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
action: 'skipped',
|
|
||||||
message: `商品 ${medusaId} 已存在`,
|
|
||||||
productId: existingProduct.id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从 Medusa 获取商品数据
|
// 从 Medusa 获取商品数据
|
||||||
const medusaProducts = await getAllMedusaProducts()
|
const medusaProducts = await getAllMedusaProducts()
|
||||||
const medusaProduct = medusaProducts.find((p) => p.id === medusaId)
|
const medusaProduct = medusaProducts.find((p) => p.id === medusaId)
|
||||||
|
|
@ -87,12 +73,69 @@ async function syncSingleProductByMedusaId(payload: any, medusaId: string, force
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确定应该同步到哪个 collection
|
||||||
|
const targetCollection = getProductCollection(medusaProduct)
|
||||||
|
const otherCollection =
|
||||||
|
targetCollection === 'preorder-products' ? 'products' : 'preorder-products'
|
||||||
|
|
||||||
|
// 在目标 collection 中检查是否已存在
|
||||||
|
const existingInTarget = await payload.find({
|
||||||
|
collection: targetCollection,
|
||||||
|
where: {
|
||||||
|
medusaId: { equals: medusaId },
|
||||||
|
},
|
||||||
|
limit: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 在另一个 collection 中检查是否存在(产品类型可能改变)
|
||||||
|
const existingInOther = await payload.find({
|
||||||
|
collection: otherCollection,
|
||||||
|
where: {
|
||||||
|
medusaId: { equals: medusaId },
|
||||||
|
},
|
||||||
|
limit: 1,
|
||||||
|
})
|
||||||
|
|
||||||
const productData = transformMedusaProductToPayload(medusaProduct)
|
const productData = transformMedusaProductToPayload(medusaProduct)
|
||||||
|
|
||||||
|
// 如果在另一个 collection 中存在,需要删除并在正确的 collection 中创建
|
||||||
|
if (existingInOther.docs[0]) {
|
||||||
|
await payload.delete({
|
||||||
|
collection: otherCollection,
|
||||||
|
id: existingInOther.docs[0].id,
|
||||||
|
})
|
||||||
|
|
||||||
|
const created = await payload.create({
|
||||||
|
collection: targetCollection,
|
||||||
|
data: productData,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
action: 'moved',
|
||||||
|
message: `商品 ${medusaId} 已从 ${otherCollection} 移动到 ${targetCollection}`,
|
||||||
|
productId: created.id,
|
||||||
|
collection: targetCollection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingProduct = existingInTarget.docs[0]
|
||||||
|
|
||||||
|
// 如果存在且不强制更新,跳过
|
||||||
|
if (existingProduct && !forceUpdate) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
action: 'skipped',
|
||||||
|
message: `商品 ${medusaId} 已存在于 ${targetCollection}`,
|
||||||
|
productId: existingProduct.id,
|
||||||
|
collection: targetCollection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (existingProduct) {
|
if (existingProduct) {
|
||||||
// 更新现有商品
|
// 更新现有商品
|
||||||
const updated = await payload.update({
|
const updated = await payload.update({
|
||||||
collection: 'products',
|
collection: targetCollection,
|
||||||
id: existingProduct.id,
|
id: existingProduct.id,
|
||||||
data: productData,
|
data: productData,
|
||||||
})
|
})
|
||||||
|
|
@ -100,21 +143,23 @@ async function syncSingleProductByMedusaId(payload: any, medusaId: string, force
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
action: 'updated',
|
action: 'updated',
|
||||||
message: `商品 ${medusaId} 已更新`,
|
message: `商品 ${medusaId} 已更新于 ${targetCollection}`,
|
||||||
productId: updated.id,
|
productId: updated.id,
|
||||||
|
collection: targetCollection,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 创建新商品
|
// 创建新商品
|
||||||
const created = await payload.create({
|
const created = await payload.create({
|
||||||
collection: 'products',
|
collection: targetCollection,
|
||||||
data: productData,
|
data: productData,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
action: 'created',
|
action: 'created',
|
||||||
message: `商品 ${medusaId} 已创建`,
|
message: `商品 ${medusaId} 已创建于 ${targetCollection}`,
|
||||||
productId: created.id,
|
productId: created.id,
|
||||||
|
collection: targetCollection,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -131,19 +176,45 @@ async function syncSingleProductByMedusaId(payload: any, medusaId: string, force
|
||||||
/**
|
/**
|
||||||
* 通过 Payload ID 同步单个商品
|
* 通过 Payload ID 同步单个商品
|
||||||
*/
|
*/
|
||||||
async function syncSingleProductByPayloadId(payload: any, payloadId: string, forceUpdate: boolean) {
|
async function syncSingleProductByPayloadId(
|
||||||
|
payload: any,
|
||||||
|
payloadId: string,
|
||||||
|
collection: string,
|
||||||
|
forceUpdate: boolean,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
// 获取 Payload 商品
|
// 如果未指定 collection,尝试在两个 collections 中查找
|
||||||
const product = await payload.findByID({
|
let product: any = null
|
||||||
collection: 'products',
|
let foundCollection: string = collection
|
||||||
id: payloadId,
|
|
||||||
})
|
if (collection) {
|
||||||
|
product = await payload.findByID({
|
||||||
|
collection,
|
||||||
|
id: payloadId,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 尝试 preorder-products
|
||||||
|
try {
|
||||||
|
product = await payload.findByID({
|
||||||
|
collection: 'preorder-products',
|
||||||
|
id: payloadId,
|
||||||
|
})
|
||||||
|
foundCollection = 'preorder-products'
|
||||||
|
} catch {
|
||||||
|
// 最后尝试旧的 products
|
||||||
|
product = await payload.findByID({
|
||||||
|
collection: 'products',
|
||||||
|
id: payloadId,
|
||||||
|
})
|
||||||
|
foundCollection = 'products'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
action: 'not_found',
|
action: 'not_found',
|
||||||
message: `Payload 中未找到商品 ID: ${payloadId}`,
|
message: `未找到商品 ID: ${payloadId}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,16 +271,55 @@ async function syncAllProducts(payload: any, forceUpdate: boolean) {
|
||||||
// 处理每个商品
|
// 处理每个商品
|
||||||
for (const medusaProduct of medusaProducts) {
|
for (const medusaProduct of medusaProducts) {
|
||||||
try {
|
try {
|
||||||
// 检查是否已存在
|
// 确定应该同步到哪个 collection
|
||||||
const existing = await payload.find({
|
const targetCollection = getProductCollection(medusaProduct)
|
||||||
collection: 'products',
|
const otherCollection =
|
||||||
|
targetCollection === 'preorder-products' ? 'products' : 'preorder-products'
|
||||||
|
|
||||||
|
// 在目标 collection 中检查是否已存在
|
||||||
|
const existingInTarget = await payload.find({
|
||||||
|
collection: targetCollection,
|
||||||
where: {
|
where: {
|
||||||
medusaId: { equals: medusaProduct.id },
|
medusaId: { equals: medusaProduct.id },
|
||||||
},
|
},
|
||||||
limit: 1,
|
limit: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
const existingProduct = existing.docs[0]
|
// 在另一个 collection 中检查是否存在(产品类型可能改变)
|
||||||
|
const existingInOther = await payload.find({
|
||||||
|
collection: otherCollection,
|
||||||
|
where: {
|
||||||
|
medusaId: { equals: medusaProduct.id },
|
||||||
|
},
|
||||||
|
limit: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const productData = transformMedusaProductToPayload(medusaProduct)
|
||||||
|
|
||||||
|
// 如果在错误的 collection 中,移动它
|
||||||
|
if (existingInOther.docs[0]) {
|
||||||
|
await payload.delete({
|
||||||
|
collection: otherCollection,
|
||||||
|
id: existingInOther.docs[0].id,
|
||||||
|
})
|
||||||
|
|
||||||
|
await payload.create({
|
||||||
|
collection: targetCollection,
|
||||||
|
data: productData,
|
||||||
|
})
|
||||||
|
|
||||||
|
results.updated++
|
||||||
|
results.details.push({
|
||||||
|
medusaId: medusaProduct.id,
|
||||||
|
title: medusaProduct.title,
|
||||||
|
action: 'moved',
|
||||||
|
from: otherCollection,
|
||||||
|
to: targetCollection,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingProduct = existingInTarget.docs[0]
|
||||||
|
|
||||||
// 如果存在且不强制更新,跳过
|
// 如果存在且不强制更新,跳过
|
||||||
if (existingProduct && !forceUpdate) {
|
if (existingProduct && !forceUpdate) {
|
||||||
|
|
@ -218,16 +328,15 @@ async function syncAllProducts(payload: any, forceUpdate: boolean) {
|
||||||
medusaId: medusaProduct.id,
|
medusaId: medusaProduct.id,
|
||||||
title: medusaProduct.title,
|
title: medusaProduct.title,
|
||||||
action: 'skipped',
|
action: 'skipped',
|
||||||
|
collection: targetCollection,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const productData = transformMedusaProductToPayload(medusaProduct)
|
|
||||||
|
|
||||||
if (existingProduct) {
|
if (existingProduct) {
|
||||||
// 更新
|
// 更新
|
||||||
await payload.update({
|
await payload.update({
|
||||||
collection: 'products',
|
collection: targetCollection,
|
||||||
id: existingProduct.id,
|
id: existingProduct.id,
|
||||||
data: productData,
|
data: productData,
|
||||||
})
|
})
|
||||||
|
|
@ -236,11 +345,12 @@ async function syncAllProducts(payload: any, forceUpdate: boolean) {
|
||||||
medusaId: medusaProduct.id,
|
medusaId: medusaProduct.id,
|
||||||
title: medusaProduct.title,
|
title: medusaProduct.title,
|
||||||
action: 'updated',
|
action: 'updated',
|
||||||
|
collection: targetCollection,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// 创建
|
// 创建
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: 'products',
|
collection: targetCollection,
|
||||||
data: productData,
|
data: productData,
|
||||||
})
|
})
|
||||||
results.created++
|
results.created++
|
||||||
|
|
@ -248,6 +358,7 @@ async function syncAllProducts(payload: any, forceUpdate: boolean) {
|
||||||
medusaId: medusaProduct.id,
|
medusaId: medusaProduct.id,
|
||||||
title: medusaProduct.title,
|
title: medusaProduct.title,
|
||||||
action: 'created',
|
action: 'created',
|
||||||
|
collection: targetCollection,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -295,7 +406,7 @@ export async function POST(request: Request) {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
const { medusaId, payloadId, forceUpdate = true } = body
|
const { medusaId, payloadId, collection = '', forceUpdate = true } = body
|
||||||
|
|
||||||
if (medusaId) {
|
if (medusaId) {
|
||||||
const result = await syncSingleProductByMedusaId(payload, medusaId, forceUpdate)
|
const result = await syncSingleProductByMedusaId(payload, medusaId, forceUpdate)
|
||||||
|
|
@ -303,7 +414,7 @@ export async function POST(request: Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payloadId) {
|
if (payloadId) {
|
||||||
const result = await syncSingleProductByPayloadId(payload, payloadId, forceUpdate)
|
const result = await syncSingleProductByPayloadId(payload, payloadId, collection, forceUpdate)
|
||||||
return NextResponse.json(result)
|
return NextResponse.json(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
import type { CollectionConfig } from 'payload'
|
||||||
|
import { logAfterChange, logAfterDelete } from '../hooks/logAction'
|
||||||
|
import { cacheAfterChange, cacheAfterDelete } from '../hooks/cacheInvalidation'
|
||||||
|
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 PreorderProducts: CollectionConfig = {
|
||||||
|
slug: 'preorder-products',
|
||||||
|
admin: {
|
||||||
|
useAsTitle: 'title',
|
||||||
|
defaultColumns: ['thumbnail', 'title', 'medusaId', 'status', 'updatedAt'],
|
||||||
|
description: '管理预售商品的详细内容和描述',
|
||||||
|
listSearchableFields: ['title', 'medusaId', 'handle'],
|
||||||
|
pagination: {
|
||||||
|
defaultLimit: 25,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
edit: {
|
||||||
|
PreviewButton: '/components/sync/ForceSyncButton#ForceSyncButton',
|
||||||
|
},
|
||||||
|
beforeListTable: [
|
||||||
|
'/components/sync/SyncMedusaButton#SyncMedusaButton',
|
||||||
|
'/components/list/ProductGridStyler',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
access: {
|
||||||
|
read: () => true,
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'tabs',
|
||||||
|
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: 'title',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
admin: {
|
||||||
|
description: '商品标题(从 Medusa 同步)',
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'handle',
|
||||||
|
type: 'text',
|
||||||
|
admin: {
|
||||||
|
description: '商品 URL handle(从 Medusa 同步)',
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'thumbnail',
|
||||||
|
type: 'text',
|
||||||
|
admin: {
|
||||||
|
description: '商品缩略图 URL(从 Medusa 同步)',
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lastSyncedAt',
|
||||||
|
type: 'date',
|
||||||
|
admin: {
|
||||||
|
description: '上次同步时间',
|
||||||
|
readOnly: true,
|
||||||
|
date: {
|
||||||
|
displayFormat: 'yyyy-MM-dd HH:mm:ss',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '商品描述',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
type: 'richText',
|
||||||
|
editor: lexicalEditor({
|
||||||
|
features: [
|
||||||
|
ParagraphFeature(),
|
||||||
|
HeadingFeature({ enabledHeadingSizes: ['h2', 'h3', 'h4'] }),
|
||||||
|
BoldFeature(),
|
||||||
|
ItalicFeature(),
|
||||||
|
UnorderedListFeature(),
|
||||||
|
OrderedListFeature(),
|
||||||
|
LinkFeature(),
|
||||||
|
AlignFeature(),
|
||||||
|
BlockquoteFeature(),
|
||||||
|
HorizontalRuleFeature(),
|
||||||
|
InlineCodeFeature(),
|
||||||
|
IndentFeature(),
|
||||||
|
ChecklistFeature(),
|
||||||
|
FixedToolbarFeature(),
|
||||||
|
InlineToolbarFeature(),
|
||||||
|
BlocksFeature({
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
slug: 'image',
|
||||||
|
imageURL: '/api/media',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'caption',
|
||||||
|
type: 'text',
|
||||||
|
label: '图片说明',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
UploadFeature({
|
||||||
|
collections: {
|
||||||
|
media: {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'caption',
|
||||||
|
type: 'text',
|
||||||
|
label: '图片说明',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
RelationshipFeature(),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
admin: {
|
||||||
|
description: '预售商品的详细描述(支持富文本编辑)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '相关商品',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'relatedProducts',
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: ['preorder-products', 'products'],
|
||||||
|
hasMany: true,
|
||||||
|
admin: {
|
||||||
|
description: '推荐的相关商品',
|
||||||
|
components: {
|
||||||
|
Field: '/components/fields/RelatedProductsField#RelatedProductsField',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
hooks: {
|
||||||
|
afterChange: [cacheAfterChange, logAfterChange],
|
||||||
|
afterDelete: [cacheAfterDelete, logAfterDelete],
|
||||||
|
},
|
||||||
|
timestamps: true,
|
||||||
|
}
|
||||||
|
|
@ -188,7 +188,7 @@ export const Products: CollectionConfig = {
|
||||||
{
|
{
|
||||||
name: 'relatedProducts',
|
name: 'relatedProducts',
|
||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
relationTo: 'products',
|
relationTo: ['products', 'preorder-products'],
|
||||||
hasMany: true,
|
hasMany: true,
|
||||||
admin: {
|
admin: {
|
||||||
description: '相关商品,支持搜索联想',
|
description: '相关商品,支持搜索联想',
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export const ProductRecommendations: GlobalConfig = {
|
||||||
en: 'Products',
|
en: 'Products',
|
||||||
zh: '商品列表',
|
zh: '商品列表',
|
||||||
},
|
},
|
||||||
relationTo: 'products',
|
relationTo: ['products', 'preorder-products'],
|
||||||
hasMany: true,
|
hasMany: true,
|
||||||
admin: {
|
admin: {
|
||||||
description: {
|
description: {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ interface MedusaProduct {
|
||||||
status: string
|
status: string
|
||||||
created_at: string
|
created_at: string
|
||||||
updated_at: string
|
updated_at: string
|
||||||
metadata?: Record<string, any>
|
metadata?: Record<string, any> & {
|
||||||
|
is_preorder?: boolean
|
||||||
|
}
|
||||||
images?: Array<{
|
images?: Array<{
|
||||||
id: string
|
id: string
|
||||||
url: string
|
url: string
|
||||||
|
|
@ -173,6 +175,21 @@ export async function uploadImageFromUrl(imageUrl: string, payload: any): Promis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断产品是否为预售商品
|
||||||
|
* 检查 metadata.is_preorder
|
||||||
|
*/
|
||||||
|
export function isPreorderProduct(product: MedusaProduct): boolean {
|
||||||
|
return product.metadata?.is_preorder === true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取产品应该同步到的 collection
|
||||||
|
*/
|
||||||
|
export function getProductCollection(product: MedusaProduct): 'preorder-products' | 'products' {
|
||||||
|
return isPreorderProduct(product) ? 'preorder-products' : 'products'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换 Medusa 商品数据到 Payload 格式
|
* 转换 Medusa 商品数据到 Payload 格式
|
||||||
* 直接保存 Medusa 的图片 URL,不上传到 Media 集合
|
* 直接保存 Medusa 的图片 URL,不上传到 Media 集合
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { MigrateUpArgs, sql } from '@payloadcms/db-postgres'
|
||||||
|
|
||||||
|
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||||
|
await db.execute(sql`
|
||||||
|
CREATE TYPE "public"."enum_preorder_products_status" AS ENUM('draft', 'published');
|
||||||
|
CREATE TABLE "preorder_products" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"medusa_id" varchar NOT NULL,
|
||||||
|
"status" "enum_preorder_products_status" DEFAULT 'draft' NOT NULL,
|
||||||
|
"title" varchar NOT NULL,
|
||||||
|
"handle" varchar,
|
||||||
|
"thumbnail" varchar,
|
||||||
|
"last_synced_at" timestamp(3) with time zone,
|
||||||
|
"description" jsonb,
|
||||||
|
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||||
|
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "preorder_products_rels" (
|
||||||
|
"id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"order" integer,
|
||||||
|
"parent_id" integer NOT NULL,
|
||||||
|
"path" varchar NOT NULL,
|
||||||
|
"preorder_products_id" integer,
|
||||||
|
"products_id" integer
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "preorder_products_id" integer;
|
||||||
|
ALTER TABLE "product_recommendations_rels" ADD COLUMN "preorder_products_id" integer;
|
||||||
|
ALTER TABLE "preorder_products_rels" ADD CONSTRAINT "preorder_products_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."preorder_products"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
ALTER TABLE "preorder_products_rels" ADD CONSTRAINT "preorder_products_rels_preorder_products_fk" FOREIGN KEY ("preorder_products_id") REFERENCES "public"."preorder_products"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
ALTER TABLE "preorder_products_rels" ADD CONSTRAINT "preorder_products_rels_products_fk" FOREIGN KEY ("products_id") REFERENCES "public"."products"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
CREATE UNIQUE INDEX "preorder_products_medusa_id_idx" ON "preorder_products" USING btree ("medusa_id");
|
||||||
|
CREATE INDEX "preorder_products_updated_at_idx" ON "preorder_products" USING btree ("updated_at");
|
||||||
|
CREATE INDEX "preorder_products_created_at_idx" ON "preorder_products" USING btree ("created_at");
|
||||||
|
CREATE INDEX "preorder_products_rels_order_idx" ON "preorder_products_rels" USING btree ("order");
|
||||||
|
CREATE INDEX "preorder_products_rels_parent_idx" ON "preorder_products_rels" USING btree ("parent_id");
|
||||||
|
CREATE INDEX "preorder_products_rels_path_idx" ON "preorder_products_rels" USING btree ("path");
|
||||||
|
CREATE INDEX "preorder_products_rels_preorder_products_id_idx" ON "preorder_products_rels" USING btree ("preorder_products_id");
|
||||||
|
CREATE INDEX "preorder_products_rels_products_id_idx" ON "preorder_products_rels" USING btree ("products_id");
|
||||||
|
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_preorder_products_fk" FOREIGN KEY ("preorder_products_id") REFERENCES "public"."preorder_products"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
ALTER TABLE "product_recommendations_rels" ADD CONSTRAINT "product_recommendations_rels_preorder_products_fk" FOREIGN KEY ("preorder_products_id") REFERENCES "public"."preorder_products"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
CREATE INDEX "payload_locked_documents_rels_preorder_products_id_idx" ON "payload_locked_documents_rels" USING btree ("preorder_products_id");
|
||||||
|
CREATE INDEX "product_recommendations_rels_preorder_products_id_idx" ON "product_recommendations_rels" USING btree ("preorder_products_id");`)
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import { MigrateUpArgs, sql } from '@payloadcms/db-postgres'
|
import { MigrateUpArgs, sql } from '@payloadcms/db-postgres'
|
||||||
|
|
||||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||||
await db.execute(
|
await db.execute(sql`ALTER TABLE hero_slider_slides ADD COLUMN IF NOT EXISTS link TEXT;`)
|
||||||
sql`ALTER TABLE hero_slider_slides ADD COLUMN IF NOT EXISTS link TEXT;`
|
|
||||||
)
|
|
||||||
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
sql`UPDATE hero_slider_slides SET link = cta_link WHERE cta_link IS NOT NULL AND cta_enabled = true;`
|
sql`UPDATE hero_slider_slides SET link = cta_link WHERE cta_link IS NOT NULL AND cta_enabled = true;`,
|
||||||
)
|
)
|
||||||
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
|
|
@ -19,6 +17,6 @@ export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||||
DROP COLUMN IF EXISTS text_position,
|
DROP COLUMN IF EXISTS text_position,
|
||||||
DROP COLUMN IF EXISTS text_color,
|
DROP COLUMN IF EXISTS text_color,
|
||||||
DROP COLUMN IF EXISTS overlay,
|
DROP COLUMN IF EXISTS overlay,
|
||||||
DROP COLUMN IF EXISTS status;`
|
DROP COLUMN IF EXISTS status;`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import * as migration_20260208_171142 from './20260208_171142';
|
import * as migration_20260208_171142 from './20260208_171142'
|
||||||
import * as migration_20260212_193303 from './20260212_193303';
|
import * as migration_20260212_193303 from './20260212_193303'
|
||||||
import * as migration_hero_slider_simplify from './hero_slider_simplify';
|
import * as migration_20260212_202303 from './20260212_202303'
|
||||||
import * as migration_product_recommendations_simplify from './product_recommendations_simplify';
|
import * as migration_hero_slider_simplify from './hero_slider_simplify'
|
||||||
|
import * as migration_product_recommendations_simplify from './product_recommendations_simplify'
|
||||||
|
|
||||||
export const migrations = [
|
export const migrations = [
|
||||||
{
|
{
|
||||||
|
|
@ -12,14 +13,18 @@ export const migrations = [
|
||||||
{
|
{
|
||||||
up: migration_20260212_193303.up,
|
up: migration_20260212_193303.up,
|
||||||
down: migration_20260212_193303.down,
|
down: migration_20260212_193303.down,
|
||||||
name: '20260212_193303'
|
name: '20260212_193303',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
up: migration_20260212_202303.up,
|
||||||
|
name: '20260212_202303',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_hero_slider_simplify.up,
|
up: migration_hero_slider_simplify.up,
|
||||||
name: 'hero_slider_simplify'
|
name: 'hero_slider_simplify',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
up: migration_product_recommendations_simplify.up,
|
up: migration_product_recommendations_simplify.up,
|
||||||
name: 'product_recommendations_simplify'
|
name: 'product_recommendations_simplify',
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,6 @@ export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||||
DROP COLUMN IF EXISTS show_price,
|
DROP COLUMN IF EXISTS show_price,
|
||||||
DROP COLUMN IF EXISTS show_rating,
|
DROP COLUMN IF EXISTS show_rating,
|
||||||
DROP COLUMN IF EXISTS show_quick_view,
|
DROP COLUMN IF EXISTS show_quick_view,
|
||||||
DROP COLUMN IF EXISTS status;`
|
DROP COLUMN IF EXISTS status;`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ export interface Config {
|
||||||
users: User;
|
users: User;
|
||||||
media: Media;
|
media: Media;
|
||||||
products: Product;
|
products: Product;
|
||||||
|
'preorder-products': PreorderProduct;
|
||||||
announcements: Announcement;
|
announcements: Announcement;
|
||||||
articles: Article;
|
articles: Article;
|
||||||
logs: Log;
|
logs: Log;
|
||||||
|
|
@ -84,6 +85,7 @@ export interface Config {
|
||||||
users: UsersSelect<false> | UsersSelect<true>;
|
users: UsersSelect<false> | UsersSelect<true>;
|
||||||
media: MediaSelect<false> | MediaSelect<true>;
|
media: MediaSelect<false> | MediaSelect<true>;
|
||||||
products: ProductsSelect<false> | ProductsSelect<true>;
|
products: ProductsSelect<false> | ProductsSelect<true>;
|
||||||
|
'preorder-products': PreorderProductsSelect<false> | PreorderProductsSelect<true>;
|
||||||
announcements: AnnouncementsSelect<false> | AnnouncementsSelect<true>;
|
announcements: AnnouncementsSelect<false> | AnnouncementsSelect<true>;
|
||||||
articles: ArticlesSelect<false> | ArticlesSelect<true>;
|
articles: ArticlesSelect<false> | ArticlesSelect<true>;
|
||||||
logs: LogsSelect<false> | LogsSelect<true>;
|
logs: LogsSelect<false> | LogsSelect<true>;
|
||||||
|
|
@ -235,7 +237,86 @@ export interface Product {
|
||||||
/**
|
/**
|
||||||
* 相关商品,支持搜索联想
|
* 相关商品,支持搜索联想
|
||||||
*/
|
*/
|
||||||
relatedProducts?: (number | Product)[] | null;
|
relatedProducts?:
|
||||||
|
| (
|
||||||
|
| {
|
||||||
|
relationTo: 'products';
|
||||||
|
value: number | Product;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
relationTo: 'preorder-products';
|
||||||
|
value: number | PreorderProduct;
|
||||||
|
}
|
||||||
|
)[]
|
||||||
|
| null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 管理预售商品的详细内容和描述
|
||||||
|
*
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "preorder-products".
|
||||||
|
*/
|
||||||
|
export interface PreorderProduct {
|
||||||
|
id: number;
|
||||||
|
/**
|
||||||
|
* Medusa 商品 ID
|
||||||
|
*/
|
||||||
|
medusaId: string;
|
||||||
|
/**
|
||||||
|
* 商品详情状态
|
||||||
|
*/
|
||||||
|
status: 'draft' | 'published';
|
||||||
|
/**
|
||||||
|
* 商品标题(从 Medusa 同步)
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
/**
|
||||||
|
* 商品 URL handle(从 Medusa 同步)
|
||||||
|
*/
|
||||||
|
handle?: string | null;
|
||||||
|
/**
|
||||||
|
* 商品缩略图 URL(从 Medusa 同步)
|
||||||
|
*/
|
||||||
|
thumbnail?: string | null;
|
||||||
|
/**
|
||||||
|
* 上次同步时间
|
||||||
|
*/
|
||||||
|
lastSyncedAt?: string | null;
|
||||||
|
/**
|
||||||
|
* 预售商品的详细描述(支持富文本编辑)
|
||||||
|
*/
|
||||||
|
description?: {
|
||||||
|
root: {
|
||||||
|
type: string;
|
||||||
|
children: {
|
||||||
|
type: any;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ('ltr' | 'rtl') | null;
|
||||||
|
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||||
|
indent: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
} | null;
|
||||||
|
/**
|
||||||
|
* 推荐的相关商品
|
||||||
|
*/
|
||||||
|
relatedProducts?:
|
||||||
|
| (
|
||||||
|
| {
|
||||||
|
relationTo: 'preorder-products';
|
||||||
|
value: number | PreorderProduct;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
relationTo: 'products';
|
||||||
|
value: number | Product;
|
||||||
|
}
|
||||||
|
)[]
|
||||||
|
| null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
@ -573,6 +654,10 @@ export interface PayloadLockedDocument {
|
||||||
relationTo: 'products';
|
relationTo: 'products';
|
||||||
value: number | Product;
|
value: number | Product;
|
||||||
} | null)
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'preorder-products';
|
||||||
|
value: number | PreorderProduct;
|
||||||
|
} | null)
|
||||||
| ({
|
| ({
|
||||||
relationTo: 'announcements';
|
relationTo: 'announcements';
|
||||||
value: number | Announcement;
|
value: number | Announcement;
|
||||||
|
|
@ -684,6 +769,22 @@ export interface ProductsSelect<T extends boolean = true> {
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "preorder-products_select".
|
||||||
|
*/
|
||||||
|
export interface PreorderProductsSelect<T extends boolean = true> {
|
||||||
|
medusaId?: T;
|
||||||
|
status?: T;
|
||||||
|
title?: T;
|
||||||
|
handle?: T;
|
||||||
|
thumbnail?: T;
|
||||||
|
lastSyncedAt?: T;
|
||||||
|
description?: T;
|
||||||
|
relatedProducts?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "announcements_select".
|
* via the `definition` "announcements_select".
|
||||||
|
|
@ -893,7 +994,18 @@ export interface ProductRecommendation {
|
||||||
/**
|
/**
|
||||||
* Select and drag to reorder products
|
* Select and drag to reorder products
|
||||||
*/
|
*/
|
||||||
products?: (number | Product)[] | null;
|
products?:
|
||||||
|
| (
|
||||||
|
| {
|
||||||
|
relationTo: 'products';
|
||||||
|
value: number | Product;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
relationTo: 'preorder-products';
|
||||||
|
value: number | PreorderProduct;
|
||||||
|
}
|
||||||
|
)[]
|
||||||
|
| null;
|
||||||
id?: string | null;
|
id?: string | null;
|
||||||
}[]
|
}[]
|
||||||
| null;
|
| null;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import sharp from 'sharp'
|
||||||
import { Users } from './collections/Users'
|
import { Users } from './collections/Users'
|
||||||
import { Media } from './collections/Media'
|
import { Media } from './collections/Media'
|
||||||
import { Products } from './collections/Products'
|
import { Products } from './collections/Products'
|
||||||
|
import { PreorderProducts } from './collections/PreorderProducts'
|
||||||
import { Announcements } from './collections/Announcements'
|
import { Announcements } from './collections/Announcements'
|
||||||
import { Articles } from './collections/Articles'
|
import { Articles } from './collections/Articles'
|
||||||
import { Logs } from './collections/Logs'
|
import { Logs } from './collections/Logs'
|
||||||
|
|
@ -46,7 +47,15 @@ export default buildConfig({
|
||||||
},
|
},
|
||||||
fallbackLanguage: 'zh',
|
fallbackLanguage: 'zh',
|
||||||
},
|
},
|
||||||
collections: [Users, Media, Products, Announcements, Articles, Logs],
|
collections: [
|
||||||
|
Users,
|
||||||
|
Media,
|
||||||
|
Products,
|
||||||
|
PreorderProducts,
|
||||||
|
Announcements,
|
||||||
|
Articles,
|
||||||
|
Logs,
|
||||||
|
],
|
||||||
globals: [AdminSettings, LogsManager, HeroSlider, ProductRecommendations],
|
globals: [AdminSettings, LogsManager, HeroSlider, ProductRecommendations],
|
||||||
editor: lexicalEditor(),
|
editor: lexicalEditor(),
|
||||||
secret: process.env.PAYLOAD_SECRET || '',
|
secret: process.env.PAYLOAD_SECRET || '',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue