forked from gxwebsoft/mp-10550
feat(components): 新增 GiftCard礼品卡组件
- 新增 GiftCard 组件,支持多种类型礼品卡的展示和交互 - 组件包含商品信息、价格、折扣、使用指南等丰富功能- 优化图像展示,支持单
This commit is contained in:
184
src/components/GiftCard.md
Normal file
184
src/components/GiftCard.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# GiftCard 礼品卡组件
|
||||
|
||||
一个功能丰富、设计精美的礼品卡组件,支持多种类型的礼品卡展示和交互。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 🎨 视觉设计
|
||||
- **多主题支持**:金色、银色、铜色、蓝色、绿色、紫色六种主题
|
||||
- **响应式设计**:适配不同屏幕尺寸
|
||||
- **状态指示**:清晰的可用、已使用、已过期状态展示
|
||||
- **折扣标识**:自动计算并显示折扣百分比
|
||||
|
||||
### 🖼️ 图片展示
|
||||
- **单图模式**:支持单张商品图片展示
|
||||
- **轮播模式**:支持多张图片轮播展示
|
||||
- **自适应尺寸**:图片自动适配容器大小
|
||||
|
||||
### 💰 价格信息
|
||||
- **面值显示**:突出显示礼品卡面值
|
||||
- **原价对比**:显示原价和折扣信息
|
||||
- **优惠活动**:展示当前优惠活动信息
|
||||
|
||||
### ⭐ 商品详情
|
||||
- **品牌分类**:显示商品品牌和分类信息
|
||||
- **评分评价**:展示用户评分和评价数量
|
||||
- **规格库存**:显示商品规格和库存状态
|
||||
- **商品标签**:支持多个商品特色标签
|
||||
|
||||
### 📋 使用指南
|
||||
- **使用说明**:详细的使用步骤说明
|
||||
- **注意事项**:重要的使用注意事项
|
||||
- **适用门店**:显示可使用的门店列表
|
||||
|
||||
### 🔧 交互功能
|
||||
- **兑换码展示**:支持兑换码的显示和隐藏
|
||||
- **操作按钮**:使用、详情等操作按钮
|
||||
- **点击事件**:支持整卡点击事件
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基础用法
|
||||
|
||||
```tsx
|
||||
import GiftCard from '@/components/GiftCard'
|
||||
|
||||
const MyComponent = () => {
|
||||
return (
|
||||
<GiftCard
|
||||
id={1}
|
||||
name="星巴克咖啡礼品卡"
|
||||
description="享受醇香咖啡时光"
|
||||
goodsImage="https://example.com/starbucks.jpg"
|
||||
faceValue="100"
|
||||
type={20}
|
||||
useStatus={0}
|
||||
theme="green"
|
||||
showUseBtn={true}
|
||||
onUse={() => console.log('使用礼品卡')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 完整功能展示
|
||||
|
||||
```tsx
|
||||
import GiftCard from '@/components/GiftCard'
|
||||
|
||||
const FullFeatureCard = () => {
|
||||
const cardData = {
|
||||
id: 1,
|
||||
name: '星巴克咖啡礼品卡',
|
||||
description: '享受醇香咖啡时光,适用于全国星巴克门店',
|
||||
code: 'SB2024001234567890',
|
||||
goodsImages: [
|
||||
'https://example.com/image1.jpg',
|
||||
'https://example.com/image2.jpg'
|
||||
],
|
||||
faceValue: '100',
|
||||
originalPrice: '120',
|
||||
type: 20,
|
||||
useStatus: 0,
|
||||
expireTime: '2024-12-31 23:59:59',
|
||||
goodsInfo: {
|
||||
brand: '星巴克',
|
||||
category: '餐饮美食',
|
||||
rating: 4.8,
|
||||
reviewCount: 1256,
|
||||
tags: ['热门', '全国通用'],
|
||||
instructions: [
|
||||
'出示兑换码至门店收银台即可使用',
|
||||
'可用于购买任意饮品和食品'
|
||||
],
|
||||
notices: [
|
||||
'礼品卡一经售出,不可退换',
|
||||
'请妥善保管兑换码'
|
||||
],
|
||||
applicableStores: ['全国星巴克门店', '机场店']
|
||||
},
|
||||
promotionInfo: {
|
||||
type: 'discount',
|
||||
description: '限时优惠:满100减20',
|
||||
validUntil: '2024-09-30 23:59:59'
|
||||
},
|
||||
showCode: true,
|
||||
showUseBtn: true,
|
||||
showGoodsDetail: true,
|
||||
theme: 'green'
|
||||
}
|
||||
|
||||
return (
|
||||
<GiftCard
|
||||
{...cardData}
|
||||
onUse={() => console.log('使用')}
|
||||
onDetail={() => console.log('详情')}
|
||||
onClick={() => console.log('点击')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 属性说明
|
||||
|
||||
### 基础属性
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| id | number | - | 礼品卡ID |
|
||||
| name | string | - | 礼品卡名称 |
|
||||
| description | string | - | 礼品卡描述 |
|
||||
| faceValue | string | - | 礼品卡面值 |
|
||||
| type | number | 10 | 类型:10-实物 20-虚拟 30-服务 |
|
||||
| useStatus | number | 0 | 状态:0-可用 1-已使用 2-已过期 |
|
||||
| theme | string | 'gold' | 主题色 |
|
||||
|
||||
### 商品信息
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| goodsInfo.brand | string | 商品品牌 |
|
||||
| goodsInfo.category | string | 商品分类 |
|
||||
| goodsInfo.rating | number | 商品评分 |
|
||||
| goodsInfo.reviewCount | number | 评价数量 |
|
||||
| goodsInfo.tags | string[] | 商品标签 |
|
||||
| goodsInfo.instructions | string[] | 使用说明 |
|
||||
| goodsInfo.notices | string[] | 注意事项 |
|
||||
|
||||
### 事件回调
|
||||
| 事件 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| onUse | () => void | 使用按钮点击 |
|
||||
| onDetail | () => void | 详情按钮点击 |
|
||||
| onClick | () => void | 卡片点击 |
|
||||
|
||||
## 主题配置
|
||||
|
||||
组件支持6种预设主题:
|
||||
|
||||
- `gold` - 金色主题(默认)
|
||||
- `silver` - 银色主题
|
||||
- `bronze` - 铜色主题
|
||||
- `blue` - 蓝色主题
|
||||
- `green` - 绿色主题
|
||||
- `purple` - 紫色主题
|
||||
|
||||
## 样式定制
|
||||
|
||||
可以通过覆盖CSS类名来自定义样式:
|
||||
|
||||
```scss
|
||||
.gift-card {
|
||||
// 自定义卡片样式
|
||||
}
|
||||
|
||||
.gift-card-gold {
|
||||
// 自定义金色主题
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保传入的图片URL有效且可访问
|
||||
2. 价格相关字段建议使用字符串类型,避免精度问题
|
||||
3. 时间字段请使用标准的日期时间格式
|
||||
4. 商品标签数量建议控制在5个以内,避免布局混乱
|
||||
5. 使用说明和注意事项条目建议简洁明了
|
||||
@@ -108,10 +108,14 @@
|
||||
|
||||
.title-text {
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 2px;
|
||||
// 商品名称可能较长,需要处理溢出
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.type-text {
|
||||
@@ -138,29 +142,151 @@
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.gift-image {
|
||||
.gift-image-container {
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
|
||||
.gift-image {
|
||||
position: relative;
|
||||
|
||||
.gift-image-single {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
border-radius: 12px;
|
||||
object-fit: cover;
|
||||
background-color: #f5f5f5;
|
||||
// 添加加载状态和错误处理
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #f5f5f5;
|
||||
border-radius: 12px;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gift-image-swiper {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
.swiper-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.swiper-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discount-badge {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
background: #ff4757;
|
||||
border-radius: 8px;
|
||||
padding: 2px 6px;
|
||||
z-index: 2;
|
||||
|
||||
.discount-text {
|
||||
font-size: 10px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gift-info {
|
||||
flex: 1;
|
||||
|
||||
.gift-value {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 8px;
|
||||
.goods-basic-info {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.value-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-right: 8px;
|
||||
.brand-category {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.brand-text {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.category-text {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.value-amount {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
.price-info {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.current-price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-right: 12px;
|
||||
|
||||
.price-symbol {
|
||||
font-size: 16px;
|
||||
color: #ff4757;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.price-amount {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #ff4757;
|
||||
}
|
||||
}
|
||||
|
||||
.original-price {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
.rating-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.rating-text {
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
margin-left: 4px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.review-count {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.nut-tag {
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +297,72 @@
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.goods-specs {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.spec-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.spec-label {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.spec-value {
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
|
||||
&.in-stock {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
&.out-stock {
|
||||
color: #ff4757;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.promotion-info {
|
||||
background: linear-gradient(135deg, #fff5f5 0%, #ffe8e8 100%);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
border-left: 3px solid #ff4757;
|
||||
|
||||
.promotion-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
|
||||
.promotion-icon {
|
||||
color: #ff4757;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.promotion-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #ff4757;
|
||||
}
|
||||
}
|
||||
|
||||
.promotion-desc {
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.promotion-valid {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.gift-code {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
@@ -179,13 +371,11 @@
|
||||
|
||||
.code-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.code-value {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-family: 'Courier New', monospace;
|
||||
@@ -195,6 +385,69 @@
|
||||
}
|
||||
}
|
||||
|
||||
.goods-instructions {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.instruction-section {
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.section-icon {
|
||||
color: #4a90e2;
|
||||
margin-right: 6px;
|
||||
|
||||
&.warning {
|
||||
color: #ff9500;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.instruction-list {
|
||||
.instruction-item {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 4px;
|
||||
padding-left: 8px;
|
||||
|
||||
&.notice {
|
||||
color: #ff9500;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.store-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
|
||||
.store-tag {
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gift-time-info {
|
||||
.time-item {
|
||||
display: flex;
|
||||
@@ -290,10 +543,42 @@
|
||||
padding: 16px;
|
||||
|
||||
.gift-card-content {
|
||||
.gift-image-container {
|
||||
margin-right: 12px;
|
||||
|
||||
.gift-image .gift-image-single,
|
||||
.gift-image-swiper {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
.gift-info {
|
||||
.gift-value {
|
||||
.value-amount {
|
||||
font-size: 20px;
|
||||
.goods-basic-info {
|
||||
.price-info {
|
||||
.current-price {
|
||||
.price-amount {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-tags {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.promotion-info {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-instructions {
|
||||
.instruction-section {
|
||||
.instruction-list {
|
||||
.instruction-item {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,6 +587,35 @@
|
||||
|
||||
.gift-card-footer {
|
||||
padding: 0 16px 16px;
|
||||
|
||||
.footer-actions {
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 小屏幕优化
|
||||
@media (max-width: 480px) {
|
||||
.gift-card {
|
||||
.gift-card-content {
|
||||
flex-direction: column;
|
||||
|
||||
.gift-image-container {
|
||||
margin-right: 0;
|
||||
margin-bottom: 12px;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-instructions {
|
||||
.instruction-section {
|
||||
.store-list {
|
||||
.store-tag {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
140
src/components/GiftCardExample.tsx
Normal file
140
src/components/GiftCardExample.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import React from 'react'
|
||||
import { View } from '@tarojs/components'
|
||||
import GiftCard from './GiftCard'
|
||||
|
||||
const GiftCardExample: React.FC = () => {
|
||||
// 示例数据
|
||||
const giftCardData = {
|
||||
id: 1,
|
||||
name: '星巴克咖啡礼品卡',
|
||||
description: '享受醇香咖啡时光,适用于全国星巴克门店',
|
||||
code: 'SB2024001234567890',
|
||||
goodsImages: [
|
||||
'https://example.com/starbucks-card-1.jpg',
|
||||
'https://example.com/starbucks-card-2.jpg',
|
||||
'https://example.com/starbucks-card-3.jpg'
|
||||
],
|
||||
faceValue: '100',
|
||||
originalPrice: '120',
|
||||
type: 20, // 虚拟礼品卡
|
||||
useStatus: 0, // 可用
|
||||
expireTime: '2024-12-31 23:59:59',
|
||||
contactInfo: '400-800-8888',
|
||||
goodsInfo: {
|
||||
brand: '星巴克',
|
||||
specification: '电子礼品卡',
|
||||
category: '餐饮美食',
|
||||
stock: 999,
|
||||
rating: 4.8,
|
||||
reviewCount: 1256,
|
||||
tags: ['热门', '全国通用', '无需预约', '即买即用'],
|
||||
instructions: [
|
||||
'出示兑换码至门店收银台即可使用',
|
||||
'可用于购买任意饮品和食品',
|
||||
'不可兑换现金,不设找零',
|
||||
'单次可使用多张礼品卡'
|
||||
],
|
||||
notices: [
|
||||
'礼品卡一经售出,不可退换',
|
||||
'请妥善保管兑换码,遗失不补',
|
||||
'部分特殊商品可能不适用',
|
||||
'具体使用规则以门店公告为准'
|
||||
],
|
||||
applicableStores: [
|
||||
'全国星巴克门店',
|
||||
'机场店',
|
||||
'高铁站店',
|
||||
'商场店'
|
||||
]
|
||||
},
|
||||
promotionInfo: {
|
||||
type: 'discount' as const,
|
||||
description: '限时优惠:满100减20,买2张送1张咖啡券',
|
||||
amount: '20',
|
||||
validUntil: '2024-09-30 23:59:59'
|
||||
},
|
||||
showCode: true,
|
||||
showUseBtn: true,
|
||||
showDetailBtn: true,
|
||||
showGoodsDetail: true,
|
||||
theme: 'green' as const
|
||||
}
|
||||
|
||||
const handleUse = () => {
|
||||
console.log('使用礼品卡')
|
||||
}
|
||||
|
||||
const handleDetail = () => {
|
||||
console.log('查看详情')
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
console.log('点击礼品卡')
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="gift-card-example">
|
||||
<GiftCard
|
||||
{...giftCardData}
|
||||
onUse={handleUse}
|
||||
onDetail={handleDetail}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
|
||||
{/* 简化版本示例 */}
|
||||
<GiftCard
|
||||
id={2}
|
||||
name="麦当劳优惠券"
|
||||
description="美味汉堡套餐,限时优惠"
|
||||
goodsImage="https://example.com/mcd-card.jpg"
|
||||
faceValue="50"
|
||||
originalPrice="60"
|
||||
type={20}
|
||||
useStatus={0}
|
||||
expireTime="2024-10-31 23:59:59"
|
||||
goodsInfo={{
|
||||
brand: '麦当劳',
|
||||
category: '快餐',
|
||||
rating: 4.5,
|
||||
reviewCount: 892,
|
||||
tags: ['快餐', '全国通用']
|
||||
}}
|
||||
showCode={false}
|
||||
showUseBtn={true}
|
||||
showDetailBtn={true}
|
||||
showGoodsDetail={false}
|
||||
theme="blue"
|
||||
onUse={handleUse}
|
||||
onDetail={handleDetail}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
|
||||
{/* 已使用状态示例 */}
|
||||
<GiftCard
|
||||
id={3}
|
||||
name="海底捞火锅券"
|
||||
description="享受正宗川味火锅"
|
||||
goodsImage="https://example.com/haidilao-card.jpg"
|
||||
faceValue="200"
|
||||
type={30}
|
||||
useStatus={1}
|
||||
useTime="2024-08-15 19:30:00"
|
||||
useLocation="海底捞王府井店"
|
||||
goodsInfo={{
|
||||
brand: '海底捞',
|
||||
category: '火锅',
|
||||
rating: 4.9,
|
||||
reviewCount: 2341
|
||||
}}
|
||||
showCode={false}
|
||||
showUseBtn={false}
|
||||
showDetailBtn={true}
|
||||
theme="gold"
|
||||
onDetail={handleDetail}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default GiftCardExample
|
||||
@@ -67,13 +67,15 @@ const GiftCardList: React.FC<GiftCardListProps> = ({
|
||||
)
|
||||
) : (
|
||||
gifts.map((gift, index) => (
|
||||
<GiftCard
|
||||
key={gift.id || index}
|
||||
{...gift}
|
||||
onClick={() => handleGiftClick(gift, index)}
|
||||
onUse={() => handleGiftUse(gift, index)}
|
||||
onDetail={() => handleGiftDetail(gift, index)}
|
||||
/>
|
||||
<>
|
||||
<GiftCard
|
||||
key={gift.id || index}
|
||||
{...gift}
|
||||
onClick={() => handleGiftClick(gift, index)}
|
||||
onUse={() => handleGiftUse(gift, index)}
|
||||
onDetail={() => handleGiftDetail(gift, index)}
|
||||
/>
|
||||
</>
|
||||
))
|
||||
)}
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user