Browse Source

refactor(礼品卡): 优化礼品卡相关功能和界面

- 修改 API 基础 URL以适应开发环境
- 移除未使用的 StoreCell 组件- 在 ShopGift 模型中添加核销时间字段
-调整 GiftCard 组件样式和布局
- 更新 goodsDetail 页面中的规格选择逻辑
- 优化 gift 组件中的有效期和类型显示
- 在 redeemGift 中添加礼品卡核销时间
- 重构 storeVerification 组件逻辑,优化核销流程
master
科技小王子 1 week ago
parent
commit
f73bfeb743
  1. 2
      config/env.ts
  2. 150
      src/admin/components/StoreCell.tsx
  3. 4
      src/api/shop/shopGift/model/index.ts
  4. 1
      src/components/GiftCard.scss
  5. 6
      src/components/GiftCard.tsx
  6. 13
      src/shop/goodsDetail/index.tsx
  7. 14
      src/user/gift/detail.tsx
  8. 6
      src/user/gift/redeem.tsx
  9. 81
      src/user/store/verification.tsx

2
config/env.ts

@ -2,7 +2,7 @@
export const ENV_CONFIG = { export const ENV_CONFIG = {
// 开发环境 // 开发环境
development: { development: {
API_BASE_URL: 'https://cms-api.websoft.top/api',
API_BASE_URL: 'http://127.0.0.1:9200/api',
APP_NAME: '开发环境', APP_NAME: '开发环境',
DEBUG: 'true', DEBUG: 'true',
}, },

150
src/admin/components/StoreCell.tsx

@ -1,150 +0,0 @@
import {Cell} from '@nutui/nutui-react-taro'
import navTo from "@/utils/common";
import Taro from '@tarojs/taro'
import {View, Text} from '@tarojs/components'
import {ArrowRight, ShieldCheck, LogisticsError, Location, Reward, Tips, Ask, Setting, Scan} from '@nutui/icons-react-taro'
import {useUser} from '@/hooks/useUser'
const UserCell = () => {
const {logoutUser, isCertified, hasRole, isAdmin} = useUser();
const onLogout = () => {
Taro.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: function (res) {
if (res.confirm) {
// 使用 useUser hook 的 logoutUser 方法
logoutUser();
Taro.reLaunch({
url: '/pages/index/index'
})
}
}
})
}
return (
<>
<View className={'px-4'}>
<Cell.Group divider={true} description={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Text style={{marginTop: '12px'}}></Text>
</View>
}>
<Cell
className="nutui-cell-clickable"
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Scan size={16}/>
<Text className={'pl-3 text-sm'}></Text>
</View>
}
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/user/wallet/index', true)
}}
/>
<Cell
className="nutui-cell-clickable"
style={{
display: 'none'
}}
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<LogisticsError size={16}/>
<Text className={'pl-3 text-sm'}></Text>
</View>
}
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/user/wallet/index', true)
}}
/>
<Cell
className="nutui-cell-clickable"
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Location size={16}/>
<Text className={'pl-3 text-sm'}></Text>
</View>
}
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/user/address/index', true)
}}
/>
<Cell
className="nutui-cell-clickable"
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<ShieldCheck size={16} color={isCertified() ? '#52c41a' : '#666'}/>
<Text className={'pl-3 text-sm'}></Text>
{isCertified() && (
<Text className={'pl-2 text-xs text-green-500'}></Text>
)}
</View>
}
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/user/userVerify/index', true)
}}
/>
<Cell
className="nutui-cell-clickable"
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Ask size={16}/>
<Text className={'pl-3 text-sm'}></Text>
</View>
}
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/user/help/index')
}}
/>
<Cell
className="nutui-cell-clickable"
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Tips size={16}/>
<Text className={'pl-3 text-sm'}></Text>
</View>
}
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/user/about/index')
}}
/>
</Cell.Group>
<Cell.Group divider={true} description={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Text style={{marginTop: '12px'}}></Text>
</View>
}>
<Cell
className="nutui-cell-clickable"
title="账号安全"
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => navTo('/user/profile/profile', true)}
/>
<Cell
className="nutui-cell-clickable"
title="退出登录"
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={onLogout}
/>
</Cell.Group>
</View>
</>
)
}
export default UserCell

