Files
glt-taro/src/components/GiftCard.tsx
赵忠林 6d66b7abbf refactor(user/gift): 修复 CSS 兼容性问题并优化礼品卡功能
- 移除了不兼容的 CSS 类名,解决了 WXSS 编译错误
- 优化了礼品卡详细页面,添加了二维码弹窗功能
- 简化了礼品卡统计组件,提高了页面加载速度
- 修复了 SimpleQRCodeModal组件中的样式问题
- 优化了验证页面中的布局结构
2025-08-17 11:02:14 +08:00

372 lines
11 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 React from 'react'
import { View, Text } from '@tarojs/components'
import { Tag, Rate } from '@nutui/nutui-react-taro'
import { Gift, Clock, Location } from '@nutui/icons-react-taro'
import dayjs from 'dayjs'
import './GiftCard.scss'
export interface GiftCardProps {
/** 礼品卡ID */
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-失效 */
status?: number
/** 过期时间 */
expireTime?: string
/** 使用时间 */
useTime?: string
/** 使用地址 */
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'
/** 使用按钮点击事件 */
onUse?: () => void
/** 详情按钮点击事件 */
onDetail?: () => void
/** 卡片点击事件 */
onClick?: () => void
}
const GiftCard: React.FC<GiftCardProps> = ({
name,
goodsName,
code,
faceValue,
originalPrice,
type = 10,
status = 0,
expireTime,
useTime,
useLocation,
goodsInfo,
promotionInfo,
showCode = false,
showGoodsDetail = true,
theme = 'gold',
onClick
}) => {
// 获取显示名称,优先使用商品名称
// const displayName = goodsName || name
// 获取礼品卡类型文本
const getTypeText = () => {
switch (type) {
case 10: return '实物礼品卡'
case 20: return '虚拟礼品卡'
case 30: return '服务礼品卡'
default: return '礼品卡'
}
}
// 获取状态信息
const getStatusInfo = () => {
switch (status) {
case 0:
return {
text: '未使用',
color: 'success' as const,
bgColor: 'bg-green-100',
textColor: 'text-green-600'
}
case 1:
return {
text: '已使用',
color: 'warning' as const,
bgColor: 'bg-gray-100',
textColor: 'text-gray-600'
}
case 2:
return {
text: '失效',
color: 'danger' as const,
bgColor: 'bg-red-100',
textColor: 'text-red-600'
}
default:
return {
text: '未知',
color: 'default' as const,
bgColor: 'bg-gray-100',
textColor: 'text-gray-600'
}
}
}
// 获取主题样式类名
const getThemeClass = () => {
return `gift-card-${theme}`
}
// 格式化过期时间显示
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) {
return '今天过期'
} else if (diffDays <= 7) {
return `${diffDays}天后过期`
} else {
return expire.format('YYYY-MM-DD 过期')
}
}
// 格式化兑换码显示
const formatCode = () => {
if (!code) return ''
if (!showCode) return code.replace(/(.{4})/g, '$1 ').trim()
return code.replace(/(.{4})/g, '$1 ').trim()
}
const statusInfo = getStatusInfo()
return (
<View
className={`gift-card ${getThemeClass()} ${status !== 0 ? 'disabled' : ''}`}
onClick={onClick}
>
{/* 卡片头部 */}
<View className="gift-card-header">
<View className="gift-card-logo">
<Gift size="24" className="text-white" />
</View>
<View className="gift-card-title">
<Text className="title-text">{getTypeText()}</Text>
<Text className="type-text">{name}</Text>
</View>
<View className="gift-card-status">
<Tag type={statusInfo.color}>{statusInfo.text}</Tag>
</View>
</View>
{/* 卡片主体 */}
<View className="gift-card-body">
<View className="gift-card-content">
<View className="gift-info">
{/* 商品基本信息 */}
<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}
/>
<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>
)}
{/* 优惠信息 */}
{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">
<Text className="code-label"></Text>
<Text className="code-value">{formatCode()}</Text>
</View>
)}
</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">
<Text className="section-title"></Text>
</View>
<View className="store-list">
{goodsInfo.applicableStores.map((store, index) => (
<Tag key={index} plain className="store-tag">
{store}
</Tag>
))}
</View>
</View>
)}
</View>
)}
{/* 时间信息 */}
<View className="gift-time-info">
{status === 1 && useTime && (
<View className="time-item">
<Clock size="14" className="text-gray-400" />
<Text className="time-text">使{dayjs(useTime).format('YYYY-MM-DD HH:mm')}</Text>
</View>
)}
{status === 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" />
<Text className="time-text">使{useLocation}</Text>
</View>
)}
</View>
</View>
{/* 卡片底部操作 */}
{/*<View className="gift-card-footer">*/}
{/* <View className="footer-info">*/}
{/* {contactInfo && (*/}
{/* <View className="contact-info">*/}
{/* <Phone size="12" className="text-gray-400" />*/}
{/* <Text className="contact-text">{contactInfo}</Text>*/}
{/* </View>*/}
{/* )}*/}
{/* </View>*/}
{/* <View className="footer-actions">*/}
{/* {showUseBtn && status === 0 && (*/}
{/* <Button*/}
{/* size="small"*/}
{/* type="primary"*/}
{/* className={`use-btn ${getThemeClass()}`}*/}
{/* >*/}
{/* 立即使用*/}
{/* </Button>*/}
{/* )}*/}
{/* </View>*/}
{/*</View>*/}
{/* 状态遮罩 */}
{status !== 0 && (
<View className="gift-card-overlay">
<View className="overlay-badge">
{statusInfo.text}
</View>
</View>
)}
</View>
)
}
export default GiftCard