- 移除了不兼容的 CSS 类名,解决了 WXSS 编译错误 - 优化了礼品卡详细页面,添加了二维码弹窗功能 - 简化了礼品卡统计组件,提高了页面加载速度 - 修复了 SimpleQRCodeModal组件中的样式问题 - 优化了验证页面中的布局结构
372 lines
11 KiB
TypeScript
372 lines
11 KiB
TypeScript
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
|