- 新增地址类型定义,增强前端地址数据结构 - 新增地址编辑页面,支持地址智能识别和定位选点功能 - 地址编辑支持省市区选择及默认地址设置 - 新增地址列表页面,支持地址展示、删除、编辑和选择功能 - 实现售后申请页面,支持选择售后类型和退款原因 - 售后申请支持商品选择、退款金额计算和凭证上传 - 新增售后详情页面,支持售后状态展示及申请取消 - 优化页面加载和用户交互体验,增加错误提示和权限处理
171 lines
6.9 KiB
TypeScript
171 lines
6.9 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
||
import { View, Text, ScrollView } from '@tarojs/components'
|
||
import Taro, { useDidShow } from '@tarojs/taro'
|
||
import { getMyRegistrations, payEventRegistration, getPayStatusLabel } from '@/api/shop/shopEvent'
|
||
import type { EventRegistration, EventFormField } from '@/api/shop/shopEvent'
|
||
|
||
definePageConfig({ navigationBarTitleText: '我的报名' })
|
||
|
||
const MyRegistrationsPage: React.FC = () => {
|
||
const [list, setList] = useState<EventRegistration[]>([])
|
||
const [loading, setLoading] = useState(true)
|
||
const [paying, setPaying] = useState<number | null>(null)
|
||
|
||
useDidShow(() => {
|
||
fetchList()
|
||
})
|
||
|
||
const fetchList = async () => {
|
||
try {
|
||
const data = await getMyRegistrations()
|
||
setList(data || [])
|
||
} catch (err: any) {
|
||
Taro.showToast({ title: err.message || '加载失败', icon: 'none' })
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleRepay = async (reg: EventRegistration) => {
|
||
setPaying(reg.id)
|
||
try {
|
||
const wxParams = await payEventRegistration(reg.id)
|
||
// 后端返回已支付(回调丢失自动修复场景)
|
||
if ((wxParams as any).paid === 'true') {
|
||
Taro.showToast({ title: '支付成功', icon: 'success' })
|
||
setList(prev => prev.map(r => r.id === reg.id ? { ...r, payStatus: 1, paidAt: new Date().toISOString() } : r))
|
||
fetchList()
|
||
return
|
||
}
|
||
await Taro.requestPayment({
|
||
timeStamp: wxParams.timeStamp,
|
||
nonceStr: wxParams.nonceStr,
|
||
package: wxParams.package,
|
||
signType: wxParams.signType as any,
|
||
paySign: wxParams.paySign,
|
||
})
|
||
// 支付成功后立即更新本地状态,避免后端回调延迟导致仍显示"待缴费"
|
||
setList(prev => prev.map(r => r.id === reg.id ? { ...r, payStatus: 1, paidAt: new Date().toISOString() } : r))
|
||
Taro.showToast({ title: '支付成功', icon: 'success' })
|
||
fetchList()
|
||
} catch (err: any) {
|
||
if (!err?.errMsg?.includes('cancel')) {
|
||
Taro.showToast({ title: err.message || '支付失败', icon: 'none' })
|
||
}
|
||
} finally {
|
||
setPaying(null)
|
||
}
|
||
}
|
||
|
||
const formatDate = (dateStr: string) => {
|
||
const d = new Date(dateStr)
|
||
return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`
|
||
}
|
||
|
||
const fieldLabelMap: Record<string, string> = {
|
||
name: '姓名',
|
||
phone: '手机号',
|
||
idCard: '身份证号',
|
||
size: '服装尺码',
|
||
email: '邮箱',
|
||
gender: '性别',
|
||
age: '年龄',
|
||
address: '地址',
|
||
company: '单位/公司',
|
||
job: '职业',
|
||
remark: '备注',
|
||
emergencyContact: '紧急联系人',
|
||
emergencyPhone: '紧急联系人电话',
|
||
wechat: '微信号',
|
||
team: '团队/队伍',
|
||
}
|
||
|
||
if (loading) {
|
||
return (
|
||
<View className='min-h-screen bg-gray-50 flex items-center justify-center'>
|
||
<Text className='text-sm text-gray-400'>加载中...</Text>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<View className='min-h-screen bg-gray-50'>
|
||
<ScrollView scrollY className='flex-1'>
|
||
<View className='p-3'>
|
||
{list.length === 0 ? (
|
||
<View className='flex flex-col items-center justify-center py-20'>
|
||
<Text className='text-4xl mb-3'>🏆</Text>
|
||
<Text className='text-sm text-gray-400'>暂无报名记录</Text>
|
||
<View
|
||
className='mt-4 bg-orange-500 px-6 py-2 rounded-full'
|
||
onClick={() => Taro.navigateTo({ url: '/pages/event/list/index' })}
|
||
>
|
||
<Text className='text-sm text-white'>去报名赛事</Text>
|
||
</View>
|
||
</View>
|
||
) : (
|
||
list.map(reg => {
|
||
// 后端支付回调可能延迟,paidAt 有值即视为已缴费
|
||
const effectivePayStatus = reg.paidAt ? 1 : reg.payStatus
|
||
const payInfo = getPayStatusLabel(effectivePayStatus)
|
||
return (
|
||
<View key={reg.id} className='bg-white rounded-xl p-4 mb-3 shadow-sm'>
|
||
<View className='flex items-start justify-between mb-3'>
|
||
<Text className='text-base font-bold text-gray-800 flex-1 mr-2'>{reg.eventName || '赛事'}</Text>
|
||
<View className={`px-2 py-1 rounded ${effectivePayStatus === 1 ? 'bg-green-50' : effectivePayStatus === 0 ? 'bg-orange-50' : 'bg-gray-50'}`}>
|
||
<Text className={`text-xs ${payInfo.color}`}>{payInfo.label}</Text>
|
||
</View>
|
||
</View>
|
||
|
||
<View className='text-xs text-gray-500 space-y-1 mb-3'>
|
||
{/* 动态表单数据展示 */}
|
||
{(() => {
|
||
let formData: Record<string, string> = {}
|
||
if (reg.formData) {
|
||
if (typeof reg.formData === 'string') {
|
||
try { formData = JSON.parse(reg.formData) } catch { formData = {} }
|
||
} else {
|
||
formData = reg.formData as Record<string, string>
|
||
}
|
||
}
|
||
// 去重:过滤掉与英文 key 重复的中文 label key
|
||
const labelToKey: Record<string, string> = {}
|
||
Object.entries(fieldLabelMap).forEach(([k, v]) => { labelToKey[v] = k })
|
||
return Object.entries(formData).filter(([key]) => {
|
||
if (fieldLabelMap[key]) return true // 英文 key 保留
|
||
if (labelToKey[key]) return false // 中文 label key 跳过(会由英文 key 渲染)
|
||
return true // 自定义字段保留
|
||
}).map(([key, value]) => (
|
||
<Text key={key} className='block'>{fieldLabelMap[key] || key}:{value}</Text>
|
||
))
|
||
})()}
|
||
<Text className='block'>
|
||
报名费:{reg.entryFee > 0 ? `¥${reg.entryFee}` : '免费'}
|
||
</Text>
|
||
<Text className='block'>报名时间:{formatDate(reg.createTime)}</Text>
|
||
{reg.paidAt && <Text className='block'>缴费时间:{formatDate(reg.paidAt)}</Text>}
|
||
</View>
|
||
|
||
{effectivePayStatus === 0 && (
|
||
<View
|
||
className='bg-orange-500 text-center py-2 rounded-full'
|
||
style={{ opacity: paying === reg.id ? 0.6 : 1 }}
|
||
onClick={paying === reg.id ? undefined : () => handleRepay(reg)}
|
||
>
|
||
<Text className='text-sm text-white font-medium'>
|
||
{paying === reg.id ? '支付中...' : `重新支付 ¥${reg.entryFee}`}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
</View>
|
||
)
|
||
})
|
||
)}
|
||
</View>
|
||
</ScrollView>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
export default MyRegistrationsPage
|