feat(user): 新增收货地址管理及售后申请页面
- 新增地址类型定义,增强前端地址数据结构 - 新增地址编辑页面,支持地址智能识别和定位选点功能 - 地址编辑支持省市区选择及默认地址设置 - 新增地址列表页面,支持地址展示、删除、编辑和选择功能 - 实现售后申请页面,支持选择售后类型和退款原因 - 售后申请支持商品选择、退款金额计算和凭证上传 - 新增售后详情页面,支持售后状态展示及申请取消 - 优化页面加载和用户交互体验,增加错误提示和权限处理
This commit is contained in:
294
src_bak/pages/redeem.tsx
Normal file
294
src_bak/pages/redeem.tsx
Normal file
@@ -0,0 +1,294 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { View, Text, Input, ScrollView } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { useUser } from '@/hooks/useUser'
|
||||
import { useRequest } from '@/hooks/useRequest'
|
||||
import { useScrollHeight } from '@/hooks/useScrollHeight'
|
||||
import { useRechargeCode, pageRechargeCodeRecords } from '@/api/shop/shopRechargeCode'
|
||||
import type { ShopRechargeCode } from '@/api/shop/shopRechargeCode/model'
|
||||
import { getUserBalance } from '@/api/system/user'
|
||||
|
||||
definePageConfig({
|
||||
navigationBarTitleText: '兑换码充值',
|
||||
})
|
||||
|
||||
const RedeemPage: React.FC = () => {
|
||||
const { isLoggedIn } = useUser()
|
||||
const maxPopupHeight = useScrollHeight(0)
|
||||
|
||||
// 兑换码输入
|
||||
const [code, setCode] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [redeemSuccess, setRedeemSuccess] = useState(false)
|
||||
const [redeemAmount, setRedeemAmount] = useState('')
|
||||
|
||||
// 兑换记录
|
||||
const [records, setRecords] = useState<ShopRechargeCode[]>([])
|
||||
const [loadingRecords, setLoadingRecords] = useState(false)
|
||||
const [page, setPage] = useState(1)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [loadingMore, setLoadingMore] = useState(false)
|
||||
|
||||
// 兑换请求
|
||||
const { run: runUseCode } = useRequest(useRechargeCode, { manual: true })
|
||||
|
||||
// 加载兑换记录
|
||||
const loadRecords = async (isLoadMore = false) => {
|
||||
if (isLoadMore) {
|
||||
if (loadingMore || !hasMore) return
|
||||
setLoadingMore(true)
|
||||
} else {
|
||||
setLoadingRecords(true)
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await pageRechargeCodeRecords({ page: isLoadMore ? page + 1 : 1, limit: 10 })
|
||||
const newRecords = res?.records || []
|
||||
|
||||
if (isLoadMore) {
|
||||
setRecords(prev => [...prev, ...newRecords])
|
||||
setPage(prev => prev + 1)
|
||||
} else {
|
||||
setRecords(newRecords)
|
||||
}
|
||||
|
||||
setHasMore(newRecords.length >= 10)
|
||||
} catch (err) {
|
||||
console.error('加载兑换记录失败', err)
|
||||
} finally {
|
||||
setLoadingRecords(false)
|
||||
setLoadingMore(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
useEffect(() => {
|
||||
if (isLoggedIn) {
|
||||
loadRecords()
|
||||
}
|
||||
}, [isLoggedIn])
|
||||
|
||||
// 兑换
|
||||
const handleRedeem = async () => {
|
||||
const trimCode = code.trim().toUpperCase()
|
||||
if (!trimCode) {
|
||||
Taro.showToast({ title: '请输入兑换码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const res = await runUseCode(trimCode)
|
||||
setRedeemSuccess(true)
|
||||
setRedeemAmount(res?.amount || '0')
|
||||
Taro.showToast({ title: '兑换成功', icon: 'success' })
|
||||
|
||||
// 刷新余额(通过事件通知)
|
||||
const pages = Taro.getCurrentPages()
|
||||
const userPage = pages.find(p => p.route === 'pages/user/wallet')
|
||||
if (userPage) {
|
||||
userPage.onShow()
|
||||
}
|
||||
|
||||
// 刷新记录
|
||||
await loadRecords()
|
||||
} catch (err: any) {
|
||||
Taro.showToast({ title: err.message || '兑换失败,兑换码无效或已过期', icon: 'none' })
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
setCode('')
|
||||
setRedeemSuccess(false)
|
||||
setRedeemAmount('')
|
||||
}
|
||||
|
||||
// 复制兑换码
|
||||
const copyCode = (codeStr: string) => {
|
||||
Taro.setClipboardData({
|
||||
data: codeStr,
|
||||
success: () => {
|
||||
Taro.showToast({ title: '已复制', icon: 'success' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 滚动到底部加载更多
|
||||
const handleScrollToLower = () => {
|
||||
if (hasMore && !loadingMore) {
|
||||
loadRecords(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 状态文本
|
||||
const getStatusText = (status: number) => {
|
||||
const map: Record<number, string> = { 0: '未使用', 1: '已使用', 2: '已过期' }
|
||||
return map[status] || '未知'
|
||||
}
|
||||
|
||||
// 状态颜色
|
||||
const getStatusColor = (status: number) => {
|
||||
const map: Record<number, string> = { 0: 'text-blue-500', 1: 'text-green-500', 2: 'text-gray-400' }
|
||||
return map[status] || 'text-gray-400'
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='min-h-screen bg-gray-50 flex flex-col'>
|
||||
<ScrollView
|
||||
scrollY
|
||||
className='flex-1'
|
||||
onScrollToLower={handleScrollToLower}
|
||||
lowerThreshold={100}
|
||||
>
|
||||
<View className='p-3'>
|
||||
{/* 兑换说明卡片 */}
|
||||
<View className='bg-white rounded-xl p-4 mb-3'>
|
||||
<View className='flex items-start gap-3'>
|
||||
<Text className='text-2xl'>🎫</Text>
|
||||
<View className='flex-1'>
|
||||
<Text className='text-sm font-medium text-gray-800 block'>兑换说明</Text>
|
||||
<Text className='text-xs text-gray-500 block mt-1'>
|
||||
请输入管理员提供的兑换码,每个兑换码只能使用一次,兑换成功后余额将自动到账。
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 兑换表单 */}
|
||||
{!redeemSuccess ? (
|
||||
<View className='bg-white rounded-xl p-4'>
|
||||
<Text className='text-sm font-medium text-gray-800 mb-3 block'>兑换码充值</Text>
|
||||
|
||||
<View className='bg-gray-50 rounded-lg px-4 py-3 mb-4'>
|
||||
<Input
|
||||
className='text-sm'
|
||||
placeholder='请输入兑换码'
|
||||
value={code}
|
||||
onInput={(e) => setCode(e.detail.value)}
|
||||
maxlength={32}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View
|
||||
className={`py-3 px-4 rounded-lg text-center text-sm font-medium ${
|
||||
code.trim() && !submitting
|
||||
? 'bg-green-600 text-white'
|
||||
: 'bg-gray-200 text-gray-400'
|
||||
}`}
|
||||
onClick={handleRedeem}
|
||||
>
|
||||
{submitting ? '兑换中...' : '立即兑换'}
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
/* 兑换成功结果 */
|
||||
<View className='bg-white rounded-xl p-4'>
|
||||
<View className='text-center py-6'>
|
||||
<Text className='text-5xl block mb-4'>🎉</Text>
|
||||
<Text className='text-lg font-medium text-gray-800 block'>兑换成功!</Text>
|
||||
<Text className='text-sm text-gray-500 block mt-2'>
|
||||
已成功充值 ¥{redeemAmount},余额已到账
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View className='flex gap-3'>
|
||||
<View
|
||||
className='flex-1 py-3 px-4 rounded-lg text-center text-sm font-medium bg-gray-100 text-gray-700'
|
||||
onClick={resetForm}
|
||||
>
|
||||
继续兑换
|
||||
</View>
|
||||
<View
|
||||
className='flex-1 py-3 px-4 rounded-lg text-center text-sm font-medium bg-green-600 text-white'
|
||||
onClick={() => Taro.navigateTo({ url: '/pages/user/wallet' })}
|
||||
>
|
||||
查看余额
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 兑换记录 */}
|
||||
<View className='bg-white rounded-xl mt-3 overflow-hidden'>
|
||||
<View className='p-4 border-b border-gray-100 flex justify-between items-center'>
|
||||
<Text className='text-sm font-medium text-gray-800'>兑换记录</Text>
|
||||
<Text
|
||||
className='text-xs text-gray-400'
|
||||
onClick={() => loadRecords()}
|
||||
>
|
||||
刷新 🔄
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{loadingRecords ? (
|
||||
<View className='py-10 text-center'>
|
||||
<Text className='text-sm text-gray-400'>加载中...</Text>
|
||||
</View>
|
||||
) : records.length === 0 ? (
|
||||
<View className='py-10 text-center'>
|
||||
<Text className='text-4xl block mb-3'>📋</Text>
|
||||
<Text className='text-sm text-gray-400'>暂无兑换记录</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View>
|
||||
{records.map((record, idx) => (
|
||||
<View
|
||||
key={record.id}
|
||||
className={`p-4 flex justify-between items-center border-b border-gray-50 ${idx === records.length - 1 ? 'border-b-0' : ''}`}
|
||||
>
|
||||
<View className='flex-1'>
|
||||
<View className='flex items-center gap-2'>
|
||||
<Text className={`text-xs ${getStatusColor(record.status)}`}>
|
||||
{getStatusText(record.status)}
|
||||
</Text>
|
||||
{record.status === 1 && (
|
||||
<Text className='text-xs text-green-500'>
|
||||
+¥{Number(record.amount).toFixed(2)}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text
|
||||
className='text-xs text-gray-400 mt-1 block'
|
||||
onClick={() => copyCode(record.code)}
|
||||
>
|
||||
码: {record.code} 📋
|
||||
</Text>
|
||||
<Text className='text-xs text-gray-300 mt-1 block'>
|
||||
{record.usedAt || record.createTime}
|
||||
</Text>
|
||||
</View>
|
||||
{record.status === 1 && (
|
||||
<Text className='text-base font-bold text-green-500'>
|
||||
+¥{Number(record.amount).toFixed(2)}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
))}
|
||||
|
||||
{loadingMore && (
|
||||
<View className='py-3 text-center'>
|
||||
<Text className='text-xs text-gray-400'>加载更多...</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{!hasMore && records.length > 0 && (
|
||||
<View className='py-3 text-center'>
|
||||
<Text className='text-xs text-gray-400'>没有更多了</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View className='h-6' />
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default RedeemPage
|
||||
Reference in New Issue
Block a user