feat(components): 新增 GiftCard礼品卡组件
- 新增 GiftCard 组件,支持多种类型礼品卡的展示和交互 - 组件包含商品信息、价格、折扣、使用指南等丰富功能- 优化图像展示,支持单
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import { View, Text, Image } from '@tarojs/components'
|
||||
import { Button, Tag } from '@nutui/nutui-react-taro'
|
||||
import { Gift, Clock, Location, Phone } from '@nutui/icons-react-taro'
|
||||
import React, { useState } from 'react'
|
||||
import { View, Text, Image, Swiper, SwiperItem } from '@tarojs/components'
|
||||
import { Button, Tag, Rate } from '@nutui/nutui-react-taro'
|
||||
import { Gift, Clock, Location, Phone, Star, Eye, ShoppingCart, Tips } from '@nutui/icons-react-taro'
|
||||
import dayjs from 'dayjs'
|
||||
import './GiftCard.scss'
|
||||
|
||||
@@ -10,14 +10,20 @@ export interface GiftCardProps {
|
||||
id: number
|
||||
/** 礼品卡名称 */
|
||||
name: string
|
||||
/** 商品名称 */
|
||||
goodsName?: string
|
||||
/** 礼品卡描述 */
|
||||
description?: string
|
||||
/** 礼品卡兑换码 */
|
||||
code?: string
|
||||
/** 商品图片 */
|
||||
goodsImage?: string
|
||||
/** 商品图片列表 */
|
||||
goodsImages?: string[]
|
||||
/** 礼品卡面值 */
|
||||
faceValue?: string
|
||||
/** 商品原价 */
|
||||
originalPrice?: string
|
||||
/** 礼品卡类型:10-实物礼品卡 20-虚拟礼品卡 30-服务礼品卡 */
|
||||
type?: number
|
||||
/** 使用状态:0-可用 1-已使用 2-已过期 */
|
||||
@@ -30,12 +36,48 @@ export interface GiftCardProps {
|
||||
useLocation?: string
|
||||
/** 客服联系方式 */
|
||||
contactInfo?: string
|
||||
/** 商品信息 */
|
||||
goodsInfo?: {
|
||||
/** 商品品牌 */
|
||||
brand?: string
|
||||
/** 商品规格 */
|
||||
specification?: string
|
||||
/** 商品分类 */
|
||||
category?: string
|
||||
/** 库存数量 */
|
||||
stock?: number
|
||||
/** 商品评分 */
|
||||
rating?: number
|
||||
/** 评价数量 */
|
||||
reviewCount?: number
|
||||
/** 商品标签 */
|
||||
tags?: string[]
|
||||
/** 使用说明 */
|
||||
instructions?: string[]
|
||||
/** 注意事项 */
|
||||
notices?: string[]
|
||||
/** 适用门店 */
|
||||
applicableStores?: string[]
|
||||
}
|
||||
/** 优惠信息 */
|
||||
promotionInfo?: {
|
||||
/** 优惠类型 */
|
||||
type?: 'discount' | 'gift' | 'cashback'
|
||||
/** 优惠描述 */
|
||||
description?: string
|
||||
/** 优惠金额 */
|
||||
amount?: string
|
||||
/** 优惠有效期 */
|
||||
validUntil?: string
|
||||
}
|
||||
/** 是否显示兑换码 */
|
||||
showCode?: boolean
|
||||
/** 是否显示使用按钮 */
|
||||
showUseBtn?: boolean
|
||||
/** 是否显示详情按钮 */
|
||||
showDetailBtn?: boolean
|
||||
/** 是否显示商品详情 */
|
||||
showGoodsDetail?: boolean
|
||||
/** 卡片主题色 */
|
||||
theme?: 'gold' | 'silver' | 'bronze' | 'blue' | 'green' | 'purple'
|
||||
/** 使用按钮点击事件 */
|
||||
@@ -49,24 +91,34 @@ export interface GiftCardProps {
|
||||
const GiftCard: React.FC<GiftCardProps> = ({
|
||||
id,
|
||||
name,
|
||||
goodsName,
|
||||
description,
|
||||
code,
|
||||
goodsImage,
|
||||
goodsImages,
|
||||
faceValue,
|
||||
originalPrice,
|
||||
type = 10,
|
||||
useStatus = 0,
|
||||
expireTime,
|
||||
useTime,
|
||||
useLocation,
|
||||
contactInfo,
|
||||
goodsInfo,
|
||||
promotionInfo,
|
||||
showCode = false,
|
||||
showUseBtn = false,
|
||||
showDetailBtn = true,
|
||||
showGoodsDetail = true,
|
||||
theme = 'gold',
|
||||
onUse,
|
||||
onDetail,
|
||||
onClick
|
||||
}) => {
|
||||
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
||||
|
||||
// 获取显示名称,优先使用商品名称
|
||||
const displayName = goodsName || name
|
||||
// 获取礼品卡类型文本
|
||||
const getTypeText = () => {
|
||||
switch (type) {
|
||||
@@ -119,11 +171,11 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
// 格式化过期时间显示
|
||||
const formatExpireTime = () => {
|
||||
if (!expireTime) return ''
|
||||
|
||||
|
||||
const expire = dayjs(expireTime)
|
||||
const now = dayjs()
|
||||
const diffDays = expire.diff(now, 'day')
|
||||
|
||||
|
||||
if (diffDays < 0) {
|
||||
return '已过期'
|
||||
} else if (diffDays === 0) {
|
||||
@@ -142,10 +194,32 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
return code.replace(/(.{4})/g, '$1 ').trim()
|
||||
}
|
||||
|
||||
// 获取商品图片列表
|
||||
const getImageList = () => {
|
||||
if (goodsImages && goodsImages.length > 0) {
|
||||
return goodsImages
|
||||
}
|
||||
if (goodsImage) {
|
||||
return [goodsImage]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// 计算折扣百分比
|
||||
const getDiscountPercent = () => {
|
||||
if (!originalPrice || !faceValue) return null
|
||||
const original = parseFloat(originalPrice)
|
||||
const current = parseFloat(faceValue)
|
||||
if (original <= current) return null
|
||||
return Math.round(((original - current) / original) * 100)
|
||||
}
|
||||
|
||||
const statusInfo = getStatusInfo()
|
||||
const imageList = getImageList()
|
||||
const discountPercent = getDiscountPercent()
|
||||
|
||||
return (
|
||||
<View
|
||||
<View
|
||||
className={`gift-card ${getThemeClass()} ${useStatus !== 0 ? 'disabled' : ''}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
@@ -155,8 +229,7 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
<Gift size="24" className="text-white" />
|
||||
</View>
|
||||
<View className="gift-card-title">
|
||||
<Text className="title-text">{name}</Text>
|
||||
<Text className="type-text">{getTypeText()}</Text>
|
||||
<Text className="title-text">{getTypeText()}</Text>
|
||||
</View>
|
||||
<View className="gift-card-status">
|
||||
<Tag type={statusInfo.color}>{statusInfo.text}</Tag>
|
||||
@@ -166,31 +239,77 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
{/* 卡片主体 */}
|
||||
<View className="gift-card-body">
|
||||
<View className="gift-card-content">
|
||||
{/* 商品图片 */}
|
||||
{goodsImage && (
|
||||
<View className="gift-image">
|
||||
<Image
|
||||
src={goodsImage}
|
||||
className="w-16 h-16 rounded-lg object-cover"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
|
||||
<View className="gift-info">
|
||||
{/* 面值 */}
|
||||
{faceValue && (
|
||||
<View className="gift-value">
|
||||
<Text className="value-label">面值</Text>
|
||||
<Text className="value-amount">¥{faceValue}</Text>
|
||||
{/* 商品基本信息 */}
|
||||
<View className="goods-basic-info">
|
||||
{/* 商品名称 */}
|
||||
{goodsName && (
|
||||
<View className="brand-category">
|
||||
<Text className="brand-text">{goodsName}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 价格信息 */}
|
||||
<View className="price-info">
|
||||
{faceValue && (
|
||||
<View className="current-price">
|
||||
<Text className="price-symbol">¥</Text>
|
||||
<Text className="price-amount">{faceValue}</Text>
|
||||
</View>
|
||||
)}
|
||||
{originalPrice && originalPrice !== faceValue && (
|
||||
<Text className="original-price">原价¥{originalPrice}</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 评分和评价 */}
|
||||
{goodsInfo?.rating && (
|
||||
<View className="rating-info">
|
||||
<Rate
|
||||
value={goodsInfo.rating}
|
||||
readonly
|
||||
size="12"
|
||||
spacing="2"
|
||||
/>
|
||||
<Text className="rating-text">{goodsInfo.rating}</Text>
|
||||
{goodsInfo.reviewCount && (
|
||||
<Text className="review-count">({goodsInfo.reviewCount}条评价)</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 规格和库存 */}
|
||||
{showGoodsDetail && (goodsInfo?.specification || goodsInfo?.stock !== undefined) && (
|
||||
<View className="goods-specs">
|
||||
{goodsInfo.stock !== undefined && (
|
||||
<View className="spec-item">
|
||||
<Text className="spec-label">库存:</Text>
|
||||
<Text className={`spec-value ${goodsInfo.stock > 0 ? 'in-stock' : 'out-stock'}`}>
|
||||
{goodsInfo.stock > 0 ? `${goodsInfo.stock}件` : '缺货'}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 描述 */}
|
||||
{description && (
|
||||
<Text className="gift-description">{description}</Text>
|
||||
|
||||
{/* 优惠信息 */}
|
||||
{promotionInfo && (
|
||||
<View className="promotion-info">
|
||||
<View className="promotion-header">
|
||||
<Gift size="14" className="promotion-icon" />
|
||||
<Text className="promotion-title">优惠活动</Text>
|
||||
</View>
|
||||
<Text className="promotion-desc">{promotionInfo.description}</Text>
|
||||
{promotionInfo.validUntil && (
|
||||
<Text className="promotion-valid">
|
||||
有效期至:{dayjs(promotionInfo.validUntil).format('YYYY-MM-DD')}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
|
||||
{/* 兑换码 */}
|
||||
{code && (
|
||||
<View className="gift-code">
|
||||
@@ -201,6 +320,27 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 使用说明和注意事项 */}
|
||||
{showGoodsDetail && (goodsInfo?.instructions || goodsInfo?.notices || goodsInfo?.applicableStores) && (
|
||||
<View className="goods-instructions">
|
||||
{goodsInfo.applicableStores && goodsInfo.applicableStores.length > 0 && (
|
||||
<View className="instruction-section">
|
||||
<View className="section-header">
|
||||
<ShoppingCart size="14" className="section-icon" />
|
||||
<Text className="section-title">适用门店</Text>
|
||||
</View>
|
||||
<View className="store-list">
|
||||
{goodsInfo.applicableStores.map((store, index) => (
|
||||
<Tag key={index} size="small" plain className="store-tag">
|
||||
{store}
|
||||
</Tag>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 时间信息 */}
|
||||
<View className="gift-time-info">
|
||||
{useStatus === 1 && useTime && (
|
||||
@@ -209,14 +349,14 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
<Text className="time-text">使用时间:{dayjs(useTime).format('YYYY-MM-DD HH:mm')}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
|
||||
{useStatus === 0 && expireTime && (
|
||||
<View className="time-item">
|
||||
<Clock size="14" className="text-orange-500" />
|
||||
<Text className="time-text">{formatExpireTime()}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
|
||||
{useLocation && (
|
||||
<View className="time-item">
|
||||
<Location size="14" className="text-gray-400" />
|
||||
@@ -236,21 +376,8 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
|
||||
<View className="footer-actions">
|
||||
{showDetailBtn && (
|
||||
<Button
|
||||
size="small"
|
||||
fill="outline"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onDetail?.()
|
||||
}}
|
||||
>
|
||||
详情
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{showUseBtn && useStatus === 0 && (
|
||||
<Button
|
||||
size="small"
|
||||
|
||||
Reference in New Issue
Block a user