当前位置: 首页 > 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.dtcms.com/a/306929.html

相关文章:

  • C++提高编程学习--模板
  • 【python 获取邮箱验证码】模拟登录并获取163邮箱验证码,仅供学习!仅供测试!仅供交流!
  • jakarta.websocket.DeploymentException:Endpoint instance creation failed
  • 2021 年 NOI 最后一题题解
  • pandas 分组相同赋值1然后累加
  • PAT 甲级题目讲解:1011《World Cup Betting》
  • 【MySQL 数据库】MySQL索引特性(一)磁盘存储定位扇区InnoDB页
  • Linux c网络专栏第四章io_uring
  • 面向对象中级编程
  • DFT不同维度中gate、cell、instance介绍
  • RAG:检索增强生成的范式演进、技术突破与前沿挑战
  • pytorch入门2:利用pytorch进行概率预测
  • [NOIP2002 提高组] 均分纸牌
  • 破解海外仓客户响应难题:自动化系统是关键
  • 如何选择GEO优化公司哪家好?
  • MySQL学习---分库和分表
  • NSGA-III(非支配排序遗传算法 III)求解 7 目标的 DTLZ2 测试函数
  • DFT设计中的不同阶段介绍
  • 天地图Api4.0.怎么根据geojson的数据。把一个省的所有市区标记色块和文字
  • 【ssh】ubuntu服务器+本地windows主机,使用密钥对进行ssh链接
  • 怎么在 WSL CentOS8 与宿主机windows11 之间传递文件
  • 网络编程(C语言)
  • 从训练到推理:Intel Extension for PyTorch混合精度优化完整指南
  • 常见CMS获取webshell的方法-靶场练习
  • 集成电路学习:什么是ARM CortexM处理器核心
  • sqli-labs:Less-5关卡详细解析
  • STP技术
  • 应用驱动 协同创新:中国人工智能开启高质量发展新篇章
  • 深度学习与图像处理案例 │ 图像分类(智能垃圾分拣器)
  • 360环视技术推荐的硬件平台:支持多摄像头与三屏异显的理想选择