377 lines
12 KiB
TypeScript
377 lines
12 KiB
TypeScript
import React, {useState} from 'react'
|
||
import {View, Text, Image} from '@tarojs/components'
|
||
import {Button, Input} 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, decryptQrData} from "@/api/shop/shopGift";
|
||
import {useUser} from "@/hooks/useUser";
|
||
import type {ShopGift} from "@/api/shop/shopGift/model";
|
||
import {isValidJSON} from "@/utils/jsonUtils";
|
||
|
||
const StoreVerification: React.FC = () => {
|
||
const {
|
||
isAdmin
|
||
} = useUser();
|
||
const [scanResult, setScanResult] = useState<string>('')
|
||
const [verificationCode, setVerificationCode] = useState<string>('')
|
||
const [giftInfo, setGiftInfo] = useState<ShopGift | null>(null)
|
||
const [loading, setLoading] = useState(false)
|
||
|
||
// 扫码功能
|
||
const handleScan = () => {
|
||
Taro.scanCode({
|
||
success: (res) => {
|
||
if (res.result) {
|
||
console.log('扫码结果:', res.result)
|
||
|
||
// 判断是否为JSON格式
|
||
if (isValidJSON(res.result)) {
|
||
setLoading(true)
|
||
const json = JSON.parse(res.result)
|
||
console.log(json, 'json')
|
||
if (json.businessType === 'gift') {
|
||
// 调用解密接口
|
||
handleDecryptAndVerify(json.token, json.data).then()
|
||
}
|
||
}
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
console.error('扫码失败:', err)
|
||
Taro.showToast({
|
||
title: '扫码失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
// 调用解密接口
|
||
const handleDecryptAndVerify = async (token: string, encryptedData: string) => {
|
||
decryptQrData({token, encryptedData}).then(res => {
|
||
const decryptedData = res;
|
||
console.log('解密结果:', decryptedData)
|
||
console.log('解密成功:', decryptedData)
|
||
setScanResult(`${decryptedData}`)
|
||
setVerificationCode(`${decryptedData}`)
|
||
handleVerification(`${decryptedData}`)
|
||
}).catch(() => {
|
||
console.error('解密失败:')
|
||
Taro.showToast({
|
||
title: `token失效,请刷新二维码重试`,
|
||
icon: 'none'
|
||
})
|
||
}).finally(() => {
|
||
setLoading(false)
|
||
})
|
||
}
|
||
|
||
// 验证商品信息
|
||
const handleVerification = async (code?: string) => {
|
||
setGiftInfo(null)
|
||
setVerificationCode(`${code}`)
|
||
// 这里应该调用后端API验证核销码
|
||
const gift = await getShopGiftByCode(`${code}`)
|
||
if(gift){
|
||
// 设置礼品信息用于显示
|
||
setGiftInfo(gift)
|
||
}
|
||
|
||
}
|
||
|
||
// 手动输入核销码验证
|
||
const handleManualVerification = async (code?: string) => {
|
||
const codeToVerify = code || verificationCode.trim()
|
||
if (!codeToVerify) {
|
||
return false;
|
||
}
|
||
setLoading(true)
|
||
try {
|
||
// 这里应该调用后端API验证核销码
|
||
const gift = await getShopGiftByCode(codeToVerify)
|
||
|
||
// 设置礼品信息用于显示
|
||
setGiftInfo(gift)
|
||
|
||
if (!isAdmin()) {
|
||
setLoading(false)
|
||
return Taro.showToast({
|
||
title: '您没有核销权限',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
if (gift.status === 1) {
|
||
setLoading(false)
|
||
return Taro.showToast({
|
||
title: '此礼品码已使用',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
if (gift.status === 2) {
|
||
setLoading(false)
|
||
return Taro.showToast({
|
||
title: '此礼品码已失效',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
if (gift.userId === 0) {
|
||
setLoading(false)
|
||
return Taro.showToast({
|
||
title: '此礼品码未认领',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
|
||
// 验证成功,设置状态
|
||
await updateShopGift({
|
||
...gift,
|
||
status: 1,
|
||
operatorUserId: Number(Taro.getStorageSync('UserId')) || 0,
|
||
takeTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||
verificationTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||
})
|
||
Taro.showToast({
|
||
title: '核销成功',
|
||
icon: 'success'
|
||
})
|
||
// 重置状态
|
||
setTimeout(() => {
|
||
resetForm()
|
||
}, 2000)
|
||
} catch (error) {
|
||
console.error('验证失败:', error)
|
||
Taro.showToast({
|
||
title: '验证失败',
|
||
icon: 'error'
|
||
})
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
// 重置表单
|
||
const resetForm = () => {
|
||
setScanResult('')
|
||
setVerificationCode('')
|
||
setGiftInfo(null)
|
||
}
|
||
|
||
// 获取类型文本
|
||
const getTypeText = (type: number) => {
|
||
switch (type) {
|
||
case 10:
|
||
return '实物礼品卡'
|
||
case 20:
|
||
return '虚拟礼品卡'
|
||
case 30:
|
||
return '服务礼品卡'
|
||
default:
|
||
return '礼品卡'
|
||
}
|
||
}
|
||
|
||
// useEffect(() => {
|
||
// handleManualVerification().then()
|
||
// },[verificationCode])
|
||
|
||
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>
|
||
|
||
|
||
{/* 手动输入区域 */}
|
||
<View className="mt-8"></View>
|
||
<View className="font-bold mb-3">手动输入核销码</View>
|
||
<View className="flex items-center justify-between">
|
||
<Input
|
||
placeholder="请输入6位核销码"
|
||
value={verificationCode}
|
||
onChange={setVerificationCode}
|
||
maxLength={6}
|
||
className="flex-1 mr-8"
|
||
style={{
|
||
backgroundColor: '#f3f3f3',
|
||
borderRadius: '8px'
|
||
}}
|
||
/>
|
||
<Button
|
||
type="primary"
|
||
icon={<Search/>}
|
||
loading={loading}
|
||
onClick={() => handleVerification(verificationCode)}
|
||
>
|
||
验证
|
||
</Button>
|
||
</View>
|
||
</View>
|
||
|
||
|
||
{/*在扫码结果显示区域添加解密状态提示*/}
|
||
{scanResult && !giftInfo && (
|
||
<View className="mt-4 p-4 bg-gray-50 rounded-lg">
|
||
<Text className="text-sm text-gray-600">
|
||
{loading ? '正在解密验证...' : '扫码结果:'}
|
||
</Text>
|
||
<Text className="text-xs text-gray-500 break-all mt-1">
|
||
{scanResult}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
|
||
{/* 商品信息展示 */}
|
||
{giftInfo && (
|
||
<View className="mt-4 mx-4 p-4 bg-white rounded-lg shadow-sm border border-gray-100">
|
||
<View className="flex items-center mb-3">
|
||
<Text className="text-lg font-semibold text-gray-800">商品信息</Text>
|
||
<View className={`ml-2 px-2 py-1 rounded text-xs ${
|
||
giftInfo.status === 0 ? 'bg-green-100 text-green-600' :
|
||
giftInfo.status === 1 ? 'bg-gray-100 text-gray-600' :
|
||
'bg-red-100 text-red-600'
|
||
}`}>
|
||
{giftInfo.status === 0 ? '未使用' :
|
||
giftInfo.status === 1 ? '已使用' : '已过期'}
|
||
</View>
|
||
</View>
|
||
|
||
<View className="mt-3 pt-3 border-t border-gray-100 flex justify-between">
|
||
{/* 商品图片 */}
|
||
{giftInfo.goodsImage && (
|
||
<View className="w-20 h-20 mr-3 flex-shrink-0">
|
||
<Image
|
||
src={giftInfo.goodsImage}
|
||
className="w-full h-full rounded-lg object-cover border border-gray-200"
|
||
mode="aspectFill"
|
||
/>
|
||
</View>
|
||
)}
|
||
|
||
{/* 商品详情 */}
|
||
<View className="flex-1">
|
||
<View className="text-base font-medium text-gray-900 mb-1">
|
||
{giftInfo.goodsName || giftInfo.name}
|
||
</View>
|
||
<View className="text-sm text-gray-400">
|
||
使用门店:{giftInfo.useLocation || '123123'}
|
||
</View>
|
||
{giftInfo.description && (
|
||
<>
|
||
<View className="text-sm text-gray-600 mb-2" style={{
|
||
overflow: 'hidden'
|
||
// 注意:小程序不支持 WebKit 文本截断属性
|
||
}}>
|
||
{giftInfo.description}
|
||
</View>
|
||
</>
|
||
)}
|
||
|
||
<View className="flex items-center justify-between">
|
||
<Text className="text-lg font-bold text-red-500">
|
||
¥{giftInfo.faceValue}
|
||
</Text>
|
||
<Text className="text-xs text-gray-500">
|
||
{giftInfo.type === 10 ? '实物礼品' :
|
||
giftInfo.type === 20 ? '虚拟礼品' : '服务礼品'}
|
||
</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 附加信息 */}
|
||
<View className="mt-3 pt-3 border-t border-gray-100">
|
||
|
||
{giftInfo.expireTime && (
|
||
<View className="flex items-center mb-2">
|
||
<Text className="text-sm text-gray-600">有效期至:</Text>
|
||
<Text className="text-sm text-gray-800">
|
||
{dayjs(giftInfo.expireTime).format('YYYY-MM-DD HH:mm')}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
|
||
{giftInfo.contactInfo && (
|
||
<View className="flex items-center">
|
||
<Text className="text-sm text-gray-600">客服电话:</Text>
|
||
<Text className="text-sm text-blue-600">{giftInfo.contactInfo}</Text>
|
||
</View>
|
||
)}
|
||
|
||
<View className="flex justify-between mb-3">
|
||
<Text className="text-gray-600">类型</Text>
|
||
<Text>{getTypeText(giftInfo.type as number)}</Text>
|
||
</View>
|
||
|
||
<View className="flex justify-between mb-3">
|
||
<Text className="text-gray-600">兑换码</Text>
|
||
<Text className="font-mono text-sm">{giftInfo.code}</Text>
|
||
</View>
|
||
|
||
{giftInfo.operatorUserName && (
|
||
<View className="flex justify-between mb-3">
|
||
<Text className="text-gray-600">核销人员</Text>
|
||
<Text className="font-mono text-sm">{giftInfo.operatorUserName}</Text>
|
||
</View>
|
||
)}
|
||
|
||
{giftInfo.takeTime && (
|
||
<View className="flex justify-between mb-3">
|
||
<Text className="text-gray-600">核销时间</Text>
|
||
<Text className="font-mono text-sm">{giftInfo.takeTime}</Text>
|
||
</View>
|
||
)}
|
||
|
||
{giftInfo && giftInfo.status === 0 && (
|
||
<Button
|
||
size="large"
|
||
type="info"
|
||
loading={loading}
|
||
onClick={() => handleManualVerification()}
|
||
block
|
||
>
|
||
确认核销
|
||
</Button>
|
||
)}
|
||
</View>
|
||
</View>
|
||
)}
|
||
|
||
{/* 使用说明 */}
|
||
<View className="bg-blue-50 mx-4 my-4 p-4 rounded-lg">
|
||
<Text className="font-bold mb-2 text-gray-500">操作说明:</Text>
|
||
<View>
|
||
<Text className="text-sm text-gray-500 mb-1">1. 用户出示礼品卡二维码</Text>
|
||
<Text className="text-sm text-gray-500 mb-1">2. 点击"扫描二维码"进行扫码</Text>
|
||
<Text className="text-sm text-gray-500 mb-1">3. 或手动输入用户提供的核销码</Text>
|
||
<Text className="text-sm text-gray-500">4. 验证成功后点击"确认核销"完成</Text>
|
||
</View>
|
||
</View>
|
||
<View className={'h-10'}></View>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
export default StoreVerification
|