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

210
src_bak/pages/user/user.tsx Normal file
View File

@@ -0,0 +1,210 @@
import React, { useEffect, useState } from 'react'
import { View, Text, Image, ScrollView } from '@tarojs/components'
import Taro, { useDidShow } from '@tarojs/taro'
import { useUser } from '@/hooks/useUser'
import { useScrollHeight } from '@/hooks/useScrollHeight'
import { useRequest } from '@/hooks/useRequest'
import { getUserCardStats, getUserOrderStats, type UserCardStats, type UserOrderStats } from '@/api/shop/shopUserCard'
import { getMyClerk } from '@/api/shop/shopStoreUser'
import MemberBadge from '@/components/business/MemberBadge'
const UserPage: React.FC = () => {
const scrollHeight = useScrollHeight()
const { user, isLoggedIn, loading: userLoading, refreshUser, syncFromStorage } = useUser()
// 查询门店关联信息
const [storeInfo, setStoreInfo] = useState<any>(null)
// 获取用户卡片统计(余额/积分/优惠券/礼品卡)
const { data: cardStats, run: runCardStats, loading: cardStatsLoading } = useRequest(getUserCardStats, {
manual: true,
refreshDeps: [isLoggedIn]
})
// 获取用户订单统计
const { data: orderStats, run: runOrderStats, loading: orderStatsLoading } = useRequest(getUserOrderStats, {
manual: true,
refreshDeps: [isLoggedIn]
})
// 登录后加载数据
useEffect(() => {
if (isLoggedIn) {
runCardStats()
runOrderStats()
// 查询门店关联
getMyClerk().then(data => setStoreInfo(data)).catch(() => setStoreInfo(null))
} else {
setStoreInfo(null)
}
}, [isLoggedIn])
// 每次页面重新显示时(从登录页返回、从其他页面返回)检查登录状态并刷新数据
useDidShow(() => {
// 先从 storage 快速同步,立即更新 UI无网络延迟
const hasUser = syncFromStorage()
if (hasUser) {
runCardStats()
runOrderStats()
// 刷新门店关联
getMyClerk().then(data => setStoreInfo(data)).catch(() => setStoreInfo(null))
}
})
const menuItems = [
// { icon: '🏆', label: '赛事活动', url: '/pages/event/my/index' },
// { icon: '📅', label: '预约穿线', url: '/pages/booking/list/index' },
// { icon: '🎫', label: '优惠券', url: '/pages/user/coupon-list' },
{ icon: '💰', label: '我的钱包', url: '/pages/user/wallet' },
{ icon: '📍', label: '收货地址', url: '/pages/user/address-list' },
// { icon: '⭐', label: '积分明细', url: '/pages/user/points-record' },
{ icon: '❤️', label: '我的收藏', url: '/pages/user/favorite-list/index' },
// { icon: '🕐', label: '浏览历史', url: '/pages/user/history-list/index' },
{ icon: '❓', label: '帮助中心', url: '/pages/user/help-center/index' },
// { icon: '🔔', label: '消息通知', url: '/pages/index/notification' },
// 门店管理:仅门店经理/店员显示
// ...(storeInfo ? [{ icon: '🏪', label: '门店管理', url: '/pages/store/list/index' }] : []),
{ icon: '⚙️', label: '设置', url: '/pages/user/setting' },
]
const handleAvatarClick = () => {
if (!isLoggedIn) {
Taro.navigateTo({ url: '/passport/login' })
} else {
Taro.navigateTo({ url: '/pages/user/profile' })
}
}
const loading = cardStatsLoading || orderStatsLoading
// 下拉刷新状态
const [refreshing, setRefreshing] = useState(false)
// 下拉刷新:同时刷新用户信息、卡片统计、订单统计
const onRefresh = async () => {
if (!isLoggedIn) { setRefreshing(false); return }
setRefreshing(true)
try {
await Promise.all([refreshUser(), runCardStats(), runOrderStats()])
} finally {
setRefreshing(false)
}
}
return (
<View className='h-full bg-gray-50'>
<ScrollView scrollY refresherEnabled={!!isLoggedIn} refresherTriggered={refreshing} onRefresherRefresh={onRefresh} style={{ height: scrollHeight }}>
{/* 用户信息卡片 */}
<View className='mx-3 mt-3 p-4 bg-white rounded-xl'>
<View className='flex items-center gap-3' onClick={handleAvatarClick}>
{isLoggedIn && user?.avatar ? (
<Image className='w-14 h-14 rounded-full' src={user.avatar} mode='aspectFill' />
) : (
<View className='w-14 h-14 rounded-full bg-gray-100 flex items-center justify-center'>
<Text className='text-2xl text-gray-300'>👤</Text>
</View>
)}
<View className='flex-1'>
<Text className='text-lg font-medium text-gray-800 block'>
{isLoggedIn ? (user?.nickname || user?.phone || '用户') : '点击登录'}
</Text>
<View className='flex items-center gap-2 mt-1'>
{isLoggedIn && <MemberBadge levelName={(user as any)?.memberLevelName} />}
</View>
</View>
{isLoggedIn && <Text className='text-gray-300 text-sm'>{'>'}</Text>}
</View>
{/* 数据概览 */}
{isLoggedIn && (
<View className='grid grid-cols-3 gap-2 mt-4 pt-4 border-t border-gray-50'>
<View className='text-center' onClick={() => Taro.navigateTo({ url: '/pages/user/wallet' })}>
{loading ? (
<Text className='text-lg font-bold text-gray-300 block'>...</Text>
) : (
<Text className='text-lg font-bold text-gray-800 block'>
{cardStats?.balance || '0.00'}
</Text>
)}
<Text className='text-xs text-gray-400'></Text>
</View>
<View className='text-center' onClick={() => Taro.navigateTo({ url: '/pages/user/points-record' })}>
{loading ? (
<Text className='text-lg font-bold text-gray-300 block'>...</Text>
) : (
<Text className='text-lg font-bold text-gray-800 block'>
{cardStats?.points || (user as any)?.points || 0}
</Text>
)}
<Text className='text-xs text-gray-400'></Text>
</View>
<View className='text-center' onClick={() => Taro.navigateTo({ url: '/pages/user/coupon-list' })}>
{loading ? (
<Text className='text-lg font-bold text-gray-300 block'>...</Text>
) : (
<Text className='text-lg font-bold text-gray-800 block'>
{cardStats?.coupons || 0}
</Text>
)}
<Text className='text-xs text-gray-400'></Text>
</View>
</View>
)}
</View>
{/* 我的订单快捷入口 */}
<View className='bg-white rounded-xl mx-3 mt-3 p-4'>
<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>
</View>
<View className='grid grid-cols-4 gap-2'>
{[
{ icon: '💳', label: '待付款', status: 0, count: orderStats?.pending },
{ icon: '📦', label: '待发货', status: 1, count: orderStats?.paid },
{ icon: '🚚', label: '待收货', status: 2, count: orderStats?.shipped },
{ icon: '✅', label: '已完成', status: 3, count: orderStats?.completed },
].map(item => (
<View
key={item.status}
className='flex flex-col items-center py-2 relative'
onClick={() => Taro.navigateTo({ url: `/pages/order/list?tab=${item.status}` })}
>
<Text className='text-xl mb-1'>{item.icon}</Text>
<Text className='text-xs text-gray-600'>{item.label}</Text>
{/* 数量角标 */}
{item.count && item.count > 0 && (
<View className='absolute top-0 right-2 bg-red-500 text-white text-xs rounded-full min-w-4 h-4 flex items-center justify-center px-1'>
{item.count > 99 ? '99+' : item.count}
</View>
)}
</View>
))}
</View>
</View>
{/* 功能菜单 */}
<View className='bg-white rounded-xl mx-3 mt-3 overflow-hidden'>
{menuItems.map((item, idx) => (
<View
key={item.label}
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 })}
>
<View className='flex items-center gap-3'>
<Text className='text-base'>{item.icon}</Text>
<Text className='text-sm text-gray-700'>{item.label}</Text>
</View>
<Text className='text-gray-300 text-sm'>{'>'}</Text>
</View>
))}
</View>
<View className='h-6' />
</ScrollView>
</View>
)
}
export default UserPage