4
src/api/shop/shopGift/model/index.ts

@ -1,4 +1,4 @@
import type { PageParam } from '@/api/index';
import type { PageParam } from '@/api';
/** /**
* *
@ -64,6 +64,8 @@ export interface ShopGift {
useLocation?: string; useLocation?: string;
// 客服联系方式 // 客服联系方式
contactInfo?: string; contactInfo?: string;
// 核销时间
verificationTime?: string;
} }
/** /**

1
src/components/GiftCard.scss

@ -460,7 +460,6 @@
margin-bottom: 6px; margin-bottom: 6px;
.time-text { .time-text {
font-size: 12px;
color: #666; color: #666;
margin-left: 6px; margin-left: 6px;
} }

6
src/components/GiftCard.tsx

@ -197,8 +197,8 @@ const GiftCard: React.FC<GiftCardProps> = ({
<Gift size="24" className="text-white" /> <Gift size="24" className="text-white" />
</View> </View>
<View className="gift-card-title"> <View className="gift-card-title">
<Text className="title-text">{getTypeText()}</Text>
<Text className="type-text">{name}</Text>
<Text className="text-left title-text">{getTypeText()}</Text>
<Text className="text-left type-text">{name}</Text>
</View> </View>
<View className="gift-card-status"> <View className="gift-card-status">
<Tag type={statusInfo.color}>{statusInfo.text}</Tag> <Tag type={statusInfo.color}>{statusInfo.text}</Tag>
@ -325,7 +325,7 @@ const GiftCard: React.FC<GiftCardProps> = ({
{useLocation && ( {useLocation && (
<View className="time-item"> <View className="time-item">
<Location size="14" className="text-gray-400" /> <Location size="14" className="text-gray-400" />
<Text className="time-text">使{useLocation}</Text>
<Text className="time-text text-sm">使{useLocation}</Text>
</View> </View>
)} )}
</View> </View>

13
src/shop/goodsDetail/index.tsx

@ -82,7 +82,7 @@ const GoodsDetail = () => {
}; };
// 规格选择确认回调 // 规格选择确认回调
const handleSpecConfirm = (sku: ShopGoodsSku, quantity: number, action: 'cart' | 'buy') => {
const handleSpecConfirm = (sku: ShopGoodsSku, quantity: number, action?: 'cart' | 'buy') => {
// setSelectedSku(sku); // setSelectedSku(sku);
setShowSpecSelector(false); setShowSpecSelector(false);
@ -96,7 +96,7 @@ const GoodsDetail = () => {
image: goods!.image || '', image: goods!.image || '',
specInfo: sku.sku, // sku字段包含规格信息 specInfo: sku.sku, // sku字段包含规格信息
}, quantity); }, quantity);
} else {
} else if (action === 'buy') {
// 立即购买 // 立即购买
const orderData = { const orderData = {
goodsId: goods!.goodsId!, goodsId: goods!.goodsId!,
@ -105,6 +105,15 @@ const GoodsDetail = () => {
price: sku.price || goods!.price || '0' price: sku.price || goods!.price || '0'
}; };
navTo(`/shop/orderConfirm/index?orderData=${encodeURIComponent(JSON.stringify(orderData))}`, true); navTo(`/shop/orderConfirm/index?orderData=${encodeURIComponent(JSON.stringify(orderData))}`, true);
} else {
// 默认情况:如果action未定义,默认为立即购买
const orderData = {
goodsId: goods!.goodsId!,
skuId: sku.id,
quantity,
price: sku.price || goods!.price || '0'
};
navTo(`/shop/orderConfirm/index?orderData=${encodeURIComponent(JSON.stringify(orderData))}`, true);
} }
}; };

14
src/user/gift/detail.tsx

@ -165,7 +165,7 @@ const GiftCardDetail = () => {
return ( return (
<ConfigProvider> <ConfigProvider>
{/* 礼品卡卡片 */} {/* 礼品卡卡片 */}
<View className="m-4 p-6 rounded-2xl text-white" style={{backgroundColor: '#fbbf24'}}>
<View className="m-4 p-6 rounded-2xl text-white" style={{background: 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%)'}}>
<View className="flex items-center justify-between mb-4"> <View className="flex items-center justify-between mb-4">
<View className="w-full"> <View className="w-full">
<Text className="text-4xl font-bold">{getGiftValueDisplay()}</Text> <Text className="text-4xl font-bold">{getGiftValueDisplay()}</Text>
@ -212,8 +212,8 @@ const GiftCardDetail = () => {
<View className="flex items-center mb-3"> <View className="flex items-center mb-3">
<Clock size="16" className="text-gray-400 mr-3" /> <Clock size="16" className="text-gray-400 mr-3" />
<View> <View>
<Text className="text-gray-600 text-sm"></Text>
<Text className="text-gray-900 px-1">{getValidityText()}</Text>
<Text className="text-gray-400 text-sm"></Text>
<Text className="text-blue-500 text-sm px-1">{getValidityText()}</Text>
</View> </View>
</View> </View>
@ -222,8 +222,8 @@ const GiftCardDetail = () => {
<View className="flex items-center mb-3"> <View className="flex items-center mb-3">
<Gift size="16" className="text-gray-400 mr-3" /> <Gift size="16" className="text-gray-400 mr-3" />
<View> <View>
<Text className="text-gray-600 text-sm"></Text>
<Text className="text-gray-900 px-1">{getGiftTypeText(gift.type)}</Text>
<Text className="text-gray-400 text-sm"></Text>
<Text className="text-blue-500 text-sm px-1">{getGiftTypeText(gift.type)}</Text>
</View> </View>
</View> </View>
@ -233,8 +233,8 @@ const GiftCardDetail = () => {
<View className="flex items-center"> <View className="flex items-center">
<Location size="16" className="text-gray-400 mr-3" /> <Location size="16" className="text-gray-400 mr-3" />
<View> <View>
<Text className="text-gray-600 text-sm">使</Text>
<Text className="text-gray-900 px-1">{gift.useLocation}</Text>
<Text className="text-gray-400 text-sm">使</Text>
<Text className="text-blue-500 text-sm px-1">{gift.useLocation}</Text>
</View> </View>
</View> </View>
</> </>

6
src/user/gift/redeem.tsx

@ -4,6 +4,7 @@ import {Button, ConfigProvider, Input, Divider} from '@nutui/nutui-react-taro'
import {ArrowLeft, QrCode, Gift, Voucher} from '@nutui/icons-react-taro' import {ArrowLeft, QrCode, Gift, Voucher} from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import {View, Text} from '@tarojs/components' import {View, Text} from '@tarojs/components'
import dayjs from 'dayjs'
import {ShopGift} from "@/api/shop/shopGift/model"; import {ShopGift} from "@/api/shop/shopGift/model";
import {pageShopGift, updateShopGift} from "@/api/shop/shopGift"; import {pageShopGift, updateShopGift} from "@/api/shop/shopGift";
import GiftCard from "@/components/GiftCard"; import GiftCard from "@/components/GiftCard";
@ -74,7 +75,8 @@ const GiftCardRedeem = () => {
try { try {
await updateShopGift({ await updateShopGift({
...validGift, ...validGift,
userId: Taro.getStorageSync('UserId')
userId: Taro.getStorageSync('UserId'),
takeTime: dayjs.unix(Date.now() / 1000).format('YYYY-MM-DD HH:mm:ss')
}) })
setRedeemSuccess(true) setRedeemSuccess(true)
@ -236,7 +238,7 @@ const GiftCardRedeem = () => {
</View> </View>
<Text className="text-2xl font-bold text-gray-900 mb-2"></Text> <Text className="text-2xl font-bold text-gray-900 mb-2"></Text>
<Text className="text-gray-600 mb-6"></Text>
<Text className="text-left text-gray-500 mb-6"></Text>
{validGift && ( {validGift && (
<View className="mb-6"> <View className="mb-6">

81
src/user/store/verification.tsx

@ -5,16 +5,7 @@ import { Scan, Search } from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import {getShopGiftByCode, updateShopGift} from "@/api/shop/shopGift"; import {getShopGiftByCode, updateShopGift} from "@/api/shop/shopGift";
// interface VerificationData {
// type: string
// giftId: number
// giftCode: string
// verificationCode: string
// faceValue: string
// timestamp: number
// expireTime?: string
// }
import {useUser} from "@/hooks/useUser";
interface GiftCardInfo { interface GiftCardInfo {
id: number id: number
@ -28,6 +19,9 @@ interface GiftCardInfo {
} }
const StoreVerification: React.FC = () => { const StoreVerification: React.FC = () => {
const {
isAdmin
} = useUser();
const [scanResult, setScanResult] = useState<string>('') const [scanResult, setScanResult] = useState<string>('')
const [verificationCode, setVerificationCode] = useState<string>('') const [verificationCode, setVerificationCode] = useState<string>('')
const [giftInfo, setGiftInfo] = useState<GiftCardInfo | null>(null) const [giftInfo, setGiftInfo] = useState<GiftCardInfo | null>(null)
@ -38,10 +32,9 @@ const StoreVerification: React.FC = () => {
const handleScan = () => { const handleScan = () => {
Taro.scanCode({ Taro.scanCode({
success: (res) => { success: (res) => {
console.log('扫码结果:', res.result)
setScanResult(res.result) setScanResult(res.result)
verificationQRCode(res.result)
// parseQRCode(res.result)
setVerificationCode(res.result)
handleManualVerification().then()
}, },
fail: (err) => { fail: (err) => {
console.error('扫码失败:', err) console.error('扫码失败:', err)
@ -52,31 +45,18 @@ const StoreVerification: React.FC = () => {
} }
}) })
} }
// 解析二维码数据
// const parseQRCode = (qrData: string) => {
// try {
// const data: VerificationData = JSON.parse(qrData)
//
// if (data.type === 'gift_card_verification') {
// setVerificationCode(data.verificationCode)
// console.log(data.verificationCode,'...vaerrr')
// } else {
// throw new Error('无效的二维码格式')
// }
// } catch (error) {
// console.error('解析二维码失败:', error)
// Taro.showToast({
// title: '无效的二维码',
// icon: 'error'
// })
// }
// }
// 扫码核销操作
const verificationQRCode = async (code:string) => {
const gift = await getShopGiftByCode(code)
// 手动输入核销码验证
const handleManualVerification = async () => {
setLoading(true)
try {
// 这里应该调用后端API验证核销码
const gift = await getShopGiftByCode(verificationCode.trim())
if(!isAdmin()){
return Taro.showToast({
title: '您没有核销权限',
icon: 'error'
})
}
if(gift.status == 1){ if(gift.status == 1){
return Taro.showToast({ return Taro.showToast({
title: '此礼品码已使用', title: '此礼品码已使用',
@ -95,20 +75,6 @@ const StoreVerification: React.FC = () => {
icon: 'error' icon: 'error'
}) })
} }
updateShopGift({
...gift,
status: 1,
takeTime: dayjs.unix(Date.now() / 1000).format('YYYY-MM-DD HH:mm:ss')
}).then(() => {
Taro.showToast({
title: '核销成功',
icon: 'success'
})
})
}
// 手动输入核销码验证
const handleManualVerification = async () => {
if (!verificationCode.trim()) { if (!verificationCode.trim()) {
Taro.showToast({ Taro.showToast({
title: '请输入核销码', title: '请输入核销码',
@ -116,15 +82,12 @@ const StoreVerification: React.FC = () => {
}) })
return return
} }
setLoading(true)
try {
// 这里应该调用后端API验证核销码
const giftCard = await getShopGiftByCode(verificationCode.trim())
await updateShopGift({ await updateShopGift({
...giftCard,
...gift,
status: 1, status: 1,
takeTime: dayjs.unix(Date.now() / 1000).format('YYYY-MM-DD HH:mm:ss')
operatorUserId: Taro.getStorageSync('UserId'),
takeTime: dayjs.unix(Date.now() / 1000).format('YYYY-MM-DD HH:mm:ss'),
verificationTime: dayjs.unix(Date.now() / 1000).format('YYYY-MM-DD HH:mm:ss')
}) })
Taro.showToast({ Taro.showToast({
title: '核销成功', title: '核销成功',

Loading…
Cancel
Save