Browse Source

refactor(components): 优化多个组件的样式和结构

-调整了多个组件的图标使用
- 优化了部分组件的布局结构
- 移除了不必要的空行和空格
-统一了部分样式类名的使用
demo
科技小王子 1 month ago
parent
commit
ee28aeeff9
  1. 5
      package.json
  2. 23
      pnpm-lock.yaml
  3. 1
      src/app.config.ts
  4. 14
      src/components/CouponShare.tsx
  5. 14
      src/components/GiftCardQRCode.tsx
  6. 6
      src/components/GiftCardShare.tsx
  7. 36
      src/components/QRCodeGenerator.tsx
  8. 12
      src/components/SimpleQRCodeModal.tsx
  9. 228
      src/user/gift/api-test.tsx
  10. 137
      src/user/gift/color-fix-summary.md
  11. 213
      src/user/gift/color-test.tsx
  12. 203
      src/user/gift/css-compatibility-fix.md
  13. 226
      src/user/gift/debug-tab.tsx
  14. 201
      src/user/gift/demo.tsx
  15. 2
      src/user/gift/detail.tsx
  16. 213
      src/user/gift/final-css-fix-summary.md
  17. 152
      src/user/gift/goodsName-integration.md
  18. 191
      src/user/gift/goodsname-test.tsx
  19. 221
      src/user/gift/qrcode-demo.tsx
  20. 212
      src/user/gift/qrcode-verification-implementation.md
  21. 237
      src/user/gift/status-field-migration.md
  22. 219
      src/user/gift/status-test.tsx
  23. 198
      src/user/gift/tab-switch-fix.md
  24. 130
      src/user/gift/test.tsx
  25. 145
      src/user/gift/usage-example.md
  26. 1
      src/user/gift/use.tsx
  27. 8
      src/user/store/verification.tsx

5
package.json

@ -63,18 +63,17 @@
"@tarojs/shared": "4.0.8",
"@tarojs/taro": "4.0.8",
"@tarojs/taro-rn": "^4.1.4",
"@types/qrcode": "^1.5.5",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"echarts-taro3-react": "^1.0.13",
"expo": "~50.0.2",
"js-base64": "^3.7.7",
"qrcode": "^1.5.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^10.1.0",
"react-native": "^0.73.1",
"react-router-dom": "^7.1.1"
"react-router-dom": "^7.1.1",
"weapp-qrcode": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.26.0",

23
pnpm-lock.yaml

@ -86,9 +86,6 @@ importers:
'@tarojs/taro-rn':
specifier: ^4.1.4
version: 4.1.4(uta2iou7tmlqjr3423a7zge7su)
'@types/qrcode':
specifier: ^1.5.5
version: 1.5.5
crypto-js:
specifier: ^4.2.0
version: 4.2.0
@ -104,9 +101,6 @@ importers:
js-base64:
specifier: ^3.7.7
version: 3.7.7
qrcode:
specifier: ^1.5.4
version: 1.5.4
react:
specifier: ^18.3.1
version: 18.3.1
@ -122,6 +116,9 @@ importers:
react-router-dom:
specifier: ^7.1.1
version: 7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
weapp-qrcode:
specifier: ^1.0.0
version: 1.0.0
devDependencies:
'@babel/core':
specifier: ^7.26.0
@ -2718,9 +2715,6 @@ packages:
'@types/prop-types@15.7.14':
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==, tarball: https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.14.tgz}
'@types/qrcode@1.5.5':
resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
'@types/qs@6.9.17':
resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==, tarball: https://registry.npmmirror.com/@types/qs/-/qs-6.9.17.tgz}
@ -9891,6 +9885,9 @@ packages:
wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==, tarball: https://registry.npmmirror.com/wcwidth/-/wcwidth-1.0.1.tgz}
weapp-qrcode@1.0.0:
resolution: {integrity: sha512-4sa3W0rGDVJ9QaeZpAKlAuUxVyjhDwiUqHyGK/jJMsRMXnhb4yO8qWU/pZruMo+iT5J6CraS67lDMFb1VY+RaA==}
web-namespaces@2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
@ -13679,10 +13676,6 @@ snapshots:
'@types/prop-types@15.7.14': {}
'@types/qrcode@1.5.5':
dependencies:
'@types/node': 18.19.68
'@types/qs@6.9.17': {}
'@types/range-parser@1.2.7': {}
@ -22360,6 +22353,10 @@ snapshots:
dependencies:
defaults: 1.0.4
weapp-qrcode@1.0.0:
dependencies:
extend: 3.0.2
web-namespaces@2.0.1: {}
webidl-conversions@3.0.1: {}

1
src/app.config.ts

@ -43,7 +43,6 @@ export default defineAppConfig({
"gift/index",
"gift/redeem",
"gift/detail",
"gift/qrcode-demo",
"store/verification"
]
},

14
src/components/CouponShare.tsx

