Files
template-10582/src/user/profile/profile.tsx
赵忠林 fc778e9de6 feat(user): 优化用户信息加载及表单体验
- 新增页面加载状态,加载中显示提示
- 等待 useUser 初始化完成后再加载用户数据
- 添加获取数据字典和用户信息的异常处理及错误提示
- 同步更新备注信息表单控件,修正校验提示文案
- 修正备注信息输入框的name属性,确保表单数据绑定正确
2026-04-29 17:07:14 +08:00

267 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {Cell, Avatar} from '@nutui/nutui-react-taro';
import {ArrowRight} from '@nutui/icons-react-taro'
import {useEffect, useState} from "react";
import {ConfigProvider} from '@nutui/nutui-react-taro'
import Taro, {getCurrentInstance} from '@tarojs/taro'
import {View, Text} from '@tarojs/components'
import {getUserInfo} from "@/api/layout";
import {TenantId} from "@/config/app";
import { TextArea } from '@nutui/nutui-react-taro'
import './profile.scss'
const {router} = getCurrentInstance()
import {
Form,
Button,
Input,
Radio,
} from '@nutui/nutui-react-taro'
import {DictData} from "@/api/system/dict-data/model";
import {pageDictData} from "@/api/system/dict-data";
import {User} from "@/api/system/user/model";
import {useUser} from "@/hooks/useUser";
// 类型定义
interface ChooseAvatarEvent {
detail: {
avatarUrl: string;
};
}
interface InputEvent {
detail: {
value: string;
};
}
function Profile() {
const formId = Number(router?.params.id)
const {user, updateUser, loading} = useUser()
const [sex, setSex] = useState<DictData[]>()
const [FormData, setFormData] = useState<User>(
{
userId: undefined,
nickname: undefined,
realName: undefined,
avatar: undefined,
sex: undefined,
phone: undefined,
address: undefined,
comments: undefined
}
)
const [pageLoading, setPageLoading] = useState(true)
const reload = () => {
// 获取数据字典
pageDictData({limit: 200}).then(res => {
setSex(res?.list.filter((item) => item.dictCode === 'sex'))
}).catch(err => {
console.error('获取数据字典失败:', err)
})
// 获取用户信息
getUserInfo().then((data) => {
// 更新表单数据
setFormData(data);
}).catch(err => {
console.error('获取用户信息失败:', err)
Taro.showToast({
title: '获取用户信息失败',
icon: 'none'
})
}).finally(() => {
setPageLoading(false)
})
}
// 提交表单
const submitSucceed = async (values: User) => {
console.log(values, 'values')
console.log(formId, 'formId>>')
try {
// 使用 useUser hook 的 updateUser 方法,它会自动更新状态和本地存储
await updateUser(values)
// 由于 useEffect 监听了 user 变化FormData 会自动同步更新
setTimeout(() => {
return Taro.navigateBack()
}, 1000)
} catch (error) {
// updateUser 方法已经处理了错误提示,这里不需要重复显示
console.error('提交表单失败:', error)
}
}
const submitFailed = (error: unknown) => {
console.log(error, 'err...')
}
const uploadAvatar = ({detail}: ChooseAvatarEvent) => {
// 先更新本地显示的头像
setFormData({
...FormData,
avatar: `${detail.avatarUrl}`,
})
Taro.uploadFile({
url: 'https://server.websoft.top/api/oss/upload',
filePath: detail.avatarUrl,
name: 'file',
header: {
'content-type': 'application/json',
TenantId
},
success: async (res) => {
const data = JSON.parse(res.data);
if (data.code === 0) {
try {
// 使用 useUser hook 的 updateUser 方法更新头像
await updateUser({
avatar: `${data.data.thumbnail}`
})
// 由于 useEffect 监听了 user 变化FormData 会自动同步更新
} catch (error) {
console.error('更新头像失败:', error)
// 如果更新失败,恢复原来的头像
setFormData({
...FormData,
avatar: user?.avatar || ''
})
}
}
},
fail: (error) => {
console.error('上传头像失败:', error)
Taro.showToast({
title: '上传失败',
icon: 'error'
})
// 恢复原来的头像
setFormData({
...FormData,
avatar: user?.avatar || ''
})
}
})
}
// 获取微信昵称
const getWxNickname = (nickname: string) => {
// 更新表单数据
setFormData({
...FormData,
nickname: nickname
});
}
// 等待 useUser 初始化完成后再加载数据
useEffect(() => {
if (!loading) {
reload()
}
}, [loading]);
// 监听 useUser hook 中的用户信息变化,同步更新表单数据
useEffect(() => {
if (user) {
setFormData(user)
}
}, [user]);
// 加载中显示
if (loading || pageLoading) {
return (
<View className={'flex justify-center items-center h-screen'}>
<Text>...</Text>
</View>
)
}
return (
<>
<div className={'p-4'}>
<Cell.Group>
<Cell title={'头像'} align={'center'} extra={
<>
<Button open-type="chooseAvatar" style={{height: '58px'}} onChooseAvatar={uploadAvatar}>
<Avatar src={FormData?.avatar} size="54"/>
</Button>
<ArrowRight color="#cccccc" className={'ml-1'} size={20}/>
</>
}
/>
<Cell title={'手机号码'} align={'center'} extra={FormData?.phone}/>
</Cell.Group>
<ConfigProvider>
<Form
divider
initialValues={FormData}
labelPosition="left"
onFinish={(values) => submitSucceed(values)}
onFinishFailed={(errors) => submitFailed(errors)}
footer={
<div
style={{
display: 'flex',
justifyContent: 'center',
width: '100%'
}}
>
<Button nativeType="submit" block type="info">
</Button>
</div>
}
>
<Form.Item
label={'昵称'}
name="nickname"
initialValue={FormData.nickname}
rules={[{message: '请获取微信昵称'}]}
>
<Input
type="nickname"
className="info-content__input"
placeholder="请输入昵称"
value={FormData?.nickname}
onInput={(e: InputEvent) => getWxNickname(e.detail.value)}
/>
</Form.Item>
<Form.Item
label="性别"
name="sex"
initialValue={FormData.sex}
rules={[
{message: '请选择性别'}
]}
>
<Radio.Group value={FormData?.sex} direction="horizontal">
{
sex?.map((item, index) => (
<Radio key={index} value={item.dictDataCode}>
{item.dictDataName}
</Radio>
))
}
</Radio.Group>
</Form.Item>
<Form.Item
label="备注信息"
name="comments"
initialValue={FormData.comments}
rules={[{message: '请输入备注信息'}]}
>
<TextArea
name="comments"
placeholder={'个性签名'}
value={FormData?.comments}
onChange={(value) => setFormData({...FormData, comments: value})}
/>
</Form.Item>
</Form>
</ConfigProvider>
</div>
</>
)
}
export default Profile