diff --git a/src/app/(payload)/admin/importMap.js b/src/app/(payload)/admin/importMap.js
index aa2ab4e..29a6f39 100644
--- a/src/app/(payload)/admin/importMap.js
+++ b/src/app/(payload)/admin/importMap.js
@@ -28,9 +28,11 @@ import { RelatedProductsField as RelatedProductsField_f3e26ca26ab1ef52a2ee0f6932
import { TaobaoLinkPreview as TaobaoLinkPreview_44c9439e828c0463191af62d21ad4959 } from '../../../components/fields/TaobaoLinkPreview'
import { UnifiedSyncButton as UnifiedSyncButton_fc99b3f144909da232f9fd4ff7269523 } from '../../../components/sync/UnifiedSyncButton'
import { default as default_c2e3814fe427263135b1f5931c37f6f2 } from '../../../components/list/ProductGridStyler'
+import { PreorderProgressCell as PreorderProgressCell_67df47753573233f0c83480de687f13b } from '../../../components/cells/PreorderProgressCell'
+import { RefreshOrderCountField as RefreshOrderCountField_ef327f0ad449eac595b5e301044c0996 } from '../../../components/fields/RefreshOrderCountField'
import { BlocksFeatureClient as BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { PreorderOrdersField as PreorderOrdersField_a4aa1b8cbd6dec364a834b059228f43f } from '../../../components/fields/PreorderOrdersField'
-import { RefreshOrderCountButton as RefreshOrderCountButton_f6ce1bfc16a20083ee4c6ceb7022839e } from '../../../components/sync/RefreshOrderCountButton'
+import { PreorderProductGridStyler as PreorderProductGridStyler_e7f6f7c2233fc58ae87e992227bb80c5 } from '../../../components/list/PreorderProductGridStyler'
import { default as default_767734c8b7b095ea28d54c32abcf46e4 } from '../../../components/views/AdminPanel'
import { default as default_a766ef013722c08f9bb937940272cb5f } from '../../../components/views/LogsManagerView'
import { S3ClientUploadHandler as S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24 } from '@payloadcms/storage-s3/client'
@@ -67,9 +69,11 @@ export const importMap = {
"/components/fields/TaobaoLinkPreview#TaobaoLinkPreview": TaobaoLinkPreview_44c9439e828c0463191af62d21ad4959,
"/components/sync/UnifiedSyncButton#UnifiedSyncButton": UnifiedSyncButton_fc99b3f144909da232f9fd4ff7269523,
"/components/list/ProductGridStyler#default": default_c2e3814fe427263135b1f5931c37f6f2,
+ "/components/cells/PreorderProgressCell#PreorderProgressCell": PreorderProgressCell_67df47753573233f0c83480de687f13b,
+ "/components/fields/RefreshOrderCountField#RefreshOrderCountField": RefreshOrderCountField_ef327f0ad449eac595b5e301044c0996,
"@payloadcms/richtext-lexical/client#BlocksFeatureClient": BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"/components/fields/PreorderOrdersField#PreorderOrdersField": PreorderOrdersField_a4aa1b8cbd6dec364a834b059228f43f,
- "/components/sync/RefreshOrderCountButton#RefreshOrderCountButton": RefreshOrderCountButton_f6ce1bfc16a20083ee4c6ceb7022839e,
+ "/components/list/PreorderProductGridStyler#PreorderProductGridStyler": PreorderProductGridStyler_e7f6f7c2233fc58ae87e992227bb80c5,
"/components/views/AdminPanel#default": default_767734c8b7b095ea28d54c32abcf46e4,
"/components/views/LogsManagerView#default": default_a766ef013722c08f9bb937940272cb5f,
"@payloadcms/storage-s3/client#S3ClientUploadHandler": S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24,
diff --git a/src/collections/PreorderProducts.ts b/src/collections/PreorderProducts.ts
index 762904e..3fc2cbc 100644
--- a/src/collections/PreorderProducts.ts
+++ b/src/collections/PreorderProducts.ts
@@ -27,7 +27,7 @@ export const PreorderProducts: CollectionConfig = {
slug: 'preorder-products',
admin: {
useAsTitle: 'title',
- defaultColumns: ['thumbnail', 'title', 'medusaId', 'status', 'updatedAt'],
+ defaultColumns: ['thumbnail', 'title', 'medusaId', 'progress', 'status', 'updatedAt'],
description: '管理预售商品的详细内容和描述',
listSearchableFields: ['title', 'medusaId'],
pagination: {
@@ -36,13 +36,15 @@ export const PreorderProducts: CollectionConfig = {
components: {
beforeListTable: [
'/components/sync/UnifiedSyncButton#UnifiedSyncButton',
- '/components/sync/RefreshOrderCountButton#RefreshOrderCountButton',
- '/components/list/ProductGridStyler',
+ '/components/list/PreorderProductGridStyler#PreorderProductGridStyler',
],
},
},
access: {
- read: () => true,
+ read: () => true, // 公开可读
+ create: ({ req: { user } }) => !!user, // 登录用户可创建
+ update: ({ req: { user } }) => !!user, // 登录用户可更新
+ delete: ({ req: { user } }) => !!user, // 登录用户可删除
},
fields: [
{
@@ -123,6 +125,15 @@ export const PreorderProducts: CollectionConfig = {
},
},
},
+ {
+ name: 'progress',
+ type: 'ui',
+ admin: {
+ components: {
+ Cell: '/components/cells/PreorderProgressCell#PreorderProgressCell',
+ },
+ },
+ },
],
},
{
@@ -168,6 +179,15 @@ export const PreorderProducts: CollectionConfig = {
},
},
},
+ {
+ type: 'ui',
+ name: 'refreshOrderCount',
+ admin: {
+ components: {
+ Field: '/components/fields/RefreshOrderCountField#RefreshOrderCountField',
+ },
+ },
+ },
{
type: 'row',
fields: [
@@ -268,6 +288,17 @@ export const PreorderProducts: CollectionConfig = {
Field: '/components/fields/RelatedProductsField#RelatedProductsField',
},
},
+ filterOptions: ({ relationTo, data }) => {
+ // 过滤掉当前商品本身,避免自引用
+ if (data?.id) {
+ return {
+ id: {
+ not_equals: data.id,
+ },
+ }
+ }
+ return true
+ },
},
],
},
diff --git a/src/collections/Products.ts b/src/collections/Products.ts
index b3ead68..1d80e98 100644
--- a/src/collections/Products.ts
+++ b/src/collections/Products.ts
@@ -196,6 +196,17 @@ export const Products: CollectionConfig = {
Field: '/components/fields/RelatedProductsField#RelatedProductsField',
},
},
+ filterOptions: ({ relationTo, data }) => {
+ // 过滤掉当前商品本身,避免自引用
+ if (data?.id) {
+ return {
+ id: {
+ not_equals: data.id,
+ },
+ }
+ }
+ return true
+ },
},
],
},
diff --git a/src/components/cells/PreorderProgressCell.tsx b/src/components/cells/PreorderProgressCell.tsx
new file mode 100644
index 0000000..4ae3248
--- /dev/null
+++ b/src/components/cells/PreorderProgressCell.tsx
@@ -0,0 +1,48 @@
+'use client'
+import React from 'react'
+
+/**
+ * 预购进度单元格组件
+ * 在列表网格视图中显示订单计数和进度
+ */
+export function PreorderProgressCell({ rowData }: any) {
+ const orderCount = parseInt(rowData?.orderCount || '0', 10) || 0
+ const fakeOrderCount = parseInt(rowData?.fakeOrderCount || '0', 10) || 0
+ const fundingGoal = parseInt(rowData?.fundingGoal || '0', 10) || 100
+
+ const totalCount = orderCount + fakeOrderCount
+ const percentage = fundingGoal > 0 ? Math.min(Math.round((totalCount / fundingGoal) * 100), 100) : 0
+
+ return (
+
+
+ 预购进度
+
+ {totalCount} / {fundingGoal}
+
+
+
+
+
+
+
+ 真实
+ {orderCount}
+
+
+ Fake
+ {fakeOrderCount}
+
+
+ 完成度
+ {percentage}%
+
+
+
+ )
+}
diff --git a/src/components/cells/ThumbnailCell.tsx b/src/components/cells/ThumbnailCell.tsx
index 694e902..cd1554d 100644
--- a/src/components/cells/ThumbnailCell.tsx
+++ b/src/components/cells/ThumbnailCell.tsx
@@ -1,19 +1,21 @@
'use client'
import Link from 'next/link'
+import { usePathname } from 'next/navigation'
export const ThumbnailCell = (props: any) => {
- console.log('=== ThumbnailCell All Props ===', props)
- console.log('Props keys:', Object.keys(props))
+ const pathname = usePathname()
+
+ // 从 URL 路径中提取 collection slug
+ const collectionSlug = pathname?.includes('/preorder-products')
+ ? 'preorder-products'
+ : 'products'
// 尝试从不同的 props 路径获取值
const value = props.value || props.cellData || props.data
const rowData = props.rowData || props.row
- console.log('Extracted value:', value)
- console.log('Extracted rowData:', rowData)
-
const isImage = typeof value === 'string' && value.match(/^https?:\/\/.+/)
- const editUrl = `/admin/collections/products/${rowData?.id || ''}`
+ const editUrl = `/admin/collections/${collectionSlug}/${rowData?.id || ''}`
return (
{
+ if (!id) {
+ setMessage('⚠️ 无法获取商品 ID')
+ return
+ }
+
+ setLoading(true)
+ setMessage('')
+
+ try {
+ const response = await fetch('/api/refresh-order-counts', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ productIds: [id],
+ }),
+ })
+
+ const data = await response.json()
+
+ if (data.success) {
+ setMessage(`✅ ${data.message || '订单计数刷新成功!'}`)
+ // 刷新页面数据
+ setTimeout(() => {
+ router.refresh()
+ // 重新加载页面以更新显示
+ window.location.reload()
+ }, 1000)
+ } else {
+ setMessage(`❌ 刷新失败: ${data.error || '未知错误'}`)
+ }
+ } catch (error) {
+ setMessage('❌ 刷新出错: ' + (error instanceof Error ? error.message : '未知错误'))
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ return (
+
+
+
+ 📊 订单计数同步
+
+
+ 从 Medusa 订单系统同步真实订单计数数据
+
+
+
+
+
+ {message && (
+
+ {message}
+
+ )}
+
+
+
💡 说明:
+
+ • 真实订单计数:从 Medusa 自动同步,只读
+
+
+ • Fake计数:上方可手动编辑
+
+
+ • 显示进度 = 真实订单 + Fake计数
+
+
+
+ )
+}
diff --git a/src/components/list/PreorderProductGridStyler.tsx b/src/components/list/PreorderProductGridStyler.tsx
new file mode 100644
index 0000000..9b7b4de
--- /dev/null
+++ b/src/components/list/PreorderProductGridStyler.tsx
@@ -0,0 +1,26 @@
+'use client'
+import { useEffect } from 'react'
+import './preorder-product-grid-styler.scss'
+
+/**
+ * 预购商品网格样式组件
+ * 将列表转换为卡片网格,显示预购进度
+ */
+export function PreorderProductGridStyler() {
+ useEffect(() => {
+ // 组件加载时添加样式类
+ const table = document.querySelector('.collection-list--preorder-products')
+ if (table) {
+ table.classList.add('preorder-grid-view')
+ }
+
+ return () => {
+ const table = document.querySelector('.collection-list--preorder-products')
+ if (table) {
+ table.classList.remove('preorder-grid-view')
+ }
+ }
+ }, [])
+
+ return null // 这是一个纯样式组件,不渲染任何内容
+}
diff --git a/src/components/list/preorder-product-grid-styler.scss b/src/components/list/preorder-product-grid-styler.scss
new file mode 100644
index 0000000..73e38bc
--- /dev/null
+++ b/src/components/list/preorder-product-grid-styler.scss
@@ -0,0 +1,240 @@
+// 预购商品网格视图样式
+// 将表格转换为卡片网格,显示预购进度
+
+.collection-list.collection-list--preorder-products.preorder-grid-view {
+ // 隐藏表头
+ thead {
+ display: none;
+ }
+
+ // 主体使用 Grid 布局
+ tbody {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
+ gap: 1.5rem;
+ padding: 1rem 0;
+ }
+
+ // 每个 tr 变成卡片
+ tr {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--theme-elevation-150);
+ border-radius: 8px;
+ padding: 1rem;
+ background: var(--theme-elevation-0);
+ transition: all 0.2s ease;
+ cursor: pointer;
+ position: relative;
+ overflow: hidden;
+
+ &:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ border-color: var(--theme-elevation-300);
+ transform: translateY(-2px);
+ }
+
+ // 选中状态
+ &.row-selected {
+ border-color: var(--theme-success-500);
+ background: var(--theme-success-50);
+ }
+ }
+
+ // 所有单元格
+ td {
+ border: none !important;
+ padding: 0.25rem 0 !important;
+ background: transparent !important;
+ width: 100% !important;
+ max-width: none !important;
+
+ // 隐藏不需要的列
+ &:not([class*='thumbnail']):not([class*='title']):not([class*='medusaId']):not([class*='status']):not([class*='updatedAt']) {
+ display: none;
+ }
+ }
+
+ // 缩略图
+ td[class*='thumbnail'] {
+ order: -1;
+ margin-bottom: 0.75rem;
+
+ img {
+ width: 100%;
+ height: 200px;
+ object-fit: cover;
+ border-radius: 6px;
+ background: var(--theme-elevation-100);
+ }
+
+ // 如果没有图片,显示占位符
+ &:empty::before {
+ content: '📦';
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 200px;
+ font-size: 4rem;
+ background: var(--theme-elevation-100);
+ border-radius: 6px;
+ color: var(--theme-elevation-400);
+ }
+ }
+
+ // 标题
+ td[class*='title'] {
+ font-size: 1rem;
+ font-weight: 600;
+ color: var(--theme-elevation-1000);
+ margin-bottom: 0.5rem;
+ line-height: 1.4;
+
+ // 限制两行,超出省略
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ }
+
+ // Medusa ID
+ td[class*='medusaId'] {
+ font-size: 0.75rem;
+ color: var(--theme-elevation-500);
+ font-family: monospace;
+ margin-bottom: 0.5rem;
+
+ &::before {
+ content: '🆔 ';
+ }
+ }
+
+ // 状态标签
+ td[class*='status'] {
+ margin: 0.5rem 0;
+
+ .pill {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.25rem 0.75rem;
+ border-radius: 12px;
+ font-size: 0.75rem;
+ font-weight: 500;
+
+ &.pill--published {
+ background: var(--theme-success-100);
+ color: var(--theme-success-900);
+ }
+
+ &.pill--draft {
+ background: var(--theme-warning-100);
+ color: var(--theme-warning-900);
+ }
+ }
+ }
+
+ // 更新时间
+ td[class*='updatedAt'] {
+ font-size: 0.75rem;
+ color: var(--theme-elevation-500);
+ margin-top: auto;
+ padding-top: 0.75rem !important;
+ border-top: 1px solid var(--theme-elevation-100);
+
+ &::before {
+ content: '🕐 ';
+ }
+ }
+
+ // 复选框单元格
+ td:first-child {
+ position: absolute;
+ top: 0.75rem;
+ right: 0.75rem;
+ width: auto !important;
+ z-index: 10;
+
+ input[type='checkbox'] {
+ width: 20px;
+ height: 20px;
+ cursor: pointer;
+ border: 2px solid var(--theme-elevation-400);
+ border-radius: 4px;
+
+ &:checked {
+ background: var(--theme-success-500);
+ border-color: var(--theme-success-500);
+ }
+ }
+ }
+
+ // 操作按钮单元格
+ td:last-child {
+ position: absolute;
+ bottom: 0.75rem;
+ right: 0.75rem;
+ width: auto !important;
+ }
+}
+
+// 预购进度信息(需要通过自定义 Cell 组件添加)
+.preorder-progress-info {
+ margin: 0.75rem 0;
+ padding: 0.75rem;
+ background: var(--theme-elevation-50);
+ border-radius: 6px;
+ border: 1px solid var(--theme-elevation-150);
+
+ .progress-label {
+ display: flex;
+ justify-content: space-between;
+ font-size: 0.75rem;
+ color: var(--theme-elevation-600);
+ margin-bottom: 0.5rem;
+
+ .progress-count {
+ font-weight: 600;
+ color: var(--theme-elevation-900);
+ }
+ }
+
+ .progress-bar {
+ width: 100%;
+ height: 8px;
+ background: var(--theme-elevation-150);
+ border-radius: 4px;
+ overflow: hidden;
+ position: relative;
+
+ .progress-fill {
+ height: 100%;
+ background: linear-gradient(90deg, var(--theme-success-500), var(--theme-success-600));
+ transition: width 0.3s ease;
+ border-radius: 4px;
+ }
+ }
+
+ .progress-stats {
+ display: flex;
+ gap: 1rem;
+ margin-top: 0.5rem;
+ font-size: 0.75rem;
+
+ .stat-item {
+ display: flex;
+ flex-direction: column;
+
+ .stat-label {
+ color: var(--theme-elevation-500);
+ margin-bottom: 0.125rem;
+ }
+
+ .stat-value {
+ font-weight: 600;
+ color: var(--theme-elevation-900);
+ }
+ }
+ }
+}