@ -1,7 +1,7 @@
import React from 'react'
import { View, Text } from '@tarojs/components'
import { Button, Popup } from '@nutui/nutui-react-taro'
import { Share, Wechat, QQ, Weibo, Link, Close } from '@nutui/icons-react-taro'
import { Popup } from '@nutui/nutui-react-taro'
import { Share, Link, Close } from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro'
export interface CouponShareProps {
@ -28,10 +28,10 @@ const CouponShare: React.FC<CouponShareProps> = ({
// 生成分享文案
const generateShareText = () => {
const typeText = coupon.type === 10 ? '满减券' : coupon.type === 20 ? '折扣券' : '免费券'
const amountText = coupon.type === 10 ? `¥${coupon.amount}` :
const amountText = coupon.type === 10 ? `¥${coupon.amount}` :
coupon.type === 20 ? `${coupon.amount}` : '免费'
const conditionText = coupon.minAmount ? `${coupon.minAmount}元可用` : '无门槛'
return `🎁 ${coupon.name}\n💰 ${amountText} ${typeText}\n📋 ${conditionText}\n快来领取吧!`
}
@ -66,7 +66,7 @@ const CouponShare: React.FC<CouponShareProps> = ({
const shareUrl = generateShareUrl()
const shareText = generateShareText()
const fullText = `${shareText}\n\n${shareUrl}`
Taro.setClipboardData({
data: fullText,
success: () => {
@ -104,7 +104,7 @@ const CouponShare: React.FC<CouponShareProps> = ({
const shareOptions = [
{
icon: <Wechat size="32" className="text-green-500" />,
icon: <Share size="32" className="text-green-500" />,
label: '微信好友',
onClick: handleWechatShare
},
@ -142,7 +142,7 @@ const CouponShare: React.FC<CouponShareProps> = ({
<View className="flex items-center justify-between">
<View>
<Text className="text-2xl font-bold">
{coupon.type === 10 ? `¥${coupon.amount}` :
{coupon.type === 10 ? `¥${coupon.amount}` :
coupon.type === 20 ? `${coupon.amount}` : '免费'}
</Text>
<Text className="text-sm opacity-90">

14
src/components/GiftCardQRCode.tsx

@ -179,7 +179,7 @@ const GiftCardQRCode: React.FC<GiftCardQRCodeProps> = ({
{/* 标题 */}
<View className="text-center mb-4">
<Text className="text-lg font-bold"></Text>
<Text className="text-sm text-gray-500 block mt-1">
<Text className="text-sm text-gray-500 mt-1">
</Text>
</View>
@ -295,13 +295,13 @@ const GiftCardQRCode: React.FC<GiftCardQRCodeProps> = ({
)}
{/* 操作按钮 */}
<View className="flex gap-3">
<View className="flex">
<Button
size="large"
fill="outline"
icon={<Share />}
onClick={shareQRCode}
className="flex-1"
className="flex-1 mr-3"
>
</Button>
@ -318,10 +318,10 @@ const GiftCardQRCode: React.FC<GiftCardQRCodeProps> = ({
{/* 使用说明 */}
<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>
<View>
<Text className="text-xs text-yellow-700 mb-1"> </Text>
<Text className="text-xs text-yellow-700 mb-1"> </Text>
<Text className="text-xs text-yellow-700 mb-1"> 使</Text>
<Text className="text-xs text-yellow-700"> {giftCard.contactInfo || '400-800-8888'}</Text>
</View>
</View>

6
src/components/GiftCardShare.tsx

@ -1,7 +1,7 @@
import React from 'react'
import { View, Text } from '@tarojs/components'
import { Button, Popup } from '@nutui/nutui-react-taro'
import { Share, Wechat, QQ, Weibo, Link, Close, Gift } from '@nutui/icons-react-taro'
import { Popup } from '@nutui/nutui-react-taro'
import { Share, Link, Close, Gift } from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro'
export interface GiftCardShareProps {
@ -140,7 +140,7 @@ const GiftCardShare: React.FC<GiftCardShareProps> = ({
const shareOptions = [
{
icon: <Wechat size="32" className="text-green-500" />,
icon: <Share size="32" className="text-green-500" />,
label: '微信好友',
onClick: handleWechatShare
},

36
src/components/QRCodeGenerator.tsx

@ -40,10 +40,10 @@ const QRCodeGenerator: React.FC<QRCodeGeneratorProps> = ({
// 方案1: 使用在线API生成二维码
const qrApiUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent(text)}`
setQrDataURL(qrApiUrl)
// 方案2: 如果需要离线生成,可以使用Canvas绘制
// await drawQRCodeOnCanvas()
} catch (error) {
console.error('生成二维码失败:', error)
Taro.showToast({
@ -58,19 +58,19 @@ const QRCodeGenerator: React.FC<QRCodeGeneratorProps> = ({
// 使用Canvas绘制二维码(简化版本)
const drawQRCodeOnCanvas = async () => {
const ctx = Taro.createCanvasContext(canvasRef.current)
// 清空画布
ctx.clearRect(0, 0, size, size)
// 绘制白色背景
ctx.setFillStyle('#ffffff')
ctx.fillRect(0, 0, size, size)
// 绘制黑色边框
ctx.setStrokeStyle('#000000')
ctx.setLineWidth(2)
ctx.strokeRect(0, 0, size, size)
// 绘制定位点
const drawFinderPattern = (x: number, y: number) => {
ctx.setFillStyle('#000000')
@ -80,17 +80,17 @@ const QRCodeGenerator: React.FC<QRCodeGeneratorProps> = ({
ctx.setFillStyle('#000000')
ctx.fillRect(x + 8, y + 8, 12, 12)
}
// 三个角的定位点
drawFinderPattern(10, 10) // 左上
drawFinderPattern(size - 38, 10) // 右上
drawFinderPattern(10, size - 38) // 左下
// 生成数据点(模拟二维码数据)
ctx.setFillStyle('#000000')
const moduleSize = 4
const modules = Math.floor((size - 80) / moduleSize)
for (let i = 0; i < modules; i++) {
for (let j = 0; j < modules; j++) {
// 简单的伪随机算法,基于文本内容生成固定的图案
@ -102,7 +102,7 @@ const QRCodeGenerator: React.FC<QRCodeGeneratorProps> = ({
}
}
}
ctx.draw()
}
@ -172,7 +172,7 @@ const QRCodeGenerator: React.FC<QRCodeGeneratorProps> = ({
<View className="text-center mb-4">
<Text className="text-lg font-bold"></Text>
{title && (
<Text className="text-sm text-gray-600 block mt-1">{title}</Text>
<Text className="text-sm text-gray-600 mt-1">{title}</Text>
)}
</View>
@ -186,7 +186,7 @@ const QRCodeGenerator: React.FC<QRCodeGeneratorProps> = ({
{/* 二维码显示区域 */}
<View className="text-center mb-4">
{loading ? (
<View
<View
className="bg-gray-100 rounded-lg flex items-center justify-center"
style={{ width: `${size}px`, height: `${size}px`, margin: '0 auto' }}
>
@ -194,11 +194,11 @@ const QRCodeGenerator: React.FC<QRCodeGeneratorProps> = ({
</View>
) : qrDataURL ? (
<View className="p-4 bg-white border border-gray-200 rounded-lg">
<img
<img
src={qrDataURL}
alt="二维码"
style={{
width: `${size}px`,
style={{
width: `${size}px`,
height: `${size}px`,
display: 'block',
margin: '0 auto'
@ -271,9 +271,9 @@ const QRCodeGenerator: React.FC<QRCodeGeneratorProps> = ({
<View className="mt-4 p-3 bg-yellow-50 rounded-lg">
<Text className="text-sm text-yellow-800 font-medium mb-2">使</Text>
<View>
<Text className="text-xs text-yellow-700 block mb-1"> </Text>
<Text className="text-xs text-yellow-700 block mb-1"> </Text>
<Text className="text-xs text-yellow-700 block"> </Text>
<Text className="text-xs text-yellow-700 mb-1"> </Text>
<Text className="text-xs text-yellow-700 mb-1"> </Text>
<Text className="text-xs text-yellow-700"> </Text>
</View>
</View>
</View>

12
src/components/SimpleQRCodeModal.tsx

@ -1,7 +1,7 @@
import React from 'react'
import {View, Text} from '@tarojs/components'
import {Popup} from '@nutui/nutui-react-taro'
import {Close, QrCode} from '@nutui/icons-react-taro'
import { View, Text } from '@tarojs/components'
import { Popup } from '@nutui/nutui-react-taro'
import { Close, QrCode } from '@nutui/icons-react-taro'
export interface SimpleQRCodeModalProps {
/** 是否显示弹窗 */
@ -27,16 +27,14 @@ const SimpleQRCodeModal: React.FC<SimpleQRCodeModalProps> = ({
closeIcon={<Close/>}
onClose={onClose}
style={{
width: '85%',
maxWidth: '350px',
borderRadius: '12px'
width: '90%'
}}
>
<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 className="text-sm text-gray-500 mt-1">
</Text>
</View>

228
src/user/gift/api-test.tsx

@ -1,228 +0,0 @@
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

@ -1,137 +0,0 @@
# 礼品卡颜色主题修复说明
## 问题描述
用户反馈礼品卡显示为灰色,视觉效果不佳。经检查发现:
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

@ -1,213 +0,0 @@
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

203
src/user/gift/css-compatibility-fix.md

@ -1,203 +0,0 @@
# CSS兼容性问题修复说明
## 问题描述
在启动项目时遇到WXSS文件编译错误:
```
[ WXSS 文件编译错误]
/app-origin.wxss(165:2): unexpected '\' at pos 6023
```
这是由于使用了小程序不支持的CSS类名导致的编译错误。
## 问题分析
小程序环境对CSS类名有一定的限制,以下类名在小程序中不被支持或可能导致编译错误:
### 1. 不支持的CSS类名
- `space-y-1`, `space-y-3` - 垂直间距类名
- `gap-2`, `gap-3` - 元素间距类名
- `inline-block` - 行内块级元素
- `break-all` - 文字换行
- `w-48`, `h-48` - 固定尺寸类名
### 2. 错误原因
这些类名通常来自Tailwind CSS等CSS框架,在小程序环境中需要转换为标准的CSS属性或使用内联样式。
## 修复方案
### 1. SimpleQRCodeModal.tsx 修复
#### 问题1:inline-block 类名
```typescript
// 修复前
<View className="inline-block p-4 bg-white border border-gray-200 rounded-lg">
// 修复后
<View className="p-4 bg-white border border-gray-200 rounded-lg" style={{ display: 'inline-block' }}>
```
#### 问题2:break-all 类名
```typescript
// 修复前
<Text className="text-base font-mono text-blue-800 break-all">
// 修复后
<Text className="text-base font-mono text-blue-800" style={{ wordBreak: 'break-all' }}>
```
#### 问题3:固定尺寸类名
```typescript
// 修复前
<View className="w-48 h-48 bg-gray-100 rounded flex items-center justify-center">
// 修复后
<View
className="bg-gray-100 rounded flex items-center justify-center"
style={{ width: '200px', height: '200px' }}
>
```
#### 问题4:gap间距类名
```typescript
// 修复前
<View className="flex gap-3">
<Button className="flex-1">关闭</Button>
<Button className="flex-1">刷新</Button>
</View>
// 修复后
<View className="flex">
<Button className="flex-1 mr-3">关闭</Button>
<Button className="flex-1">刷新</Button>
</View>
```
### 2. simple-qrcode-demo.tsx 修复
#### 问题:space-y 类名
```typescript
// 修复前
<View className="space-y-1">
<Text className="text-sm text-gray-600">✅ 简洁的二维码弹窗设计</Text>
<Text className="text-sm text-gray-600">✅ 显示礼品卡code码内容</Text>
</View>
// 修复后
<View>
<Text className="text-sm text-gray-600 block mb-1">✅ 简洁的二维码弹窗设计</Text>
<Text className="text-sm text-gray-600 block mb-1">✅ 显示礼品卡code码内容</Text>
</View>
```
### 3. verification.tsx 修复
#### 问题:space-y 类名
```typescript
// 修复前
<View className="space-y-3">
<View className="flex justify-between">
// 修复后
<View>
<View className="flex justify-between mb-3">
```
## 修复的文件列表
### 1. 主要组件文件
- `src/components/SimpleQRCodeModal.tsx`
- 修复 `inline-block` 类名
- 修复 `break-all` 类名
- 修复 `w-48 h-48` 固定尺寸类名
### 2. 演示页面文件
- `src/user/gift/simple-qrcode-demo.tsx`
- 修复所有 `space-y-1``space-y-3` 类名
- 使用 `block mb-1` 替代垂直间距
### 3. 功能页面文件
- `src/user/store/verification.tsx`
- 修复 `space-y-3``space-y-1` 类名
- 使用 `mb-3``mb-1` 替代垂直间距
## 修复原则
### 1. 类名替换原则
- **垂直间距**:`space-y-1` → `block mb-1`
- **水平间距**:`space-x-1` → `inline-block mr-1`
- **固定尺寸**:`w-48` → `style={{ width: '200px' }}`
- **显示方式**:`inline-block` → `style={{ display: 'inline-block' }}`
- **文字换行**:`break-all` → `style={{ wordBreak: 'break-all' }}`
### 2. 兼容性考虑
- 优先使用小程序支持的标准CSS类名
- 复杂样式使用内联样式 `style` 属性
- 避免使用CSS框架特有的工具类名
### 3. 代码可读性
- 保持代码结构清晰
- 添加适当的注释说明
- 使用语义化的类名
## 测试验证
### 1. 编译测试
- ✅ 项目能够正常启动
- ✅ 没有WXSS编译错误
- ✅ 所有页面能够正常加载
### 2. 功能测试
- ✅ 二维码弹窗正常显示
- ✅ 样式效果与预期一致
- ✅ 交互功能正常工作
### 3. 兼容性测试
- ✅ 小程序环境正常运行
- ✅ H5环境正常运行
- ✅ 不同设备尺寸适配正常
## 预防措施
### 1. 开发规范
- 避免使用CSS框架特有的工具类名
- 优先使用小程序支持的标准CSS属性
- 复杂样式使用内联样式或SCSS文件
### 2. 代码检查
- 在提交代码前进行编译测试
- 使用ESLint等工具检查CSS类名
- 定期检查项目的CSS兼容性
### 3. 文档维护
- 维护CSS兼容性文档
- 记录不支持的CSS类名列表
- 提供替代方案参考
## 后续优化
### 1. 样式系统优化
- 建立统一的样式规范
- 创建可复用的样式组件
- 使用CSS变量管理主题色彩
### 2. 工具链改进
- 配置CSS兼容性检查工具
- 自动化CSS类名转换
- 集成样式lint工具
### 3. 开发体验提升
- 提供CSS类名智能提示
- 建立样式组件库
- 优化开发调试工具
## 总结
通过系统性地修复CSS兼容性问题,项目现在能够在小程序环境中正常运行。主要修复了以下几类问题:
1. **垂直间距类名**:`space-y-*` → `mb-*`
2. **显示方式类名**:`inline-block` → 内联样式
3. **文字处理类名**:`break-all` → 内联样式
4. **固定尺寸类名**:`w-* h-*` → 内联样式
这些修复确保了代码在小程序环境中的兼容性,同时保持了良好的代码可读性和维护性。

226
src/user/gift/debug-tab.tsx

@ -1,226 +0,0 @@
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

201
src/user/gift/demo.tsx

@ -1,201 +0,0 @@
import React, { useState } from 'react'
import { View, Text } from '@tarojs/components'
import { Button, Tabs, TabPane } from '@nutui/nutui-react-taro'
import GiftCard from '@/components/GiftCard'
import { ShopGift } from '@/api/shop/shopGift/model'
const GiftCardDemo: React.FC = () => {
const [activeTab, setActiveTab] = useState('0')
// 模拟不同类型的礼品卡数据
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,
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: 'MCD2024987654321',
goodsId: 102,
faceValue: '50',
type: 20,
useStatus: 0,
expireTime: '2024-10-31 23:59:59',
instructions: '适用于全国麦当劳门店,不可与其他优惠同享',
contactInfo: '400-517-517'
},
{
id: 3,
name: '海底捞火锅券',
goodsName: '海底捞火锅代金券',
goodsImage: 'https://img.alicdn.com/imgextra/i3/2206571109/O1CN01DEF456_!!2206571109.jpg',
description: '享受正宗川味火锅',
code: 'HDL2024555666777',
goodsId: 103,
faceValue: '200',
type: 30,
useStatus: 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.goodsName || gift.name || '礼品卡',
description: gift.description || gift.instructions,
code: gift.code,
goodsImage: gift.goodsImage,
faceValue: gift.faceValue,
type: gift.type,
useStatus: gift.useStatus,
expireTime: gift.expireTime,
useTime: gift.useTime,
useLocation: gift.useLocation,
contactInfo: gift.contactInfo,
goodsInfo: {
...(gift.goodsId && {
specification: `礼品卡面值:¥${gift.faceValue}`,
category: getTypeText(gift.type),
tags: [
getTypeText(gift.type),
gift.useStatus === 0 ? '可使用' : gift.useStatus === 1 ? '已使用' : '已过期',
'全国通用',
gift.type === 20 ? '即买即用' : '需预约'
].filter(Boolean),
instructions: gift.instructions ? [gift.instructions] : [
'请在有效期内使用',
'出示兑换码即可使用',
'不可兑换现金'
],
notices: [
'礼品卡一经使用不可退换',
'请妥善保管兑换码',
'如有疑问请联系客服'
]
})
},
showCode: gift.useStatus === 0,
showUseBtn: gift.useStatus === 0,
showDetailBtn: true,
showGoodsDetail: true,
theme: getThemeByType(gift.type),
onUse: () => handleUse(gift),
onDetail: () => handleDetail(gift),
onClick: () => handleClick(gift)
}
}
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 'silver'
}
}
const handleUse = (gift: ShopGift) => {
console.log('使用礼品卡:', gift.goodsName)
}
const handleDetail = (gift: ShopGift) => {
console.log('查看详情:', gift.goodsName)
}
const handleClick = (gift: ShopGift) => {
console.log('点击礼品卡:', gift.goodsName)
}
// 根据状态筛选礼品卡
const getFilteredGifts = () => {
const statusMap = {
'0': 0, // 可用
'1': 1, // 已使用
'2': 2 // 已过期
}
const targetStatus = statusMap[activeTab as keyof typeof statusMap]
return mockGifts.filter(gift => gift.useStatus === targetStatus)
}
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>
</View>
{/* Tab切换 */}
<View className="bg-white">
<Tabs value={activeTab} onChange={setActiveTab}>
<TabPane title="可用" value="0" />
<TabPane title="已使用" value="1" />
<TabPane title="已过期" value="2" />
</Tabs>
</View>
{/* 礼品卡列表 */}
<View className="p-4">
{getFilteredGifts().map((gift) => (
<View key={gift.id} className="mb-4">
<GiftCard {...transformGiftData(gift)} />
</View>
))}
{getFilteredGifts().length === 0 && (
<View className="text-center py-16">
<Text className="text-gray-500">
{activeTab === '0' ? '暂无可用礼品卡' :
activeTab === '1' ? '暂无已使用礼品卡' :
'暂无已过期礼品卡'}
</Text>
</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"> goodsName</Text>
<Text className="text-sm text-gray-600"> goodsImage</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 GiftCardDemo

2
src/user/gift/detail.tsx

@ -324,8 +324,6 @@ const GiftCardDetail = () => {
visible={showQRCode}
onClose={() => setShowQRCode(false)}
qrContent={gift.code || ''}
giftName={gift.goodsName || gift.name}
faceValue={gift.faceValue}
/>
)}
</ConfigProvider>

213
src/user/gift/final-css-fix-summary.md

@ -1,213 +0,0 @@
# 最终CSS兼容性修复总结
## 问题描述
项目启动时遇到WXSS编译错误:
```
[ WXSS 文件编译错误]
/app-origin.wxss(165:2): unexpected '\' at pos 6023
```
## 根本原因
使用了小程序不支持的CSS类名,主要包括:
1. `space-y-*` - 垂直间距类名
2. `gap-*` - Flexbox间距类名
3. `inline-block` - 显示方式类名
4. `break-all` - 文字换行类名
5. `w-* h-*` - 固定尺寸类名
## 完整修复方案
### 1. SimpleQRCodeModal.tsx 修复
#### 修复前的问题代码:
```typescript
// 问题1: gap-3 类名
<View className="flex gap-3">
// 问题2: inline-block 类名
<View className="inline-block p-4 bg-white border border-gray-200 rounded-lg">
// 问题3: break-all 类名
<Text className="text-base font-mono text-blue-800 break-all">
// 问题4: w-48 h-48 固定尺寸类名
<View className="w-48 h-48 bg-gray-100 rounded flex items-center justify-center">
```
#### 修复后的代码:
```typescript
// 修复1: 使用 mr-3 替代 gap-3
<View className="flex">
<Button className="flex-1 mr-3">关闭</Button>
<Button className="flex-1">刷新</Button>
</View>
// 修复2: 移除 inline-block,使用内联样式
<View className="p-4 bg-white border border-gray-200 rounded-lg">
// 修复3: 使用内联样式替代 break-all
<Text className="text-base font-mono text-blue-800" style={{ wordBreak: 'break-all' }}>
// 修复4: 使用内联样式替代固定尺寸类名
<View
className="bg-gray-100 rounded flex items-center justify-center"
style={{ width: '200px', height: '200px', margin: '0 auto' }}
>
```
### 2. simple-qrcode-demo.tsx 修复
#### 修复前:
```typescript
<View className="space-y-1">
<Text className="text-sm text-gray-600">✅ 简洁的二维码弹窗设计</Text>
<Text className="text-sm text-gray-600">✅ 显示礼品卡code码内容</Text>
</View>
```
#### 修复后:
```typescript
<View>
<Text className="text-sm text-gray-600 block mb-1">✅ 简洁的二维码弹窗设计</Text>
<Text className="text-sm text-gray-600 block mb-1">✅ 显示礼品卡code码内容</Text>
</View>
```
### 3. verification.tsx 修复
#### 修复前:
```typescript
<View className="flex gap-2">
<Input className="flex-1" />
<Button>验证</Button>
</View>
<View className="space-y-3">
<View className="flex justify-between">
```
#### 修复后:
```typescript
<View className="flex">
<Input className="flex-1 mr-2" />
<Button>验证</Button>
</View>
<View>
<View className="flex justify-between mb-3">
```
## 修复原则总结
### 1. 间距类名替换
- `space-y-1``block mb-1`
- `space-y-3``mb-3`
- `gap-2``mr-2`
- `gap-3``mr-3`
### 2. 显示方式替换
- `inline-block` → 移除或使用内联样式
- `break-all``style={{ wordBreak: 'break-all' }}`
### 3. 尺寸类名替换
- `w-48``style={{ width: '200px' }}`
- `h-48``style={{ height: '200px' }}`
### 4. 布局优化
- 保持Flexbox布局的基本功能
- 使用标准的margin/padding类名
- 复杂样式使用内联样式
## 修复的文件列表
1. **src/components/SimpleQRCodeModal.tsx**
- 移除Canvas相关复杂逻辑
- 修复所有不兼容的CSS类名
- 简化二维码显示逻辑
2. **src/user/gift/simple-qrcode-demo.tsx**
- 修复所有 `space-y-*` 类名
- 使用 `block mb-*` 替代
3. **src/user/store/verification.tsx**
- 修复 `gap-*` 类名
- 修复 `space-y-*` 类名
## 功能保持
修复后保持的功能:
- ✅ 二维码弹窗正常显示
- ✅ 复制兑换码功能正常
- ✅ 弹窗交互体验良好
- ✅ 响应式布局正常
- ✅ 视觉效果与预期一致
## 简化的功能
为了确保兼容性,简化了以下功能:
- 🔄 Canvas二维码生成 → 静态二维码图标显示
- 🔄 复杂的二维码绘制 → 简单的占位符显示
- 🔄 动态二维码刷新 → 静态内容显示
## 测试验证
### 编译测试
- ✅ 项目能够正常启动
- ✅ 没有WXSS编译错误
- ✅ 所有页面正常加载
### 功能测试
- ✅ 二维码弹窗正常打开和关闭
- ✅ 复制功能正常工作
- ✅ 按钮交互正常响应
- ✅ 样式显示符合预期
### 兼容性测试
- ✅ 小程序环境正常运行
- ✅ 不同设备尺寸适配正常
- ✅ 所有CSS类名都被小程序支持
## 预防措施
### 开发规范
1. **避免使用CSS框架特有类名**
- 不使用Tailwind CSS的工具类名
- 优先使用小程序支持的标准CSS
2. **使用兼容的替代方案**
- 间距:使用 `mb-*`, `mr-*` 等标准类名
- 尺寸:使用内联样式或标准CSS属性
- 布局:使用基础的Flexbox类名
3. **代码检查流程**
- 提交前进行编译测试
- 定期检查CSS兼容性
- 建立CSS类名白名单
## 后续优化建议
1. **建立样式规范**
- 创建小程序兼容的CSS类名库
- 建立统一的样式组件
2. **工具链改进**
- 配置CSS兼容性检查工具
- 自动化不兼容类名检测
3. **功能增强**
- 集成真实的二维码生成库
- 优化二维码显示效果
- 添加更多交互功能
## 总结
通过系统性地修复CSS兼容性问题,项目现在能够在小程序环境中正常启动和运行。主要成果:
1. **解决了编译错误**:移除了所有不兼容的CSS类名
2. **保持了功能完整性**:核心功能正常工作
3. **提升了兼容性**:确保在小程序环境中稳定运行
4. **建立了规范**:为后续开发提供了CSS兼容性指导
现在项目应该能够正常启动,二维码弹窗功能可以正常使用!

152
src/user/gift/goodsName-integration.md

@ -1,152 +0,0 @@
# GoodsName 字段集成说明
## 概述
后端已新增 `goodsName` 字段,前端已完成相应的集成工作,现在礼品卡组件可以正确显示商品名称。
## 修改内容
### 1. GiftCard 组件接口更新
**文件**: `src/components/GiftCard.tsx`
```typescript
export interface GiftCardProps {
id: number
name: string
goodsName?: string // 新增:商品名称字段
description?: string
// ... 其他字段
}
```
**显示逻辑**:
```typescript
// 获取显示名称,优先使用商品名称
const displayName = goodsName || name
// 在模板中使用
<Text className="title-text">{displayName}</Text>
```
### 2. 数据转换函数更新
**文件**: `src/user/gift/index.tsx`
```typescript
const transformGiftData = (gift: ShopGift): GiftCardProps => {
return {
id: gift.id || 0,
name: gift.name || '礼品卡',
goodsName: gift.goodsName, // 传递商品名称
// ... 其他字段映射
}
}
```
### 3. 类型定义更新
**文件**: `src/types/giftCard.ts`
```typescript
export interface GiftCardData {
id: number
name: string
goodsName?: string // 新增字段
// ... 其他字段
}
```
## 显示规则
### 优先级规则
1. **有 `goodsName`**: 显示商品名称
2. **无 `goodsName`**: 显示礼品卡名称 (`name`)
### 示例对比
| 数据情况 | name | goodsName | 显示结果 |
|---------|------|-----------|----------|
| 情况1 | "星巴克礼品卡" | "星巴克经典拿铁咖啡券" | "星巴克经典拿铁咖啡券" |
| 情况2 | "通用礼品卡" | null/undefined | "通用礼品卡" |
| 情况3 | "麦当劳优惠券" | "麦当劳巨无霸套餐券" | "麦当劳巨无霸套餐券" |
## 后端数据结构
确保后端返回的 `ShopGift` 对象包含 `goodsName` 字段:
```json
{
"id": 1,
"name": "星巴克礼品卡",
"goodsName": "星巴克经典拿铁咖啡券",
"goodsImage": "https://example.com/image.jpg",
"faceValue": "100",
"type": 20,
"useStatus": 0,
// ... 其他字段
}
```
## 测试验证
### 测试页面
访问测试页面验证显示效果:
- `/user/gift/goodsname-test` - 专门测试 goodsName 字段的页面
### 测试用例
1. **有商品名称的礼品卡**: 应显示 `goodsName` 的值
2. **无商品名称的礼品卡**: 应显示 `name` 的值
3. **不同状态的礼品卡**: 确保各种状态下名称显示正确
4. **长名称处理**: 确保长商品名称不会破坏布局
## 兼容性
### 向后兼容
- 对于没有 `goodsName` 字段的旧数据,组件会自动使用 `name` 字段
- 不会影响现有功能的正常使用
### 数据验证
```typescript
// 在组件中的处理逻辑
const displayName = goodsName || name || '礼品卡'
```
## 样式优化
### 长名称处理
```scss
.title-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200px;
}
```
### 响应式适配
- 小屏幕设备上自动调整显示宽度
- 保持良好的视觉效果
## 注意事项
1. **数据完整性**: 建议后端确保重要礼品卡都有 `goodsName` 字段
2. **名称长度**: 商品名称不宜过长,建议控制在20个字符以内
3. **特殊字符**: 确保商品名称不包含可能影响显示的特殊字符
4. **多语言**: 如需支持多语言,`goodsName` 也需要相应的国际化处理
## 后续优化建议
1. **搜索功能**: 在搜索时同时匹配 `name``goodsName`
2. **排序功能**: 支持按商品名称排序
3. **筛选功能**: 支持按是否有商品名称筛选
4. **统计功能**: 统计有商品名称的礼品卡比例
## 部署检查清单
- [ ] 后端 API 返回 `goodsName` 字段
- [ ] 前端组件正确显示商品名称
- [ ] 测试页面验证通过
- [ ] 兼容性测试通过
- [ ] 样式在各设备上显示正常
- [ ] 长名称处理正确

191
src/user/gift/goodsname-test.tsx

@ -1,191 +0,0 @@
import React from 'react'
import { View, Text } from '@tarojs/components'
import GiftCard from '@/components/GiftCard'
import { ShopGift } from '@/api/shop/shopGift/model'
const GoodsNameTest: React.FC = () => {
// 测试数据:包含 goodsName 字段的礼品卡
const testGifts: 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,
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: 'MCD2024987654321',
goodsId: 102,
faceValue: '50',
type: 20,
useStatus: 0,
expireTime: '2024-10-31 23:59:59',
instructions: '适用于全国麦当劳门店',
contactInfo: '400-517-517'
},
{
id: 3,
name: '通用礼品卡',
// 没有 goodsName,应该显示 name
goodsImage: 'https://img.alicdn.com/imgextra/i3/2206571109/O1CN01DEF456_!!2206571109.jpg',
description: '通用型礼品卡',
code: 'GEN2024555666777',
faceValue: '200',
type: 10,
useStatus: 0,
expireTime: '2024-11-30 23:59:59',
instructions: '可在指定商户使用',
contactInfo: '400-123-456'
},
{
id: 4,
name: '海底捞火锅券',
goodsName: '海底捞4人套餐券', // 已使用状态的商品
goodsImage: 'https://img.alicdn.com/imgextra/i4/2206571109/O1CN01GHI789_!!2206571109.jpg',
description: '享受正宗川味火锅',
code: 'HDL2024888999000',
goodsId: 103,
faceValue: '300',
type: 30,
useStatus: 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,
useStatus: gift.useStatus,
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),
gift.useStatus === 0 ? '可使用' : gift.useStatus === 1 ? '已使用' : '已过期',
...(gift.goodsName ? ['商品礼品卡'] : [])
].filter(Boolean),
instructions: gift.instructions ? [gift.instructions] : [
'请在有效期内使用',
'出示兑换码即可使用',
'不可兑换现金'
],
notices: [
'礼品卡一经使用不可退换',
'请妥善保管兑换码',
'如有疑问请联系客服'
]
})
},
showCode: gift.useStatus === 0,
showUseBtn: gift.useStatus === 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 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'
}
}
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">
goodsName
</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"> 1 goodsName"星巴克经典拿铁咖啡券"</Text>
<Text className="text-sm text-gray-600"> 2 goodsName"麦当劳巨无霸套餐券"</Text>
<Text className="text-sm text-gray-600"> 3 goodsName"通用礼品卡"</Text>
<Text className="text-sm text-gray-600"> 4使"海底捞4人套餐券"</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}: {gift.goodsName ? `goodsName="${gift.goodsName}"` : '无 goodsName'}
</Text>
<Text className="text-xs text-blue-600">
name="{gift.name}"
</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"> goodsName </Text>
<Text className="text-sm text-gray-600"> goodsName </Text>
<Text className="text-sm text-gray-600"> </Text>
<Text className="text-sm text-gray-600"> </Text>
</View>
</View>
</View>
)
}
export default GoodsNameTest

221
src/user/gift/qrcode-demo.tsx

@ -1,221 +0,0 @@
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

@ -1,212 +0,0 @@
# 礼品卡二维码核销功能实现说明
## 功能概述
将礼品卡的"立即使用"功能改为生成二维码,供门店工作人员扫码核销,提升用户体验和核销效率。
## 核心功能
### 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. **多门店支持**:支持多门店的核销管理

237
src/user/gift/status-field-migration.md

@ -1,237 +0,0 @@
# 礼品卡状态字段迁移说明
## 概述
根据后端字段调整,将礼品卡的状态字段从 `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

@ -1,219 +0,0 @@
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"> 0disabled样式</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

@ -1,198 +0,0 @@
# 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. **用户反馈**:收集用户使用反馈,持续优化体验

130
src/user/gift/test.tsx

@ -1,130 +0,0 @@
import React from 'react'
import { View } from '@tarojs/components'
import GiftCard from '@/components/GiftCard'
import { ShopGift } from '@/api/shop/shopGift/model'
const GiftCardTest: React.FC = () => {
// 模拟礼品卡数据,包含商品名称和图片
const mockGiftData: 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, // 虚拟礼品卡
useStatus: 0, // 可用
expireTime: '2024-12-31 23:59:59',
instructions: '请在有效期内使用,出示兑换码即可使用',
contactInfo: '400-800-8888',
createTime: '2024-08-01 10:00:00'
}
// 转换数据格式
const transformGiftData = (gift: ShopGift) => {
return {
id: gift.id || 0,
name: gift.goodsName || gift.name || '礼品卡', // 优先显示商品名称
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.goodsId && {
specification: `礼品卡面值:¥${gift.faceValue}`,
category: getTypeText(gift.type),
tags: [
getTypeText(gift.type),
gift.useStatus === 0 ? '可使用' : gift.useStatus === 1 ? '已使用' : '已过期',
'全国通用',
'即买即用'
].filter(Boolean),
instructions: gift.instructions ? [gift.instructions] : [
'请在有效期内使用',
'出示兑换码即可使用',
'不可兑换现金',
'可用于购买任意饮品和食品'
],
notices: [
'礼品卡一经使用不可退换',
'请妥善保管兑换码',
'如有疑问请联系客服',
'部分特殊商品可能不适用'
]
})
},
showCode: gift.useStatus === 0,
showUseBtn: gift.useStatus === 0,
showDetailBtn: true,
showGoodsDetail: true,
theme: 'green' as const,
onUse: () => console.log('使用礼品卡'),
onDetail: () => console.log('查看详情')
}
}
const getTypeText = (type?: number): string => {
switch (type) {
case 10: return '实物礼品卡'
case 20: return '虚拟礼品卡'
case 30: return '服务礼品卡'
default: return '礼品卡'
}
}
// 多个测试数据
const testGifts: ShopGift[] = [
{
...mockGiftData,
id: 1,
goodsName: '星巴克咖啡礼品卡(电子版)',
goodsImage: 'https://img.alicdn.com/imgextra/i1/2206571109/O1CN01QZxQJJ1Uw8QZxQJJ_!!2206571109.jpg'
},
{
...mockGiftData,
id: 2,
goodsName: '麦当劳优惠券套餐',
goodsImage: 'https://img.alicdn.com/imgextra/i2/2206571109/O1CN01ABC123_!!2206571109.jpg',
faceValue: '50',
type: 20,
useStatus: 0
},
{
...mockGiftData,
id: 3,
goodsName: '海底捞火锅券',
goodsImage: 'https://img.alicdn.com/imgextra/i3/2206571109/O1CN01DEF456_!!2206571109.jpg',
faceValue: '200',
type: 30,
useStatus: 1,
useTime: '2024-08-15 19:30:00'
}
]
return (
<View className="bg-gray-50 min-h-screen">
<View className="p-4">
<View className="text-lg font-bold mb-4 text-center">
</View>
{testGifts.map((gift, index) => (
<View key={gift.id} className="mb-4">
<GiftCard
{...transformGiftData(gift)}
/>
</View>
))}
</View>
</View>
)
}
export default GiftCardTest

145
src/user/gift/usage-example.md

@ -1,145 +0,0 @@
# 礼品卡商品信息显示使用说明
## 概述
已成功优化礼品卡组件,现在可以正确显示商品名称和图片。主要改进包括:
1. **优先显示商品名称**:`goodsName` 优先于 `name` 显示
2. **显示商品图片**:使用 `goodsImage` 字段显示商品图片
3. **丰富的商品信息**:包括规格、分类、标签、使用说明等
4. **更好的用户体验**:清晰的信息层次和视觉效果
## 数据结构
### ShopGift 模型中的关键字段
```typescript
interface ShopGift {
id?: number;
name?: string; // 礼品卡名称
goodsName?: string; // 商品名称(优先显示)
goodsImage?: string; // 商品图片
goodsId?: number; // 关联商品ID
description?: string; // 礼品卡描述
faceValue?: string; // 面值
type?: number; // 类型(10实物 20虚拟 30服务)
useStatus?: number; // 状态(0可用 1已使用 2已过期)
// ... 其他字段
}
```
## 代码实现
### 1. 数据转换函数
`src/user/gift/index.tsx` 中的 `transformGiftData` 函数已经优化:
```typescript
const transformGiftData = (gift: ShopGift): GiftCardProps => {
return {
id: gift.id || 0,
name: gift.goodsName || gift.name || '礼品卡', // 优先显示商品名称
description: gift.description || gift.instructions,
code: gift.code,
goodsImage: gift.goodsImage, // 商品图片
faceValue: gift.faceValue,
type: gift.type,
useStatus: gift.useStatus,
expireTime: gift.expireTime,
useTime: gift.useTime,
useLocation: gift.useLocation,
contactInfo: gift.contactInfo,
// 添加商品信息
goodsInfo: {
...(gift.goodsId && {
specification: `礼品卡面值:¥${gift.faceValue}`,
category: getTypeText(gift.type),
tags: [
getTypeText(gift.type),
gift.useStatus === 0 ? '可使用' : gift.useStatus === 1 ? '已使用' : '已过期'
].filter(Boolean),
instructions: gift.instructions ? [gift.instructions] : [
'请在有效期内使用',
'出示兑换码即可使用',
'不可兑换现金'
],
notices: [
'礼品卡一经使用不可退换',
'请妥善保管兑换码',
'如有疑问请联系客服'
]
})
},
showCode: gift.useStatus === 0,
showUseBtn: gift.useStatus === 0,
showDetailBtn: true,
showGoodsDetail: true, // 显示商品详情
theme: getThemeByType(gift.type),
onUse: () => handleUseGift(gift),
onDetail: () => handleGiftDetail(gift)
}
}
```
### 2. 类型文本获取函数
```typescript
const getTypeText = (type?: number): string => {
switch (type) {
case 10: return '实物礼品卡'
case 20: return '虚拟礼品卡'
case 30: return '服务礼品卡'
default: return '礼品卡'
}
}
```
## 显示效果
### 商品信息展示包括:
1. **基础信息**
- 商品名称(优先显示)
- 商品图片
- 礼品卡面值
- 商品分类
2. **详细信息**
- 商品规格
- 商品标签
- 使用说明
- 注意事项
3. **状态信息**
- 使用状态标识
- 过期时间提醒
- 兑换码显示
## 测试验证
可以使用测试页面验证显示效果:
```bash
# 访问测试页面
/user/gift/test
```
测试页面包含了不同类型和状态的礼品卡示例,可以验证:
- 商品名称正确显示
- 商品图片正常加载
- 商品信息完整展示
- 不同状态的样式效果
## 注意事项
1. **图片加载**:确保 `goodsImage` 字段包含有效的图片URL
2. **数据完整性**:建议在后端确保 `goodsName``goodsImage` 字段有值
3. **兼容性**:保持对旧数据的兼容,当 `goodsName` 为空时使用 `name` 字段
4. **性能优化**:大量礼品卡列表时注意图片懒加载
## 后续优化建议
1. **图片优化**:添加图片懒加载和占位符
2. **缓存机制**:对商品信息进行本地缓存
3. **错误处理**:添加图片加载失败的降级处理
4. **用户体验**:添加骨架屏和加载状态

1
src/user/gift/use.tsx

@ -127,7 +127,6 @@ const GiftCardUse = () => {
goodsImage: gift.goodsImage,
faceValue: gift.faceValue,
type: gift.type,
useStatus: gift.useStatus,
expireTime: gift.expireTime,
useTime: gift.useTime,
useLocation: gift.useLocation,

8
src/user/store/verification.tsx

@ -303,10 +303,10 @@ const StoreVerification: React.FC = () => {
<View className="bg-blue-50 mx-4 mb-4 p-4 rounded-lg">
<Text className="font-bold mb-2 text-gray-500"></Text>
<View>
<Text className="text-sm text-gray-500 block mb-1">1. </Text>
<Text className="text-sm text-gray-500 block mb-1">2. "扫描二维码"</Text>
<Text className="text-sm text-gray-500 block mb-1">3. </Text>
<Text className="text-sm text-gray-500 block">4. "确认核销"</Text>
<Text className="text-sm text-gray-500 mb-1">1. </Text>
<Text className="text-sm text-gray-500 mb-1">2. "扫描二维码"</Text>
<Text className="text-sm text-gray-500 mb-1">3. </Text>
<Text className="text-sm text-gray-500">4. "确认核销"</Text>
</View>
</View>
</View>

Loading…
Cancel
Save