forked from gxwebsoft/mp-10550
feat(礼品卡): 优化颜色主题并添加核销功能
- 修改礼品卡颜色主题,使用渐变色提升视觉效果 - 添加礼品卡核销功能,包括生成和验证核销码 -优化礼品卡组件,增加状态显示和使用说明 - 新增礼品卡颜色测试页面,用于验证颜色
This commit is contained in:
@@ -113,6 +113,22 @@ export async function getShopGift(id: number) {
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code查询礼品卡
|
||||
* @param code
|
||||
*/
|
||||
export async function getShopGiftByCode(code: string) {
|
||||
const res = await request.get<ApiResult<ShopGift>>(
|
||||
'/shop/shop-gift/by-code/' + code
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 兑换礼品卡
|
||||
*/
|
||||
@@ -178,3 +194,52 @@ export async function exportShopGift(ids?: number[]) {
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成礼品卡核销码
|
||||
*/
|
||||
export async function generateVerificationCode(giftId: number) {
|
||||
const res = await request.post<ApiResult<{ verificationCode: string; expireTime: string }>>(
|
||||
'/shop/shop-gift/generate-verification-code',
|
||||
{ giftId }
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证核销码
|
||||
*/
|
||||
export async function verifyGiftCard(params: { verificationCode?: string; giftCode?: string }) {
|
||||
const res = await request.post<ApiResult<ShopGift>>(
|
||||
'/shop/shop-gift/verify',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成礼品卡核销
|
||||
*/
|
||||
export async function completeVerification(params: {
|
||||
giftId: number;
|
||||
verificationCode: string;
|
||||
storeId?: number;
|
||||
storeName?: string;
|
||||
operatorId?: number;
|
||||
operatorName?: string;
|
||||
}) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-gift/complete-verification',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
@@ -36,8 +36,6 @@ export interface ShopGift {
|
||||
isShow?: string;
|
||||
// 状态 (0未使用 1已使用 2已过期 3已失效)
|
||||
status?: number;
|
||||
// 使用状态 (0可用 1已使用 2已过期)
|
||||
useStatus?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 使用说明
|
||||
@@ -74,12 +72,11 @@ export interface ShopGift {
|
||||
export interface ShopGiftParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
code?: string;
|
||||
// 礼品卡类型筛选
|
||||
type?: number;
|
||||
// 状态筛选
|
||||
// 状态筛选 (0未使用 1已使用 2失效)
|
||||
status?: number;
|
||||
// 使用状态筛选
|
||||
useStatus?: number;
|
||||
// 用户ID筛选
|
||||
userId?: number;
|
||||
// 商品ID筛选
|
||||
|
||||
@@ -42,7 +42,9 @@ export default defineAppConfig({
|
||||
"points/points",
|
||||
"gift/index",
|
||||
"gift/redeem",
|
||||
"gift/detail"
|
||||
"gift/detail",
|
||||
"gift/qrcode-demo",
|
||||
"store/verification"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -20,21 +20,22 @@
|
||||
// 主题色彩
|
||||
&.gift-card-gold {
|
||||
.gift-card-header {
|
||||
background: #ffd700;
|
||||
background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
|
||||
}
|
||||
.use-btn {
|
||||
background: #ffd700;
|
||||
background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
|
||||
border: none;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&.gift-card-silver {
|
||||
.gift-card-header {
|
||||
background: #c0c0c0;
|
||||
background: linear-gradient(135deg, #e8e8e8 0%, #d0d0d0 100%);
|
||||
}
|
||||
.use-btn {
|
||||
background: #c0c0c0;
|
||||
background: linear-gradient(135deg, #e8e8e8 0%, #d0d0d0 100%);
|
||||
border: none;
|
||||
color: #333;
|
||||
}
|
||||
@@ -42,45 +43,49 @@
|
||||
|
||||
&.gift-card-bronze {
|
||||
.gift-card-header {
|
||||
background: #cd7f32;
|
||||
background: linear-gradient(135deg, #cd7f32 0%, #b8722c 100%);
|
||||
}
|
||||
.use-btn {
|
||||
background: #cd7f32;
|
||||
background: linear-gradient(135deg, #cd7f32 0%, #b8722c 100%);
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&.gift-card-blue {
|
||||
.gift-card-header {
|
||||
background: #4a90e2;
|
||||
background: linear-gradient(135deg, #4a90e2 0%, #357abd 100%);
|
||||
}
|
||||
.use-btn {
|
||||
background: #4a90e2;
|
||||
background: linear-gradient(135deg, #4a90e2 0%, #357abd 100%);
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&.gift-card-green {
|
||||
.gift-card-header {
|
||||
background: #5cb85c;
|
||||
background: linear-gradient(135deg, #5cb85c 0%, #449d44 100%);
|
||||
}
|
||||
.use-btn {
|
||||
background: #5cb85c;
|
||||
background: linear-gradient(135deg, #5cb85c 0%, #449d44 100%);
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&.gift-card-purple {
|
||||
.gift-card-header {
|
||||
background: #9b59b6;
|
||||
background: linear-gradient(135deg, #9b59b6 0%, #8e44ad 100%);
|
||||
}
|
||||
.use-btn {
|
||||
background: #9b59b6;
|
||||
background: linear-gradient(135deg, #9b59b6 0%, #8e44ad 100%);
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,7 +524,6 @@
|
||||
color: #fff;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React, { useState } from 'react'
|
||||
import { View, Text, Image, Swiper, SwiperItem } from '@tarojs/components'
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import { Button, Tag, Rate } from '@nutui/nutui-react-taro'
|
||||
import { Gift, Clock, Location, Phone, Star, Eye, ShoppingCart, Tips } from '@nutui/icons-react-taro'
|
||||
import { Gift, Clock, Location, Phone, ShoppingCart, Tips, QrCode } from '@nutui/icons-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import dayjs from 'dayjs'
|
||||
import GiftCardQRCode from './GiftCardQRCode'
|
||||
import './GiftCard.scss'
|
||||
|
||||
export interface GiftCardProps {
|
||||
@@ -26,8 +28,8 @@ export interface GiftCardProps {
|
||||
originalPrice?: string
|
||||
/** 礼品卡类型:10-实物礼品卡 20-虚拟礼品卡 30-服务礼品卡 */
|
||||
type?: number
|
||||
/** 使用状态:0-可用 1-已使用 2-已过期 */
|
||||
useStatus?: number
|
||||
/** 状态:0-未使用 1-已使用 2-失效 */
|
||||
status?: number
|
||||
/** 过期时间 */
|
||||
expireTime?: string
|
||||
/** 使用时间 */
|
||||
@@ -99,7 +101,7 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
faceValue,
|
||||
originalPrice,
|
||||
type = 10,
|
||||
useStatus = 0,
|
||||
status = 0,
|
||||
expireTime,
|
||||
useTime,
|
||||
useLocation,
|
||||
@@ -116,6 +118,7 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
onClick
|
||||
}) => {
|
||||
const [currentImageIndex, setCurrentImageIndex] = useState(0)
|
||||
const [showQRCode, setShowQRCode] = useState(false)
|
||||
|
||||
// 获取显示名称,优先使用商品名称
|
||||
const displayName = goodsName || name
|
||||
@@ -129,12 +132,12 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
// 获取使用状态信息
|
||||
// 获取状态信息
|
||||
const getStatusInfo = () => {
|
||||
switch (useStatus) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return {
|
||||
text: '可使用',
|
||||
text: '未使用',
|
||||
color: 'success' as const,
|
||||
bgColor: 'bg-green-100',
|
||||
textColor: 'text-green-600'
|
||||
@@ -148,7 +151,7 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
}
|
||||
case 2:
|
||||
return {
|
||||
text: '已过期',
|
||||
text: '失效',
|
||||
color: 'danger' as const,
|
||||
bgColor: 'bg-red-100',
|
||||
textColor: 'text-red-600'
|
||||
@@ -220,7 +223,7 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
|
||||
return (
|
||||
<View
|
||||
className={`gift-card ${getThemeClass()} ${useStatus !== 0 ? 'disabled' : ''}`}
|
||||
className={`gift-card ${getThemeClass()} ${status !== 0 ? 'disabled' : ''}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{/* 卡片头部 */}
|
||||
@@ -230,6 +233,7 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
</View>
|
||||
<View className="gift-card-title">
|
||||
<Text className="title-text">{getTypeText()}</Text>
|
||||
<Text className="type-text">{name}</Text>
|
||||
</View>
|
||||
<View className="gift-card-status">
|
||||
<Tag type={statusInfo.color}>{statusInfo.text}</Tag>
|
||||
@@ -343,14 +347,14 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
|
||||
{/* 时间信息 */}
|
||||
<View className="gift-time-info">
|
||||
{useStatus === 1 && useTime && (
|
||||
{status === 1 && useTime && (
|
||||
<View className="time-item">
|
||||
<Clock size="14" className="text-gray-400" />
|
||||
<Text className="time-text">使用时间:{dayjs(useTime).format('YYYY-MM-DD HH:mm')}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{useStatus === 0 && expireTime && (
|
||||
{status === 0 && expireTime && (
|
||||
<View className="time-item">
|
||||
<Clock size="14" className="text-orange-500" />
|
||||
<Text className="time-text">{formatExpireTime()}</Text>
|
||||
@@ -378,14 +382,15 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
</View>
|
||||
|
||||
<View className="footer-actions">
|
||||
{showUseBtn && useStatus === 0 && (
|
||||
{showUseBtn && status === 0 && (
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
icon={<QrCode />}
|
||||
className={`use-btn ${getThemeClass()}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onUse?.()
|
||||
setShowQRCode(true)
|
||||
}}
|
||||
>
|
||||
立即使用
|
||||
@@ -395,13 +400,30 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
</View>
|
||||
|
||||
{/* 状态遮罩 */}
|
||||
{useStatus !== 0 && (
|
||||
{status !== 0 && (
|
||||
<View className="gift-card-overlay">
|
||||
<View className="overlay-badge">
|
||||
{statusInfo.text}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 二维码核销弹窗 */}
|
||||
<GiftCardQRCode
|
||||
visible={showQRCode}
|
||||
onClose={() => setShowQRCode(false)}
|
||||
giftCard={{
|
||||
id,
|
||||
name,
|
||||
goodsName,
|
||||
code,
|
||||
faceValue,
|
||||
type,
|
||||
status,
|
||||
expireTime,
|
||||
contactInfo
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
333
src/components/GiftCardQRCode.tsx
Normal file
333
src/components/GiftCardQRCode.tsx
Normal file
@@ -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
|
||||
@@ -46,13 +46,13 @@ function UserCard() {
|
||||
})
|
||||
|
||||
// 加载积分数量
|
||||
getUserPointsStats(userId)
|
||||
.then((res: any) => {
|
||||
setPointsCount(res.currentPoints || 0)
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('Points stats error:', error)
|
||||
})
|
||||
// getUserPointsStats(userId)
|
||||
// .then((res: any) => {
|
||||
// setPointsCount(res.currentPoints || 0)
|
||||
// })
|
||||
// .catch((error: any) => {
|
||||
// console.error('Points stats error:', error)
|
||||
// })
|
||||
// 加载礼品劵数量
|
||||
setGiftCount(0)
|
||||
// pageUserGiftLog({userId, page: 1, limit: 1}).then(res => {
|
||||
|
||||
@@ -12,14 +12,14 @@ export enum GiftCardType {
|
||||
SERVICE = 30
|
||||
}
|
||||
|
||||
/** 使用状态枚举 */
|
||||
export enum UseStatus {
|
||||
/** 可用 */
|
||||
AVAILABLE = 0,
|
||||
/** 状态枚举 */
|
||||
export enum GiftStatus {
|
||||
/** 未使用 */
|
||||
UNUSED = 0,
|
||||
/** 已使用 */
|
||||
USED = 1,
|
||||
/** 已过期 */
|
||||
EXPIRED = 2
|
||||
/** 失效 */
|
||||
INVALID = 2
|
||||
}
|
||||
|
||||
/** 优惠类型枚举 */
|
||||
@@ -100,8 +100,8 @@ export interface GiftCardData {
|
||||
originalPrice?: string
|
||||
/** 礼品卡类型 */
|
||||
type?: GiftCardType
|
||||
/** 使用状态 */
|
||||
useStatus?: UseStatus
|
||||
/** 状态 */
|
||||
status?: GiftStatus
|
||||
/** 过期时间 */
|
||||
expireTime?: string
|
||||
/** 使用时间 */
|
||||
@@ -156,7 +156,7 @@ export interface GiftCardFilter {
|
||||
/** 类型筛选 */
|
||||
type?: GiftCardType[]
|
||||
/** 状态筛选 */
|
||||
useStatus?: UseStatus[]
|
||||
status?: GiftStatus[]
|
||||
/** 品牌筛选 */
|
||||
brand?: string[]
|
||||
/** 分类筛选 */
|
||||
|
||||
228
src/user/gift/api-test.tsx
Normal file
228
src/user/gift/api-test.tsx
Normal file
@@ -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
|
||||
137
src/user/gift/color-fix-summary.md
Normal file
137
src/user/gift/color-fix-summary.md
Normal file
@@ -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. **品牌主题**: 根据商品品牌使用对应的品牌色
|
||||
|
||||
## 部署检查
|
||||
|
||||
- [ ] 样式文件更新正确
|
||||
- [ ] 主题选择逻辑正确
|
||||
- [ ] 测试页面验证通过
|
||||
- [ ] 各种设备上显示正常
|
||||
- [ ] 不同状态下颜色正确
|
||||
213
src/user/gift/color-test.tsx
Normal file
213
src/user/gift/color-test.tsx
Normal file
@@ -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
|
||||
226
src/user/gift/debug-tab.tsx
Normal file
226
src/user/gift/debug-tab.tsx
Normal file
@@ -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
|
||||
@@ -1,7 +1,7 @@
|
||||
import {useState, useEffect} from "react";
|
||||
import {useRouter} from '@tarojs/taro'
|
||||
import {Button, ConfigProvider, Tag, Divider, Image} from '@nutui/nutui-react-taro'
|
||||
import {ArrowLeft, Gift, Clock, Location, Phone, Share, Copy} from '@nutui/icons-react-taro'
|
||||
import {Button, ConfigProvider, Divider} from '@nutui/nutui-react-taro'
|
||||
import {Gift, Clock, Location, Phone, Copy, QrCode} from '@nutui/icons-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {ShopGift} from "@/api/shop/shopGift/model";
|
||||
@@ -87,7 +87,7 @@ const GiftCardDetail = () => {
|
||||
const getGiftStatus = () => {
|
||||
if (!gift) return { status: 0, text: '未知', color: 'default' }
|
||||
|
||||
switch (gift.useStatus) {
|
||||
switch (gift.status) {
|
||||
case 0:
|
||||
return { status: 0, text: '可使用', color: 'success' }
|
||||
case 1:
|
||||
@@ -103,18 +103,7 @@ const GiftCardDetail = () => {
|
||||
const handleUseGift = () => {
|
||||
if (!gift) return
|
||||
|
||||
Taro.showModal({
|
||||
title: '使用礼品卡',
|
||||
content: `确定要使用"${gift.name}"吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 跳转到礼品卡使用页面
|
||||
Taro.navigateTo({
|
||||
url: `/user/gift/use?id=${gift.id}`
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 复制兑换码
|
||||
@@ -171,30 +160,22 @@ const GiftCardDetail = () => {
|
||||
{/* 礼品卡卡片 */}
|
||||
<View className="m-4 p-6 rounded-2xl text-white" style={{backgroundColor: '#fbbf24'}}>
|
||||
<View className="flex items-center justify-between mb-4">
|
||||
<View className="flex-1">
|
||||
<View className="w-full">
|
||||
<Text className="text-4xl font-bold">{getGiftValueDisplay()}</Text>
|
||||
<Text className="text-lg opacity-90 mt-1">{getGiftTypeText(gift.type)}</Text>
|
||||
</View>
|
||||
{gift.goodsImage ? (
|
||||
<Image
|
||||
src={gift.goodsImage}
|
||||
className="w-16 h-16 rounded-lg"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
) : (
|
||||
<Gift size="40" />
|
||||
)}
|
||||
<QrCode />
|
||||
</View>
|
||||
|
||||
<Text className="text-xl font-semibold mb-2">{gift.name}</Text>
|
||||
<Text className="text-base opacity-90">{gift.description || getUsageText()}</Text>
|
||||
<Text className="text-base opacity-90 px-2">{gift.description || getUsageText()}</Text>
|
||||
|
||||
{/* 兑换码 */}
|
||||
{gift.code && (
|
||||
<View className="mt-4 p-3 bg-white bg-opacity-20 rounded-lg">
|
||||
<View className="flex items-center justify-between">
|
||||
<View>
|
||||
<Text className="text-sm opacity-80">兑换码</Text>
|
||||
<Text className="text-sm opacity-80 px-2">兑换码</Text>
|
||||
<Text className="text-lg font-mono font-bold">{gift.code}</Text>
|
||||
</View>
|
||||
<Button
|
||||
@@ -213,14 +194,14 @@ const GiftCardDetail = () => {
|
||||
|
||||
{/* 详细信息 */}
|
||||
<View className="bg-white mx-4 rounded-xl p-4">
|
||||
<Text className="text-lg font-semibold mb-4">使用说明</Text>
|
||||
<Text className="text-lg font-semibold">使用说明</Text>
|
||||
|
||||
<View>
|
||||
<View className={'mt-4'}>
|
||||
<View className="flex items-center mb-3">
|
||||
<Clock size="16" className="text-gray-400 mr-3" />
|
||||
<View>
|
||||
<Text className="text-gray-600 text-sm">有效期</Text>
|
||||
<Text className="text-gray-900">{getValidityText()}</Text>
|
||||
<Text className="text-gray-900 px-1">{getValidityText()}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -230,7 +211,7 @@ const GiftCardDetail = () => {
|
||||
<Gift size="16" className="text-gray-400 mr-3" />
|
||||
<View>
|
||||
<Text className="text-gray-600 text-sm">礼品卡类型</Text>
|
||||
<Text className="text-gray-900">{getGiftTypeText(gift.type)}</Text>
|
||||
<Text className="text-gray-900 px-1">{getGiftTypeText(gift.type)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -241,7 +222,7 @@ const GiftCardDetail = () => {
|
||||
<Location size="16" className="text-gray-400 mr-3" />
|
||||
<View>
|
||||
<Text className="text-gray-600 text-sm">使用地址</Text>
|
||||
<Text className="text-gray-900">{gift.useLocation}</Text>
|
||||
<Text className="text-gray-900 px-1">{gift.useLocation}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
|
||||
@@ -35,17 +35,61 @@ const GiftCardManage = () => {
|
||||
// 获取礼品卡状态过滤条件
|
||||
const getStatusFilter = () => {
|
||||
switch (String(activeTab)) {
|
||||
case '0': // 可用
|
||||
return { useStatus: 0 }
|
||||
case '0': // 未使用
|
||||
return { status: 0 }
|
||||
case '1': // 已使用
|
||||
return { useStatus: 1 }
|
||||
case '2': // 已过期
|
||||
return { useStatus: 2 }
|
||||
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 {}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据状态过滤条件加载礼品卡
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
const reload = async (isRefresh = false) => {
|
||||
if (isRefresh) {
|
||||
setPage(1)
|
||||
@@ -57,6 +101,8 @@ const GiftCardManage = () => {
|
||||
try {
|
||||
const currentPage = isRefresh ? 1 : page
|
||||
const statusFilter = getStatusFilter()
|
||||
console.log('reload - 当前activeTab:', activeTab, '状态过滤:', statusFilter)
|
||||
|
||||
const res = await getUserGifts({
|
||||
page: currentPage,
|
||||
limit: 10,
|
||||
@@ -68,7 +114,7 @@ const GiftCardManage = () => {
|
||||
// sortBy: filters.sortBy,
|
||||
// sortOrder: filters.sortOrder
|
||||
})
|
||||
console.log(res?.list,'>>>>lalala')
|
||||
console.log('reload - API返回数据:', res?.list)
|
||||
if (res && res.list) {
|
||||
const newList = isRefresh ? res.list : [...list, ...res.list]
|
||||
setList(newList)
|
||||
@@ -110,14 +156,18 @@ const GiftCardManage = () => {
|
||||
|
||||
// Tab切换
|
||||
const handleTabChange = (value: string | number) => {
|
||||
console.log('Tab切换到:', value)
|
||||
setActiveTab(value)
|
||||
setPage(1)
|
||||
setList([])
|
||||
setHasMore(true)
|
||||
// 延迟执行reload,确保状态更新完成
|
||||
setTimeout(() => {
|
||||
reload(true)
|
||||
}, 100)
|
||||
|
||||
// 直接传递状态值,避免异步状态更新问题
|
||||
const statusFilter = getStatusFilterByValue(value)
|
||||
console.log('状态过滤条件:', statusFilter)
|
||||
|
||||
// 立即加载数据
|
||||
loadGiftsByStatus(statusFilter)
|
||||
}
|
||||
|
||||
// 转换礼品卡数据为GiftCard组件所需格式
|
||||
@@ -131,7 +181,7 @@ const GiftCardManage = () => {
|
||||
goodsImage: gift.goodsImage, // 商品图片
|
||||
faceValue: gift.faceValue,
|
||||
type: gift.type,
|
||||
useStatus: gift.useStatus,
|
||||
status: gift.status,
|
||||
expireTime: gift.expireTime,
|
||||
useTime: gift.useTime,
|
||||
useLocation: gift.useLocation,
|
||||
@@ -144,7 +194,7 @@ const GiftCardManage = () => {
|
||||
category: getTypeText(gift.type),
|
||||
tags: [
|
||||
getTypeText(gift.type),
|
||||
gift.useStatus === 0 ? '可使用' : gift.useStatus === 1 ? '已使用' : '已过期',
|
||||
gift.status === 0 ? '未使用' : gift.status === 1 ? '已使用' : '失效',
|
||||
...(gift.goodsName ? ['商品礼品卡'] : [])
|
||||
].filter(Boolean),
|
||||
instructions: gift.instructions ? [gift.instructions] : [
|
||||
@@ -161,8 +211,8 @@ const GiftCardManage = () => {
|
||||
]
|
||||
})
|
||||
},
|
||||
showCode: gift.useStatus === 0, // 只有可用状态显示兑换码
|
||||
showUseBtn: gift.useStatus === 0, // 只有可用状态显示使用按钮
|
||||
showCode: gift.status === 0, // 只有未使用状态显示兑换码
|
||||
showUseBtn: gift.status === 0, // 只有未使用状态显示使用按钮
|
||||
showDetailBtn: true,
|
||||
showGoodsDetail: true, // 显示商品详情
|
||||
theme: getThemeByType(gift.type),
|
||||
@@ -184,10 +234,10 @@ const GiftCardManage = () => {
|
||||
// 根据礼品卡类型获取主题色
|
||||
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 'silver'
|
||||
case 10: return 'gold' // 实物礼品卡 - 金色
|
||||
case 20: return 'blue' // 虚拟礼品卡 - 蓝色
|
||||
case 30: return 'green' // 服务礼品卡 - 绿色
|
||||
default: return 'purple' // 默认使用紫色主题,更美观
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,11 +408,11 @@ const GiftCardManage = () => {
|
||||
{/* Tab切换 */}
|
||||
<View className="bg-white">
|
||||
<Tabs value={activeTab} onChange={handleTabChange}>
|
||||
<TabPane title="可用" value="0">
|
||||
<TabPane title="未使用" value="0">
|
||||
</TabPane>
|
||||
<TabPane title="已使用" value="1">
|
||||
</TabPane>
|
||||
<TabPane title="已过期" value="2">
|
||||
<TabPane title="失效" value="2">
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</View>
|
||||
@@ -377,9 +427,9 @@ const GiftCardManage = () => {
|
||||
<View className="flex flex-col justify-center items-center" style={{height: '500px'}}>
|
||||
<Empty
|
||||
description={
|
||||
activeTab === '0' ? "暂无可用礼品卡" :
|
||||
activeTab === '0' ? "暂无未使用礼品卡" :
|
||||
activeTab === '1' ? "暂无已使用礼品卡" :
|
||||
"暂无已过期礼品卡"
|
||||
"暂无失效礼品卡"
|
||||
}
|
||||
style={{backgroundColor: 'transparent'}}
|
||||
/>
|
||||
@@ -417,7 +467,7 @@ const GiftCardManage = () => {
|
||||
<View className="text-gray-400 mb-4">
|
||||
<Gift size="48" />
|
||||
</View>
|
||||
<View className="text-gray-500 mb-2">暂无可用礼品卡</View>
|
||||
<View className="text-gray-500 mb-2">暂无未使用礼品卡</View>
|
||||
<View className="flex gap-2 justify-center">
|
||||
<Button
|
||||
size="small"
|
||||
|
||||
221
src/user/gift/qrcode-demo.tsx
Normal file
221
src/user/gift/qrcode-demo.tsx
Normal file
@@ -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
|
||||
212
src/user/gift/qrcode-verification-implementation.md
Normal file
212
src/user/gift/qrcode-verification-implementation.md
Normal file
@@ -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. **多门店支持**:支持多门店的核销管理
|
||||
@@ -5,7 +5,7 @@ import {ArrowLeft, QrCode, Gift, Voucher} from '@nutui/icons-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {ShopGift} from "@/api/shop/shopGift/model";
|
||||
import {validateGiftCode, redeemGift} from "@/api/shop/shopGift";
|
||||
import {validateGiftCode, redeemGift, pageShopGift, updateShopGift} from "@/api/shop/shopGift";
|
||||
import GiftCard from "@/components/GiftCard";
|
||||
|
||||
const GiftCardRedeem = () => {
|
||||
@@ -38,14 +38,22 @@ const GiftCardRedeem = () => {
|
||||
|
||||
setValidating(true)
|
||||
try {
|
||||
const gift = await validateGiftCode(codeToValidate.trim())
|
||||
if(gift){
|
||||
setValidGift(gift)
|
||||
const gifts = await pageShopGift({code: codeToValidate,status: 0})
|
||||
if(gifts?.count == 0){
|
||||
Taro.showToast({
|
||||
title: '兑换码无效或已使用',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
const item = gifts?.list[0];
|
||||
if(item){
|
||||
setValidGift(item)
|
||||
Taro.showToast({
|
||||
title: '验证成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
Taro.showToast({
|
||||
title: '验证成功',
|
||||
icon: 'success'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('验证兑换码失败:', error)
|
||||
setValidGift(null)
|
||||
@@ -64,8 +72,9 @@ const GiftCardRedeem = () => {
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
await redeemGift({
|
||||
code: code.trim()
|
||||
await updateShopGift({
|
||||
...validGift,
|
||||
userId: Taro.getStorageSync('UserId')
|
||||
})
|
||||
|
||||
setRedeemSuccess(true)
|
||||
@@ -125,7 +134,6 @@ const GiftCardRedeem = () => {
|
||||
goodsImage: gift.goodsImage,
|
||||
faceValue: gift.faceValue,
|
||||
type: gift.type,
|
||||
useStatus: gift.useStatus,
|
||||
expireTime: gift.expireTime,
|
||||
useTime: gift.useTime,
|
||||
useLocation: gift.useLocation,
|
||||
|
||||
237
src/user/gift/status-field-migration.md
Normal file
237
src/user/gift/status-field-migration.md
Normal file
@@ -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. **监控告警**:关注迁移后的错误日志和用户反馈
|
||||
219
src/user/gift/status-test.tsx
Normal file
219
src/user/gift/status-test.tsx
Normal file
@@ -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
|
||||
198
src/user/gift/tab-switch-fix.md
Normal file
198
src/user/gift/tab-switch-fix.md
Normal file
@@ -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. **用户反馈**:收集用户使用反馈,持续优化体验
|
||||
4
src/user/store/verification.config.ts
Normal file
4
src/user/store/verification.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '门店核销',
|
||||
navigationBarTextStyle: 'black'
|
||||
})
|
||||
348
src/user/store/verification.tsx
Normal file
348
src/user/store/verification.tsx
Normal 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
|
||||
Reference in New Issue
Block a user