当前位置: 首页 > news >正文

实习小记(个人中心的编辑模块)

实习小记(个人中心的编辑模块)

项目需要加一个个人中心的编辑模块,也是差不多搞了一天下来,其中遇到了很多问题,也是来记录、分享一下。

技术栈:React、antd、TypeScript

在这里插入图片描述

需求
点击编辑,弹出编辑个人信息浮窗,调用后端接口渲染初始表单。其中头像是点击上传新头像,手机号是不可修改。

代码

import {Form,Modal,FormProps,Input,message,Avatar,Upload,Typography
} from 'antd'
import React, { useImperativeHandle, useState } from 'react'
import { UserOutlined } from '@ant-design/icons'
import { Users } from '@ysx-use/certificate-service'
import { useStateAsync } from '@ysx-use/hooks'
import { noop } from '@ysx-use/shared'
import { eventBus } from '@/components'
import { personalStore, logout } from '@/store'const { Text } = Typographyexport namespace PersonalModule {export interface FormModalInstance {show: (id?: number) => void}export interface FormModalProps {onSuccess?: () => void}export const FormModal = React.memo(React.forwardRef<FormModalInstance, FormModalProps>(({ onSuccess = noop }, ref) => {const [isModalOpen, setIsModalOpen] = useState(false)const [id, setId] = useState<number>()const [avatarUrl, setAvatarUrl] = useState<string>('') // 显示头像const [form] = Form.useForm<Users.Model>()useImperativeHandle(ref, () => ({show(id) {setId(id)form.resetFields()if (id) {Users.getMeInfo().then((res) => {const data = res.data || {}form.setFieldsValue({...data,password: undefined // 密码不显示})setAvatarUrl(data.avatar || '')})}setIsModalOpen(true)}}))const handleCancel = () => {setIsModalOpen(false)}const submit = useStateAsync(async (form: Users.Model) => {await Users.updatePersonalInfo({...form,password: form.password || undefined})personalStore.remove()message.success('修改成功,请重新登录')eventBus.emit('PersonalReload', id)logout()},{ immediate: false })const onFinish: FormProps<Users.Model>['onFinish'] = (values) => {submit.execute(values)}const onFinishFailed: FormProps<Users.Model>['onFinishFailed'] = (errorInfo) => {console.error('Failed:', errorInfo)}const onSubmit = () => {form.submit()}// 上传头像逻辑const importAvatar = useStateAsync(async (file: File) => {const res = await Users.importPersonalAvatar(file)const url = res?.data || ''if (url) {setAvatarUrl(url)form.setFieldsValue({ avatar: url })message.success('头像上传成功')} else {message.error('上传失败,请重试')}},{immediate: false})return (<Modaltitle="编辑个人信息"open={isModalOpen}onCancel={handleCancel}onOk={onSubmit}closable><Formstyle={{ marginTop: 16 }}form={form}layout="vertical"name="personal-form"onFinish={onFinish}onFinishFailed={onFinishFailed}autoComplete="off"><Form.Item label="头像" name="avatar"><div style={{ display: 'flex', alignItems: 'center', gap: 16 }}><Uploadaccept=""customRequest={() => {}}//beforeUpload={() => false} // 阻止默认上传showUploadList={false}onChange={(info) => {if (info.file.originFileObj) {importAvatar.execute(info.file.originFileObj)}}}maxCount={1}disabled={importAvatar.isLoading}name="file"><Avatarsize={64}icon={<UserOutlined />}src={avatarUrl}style={{ cursor: 'pointer' }}/></Upload><Text type="secondary">点击头像上传更换</Text></div></Form.Item><Form.Item<Users.Model>label="账户"name="username"rules={[{ required: true, message: '请输入账户名' }]}><Input placeholder="请输入账户名" /></Form.Item><Form.Item<Users.Model> label="手机号" name="mobile" extra="手机号一旦注册不可修改"><Input readOnly /></Form.Item><Form.Item<Users.Model> label="昵称" name="nickname"><Input placeholder="请输入昵称" /></Form.Item><Form.Item<Users.Model> label="密码" name="password"><Input.Password placeholder="如需修改请输入新密码" /></Form.Item></Form></Modal>)}))
}
  • 个人中心页(index.tsx) :用于展示用户信息。

  • 编辑弹窗模块(FormModal) :用于修改头像、昵称、密码等信息。

个人中心主页面

实现:

使用了antd pro(中后台管理)的组件PageContainer, ProCard, ProDescriptions

从状态管理中获取当前登录用户信息

通过 useRef 持有对弹窗组件的引用,以便调用 show(user_id) 弹出表单。

挂载
<PersonalModule.FormModal ref={formModal}></PersonalModule.FormModal>

点击编辑按钮时,调用 FormModal 暴露的 show 方法,打开弹窗。

通过 ProDescriptions 来渲染个人信息

个人中心的编辑弹窗

功能:

  • 支持修改昵称、密码、头像。

  • 上传头像后立即预览。

  • 保存后强制重新登录。

实现:

通过 useImperativeHandle 实现组件间命令式通信

useImperativeHandle(ref, () => ({show(id) {// 重置表单、加载用户数据}
}))

其中调用了获取用户信息接口去加载当前用户信息并填充到编辑表单中

头像上传
使用封装的 useStateAsync 来处理异步上传逻辑,增强可控性与状态追踪。
通过点击 Avatar,用户可直接上传图片替换头像,体验更友好。

更新个人信息后,清空用户状态并触发登出,强制用户重新登录以刷新身份数据。

然后遇到了很多问题

手机号不可修改

加下面注释

<Form.Item<Users.Model> label="手机号" name="mobile" extra="手机号一旦注册不可修改"><Input ></Input>{/* <div style={{ color: '  #C0C0C0', fontSize: '12px' }}>手机号一旦注册不可修改</div> */}
</Form.Item>

起初是直接给Input加了一个disabled
发现不行,不会被渲染初始值
因为:
Ant Design 的 Form.Item + Input disabled 组合有个限制:
被 disabled 的 Input 不会响应 setFieldsValue 的值变化,AntD 默认不更新它(这在受控组件中属于正常行为)。

改成了 readOnly

还有一个问题:<Form.Item> 内的 div 干扰了渲染

Ant Design 的 <Form.Item> 不支持你在其中直接放多个非表单组件子元素(如 <Input /> 之外的 <div>)。

这种用法会导致:

  • 表单字段渲染混乱(尤其是 setFieldsValue 失效或渲染错位);
  • 特别是你用的 form 是受控的,value 绑定会丢失。

可以使用 Form.Item 的 extra 属性,会在下方显示提示信息,渲染安全,不会干扰 Input

头像上传

首先这是又用了一个独立的接口,然后在Axios实例的请求拦截器那里要进行配置,在请求真正发出前对 config 进行处理。

instance.interceptors.request.use((config) => {// console.log(config)const { url } = configif (url && !whiteList.includes(url)) {const auth = accessStore.get().access_tokenif (auth) {config.headers.Authorization = `Bearer ${auth}`}} else {config.headers.Authorization = ''}if (url === Users.ApiUrls.ExportTemplate) {config.responseType = 'blob'config.headers['Content-Type'] = 'application/vnd.ms-csv'}if (url === Users.ApiUrls.ImportData|| url===Users.ApiUrls.ImportPersonalAvatar) {const formData = new FormData()formData.append('file', config.data.file)config.data = formDataconfig.headers['Content-Type'] = 'multipart/form-data'}return config},(error) => {return Promise.reject(error)}
)
  1. 鉴权处理:请求 URL 存在并且不在 whiteList 白名单中,请求头加 token

  2. 文件下载:设置响应类型与内容类型

  3. 文件上传:封装为 FormData

  4. 错误处理

组件的默认行为

完成后,发现了一个不知道哪里发出的 POST http://localhost:3000/personal 请求
发现了这个url是对应的个人中心页面,但应该是 GET 方法
所以应该是某个组件或 hook 在加载 /personal 页面时自动触发了一个不必要的 POST 请求。
然后发现是头像上传组件的 问题

<Form.Item label="头像" name="avatar"><div style={{ display: 'flex', alignItems: 'center', gap: 16 }}><Uploadaccept=""customRequest={() => {}}//beforeUpload={() => false} // 阻止默认上传showUploadList={false}onChange={(info) => {if (info.file.originFileObj) {importAvatar.execute(info.file.originFileObj)}}}maxCount={1}disabled={importAvatar.isLoading}name="file"><Avatarsize={64}icon={<UserOutlined />}src={avatarUrl}style={{ cursor: 'pointer' }}/></Upload><Text type="secondary">点击头像上传更换</Text></div>
</Form.Item>

如果 info.file.originFileObj 不存在,Upload 组件会默认走 action=“/personal” 来上传!
你没有显式指定 action 参数,所以 Ant Design 的 组件默认会使用当前页面地址 /personal 作为上传地址。

使用beforeUpload={() => false} // 阻止默认上传
加这个就不能上传本地图片了(都没有发起网络请求)

✅ 阻止 Ant Design Upload 组件的 自动上传行为

❌ 但不会触发 onChange 中的 info.file.originFileObj → 因为根本没触发上传流程

最后用customRequest={() => {}}
会触发onChange,适合手动上传

http://www.lryc.cn/news/604771.html

相关文章:

  • 商标注册后可以随意更改字体和颜色吗!
  • 怎么理解锁相环主时钟(PLL)怎么做到对时钟进行倍频?
  • Keil STM32工程各文件作用
  • AI框架工具FastRTC快速上手2——整体框架及Stream类详解
  • 方块世界:失落文明的遗产
  • Deforum Stable Diffusion,轻松实现AI视频生成自由!
  • 语音识别dolphin 学习笔记
  • UE5多人MOBA+GAS 番外篇:将冷却缩减属性应用到技能冷却中
  • 设计模式十四:适配器模式(Adapter Pattern)
  • Linux ps -ef 命令解析
  • 基于成像空间转录组技术的肿瘤亚克隆CNV原位推断方法
  • composer 常用命令
  • 智慧城市SaaS平台|市政公用管理系统
  • 从单机到分布式:Redis如何成为架构升级的胜负手
  • 串口接收数据包(协议带帧头帧尾)的编程实现方法:1、数据包格式定义结构体2、使用队列进行数据接收、校验解包
  • 三十二、【Linux网站服务器】搭建httpd服务器演示虚拟主机配置、网页重定向功能
  • uni-app webview 的message无法触发的解决方案
  • MacTex+Vscode数学建模排版
  • 字节跳动“扣子”(Coze)开源:AI智能体生态的技术革命
  • AI Compass前沿速览:可灵创意工坊、字节Coze StudioCoze Loop、通义万相2.2 、智谱GLM-4.5、腾讯混元3D世界模型开源
  • 添加捕捉吸附标识(使用QT+OpenGL开发三维CAD)
  • 【翻译】Label Studio——开源标注工具README.md
  • 2025年DDoS攻防战:六层防护体系构建业务“数字免疫”
  • ADA4622-2ARMZ-R7 ADI双通道精密运算放大器 ±0.25μV超低失调+0.1μV/°C温漂
  • K8s 备份与恢复利器:Velero 实战指南
  • click和touch事件触发顺序 糊里糊涂解决的奇怪bug
  • VUE -- 基础知识讲解(三)
  • 前端安全防护:XSS、CSRF与SQL注入漏洞深度解析与防御
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | 3dBackgroundBoxes(3D背景盒子组件)
  • 前端兼容性问题全面解决方案