feat(礼品卡): 优化颜色主题并添加核销功能

- 修改礼品卡颜色主题,使用渐变色提升视觉效果
- 添加礼品卡核销功能,包括生成和验证核销码
-优化礼品卡组件,增加状态显示和使用说明
- 新增礼品卡颜色测试页面,用于验证颜色
This commit is contained in:
2025-08-17 10:01:56 +08:00
parent ecb5d9059a
commit ecfbdc0286
22 changed files with 2821 additions and 116 deletions

View File

@@ -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