|
|
@@ -1,4 +1,4 @@
|
|
|
-import { View, ScrollView, Text, RichText, Input } from '@tarojs/components'
|
|
|
+import { View, ScrollView, Text, Input, Image } from '@tarojs/components'
|
|
|
import { useQuery } from '@tanstack/react-query'
|
|
|
import { useState, useEffect } from 'react'
|
|
|
import Taro, { useRouter, useShareAppMessage } from '@tarojs/taro'
|
|
|
@@ -8,6 +8,7 @@ import { Navbar } from '@/components/ui/navbar'
|
|
|
import { Button } from '@/components/ui/button'
|
|
|
import { Carousel } from '@/components/ui/carousel'
|
|
|
import { GoodsSpecSelector } from '@/components/goods-spec-selector'
|
|
|
+import TDesignTabs from '@/components/tdesign/tabs'
|
|
|
import { useCart } from '@/contexts/CartContext'
|
|
|
import './index.css'
|
|
|
|
|
|
@@ -21,68 +22,15 @@ interface SelectedSpec {
|
|
|
image?: string
|
|
|
}
|
|
|
|
|
|
-interface Review {
|
|
|
- id: number
|
|
|
- userName: string
|
|
|
- rating: number
|
|
|
- content: string
|
|
|
- createTime: string
|
|
|
- images?: string[]
|
|
|
-}
|
|
|
-
|
|
|
-interface ReviewStats {
|
|
|
- averageRating: number
|
|
|
- totalCount: number
|
|
|
- goodRate: number
|
|
|
- ratingDistribution: {
|
|
|
- [key: number]: number
|
|
|
- }
|
|
|
-}
|
|
|
|
|
|
export default function GoodsDetailPage() {
|
|
|
const [quantity, setQuantity] = useState(1)
|
|
|
const [selectedSpec, setSelectedSpec] = useState<SelectedSpec | null>(null)
|
|
|
const [showSpecModal, setShowSpecModal] = useState(false)
|
|
|
- const [pendingAction, setPendingAction] = useState<'add-to-cart' | 'buy-now' | null>(null)
|
|
|
+ const [pendingAction, setPendingAction] = useState<'add-to-cart' | 'buy-now' | undefined>(undefined)
|
|
|
+ const [activeTab, setActiveTab] = useState<string>('商品详情')
|
|
|
const { addToCart } = useCart()
|
|
|
|
|
|
- // 模拟评价数据
|
|
|
- const [reviewStats] = useState<ReviewStats>({
|
|
|
- averageRating: 4.8,
|
|
|
- totalCount: 156,
|
|
|
- goodRate: 0.95,
|
|
|
- ratingDistribution: {
|
|
|
- 5: 120,
|
|
|
- 4: 25,
|
|
|
- 3: 8,
|
|
|
- 2: 2,
|
|
|
- 1: 1
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- const [reviews] = useState<Review[]>([
|
|
|
- {
|
|
|
- id: 1,
|
|
|
- userName: '用户****1234',
|
|
|
- rating: 5,
|
|
|
- content: '商品质量很好,物流很快,非常满意!包装也很精美,下次还会再来购买。',
|
|
|
- createTime: '2025-11-20'
|
|
|
- },
|
|
|
- {
|
|
|
- id: 2,
|
|
|
- userName: '用户****5678',
|
|
|
- rating: 4,
|
|
|
- content: '商品不错,性价比很高,就是物流稍微慢了一点,总体还是很满意的。',
|
|
|
- createTime: '2025-11-19'
|
|
|
- },
|
|
|
- {
|
|
|
- id: 3,
|
|
|
- userName: '用户****9012',
|
|
|
- rating: 5,
|
|
|
- content: '非常喜欢这个商品,质量超出预期,客服态度也很好,五星好评!',
|
|
|
- createTime: '2025-11-18'
|
|
|
- }
|
|
|
- ])
|
|
|
|
|
|
// 使用useRouter钩子获取路由参数
|
|
|
const router = useRouter()
|
|
|
@@ -257,13 +205,6 @@ export default function GoodsDetailPage() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 处理商品描述中的换行符
|
|
|
- const renderGoodsDescription = (description: string | undefined) => {
|
|
|
- if (!description) return '暂无商品描述'
|
|
|
-
|
|
|
- // 将换行符转换为可显示的格式,确保换行生效
|
|
|
- return description
|
|
|
- }
|
|
|
|
|
|
// 规格选择确认
|
|
|
const handleSpecConfirm = (spec: SelectedSpec | null, qty: number, actionType?: 'add-to-cart' | 'buy-now') => {
|
|
|
@@ -289,7 +230,7 @@ export default function GoodsDetailPage() {
|
|
|
title: '库存不足',
|
|
|
icon: 'none'
|
|
|
})
|
|
|
- setPendingAction(null)
|
|
|
+ setPendingAction(undefined)
|
|
|
setShowSpecModal(false)
|
|
|
return
|
|
|
}
|
|
|
@@ -324,7 +265,7 @@ export default function GoodsDetailPage() {
|
|
|
title: '库存不足',
|
|
|
icon: 'none'
|
|
|
})
|
|
|
- setPendingAction(null)
|
|
|
+ setPendingAction(undefined)
|
|
|
setShowSpecModal(false)
|
|
|
return
|
|
|
}
|
|
|
@@ -351,14 +292,10 @@ export default function GoodsDetailPage() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- setPendingAction(null)
|
|
|
+ setPendingAction(undefined)
|
|
|
setShowSpecModal(false)
|
|
|
}
|
|
|
|
|
|
- // 打开规格选择弹窗
|
|
|
- const handleOpenSpecModal = () => {
|
|
|
- setShowSpecModal(true)
|
|
|
- }
|
|
|
|
|
|
// 添加到购物车
|
|
|
const handleAddToCart = () => {
|
|
|
@@ -376,7 +313,6 @@ export default function GoodsDetailPage() {
|
|
|
const targetGoodsName = selectedSpec ? selectedSpec.name : goods.name
|
|
|
const targetPrice = selectedSpec ? selectedSpec.price : goods.price
|
|
|
const targetStock = selectedSpec ? selectedSpec.stock : goods.stock
|
|
|
- const targetSpec = selectedSpec ? selectedSpec.name : ''
|
|
|
|
|
|
const finalQuantity = quantity === 0 ? 1 : quantity
|
|
|
|
|
|
@@ -441,7 +377,6 @@ export default function GoodsDetailPage() {
|
|
|
const targetGoodsName = selectedSpec ? selectedSpec.name : goods.name
|
|
|
const targetPrice = selectedSpec ? selectedSpec.price : goods.price
|
|
|
const targetStock = selectedSpec ? selectedSpec.stock : goods.stock
|
|
|
- const targetSpec = selectedSpec ? selectedSpec.name : ''
|
|
|
const finalQuantity = quantity === 0 ? 1 : quantity
|
|
|
|
|
|
if (finalQuantity > targetStock) {
|
|
|
@@ -518,7 +453,7 @@ export default function GoodsDetailPage() {
|
|
|
autoplay={true}
|
|
|
interval={4000}
|
|
|
circular={true}
|
|
|
- onItemClick={(item, index) => {
|
|
|
+ onItemClick={(_, index) => {
|
|
|
// 点击图片放大预览
|
|
|
Taro.previewImage({
|
|
|
urls: carouselItems.map(item => item.src),
|
|
|
@@ -550,35 +485,77 @@ export default function GoodsDetailPage() {
|
|
|
|
|
|
{/* 商品评价区域 - 暂时移除,后端暂无评价API */}
|
|
|
|
|
|
- {/* 商品详情区域 */}
|
|
|
+ {/* 商品详情区域 - 改为标签页 */}
|
|
|
<View className="detail-section">
|
|
|
- <Text className="detail-title">商品详情</Text>
|
|
|
-
|
|
|
- <View>
|
|
|
- <Text className="goods-description">{goods.instructions || '暂无商品描述'}</Text>
|
|
|
- </View>
|
|
|
-
|
|
|
- {/* {goods.detail && goods.detail.trim() ? (
|
|
|
- <RichText
|
|
|
- className="detail-content"
|
|
|
- nodes={goods.detail
|
|
|
- .replace(/<img/g, '<img style="max-width:100%;height:auto;display:block;margin:0 auto"')
|
|
|
- .replace(/<p>/g, '<p style="margin:10px 0;line-height:1.6"')
|
|
|
- .replace(/<h1>/g, '<h1 style="font-size:32rpx;font-weight:bold;margin:20rpx 0"')
|
|
|
- .replace(/<h2>/g, '<h2 style="font-size:30rpx;font-weight:bold;margin:18rpx 0"')
|
|
|
- .replace(/<h3>/g, '<h3 style="font-size:28rpx;font-weight:bold;margin:16rpx 0"')
|
|
|
- }
|
|
|
- />
|
|
|
- ) : (
|
|
|
- <Text className="no-detail">暂无商品详情</Text>
|
|
|
- )} */}
|
|
|
+ <TDesignTabs
|
|
|
+ value={activeTab}
|
|
|
+ onChange={(value) => setActiveTab(value as string)}
|
|
|
+ list={[
|
|
|
+ { label: '商品详情', value: '商品详情' },
|
|
|
+ { label: '产品细节图', value: '产品细节图' }
|
|
|
+ ]}
|
|
|
+ theme="line"
|
|
|
+ spaceEvenly={true}
|
|
|
+ showBottomLine={true}
|
|
|
+ >
|
|
|
+ {/* 商品详情标签页内容 */}
|
|
|
+ <View className="detail-tab-content">
|
|
|
+ <View>
|
|
|
+ <Text className="goods-description">{goods.instructions || '暂无商品描述'}</Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* {goods.detail && goods.detail.trim() ? (
|
|
|
+ <RichText
|
|
|
+ className="detail-content"
|
|
|
+ nodes={goods.detail
|
|
|
+ .replace(/<img/g, '<img style="max-width:100%;height:auto;display:block;margin:0 auto"')
|
|
|
+ .replace(/<p>/g, '<p style="margin:10px 0;line-height:1.6"')
|
|
|
+ .replace(/<h1>/g, '<h1 style="font-size:32rpx;font-weight:bold;margin:20rpx 0"')
|
|
|
+ .replace(/<h2>/g, '<h2 style="font-size:30rpx;font-weight:bold;margin:18rpx 0"')
|
|
|
+ .replace(/<h3>/g, '<h3 style="font-size:28rpx;font-weight:bold;margin:16rpx 0"')
|
|
|
+ }
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <Text className="no-detail">暂无商品详情</Text>
|
|
|
+ )} */}
|
|
|
+ </View>
|
|
|
|
|
|
+ {/* 产品细节图标签页内容 */}
|
|
|
+ <View className="detail-tab-content">
|
|
|
+ {carouselItems.length > 0 ? (
|
|
|
+ <View className="detail-images-grid">
|
|
|
+ {carouselItems.map((item, index) => (
|
|
|
+ <View
|
|
|
+ key={index}
|
|
|
+ className="detail-image-item"
|
|
|
+ onClick={() => {
|
|
|
+ // 点击图片放大预览
|
|
|
+ Taro.previewImage({
|
|
|
+ urls: carouselItems.map(item => item.src),
|
|
|
+ current: item.src
|
|
|
+ })
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Image
|
|
|
+ src={item.src}
|
|
|
+ mode="aspectFit"
|
|
|
+ className="detail-image"
|
|
|
+ style={{ width: '100%' }}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ ))}
|
|
|
+ </View>
|
|
|
+ ) : (
|
|
|
+ <Text className="no-detail-images">暂无产品细节图</Text>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+ </TDesignTabs>
|
|
|
</View>
|
|
|
</ScrollView>
|
|
|
|
|
|
{/* 底部操作栏 */}
|
|
|
<View className="bottom-action-bar">
|
|
|
- { <View className="quantity-section">
|
|
|
+ <View className="quantity-section">
|
|
|
<Text className="quantity-label">数量</Text>
|
|
|
<View className="quantity-controls">
|
|
|
<Button
|
|
|
@@ -608,7 +585,7 @@ export default function GoodsDetailPage() {
|
|
|
+
|
|
|
</Button>
|
|
|
</View>
|
|
|
- </View> }
|
|
|
+ </View>
|
|
|
|
|
|
|
|
|
|
|
|
@@ -647,7 +624,7 @@ export default function GoodsDetailPage() {
|
|
|
visible={showSpecModal}
|
|
|
onClose={() => {
|
|
|
setShowSpecModal(false)
|
|
|
- setPendingAction(null) // 重置待处理操作
|
|
|
+ setPendingAction(undefined) // 重置待处理操作
|
|
|
}}
|
|
|
onConfirm={handleSpecConfirm}
|
|
|
parentGoodsId={goods?.id || 0}
|