feat(user): 新增收货地址管理及售后申请页面
- 新增地址类型定义,增强前端地址数据结构 - 新增地址编辑页面,支持地址智能识别和定位选点功能 - 地址编辑支持省市区选择及默认地址设置 - 新增地址列表页面,支持地址展示、删除、编辑和选择功能 - 实现售后申请页面,支持选择售后类型和退款原因 - 售后申请支持商品选择、退款金额计算和凭证上传 - 新增售后详情页面,支持售后状态展示及申请取消 - 优化页面加载和用户交互体验,增加错误提示和权限处理
This commit is contained in:
3
src_bak/pages/event/my/index.config.ts
Normal file
3
src_bak/pages/event/my/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
navigationBarTitleText: '我的报名',
|
||||
}
|
||||
170
src_bak/pages/event/my/index.tsx
Normal file
170
src_bak/pages/event/my/index.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
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
|
||||
Reference in New Issue
Block a user