Files
wechat_wc/src/pages/product/index.tsx
T
broccoli b19a56003f 添加登录和后端校验
完成后端设计(未在本仓库体现),通过安全的手段完成了登录鉴权
2026-08-06 16:27:36 +08:00

185 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { View, Text, Image, Swiper, SwiperItem } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useState } from 'react'
import './index.scss'
import { getProductById } from '../../utils/productConfig'
import { addDesign } from '../../utils/store'
import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import { getProductIconImg } from '../../utils/productConfig'
const HERO_COLORS = ['#FFE4EC', '#FFF0F5', '#FCE4EC']
export default function ProductPage() {
const { theme, resolvedTheme } = useThemeContext()
const safe = useSafeArea()
useStatusBar(resolvedTheme)
const [showModal, setShowModal] = useState(false)
const [quantity, setQuantity] = useState(1)
const params = Taro.getCurrentInstance().router?.params
const productId = params?.id || ''
const product = getProductById(productId)
if (!product) {
return (
<View className={`theme-${resolvedTheme}`}>
<View className='product-page'>
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
<Text className='page-title'>商品未找到</Text>
</View>
</View>
</View>
)
}
const handleAddToList = () => {
setQuantity(1)
setShowModal(true)
}
const confirmAdd = () => {
addDesign(product, quantity)
setShowModal(false)
Taro.showToast({ title: `已加入设计清单 x${quantity}`, icon: 'success' })
setTimeout(() => Taro.navigateBack(), 1200)
}
const goToDIY = () => {
Taro.navigateTo({
url: `/pages/diy/index?source=product&productId=${product.id}&quantity=1`
})
}
const modQty = (delta: number) => {
setQuantity(q => Math.max(1, Math.min(99, q + delta)))
}
return (
<View className={`theme-${resolvedTheme}`}>
<View className='product-page'>
<View className='page-header dashed-card mt-20' style={{ marginTop: `${safe.headerPaddingTop}px` }}>
<View className='star-badge' />
<View className='flex-between'>
<Text className='back-btn' onTap={() => Taro.navigateBack()}></Text>
<Text className='page-title'>商品详情</Text>
<View style={{ width: '60px' }} />
</View>
</View>
{/* 轮播 Hero */}
<View className='hero-section mt-20'>
<Swiper
className='hero-swiper'
circular
autoplay
interval={3000}
duration={500}
indicatorDots
indicatorColor='rgba(0,0,0,0.2)'
indicatorActiveColor='#ff9a9e'
>
{product.images && product.images.length > 0 ? (
product.images.map((img, idx) => (
<SwiperItem key={idx}>
<View className='hero-card dashed-card'>
<View className='star-badge' />
<Image className='hero-image' src={img} mode='aspectFit' />
</View>
</SwiperItem>
))
) : (
HERO_COLORS.map((color, idx) => (
<SwiperItem key={idx}>
<View className='hero-card dashed-card' style={{ background: color }}>
<View className='star-badge' />
<Image className='hero-icon-img' src={getProductIconImg(product)} mode='aspectFit' />
<Text className='hero-name'>{product.name}</Text>
</View>
</SwiperItem>
))
)}
</Swiper>
</View>
{/* 基本信息 */}
<View className='info-section dashed-card mt-20'>
<View className='star-badge' />
<View className='info-row'>
<Text className='info-label'>单价</Text>
<Text className='info-price'>¥{product.price}</Text>
</View>
<View className='info-row'>
<Text className='info-label'>工期</Text>
<Text className='info-value'>{product.leadTime}</Text>
</View>
<View className='info-row'>
<Text className='info-label'>尺寸</Text>
<Text className='info-value'>{product.mask.width} × {product.mask.height} px</Text>
</View>
</View>
{/* 详细介绍 */}
<View className='detail-section dashed-card mt-20'>
<View className='star-badge' />
<Text className='section-title'>商品介绍</Text>
<Text className='detail-text'>{product.description}</Text>
</View>
{/* 底部操作栏 */}
<View className='action-bar'>
<View className='price-summary'>
<Text className='summary-label'>起价</Text>
<Text className='summary-price'>¥{product.price}</Text>
</View>
<View className='btn-outline' onTap={handleAddToList}>
<Text>加入设计清单</Text>
</View>
<View className='btn-gradient' onTap={goToDIY}>
<Text>立即下单</Text>
</View>
</View>
{/* 安全区占位(防止内容被 action-bar 遮挡) */}
<View className='safe-bottom-placeholder' />
{/* 数量选择弹窗 */}
{showModal && (
<View className='modal-overlay'>
<View className='modal-card dashed-card'>
<View className='star-badge' />
<Text className='modal-title'>加入设计清单</Text>
<View className='modal-product'>
<Image className='modal-icon-img' src={getProductIconImg(product)} mode='aspectFit' />
<Text className='modal-name'>{product.name}</Text>
</View>
<View className='quantity-row'>
<Text className='qty-label'>数量</Text>
<View className='qty-control'>
<View className='qty-btn' onTap={() => modQty(-1)}></View>
<Text className='qty-num'>{quantity}</Text>
<View className='qty-btn' onTap={() => modQty(1)}></View>
</View>
</View>
<View className='modal-total'>
<Text className='total-label'>小计</Text>
<Text className='total-price'>¥{(product.price * quantity).toFixed(2)}</Text>
</View>
<View className='modal-actions'>
<View className='btn-outline' onTap={() => setShowModal(false)}>
<Text>取消</Text>
</View>
<View className='btn-gradient' onTap={confirmAdd}>
<Text>确定</Text>
</View>
</View>
</View>
</View>
)}
</View>
</View>
)
}