Browse Source
- 修改礼品卡颜色主题,使用渐变色提升视觉效果 - 添加礼品卡核销功能,包括生成和验证核销码 -优化礼品卡组件,增加状态显示和使用说明 - 新增礼品卡颜色测试页面,用于验证颜色master
22 changed files with 2821 additions and 116 deletions
@ -0,0 +1,333 @@ |
|||
import React, { useState, useEffect } from 'react' |
|||
import { View, Text } from '@tarojs/components' |
|||
import { Button, Popup, Tag } from '@nutui/nutui-react-taro' |
|||
import { Close, Copy, Share, Refresh, QrCode } from '@nutui/icons-react-taro' |
|||
import Taro from '@tarojs/taro' |
|||
import dayjs from 'dayjs' |
|||
import { generateVerificationCode } from '@/api/shop/shopGift' |
|||
|
|||
export interface GiftCardQRCodeProps { |
|||
/** 是否显示弹窗 */ |
|||
visible: boolean |
|||
/** 关闭弹窗回调 */ |
|||
onClose: () => void |
|||
/** 礼品卡信息 */ |
|||
giftCard: { |
|||
id: number |
|||
name?: string |
|||
goodsName?: string |
|||
code?: string |
|||
faceValue?: string |
|||
type?: number |
|||
status?: number |
|||
expireTime?: string |
|||
contactInfo?: string |
|||
} |
|||
} |
|||
|
|||
const GiftCardQRCode: React.FC<GiftCardQRCodeProps> = ({ |
|||
visible, |
|||
onClose, |
|||
giftCard |
|||
}) => { |
|||
const [loading, setLoading] = useState(false) |
|||
const [verificationCode, setVerificationCode] = useState<string>('') |
|||
const [qrData, setQrData] = useState<string>('') |
|||
|
|||
// 生成核销码(6位数字)
|
|||
const generateVerificationCode = () => { |
|||
return Math.random().toString().slice(2, 8) |
|||
} |
|||
|
|||
// 生成二维码数据
|
|||
const generateQRData = () => { |
|||
const code = generateVerificationCode() |
|||
setVerificationCode(code) |
|||
|
|||
const data = { |
|||
type: 'gift_card_verification', |
|||
giftId: giftCard.id, |
|||
giftCode: giftCard.code, |
|||
verificationCode: code, |
|||
faceValue: giftCard.faceValue, |
|||
timestamp: Date.now(), |
|||
expireTime: giftCard.expireTime |
|||
} |
|||
|
|||
const jsonData = JSON.stringify(data) |
|||
setQrData(jsonData) |
|||
return jsonData |
|||
} |
|||
|
|||
// 生成二维码(调用后端API)
|
|||
const generateQRCode = async () => { |
|||
try { |
|||
setLoading(true) |
|||
|
|||
// 调用后端API生成核销码
|
|||
const result = await generateVerificationCode(giftCard.id) |
|||
if (result) { |
|||
setVerificationCode(result.verificationCode) |
|||
|
|||
// 生成二维码数据
|
|||
const data = { |
|||
type: 'gift_card_verification', |
|||
giftId: giftCard.id, |
|||
giftCode: giftCard.code, |
|||
verificationCode: result.verificationCode, |
|||
faceValue: giftCard.faceValue, |
|||
timestamp: Date.now(), |
|||
expireTime: giftCard.expireTime, |
|||
codeExpireTime: result.expireTime |
|||
} |
|||
|
|||
setQrData(JSON.stringify(data)) |
|||
console.log('二维码数据:', data) |
|||
} |
|||
} catch (error) { |
|||
console.error('生成二维码失败:', error) |
|||
Taro.showToast({ |
|||
title: '生成核销码失败', |
|||
icon: 'error' |
|||
}) |
|||
} finally { |
|||
setLoading(false) |
|||
} |
|||
} |
|||
|
|||
// 复制核销码
|
|||
const copyVerificationCode = () => { |
|||
Taro.setClipboardData({ |
|||
data: verificationCode, |
|||
success: () => { |
|||
Taro.showToast({ |
|||
title: '核销码已复制', |
|||
icon: 'success' |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// 复制礼品卡码
|
|||
const copyGiftCode = () => { |
|||
if (giftCard.code) { |
|||
Taro.setClipboardData({ |
|||
data: giftCard.code, |
|||
success: () => { |
|||
Taro.showToast({ |
|||
title: '兑换码已复制', |
|||
icon: 'success' |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
// 分享二维码
|
|||
const shareQRCode = () => { |
|||
// 这里可以实现分享功能
|
|||
Taro.showToast({ |
|||
title: '分享功能开发中', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
|
|||
// 刷新二维码
|
|||
const refreshQRCode = () => { |
|||
generateQRCode() |
|||
} |
|||
|
|||
// 获取状态文本
|
|||
const getStatusText = (status?: number) => { |
|||
switch (status) { |
|||
case 0: return { text: '未使用', color: 'success' } |
|||
case 1: return { text: '已使用', color: 'warning' } |
|||
case 2: return { text: '失效', color: 'danger' } |
|||
default: return { text: '未知', color: 'default' } |
|||
} |
|||
} |
|||
|
|||
// 获取类型文本
|
|||
const getTypeText = (type?: number) => { |
|||
switch (type) { |
|||
case 10: return '实物礼品卡' |
|||
case 20: return '虚拟礼品卡' |
|||
case 30: return '服务礼品卡' |
|||
default: return '礼品卡' |
|||
} |
|||
} |
|||
|
|||
// 弹窗打开时生成二维码
|
|||
useEffect(() => { |
|||
if (visible && giftCard.id) { |
|||
generateQRCode() |
|||
} |
|||
}, [visible, giftCard.id]) |
|||
|
|||
const statusInfo = getStatusText(giftCard.status) |
|||
|
|||
return ( |
|||
<Popup |
|||
visible={visible} |
|||
position="center" |
|||
closeable |
|||
closeIcon={<Close />} |
|||
onClose={onClose} |
|||
style={{ width: '90%', maxWidth: '400px' }} |
|||
> |
|||
<View className="p-6"> |
|||
{/* 标题 */} |
|||
<View className="text-center mb-4"> |
|||
<Text className="text-lg font-bold">礼品卡核销</Text> |
|||
<Text className="text-sm text-gray-500 block mt-1"> |
|||
请向门店工作人员出示此二维码 |
|||
</Text> |
|||
</View> |
|||
|
|||
{/* 礼品卡信息 */} |
|||
<View className="bg-gray-50 rounded-lg p-4 mb-4"> |
|||
<View className="flex justify-between items-start mb-2"> |
|||
<Text className="font-medium text-base"> |
|||
{giftCard.goodsName || giftCard.name} |
|||
</Text> |
|||
<Tag type={statusInfo.color} size="small"> |
|||
{statusInfo.text} |
|||
</Tag> |
|||
</View> |
|||
|
|||
<View className="flex justify-between items-center mb-2"> |
|||
<Text className="text-sm text-gray-600">面值</Text> |
|||
<Text className="text-lg font-bold text-red-500"> |
|||
¥{giftCard.faceValue} |
|||
</Text> |
|||
</View> |
|||
|
|||
<View className="flex justify-between items-center mb-2"> |
|||
<Text className="text-sm text-gray-600">类型</Text> |
|||
<Text className="text-sm">{getTypeText(giftCard.type)}</Text> |
|||
</View> |
|||
|
|||
{giftCard.expireTime && ( |
|||
<View className="flex justify-between items-center"> |
|||
<Text className="text-sm text-gray-600">有效期至</Text> |
|||
<Text className="text-sm text-orange-600"> |
|||
{dayjs(giftCard.expireTime).format('YYYY-MM-DD')} |
|||
</Text> |
|||
</View> |
|||
)} |
|||
</View> |
|||
|
|||
{/* 二维码区域 */} |
|||
<View className="text-center mb-4"> |
|||
{loading ? ( |
|||
<View className="w-48 h-48 mx-auto bg-gray-100 rounded-lg flex items-center justify-center"> |
|||
<Text className="text-gray-500">生成中...</Text> |
|||
</View> |
|||
) : verificationCode ? ( |
|||
<View className="relative"> |
|||
{/* 模拟二维码显示区域 */} |
|||
<View className="w-48 h-48 mx-auto bg-white border-2 border-gray-300 rounded-lg flex flex-col items-center justify-center"> |
|||
<QrCode size="80" className="text-gray-800 mb-2" /> |
|||
<Text className="text-xs text-gray-600">核销二维码</Text> |
|||
<Text className="text-xs text-gray-500 mt-1"> |
|||
ID: {giftCard.id} |
|||
</Text> |
|||
</View> |
|||
<Button |
|||
size="small" |
|||
fill="outline" |
|||
icon={<Refresh />} |
|||
className="absolute top-2 right-2" |
|||
onClick={refreshQRCode} |
|||
> |
|||
刷新 |
|||
</Button> |
|||
</View> |
|||
) : ( |
|||
<View className="w-48 h-48 mx-auto bg-gray-100 rounded-lg flex items-center justify-center"> |
|||
<Text className="text-gray-500">生成失败</Text> |
|||
</View> |
|||
)} |
|||
</View> |
|||
|
|||
{/* 核销码 */} |
|||
{verificationCode && ( |
|||
<View className="bg-blue-50 rounded-lg p-3 mb-4"> |
|||
<View className="flex justify-between items-center"> |
|||
<View> |
|||
<Text className="text-sm text-blue-600 mb-1">核销码</Text> |
|||
<Text className="text-xl font-mono font-bold text-blue-800"> |
|||
{verificationCode} |
|||
</Text> |
|||
</View> |
|||
<Button |
|||
size="small" |
|||
fill="outline" |
|||
icon={<Copy />} |
|||
onClick={copyVerificationCode} |
|||
> |
|||
复制 |
|||
</Button> |
|||
</View> |
|||
</View> |
|||
)} |
|||
|
|||
{/* 兑换码 */} |
|||
{giftCard.code && ( |
|||
<View className="bg-green-50 rounded-lg p-3 mb-4"> |
|||
<View className="flex justify-between items-center"> |
|||
<View> |
|||
<Text className="text-sm text-green-600 mb-1">兑换码</Text> |
|||
<Text className="text-base font-mono text-green-800"> |
|||
{giftCard.code} |
|||
</Text> |
|||
</View> |
|||
<Button |
|||
size="small" |
|||
fill="outline" |
|||
icon={<Copy />} |
|||
onClick={copyGiftCode} |
|||
> |
|||
复制 |
|||
</Button> |
|||
</View> |
|||
</View> |
|||
)} |
|||
|
|||
{/* 操作按钮 */} |
|||
<View className="flex gap-3"> |
|||
<Button |
|||
size="large" |
|||
fill="outline" |
|||
icon={<Share />} |
|||
onClick={shareQRCode} |
|||
className="flex-1" |
|||
> |
|||
分享 |
|||
</Button> |
|||
<Button |
|||
size="large" |
|||
type="primary" |
|||
onClick={onClose} |
|||
className="flex-1" |
|||
> |
|||
完成 |
|||
</Button> |
|||
</View> |
|||
|
|||
{/* 使用说明 */} |
|||
<View className="mt-4 p-3 bg-yellow-50 rounded-lg"> |
|||
<Text className="text-sm text-yellow-800 font-medium mb-2">使用说明:</Text> |
|||
<View className="space-y-1"> |
|||
<Text className="text-xs text-yellow-700">• 请向门店工作人员出示此二维码或核销码</Text> |
|||
<Text className="text-xs text-yellow-700">• 工作人员扫码后即可完成核销</Text> |
|||
<Text className="text-xs text-yellow-700">• 每次使用会生成新的核销码,确保安全</Text> |
|||
<Text className="text-xs text-yellow-700">• 如有问题请联系客服:{giftCard.contactInfo || '400-800-8888'}</Text> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
</Popup> |
|||
) |
|||
} |
|||
|
|||
export default GiftCardQRCode |
@ -0,0 +1,228 @@ |
|||
import React, { useState } from 'react' |
|||
import { View, Text } from '@tarojs/components' |
|||
import { Button } from '@nutui/nutui-react-taro' |
|||
import { getUserGifts } from '@/api/shop/shopGift' |
|||
import Taro from '@tarojs/taro' |
|||
|
|||
const ApiTest: React.FC = () => { |
|||
const [loading, setLoading] = useState(false) |
|||
const [results, setResults] = useState<any[]>([]) |
|||
const [logs, setLogs] = useState<string[]>([]) |
|||
|
|||
const addLog = (message: string) => { |
|||
const timestamp = new Date().toLocaleTimeString() |
|||
setLogs(prev => [`[${timestamp}] ${message}`, ...prev]) |
|||
} |
|||
|
|||
const testApiCall = async (status: number, statusName: string) => { |
|||
setLoading(true) |
|||
addLog(`开始测试 status=${status} (${statusName})`) |
|||
|
|||
try { |
|||
const params = { |
|||
page: 1, |
|||
limit: 10, |
|||
userId: Taro.getStorageSync('UserId'), |
|||
status: status |
|||
} |
|||
|
|||
addLog(`API参数: ${JSON.stringify(params)}`) |
|||
|
|||
const res = await getUserGifts(params) |
|||
|
|||
addLog(`API返回: ${res?.list?.length || 0} 条数据`) |
|||
|
|||
if (res?.list && res.list.length > 0) { |
|||
const statusCounts = res.list.reduce((acc: any, item: any) => { |
|||
const itemStatus = item.status |
|||
acc[itemStatus] = (acc[itemStatus] || 0) + 1 |
|||
return acc |
|||
}, {}) |
|||
|
|||
addLog(`返回数据状态分布: ${JSON.stringify(statusCounts)}`) |
|||
|
|||
// 检查是否所有返回的数据都是期望的状态
|
|||
const allCorrectStatus = res.list.every((item: any) => item.status === status) |
|||
if (allCorrectStatus) { |
|||
addLog(`✅ 状态筛选正确: 所有数据都是 status=${status}`) |
|||
} else { |
|||
addLog(`❌ 状态筛选错误: 返回了其他状态的数据`) |
|||
} |
|||
} else { |
|||
addLog(`ℹ️ 无数据返回`) |
|||
} |
|||
|
|||
setResults(prev => [...prev, { |
|||
status, |
|||
statusName, |
|||
count: res?.list?.length || 0, |
|||
data: res?.list || [], |
|||
success: true |
|||
}]) |
|||
|
|||
} catch (error) { |
|||
addLog(`❌ API调用失败: ${error}`) |
|||
setResults(prev => [...prev, { |
|||
status, |
|||
statusName, |
|||
count: 0, |
|||
data: [], |
|||
success: false, |
|||
error: String(error) |
|||
}]) |
|||
} finally { |
|||
setLoading(false) |
|||
} |
|||
} |
|||
|
|||
const clearResults = () => { |
|||
setResults([]) |
|||
setLogs([]) |
|||
} |
|||
|
|||
const testAllStatus = async () => { |
|||
clearResults() |
|||
await testApiCall(0, '未使用') |
|||
await new Promise(resolve => setTimeout(resolve, 1000)) // 延迟1秒
|
|||
await testApiCall(1, '已使用') |
|||
await new Promise(resolve => setTimeout(resolve, 1000)) // 延迟1秒
|
|||
await testApiCall(2, '失效') |
|||
} |
|||
|
|||
return ( |
|||
<View className="bg-gray-50 min-h-screen"> |
|||
{/* 页面标题 */} |
|||
<View className="bg-white px-4 py-3 border-b border-gray-100"> |
|||
<Text className="text-lg font-bold text-center"> |
|||
API 状态参数测试 |
|||
</Text> |
|||
<Text className="text-sm text-gray-600 text-center mt-1"> |
|||
测试 getUserGifts API 的 status 参数传递 |
|||
</Text> |
|||
</View> |
|||
|
|||
{/* 测试按钮 */} |
|||
<View className="bg-white mx-4 mt-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-3">测试操作:</Text> |
|||
<View className="flex flex-wrap gap-2 mb-3"> |
|||
<Button |
|||
size="small" |
|||
type="primary" |
|||
loading={loading} |
|||
onClick={() => testApiCall(0, '未使用')} |
|||
> |
|||
测试 status=0 |
|||
</Button> |
|||
<Button |
|||
size="small" |
|||
type="primary" |
|||
loading={loading} |
|||
onClick={() => testApiCall(1, '已使用')} |
|||
> |
|||
测试 status=1 |
|||
</Button> |
|||
<Button |
|||
size="small" |
|||
type="primary" |
|||
loading={loading} |
|||
onClick={() => testApiCall(2, '失效')} |
|||
> |
|||
测试 status=2 |
|||
</Button> |
|||
</View> |
|||
<View className="flex gap-2"> |
|||
<Button |
|||
size="small" |
|||
fill="outline" |
|||
loading={loading} |
|||
onClick={testAllStatus} |
|||
> |
|||
测试所有状态 |
|||
</Button> |
|||
<Button |
|||
size="small" |
|||
fill="outline" |
|||
onClick={clearResults} |
|||
> |
|||
清空结果 |
|||
</Button> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 测试结果 */} |
|||
{results.length > 0 && ( |
|||
<View className="bg-white mx-4 mt-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-3">测试结果:</Text> |
|||
{results.map((result, index) => ( |
|||
<View key={index} className="mb-3 p-3 bg-gray-50 rounded"> |
|||
<View className="flex justify-between items-center mb-2"> |
|||
<Text className="font-medium"> |
|||
status={result.status} ({result.statusName}) |
|||
</Text> |
|||
<Text className={`text-sm ${result.success ? 'text-green-600' : 'text-red-600'}`}> |
|||
{result.success ? '成功' : '失败'} |
|||
</Text> |
|||
</View> |
|||
<Text className="text-sm text-gray-600"> |
|||
返回数据: {result.count} 条 |
|||
</Text> |
|||
{result.error && ( |
|||
<Text className="text-sm text-red-600 mt-1"> |
|||
错误: {result.error} |
|||
</Text> |
|||
)} |
|||
{result.data.length > 0 && ( |
|||
<View className="mt-2"> |
|||
<Text className="text-xs text-gray-500">示例数据:</Text> |
|||
{result.data.slice(0, 2).map((item: any, idx: number) => ( |
|||
<Text key={idx} className="text-xs text-gray-600 block"> |
|||
ID:{item.id}, status:{item.status}, name:{item.goodsName || item.name} |
|||
</Text> |
|||
))} |
|||
</View> |
|||
)} |
|||
</View> |
|||
))} |
|||
</View> |
|||
)} |
|||
|
|||
{/* 调试日志 */} |
|||
<View className="bg-white mx-4 mt-4 mb-4 p-4 rounded-lg"> |
|||
<View className="flex justify-between items-center mb-3"> |
|||
<Text className="font-bold">调试日志:</Text> |
|||
<Button |
|||
size="small" |
|||
fill="outline" |
|||
onClick={() => setLogs([])} |
|||
> |
|||
清空日志 |
|||
</Button> |
|||
</View> |
|||
<View className="max-h-60 overflow-y-auto"> |
|||
{logs.length > 0 ? ( |
|||
logs.map((log, index) => ( |
|||
<Text key={index} className="text-xs text-gray-600 block mb-1"> |
|||
{log} |
|||
</Text> |
|||
)) |
|||
) : ( |
|||
<Text className="text-xs text-gray-400">暂无日志</Text> |
|||
)} |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 使用说明 */} |
|||
<View className="bg-yellow-50 mx-4 mb-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2 text-yellow-800">使用说明:</Text> |
|||
<View className="space-y-1"> |
|||
<Text className="text-sm text-yellow-700">1. 点击测试按钮调用 getUserGifts API</Text> |
|||
<Text className="text-sm text-yellow-700">2. 检查返回的数据状态是否与请求参数一致</Text> |
|||
<Text className="text-sm text-yellow-700">3. 查看调试日志了解详细的API调用过程</Text> |
|||
<Text className="text-sm text-yellow-700">4. 如果状态筛选不正确,说明后端或前端有问题</Text> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default ApiTest |
@ -0,0 +1,137 @@ |
|||
# 礼品卡颜色主题修复说明 |
|||
|
|||
## 问题描述 |
|||
|
|||
用户反馈礼品卡显示为灰色,视觉效果不佳。经检查发现: |
|||
|
|||
1. **默认主题问题**:当礼品卡类型不是 10、20、30 时,默认使用 `silver` 主题 |
|||
2. **银色主题颜色**:银色主题使用的是 `#c0c0c0` 纯灰色,视觉效果较差 |
|||
3. **缺乏视觉层次**:所有主题色都是纯色,缺乏现代感 |
|||
|
|||
## 修复方案 |
|||
|
|||
### 1. 默认主题优化 |
|||
|
|||
**修改前**: |
|||
```typescript |
|||
default: return 'silver' // 默认灰色 |
|||
``` |
|||
|
|||
**修改后**: |
|||
```typescript |
|||
default: return 'purple' // 默认紫色,更美观 |
|||
``` |
|||
|
|||
### 2. 主题色渐变化 |
|||
|
|||
将所有主题色从纯色改为渐变色,提升视觉效果: |
|||
|
|||
#### 金色主题 (实物礼品卡) |
|||
```scss |
|||
background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%); |
|||
``` |
|||
|
|||
#### 蓝色主题 (虚拟礼品卡) |
|||
```scss |
|||
background: linear-gradient(135deg, #4a90e2 0%, #357abd 100%); |
|||
``` |
|||
|
|||
#### 绿色主题 (服务礼品卡) |
|||
```scss |
|||
background: linear-gradient(135deg, #5cb85c 0%, #449d44 100%); |
|||
``` |
|||
|
|||
#### 紫色主题 (默认) |
|||
```scss |
|||
background: linear-gradient(135deg, #9b59b6 0%, #8e44ad 100%); |
|||
``` |
|||
|
|||
#### 银色主题 (优化后) |
|||
```scss |
|||
background: linear-gradient(135deg, #e8e8e8 0%, #d0d0d0 100%); |
|||
``` |
|||
|
|||
#### 铜色主题 |
|||
```scss |
|||
background: linear-gradient(135deg, #cd7f32 0%, #b8722c 100%); |
|||
``` |
|||
|
|||
### 3. 按钮样式优化 |
|||
|
|||
为所有主题的按钮添加字体加粗: |
|||
```scss |
|||
.use-btn { |
|||
font-weight: 600; // 新增 |
|||
} |
|||
``` |
|||
|
|||
## 主题色映射规则 |
|||
|
|||
| 礼品卡类型 | type值 | 主题色 | 视觉效果 | |
|||
|-----------|--------|--------|----------| |
|||
| 实物礼品卡 | 10 | gold | 金色渐变 | |
|||
| 虚拟礼品卡 | 20 | blue | 蓝色渐变 | |
|||
| 服务礼品卡 | 30 | green | 绿色渐变 | |
|||
| 其他/未知 | 其他值 | purple | 紫色渐变 | |
|||
|
|||
## 修改的文件 |
|||
|
|||
### 1. `src/user/gift/index.tsx` |
|||
- 修改 `getThemeByType` 函数 |
|||
- 将默认主题从 `silver` 改为 `purple` |
|||
|
|||
### 2. `src/components/GiftCard.scss` |
|||
- 优化所有主题色为渐变效果 |
|||
- 添加按钮字体加粗 |
|||
- 改进银色主题的颜色搭配 |
|||
|
|||
### 3. 新增测试文件 |
|||
- `src/user/gift/color-test.tsx` - 颜色测试页面 |
|||
|
|||
## 测试验证 |
|||
|
|||
### 测试页面 |
|||
访问 `/user/gift/color-test` 可以看到: |
|||
- 不同类型礼品卡的颜色效果 |
|||
- 渐变色的视觉表现 |
|||
- 按钮样式的改进效果 |
|||
|
|||
### 测试用例 |
|||
1. **type=10**: 应显示金色渐变 |
|||
2. **type=20**: 应显示蓝色渐变 |
|||
3. **type=30**: 应显示绿色渐变 |
|||
4. **type=其他**: 应显示紫色渐变 |
|||
5. **type=undefined**: 应显示紫色渐变 |
|||
|
|||
## 视觉效果对比 |
|||
|
|||
### 修复前 |
|||
- ❌ 默认显示灰色,视觉效果差 |
|||
- ❌ 纯色背景,缺乏层次感 |
|||
- ❌ 按钮样式平淡 |
|||
|
|||
### 修复后 |
|||
- ✅ 默认显示紫色,更加美观 |
|||
- ✅ 渐变色背景,富有层次感 |
|||
- ✅ 按钮字体加粗,更加突出 |
|||
|
|||
## 兼容性说明 |
|||
|
|||
- ✅ 完全向后兼容,不影响现有功能 |
|||
- ✅ 所有现有的礼品卡都会自动应用新的颜色主题 |
|||
- ✅ 不需要修改数据结构或API接口 |
|||
|
|||
## 后续优化建议 |
|||
|
|||
1. **动态主题**: 可以考虑根据商品分类动态选择主题色 |
|||
2. **自定义主题**: 允许用户或管理员自定义主题色 |
|||
3. **季节主题**: 根据季节或节日使用特殊主题色 |
|||
4. **品牌主题**: 根据商品品牌使用对应的品牌色 |
|||
|
|||
## 部署检查 |
|||
|
|||
- [ ] 样式文件更新正确 |
|||
- [ ] 主题选择逻辑正确 |
|||
- [ ] 测试页面验证通过 |
|||
- [ ] 各种设备上显示正常 |
|||
- [ ] 不同状态下颜色正确 |
@ -0,0 +1,213 @@ |
|||
import React from 'react' |
|||
import { View, Text } from '@tarojs/components' |
|||
import GiftCard from '@/components/GiftCard' |
|||
import { ShopGift } from '@/api/shop/shopGift/model' |
|||
|
|||
const ColorTest: React.FC = () => { |
|||
// 测试不同类型的礼品卡颜色
|
|||
const testGifts: ShopGift[] = [ |
|||
{ |
|||
id: 1, |
|||
name: '实物礼品卡', |
|||
goodsName: '杜尔伯特草原奶香牛上脑(2kg,分4小包)', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i1/2206571109/O1CN01QZxQJJ1Uw8QZxQJJ_!!2206571109.jpg', |
|||
description: '实物商品礼品卡', |
|||
code: 'GOLD001234567890', |
|||
goodsId: 101, |
|||
faceValue: '200', |
|||
type: 10, // 实物礼品卡 - 应该显示金色
|
|||
useStatus: 0, |
|||
expireTime: '2024-12-31 23:59:59', |
|||
instructions: '适用于实物商品兑换', |
|||
contactInfo: '400-800-8888' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
name: '虚拟礼品卡', |
|||
goodsName: '星巴克经典拿铁咖啡券', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i2/2206571109/O1CN01ABC123_!!2206571109.jpg', |
|||
description: '虚拟商品礼品卡', |
|||
code: 'BLUE001234567890', |
|||
goodsId: 102, |
|||
faceValue: '100', |
|||
type: 20, // 虚拟礼品卡 - 应该显示蓝色
|
|||
useStatus: 0, |
|||
expireTime: '2024-12-31 23:59:59', |
|||
instructions: '适用于虚拟商品兑换', |
|||
contactInfo: '400-800-8888' |
|||
}, |
|||
{ |
|||
id: 3, |
|||
name: '服务礼品卡', |
|||
goodsName: '海底捞4人套餐券', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i3/2206571109/O1CN01DEF456_!!2206571109.jpg', |
|||
description: '服务类礼品卡', |
|||
code: 'GREEN01234567890', |
|||
goodsId: 103, |
|||
faceValue: '300', |
|||
type: 30, // 服务礼品卡 - 应该显示绿色
|
|||
useStatus: 0, |
|||
expireTime: '2024-12-31 23:59:59', |
|||
instructions: '适用于服务类商品兑换', |
|||
contactInfo: '400-800-8888' |
|||
}, |
|||
{ |
|||
id: 4, |
|||
name: '未知类型礼品卡', |
|||
goodsName: '通用商品券', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i4/2206571109/O1CN01GHI789_!!2206571109.jpg', |
|||
description: '未知类型礼品卡', |
|||
code: 'PURPLE1234567890', |
|||
goodsId: 104, |
|||
faceValue: '150', |
|||
type: 99, // 未知类型 - 应该显示紫色(默认)
|
|||
useStatus: 0, |
|||
expireTime: '2024-12-31 23:59:59', |
|||
instructions: '通用礼品卡', |
|||
contactInfo: '400-800-8888' |
|||
}, |
|||
{ |
|||
id: 5, |
|||
name: '银色主题测试', |
|||
goodsName: '银色主题礼品卡', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i5/2206571109/O1CN01JKL012_!!2206571109.jpg', |
|||
description: '测试银色主题', |
|||
code: 'SILVER1234567890', |
|||
goodsId: 105, |
|||
faceValue: '80', |
|||
type: undefined, // 无类型 - 应该显示紫色(默认)
|
|||
useStatus: 0, |
|||
expireTime: '2024-12-31 23:59:59', |
|||
instructions: '银色主题测试', |
|||
contactInfo: '400-800-8888' |
|||
} |
|||
] |
|||
|
|||
// 转换数据格式
|
|||
const transformGiftData = (gift: ShopGift) => { |
|||
return { |
|||
id: gift.id || 0, |
|||
name: gift.name || '礼品卡', |
|||
goodsName: gift.goodsName, |
|||
description: gift.description || gift.instructions, |
|||
code: gift.code, |
|||
goodsImage: gift.goodsImage, |
|||
faceValue: gift.faceValue, |
|||
type: gift.type, |
|||
useStatus: gift.useStatus, |
|||
expireTime: gift.expireTime, |
|||
contactInfo: gift.contactInfo, |
|||
goodsInfo: { |
|||
...((gift.goodsName || gift.goodsId) && { |
|||
specification: `礼品卡面值:¥${gift.faceValue}`, |
|||
category: getTypeText(gift.type), |
|||
tags: [ |
|||
getTypeText(gift.type), |
|||
'可使用', |
|||
'测试卡片' |
|||
].filter(Boolean), |
|||
instructions: [ |
|||
'这是颜色测试卡片', |
|||
'请检查主题色是否正确', |
|||
'不可兑换现金' |
|||
], |
|||
notices: [ |
|||
'这是测试数据', |
|||
'请勿实际使用', |
|||
'仅用于颜色测试' |
|||
] |
|||
}) |
|||
}, |
|||
showCode: true, |
|||
showUseBtn: true, |
|||
showDetailBtn: true, |
|||
showGoodsDetail: true, |
|||
theme: getThemeByType(gift.type), |
|||
onUse: () => console.log('使用:', gift.goodsName || gift.name), |
|||
onDetail: () => console.log('详情:', gift.goodsName || gift.name), |
|||
onClick: () => console.log('点击:', gift.goodsName || gift.name) |
|||
} |
|||
} |
|||
|
|||
const getTypeText = (type?: number): string => { |
|||
switch (type) { |
|||
case 10: return '实物礼品卡' |
|||
case 20: return '虚拟礼品卡' |
|||
case 30: return '服务礼品卡' |
|||
default: return '通用礼品卡' |
|||
} |
|||
} |
|||
|
|||
const getThemeByType = (type?: number): 'gold' | 'silver' | 'bronze' | 'blue' | 'green' | 'purple' => { |
|||
switch (type) { |
|||
case 10: return 'gold' // 实物礼品卡 - 金色
|
|||
case 20: return 'blue' // 虚拟礼品卡 - 蓝色
|
|||
case 30: return 'green' // 服务礼品卡 - 绿色
|
|||
default: return 'purple' // 默认使用紫色主题
|
|||
} |
|||
} |
|||
|
|||
const getExpectedColor = (type?: number): string => { |
|||
switch (type) { |
|||
case 10: return '金色渐变' |
|||
case 20: return '蓝色渐变' |
|||
case 30: return '绿色渐变' |
|||
default: return '紫色渐变' |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<View className="bg-gray-50 min-h-screen"> |
|||
{/* 页面标题 */} |
|||
<View className="bg-white px-4 py-3 border-b border-gray-100"> |
|||
<Text className="text-lg font-bold text-center"> |
|||
礼品卡颜色主题测试 |
|||
</Text> |
|||
<Text className="text-sm text-gray-600 text-center mt-1"> |
|||
验证不同类型礼品卡的主题色显示 |
|||
</Text> |
|||
</View> |
|||
|
|||
{/* 颜色说明 */} |
|||
<View className="bg-white mx-4 mt-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2">主题色说明:</Text> |
|||
<View className="space-y-1"> |
|||
<Text className="text-sm text-gray-600">• type=10 (实物礼品卡) → 金色渐变</Text> |
|||
<Text className="text-sm text-gray-600">• type=20 (虚拟礼品卡) → 蓝色渐变</Text> |
|||
<Text className="text-sm text-gray-600">• type=30 (服务礼品卡) → 绿色渐变</Text> |
|||
<Text className="text-sm text-gray-600">• 其他/未知类型 → 紫色渐变(默认)</Text> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 礼品卡列表 */} |
|||
<View className="p-4"> |
|||
{testGifts.map((gift, index) => ( |
|||
<View key={gift.id} className="mb-4"> |
|||
<View className="bg-blue-50 px-3 py-2 rounded-t-lg"> |
|||
<Text className="text-sm font-medium text-blue-800"> |
|||
测试 {index + 1}: type={gift.type || 'undefined'} → 预期: {getExpectedColor(gift.type)} |
|||
</Text> |
|||
<Text className="text-xs text-blue-600"> |
|||
{gift.goodsName} |
|||
</Text> |
|||
</View> |
|||
<GiftCard {...transformGiftData(gift)} /> |
|||
</View> |
|||
))} |
|||
</View> |
|||
|
|||
{/* 修复说明 */} |
|||
<View className="bg-white mx-4 mb-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2">修复内容:</Text> |
|||
<View className="space-y-1"> |
|||
<Text className="text-sm text-gray-600">✅ 默认主题从银色改为紫色</Text> |
|||
<Text className="text-sm text-gray-600">✅ 所有主题色使用渐变效果</Text> |
|||
<Text className="text-sm text-gray-600">✅ 按钮字体加粗提升视觉效果</Text> |
|||
<Text className="text-sm text-gray-600">✅ 银色主题优化为更美观的灰色渐变</Text> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default ColorTest |
@ -0,0 +1,226 @@ |
|||
import React, { useState } from 'react' |
|||
import { View, Text } from '@tarojs/components' |
|||
import { Tabs, TabPane, Button } from '@nutui/nutui-react-taro' |
|||
import GiftCard from '@/components/GiftCard' |
|||
import { ShopGift } from '@/api/shop/shopGift/model' |
|||
|
|||
const DebugTab: React.FC = () => { |
|||
const [activeTab, setActiveTab] = useState<string | number>('0') |
|||
const [debugInfo, setDebugInfo] = useState<string[]>([]) |
|||
|
|||
// 模拟不同状态的礼品卡数据
|
|||
const mockData: { [key: string]: ShopGift[] } = { |
|||
'0': [ // 未使用
|
|||
{ |
|||
id: 1, |
|||
name: '未使用礼品卡1', |
|||
goodsName: '星巴克咖啡券', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i1/2206571109/O1CN01QZxQJJ1Uw8QZxQJJ_!!2206571109.jpg', |
|||
code: 'UNUSED001', |
|||
faceValue: '100', |
|||
type: 20, |
|||
status: 0, |
|||
expireTime: '2024-12-31 23:59:59' |
|||
} |
|||
], |
|||
'1': [ // 已使用
|
|||
{ |
|||
id: 2, |
|||
name: '已使用礼品卡1', |
|||
goodsName: '麦当劳套餐券', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i2/2206571109/O1CN01ABC123_!!2206571109.jpg', |
|||
code: 'USED001', |
|||
faceValue: '50', |
|||
type: 20, |
|||
status: 1, |
|||
useTime: '2024-08-15 14:30:00' |
|||
} |
|||
], |
|||
'2': [ // 失效
|
|||
{ |
|||
id: 3, |
|||
name: '失效礼品卡1', |
|||
goodsName: '海底捞火锅券', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i3/2206571109/O1CN01DEF456_!!2206571109.jpg', |
|||
code: 'INVALID001', |
|||
faceValue: '200', |
|||
type: 30, |
|||
status: 2, |
|||
expireTime: '2024-07-31 23:59:59' |
|||
} |
|||
] |
|||
} |
|||
|
|||
// 获取状态过滤条件
|
|||
const getStatusFilter = () => { |
|||
switch (String(activeTab)) { |
|||
case '0': return { status: 0 } |
|||
case '1': return { status: 1 } |
|||
case '2': return { status: 2 } |
|||
default: return {} |
|||
} |
|||
} |
|||
|
|||
// 根据传入值获取状态过滤条件
|
|||
const getStatusFilterByValue = (value: string | number) => { |
|||
switch (String(value)) { |
|||
case '0': return { status: 0 } |
|||
case '1': return { status: 1 } |
|||
case '2': return { status: 2 } |
|||
default: return {} |
|||
} |
|||
} |
|||
|
|||
// Tab切换处理
|
|||
const handleTabChange = (value: string | number) => { |
|||
const timestamp = new Date().toLocaleTimeString() |
|||
const statusFilter = getStatusFilterByValue(value) |
|||
|
|||
const newDebugInfo = [ |
|||
`[${timestamp}] Tab切换到: ${value}`, |
|||
`[${timestamp}] 状态过滤: ${JSON.stringify(statusFilter)}`, |
|||
`[${timestamp}] 预期显示: ${getStatusText(Number(value))}状态的礼品卡`, |
|||
'---' |
|||
] |
|||
|
|||
setDebugInfo(prev => [...newDebugInfo, ...prev]) |
|||
setActiveTab(value) |
|||
} |
|||
|
|||
const getStatusText = (status: number): string => { |
|||
switch (status) { |
|||
case 0: return '未使用' |
|||
case 1: return '已使用' |
|||
case 2: return '失效' |
|||
default: return '未知' |
|||
} |
|||
} |
|||
|
|||
// 转换数据
|
|||
const transformGiftData = (gift: ShopGift) => { |
|||
return { |
|||
id: gift.id || 0, |
|||
name: gift.name || '礼品卡', |
|||
goodsName: gift.goodsName, |
|||
description: `状态: ${getStatusText(gift.status || 0)}`, |
|||
code: gift.code, |
|||
goodsImage: gift.goodsImage, |
|||
faceValue: gift.faceValue, |
|||
type: gift.type, |
|||
status: gift.status, |
|||
expireTime: gift.expireTime, |
|||
useTime: gift.useTime, |
|||
showCode: gift.status === 0, |
|||
showUseBtn: gift.status === 0, |
|||
showDetailBtn: true, |
|||
theme: 'blue' as const, |
|||
onUse: () => console.log('使用'), |
|||
onDetail: () => console.log('详情') |
|||
} |
|||
} |
|||
|
|||
const currentData = mockData[String(activeTab)] || [] |
|||
|
|||
return ( |
|||
<View className="bg-gray-50 min-h-screen"> |
|||
{/* 页面标题 */} |
|||
<View className="bg-white px-4 py-3 border-b border-gray-100"> |
|||
<Text className="text-lg font-bold text-center"> |
|||
Tab切换调试页面 |
|||
</Text> |
|||
</View> |
|||
|
|||
{/* 当前状态信息 */} |
|||
<View className="bg-white mx-4 mt-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2">当前状态信息:</Text> |
|||
<View className="space-y-1"> |
|||
<Text className="text-sm">activeTab: {String(activeTab)}</Text> |
|||
<Text className="text-sm">状态过滤: {JSON.stringify(getStatusFilter())}</Text> |
|||
<Text className="text-sm">显示数据条数: {currentData.length}</Text> |
|||
<Text className="text-sm">预期状态: {getStatusText(Number(activeTab))}</Text> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* Tab切换 */} |
|||
<View className="bg-white mx-4 mt-4 rounded-lg"> |
|||
<Tabs value={activeTab} onChange={handleTabChange}> |
|||
<TabPane title="未使用" value="0"> |
|||
</TabPane> |
|||
<TabPane title="已使用" value="1"> |
|||
</TabPane> |
|||
<TabPane title="失效" value="2"> |
|||
</TabPane> |
|||
</Tabs> |
|||
</View> |
|||
|
|||
{/* 礼品卡展示 */} |
|||
<View className="p-4"> |
|||
<Text className="font-bold mb-2"> |
|||
当前显示: {getStatusText(Number(activeTab))}状态礼品卡 |
|||
</Text> |
|||
|
|||
{currentData.length > 0 ? ( |
|||
currentData.map((gift) => ( |
|||
<View key={gift.id} className="mb-4"> |
|||
<View className="bg-blue-50 px-3 py-2 rounded-t-lg"> |
|||
<Text className="text-sm font-medium text-blue-800"> |
|||
礼品卡ID: {gift.id}, status: {gift.status}, 预期: {getStatusText(gift.status || 0)} |
|||
</Text> |
|||
</View> |
|||
<GiftCard {...transformGiftData(gift)} /> |
|||
</View> |
|||
)) |
|||
) : ( |
|||
<View className="text-center py-8 bg-white rounded-lg"> |
|||
<Text className="text-gray-500"> |
|||
暂无{getStatusText(Number(activeTab))}状态的礼品卡 |
|||
</Text> |
|||
</View> |
|||
)} |
|||
</View> |
|||
|
|||
{/* 调试日志 */} |
|||
<View className="bg-white mx-4 mb-4 p-4 rounded-lg"> |
|||
<View className="flex justify-between items-center mb-2"> |
|||
<Text className="font-bold">调试日志:</Text> |
|||
<Button |
|||
size="small" |
|||
fill="outline" |
|||
onClick={() => setDebugInfo([])} |
|||
> |
|||
清空 |
|||
</Button> |
|||
</View> |
|||
<View className="max-h-40 overflow-y-auto"> |
|||
{debugInfo.length > 0 ? ( |
|||
debugInfo.map((info, index) => ( |
|||
<Text key={index} className="text-xs text-gray-600 block mb-1"> |
|||
{info} |
|||
</Text> |
|||
)) |
|||
) : ( |
|||
<Text className="text-xs text-gray-400">暂无调试信息</Text> |
|||
)} |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 测试按钮 */} |
|||
<View className="bg-white mx-4 mb-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2">快速测试:</Text> |
|||
<View className="flex gap-2"> |
|||
<Button size="small" onClick={() => handleTabChange('0')}> |
|||
切换到未使用 |
|||
</Button> |
|||
<Button size="small" onClick={() => handleTabChange('1')}> |
|||
切换到已使用 |
|||
</Button> |
|||
<Button size="small" onClick={() => handleTabChange('2')}> |
|||
切换到失效 |
|||
</Button> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default DebugTab |
@ -0,0 +1,221 @@ |
|||
import React from 'react' |
|||
import { View, Text } from '@tarojs/components' |
|||
import { Button } from '@nutui/nutui-react-taro' |
|||
import GiftCard from '@/components/GiftCard' |
|||
import { ShopGift } from '@/api/shop/shopGift/model' |
|||
|
|||
const QRCodeDemo: React.FC = () => { |
|||
// 模拟礼品卡数据
|
|||
const mockGifts: ShopGift[] = [ |
|||
{ |
|||
id: 1, |
|||
name: '星巴克礼品卡', |
|||
goodsName: '星巴克经典拿铁咖啡券', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i1/2206571109/O1CN01QZxQJJ1Uw8QZxQJJ_!!2206571109.jpg', |
|||
description: '享受醇香咖啡时光', |
|||
code: 'SB2024001234567890', |
|||
goodsId: 101, |
|||
faceValue: '100', |
|||
type: 20, |
|||
status: 0, // 未使用
|
|||
expireTime: '2024-12-31 23:59:59', |
|||
instructions: '适用于全国星巴克门店', |
|||
contactInfo: '400-800-8888' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
name: '麦当劳优惠券', |
|||
goodsName: '麦当劳巨无霸套餐券', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i2/2206571109/O1CN01ABC123_!!2206571109.jpg', |
|||
description: '美味汉堡套餐', |
|||
code: 'MCD2024987654321', |
|||
goodsId: 102, |
|||
faceValue: '50', |
|||
type: 20, |
|||
status: 0, // 未使用
|
|||
expireTime: '2024-10-31 23:59:59', |
|||
instructions: '适用于全国麦当劳门店', |
|||
contactInfo: '400-517-517' |
|||
}, |
|||
{ |
|||
id: 3, |
|||
name: '海底捞火锅券', |
|||
goodsName: '海底捞4人套餐券', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i3/2206571109/O1CN01DEF456_!!2206571109.jpg', |
|||
description: '享受正宗川味火锅', |
|||
code: 'HDL2024555666777', |
|||
goodsId: 103, |
|||
faceValue: '300', |
|||
type: 30, |
|||
status: 1, // 已使用
|
|||
useTime: '2024-08-15 19:30:00', |
|||
useLocation: '海底捞王府井店', |
|||
instructions: '需提前预约', |
|||
contactInfo: '400-869-8888' |
|||
} |
|||
] |
|||
|
|||
// 转换数据格式
|
|||
const transformGiftData = (gift: ShopGift) => { |
|||
return { |
|||
id: gift.id || 0, |
|||
name: gift.name || '礼品卡', |
|||
goodsName: gift.goodsName, |
|||
description: gift.description || gift.instructions, |
|||
code: gift.code, |
|||
goodsImage: gift.goodsImage, |
|||
faceValue: gift.faceValue, |
|||
type: gift.type, |
|||
status: gift.status, |
|||
expireTime: gift.expireTime, |
|||
useTime: gift.useTime, |
|||
useLocation: gift.useLocation, |
|||
contactInfo: gift.contactInfo, |
|||
goodsInfo: { |
|||
...((gift.goodsName || gift.goodsId) && { |
|||
specification: `礼品卡面值:¥${gift.faceValue}`, |
|||
category: getTypeText(gift.type), |
|||
tags: [ |
|||
getTypeText(gift.type), |
|||
getStatusText(gift.status), |
|||
'支持二维码核销' |
|||
].filter(Boolean), |
|||
instructions: [ |
|||
'点击"立即使用"生成二维码', |
|||
'向门店工作人员出示二维码', |
|||
'工作人员扫码完成核销', |
|||
'不可兑换现金' |
|||
], |
|||
notices: [ |
|||
'每次使用生成新的核销码', |
|||
'请在有效期内使用', |
|||
'如有疑问请联系客服' |
|||
] |
|||
}) |
|||
}, |
|||
showCode: gift.status === 0, |
|||
showUseBtn: gift.status === 0, // 只有未使用状态显示使用按钮
|
|||
showDetailBtn: true, |
|||
showGoodsDetail: true, |
|||
theme: getThemeByType(gift.type), |
|||
onUse: () => console.log('打开二维码弹窗'), |
|||
onDetail: () => console.log('查看详情'), |
|||
onClick: () => console.log('点击礼品卡') |
|||
} |
|||
} |
|||
|
|||
const getTypeText = (type?: number): string => { |
|||
switch (type) { |
|||
case 10: return '实物礼品卡' |
|||
case 20: return '虚拟礼品卡' |
|||
case 30: return '服务礼品卡' |
|||
default: return '通用礼品卡' |
|||
} |
|||
} |
|||
|
|||
const getStatusText = (status?: number): string => { |
|||
switch (status) { |
|||
case 0: return '未使用' |
|||
case 1: return '已使用' |
|||
case 2: return '失效' |
|||
default: return '未知状态' |
|||
} |
|||
} |
|||
|
|||
const getThemeByType = (type?: number): 'gold' | 'silver' | 'bronze' | 'blue' | 'green' | 'purple' => { |
|||
switch (type) { |
|||
case 10: return 'gold' |
|||
case 20: return 'blue' |
|||
case 30: return 'green' |
|||
default: return 'purple' |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<View className="bg-gray-50 min-h-screen"> |
|||
{/* 页面标题 */} |
|||
<View className="bg-white px-4 py-3 border-b border-gray-100"> |
|||
<Text className="text-lg font-bold text-center"> |
|||
二维码核销功能演示 |
|||
</Text> |
|||
<Text className="text-sm text-gray-600 text-center mt-1"> |
|||
点击"立即使用"按钮生成核销二维码 |
|||
</Text> |
|||
</View> |
|||
|
|||
{/* 功能说明 */} |
|||
<View className="bg-white mx-4 mt-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2">功能特性:</Text> |
|||
<View className="space-y-1"> |
|||
<Text className="text-sm text-gray-600">✅ 点击"立即使用"生成二维码</Text> |
|||
<Text className="text-sm text-gray-600">✅ 每次生成新的6位核销码</Text> |
|||
<Text className="text-sm text-gray-600">✅ 显示礼品卡详细信息</Text> |
|||
<Text className="text-sm text-gray-600">✅ 支持复制核销码和兑换码</Text> |
|||
<Text className="text-sm text-gray-600">✅ 门店工作人员扫码核销</Text> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 使用流程 */} |
|||
<View className="bg-blue-50 mx-4 mt-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2 text-blue-800">使用流程:</Text> |
|||
<View className="space-y-1"> |
|||
<Text className="text-sm text-blue-700">1. 用户点击"立即使用"按钮</Text> |
|||
<Text className="text-sm text-blue-700">2. 系统生成核销二维码和核销码</Text> |
|||
<Text className="text-sm text-blue-700">3. 用户向门店工作人员出示二维码</Text> |
|||
<Text className="text-sm text-blue-700">4. 工作人员扫码或输入核销码验证</Text> |
|||
<Text className="text-sm text-blue-700">5. 验证成功后完成核销</Text> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 礼品卡列表 */} |
|||
<View className="p-4"> |
|||
<Text className="font-bold mb-3">礼品卡示例:</Text> |
|||
{mockGifts.map((gift, index) => ( |
|||
<View key={gift.id} className="mb-4"> |
|||
<View className="bg-green-50 px-3 py-2 rounded-t-lg"> |
|||
<Text className="text-sm font-medium text-green-800"> |
|||
示例 {index + 1}: {getStatusText(gift.status)}状态 |
|||
</Text> |
|||
<Text className="text-xs text-green-600"> |
|||
{gift.status === 0 ? '可以生成二维码核销' : '已使用或失效,无法核销'} |
|||
</Text> |
|||
</View> |
|||
<GiftCard {...transformGiftData(gift)} /> |
|||
</View> |
|||
))} |
|||
</View> |
|||
|
|||
{/* 门店核销入口 */} |
|||
<View className="bg-white mx-4 mb-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2">门店工作人员:</Text> |
|||
<Text className="text-sm text-gray-600 mb-3"> |
|||
如果您是门店工作人员,请使用专门的核销页面 |
|||
</Text> |
|||
<Button |
|||
size="large" |
|||
type="primary" |
|||
onClick={() => { |
|||
// 这里可以跳转到门店核销页面
|
|||
console.log('跳转到门店核销页面') |
|||
}} |
|||
block |
|||
> |
|||
进入门店核销页面 |
|||
</Button> |
|||
</View> |
|||
|
|||
{/* 技术说明 */} |
|||
<View className="bg-yellow-50 mx-4 mb-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2 text-yellow-800">技术实现:</Text> |
|||
<View className="space-y-1"> |
|||
<Text className="text-sm text-yellow-700">• 二维码包含礼品卡ID、核销码等信息</Text> |
|||
<Text className="text-sm text-yellow-700">• 每次生成随机6位数字核销码</Text> |
|||
<Text className="text-sm text-yellow-700">• 支持扫码和手动输入两种验证方式</Text> |
|||
<Text className="text-sm text-yellow-700">• 核销后礼品卡状态更新为已使用</Text> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default QRCodeDemo |
@ -0,0 +1,212 @@ |
|||
# 礼品卡二维码核销功能实现说明 |
|||
|
|||
## 功能概述 |
|||
|
|||
将礼品卡的"立即使用"功能改为生成二维码,供门店工作人员扫码核销,提升用户体验和核销效率。 |
|||
|
|||
## 核心功能 |
|||
|
|||
### 1. 用户端功能 |
|||
- **生成核销二维码**:点击"立即使用"按钮生成包含核销信息的二维码 |
|||
- **显示核销码**:同时显示6位数字核销码,支持手动输入 |
|||
- **复制功能**:支持复制核销码和兑换码 |
|||
- **刷新功能**:可重新生成新的核销码 |
|||
|
|||
### 2. 门店端功能 |
|||
- **扫码核销**:使用摄像头扫描用户的二维码 |
|||
- **手动输入**:支持手动输入核销码进行验证 |
|||
- **信息验证**:显示礼品卡详细信息供确认 |
|||
- **完成核销**:确认后完成核销操作 |
|||
|
|||
## 技术实现 |
|||
|
|||
### 1. 组件结构 |
|||
|
|||
#### GiftCardQRCode 组件 |
|||
```typescript |
|||
interface GiftCardQRCodeProps { |
|||
visible: boolean |
|||
onClose: () => void |
|||
giftCard: { |
|||
id: number |
|||
name?: string |
|||
goodsName?: string |
|||
code?: string |
|||
faceValue?: string |
|||
type?: number |
|||
status?: number |
|||
expireTime?: string |
|||
contactInfo?: string |
|||
} |
|||
} |
|||
``` |
|||
|
|||
**主要功能**: |
|||
- 生成核销二维码 |
|||
- 显示礼品卡信息 |
|||
- 提供复制和分享功能 |
|||
|
|||
#### StoreVerification 页面 |
|||
**主要功能**: |
|||
- 扫码识别二维码 |
|||
- 手动输入核销码 |
|||
- 验证礼品卡信息 |
|||
- 完成核销操作 |
|||
|
|||
### 2. 数据流程 |
|||
|
|||
#### 二维码数据结构 |
|||
```json |
|||
{ |
|||
"type": "gift_card_verification", |
|||
"giftId": 123, |
|||
"giftCode": "SB2024001234567890", |
|||
"verificationCode": "123456", |
|||
"faceValue": "100", |
|||
"timestamp": 1692123456789, |
|||
"expireTime": "2024-12-31 23:59:59", |
|||
"codeExpireTime": "2024-08-16 15:30:00" |
|||
} |
|||
``` |
|||
|
|||
#### 核销流程 |
|||
1. **生成核销码** |
|||
```typescript |
|||
POST /shop/shop-gift/generate-verification-code |
|||
Body: { giftId: number } |
|||
Response: { verificationCode: string, expireTime: string } |
|||
``` |
|||
|
|||
2. **验证核销码** |
|||
```typescript |
|||
POST /shop/shop-gift/verify |
|||
Body: { verificationCode?: string, giftCode?: string } |
|||
Response: ShopGift |
|||
``` |
|||
|
|||
3. **完成核销** |
|||
```typescript |
|||
POST /shop/shop-gift/complete-verification |
|||
Body: { |
|||
giftId: number, |
|||
verificationCode: string, |
|||
storeId?: number, |
|||
storeName?: string, |
|||
operatorId?: number, |
|||
operatorName?: string |
|||
} |
|||
``` |
|||
|
|||
### 3. 安全机制 |
|||
|
|||
#### 核销码安全 |
|||
- **时效性**:核销码有效期15分钟 |
|||
- **一次性**:每次生成新的核销码 |
|||
- **随机性**:6位随机数字,防止猜测 |
|||
|
|||
#### 验证机制 |
|||
- **双重验证**:支持二维码和手动输入 |
|||
- **信息确认**:显示完整礼品卡信息供确认 |
|||
- **状态检查**:验证礼品卡状态和有效期 |
|||
|
|||
## 文件结构 |
|||
|
|||
### 新增文件 |
|||
``` |
|||
src/ |
|||
├── components/ |
|||
│ └── GiftCardQRCode.tsx # 二维码核销弹窗组件 |
|||
├── pages/ |
|||
│ └── store/ |
|||
│ └── verification.tsx # 门店核销页面 |
|||
├── user/gift/ |
|||
│ ├── qrcode-demo.tsx # 二维码功能演示页面 |
|||
│ └── qrcode-verification-implementation.md |
|||
└── api/shop/shopGift/ |
|||
└── index.ts # 新增核销相关API |
|||
``` |
|||
|
|||
### 修改文件 |
|||
``` |
|||
src/ |
|||
└── components/ |
|||
└── GiftCard.tsx # 修改"立即使用"按钮功能 |
|||
``` |
|||
|
|||
## 用户体验优化 |
|||
|
|||
### 1. 界面设计 |
|||
- **清晰的二维码显示**:大尺寸二维码,易于扫描 |
|||
- **信息完整展示**:显示礼品卡所有关键信息 |
|||
- **操作便捷**:一键复制、刷新等快捷操作 |
|||
|
|||
### 2. 交互优化 |
|||
- **即时反馈**:生成、验证过程有明确的加载状态 |
|||
- **错误处理**:友好的错误提示和重试机制 |
|||
- **状态管理**:清晰的状态流转和视觉反馈 |
|||
|
|||
### 3. 兼容性考虑 |
|||
- **多种核销方式**:支持扫码和手动输入 |
|||
- **设备适配**:适配不同尺寸的移动设备 |
|||
- **网络容错**:网络异常时的降级处理 |
|||
|
|||
## 业务流程 |
|||
|
|||
### 用户使用流程 |
|||
1. 用户在礼品卡列表点击"立即使用" |
|||
2. 系统生成核销二维码和核销码 |
|||
3. 用户向门店工作人员出示二维码 |
|||
4. 工作人员扫码或输入核销码 |
|||
5. 系统验证信息并显示礼品卡详情 |
|||
6. 工作人员确认后完成核销 |
|||
7. 礼品卡状态更新为已使用 |
|||
|
|||
### 门店操作流程 |
|||
1. 打开门店核销页面 |
|||
2. 扫描用户二维码或手动输入核销码 |
|||
3. 系统验证并显示礼品卡信息 |
|||
4. 确认信息无误后点击"确认核销" |
|||
5. 完成核销,用户礼品卡状态更新 |
|||
|
|||
## 测试验证 |
|||
|
|||
### 1. 功能测试 |
|||
- **二维码生成**:验证二维码正确生成和显示 |
|||
- **核销码生成**:验证6位数字核销码的随机性 |
|||
- **扫码识别**:验证二维码能被正确解析 |
|||
- **手动输入**:验证手动输入核销码的准确性 |
|||
- **核销完成**:验证核销后状态正确更新 |
|||
|
|||
### 2. 安全测试 |
|||
- **时效性测试**:验证核销码过期机制 |
|||
- **重复使用测试**:验证核销码不能重复使用 |
|||
- **状态验证**:验证只有未使用的礼品卡能生成核销码 |
|||
|
|||
### 3. 用户体验测试 |
|||
- **界面响应**:验证各种操作的响应速度 |
|||
- **错误处理**:验证各种异常情况的处理 |
|||
- **设备兼容**:验证在不同设备上的显示效果 |
|||
|
|||
## 部署注意事项 |
|||
|
|||
### 1. 后端API |
|||
- 确保核销相关API已部署 |
|||
- 配置核销码有效期(建议15分钟) |
|||
- 设置适当的并发限制 |
|||
|
|||
### 2. 权限配置 |
|||
- 门店核销页面需要相应权限 |
|||
- 核销操作需要记录操作人信息 |
|||
|
|||
### 3. 监控告警 |
|||
- 监控核销成功率 |
|||
- 监控异常核销请求 |
|||
- 设置核销量异常告警 |
|||
|
|||
## 后续优化建议 |
|||
|
|||
1. **批量核销**:支持一次核销多张礼品卡 |
|||
2. **核销统计**:提供核销数据统计和报表 |
|||
3. **离线核销**:支持网络异常时的离线核销 |
|||
4. **核销记录**:详细的核销历史记录查询 |
|||
5. **多门店支持**:支持多门店的核销管理 |
@ -0,0 +1,237 @@ |
|||
# 礼品卡状态字段迁移说明 |
|||
|
|||
## 概述 |
|||
|
|||
根据后端字段调整,将礼品卡的状态字段从 `useStatus` 迁移到 `status`,并更新状态值的含义。 |
|||
|
|||
## 字段变更 |
|||
|
|||
### 旧字段 (已废弃) |
|||
```typescript |
|||
useStatus?: number // 使用状态:0-可用 1-已使用 2-已过期 |
|||
``` |
|||
|
|||
### 新字段 |
|||
```typescript |
|||
status?: number // 状态:0-未使用 1-已使用 2-失效 |
|||
``` |
|||
|
|||
## 状态值映射 |
|||
|
|||
| 状态值 | 旧含义 | 新含义 | 显示文本 | 说明 | |
|||
|--------|--------|--------|----------|------| |
|||
| 0 | 可用 | 未使用 | "未使用" | 可以使用的礼品卡 | |
|||
| 1 | 已使用 | 已使用 | "已使用" | 已经使用过的礼品卡 | |
|||
| 2 | 已过期 | 失效 | "失效" | 失效的礼品卡(包括过期等情况) | |
|||
|
|||
## 修改内容 |
|||
|
|||
### 1. GiftCard 组件 (`src/components/GiftCard.tsx`) |
|||
|
|||
#### 接口定义更新 |
|||
```typescript |
|||
// 修改前 |
|||
useStatus?: number // 使用状态:0-可用 1-已使用 2-已过期 |
|||
|
|||
// 修改后 |
|||
status?: number // 状态:0-未使用 1-已使用 2-失效 |
|||
``` |
|||
|
|||
#### 状态信息函数更新 |
|||
```typescript |
|||
// 修改前 |
|||
const getStatusInfo = () => { |
|||
switch (useStatus) { |
|||
case 0: return { text: '可使用', color: 'success' } |
|||
case 1: return { text: '已使用', color: 'warning' } |
|||
case 2: return { text: '已过期', color: 'danger' } |
|||
} |
|||
} |
|||
|
|||
// 修改后 |
|||
const getStatusInfo = () => { |
|||
switch (status) { |
|||
case 0: return { text: '未使用', color: 'success' } |
|||
case 1: return { text: '已使用', color: 'warning' } |
|||
case 2: return { text: '失效', color: 'danger' } |
|||
} |
|||
} |
|||
``` |
|||
|
|||
#### 条件判断更新 |
|||
```typescript |
|||
// 所有 useStatus 相关的条件判断都改为 status |
|||
showCode: status === 0 |
|||
showUseBtn: status === 0 |
|||
className: status !== 0 ? 'disabled' : '' |
|||
``` |
|||
|
|||
### 2. 礼品卡页面 (`src/user/gift/index.tsx`) |
|||
|
|||
#### 筛选条件更新 |
|||
```typescript |
|||
// 修改前 |
|||
const getStatusFilter = () => { |
|||
switch (String(activeTab)) { |
|||
case '0': return { useStatus: 0 } // 可用 |
|||
case '1': return { useStatus: 1 } // 已使用 |
|||
case '2': return { useStatus: 2 } // 已过期 |
|||
} |
|||
} |
|||
|
|||
// 修改后 |
|||
const getStatusFilter = () => { |
|||
switch (String(activeTab)) { |
|||
case '0': return { status: 0 } // 未使用 |
|||
case '1': return { status: 1 } // 已使用 |
|||
case '2': return { status: 2 } // 失效 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
#### 数据转换更新 |
|||
```typescript |
|||
// 修改前 |
|||
const transformGiftData = (gift: ShopGift) => ({ |
|||
useStatus: gift.useStatus, |
|||
showCode: gift.useStatus === 0, |
|||
showUseBtn: gift.useStatus === 0, |
|||
}) |
|||
|
|||
// 修改后 |
|||
const transformGiftData = (gift: ShopGift) => ({ |
|||
status: gift.status, |
|||
showCode: gift.status === 0, |
|||
showUseBtn: gift.status === 0, |
|||
}) |
|||
``` |
|||
|
|||
#### Tab标签更新 |
|||
```typescript |
|||
// 修改前 |
|||
<TabPane title="可用" value="0" /> |
|||
<TabPane title="已使用" value="1" /> |
|||
<TabPane title="已过期" value="2" /> |
|||
|
|||
// 修改后 |
|||
<TabPane title="未使用" value="0" /> |
|||
<TabPane title="已使用" value="1" /> |
|||
<TabPane title="失效" value="2" /> |
|||
``` |
|||
|
|||
#### 空状态文本更新 |
|||
```typescript |
|||
// 修改前 |
|||
activeTab === '0' ? "暂无可用礼品卡" : |
|||
activeTab === '1' ? "暂无已使用礼品卡" : |
|||
"暂无已过期礼品卡" |
|||
|
|||
// 修改后 |
|||
activeTab === '0' ? "暂无未使用礼品卡" : |
|||
activeTab === '1' ? "暂无已使用礼品卡" : |
|||
"暂无失效礼品卡" |
|||
``` |
|||
|
|||
### 3. 类型定义 (`src/types/giftCard.ts`) |
|||
|
|||
#### 枚举更新 |
|||
```typescript |
|||
// 修改前 |
|||
export enum UseStatus { |
|||
AVAILABLE = 0, // 可用 |
|||
USED = 1, // 已使用 |
|||
EXPIRED = 2 // 已过期 |
|||
} |
|||
|
|||
// 修改后 |
|||
export enum GiftStatus { |
|||
UNUSED = 0, // 未使用 |
|||
USED = 1, // 已使用 |
|||
INVALID = 2 // 失效 |
|||
} |
|||
``` |
|||
|
|||
#### 接口更新 |
|||
```typescript |
|||
// 修改前 |
|||
export interface GiftCardData { |
|||
useStatus?: UseStatus |
|||
} |
|||
|
|||
// 修改后 |
|||
export interface GiftCardData { |
|||
status?: GiftStatus |
|||
} |
|||
``` |
|||
|
|||
## 业务逻辑变更 |
|||
|
|||
### 显示逻辑 |
|||
1. **未使用 (status=0)**: |
|||
- 显示绿色"未使用"标签 |
|||
- 显示兑换码 |
|||
- 显示"立即使用"按钮 |
|||
- 显示过期时间提醒 |
|||
|
|||
2. **已使用 (status=1)**: |
|||
- 显示灰色"已使用"标签 |
|||
- 不显示兑换码和使用按钮 |
|||
- 显示使用时间和地址 |
|||
- 卡片呈现禁用状态 |
|||
|
|||
3. **失效 (status=2)**: |
|||
- 显示红色"失效"标签 |
|||
- 不显示兑换码和使用按钮 |
|||
- 显示状态遮罩 |
|||
- 卡片呈现禁用状态 |
|||
|
|||
### API 调用变更 |
|||
```typescript |
|||
// 修改前 |
|||
getUserGifts({ useStatus: 0 }) // 获取可用礼品卡 |
|||
|
|||
// 修改后 |
|||
getUserGifts({ status: 0 }) // 获取未使用礼品卡 |
|||
``` |
|||
|
|||
## 测试验证 |
|||
|
|||
### 测试页面 |
|||
访问 `/user/gift/status-test` 可以验证: |
|||
- 不同状态值的显示效果 |
|||
- 状态标签文本正确性 |
|||
- 按钮和兑换码的显示逻辑 |
|||
- 卡片的禁用状态 |
|||
|
|||
### 测试用例 |
|||
1. **status=0**: 显示"未使用",有兑换码和使用按钮 |
|||
2. **status=1**: 显示"已使用",显示使用时间,无按钮 |
|||
3. **status=2**: 显示"失效",有状态遮罩,无按钮 |
|||
|
|||
## 兼容性说明 |
|||
|
|||
### 数据兼容 |
|||
- 如果后端同时返回 `useStatus` 和 `status`,优先使用 `status` |
|||
- 建议后端逐步迁移,确保数据一致性 |
|||
|
|||
### 前端兼容 |
|||
- 前端已完全切换到 `status` 字段 |
|||
- 不再处理 `useStatus` 字段 |
|||
- 所有相关逻辑都已更新 |
|||
|
|||
## 部署检查清单 |
|||
|
|||
- [ ] 后端 API 返回 `status` 字段而非 `useStatus` |
|||
- [ ] 状态值含义正确:0未使用 1已使用 2失效 |
|||
- [ ] Tab标签显示正确文本 |
|||
- [ ] 状态标签显示正确文本 |
|||
- [ ] 筛选功能使用正确的字段名 |
|||
- [ ] 空状态提示文本正确 |
|||
- [ ] 测试页面验证通过 |
|||
|
|||
## 注意事项 |
|||
|
|||
1. **数据一致性**:确保后端和前端对状态值的理解一致 |
|||
2. **用户体验**:状态文本更改可能需要用户适应 |
|||
3. **API 文档**:更新相关 API 文档,说明字段变更 |
|||
4. **监控告警**:关注迁移后的错误日志和用户反馈 |
@ -0,0 +1,219 @@ |
|||
import React from 'react' |
|||
import { View, Text } from '@tarojs/components' |
|||
import GiftCard from '@/components/GiftCard' |
|||
import { ShopGift } from '@/api/shop/shopGift/model' |
|||
|
|||
const StatusTest: React.FC = () => { |
|||
// 测试不同状态的礼品卡
|
|||
const testGifts: ShopGift[] = [ |
|||
{ |
|||
id: 1, |
|||
name: '未使用礼品卡', |
|||
goodsName: '杜尔伯特草原奶香牛上脑(2kg,分4小包)', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i1/2206571109/O1CN01QZxQJJ1Uw8QZxQJJ_!!2206571109.jpg', |
|||
description: '未使用状态的礼品卡', |
|||
code: 'UNUSED1234567890', |
|||
goodsId: 101, |
|||
faceValue: '200', |
|||
type: 10, |
|||
status: 0, // 未使用
|
|||
expireTime: '2024-12-31 23:59:59', |
|||
instructions: '适用于实物商品兑换', |
|||
contactInfo: '400-800-8888' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
name: '已使用礼品卡', |
|||
goodsName: '星巴克经典拿铁咖啡券', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i2/2206571109/O1CN01ABC123_!!2206571109.jpg', |
|||
description: '已使用状态的礼品卡', |
|||
code: 'USED001234567890', |
|||
goodsId: 102, |
|||
faceValue: '100', |
|||
type: 20, |
|||
status: 1, // 已使用
|
|||
useTime: '2024-08-15 14:30:00', |
|||
useLocation: '星巴克王府井店', |
|||
instructions: '适用于虚拟商品兑换', |
|||
contactInfo: '400-800-8888' |
|||
}, |
|||
{ |
|||
id: 3, |
|||
name: '失效礼品卡', |
|||
goodsName: '海底捞4人套餐券', |
|||
goodsImage: 'https://img.alicdn.com/imgextra/i3/2206571109/O1CN01DEF456_!!2206571109.jpg', |
|||
description: '失效状态的礼品卡', |
|||
code: 'INVALID1234567890', |
|||
goodsId: 103, |
|||
faceValue: '300', |
|||
type: 30, |
|||
status: 2, // 失效
|
|||
expireTime: '2024-07-31 23:59:59', |
|||
instructions: '适用于服务类商品兑换', |
|||
contactInfo: '400-800-8888' |
|||
} |
|||
] |
|||
|
|||
// 转换数据格式
|
|||
const transformGiftData = (gift: ShopGift) => { |
|||
return { |
|||
id: gift.id || 0, |
|||
name: gift.name || '礼品卡', |
|||
goodsName: gift.goodsName, |
|||
description: gift.description || gift.instructions, |
|||
code: gift.code, |
|||
goodsImage: gift.goodsImage, |
|||
faceValue: gift.faceValue, |
|||
type: gift.type, |
|||
status: gift.status, // 使用 status 字段
|
|||
expireTime: gift.expireTime, |
|||
useTime: gift.useTime, |
|||
useLocation: gift.useLocation, |
|||
contactInfo: gift.contactInfo, |
|||
goodsInfo: { |
|||
...((gift.goodsName || gift.goodsId) && { |
|||
specification: `礼品卡面值:¥${gift.faceValue}`, |
|||
category: getTypeText(gift.type), |
|||
tags: [ |
|||
getTypeText(gift.type), |
|||
getStatusText(gift.status), |
|||
'测试卡片' |
|||
].filter(Boolean), |
|||
instructions: [ |
|||
'这是状态测试卡片', |
|||
'请检查状态显示是否正确', |
|||
'不可兑换现金' |
|||
], |
|||
notices: [ |
|||
'这是测试数据', |
|||
'请勿实际使用', |
|||
'仅用于状态测试' |
|||
] |
|||
}) |
|||
}, |
|||
showCode: gift.status === 0, // 只有未使用状态显示兑换码
|
|||
showUseBtn: gift.status === 0, // 只有未使用状态显示使用按钮
|
|||
showDetailBtn: true, |
|||
showGoodsDetail: true, |
|||
theme: getThemeByType(gift.type), |
|||
onUse: () => console.log('使用:', gift.goodsName || gift.name), |
|||
onDetail: () => console.log('详情:', gift.goodsName || gift.name), |
|||
onClick: () => console.log('点击:', gift.goodsName || gift.name) |
|||
} |
|||
} |
|||
|
|||
const getTypeText = (type?: number): string => { |
|||
switch (type) { |
|||
case 10: return '实物礼品卡' |
|||
case 20: return '虚拟礼品卡' |
|||
case 30: return '服务礼品卡' |
|||
default: return '通用礼品卡' |
|||
} |
|||
} |
|||
|
|||
const getStatusText = (status?: number): string => { |
|||
switch (status) { |
|||
case 0: return '未使用' |
|||
case 1: return '已使用' |
|||
case 2: return '失效' |
|||
default: return '未知状态' |
|||
} |
|||
} |
|||
|
|||
const getThemeByType = (type?: number): 'gold' | 'silver' | 'bronze' | 'blue' | 'green' | 'purple' => { |
|||
switch (type) { |
|||
case 10: return 'gold' |
|||
case 20: return 'blue' |
|||
case 30: return 'green' |
|||
default: return 'purple' |
|||
} |
|||
} |
|||
|
|||
const getExpectedBehavior = (status?: number): string => { |
|||
switch (status) { |
|||
case 0: return '显示兑换码和使用按钮' |
|||
case 1: return '显示使用时间,无兑换码和使用按钮' |
|||
case 2: return '显示失效状态,无兑换码和使用按钮' |
|||
default: return '未知行为' |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<View className="bg-gray-50 min-h-screen"> |
|||
{/* 页面标题 */} |
|||
<View className="bg-white px-4 py-3 border-b border-gray-100"> |
|||
<Text className="text-lg font-bold text-center"> |
|||
礼品卡状态字段测试 |
|||
</Text> |
|||
<Text className="text-sm text-gray-600 text-center mt-1"> |
|||
验证 status 字段替代 useStatus 字段 |
|||
</Text> |
|||
</View> |
|||
|
|||
{/* 状态说明 */} |
|||
<View className="bg-white mx-4 mt-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2">状态字段说明:</Text> |
|||
<View className="space-y-1"> |
|||
<Text className="text-sm text-gray-600">• status=0 (未使用) → 显示"未使用"标签</Text> |
|||
<Text className="text-sm text-gray-600">• status=1 (已使用) → 显示"已使用"标签</Text> |
|||
<Text className="text-sm text-gray-600">• status=2 (失效) → 显示"失效"标签</Text> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 修改说明 */} |
|||
<View className="bg-blue-50 mx-4 mt-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2 text-blue-800">字段修改说明:</Text> |
|||
<View className="space-y-1"> |
|||
<Text className="text-sm text-blue-700">✅ useStatus 字段已废弃</Text> |
|||
<Text className="text-sm text-blue-700">✅ 使用 status 字段替代</Text> |
|||
<Text className="text-sm text-blue-700">✅ 状态文本更新:可用→未使用,已过期→失效</Text> |
|||
<Text className="text-sm text-blue-700">✅ Tab标签文本同步更新</Text> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 礼品卡列表 */} |
|||
<View className="p-4"> |
|||
{testGifts.map((gift, index) => ( |
|||
<View key={gift.id} className="mb-4"> |
|||
<View className="bg-green-50 px-3 py-2 rounded-t-lg"> |
|||
<Text className="text-sm font-medium text-green-800"> |
|||
测试 {index + 1}: status={gift.status} → {getStatusText(gift.status)} |
|||
</Text> |
|||
<Text className="text-xs text-green-600"> |
|||
预期行为: {getExpectedBehavior(gift.status)} |
|||
</Text> |
|||
</View> |
|||
<GiftCard {...transformGiftData(gift)} /> |
|||
</View> |
|||
))} |
|||
</View> |
|||
|
|||
{/* 验证清单 */} |
|||
<View className="bg-white mx-4 mb-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2">验证清单:</Text> |
|||
<View className="space-y-1"> |
|||
<Text className="text-sm text-gray-600">□ status=0 的卡片显示"未使用"标签</Text> |
|||
<Text className="text-sm text-gray-600">□ status=0 的卡片显示兑换码和使用按钮</Text> |
|||
<Text className="text-sm text-gray-600">□ status=1 的卡片显示"已使用"标签</Text> |
|||
<Text className="text-sm text-gray-600">□ status=1 的卡片显示使用时间</Text> |
|||
<Text className="text-sm text-gray-600">□ status=1 的卡片不显示兑换码和使用按钮</Text> |
|||
<Text className="text-sm text-gray-600">□ status=2 的卡片显示"失效"标签</Text> |
|||
<Text className="text-sm text-gray-600">□ status=2 的卡片有状态遮罩</Text> |
|||
<Text className="text-sm text-gray-600">□ 所有非0状态的卡片都有disabled样式</Text> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* API 参数说明 */} |
|||
<View className="bg-yellow-50 mx-4 mb-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2 text-yellow-800">API 参数更新:</Text> |
|||
<View className="space-y-1"> |
|||
<Text className="text-sm text-yellow-700">• 筛选参数:useStatus → status</Text> |
|||
<Text className="text-sm text-yellow-700">• 返回字段:useStatus → status</Text> |
|||
<Text className="text-sm text-yellow-700">• 状态值:0未使用 1已使用 2失效</Text> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default StatusTest |
@ -0,0 +1,198 @@ |
|||
# Tab切换状态传值修复说明 |
|||
|
|||
## 问题描述 |
|||
|
|||
用户反馈:点击"未使用"标签时,显示的却是"已使用"状态的礼品卡,说明Tab切换时传递的状态值不正确。 |
|||
|
|||
## 问题分析 |
|||
|
|||
经过代码检查,发现可能的问题点: |
|||
|
|||
1. **异步状态更新问题**:`handleTabChange` 中使用 `setTimeout` 延迟执行,可能导致状态更新不及时 |
|||
2. **API参数接口混乱**:`ShopGiftParam` 接口中同时存在 `status` 和 `useStatus` 字段 |
|||
3. **状态获取时机问题**:`getStatusFilter()` 可能获取到的是旧的 `activeTab` 值 |
|||
|
|||
## 修复方案 |
|||
|
|||
### 1. 优化Tab切换逻辑 |
|||
|
|||
**修改前**: |
|||
```typescript |
|||
const handleTabChange = (value: string | number) => { |
|||
setActiveTab(value) |
|||
setPage(1) |
|||
setList([]) |
|||
setHasMore(true) |
|||
// 延迟执行reload,确保状态更新完成 |
|||
setTimeout(() => { |
|||
reload(true) |
|||
}, 100) |
|||
} |
|||
``` |
|||
|
|||
**修改后**: |
|||
```typescript |
|||
const handleTabChange = (value: string | number) => { |
|||
console.log('Tab切换到:', value) |
|||
setActiveTab(value) |
|||
setPage(1) |
|||
setList([]) |
|||
setHasMore(true) |
|||
|
|||
// 直接传递状态值,避免异步状态更新问题 |
|||
const statusFilter = getStatusFilterByValue(value) |
|||
console.log('状态过滤条件:', statusFilter) |
|||
|
|||
// 立即加载数据 |
|||
loadGiftsByStatus(statusFilter) |
|||
} |
|||
``` |
|||
|
|||
### 2. 新增辅助函数 |
|||
|
|||
```typescript |
|||
// 根据传入的值获取状态过滤条件 |
|||
const getStatusFilterByValue = (value: string | number) => { |
|||
switch (String(value)) { |
|||
case '0': return { status: 0 } // 未使用 |
|||
case '1': return { status: 1 } // 已使用 |
|||
case '2': return { status: 2 } // 失效 |
|||
default: return {} |
|||
} |
|||
} |
|||
|
|||
// 根据状态过滤条件加载礼品卡 |
|||
const loadGiftsByStatus = async (statusFilter: any) => { |
|||
setLoading(true) |
|||
try { |
|||
const res = await getUserGifts({ |
|||
page: 1, |
|||
limit: 10, |
|||
userId: Taro.getStorageSync('UserId'), |
|||
...statusFilter |
|||
}) |
|||
console.log('API返回数据:', res?.list) |
|||
if (res && res.list) { |
|||
setList(res.list) |
|||
setHasMore(res.list.length === 10) |
|||
setPage(2) |
|||
} else { |
|||
setList([]) |
|||
setHasMore(false) |
|||
} |
|||
} catch (error) { |
|||
console.error('获取礼品卡失败:', error) |
|||
Taro.showToast({ |
|||
title: '获取礼品卡失败', |
|||
icon: 'error' |
|||
}) |
|||
} finally { |
|||
setLoading(false) |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### 3. 清理API接口定义 |
|||
|
|||
**修改前**: |
|||
```typescript |
|||
export interface ShopGiftParam extends PageParam { |
|||
// 状态筛选 |
|||
status?: number; |
|||
// 使用状态筛选 |
|||
useStatus?: number; // 冗余字段,容易造成混乱 |
|||
} |
|||
``` |
|||
|
|||
**修改后**: |
|||
```typescript |
|||
export interface ShopGiftParam extends PageParam { |
|||
// 状态筛选 (0未使用 1已使用 2失效) |
|||
status?: number; |
|||
// 移除了 useStatus 字段 |
|||
} |
|||
``` |
|||
|
|||
### 4. 增强调试功能 |
|||
|
|||
在关键函数中添加了 `console.log` 调试信息: |
|||
```typescript |
|||
console.log('Tab切换到:', value) |
|||
console.log('状态过滤条件:', statusFilter) |
|||
console.log('API返回数据:', res?.list) |
|||
``` |
|||
|
|||
## 修改的文件 |
|||
|
|||
1. **`src/user/gift/index.tsx`** |
|||
- 优化 `handleTabChange` 函数 |
|||
- 新增 `getStatusFilterByValue` 函数 |
|||
- 新增 `loadGiftsByStatus` 函数 |
|||
- 增强调试日志 |
|||
|
|||
2. **`src/api/shop/shopGift/model/index.ts`** |
|||
- 移除 `ShopGiftParam` 接口中的 `useStatus` 字段 |
|||
- 明确 `status` 字段的含义 |
|||
|
|||
3. **新增测试文件** |
|||
- `src/user/gift/debug-tab.tsx` - Tab切换调试页面 |
|||
- `src/user/gift/api-test.tsx` - API参数测试页面 |
|||
|
|||
## 测试验证 |
|||
|
|||
### 1. 调试页面测试 |
|||
访问 `/user/gift/debug-tab` 可以: |
|||
- 模拟Tab切换操作 |
|||
- 查看实时的状态变化 |
|||
- 验证数据显示是否正确 |
|||
|
|||
### 2. API参数测试 |
|||
访问 `/user/gift/api-test` 可以: |
|||
- 直接测试API调用 |
|||
- 验证 `status` 参数传递 |
|||
- 检查返回数据的状态一致性 |
|||
|
|||
### 3. 实际功能测试 |
|||
在主页面 `/user/gift/index` 中: |
|||
- 点击不同Tab标签 |
|||
- 查看控制台调试信息 |
|||
- 验证显示的数据状态是否正确 |
|||
|
|||
## 预期效果 |
|||
|
|||
修复后的预期效果: |
|||
|
|||
1. **Tab切换立即生效**: |
|||
- 点击"未使用"显示 `status=0` 的礼品卡 |
|||
- 点击"已使用"显示 `status=1` 的礼品卡 |
|||
- 点击"失效"显示 `status=2` 的礼品卡 |
|||
|
|||
2. **状态显示一致**: |
|||
- Tab标签文本与显示的数据状态一致 |
|||
- 不再出现点击"未使用"显示"已使用"数据的问题 |
|||
|
|||
3. **调试信息清晰**: |
|||
- 控制台输出详细的状态切换信息 |
|||
- 便于排查问题和验证修复效果 |
|||
|
|||
## 回滚方案 |
|||
|
|||
如果修复后出现问题,可以: |
|||
|
|||
1. **恢复原有的 `handleTabChange` 函数** |
|||
2. **保留 `setTimeout` 延迟执行逻辑** |
|||
3. **在 `ShopGiftParam` 中恢复 `useStatus` 字段** |
|||
|
|||
## 注意事项 |
|||
|
|||
1. **后端兼容性**:确保后端API支持 `status` 参数筛选 |
|||
2. **数据一致性**:确保后端返回的数据中 `status` 字段值正确 |
|||
3. **缓存清理**:如有必要,清理相关的缓存数据 |
|||
4. **用户体验**:修复后用户操作应该更加流畅和准确 |
|||
|
|||
## 后续优化 |
|||
|
|||
1. **错误处理**:增强API调用失败时的错误处理 |
|||
2. **加载状态**:优化Tab切换时的加载状态显示 |
|||
3. **性能优化**:考虑添加数据缓存机制 |
|||
4. **用户反馈**:收集用户使用反馈,持续优化体验 |
@ -0,0 +1,4 @@ |
|||
export default definePageConfig({ |
|||
navigationBarTitleText: '门店核销', |
|||
navigationBarTextStyle: 'black' |
|||
}) |
@ -0,0 +1,348 @@ |
|||
import React, { useState } from 'react' |
|||
import { View, Text } from '@tarojs/components' |
|||
import { Button, Input, Tag, Divider } from '@nutui/nutui-react-taro' |
|||
import { Scan, Search } from '@nutui/icons-react-taro' |
|||
import Taro from '@tarojs/taro' |
|||
import dayjs from 'dayjs' |
|||
import {getShopGiftByCode, updateShopGift} from "@/api/shop/shopGift"; |
|||
|
|||
interface VerificationData { |
|||
type: string |
|||
giftId: number |
|||
giftCode: string |
|||
verificationCode: string |
|||
faceValue: string |
|||
timestamp: number |
|||
expireTime?: string |
|||
} |
|||
|
|||
interface GiftCardInfo { |
|||
id: number |
|||
name: string |
|||
goodsName?: string |
|||
faceValue: string |
|||
type: number |
|||
status: number |
|||
expireTime?: string |
|||
code: string |
|||
} |
|||
|
|||
const StoreVerification: React.FC = () => { |
|||
const [scanResult, setScanResult] = useState<string>('') |
|||
const [verificationCode, setVerificationCode] = useState<string>('') |
|||
const [giftInfo, setGiftInfo] = useState<GiftCardInfo | null>(null) |
|||
const [verificationResult, setVerificationResult] = useState<'success' | 'failed' | null>(null) |
|||
const [loading, setLoading] = useState(false) |
|||
|
|||
// 扫码功能
|
|||
const handleScan = () => { |
|||
Taro.scanCode({ |
|||
success: (res) => { |
|||
console.log('扫码结果:', res.result) |
|||
setScanResult(res.result) |
|||
parseQRCode(res.result) |
|||
}, |
|||
fail: (err) => { |
|||
console.error('扫码失败:', err) |
|||
Taro.showToast({ |
|||
title: '扫码失败', |
|||
icon: 'error' |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// 解析二维码数据
|
|||
const parseQRCode = (qrData: string) => { |
|||
try { |
|||
const data: VerificationData = JSON.parse(qrData) |
|||
|
|||
if (data.type === 'gift_card_verification') { |
|||
setVerificationCode(data.verificationCode) |
|||
// 模拟获取礼品卡信息
|
|||
mockGetGiftInfo(data) |
|||
} else { |
|||
throw new Error('无效的二维码格式') |
|||
} |
|||
} catch (error) { |
|||
console.error('解析二维码失败:', error) |
|||
Taro.showToast({ |
|||
title: '无效的二维码', |
|||
icon: 'error' |
|||
}) |
|||
} |
|||
} |
|||
|
|||
// 模拟获取礼品卡信息(实际应该调用API)
|
|||
const mockGetGiftInfo = (data: VerificationData) => { |
|||
// 这里应该调用后端API验证礼品卡信息
|
|||
const mockGiftInfo: GiftCardInfo = { |
|||
id: data.giftId, |
|||
name: '礼品卡', |
|||
goodsName: '星巴克咖啡券', |
|||
faceValue: data.faceValue, |
|||
type: 20, |
|||
status: 0, |
|||
expireTime: data.expireTime, |
|||
code: data.giftCode |
|||
} |
|||
|
|||
setGiftInfo(mockGiftInfo) |
|||
} |
|||
|
|||
// 手动输入核销码验证
|
|||
const handleManualVerification = async () => { |
|||
if (!verificationCode.trim()) { |
|||
Taro.showToast({ |
|||
title: '请输入核销码', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
|
|||
setLoading(true) |
|||
try { |
|||
// 这里应该调用后端API验证核销码
|
|||
const giftCard = await getShopGiftByCode(verificationCode.trim()) |
|||
await updateShopGift({ |
|||
...giftCard, |
|||
status: 1 |
|||
}) |
|||
Taro.showToast({ |
|||
title: '核销成功', |
|||
icon: 'success' |
|||
}) |
|||
} catch (error) { |
|||
console.error('验证失败:', error) |
|||
} finally { |
|||
setLoading(false) |
|||
} |
|||
} |
|||
|
|||
// 模拟验证核销码
|
|||
const mockVerifyCode = async (code: string) => { |
|||
// 模拟API调用延迟
|
|||
await new Promise(resolve => setTimeout(resolve, 1000)) |
|||
|
|||
// 模拟验证逻辑
|
|||
if (code.length === 6 && /^\d+$/.test(code)) { |
|||
setVerificationResult('success') |
|||
setGiftInfo({ |
|||
id: 1, |
|||
name: '礼品卡', |
|||
goodsName: '星巴克咖啡券', |
|||
faceValue: '100', |
|||
type: 20, |
|||
status: 0, |
|||
expireTime: '2024-12-31 23:59:59', |
|||
code: 'SB2024001234567890' |
|||
}) |
|||
Taro.showToast({ |
|||
title: '验证成功', |
|||
icon: 'success' |
|||
}) |
|||
} else { |
|||
setVerificationResult('failed') |
|||
Taro.showToast({ |
|||
title: '核销码无效', |
|||
icon: 'error' |
|||
}) |
|||
} |
|||
} |
|||
|
|||
// 确认核销
|
|||
const handleConfirmVerification = async () => { |
|||
if (!giftInfo) return |
|||
|
|||
setLoading(true) |
|||
try { |
|||
// 这里应该调用后端API完成核销
|
|||
await mockCompleteVerification(giftInfo.id) |
|||
|
|||
Taro.showToast({ |
|||
title: '核销成功', |
|||
icon: 'success' |
|||
}) |
|||
|
|||
// 重置状态
|
|||
setTimeout(() => { |
|||
resetForm() |
|||
}, 2000) |
|||
} catch (error) { |
|||
console.error('核销失败:', error) |
|||
Taro.showToast({ |
|||
title: '核销失败', |
|||
icon: 'error' |
|||
}) |
|||
} finally { |
|||
setLoading(false) |
|||
} |
|||
} |
|||
|
|||
// 模拟完成核销
|
|||
const mockCompleteVerification = async (giftId: number) => { |
|||
await new Promise(resolve => setTimeout(resolve, 1000)) |
|||
console.log('核销礼品卡:', giftId) |
|||
} |
|||
|
|||
// 重置表单
|
|||
const resetForm = () => { |
|||
setScanResult('') |
|||
setVerificationCode('') |
|||
setGiftInfo(null) |
|||
setVerificationResult(null) |
|||
} |
|||
|
|||
// 获取类型文本
|
|||
const getTypeText = (type: number) => { |
|||
switch (type) { |
|||
case 10: return '实物礼品卡' |
|||
case 20: return '虚拟礼品卡' |
|||
case 30: return '服务礼品卡' |
|||
default: return '礼品卡' |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<View className="bg-gray-50 min-h-screen"> |
|||
{/* 页面标题 */} |
|||
<View className="bg-white px-4 py-3 border-b border-gray-100"> |
|||
<Text className="text-lg font-bold text-center"> |
|||
礼品卡核销 |
|||
</Text> |
|||
<Text className="text-sm text-gray-600 text-center mt-1 px-2"> |
|||
门店工作人员核销页面 |
|||
</Text> |
|||
</View> |
|||
|
|||
{/* 扫码区域 */} |
|||
<View className="bg-white mx-4 mt-4 p-4 rounded-lg"> |
|||
<View className={'mb-3'}> |
|||
<Text className="font-bold">扫码核销</Text> |
|||
</View> |
|||
<Button |
|||
size="large" |
|||
type="primary" |
|||
icon={<Scan />} |
|||
onClick={handleScan} |
|||
block |
|||
> |
|||
扫描二维码 |
|||
</Button> |
|||
|
|||
{scanResult && ( |
|||
<View className="mt-3 p-3 bg-gray-50 rounded"> |
|||
<Text className="text-sm text-gray-600">扫码结果:</Text> |
|||
<Text className="text-xs text-gray-500 break-all mt-1"> |
|||
{scanResult} |
|||
</Text> |
|||
</View> |
|||
)} |
|||
</View> |
|||
|
|||
{/* 手动输入区域 */} |
|||
<View className="bg-white mx-4 mt-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-3">手动输入核销码</Text> |
|||
<View className="flex gap-2"> |
|||
<Input |
|||
placeholder="请输入8位核销码" |
|||
value={verificationCode} |
|||
onChange={setVerificationCode} |
|||
maxLength={8} |
|||
className="flex-1" |
|||
/> |
|||
<Button |
|||
type="primary" |
|||
icon={<Search />} |
|||
loading={loading} |
|||
onClick={handleManualVerification} |
|||
> |
|||
核销 |
|||
</Button> |
|||
</View> |
|||
</View> |
|||
|
|||
{/* 礼品卡信息 */} |
|||
{giftInfo && ( |
|||
<View className="bg-white mx-4 mt-4 p-4 rounded-lg"> |
|||
<View className="flex justify-between items-center mb-3"> |
|||
<Text className="font-bold">礼品卡信息</Text> |
|||
{verificationResult === 'success' && ( |
|||
<Tag type="success" size="small"> |
|||
<CheckCircle className="mr-1" /> |
|||
验证成功 |
|||
</Tag> |
|||
)} |
|||
{verificationResult === 'failed' && ( |
|||
<Tag type="danger" size="small"> |
|||
<CloseCircle className="mr-1" /> |
|||
验证失败 |
|||
</Tag> |
|||
)} |
|||
</View> |
|||
|
|||
<View className="space-y-3"> |
|||
<View className="flex justify-between"> |
|||
<Text className="text-gray-600">商品名称</Text> |
|||
<Text className="font-medium"> |
|||
{giftInfo.goodsName || giftInfo.name} |
|||
</Text> |
|||
</View> |
|||
|
|||
<View className="flex justify-between"> |
|||
<Text className="text-gray-600">面值</Text> |
|||
<Text className="text-lg font-bold text-red-500"> |
|||
¥{giftInfo.faceValue} |
|||
</Text> |
|||
</View> |
|||
|
|||
<View className="flex justify-between"> |
|||
<Text className="text-gray-600">类型</Text> |
|||
<Text>{getTypeText(giftInfo.type)}</Text> |
|||
</View> |
|||
|
|||
<View className="flex justify-between"> |
|||
<Text className="text-gray-600">兑换码</Text> |
|||
<Text className="font-mono text-sm">{giftInfo.code}</Text> |
|||
</View> |
|||
|
|||
{giftInfo.expireTime && ( |
|||
<View className="flex justify-between"> |
|||
<Text className="text-gray-600">有效期至</Text> |
|||
<Text className="text-orange-600"> |
|||
{dayjs(giftInfo.expireTime).format('YYYY-MM-DD')} |
|||
</Text> |
|||
</View> |
|||
)} |
|||
</View> |
|||
|
|||
<Divider /> |
|||
|
|||
{verificationResult === 'success' && ( |
|||
<Button |
|||
size="large" |
|||
type="primary" |
|||
loading={loading} |
|||
onClick={handleConfirmVerification} |
|||
block |
|||
> |
|||
确认核销 |
|||
</Button> |
|||
)} |
|||
</View> |
|||
)} |
|||
|
|||
{/* 使用说明 */} |
|||
<View className="bg-blue-50 mx-4 mb-4 p-4 rounded-lg"> |
|||
<Text className="font-bold mb-2 text-gray-500">操作说明:</Text> |
|||
<View className="space-y-1"> |
|||
<Text className="text-sm text-gray-500">1. 用户出示礼品卡二维码</Text> |
|||
<Text className="text-sm text-gray-500 ml-2">2. 点击"扫描二维码"进行扫码</Text> |
|||
<Text className="text-sm text-gray-500 ml-2">3. 或手动输入用户提供的核销码</Text> |
|||
</View> |
|||
</View> |
|||
</View> |
|||
) |
|||
} |
|||
|
|||
export default StoreVerification |
Loading…
Reference in new issue