api 结构大精简
This commit is contained in:
parent
b9cb60e3d0
commit
1860affd69
|
|
@ -4,7 +4,7 @@ import { NextRequest } from 'next/server'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除日志 API
|
* 批量删除日志 API
|
||||||
* DELETE /api/delete-logs?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD
|
* DELETE /api/admin/log?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD
|
||||||
*/
|
*/
|
||||||
export async function DELETE(req: NextRequest) {
|
export async function DELETE(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -4,7 +4,7 @@ import config from '@payload-config'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API Route: Restore Product Recommendations from Seed
|
* API Route: Restore Product Recommendations from Seed
|
||||||
* POST /api/restore-recommendations-seed
|
* POST /api/admin/restore-recommendations-seed
|
||||||
*
|
*
|
||||||
* This server-side route uses Payload's local API to update the global config
|
* This server-side route uses Payload's local API to update the global config
|
||||||
* which requires proper authentication context that client-side fetch doesn't have.
|
* which requires proper authentication context that client-side fetch doesn't have.
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
|
import { getPayload } from 'payload'
|
||||||
|
import config from '@payload-config'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/home
|
||||||
|
* 获取首页所有数据:公告 + Hero Slider + 产品推荐
|
||||||
|
*/
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const payload = await getPayload({ config })
|
||||||
|
|
||||||
|
// 获取首页公告(已发布且在首页显示)
|
||||||
|
const announcements = await payload.find({
|
||||||
|
collection: 'announcements',
|
||||||
|
where: {
|
||||||
|
and: [
|
||||||
|
{
|
||||||
|
status: {
|
||||||
|
equals: 'published',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
showOnHomepage: {
|
||||||
|
equals: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
sort: '-priority',
|
||||||
|
limit: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取 Hero Slider
|
||||||
|
const heroSlider = await payload.findGlobal({
|
||||||
|
slug: 'hero-slider',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取产品推荐
|
||||||
|
const productRecommendations = await payload.findGlobal({
|
||||||
|
slug: 'product-recommendations',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 构建响应数据
|
||||||
|
const response = {
|
||||||
|
announcements: announcements.docs.map((announcement) => ({
|
||||||
|
id: announcement.id,
|
||||||
|
title: announcement.title,
|
||||||
|
type: announcement.type,
|
||||||
|
summary: announcement.summary,
|
||||||
|
priority: announcement.priority,
|
||||||
|
publishedAt: announcement.publishedAt,
|
||||||
|
})),
|
||||||
|
heroSlider: {
|
||||||
|
slides: heroSlider.slides || [],
|
||||||
|
},
|
||||||
|
productRecommendations: {
|
||||||
|
enabled: productRecommendations.enabled || false,
|
||||||
|
lists: productRecommendations.lists || [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(response, { status: 200 })
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error fetching homepage data:', error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: 'Failed to fetch homepage data',
|
||||||
|
message: error.message,
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
|
import payload from 'payload'
|
||||||
|
|
||||||
|
const MEDUSA_BACKEND_URL = process.env.MEDUSA_BACKEND_URL || 'http://localhost:9000'
|
||||||
|
const MEDUSA_ADMIN_API_KEY = process.env.MEDUSA_ADMIN_API_KEY || ''
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新预购商品的订单计数
|
||||||
|
* POST /api/preorders/refresh-order-counts
|
||||||
|
*
|
||||||
|
* Body:
|
||||||
|
* - productIds?: string[] - 要刷新的商品 ID 列表(Payload ID)
|
||||||
|
* - refreshAll?: boolean - 是否刷新所有预购商品
|
||||||
|
*/
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body = await req.json()
|
||||||
|
const { productIds, refreshAll } = body
|
||||||
|
|
||||||
|
if (!productIds && !refreshAll) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: '请提供 productIds 或设置 refreshAll' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let products: any[] = []
|
||||||
|
|
||||||
|
if (refreshAll) {
|
||||||
|
// 获取所有预购商品
|
||||||
|
const result = await payload.find({
|
||||||
|
collection: 'preorder-products',
|
||||||
|
limit: 1000,
|
||||||
|
where: {
|
||||||
|
medusaId: {
|
||||||
|
exists: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
products = result.docs
|
||||||
|
} else {
|
||||||
|
// 获取指定的商品
|
||||||
|
const result = await payload.find({
|
||||||
|
collection: 'preorder-products',
|
||||||
|
limit: productIds.length,
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
in: productIds,
|
||||||
|
},
|
||||||
|
medusaId: {
|
||||||
|
exists: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
products = result.docs
|
||||||
|
}
|
||||||
|
|
||||||
|
if (products.length === 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: '没有找到要刷新的商品' },
|
||||||
|
{ status: 404 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计更新结果
|
||||||
|
let successCount = 0
|
||||||
|
let failCount = 0
|
||||||
|
const errors: string[] = []
|
||||||
|
|
||||||
|
// 为每个商品刷新订单计数
|
||||||
|
for (const product of products) {
|
||||||
|
try {
|
||||||
|
const medusaId = product.medusaId
|
||||||
|
|
||||||
|
// 从 Medusa 获取订单数据
|
||||||
|
const response = await fetch(
|
||||||
|
`${MEDUSA_BACKEND_URL}/admin/orders?product_id=${medusaId}&limit=1000`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'x-medusa-access-token': MEDUSA_ADMIN_API_KEY,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Medusa API 返回错误: ${response.status} ${response.statusText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
const orders = data.orders || []
|
||||||
|
|
||||||
|
// 计算真实订单数
|
||||||
|
let realOrderCount = 0
|
||||||
|
|
||||||
|
// 筛选有效订单(排除取消的)
|
||||||
|
const validOrders = orders.filter((order: any) =>
|
||||||
|
order.status !== 'canceled' && order.payment_status !== 'not_paid'
|
||||||
|
)
|
||||||
|
|
||||||
|
// 遍历有效订单,统计该商品的数量
|
||||||
|
for (const order of validOrders) {
|
||||||
|
const items = order.items || []
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.product_id === medusaId) {
|
||||||
|
realOrderCount += item.quantity || 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 Payload 中的订单计数
|
||||||
|
await payload.update({
|
||||||
|
collection: 'preorder-products',
|
||||||
|
id: product.id,
|
||||||
|
data: {
|
||||||
|
orderCount: realOrderCount,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
successCount++
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`刷新商品 ${product.id} (${product.title}) 失败:`, error)
|
||||||
|
failCount++
|
||||||
|
errors.push(`${product.title}: ${error instanceof Error ? error.message : '未知错误'}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: `刷新完成: ${successCount} 个成功, ${failCount} 个失败`,
|
||||||
|
successCount,
|
||||||
|
failCount,
|
||||||
|
errors,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('刷新订单计数失败:', error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : '未知错误',
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个商品的订单计数(不更新数据库)
|
||||||
|
* GET /api/preorders/refresh-order-counts?productId=xxx
|
||||||
|
*/
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(req.url)
|
||||||
|
const productId = searchParams.get('productId')
|
||||||
|
|
||||||
|
if (!productId) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: '请提供 productId' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const product = await payload.findByID({
|
||||||
|
collection: 'preorder-products',
|
||||||
|
id: productId,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!product || !product.medusaId) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ success: false, error: '商品不存在或没有 Medusa ID' },
|
||||||
|
{ status: 404 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
currentCount: product.orderCount || 0,
|
||||||
|
medusaId: product.medusaId,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取订单计数失败:', error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : '未知错误',
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { getPayload } from 'payload'
|
|
||||||
import config from '@payload-config'
|
|
||||||
import { getCache, setCache } from '@/lib/redis'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/public/hero-slider
|
|
||||||
* 获取首页幻灯片数据(带缓存)
|
|
||||||
* 需要 x-store-api-key 验证
|
|
||||||
*/
|
|
||||||
export async function GET(req: NextRequest) {
|
|
||||||
try {
|
|
||||||
// 验证 API Key
|
|
||||||
const apiKey = req.headers.get('x-store-api-key')
|
|
||||||
const validApiKey = process.env.PAYLOAD_API_KEY
|
|
||||||
|
|
||||||
if (!apiKey || !validApiKey || apiKey !== validApiKey) {
|
|
||||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成缓存 key
|
|
||||||
const cacheKey = 'hero-slider:data'
|
|
||||||
|
|
||||||
// 尝试从缓存获取
|
|
||||||
const cached = await getCache(cacheKey)
|
|
||||||
if (cached) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
data: cached,
|
|
||||||
cached: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从数据库获取
|
|
||||||
const payload = await getPayload({ config })
|
|
||||||
const result = await payload.findGlobal({
|
|
||||||
slug: 'hero-slider' as any,
|
|
||||||
depth: 2, // 填充图片关联数据
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取所有幻灯片
|
|
||||||
const slides = (result as any).slides || []
|
|
||||||
|
|
||||||
const responseData = {
|
|
||||||
slides,
|
|
||||||
totalSlides: slides.length,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缓存结果(1 小时)
|
|
||||||
await setCache(cacheKey, responseData, 3600)
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
data: responseData,
|
|
||||||
cached: false,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Hero slider API error:', error)
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Failed to fetch hero slider',
|
|
||||||
},
|
|
||||||
{ status: 500 },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { getPayload } from 'payload'
|
|
||||||
import config from '@payload-config'
|
|
||||||
import { getCache, setCache } from '@/lib/redis'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/public/homepage
|
|
||||||
* 获取首页所有数据:幻灯片 + 商品推荐列表(带缓存)
|
|
||||||
*
|
|
||||||
* 这个 API 组合了多个数据源,提供完整的首页内容
|
|
||||||
*/
|
|
||||||
export async function GET(req: NextRequest) {
|
|
||||||
try {
|
|
||||||
// 生成缓存 key
|
|
||||||
const cacheKey = 'homepage:data'
|
|
||||||
|
|
||||||
// 尝试从缓存获取
|
|
||||||
const cached = await getCache(cacheKey)
|
|
||||||
if (cached) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
data: cached,
|
|
||||||
cached: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从数据库获取数据
|
|
||||||
const payload = await getPayload({ config })
|
|
||||||
|
|
||||||
// 并行获取所有数据
|
|
||||||
const [heroSliderResult, productRecommendationsResult] = await Promise.all([
|
|
||||||
// 获取幻灯片数据
|
|
||||||
payload
|
|
||||||
.findGlobal({
|
|
||||||
slug: 'hero-slider' as any,
|
|
||||||
depth: 2,
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed to fetch hero slider:', error)
|
|
||||||
return null
|
|
||||||
}),
|
|
||||||
|
|
||||||
// 获取商品推荐数据
|
|
||||||
payload
|
|
||||||
.findGlobal({
|
|
||||||
slug: 'product-recommendations' as any,
|
|
||||||
depth: 3,
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed to fetch product recommendations:', error)
|
|
||||||
return null
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
|
|
||||||
// 处理幻灯片数据
|
|
||||||
let heroSlider = {
|
|
||||||
slides: [],
|
|
||||||
totalSlides: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (heroSliderResult) {
|
|
||||||
const slides = (heroSliderResult as any).slides || []
|
|
||||||
|
|
||||||
heroSlider = {
|
|
||||||
slides,
|
|
||||||
totalSlides: slides.length,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理商品推荐数据
|
|
||||||
let productRecommendations = {
|
|
||||||
enabled: false,
|
|
||||||
lists: [],
|
|
||||||
totalLists: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (productRecommendationsResult && (productRecommendationsResult as any).enabled) {
|
|
||||||
// 获取所有列表
|
|
||||||
const lists = (productRecommendationsResult as any).lists || []
|
|
||||||
|
|
||||||
// 处理每个列表
|
|
||||||
const processedLists = lists.map((list: any) => {
|
|
||||||
const products = Array.isArray(list.products)
|
|
||||||
? list.products.filter((product: any) => product && product.status === 'published')
|
|
||||||
: []
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: list.id,
|
|
||||||
title: list.title,
|
|
||||||
subtitle: list.subtitle,
|
|
||||||
products,
|
|
||||||
totalProducts: products.length,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
productRecommendations = {
|
|
||||||
enabled: (productRecommendationsResult as any).enabled,
|
|
||||||
lists: processedLists,
|
|
||||||
totalLists: processedLists.length,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 组合响应数据
|
|
||||||
const responseData = {
|
|
||||||
heroSlider,
|
|
||||||
productRecommendations,
|
|
||||||
meta: {
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
version: '1.0',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缓存结果(1 小时)
|
|
||||||
await setCache(cacheKey, responseData, 3600)
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
data: responseData,
|
|
||||||
cached: false,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Homepage API error:', error)
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Failed to fetch homepage data',
|
|
||||||
},
|
|
||||||
{ status: 500 },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { getPayload } from 'payload'
|
|
||||||
import config from '@payload-config'
|
|
||||||
import { getCache, setCache } from '@/lib/redis'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/public/product-recommendations
|
|
||||||
* 获取商品推荐列表数据(带缓存)
|
|
||||||
* 需要 x-store-api-key 验证
|
|
||||||
*/
|
|
||||||
export async function GET(req: NextRequest) {
|
|
||||||
try {
|
|
||||||
// 验证 API Key
|
|
||||||
const apiKey = req.headers.get('x-store-api-key')
|
|
||||||
const validApiKey = process.env.PAYLOAD_API_KEY
|
|
||||||
|
|
||||||
if (!apiKey || !validApiKey || apiKey !== validApiKey) {
|
|
||||||
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成缓存 key
|
|
||||||
const cacheKey = 'product-recommendations:data'
|
|
||||||
|
|
||||||
// 尝试从缓存获取
|
|
||||||
const cached = await getCache(cacheKey)
|
|
||||||
if (cached) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
data: cached,
|
|
||||||
cached: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从数据库获取
|
|
||||||
const payload = await getPayload({ config })
|
|
||||||
const result = await payload.findGlobal({
|
|
||||||
slug: 'product-recommendations' as any,
|
|
||||||
depth: 3, // 填充商品和图片关联数据
|
|
||||||
})
|
|
||||||
|
|
||||||
// 如果功能未启用,返回空数据
|
|
||||||
if (!(result as any).enabled) {
|
|
||||||
const emptyData = {
|
|
||||||
enabled: false,
|
|
||||||
lists: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缓存空数据(较短时间)
|
|
||||||
await setCache(cacheKey, emptyData, 600) // 10 分钟
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
data: emptyData,
|
|
||||||
cached: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取所有列表
|
|
||||||
const lists = (result as any).lists || []
|
|
||||||
|
|
||||||
// 处理每个列表
|
|
||||||
const processedLists = lists.map((list: any) => {
|
|
||||||
const products = Array.isArray(list.products)
|
|
||||||
? list.products.filter((product: any) => product && product.status === 'published')
|
|
||||||
: []
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: list.id,
|
|
||||||
title: list.title,
|
|
||||||
subtitle: list.subtitle,
|
|
||||||
products,
|
|
||||||
totalProducts: products.length,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const responseData = {
|
|
||||||
enabled: (result as any).enabled,
|
|
||||||
lists: processedLists,
|
|
||||||
totalLists: processedLists.length,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缓存结果(1 小时)
|
|
||||||
await setCache(cacheKey, responseData, 3600)
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
data: responseData,
|
|
||||||
cached: false,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Product recommendations API error:', error)
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Failed to fetch product recommendations',
|
|
||||||
},
|
|
||||||
{ status: 500 },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { getPayload } from 'payload'
|
|
||||||
import config from '@payload-config'
|
|
||||||
import { getCache, setCache } from '@/lib/redis'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/public/products/[id]
|
|
||||||
* 获取单个产品详情(带缓存)
|
|
||||||
* 支持参数:
|
|
||||||
* - collection: 'preorder-products' | 'products' (可选,如不指定则自动搜索)
|
|
||||||
*/
|
|
||||||
export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
|
|
||||||
try {
|
|
||||||
const { id } = params
|
|
||||||
const searchParams = req.nextUrl.searchParams
|
|
||||||
const collection = searchParams.get('collection')
|
|
||||||
|
|
||||||
// 生成缓存 key
|
|
||||||
const cacheKey = `products:detail:${id}:collection=${collection || 'auto'}`
|
|
||||||
|
|
||||||
// 尝试从缓存获取
|
|
||||||
const cached = await getCache(cacheKey)
|
|
||||||
if (cached) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
data: cached,
|
|
||||||
cached: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = await getPayload({ config })
|
|
||||||
let result: any = null
|
|
||||||
let foundCollection: string = ''
|
|
||||||
|
|
||||||
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') {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
error: 'Product not found or not published',
|
|
||||||
},
|
|
||||||
{ status: 404 },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加 collection 信息
|
|
||||||
const resultWithMeta = {
|
|
||||||
...result,
|
|
||||||
_collection: foundCollection,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缓存结果(1 小时)
|
|
||||||
await setCache(cacheKey, resultWithMeta, 3600)
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
data: resultWithMeta,
|
|
||||||
cached: false,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Product detail API error:', error)
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Failed to fetch product',
|
|
||||||
},
|
|
||||||
{ status: 500 },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
|
||||||
import { getPayload } from 'payload'
|
|
||||||
import config from '@payload-config'
|
|
||||||
import { getCache, setCache } from '@/lib/redis'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET /api/public/products
|
|
||||||
* 获取产品列表(带缓存)
|
|
||||||
* 支持参数:
|
|
||||||
* - type: 'preorder' | 'order' | 'all' (默认 'all')
|
|
||||||
* - page: 页码
|
|
||||||
* - limit: 每页数量
|
|
||||||
* - status: 'draft' | 'published' (默认 'published')
|
|
||||||
*/
|
|
||||||
export async function GET(req: NextRequest) {
|
|
||||||
try {
|
|
||||||
const searchParams = req.nextUrl.searchParams
|
|
||||||
const page = parseInt(searchParams.get('page') || '1', 10)
|
|
||||||
const limit = parseInt(searchParams.get('limit') || '10', 10)
|
|
||||||
const status = searchParams.get('status') || 'published'
|
|
||||||
const type = searchParams.get('type') || 'all'
|
|
||||||
|
|
||||||
// 生成缓存 key
|
|
||||||
const cacheKey = `products:list:type=${type}:page=${page}:limit=${limit}:status=${status}`
|
|
||||||
|
|
||||||
// 尝试从缓存获取
|
|
||||||
const cached = await getCache(cacheKey)
|
|
||||||
if (cached) {
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
data: cached,
|
|
||||||
cached: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = await getPayload({ config })
|
|
||||||
const where = { status: { equals: status } }
|
|
||||||
|
|
||||||
let result
|
|
||||||
|
|
||||||
if (type === 'all') {
|
|
||||||
// 查询所有类型
|
|
||||||
const [preorders, products] = await Promise.all([
|
|
||||||
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 小时)
|
|
||||||
await setCache(cacheKey, result, 3600)
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
data: result,
|
|
||||||
cached: false,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Products API error:', error)
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Failed to fetch products',
|
|
||||||
},
|
|
||||||
{ status: 500 },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
|
||||||
import payload from 'payload'
|
|
||||||
|
|
||||||
const MEDUSA_BACKEND_URL = process.env.MEDUSA_BACKEND_URL || 'http://localhost:9000'
|
|
||||||
const MEDUSA_ADMIN_API_KEY = process.env.MEDUSA_ADMIN_API_KEY || ''
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新预购商品的订单计数
|
|
||||||
* POST /api/refresh-order-counts
|
|
||||||
*
|
|
||||||
* Body:
|
|
||||||
* - productIds?: string[] - 要刷新的商品 ID 列表(Payload ID)
|
|
||||||
* - refreshAll?: boolean - 是否刷新所有预购商品
|
|
||||||
*/
|
|
||||||
export async function POST(req: NextRequest) {
|
|
||||||
try {
|
|
||||||
const body = await req.json()
|
|
||||||
const { productIds, refreshAll } = body
|
|
||||||
|
|
||||||
if (!productIds && !refreshAll) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: '请提供 productIds 或设置 refreshAll' },
|
|
||||||
{ status: 400 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let products: any[] = []
|
|
||||||
|
|
||||||
if (refreshAll) {
|
|
||||||
// 获取所有预购商品
|
|
||||||
const result = await payload.find({
|
|
||||||
collection: 'preorder-products',
|
|
||||||
limit: 1000,
|
|
||||||
where: {
|
|
||||||
medusaId: {
|
|
||||||
exists: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
products = result.docs
|
|
||||||
} else {
|
|
||||||
// 获取指定的商品
|
|
||||||
const result = await payload.find({
|
|
||||||
collection: 'preorder-products',
|
|
||||||
limit: productIds.length,
|
|
||||||
where: {
|
|
||||||
id: {
|
|
||||||
in: productIds,
|
|
||||||
},
|
|
||||||
medusaId: {
|
|
||||||
exists: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
products = result.docs
|
|
||||||
}
|
|
||||||
|
|
||||||
if (products.length === 0) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ success: false, error: '没有找到要刷新的商品' },
|
|
||||||
{ status: 404 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 统计更新结果
|
|
||||||
let successCount = 0
|
|
||||||
let failCount = 0
|
|
||||||
const errors: string[] = []
|
|
||||||
|
|
||||||
// 为每个商品刷新订单计数
|
|
||||||
for (const product of products) {
|
|
||||||
try {
|
|
||||||
const orderCount = await fetchProductOrderCount(product.medusaId)
|
|
||||||
|
|
||||||
await payload.update({
|
|
||||||
collection: 'preorder-products',
|
|
||||||
id: product.id,
|
|
||||||
data: {
|
|
||||||
orderCount,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
successCount++
|
|
||||||
} catch (error) {
|
|
||||||
failCount++
|
|
||||||
const errorMsg = `商品 ${product.title} (${product.medusaId}): ${
|
|
||||||
error instanceof Error ? error.message : '未知错误'
|
|
||||||
}`
|
|
||||||
errors.push(errorMsg)
|
|
||||||
console.error('刷新订单计数失败:', errorMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
message: `刷新完成!成功: ${successCount},失败: ${failCount}`,
|
|
||||||
details: {
|
|
||||||
total: products.length,
|
|
||||||
success: successCount,
|
|
||||||
failed: failCount,
|
|
||||||
errors: errors.length > 0 ? errors : undefined,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('刷新订单计数 API 错误:', error)
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : '未知错误',
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从 Medusa 获取指定商品的订单数量
|
|
||||||
*/
|
|
||||||
async function fetchProductOrderCount(productId: string): Promise<number> {
|
|
||||||
try {
|
|
||||||
// 使用 Medusa Admin API 查询订单
|
|
||||||
// 注意:这里需要使用 Query API 或者 Admin Orders API
|
|
||||||
// 查询所有包含该商品的已完成订单
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${MEDUSA_BACKEND_URL}/admin/orders?fields=id,items&items[product_id]=${productId}`,
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'x-publishable-api-key': process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || '',
|
|
||||||
...(MEDUSA_ADMIN_API_KEY && { 'Authorization': `Bearer ${MEDUSA_ADMIN_API_KEY}` }),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Medusa API 返回错误: ${response.status} ${response.statusText}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json()
|
|
||||||
|
|
||||||
// 计算订单中该商品的总数量
|
|
||||||
let totalQuantity = 0
|
|
||||||
|
|
||||||
if (data.orders && Array.isArray(data.orders)) {
|
|
||||||
for (const order of data.orders) {
|
|
||||||
if (order.items && Array.isArray(order.items)) {
|
|
||||||
for (const item of order.items) {
|
|
||||||
if (item.product_id === productId) {
|
|
||||||
totalQuantity += item.quantity || 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalQuantity
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`获取商品 ${productId} 订单数量失败:`, error)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { getAllMedusaProducts } from '@/lib/medusa'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Batch Sync Selected Products
|
* Batch Sync Selected Products
|
||||||
* POST /api/batch-sync-medusa
|
* POST /api/sync/batch-medusa
|
||||||
* Body: { ids: string[], collection: 'products' | 'preorder-products', forceUpdate?: boolean }
|
* Body: { ids: string[], collection: 'products' | 'preorder-products', forceUpdate?: boolean }
|
||||||
*/
|
*/
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
|
|
@ -80,8 +80,7 @@ export async function POST(request: Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceUpdate || !product.title) updateData.title = medusaProduct.title
|
if (forceUpdate || !product.title) updateData.title = medusaProduct.title
|
||||||
if (forceUpdate || !product.handle) updateData.handle = medusaProduct.handle
|
if (forceUpdate || !product.thumbnail) updateData.thumbnail = medusaProduct.thumbnail
|
||||||
if (forceUpdate || !product.thumbnail) updateData.thumbnail = medusaProduct.thumbnail?.url
|
|
||||||
|
|
||||||
await payload.update({
|
await payload.update({
|
||||||
collection: collection as 'products' | 'preorder-products',
|
collection: collection as 'products' | 'preorder-products',
|
||||||
|
|
@ -125,10 +125,10 @@ function mergeProductData(existingProduct: any, newData: any, forceUpdate: boole
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步 Medusa 商品到 Payload CMS
|
* 同步 Medusa 商品到 Payload CMS
|
||||||
* GET /api/sync-medusa - 同步所有商品
|
* GET /api/sync/medusa - 同步所有商品
|
||||||
* GET /api/sync-medusa?medusaId=prod_xxx - 同步单个商品
|
* GET /api/sync/medusa?medusaId=prod_xxx - 同步单个商品
|
||||||
* GET /api/sync-medusa?medusaId=prod_xxx&collection=preorder-products - 指定目标 collection
|
* GET /api/sync/medusa?medusaId=prod_xxx&collection=preorder-products - 指定目标 collection
|
||||||
* GET /api/sync-medusa?forceUpdate=true - 强制更新所有字段
|
* GET /api/sync/medusa?forceUpdate=true - 强制更新所有字段
|
||||||
*/
|
*/
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
const origin = request.headers.get('origin')
|
const origin = request.headers.get('origin')
|
||||||
|
|
@ -481,7 +481,7 @@ async function syncAllProducts(payload: any, forceUpdate: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/sync-medusa
|
* POST /api/sync/medusa
|
||||||
* 手动触发同步(与 GET 参数相同)
|
* 手动触发同步(与 GET 参数相同)
|
||||||
* Body: { medusaId?, collection?, forceUpdate? }
|
* Body: { medusaId?, collection?, forceUpdate? }
|
||||||
*/
|
*/
|
||||||
|
|
@ -18,7 +18,7 @@ export async function OPTIONS(request: Request) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步单个产品并返回完整的产品数据
|
* 同步单个产品并返回完整的产品数据
|
||||||
* POST /api/sync-product
|
* POST /api/sync/product
|
||||||
* Body: { medusaId: string, collection?: 'products' | 'preorder-products', forceUpdate?: boolean }
|
* Body: { medusaId: string, collection?: 'products' | 'preorder-products', forceUpdate?: boolean }
|
||||||
*
|
*
|
||||||
* 返回: { success: true, product: {...完整的产品数据}, action: 'created' | 'updated' | 'skipped' }
|
* 返回: { success: true, product: {...完整的产品数据}, action: 'created' | 'updated' | 'skipped' }
|
||||||
|
|
@ -23,7 +23,7 @@ export function RefreshOrderCountField() {
|
||||||
setMessage('')
|
setMessage('')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/refresh-order-counts', {
|
const response = await fetch('/api/preorders/refresh-order-counts', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ export function RestoreRecommendationsSeedButton({ className }: Props) {
|
||||||
setMessage('💾 Updating configuration...')
|
setMessage('💾 Updating configuration...')
|
||||||
|
|
||||||
// Update product-recommendations global via server-side API
|
// Update product-recommendations global via server-side API
|
||||||
const updateResponse = await fetch('/api/restore-recommendations-seed', {
|
const updateResponse = await fetch('/api/admin/restore-recommendations-seed', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export function RefreshOrderCountButton() {
|
||||||
setMessage('')
|
setMessage('')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/refresh-order-counts', {
|
const response = await fetch('/api/preorders/refresh-order-counts', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
@ -71,7 +71,7 @@ export function RefreshOrderCountButton() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setMessage('')
|
setMessage('')
|
||||||
|
|
||||||
const response = await fetch('/api/refresh-order-counts', {
|
const response = await fetch('/api/preorders/refresh-order-counts', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export function UnifiedSyncButton() {
|
||||||
setMessage('')
|
setMessage('')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/sync-medusa?forceUpdate=false', {
|
const response = await fetch('/api/sync/medusa?forceUpdate=false', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ export function UnifiedSyncButton() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
setMessage('')
|
setMessage('')
|
||||||
|
|
||||||
const response = await fetch('/api/batch-sync-medusa', {
|
const response = await fetch('/api/sync/batch-medusa', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
@ -124,7 +124,7 @@ export function UnifiedSyncButton() {
|
||||||
setShowForceAllConfirm(false)
|
setShowForceAllConfirm(false)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/sync-medusa?forceUpdate=true', {
|
const response = await fetch('/api/sync/medusa?forceUpdate=true', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ export default function LogsManagerView() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/delete-logs?startDate=${deleteStartDate}&endDate=${deleteEndDate}`,
|
`/api/admin/log?startDate=${deleteStartDate}&endDate=${deleteEndDate}`,
|
||||||
{
|
{
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
import type { Endpoint } from 'payload'
|
|
||||||
|
|
||||||
export const homepageDataEndpoint: Endpoint = {
|
|
||||||
path: '/homepage-data',
|
|
||||||
method: 'get',
|
|
||||||
handler: async (req) => {
|
|
||||||
try {
|
|
||||||
const payload = req.payload
|
|
||||||
|
|
||||||
// 获取首页公告(已发布且在首页显示)
|
|
||||||
const announcements = await payload.find({
|
|
||||||
collection: 'announcements',
|
|
||||||
where: {
|
|
||||||
and: [
|
|
||||||
{
|
|
||||||
status: {
|
|
||||||
equals: 'published',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
showOnHomepage: {
|
|
||||||
equals: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
sort: '-priority',
|
|
||||||
limit: 10,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取 Hero Slider
|
|
||||||
const heroSlider = await payload.findGlobal({
|
|
||||||
slug: 'hero-slider',
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取产品推荐
|
|
||||||
const productRecommendations = await payload.findGlobal({
|
|
||||||
slug: 'product-recommendations',
|
|
||||||
})
|
|
||||||
|
|
||||||
// 构建响应数据
|
|
||||||
const response = {
|
|
||||||
announcements: announcements.docs.map((announcement) => ({
|
|
||||||
id: announcement.id,
|
|
||||||
title: announcement.title,
|
|
||||||
type: announcement.type,
|
|
||||||
summary: announcement.summary,
|
|
||||||
priority: announcement.priority,
|
|
||||||
publishedAt: announcement.publishedAt,
|
|
||||||
})),
|
|
||||||
heroSlider: {
|
|
||||||
slides: heroSlider.slides || [],
|
|
||||||
},
|
|
||||||
productRecommendations: {
|
|
||||||
enabled: productRecommendations.enabled || false,
|
|
||||||
lists: productRecommendations.lists || [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.json(response, { status: 200 })
|
|
||||||
} catch (error: any) {
|
|
||||||
req.payload.logger.error('Error fetching homepage data:', error)
|
|
||||||
return Response.json(
|
|
||||||
{
|
|
||||||
error: 'Failed to fetch homepage data',
|
|
||||||
message: error.message,
|
|
||||||
},
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
@ -20,7 +20,6 @@ import { SiteAccess } from './globals/SiteAccess'
|
||||||
import { s3Storage } from '@payloadcms/storage-s3'
|
import { s3Storage } from '@payloadcms/storage-s3'
|
||||||
import { en } from '@payloadcms/translations/languages/en'
|
import { en } from '@payloadcms/translations/languages/en'
|
||||||
import { zh } from '@payloadcms/translations/languages/zh'
|
import { zh } from '@payloadcms/translations/languages/zh'
|
||||||
import { homepageDataEndpoint } from './endpoints/homepage'
|
|
||||||
|
|
||||||
// 导入自定义翻译
|
// 导入自定义翻译
|
||||||
import enProducts from './translations/en/products.json'
|
import enProducts from './translations/en/products.json'
|
||||||
|
|
@ -51,7 +50,6 @@ export default buildConfig({
|
||||||
},
|
},
|
||||||
collections: [Users, Media, Products, PreorderProducts, Announcements, Articles, Logs],
|
collections: [Users, Media, Products, PreorderProducts, Announcements, Articles, Logs],
|
||||||
globals: [AdminSettings, LogsManager, HeroSlider, ProductRecommendations, SiteAccess],
|
globals: [AdminSettings, LogsManager, HeroSlider, ProductRecommendations, SiteAccess],
|
||||||
endpoints: [homepageDataEndpoint],
|
|
||||||
editor: lexicalEditor(),
|
editor: lexicalEditor(),
|
||||||
secret: process.env.PAYLOAD_SECRET || '',
|
secret: process.env.PAYLOAD_SECRET || '',
|
||||||
typescript: {
|
typescript: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue