From 35928a6144255e7daa2e51ac319e19317e826e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=9F=E7=94=B7=E6=97=A5=E8=AE=B0=5Cwww?= Date: Fri, 20 Feb 2026 04:56:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=86=E7=B1=BB=20preoder=20=E5=92=8C=20=20o?= =?UTF-8?q?rder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(payload)/admin/importMap.js | 8 +- src/collections/PreorderProducts.ts | 39 ++- src/collections/Products.ts | 11 + src/components/cells/PreorderProgressCell.tsx | 48 ++++ src/components/cells/ThumbnailCell.tsx | 14 +- .../fields/RefreshOrderCountField.tsx | 130 ++++++++++ .../list/PreorderProductGridStyler.tsx | 26 ++ .../list/preorder-product-grid-styler.scss | 240 ++++++++++++++++++ 8 files changed, 504 insertions(+), 12 deletions(-) create mode 100644 src/components/cells/PreorderProgressCell.tsx create mode 100644 src/components/fields/RefreshOrderCountField.tsx create mode 100644 src/components/list/PreorderProductGridStyler.tsx create mode 100644 src/components/list/preorder-product-grid-styler.scss 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); + } + } + } +}