feat(user): 新增收货地址管理及售后申请页面

- 新增地址类型定义,增强前端地址数据结构
- 新增地址编辑页面,支持地址智能识别和定位选点功能
- 地址编辑支持省市区选择及默认地址设置
- 新增地址列表页面,支持地址展示、删除、编辑和选择功能
- 实现售后申请页面,支持选择售后类型和退款原因
- 售后申请支持商品选择、退款金额计算和凭证上传
- 新增售后详情页面,支持售后状态展示及申请取消
- 优化页面加载和用户交互体验,增加错误提示和权限处理
This commit is contained in:
2026-07-01 12:11:56 +08:00
parent bf6ed504cc
commit 1fa58040f3
636 changed files with 58878 additions and 716 deletions

View File

@@ -7,6 +7,17 @@ import MemberBadge from '@/components/business/MemberBadge'
const UserPage: React.FC = () => {
const { user, isLoggedIn } = useUser()
// TabBar 页面列表
const tabBarPages = ['/pages/index/index', '/pages/shop/index', '/pages/order/list', '/pages/user/user']
const handleNavigate = (url: string) => {
if (tabBarPages.includes(url)) {
Taro.switchTab({ url })
} else {
Taro.navigateTo({ url })
}
}
const menuItems = [
{ icon: '📦', label: '我的订单', url: '/pages/order/list' },
{ icon: '📅', label: '预约订单', url: '/pages/booking/list' },
@@ -30,68 +41,56 @@ const UserPage: React.FC = () => {
<ScrollView scrollY className='flex-1'>
{/* 顶部渐变背景区域 */}
<View className='relative'>
{/* 渐变背景 — 绿色系 */}
{/* 渐变背景 */}
<View
className='absolute inset-0'
style={{
background: 'linear-gradient(135deg, #0d9488 0%, #059669 50%, #10b981 100%)',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%)',
}}
/>
{/* 装饰圆形 */}
<View
className='absolute'
style={{
width: '240px',
height: '240px',
borderRadius: '120px',
width: '200px',
height: '200px',
borderRadius: '100px',
background: 'rgba(255, 255, 255, 0.1)',
top: '-80px',
right: '-60px',
}}
/>
<View
className='absolute'
style={{
width: '150px',
height: '150px',
borderRadius: '75px',
background: 'rgba(255, 255, 255, 0.08)',
top: '-100px',
right: '-80px',
}}
/>
<View
className='absolute'
style={{
width: '180px',
height: '180px',
borderRadius: '90px',
background: 'rgba(255, 255, 255, 0.06)',
top: '-60px',
left: '-60px',
}}
/>
<View
className='absolute'
style={{
width: '120px',
height: '120px',
borderRadius: '60px',
background: 'rgba(255, 255, 255, 0.05)',
bottom: '20px',
right: '-30px',
top: '-40px',
left: '-40px',
}}
/>
{/* 用户信息卡片 */}
<View className='relative mx-3 mt-3 p-4 rounded-2xl' style={{
background: 'rgba(255, 255, 255, 0.15)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255, 255, 255, 0.2)',
background: 'rgba(255, 255, 255, 0.95)',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)',
}}>
<View className='flex items-center gap-4' onClick={handleLogin}>
{/* 头像区域 */}
<View className='relative'>
{isLoggedIn && user?.avatar ? (
<Image
className='w-16 h-16 rounded-full border-3 border-white/30'
style={{ boxShadow: '0 4px 16px rgba(0, 0, 0, 0.2)' }}
className='w-16 h-16 rounded-full border-4 border-white'
style={{ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)' }}
src={user.avatar}
mode='aspectFill'
/>
) : (
<View
className='w-16 h-16 rounded-full border-3 border-white/30 flex items-center justify-center'
style={{ background: 'rgba(255, 255, 255, 0.25)', boxShadow: '0 4px 16px rgba(0, 0, 0, 0.2)' }}
className='w-16 h-16 rounded-full border-4 border-white flex items-center justify-center'
style={{ background: 'linear-gradient(135deg, #667eea, #764ba2)', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)' }}
>
<Text className='text-2xl text-white'>👤</Text>
</View>
@@ -100,7 +99,7 @@ const UserPage: React.FC = () => {
{isLoggedIn && user?.memberLevelName && (
<View
className='absolute -bottom-1 -right-1 px-1 py-1 rounded text-xs text-white'
style={{ background: 'linear-gradient(135deg, #fbbf24, #f59e0b)' }}
style={{ background: 'linear-gradient(135deg, #f093fb, #f5576c)' }}
>
VIP
</View>
@@ -110,7 +109,7 @@ const UserPage: React.FC = () => {
{/* 用户信息 */}
<View className='flex-1'>
<View className='flex items-center gap-2'>
<Text className='text-lg font-bold text-white'>
<Text className='text-lg font-bold text-gray-800'>
{isLoggedIn ? (user?.nickname || user?.phone || '用户') : '点击登录'}
</Text>
{isLoggedIn && user?.memberLevelName && (
@@ -118,59 +117,50 @@ const UserPage: React.FC = () => {
)}
</View>
{isLoggedIn ? (
<Text className='text-xs text-white/60 mt-1'>ID: {(user as any)?.id || '暂无'}</Text>
<Text className='text-xs text-gray-400 mt-1'>ID: {(user as any)?.id || '暂无'}</Text>
) : (
<Text className='text-xs text-white/60 mt-1'></Text>
<Text className='text-xs text-gray-400 mt-1'></Text>
)}
</View>
{/* 箭头 */}
<View className='w-6 h-6 rounded-full bg-white/20 flex items-center justify-center'>
<Text className='text-white/80 text-xs'>{'>'}</Text>
<View className='w-6 h-6 rounded-full bg-gray-100 flex items-center justify-center'>
<Text className='text-gray-400 text-xs'>{'>'}</Text>
</View>
</View>
{/* 数据概览 — 4列白色文字 */}
{/* 数据概览 */}
{isLoggedIn && (
<View
className='grid grid-cols-4 gap-2 mt-4 pt-4'
style={{ borderTop: '1px solid rgba(255, 255, 255, 0.2)' }}
className='grid grid-cols-3 gap-2 mt-4 pt-4 rounded-xl'
style={{ background: 'rgba(102, 126, 234, 0.05)' }}
>
<View
className='text-center py-2'
onClick={() => Taro.navigateTo({ url: '/pages/user/wallet' })}
className='text-center py-2 rounded-lg'
onClick={() => Taro.switchTab({ url: '/pages/points/index' })}
>
<Text className='text-xl font-bold text-white'>
¥{((user as any)?.balance || '0.00')}
</Text>
<Text className='text-xs text-white/70 mt-1 block'></Text>
</View>
<View
className='text-center py-2'
onClick={() => Taro.navigateTo({ url: '/pages/points/index' })}
>
<Text className='text-xl font-bold text-white'>
<Text className='text-xl font-bold text-transparent' style={{ background: 'linear-gradient(135deg, #667eea, #764ba2)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
{(user as any)?.points || 0}
</Text>
<Text className='text-xs text-white/70 mt-1 block'></Text>
<Text className='text-xs text-gray-500 mt-1'></Text>
</View>
<View
className='text-center py-2'
className='text-center py-2 rounded-lg'
onClick={() => Taro.navigateTo({ url: '/pages/user/coupon-list' })}
>
<Text className='text-xl font-bold text-white'>
<Text className='text-xl font-bold text-transparent' style={{ background: 'linear-gradient(135deg, #f093fb, #f5576c)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
0
</Text>
<Text className='text-xs text-white/70 mt-1 block'></Text>
<Text className='text-xs text-gray-500 mt-1'></Text>
</View>
<View
className='text-center py-2'
onClick={() => Taro.navigateTo({ url: '/pages/order/list' })}
className='text-center py-2 rounded-lg'
onClick={() => Taro.switchTab({ url: '/pages/order/list' })}
>
<Text className='text-xl font-bold text-white'>
<Text className='text-xl font-bold text-transparent' style={{ background: 'linear-gradient(135deg, #4facfe, #00f2fe)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
0
</Text>
<Text className='text-xs text-white/70 mt-1 block'></Text>
<Text className='text-xs text-gray-500 mt-1'></Text>
</View>
</View>
)}
@@ -182,7 +172,7 @@ const UserPage: React.FC = () => {
<View className='bg-white rounded-xl mx-3 mt-3 p-4' style={{ boxShadow: '0 2px 8px rgba(0, 0, 0, 0.05)' }}>
<View className='flex justify-between items-center mb-3'>
<Text className='text-base font-medium text-gray-800'></Text>
<Text className='text-xs text-gray-400' onClick={() => Taro.navigateTo({ url: '/pages/order/list' })}>
<Text className='text-xs text-gray-400' onClick={() => Taro.switchTab({ url: '/pages/order/list' })}>
{'>'}
</Text>
</View>
@@ -197,7 +187,7 @@ const UserPage: React.FC = () => {
key={item.status}
className='flex flex-col items-center py-2 rounded-lg'
style={{ background: `${item.color}10` }}
onClick={() => Taro.navigateTo({ url: `/pages/order/list?tab=${item.status}` })}
onClick={() => { Taro.setStorageSync('order_tab', item.status); Taro.switchTab({ url: '/pages/order/list' }) }}
>
<Text className='text-xl mb-1'>{item.icon}</Text>
<Text className='text-xs text-gray-600'>{item.label}</Text>
@@ -213,7 +203,7 @@ const UserPage: React.FC = () => {
className={`flex items-center justify-between px-4 py-3 ${
idx < menuItems.length - 1 ? 'border-b border-gray-50' : ''
}`}
onClick={() => Taro.navigateTo({ url: item.url })}
onClick={() => handleNavigate(item.url)}
>
<View className='flex items-center gap-3'>
<Text className='text-base'>{item.icon}</Text>