This commit is contained in:
龟男日记\www 2026-02-09 03:52:44 +08:00
parent 3ad86524d4
commit cccbe20aa0
6 changed files with 120 additions and 18 deletions

View File

@ -1,3 +1,5 @@
import { ThumbnailCell as ThumbnailCell_a0b2acb813359aec894b6644d7c3bfd2 } from '../../../components/products/ThumbnailCell'
import { ThumbnailAndStatusField as ThumbnailAndStatusField_8fa95ec6265982d11b99fbeb81e24c1c } from '../../../components/products/ThumbnailAndStatusField'
import { SyncMedusaButton as SyncMedusaButton_8c90663551920f0510ea531726668adc } from '../../../components/products/SyncMedusaButton' import { SyncMedusaButton as SyncMedusaButton_8c90663551920f0510ea531726668adc } from '../../../components/products/SyncMedusaButton'
import { default as default_3fd1353246fc8a459244c8dc11f58470 } from '../../../components/products/ProductGridStyler' import { default as default_3fd1353246fc8a459244c8dc11f58470 } from '../../../components/products/ProductGridStyler'
import { ForceSyncButton as ForceSyncButton_86f9d5df4f20495427521354d06db618 } from '../../../components/products/ForceSyncButton' import { ForceSyncButton as ForceSyncButton_86f9d5df4f20495427521354d06db618 } from '../../../components/products/ForceSyncButton'
@ -5,6 +7,8 @@ import { S3ClientUploadHandler as S3ClientUploadHandler_f97aa6c64367fa259c5bc056
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc' import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
export const importMap = { export const importMap = {
"/components/products/ThumbnailCell#ThumbnailCell": ThumbnailCell_a0b2acb813359aec894b6644d7c3bfd2,
"/components/products/ThumbnailAndStatusField#ThumbnailAndStatusField": ThumbnailAndStatusField_8fa95ec6265982d11b99fbeb81e24c1c,
"/components/products/SyncMedusaButton#SyncMedusaButton": SyncMedusaButton_8c90663551920f0510ea531726668adc, "/components/products/SyncMedusaButton#SyncMedusaButton": SyncMedusaButton_8c90663551920f0510ea531726668adc,
"/components/products/ProductGridStyler#default": default_3fd1353246fc8a459244c8dc11f58470, "/components/products/ProductGridStyler#default": default_3fd1353246fc8a459244c8dc11f58470,
"/components/products/ForceSyncButton#ForceSyncButton": ForceSyncButton_86f9d5df4f20495427521354d06db618, "/components/products/ForceSyncButton#ForceSyncButton": ForceSyncButton_86f9d5df4f20495427521354d06db618,

View File

@ -8,7 +8,7 @@ export const Products: CollectionConfig = {
description: '管理 Medusa 商品的详细内容和描述', description: '管理 Medusa 商品的详细内容和描述',
listSearchableFields: ['title', 'medusaId', 'handle'], listSearchableFields: ['title', 'medusaId', 'handle'],
pagination: { pagination: {
defaultLimit: 48, defaultLimit: 25,
}, },
components: { components: {
edit: { edit: {
@ -57,6 +57,10 @@ export const Products: CollectionConfig = {
admin: { admin: {
description: '商品缩略图 URL从 Medusa 同步)', description: '商品缩略图 URL从 Medusa 同步)',
readOnly: true, readOnly: true,
components: {
Cell: '/components/products/ThumbnailCell#ThumbnailCell',
Field: '/components/products/ThumbnailAndStatusField#ThumbnailAndStatusField',
},
}, },
}, },
{ {

View File

@ -0,0 +1,7 @@
'use client'
import type { SelectFieldClientComponent } from 'payload'
// 隐藏字段因为在ThumbnailAndStatusField中已经显示和编辑
export const HiddenField: SelectFieldClientComponent = () => {
return null
}

View File

@ -0,0 +1,60 @@
'use client'
import { useFormFields, useField } from '@payloadcms/ui'
import type { TextFieldClientComponent } from 'payload'
// 并排显示缩略图和状态(带状态选择器)
export const ThumbnailAndStatusField: TextFieldClientComponent = ({ path }) => {
// 获取thumbnail值
const fields = useFormFields(([fields]) => fields)
const thumbnail = fields.thumbnail?.value
// 获取status字段的值和setter
const { value: status, setValue: setStatus } = useField({ path: 'status' })
const isImage = typeof thumbnail === 'string' && thumbnail.match(/^https?:\/\/.+/)
return (
<div>
<label style={{ display: 'block', marginBottom: 8, fontWeight: 500 }}></label>
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 16 }}>
<div
style={{
width: 100,
height: 100,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 8,
flexShrink: 0,
}}
>
{isImage ? (
<img
src={thumbnail}
alt="商品缩略图"
style={{ maxWidth: '100%', maxHeight: '100%', borderRadius: 6 }}
/>
) : (
<span style={{ color: '#bbb' }}></span>
)}
</div>
<div style={{ flex: 1 }}>
<label style={{ display: 'block', marginBottom: 4, fontSize: 14 }}></label>
<select
value={(status as string) || 'draft'}
onChange={(e) => setStatus(e.target.value)}
style={{
padding: '8px 12px',
borderRadius: 4,
fontSize: 14,
minWidth: 120,
}}
>
<option value="draft">稿</option>
<option value="published"></option>
</select>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,30 @@
'use client'
import Link from 'next/link'
export const ThumbnailCell = (props: any) => {
console.log('=== ThumbnailCell All Props ===', props)
console.log('Props keys:', Object.keys(props))
// 尝试从不同的 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 || ''}`
return (
<Link
href={editUrl}
style={{ display: 'block', width: '100%', height: '200px', textDecoration: 'none' }}
>
{isImage ? (
<img src={value} alt="商品缩略图" className="thumbnail-img" />
) : (
<div className="no-image">{value || '无图片'}</div>
)}
</Link>
)
}

View File

@ -27,11 +27,14 @@
transition: all 0.2s ease-in-out !important; transition: all 0.2s ease-in-out !important;
height: 100% !important; height: 100% !important;
min-height: 320px; min-height: 320px;
cursor: pointer;
text-decoration: none !important;
&:hover { &:hover {
transform: translateY(-4px); transform: translateY(-4px);
box-shadow: 0 4px 12px -2px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 12px -2px rgba(0, 0, 0, 0.1);
border-color: var(--theme-elevation-300) !important; border-color: var(--theme-elevation-300) !important;
background: var(--theme-elevation-100) !important;
} }
td { td {
@ -59,8 +62,6 @@
} }
// 2. Thumbnail (First content column) // 2. Thumbnail (First content column)
// We depend on 'thumbnail' being the first custom column in Products.ts
// Note: td:nth-child(2) because checkbox is #1
&:nth-child(2) { &:nth-child(2) {
padding: 0 !important; padding: 0 !important;
height: 200px !important; height: 200px !important;
@ -69,27 +70,23 @@
background: var(--theme-elevation-100) !important; background: var(--theme-elevation-100) !important;
order: -1 !important; // Force to top order: -1 !important; // Force to top
flex-grow: 0 !important; flex-grow: 0 !important;
position: relative;
// Support for Payload's file cell or specific image structures
.file-cell, .thumbnail { img {
width: 100%; width: 100% !important;
height: 100%; height: 100% !important;
object-fit: cover !important;
img { display: block !important;
width: 100%; background: var(--theme-elevation-100);
height: 100%;
object-fit: cover;
}
} }
.no-image {
// Fallback purely text content in that cell
span {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100%; height: 100%;
color: var(--theme-elevation-400); color: var(--theme-elevation-400);
font-size: 0.8rem; font-size: 0.8rem;
background: transparent;
} }
} }