Files
xinlong-shop-taro/src_bak/pages/points/exchange/index.tsx
赵忠林 1fa58040f3 feat(user): 新增收货地址管理及售后申请页面
- 新增地址类型定义,增强前端地址数据结构
- 新增地址编辑页面,支持地址智能识别和定位选点功能
- 地址编辑支持省市区选择及默认地址设置
- 新增地址列表页面,支持地址展示、删除、编辑和选择功能
- 实现售后申请页面,支持选择售后类型和退款原因
- 售后申请支持商品选择、退款金额计算和凭证上传
- 新增售后详情页面,支持售后状态展示及申请取消
- 优化页面加载和用户交互体验,增加错误提示和权限处理
2026-07-01 12:11:56 +08:00

227 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from 'react'
import { View, Text, ScrollView } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useRequest } from '@/hooks/useRequest'
import { listShopGoods, type ShopGoods } from '@/api/shop/shopGoods'
definePageConfig({
navigationBarTitleText: '积分兑换',
})
// 兑换分类
const categories = [
{ label: '全部', value: 0 },
{ label: '优惠券', value: 1 },
{ label: '实物商品', value: 2 },
{ label: '虚拟商品', value: 3 },
]
const ExchangePage: React.FC = () => {
const [activeCat, setActiveCat] = useState(0)
const [userPoints] = useState(1280) // 模拟用户积分
// 获取可兑换商品
const { data, loading } = useRequest(listShopGoods, {
defaultParams: [{ page: 1, limit: 20, exchangeType: 1 }], // exchangeType=1 表示积分兑换商品
onError: (err) => {
Taro.showToast({ title: err.message || '加载失败', icon: 'none' })
}
})
// 模拟兑换商品数据实际应该从API获取
const exchangeProducts = [
{
id: 1,
name: '满100减10优惠券',
points: 500,
image: '',
stock: 100,
type: 1,
description: '积分兑换专属优惠券',
},
{
id: 2,
name: '满200减25优惠券',
points: 1000,
image: '',
stock: 50,
type: 1,
description: '积分兑换专属优惠券',
},
{
id: 3,
name: '品牌定制水杯',
points: 2000,
image: '',
stock: 30,
type: 2,
description: '高品质保温水杯,限量兑换',
},
{
id: 4,
name: '精美帆布袋',
points: 1500,
image: '',
stock: 50,
type: 2,
description: '环保帆布袋,时尚实用',
},
{
id: 5,
name: '视频会员月卡',
points: 3000,
image: '',
stock: 20,
type: 3,
description: '主流视频平台会员月卡',
},
]
// 筛选商品
const filteredProducts = activeCat === 0
? exchangeProducts
: exchangeProducts.filter(p => p.type === activeCat)
// 处理兑换
const handleExchange = (product: typeof exchangeProducts[0]) => {
if (userPoints < product.points) {
Taro.showToast({ title: '积分不足', icon: 'none' })
return
}
if (product.stock <= 0) {
Taro.showToast({ title: '库存不足', icon: 'none' })
return
}
Taro.showModal({
title: '确认兑换',
content: `确定使用 ${product.points} 积分兑换「${product.name}」吗?`,
confirmText: '确认兑换',
confirmColor: '#0e932e',
success: (res) => {
if (res.confirm) {
Taro.showLoading({ title: '兑换中...' })
// 这里应该调用兑换API
setTimeout(() => {
Taro.hideLoading()
Taro.showToast({ title: '兑换成功', icon: 'success' })
}, 1500)
}
}
})
}
// 获取类型标签
const getTypeLabel = (type: number) => {
const map: Record<number, string> = { 1: '优惠券', 2: '实物商品', 3: '虚拟商品' }
return map[type] || '其他'
}
return (
<View className='min-h-screen bg-gray-50'>
{/* 用户积分信息 */}
<View className='p-4' style={{ background: 'linear-gradient(to right, #fb923c, #ef4444)' }}>
<View className='rounded-xl p-4' style={{ backgroundColor: 'rgba(255,255,255,0.2)' }}>
<Text className='text-white text-sm block mb-2'></Text>
<View className='flex items-baseline gap-1'>
<Text className='text-white text-4xl font-bold'>{userPoints}</Text>
<Text className='text-white text-sm opacity-80'></Text>
</View>
<View className='flex gap-4 mt-3'>
<View className='flex-1 rounded-lg p-2 text-center' style={{ backgroundColor: 'rgba(255,255,255,0.3)' }}>
<Text className='text-white text-xs block'></Text>
<Text className='text-white text-sm font-bold block mt-1'>3 </Text>
</View>
<View className='flex-1 rounded-lg p-2 text-center' style={{ backgroundColor: 'rgba(255,255,255,0.3)' }}>
<Text className='text-white text-xs block'></Text>
<Text className='text-white text-sm font-bold block mt-1'></Text>
</View>
</View>
</View>
</View>
{/* 分类标签 */}
<View className='bg-white flex overflow-x-auto'>
{categories.map(cat => (
<View
key={cat.value}
className={`flex-shrink-0 px-4 py-3 relative ${
activeCat === cat.value ? 'text-orange-500 font-medium' : 'text-gray-600'
}`}
onClick={() => setActiveCat(cat.value)}
>
<Text className='text-sm'>{cat.label}</Text>
{activeCat === cat.value && (
<View className='absolute bottom-0 left-0 right-0 flex justify-center'>
<View className='w-8 h-px bg-orange-500 rounded' />
</View>
)}
</View>
))}
</View>
{/* 商品列表 */}
<ScrollView className='flex-1'>
{filteredProducts.length === 0 ? (
<View className='text-center py-16'>
<Text className='text-4xl mb-3 block'>🎁</Text>
<Text className='text-sm text-gray-400'></Text>
</View>
) : (
<View className='p-3'>
{filteredProducts.map(product => {
const canExchange = userPoints >= product.points && product.stock > 0
return (
<View key={product.id} className='bg-white rounded-xl p-4 mb-3 shadow-sm'>
{/* 商品信息 */}
<View className='flex gap-3'>
{/* 商品图片 */}
<View className='w-20 h-20 bg-gray-100 rounded-lg flex items-center justify-center flex-shrink-0'>
<Text className='text-3xl'>🎁</Text>
</View>
{/* 商品详情 */}
<View className='flex-1'>
<Text className='text-sm text-gray-800 font-medium block mb-1'>{product.name}</Text>
<Text className='text-xs text-gray-400 block mb-2'>{product.description}</Text>
<View className='flex items-center gap-2'>
<View className='bg-orange-50 px-2 py-1 rounded'>
<Text className='text-xs text-orange-500 font-bold'>{product.points} </Text>
</View>
<View className='bg-blue-50 px-2 py-1 rounded'>
<Text className='text-xs text-blue-500'>{getTypeLabel(product.type)}</Text>
</View>
</View>
<View className='flex justify-between items-center mt-2'>
<Text className='text-xs text-gray-400'>: {product.stock}</Text>
<View
className={`px-3 py-1 rounded-full ${
canExchange ? 'bg-orange-500' : 'bg-gray-300'
}`}
onClick={() => canExchange && handleExchange(product)}
>
<Text className='text-xs text-white'>
{!canExchange
? userPoints < product.points ? '积分不足' : '库存不足'
: '立即兑换'
}
</Text>
</View>
</View>
</View>
</View>
</View>
)
})}
</View>
)}
</ScrollView>
</View>
)
}
export default ExchangePage