Selaa lähdekoodia

✨ feat(goods-detail): 重构商品详情页为标签页布局并优化样式

- 将商品详情区域重构为标签页布局,包含【商品详情】和【产品细节图】两个标签
- 新增产品细节图展示区域,支持图片点击预览功能
- 优化TDesignTabs组件,支持等宽分布和自定义指示线宽度计算
- 移除未使用的评价相关代码和组件导入

💄 style(goods-detail): 调整商品详情页样式和布局

- 隐藏详情标题,改为标签页形式展示内容
- 调整详情区域内边距和底部间距
- 为标签页内容区域添加专属样式类
- 优化商品描述文本的显示样式

♻️ refactor(tabs): 优化TDesignTabs组件样式逻辑

- 动态计算指示线宽度,支持等宽分布模式
- 添加自定义CSS属性用于外部样式控制
- 改进类型定义以支持扩展样式属性
yourname 1 kuukausi sitten
vanhempi
sitoutus
961608f9f6

+ 4 - 3
mini/src/components/tdesign/tabs/index.tsx

@@ -162,12 +162,13 @@ export default function TDesignTabs({
                 className="tdesign-tabs__track"
                 style={{
                   backgroundColor: 'var(--td-tab-track-color, #fa550f)',
-                  width: 'var(--td-tab-track-width, 32rpx)',
+                  width: spaceEvenly && list.length > 0 ? `${100 / list.length}%` : 'var(--td-tab-track-width, 64rpx)',
                   height: 'var(--td-tab-track-thickness, 6rpx)',
                   borderRadius: 'var(--td-tab-track-radius, 8rpx)',
                   transform: `translateX(${currentIndex * 100}%)`,
-                  transition: 'transform 0.3s ease'
-                }}
+                  transition: 'transform 0.3s ease',
+                  '--tab-index': currentIndex.toString()
+                } as React.CSSProperties & Record<string, string>}
               />
             )}
           </View>

+ 100 - 1
mini/src/pages/goods-detail/index.css

@@ -253,10 +253,11 @@
 /* 详情区域 */
 .detail-section {
   background: white;
-  padding: 32rpx 24rpx;
+  padding: 32rpx 24rpx 32rpx;
 }
 
 .detail-title {
+  display: none;
   font-size: 32rpx;
   font-weight: 600;
   color: #333;
@@ -407,3 +408,101 @@
   transform: none;
 }
 
+/* 详情栏产品细节图 */
+.detail-images-section {
+  margin-top: 40rpx;
+  padding: 32rpx 0;
+  border-top: 1rpx solid #f0f0f0;
+}
+
+.detail-images-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #333;
+  margin-bottom: 32rpx;
+  text-align: left;
+}
+
+.detail-images-grid {
+  display: flex;
+  flex-direction: column;
+  gap: 32rpx;
+  margin-bottom: 100rpx;
+}
+
+.detail-image-item {
+  background: white;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
+  transition: transform 0.2s ease;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 400rpx;
+  max-height: 800rpx;
+}
+
+.detail-image-item:active {
+  transform: scale(0.98);
+}
+
+.detail-image {
+  max-width: 100%;
+  max-height: 100%;
+  display: block;
+  object-fit: contain;
+}
+
+.no-detail-images {
+  font-size: 28rpx;
+  color: #999;
+  text-align: center;
+  padding: 60rpx 0;
+}
+
+/* 标签页内容区域 */
+.detail-tab-content {
+  padding: 32rpx 0;
+}
+
+/* 调整详情区域,移除标题样式 */
+.detail-section {
+  background: white;
+  padding: 32rpx 24rpx 32rpx;
+}
+
+/* 商品描述文本 */
+.goods-description {
+  font-size: 28rpx;
+  color: #666;
+  line-height: 2;
+  text-indent: 2em;
+  display: block;
+}
+
+
+/* 移除不再使用的样式 */
+.detail-title {
+  display: none;
+}
+
+/* 自定义TDesignTabs样式 - 商品详情页 */
+.detail-section .tdesign-tabs {
+  --td-tab-item-active-color: #fa4126;
+}
+
+/* 调整标签项内边距,让文字周围有更多空间 */
+.detail-section .tdesign-tabs__item {
+  padding: 0 20rpx;
+}
+
+/* 调整指示线位置和宽度,使其在标签文字下方居中 */
+.detail-section .tdesign-tabs__track {
+  width: 50% !important;
+  left: calc(13% + var(--tab-index, 0) * 50%) !important;
+  transform: none !important;
+  max-width: 150rpx;
+  transition: left 0.3s ease !important;
+}
+

+ 74 - 97
mini/src/pages/goods-detail/index.tsx

@@ -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}