fix: 修复 HomeClient 和 ProductListItem 类型兼容性问题

This commit is contained in:
WuYongJie 2025-12-22 18:29:19 +08:00
parent 8a88fcde10
commit 144a5fc944
4 changed files with 55 additions and 16 deletions

View File

@ -10,19 +10,53 @@ import { useDebounce } from '@/hooks/usePerformance'
type TabType = 'models' | 'hardware' | 'bom' | 'docs'
// 产品 SKU 类型(与 ProductListItem 兼容)
interface ProductSku {
id: string
skuCode: string
price: number
stock: number
isActive: boolean
name?: string | null
}
// 产品类型(与 ProductListItem 兼容)
interface ProductWithSkus {
id: string
name: string
modelNumber: string
description?: string | null
category: string | null
modelUrl?: string | null
thumbnailUrl?: string | null
visibility?: 'TEAM' | 'DEPARTMENT' | 'PUBLIC'
skus: ProductSku[]
createdAt: Date | string
updatedAt?: Date | string
_count: {
assemblySteps: number
shareLinks: number
skus: number
}
shareLinks: Array<{
token: string
viewCount: number
}>
}
interface HomeClientProps {
products: any[]
products: ProductWithSkus[]
}
export default function HomeClient({ products: initialProducts }: HomeClientProps) {
const [activeTab, setActiveTab] = useState<TabType>('models')
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false)
const [products, setProducts] = useState(initialProducts)
const [products, setProducts] = useState<ProductWithSkus[]>(initialProducts)
const [searchQuery, setSearchQuery] = useState('')
// 控制“新建型号”弹窗
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false)
// 控制“编辑产品”弹窗以及当前要编辑的产品
const [editingProduct, setEditingProduct] = useState<any | null>(null)
const [editingProduct, setEditingProduct] = useState<ProductWithSkus | null>(null)
// 新建型号表单状态
const [newModelNumber, setNewModelNumber] = useState('')
const [newName, setNewName] = useState('')
@ -77,12 +111,12 @@ export default function HomeClient({ products: initialProducts }: HomeClientProp
const query = raw.toLowerCase()
return products.filter((product: any) => {
return products.filter((product) => {
const inModelNumber = product.modelNumber?.toLowerCase().includes(query)
const inName = product.name?.toLowerCase().includes(query)
const inDescription = product.description?.toLowerCase().includes(query)
const inSkuCodes = Array.isArray(product.skus)
? product.skus.some((sku: any) =>
? product.skus.some((sku) =>
sku.skuCode?.toLowerCase().includes(query)
)
: false
@ -174,7 +208,7 @@ export default function HomeClient({ products: initialProducts }: HomeClientProp
}, [newModelNumber, newName, newCategory, newDescription, newVisibility, reloadProducts])
// 打开“编辑产品”弹窗,并根据当前产品初始化表单
const handleOpenEditProduct = useCallback((product: any) => {
const handleOpenEditProduct = useCallback((product: ProductWithSkus) => {
setEditingProduct(product)
setEditName(product.name || '')
setEditCategory(product.category || '')
@ -734,7 +768,7 @@ interface TabCardProps {
id: TabType
name: string
description: string
icon: any
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
}
isActive: boolean
onClick: (tabId: TabType) => void

View File

@ -68,6 +68,7 @@ export default async function HomePage() {
// 手动计算 count 并转换 Decimal 类型
const productsWithCount = products.map(product => ({
...product,
visibility: product.visibility as 'TEAM' | 'DEPARTMENT' | 'PUBLIC',
skus: product.skus.map(sku => ({
...sku,
price: sku.price.toNumber() // 将 Prisma Decimal 转换为 number

View File

@ -9,16 +9,15 @@
* - 使用 useMemo 缓存计算结果
*
* @created 2025-12-12
* @updated 2025-12-13 - 性能优化
* @updated 2025-12-22 - 修复类型兼容性
*/
'use client'
import React, { memo, useCallback, useMemo } from 'react'
import Link from 'next/link'
import { ChevronDown, ChevronRight, Package, Eye, User } from 'lucide-react'
import { ChevronDown, ChevronRight, Package, User } from 'lucide-react'
import Badge from '@/components/ui/Badge'
import Image from 'next/image'
// 产品列表项组件的入参类型
interface ProductListItemProps {
@ -27,7 +26,10 @@ interface ProductListItemProps {
name: string
modelNumber: string
category: string | null
createdAt: Date
createdAt: Date | string
updatedAt?: Date | string
description?: string | null
visibility?: 'TEAM' | 'DEPARTMENT' | 'PUBLIC'
_count: {
assemblySteps: number
shareLinks: number
@ -45,8 +47,9 @@ interface ProductListItemProps {
isActive: boolean
}>
}
// 当点击“编辑产品”按钮时触发,由父组件控制弹窗
onEdit?: (product: ProductListItemProps['product']) => void
// 当点击"编辑产品"按钮时触发,由父组件控制弹窗
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onEdit?: (product: any) => void
}
const ProductListItem = memo(function ProductListItem({ product, onEdit }: ProductListItemProps) {
@ -142,7 +145,6 @@ const ProductListItem = memo(function ProductListItem({ product, onEdit }: Produ
color: 'var(--color-label-primary)',
cursor: 'pointer'
}}
// 交给父组件决定如何编辑(弹窗形式)
onClick={() => onEdit?.(product)}
>
编辑产品
@ -205,7 +207,7 @@ const ProductListItem = memo(function ProductListItem({ product, onEdit }: Produ
{sku.isActive ? '已上架' : '未上架'}
</Badge>
{/* SKU 预览按钮 - 去掉图标 */}
{/* SKU 预览按钮 */}
{shareLink && (
<Link href={`/share/${shareLink.token}?sku=${sku.id}`}>
<button
@ -222,7 +224,7 @@ const ProductListItem = memo(function ProductListItem({ product, onEdit }: Produ
</Link>
)}
{/* SKU 编辑按钮 - 改名为编辑模型 */}
{/* SKU 编辑按钮 */}
<button
className="px-4 py-2 rounded-apple-md transition-all text-sm font-medium"
style={{

View File

@ -57,6 +57,7 @@ describe('HomeClient 组件测试', () => {
description: '这是测试产品1的描述',
category: '家具',
createdAt: new Date('2025-01-01'),
updatedAt: new Date('2025-01-01'),
_count: {
assemblySteps: 5,
shareLinks: 1,
@ -75,6 +76,7 @@ describe('HomeClient 组件测试', () => {
description: '这是测试产品2的描述',
category: '家具',
createdAt: new Date('2025-01-02'),
updatedAt: new Date('2025-01-02'),
_count: {
assemblySteps: 3,
shareLinks: 0,