forked from gxwebsoft/mp-10550
feat(user): 优化礼品卡核销功能
- 修改 API基础 URL 为正式环境地址 - 移除礼品卡信息中的 useTime 字段 -优化礼品卡核销页面布局和功能 - 完善礼品卡信息展示,包括商品图片、描述等 - 优化手动输入核销码流程,支持直接验证- 调整礼品卡状态展示方式,增加过期状态- 优化代码结构,提高可读性和可维护性
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
export const ENV_CONFIG = {
|
export const ENV_CONFIG = {
|
||||||
// 开发环境
|
// 开发环境
|
||||||
development: {
|
development: {
|
||||||
API_BASE_URL: 'http://127.0.0.1:9200/api',
|
API_BASE_URL: 'https://cms-api.websoft.top/api',
|
||||||
APP_NAME: '开发环境',
|
APP_NAME: '开发环境',
|
||||||
DEBUG: 'true',
|
DEBUG: 'true',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ export interface ShopGift {
|
|||||||
type?: number;
|
type?: number;
|
||||||
// 领取时间
|
// 领取时间
|
||||||
takeTime?: string;
|
takeTime?: string;
|
||||||
// 使用时间
|
|
||||||
useTime?: string;
|
|
||||||
// 过期时间
|
// 过期时间
|
||||||
expireTime?: string;
|
expireTime?: string;
|
||||||
// 有效期天数
|
// 有效期天数
|
||||||
|
|||||||
@@ -1,22 +1,12 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { View, Text } from '@tarojs/components'
|
import {View, Text, Image} from '@tarojs/components'
|
||||||
import { Button, Input, Tag, Divider } from '@nutui/nutui-react-taro'
|
import {Button, Input, Tag, Divider} from '@nutui/nutui-react-taro'
|
||||||
import { Scan, Search } from '@nutui/icons-react-taro'
|
import {Scan, Search} from '@nutui/icons-react-taro'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import {getShopGiftByCode, updateShopGift} from "@/api/shop/shopGift";
|
import {getShopGiftByCode, updateShopGift} from "@/api/shop/shopGift";
|
||||||
import {useUser} from "@/hooks/useUser";
|
import {useUser} from "@/hooks/useUser";
|
||||||
|
import type { ShopGift } from "@/api/shop/shopGift/model";
|
||||||
interface GiftCardInfo {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
goodsName?: string
|
|
||||||
faceValue: string
|
|
||||||
type: number
|
|
||||||
status: number
|
|
||||||
expireTime?: string
|
|
||||||
code: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const StoreVerification: React.FC = () => {
|
const StoreVerification: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
@@ -24,7 +14,7 @@ const StoreVerification: React.FC = () => {
|
|||||||
} = useUser();
|
} = useUser();
|
||||||
const [scanResult, setScanResult] = useState<string>('')
|
const [scanResult, setScanResult] = useState<string>('')
|
||||||
const [verificationCode, setVerificationCode] = useState<string>('')
|
const [verificationCode, setVerificationCode] = useState<string>('')
|
||||||
const [giftInfo, setGiftInfo] = useState<GiftCardInfo | null>(null)
|
const [giftInfo, setGiftInfo] = useState<ShopGift | null>(null)
|
||||||
const [verificationResult, setVerificationResult] = useState<'success' | 'failed' | null>(null)
|
const [verificationResult, setVerificationResult] = useState<'success' | 'failed' | null>(null)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
@@ -32,9 +22,11 @@ const StoreVerification: React.FC = () => {
|
|||||||
const handleScan = () => {
|
const handleScan = () => {
|
||||||
Taro.scanCode({
|
Taro.scanCode({
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
|
if (res.result) {
|
||||||
setScanResult(res.result)
|
setScanResult(res.result)
|
||||||
setVerificationCode(res.result)
|
setVerificationCode(res.result)
|
||||||
handleManualVerification().then()
|
handleManualVerification(res.result)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
console.error('扫码失败:', err)
|
console.error('扫码失败:', err)
|
||||||
@@ -45,83 +37,78 @@ const StoreVerification: React.FC = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 手动输入核销码验证
|
// 手动输入核销码验证
|
||||||
const handleManualVerification = async () => {
|
const handleManualVerification = async (code?: string) => {
|
||||||
|
const codeToVerify = code || verificationCode.trim()
|
||||||
|
if (!codeToVerify) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
// 这里应该调用后端API验证核销码
|
// 这里应该调用后端API验证核销码
|
||||||
const gift = await getShopGiftByCode(verificationCode.trim())
|
const gift = await getShopGiftByCode(codeToVerify)
|
||||||
if(!isAdmin()){
|
|
||||||
|
// 设置礼品信息用于显示
|
||||||
|
setGiftInfo(gift)
|
||||||
|
|
||||||
|
if (!isAdmin()) {
|
||||||
|
setVerificationResult('failed')
|
||||||
|
setLoading(false)
|
||||||
return Taro.showToast({
|
return Taro.showToast({
|
||||||
title: '您没有核销权限',
|
title: '您没有核销权限',
|
||||||
icon: 'error'
|
icon: 'error'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if(gift.status == 1){
|
if (gift.status === 1) {
|
||||||
|
setVerificationResult('failed')
|
||||||
|
setLoading(false)
|
||||||
return Taro.showToast({
|
return Taro.showToast({
|
||||||
title: '此礼品码已使用',
|
title: '此礼品码已使用',
|
||||||
icon: 'error'
|
icon: 'error'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if(gift.status == 2){
|
if (gift.status === 2) {
|
||||||
|
setVerificationResult('failed')
|
||||||
|
setLoading(false)
|
||||||
return Taro.showToast({
|
return Taro.showToast({
|
||||||
title: '此礼品码已失效',
|
title: '此礼品码已失效',
|
||||||
icon: 'error'
|
icon: 'error'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if(gift.userId == 0){
|
if (gift.userId === 0) {
|
||||||
|
setVerificationResult('failed')
|
||||||
|
setLoading(false)
|
||||||
return Taro.showToast({
|
return Taro.showToast({
|
||||||
title: '此礼品码未认领',
|
title: '此礼品码未认领',
|
||||||
icon: 'error'
|
icon: 'error'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (!verificationCode.trim()) {
|
|
||||||
Taro.showToast({
|
// 验证成功,设置状态
|
||||||
title: '请输入核销码',
|
setVerificationResult('success')
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await updateShopGift({
|
await updateShopGift({
|
||||||
...gift,
|
...gift,
|
||||||
status: 1,
|
status: 1,
|
||||||
operatorUserId: Taro.getStorageSync('UserId'),
|
operatorUserId: Number(Taro.getStorageSync('UserId')) || 0,
|
||||||
takeTime: dayjs.unix(Date.now() / 1000).format('YYYY-MM-DD HH:mm:ss'),
|
takeTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
verificationTime: dayjs.unix(Date.now() / 1000).format('YYYY-MM-DD HH:mm:ss')
|
verificationTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
})
|
})
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '核销成功',
|
title: '核销成功',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
})
|
})
|
||||||
} catch (error) {
|
|
||||||
console.error('验证失败:', error)
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认核销
|
|
||||||
const handleConfirmVerification = async () => {
|
|
||||||
if (!giftInfo) return
|
|
||||||
|
|
||||||
setLoading(true)
|
|
||||||
try {
|
|
||||||
// 这里应该调用后端API完成核销
|
|
||||||
await mockCompleteVerification(giftInfo.id)
|
|
||||||
|
|
||||||
Taro.showToast({
|
|
||||||
title: '核销成功',
|
|
||||||
icon: 'success'
|
|
||||||
})
|
|
||||||
|
|
||||||
// 重置状态
|
// 重置状态
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resetForm()
|
resetForm()
|
||||||
}, 2000)
|
}, 2000)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('核销失败:', error)
|
console.error('验证失败:', error)
|
||||||
|
setVerificationResult('failed')
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '核销失败',
|
title: '验证失败',
|
||||||
icon: 'error'
|
icon: 'error'
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
@@ -129,12 +116,6 @@ const StoreVerification: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟完成核销
|
|
||||||
const mockCompleteVerification = async (giftId: number) => {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
||||||
console.log('核销礼品卡:', giftId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置表单
|
// 重置表单
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setScanResult('')
|
setScanResult('')
|
||||||
@@ -146,13 +127,21 @@ const StoreVerification: React.FC = () => {
|
|||||||
// 获取类型文本
|
// 获取类型文本
|
||||||
const getTypeText = (type: number) => {
|
const getTypeText = (type: number) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 10: return '实物礼品卡'
|
case 10:
|
||||||
case 20: return '虚拟礼品卡'
|
return '实物礼品卡'
|
||||||
case 30: return '服务礼品卡'
|
case 20:
|
||||||
default: return '礼品卡'
|
return '虚拟礼品卡'
|
||||||
|
case 30:
|
||||||
|
return '服务礼品卡'
|
||||||
|
default:
|
||||||
|
return '礼品卡'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// handleManualVerification().then()
|
||||||
|
// },[verificationCode])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="bg-gray-50 min-h-screen">
|
<View className="bg-gray-50 min-h-screen">
|
||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
@@ -173,14 +162,15 @@ const StoreVerification: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
size="large"
|
size="large"
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<Scan />}
|
icon={<Scan/>}
|
||||||
onClick={handleScan}
|
onClick={handleScan}
|
||||||
block
|
block
|
||||||
>
|
>
|
||||||
扫描二维码
|
扫描二维码
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{scanResult && (
|
{/* 扫码结果显示 */}
|
||||||
|
{scanResult && !giftInfo && (
|
||||||
<View className="mt-3 p-3 bg-gray-50 rounded">
|
<View className="mt-3 p-3 bg-gray-50 rounded">
|
||||||
<Text className="text-sm text-gray-600">扫码结果:</Text>
|
<Text className="text-sm text-gray-600">扫码结果:</Text>
|
||||||
<Text className="text-xs text-gray-500 break-all mt-1">
|
<Text className="text-xs text-gray-500 break-all mt-1">
|
||||||
@@ -188,9 +178,95 @@ const StoreVerification: React.FC = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 商品信息展示 */}
|
||||||
|
{giftInfo && (
|
||||||
|
<View className="mt-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="flex">
|
||||||
|
{/* 商品图片 */}
|
||||||
|
{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">
|
||||||
|
<Text className="text-base font-medium text-gray-900 mb-1">
|
||||||
|
{giftInfo.goodsName || giftInfo.name}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{giftInfo.description && (
|
||||||
|
<Text className="text-sm text-gray-600 mb-2" style={{
|
||||||
|
display: '-webkit-box',
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}>
|
||||||
|
{giftInfo.description}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<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.useLocation && (
|
||||||
|
<View className="flex items-center mb-2">
|
||||||
|
<Text className="text-sm text-gray-600 w-16">使用门店:</Text>
|
||||||
|
<Text className="text-sm text-gray-800 flex-1">{giftInfo.useLocation}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{giftInfo.expireTime && (
|
||||||
|
<View className="flex items-center mb-2">
|
||||||
|
<Text className="text-sm text-gray-600 w-16">有效期至:</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 w-16">客服电话:</Text>
|
||||||
|
<Text className="text-sm text-blue-600">{giftInfo.contactInfo}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 手动输入区域 */}
|
{/* 手动输入区域 */}
|
||||||
|
{
|
||||||
|
!giftInfo && (
|
||||||
<View className="bg-white mx-4 mt-4 p-4 rounded-lg">
|
<View className="bg-white mx-4 mt-4 p-4 rounded-lg">
|
||||||
<Text className="font-bold mb-3">手动输入核销码</Text>
|
<Text className="font-bold mb-3">手动输入核销码</Text>
|
||||||
<View className="flex">
|
<View className="flex">
|
||||||
@@ -203,14 +279,17 @@ const StoreVerification: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<Search />}
|
icon={<Search/>}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onClick={handleManualVerification}
|
onClick={() => handleManualVerification(verificationCode)}
|
||||||
>
|
>
|
||||||
验证
|
验证
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{/* 礼品卡信息 */}
|
{/* 礼品卡信息 */}
|
||||||
{giftInfo && (
|
{giftInfo && (
|
||||||
@@ -246,7 +325,7 @@ const StoreVerification: React.FC = () => {
|
|||||||
|
|
||||||
<View className="flex justify-between mb-3">
|
<View className="flex justify-between mb-3">
|
||||||
<Text className="text-gray-600">类型</Text>
|
<Text className="text-gray-600">类型</Text>
|
||||||
<Text>{getTypeText(giftInfo.type)}</Text>
|
<Text>{getTypeText(giftInfo.type as number)}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className="flex justify-between mb-3">
|
<View className="flex justify-between mb-3">
|
||||||
@@ -264,14 +343,14 @@ const StoreVerification: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Divider />
|
<Divider/>
|
||||||
|
|
||||||
{verificationResult === 'success' && (
|
{giftInfo && (
|
||||||
<Button
|
<Button
|
||||||
size="large"
|
size="large"
|
||||||
type="primary"
|
type="primary"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onClick={handleConfirmVerification}
|
onClick={() => handleManualVerification()}
|
||||||
block
|
block
|
||||||
>
|
>
|
||||||
确认核销
|
确认核销
|
||||||
|
|||||||
Reference in New Issue
Block a user