api 结构大精简

This commit is contained in:
龟男日记\www 2026-02-21 03:28:58 +08:00
parent b9cb60e3d0
commit 1860affd69
20 changed files with 282 additions and 785 deletions

View File

@ -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 {

View File

@ -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.

74
src/app/api/home/route.ts Normal file
View File

@ -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 }
)
}
}

View File

@ -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 }
)
}
}

View File

@ -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 },
)
}
}

View File

@ -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 },
)
}
}

View File

@ -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 },
)
}
}

View File

@ -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 },
)
}
}

View File

@ -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 },
)
}
}

View File

@ -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
}
}

View File

@ -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',

View File

@ -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? }
*/ */

View File

@ -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' }

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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',
}) })

View File

@ -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',
}, },

View File

@ -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 }
)
}
},
}

View File

@ -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: {