- 新增地址类型定义,增强前端地址数据结构 - 新增地址编辑页面,支持地址智能识别和定位选点功能 - 地址编辑支持省市区选择及默认地址设置 - 新增地址列表页面,支持地址展示、删除、编辑和选择功能 - 实现售后申请页面,支持选择售后类型和退款原因 - 售后申请支持商品选择、退款金额计算和凭证上传 - 新增售后详情页面,支持售后状态展示及申请取消 - 优化页面加载和用户交互体验,增加错误提示和权限处理
204 lines
7.3 KiB
TypeScript
204 lines
7.3 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
||
import { View, Text, Image, Input, Button as TaroButton } from '@tarojs/components'
|
||
import Taro from '@tarojs/taro'
|
||
import BottomButton from '@/components/common/BottomButton'
|
||
import { useUser } from '@/hooks/useUser'
|
||
import { updateUser } from '@/api/system/user'
|
||
import type { User } from '@/api/system/user/model'
|
||
import { TenantId } from '@/config/app'
|
||
|
||
definePageConfig({
|
||
navigationBarTitleText: '个人信息',
|
||
})
|
||
|
||
const genderOptions = ['保密', '男', '女']
|
||
|
||
const ProfilePage: React.FC = () => {
|
||
const { user, refreshUser } = useUser()
|
||
const [form, setForm] = useState<Partial<User>>({})
|
||
const [loading, setLoading] = useState(false)
|
||
const [showGenderSheet, setShowGenderSheet] = useState(false)
|
||
|
||
useEffect(() => {
|
||
if (user) {
|
||
setForm({
|
||
userId: (user as any)?.userId || (user as any)?.id,
|
||
nickname: (user as any)?.nickname || '',
|
||
avatar: (user as any)?.avatar || '',
|
||
gender: (user as any)?.gender ?? 0,
|
||
phone: (user as any)?.phone || '',
|
||
})
|
||
}
|
||
}, [user])
|
||
|
||
// 微信头像选择回调(open-type="chooseAvatar")
|
||
const handleWechatAvatar = (e: any) => {
|
||
const { avatarUrl } = e.detail
|
||
if (!avatarUrl) return
|
||
Taro.showLoading({ title: '上传中...' })
|
||
Taro.uploadFile({
|
||
url: 'https://server.websoft.top/api/oss/upload',
|
||
filePath: avatarUrl,
|
||
name: 'file',
|
||
header: {
|
||
'content-type': 'application/json',
|
||
TenantId,
|
||
},
|
||
success: (uploadRes) => {
|
||
const data = JSON.parse(uploadRes.data)
|
||
if (data.code === 0 && data.data?.url) {
|
||
setForm(prev => ({ ...prev, avatar: data.data.url }))
|
||
Taro.showToast({ title: '上传成功', icon: 'success' })
|
||
} else {
|
||
Taro.showToast({ title: data.message || '上传失败', icon: 'none' })
|
||
}
|
||
},
|
||
fail: () => {
|
||
Taro.showToast({ title: '上传失败', icon: 'none' })
|
||
},
|
||
complete: () => {
|
||
Taro.hideLoading()
|
||
},
|
||
})
|
||
}
|
||
|
||
// 选择性别
|
||
const handleGenderSelect = (index: number) => {
|
||
setForm(prev => ({ ...prev, gender: index }))
|
||
setShowGenderSheet(false)
|
||
}
|
||
|
||
// 保存
|
||
const handleSave = async () => {
|
||
if (!form.nickname?.trim()) {
|
||
Taro.showToast({ title: '昵称不能为空', icon: 'none' })
|
||
return
|
||
}
|
||
setLoading(true)
|
||
try {
|
||
await updateUser(form as User)
|
||
await refreshUser()
|
||
Taro.showToast({ title: '保存成功', icon: 'success' })
|
||
} catch (error) {
|
||
console.error('保存失败:', error)
|
||
Taro.showToast({ title: '保存失败', icon: 'none' })
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const displayId = (user as any)?.userId || (user as any)?.id || ''
|
||
const registerTime = (user as any)?.createTime || (user as any)?.createdAt || ''
|
||
|
||
return (
|
||
<View className='min-h-screen bg-gray-50 pb-32'>
|
||
{/* 头像 - 使用 Taro 原生 Button 支持 open-type="chooseAvatar" */}
|
||
<View className='flex items-center justify-between px-4 py-3 bg-white border-b border-gray-50'>
|
||
<Text className='text-sm text-gray-700 w-20'>头像</Text>
|
||
<View className='flex items-center gap-2'>
|
||
<TaroButton
|
||
openType='chooseAvatar'
|
||
onChooseAvatar={handleWechatAvatar}
|
||
plain
|
||
style={{
|
||
padding: 0,
|
||
margin: 0,
|
||
background: 'transparent',
|
||
border: 'none',
|
||
lineHeight: '40px',
|
||
width: 'auto',
|
||
minHeight: 0,
|
||
}}
|
||
>
|
||
{form.avatar ? (
|
||
<Image className='w-10 h-10 rounded-full' src={form.avatar} mode='aspectFill' />
|
||
) : (
|
||
<View className='w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center'>
|
||
<Text className='text-gray-300 text-xs'>头像</Text>
|
||
</View>
|
||
)}
|
||
</TaroButton>
|
||
<Text className='text-gray-300 text-sm'>{'>'}</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 昵称 - 支持获取微信昵称 */}
|
||
<View className='flex items-center justify-between px-4 py-3 bg-white border-b border-gray-50'>
|
||
<Text className='text-sm text-gray-700 w-20'>昵称</Text>
|
||
<Input
|
||
className='flex-1 text-right text-sm text-gray-600'
|
||
placeholder='请输入昵称'
|
||
value={form.nickname || ''}
|
||
onInput={(e: any) => setForm(prev => ({ ...prev, nickname: e.detail.value }))}
|
||
maxlength={20}
|
||
type='nickname'
|
||
/>
|
||
</View>
|
||
|
||
{/* 性别 */}
|
||
<View className='flex items-center justify-between px-4 py-3 bg-white border-b border-gray-50' onClick={() => setShowGenderSheet(true)}>
|
||
<Text className='text-sm text-gray-700 w-20'>性别</Text>
|
||
<View className='flex items-center gap-2'>
|
||
<Text className='text-sm text-gray-600'>{genderOptions[form.gender ?? 0]}</Text>
|
||
<Text className='text-gray-300 text-sm'>{'>'}</Text>
|
||
</View>
|
||
</View>
|
||
|
||
{/* 手机号 - 不可编辑 */}
|
||
<View className='flex items-center justify-between px-4 py-3 bg-white border-b border-gray-50'>
|
||
<Text className='text-sm text-gray-700 w-20'>手机号</Text>
|
||
<Text className='text-sm text-gray-600'>{form.phone || '未绑定'}</Text>
|
||
</View>
|
||
|
||
{/* 用户ID */}
|
||
<View className='flex items-center justify-between px-4 py-3 bg-white border-b border-gray-50'>
|
||
<Text className='text-sm text-gray-700 w-20'>用户ID</Text>
|
||
<Text className='text-sm text-gray-600'>{displayId || '未设置'}</Text>
|
||
</View>
|
||
|
||
{/* 注册时间 */}
|
||
<View className='flex items-center justify-between px-4 py-3 bg-white border-b border-gray-50'>
|
||
<Text className='text-sm text-gray-700 w-20'>注册时间</Text>
|
||
<Text className='text-sm text-gray-600'>{registerTime || '未知'}</Text>
|
||
</View>
|
||
|
||
{/* 保存按钮 */}
|
||
<BottomButton
|
||
text='保存'
|
||
onClick={handleSave}
|
||
loading={loading}
|
||
disabled={loading}
|
||
/>
|
||
|
||
{/* 性别选择弹窗 */}
|
||
{showGenderSheet && (
|
||
<View className='fixed inset-0 z-50'>
|
||
<View className='absolute inset-0 bg-black bg-opacity-50' onClick={() => setShowGenderSheet(false)} />
|
||
<View className='absolute bottom-0 left-0 right-0 bg-white rounded-t-xl'>
|
||
<View className='px-4 py-3 border-b border-gray-50'>
|
||
<Text className='text-base font-medium text-gray-800'>选择性别</Text>
|
||
</View>
|
||
{genderOptions.map((label, index) => (
|
||
<View
|
||
key={index}
|
||
className='px-4 py-3 border-b border-gray-50 flex items-center justify-between'
|
||
onClick={() => handleGenderSelect(index)}
|
||
>
|
||
<Text className={`text-sm ${(form.gender ?? 0) === index ? 'text-green-600 font-medium' : 'text-gray-700'}`}>
|
||
{label}
|
||
</Text>
|
||
{(form.gender ?? 0) === index && <Text className='text-green-600'>✓</Text>}
|
||
</View>
|
||
))}
|
||
<View className='px-4 py-3' onClick={() => setShowGenderSheet(false)}>
|
||
<Text className='text-sm text-gray-400 text-center block'>取消</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)}
|
||
</View>
|
||
)
|
||
}
|
||
|
||
export default ProfilePage
|