Skip to content

综合案例:个人设置 ​

本页对应 1-6综合案例-个人设置,共 5 段视频,约 54 分钟。

学习目标 ​

  • 能够根据接口文档,使用 axios 以查询参数(params)传递用户外号,发起 GET 请求获取个人设置数据,并从响应对象中正确取出 data 字段作为用户信息对象
  • 能够借助接口文档确认返回数据中每个属性的类型与含义,并运用 Object.keys 配合 forEach 遍历属性名,按标签类别(src / checked / value)分别完成头像、性别、邮箱昵称与个人简介的渲染
  • 能够为头像文件选择框绑定 change 事件获取用户所选文件,用 FormData 通过 append 追加文件与外号两个参数,调用修改头像接口,并将返回的新 URL 同步更新到页面图片上
  • 能够为提交按钮绑定点击事件,使用 form-serialize 收集表单数据,并按接口文档要求完成性别字段的整数类型转换与 creator 字段的对象合并,再以 PUT 方式提交
  • 能够使用 Bootstrap Toast 组件(获取 DOM 元素、new bootstrap.Toast() 实例化、调用 show())在接口成功回调中弹出操作成功提示,并通过 data-bs-delay 调整自动隐藏的停留时长
  • 能够通过浏览器网络面板与控制台验证请求参数、响应数据及刷新后的数据持久化结果,并说明提交数据不符合文档要求时服务器会返回校验失败错误

知识点 ​

案例总览:先看效果,再拆需求 ​

为什么需要:这是个人信息设置案例的介绍课,先要知道“最终要做出什么”,否则直接写代码容易迷路。
怎么想:先运行备课代码看效果,再把效果拆成可实现的步骤。转写中演示了三件事:点击“更换头像”选图后头像立刻变化;修改左侧昵称等信息后点“提交”,刷新页面仍然保留;如果改了但不提交,刷新会还原。
怎么做:本案例整体是“头像修改 + 信息修改”两大块,实际拆成 5 个视频:获取数据、渲染数据、修改头像、修改信息、提示框。需求可归纳为 3 个:信息渲染、修改头像、修改信息。
易错点:不要把“介绍课”当成完整实现课;本节重点是确认效果、步骤和验证方式。验证时常用刷新页面、看网络面板、看控制台输出。

信息渲染之一:获取数据 ​

为什么需要:页面打开后要显示“当前用户”的头像、邮箱、昵称、性别、简介,必须先拿到自己的数据。
怎么想:案例通过“外号”告诉服务器要取谁的数据。外号要定义成变量保存,因为后面修改头像、修改信息还要用同一个外号。
怎么做:按接口文档用 GET 请求,参数放在 params 里。先不写 then,在网络面板预览中确认“获取个人设置成功”,再打印 res,从 res.data 取用户对象存到 userInfo。
易错点:属性含义不要只靠猜单词,要看接口文档的响应结构;转写中反复强调“实际开发看接口文档”。外号变量名和值要在后续接口中保持一致。

js
const creta = '西兰花'
axios({ url: '按接口文档粘贴', params: { creta } }).then(res => {
  const userInfo = res.data
  console.log(userInfo)
})

信息渲染之二:渲染数据 ​

为什么需要:拿到数据后要显示到复杂表单里,包括图片、输入框、单选框、文本域。
怎么想:先找规律。转写中关键标签的类名与数据属性名一致,因此可用 Object.keys(userInfo) 拿到属性名,再 forEach 遍历。设置分三类:头像用 src;性别单选框用 checked;邮箱、昵称、简介用 value。
怎么做:遍历 key,用 if / else if / else 分类处理。性别要先用 querySelectorAll 拿到两个 radio,服务器返回的 gender 是整数:0 男、1 女,正好可作为数组索引。
易错点:querySelectorAll 得到的是类数组,要按索引取;动态属性用中括号;类选择器模板字符串别漏点;性别不是直接写“男/女”,要按文档的 0/1 处理。

js
Object.keys(userInfo).forEach(key => {
  if (key === 'avatar') {
    document.querySelector(`.${key}`).src = userInfo[key]
  } else if (key === 'gender') {
    const genderRadios = document.querySelectorAll(`.${key}`)
    const gender = userInfo[key]
    genderRadios[gender].checked = true
  } else {
    document.querySelector(`.${key}`).value = userInfo[key]
  }
})

修改头像:服务器更新后还要更新本地图片 ​

为什么需要:用户选图后不仅要传到服务器,页面也要立刻显示新头像。
怎么想:分三步:获取用户选择的文件、调用修改头像接口、更新本地图片 URL。接口参数比之前多一个外号,用 FormData 追加 avatar 和 creta。
怎么做:给文件输入注册 change 事件,打印 this.files 并取 [0];new FormData() 后 append 两个参数;请求成功后从返回数据中取新头像 URL,设置给 img.src。
易错点:文件索引 [0] 别漏;append 的参数名按文档写;服务器改了不代表页面改了;刷新验证能确认服务器是否真的保存。

js
const data = new FormData()
data.append('avatar', this.files[0])
data.append('creta', creta)
axios({ url: '按接口文档粘贴', method: 'put', data }).then(res => {
  const avatar = res.data.avatar
  document.querySelector('.avatar').src = avatar
})

修改信息:收集、转换、合并、提交 ​

为什么需要:左侧表单要整体提交,且提交数据必须符合接口文档。
怎么想:页面已准备好 form 和 name 属性,可用 form-serialize 快速收集。转写中对比文档发现两处不同:少一个外号 creta;gender 文档要求整数,而表单收集到的是字符串。
怎么做:点击提交后 serialize 得到 formData;用 +formData.gender 转数字;用展开运算符合并 creta 生成新对象;再按文档用 PUT 提交。
易错点:不转类型测试会报“校验失败”;个数、类型都要符合文档;不要忘记外号,否则服务器不知道改谁的数据。

js
const formData = serialize(form, { hash: true, empty: true })
formData.gender = +formData.gender
const data = { ...formData, creta }
axios({ url: '按接口文档粘贴', method: 'put', data }).then(res => {
  // 这里接提示框
})

提示框:只控制显示,自动隐藏 ​

为什么需要:提交成功后要反馈用户。
怎么想:使用 Bootstrap 的 Toast 组件。页面结构已准备好,JS 只需拿到 DOM、实例化、调用 show();隐藏会自动完成。
怎么做:new bootstrap.Toast(dom).show();显示时长由结构上的 data-bs-delay 控制,单位毫秒,转写中把 1500 改成 3000 测试 3 秒后消失。
易错点:Toast 的 T 大写;不用手动调隐藏;改延迟要改 HTML 自定义属性。

js
const toastDom = document.querySelector('.toast')
const toast = new bootstrap.Toast(toastDom)
toast.show()

小结 ​

本案例介绍课要建立整体地图:信息渲染先“获取”再“渲染”;修改头像要“传服务器 + 更新本地”;修改信息要“收集 + 按文档转换合并 + 提交 + 提示”。调试时多用网络面板、控制台和刷新验证;复杂表单不可怕,关键是找规律、看文档、按类型处理数据。

目标与效果 ​

本节只做“个人信息设置”案例的第一步:信息渲染。页面打开后,根据自己准备的外号,从服务器拿到属于自己的用户信息,再把头像、邮箱、昵称、性别、个人简介回显到页面上。

为什么需要先做信息渲染?因为后面的修改头像、修改信息都建立在“页面已经有正确用户数据”的基础上。怎么想?核心只有两步:获取数据 → 渲染数据。怎么做?先准备外号并调接口,确认数据能拿到;再用 Object.keys 遍历属性名,按标签类型分别设置。易错点:不要把“修改头像/修改信息”混进本节,本节只完成回显。

先准备外号 ​

为什么需要外号?转写稿里说得很清楚:想看“自己”的数据,又不想被别人影响,所以给自己起一个外号,并把这个外号告诉服务器。怎么想?外号后面修改头像、修改信息还会复用,最好用变量存起来,不要到处写字符串。怎么做?

js
// 转写中音近 create/creta,实际参数名以接口文档为准
// 这里用 creator 表示“外号”参数
const creator = '西兰花'

易错点:外号变量名要和接口参数名对应;后续请求要保持同一个外号,否则服务器不知道改的是谁的数据。

获取数据 ​

为什么需要先获取?页面要回显的内容不在本地写死,而是服务器返回的。怎么想?按接口文档确认请求方法、地址、参数位置。转写中参数在 query,所以用 params。怎么做?

js
axios({
  url: '接口文档中的地址',
  method: 'get',
  params: {
    creator
  }
}).then(res => {
  console.log(res)
  const userInfo = res.data.data
})

调试过程可以照转写稿走:保存后打开浏览器“网络”面板,点开请求,先看“预览”里是否提示获取成功,再看 data 里是否有“我是西兰花”的数据;然后在控制台打印 res,逐层点开找到用户信息对象。易错点:返回层级容易漏,用户信息通常是 res.data.data,不要直接把 res 当用户对象;params 不要写成 data。

看懂返回结构 ​

为什么需要看结构?因为要确定每个属性对应页面哪个位置。怎么想?转写稿强调:不要只靠猜单词,实际开发更推荐看接口文档的“返回响应/数据结构”。怎么做?核对返回对象中的 5 个属性:avatar、email、name、gender、desc,并确认 gender 是整数:0 表示男,1 表示女。易错点:gender 不是“男/女”字符串,而是数字;后面渲染单选框时要把它当索引使用。

渲染思路:利用类名与属性名一致 ​

为什么可以批量渲染?转写稿里页面关键标签的类名和数据属性名一致,例如头像类名 avatar、邮箱类名 email。怎么想?先用 Object.keys(userInfo) 拿到所有属性名,再 forEach 遍历,每个 key 去页面找对应标签。怎么做?

js
Object.keys(userInfo).forEach(key => {
  console.log(key)
})

易错点:Object.keys 返回数组;遍历时 key 是字符串;动态取对象属性要用 userInfo[key],不要写 userInfo.key。

头像、性别、文本三类分别设置 ​

为什么不能一套代码设置到底?因为标签类型不同:头像是 img,性别是 radio,邮箱/昵称/简介是输入类元素。怎么想?先分类再逐个击破。怎么做?

js
Object.keys(userInfo).forEach(key => {
  if (key === 'avatar') {
    document.querySelector(`.${key}`).src = userInfo[key]
  } else if (key === 'gender') {
    const genderRadios = document.querySelectorAll(`.${key}`)
    const gender = userInfo[key]
    genderRadios[gender].checked = true
  } else {
    document.querySelector(`.${key}`).value = userInfo[key]
  }
})

头像用 src;性别用 querySelectorAll 拿到两个单选框,再用 genderRadios[gender] 找到对应项,设置 checked = true;邮箱、昵称、个人简介统一设置 value。易错点:类选择器模板字符串别漏点;querySelectorAll 返回伪数组;性别不要设置 value,要设置 checked;头像不要设置 value,要设置 src。

调试与小结 ​

怎么验证?保存后刷新页面,看头像是否显示、邮箱昵称简介是否有值、性别是否选中对应项;若不对,回到控制台检查 userInfo 和每个 key。本节小结:信息渲染和图书列表渲染思路一样,都是“获取数据,然后渲染到页面”;区别是这里要根据标签类型选择 src、checked、value。易错点:接口返回层级、动态属性中括号、性别数字索引、类名选择器,是这一段最容易出错的地方。

整体思路:先改服务器,再改页面 ​

这个案例的目标是“修改用户头像并立刻生效”。转写稿里的操作顺序是:点击“更换头像”→选择图片→头像马上变;再刷新页面,头像仍然是新的。这说明服务器已经保存成功,不只是本地预览。

为什么需要:如果只把图片显示到页面,刷新就会丢;如果只传服务器不更新页面,用户会以为没成功。
怎么想:先拿到用户选择的文件,再按接口文档把“头像文件 + 外号”放进 FormData,调用修改头像接口;服务器返回新头像 URL 后,再设置到页面图片上。
怎么做:分四步——获取文件、组织 FormData、调接口、更新页面 URL。
易错点:不要把整个 files 伪数组传过去;外号必须和获取个人信息时一致;接口参数名以文档为准,转写稿中“creta/create”一类读法要注意同音/听写误差。

知识点一:获取用户选择的头像文件 ​

为什么需要:上传的前提是先拿到用户真正选中的那个文件。
怎么想:点击“更换头像”只是标签交互,真正变化的是关联的 input[type=file];文件选择变化应监听 change 事件。
怎么做:给头像输入框绑定 change,在回调里用 this.files[0] 取第一个文件。转写稿里“劝举事件”应纠正为 change 事件,“尾数组”应理解为伪数组。
易错点:回调写成箭头函数时 this 不是输入框;要取 [0],不是把 this.files 整体上传。

js
avatarInput.addEventListener('change', function () {
  const file = this.files[0]
  console.log(file)
})

调试时先看控制台:选择图片后应打印出一个文件对象;若打印的是伪数组,说明还没取索引 0。

知识点二:用 FormData 携带头像和外号 ​

为什么需要:修改头像不是只传文件,还要告诉服务器“改谁的头像”。笔记小结也强调:头像要保存在某个用户名下,所以要把外号一起携带过去。
怎么想:接口文档要求两个参数:头像文件和外号。FormData 正好可以追加多组键值。
怎么做:先 new FormData(),再按文档参数名 append 头像文件和外号。转写稿里“方 data”应纠正为 FormData。
易错点:参数名不能自己猜;外号变量要和获取数据时一致;append 第一个参数是接口字段名,第二个才是值。

js
const formData = new FormData()
formData.append('avatar', this.files[0])
formData.append('外号参数名', creator) // 参数名以接口文档为准

网络面板里要能看到 avatar 和对应外号字段都传到了服务器。

知识点三:调用修改头像接口并取回新 URL ​

为什么需要:服务器保存后,页面还不知道新地址,需要接口返回最新头像 URL。
怎么想:按文档用 PUT,请求体传 FormData;成功后从响应里取头像地址。转写稿里“hen”应纠正为 then,“掉接口”应纠正为“调接口”。
怎么做:用 axios 发请求,data 直接传 formData;在 then 里打印响应,找到 data 下的头像 URL。
易错点:不要漏 method: 'put';不要把 FormData 再包成普通对象;响应层级要看清楚,转写稿中是点开 data 后复制属性路径。

js
axios({
  url: '修改头像接口地址', // 以接口文档为准
  method: 'put',
  data: formData
}).then(res => {
  console.log(res)
  const avatarUrl = res.data.data.avatar
})

调试时看网络面板:预览出现“保存个人头像成功”,说明文件已到服务器。

知识点四:更新页面头像并刷新验证 ​

为什么需要:服务器变了,本地图片不会自动变;必须把新 URL 设置给页面图片。
怎么想:页面头像是 img,改它的 src 即可。转写稿里类名与数据属性名对应,可用类选择器找到图片。
怎么做:拿到 avatarUrl 后,选中头像 img,设置 .src = avatarUrl。
易错点:只调接口不更新页面,用户看不到变化;设置完要刷新验证,刷新后仍是新头像,才证明服务器保存成功。

js
document.querySelector('.avatar').src = avatarUrl

最后按转写稿流程验证:更换头像→页面立即更新→刷新页面→头像仍是新的。小结问题的答案是:因为头像要保存到某个用户名下,所以上传时必须把外号一起携带过去。

目标与实现思路 ​

本节要完成的是“个人信息设置”中的“信息修改”:用户在页面上改好昵称、性别、个人简介等信息后,点击提交,把最新数据提交到服务器保存。

为什么需要:前面的信息渲染已经把服务器数据回显到页面,用户现在可以在页面上直接修改。此时页面上的值就是用户最终想保存的值,所以核心不是重新查数据再渲染,而是“收集用户刚改完的数据,提交保存”。

怎么想:它和以前“编辑”功能的思路最接近:先有回显,再收集最新改动,最后提交到服务器。区别是本节表单结构更复杂,但表单标签和 name 属性已经准备好,数据也已经在页面里,因此不需要重新获取并刷新页面。

怎么做:分两步走——先收集表单数据,再调用用户信息更新接口;注意更新接口的请求方法是 PUT,表示更新数据。

易错点:不要把它当成“新增”或“删除”;不要漏掉 PUT;提交的数据必须符合接口文档要求,不能直接把页面收集到的原对象原样发出去。

收集表单数据 ​

为什么需要:页面上有多个表单项,如果一个个用 document.querySelector 取值,代码多且容易漏。表单元素已经设置好 name 属性,适合用表单序列化插件一次性收集。

怎么想:先确认表单结构:左侧区域被一个表单包裹,邮箱、昵称、性别、个人简介等表单元素都有 name。既然结构准备好了,就用 form-serialize 把表单转成对象,方便后续处理。

怎么做:给提交按钮绑定点击事件,在事件里获取表单并序列化。转写稿中的调试过程是先打印 formData,确认能拿到昵称、性别、简介等字段:

js
document.querySelector('.submit').addEventListener('click', () => {
  const form = document.querySelector('.user-form')
  const formData = formSerialize(form, { hash: true, empty: true })

  console.log(formData)
})

易错点:hash 和 empty 要按插件用法写对;表单元素没有 name 就收集不到;不要用错选择器,表单类名是 .user-form,提交按钮类名是 .submit。

按接口文档处理数据 ​

为什么需要:页面收集到的数据不一定符合接口文档。转写稿中特别强调:修改信息接口要求的数据有 5 个属性,而表单收集到的是 4 个;同时 gender 要求是整数,页面收集到的却是字符串。

怎么想:先看接口文档,对比“收集到的对象”和“文档要求的数据结构”。个数上少了外号 creator,需要合并进去,告诉服务器改的是谁的数据;类型上 gender 要转成数字,文档中 0 表示男、1 表示女。

怎么做:先把 gender 转成数字,再合并外号:

js
formData.gender = +formData.gender

const data = {
  ...formData,
  creator
}

调试时先打印 data,确认对象里有 5 个属性,且 gender 已经是数字。

易错点:转写稿中做过反例:如果不转换 gender 类型,提交后服务器会报“校验失败,请检查必填项字段”等提示。原因是文档要求整数,而字符串不符合要求。另一个易错点是漏掉 creator,服务器无法确认要修改谁的数据。

用 PUT 提交保存 ​

为什么需要:数据准备好后,必须调用更新接口,服务器才会真正保存。更新接口的请求方法是 PUT,不是 GET 或 POST。

怎么想:按接口文档准备 url、method、请求头和请求体。转写稿中接口要求 application/json,所以要把处理后的对象作为 data 传给 axios。

怎么做:

js
axios({
  url: '接口文档中的修改个人信息地址',
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json'
  },
  data
}).then(res => {
  // 保存成功后的提示,后续用提示框组件完成
})

调试时打开网络面板,修改昵称、性别、个人简介后点击提交,确认请求方法为 PUT,请求体里包含 5 个属性;接口返回“保存个人设置成功”后,刷新页面验证数据是否保存。

易错点:method 不能省,必须是 PUT;data 要传处理后的对象,不能传原始 formData;不要忘记设置 JSON 请求头。保存成功后页面本身已经是用户刚写的数据,所以不需要重新获取并刷新页面;刷新只是用来验证服务器是否保存成功。

小结 ​

信息修改和“编辑”实现最接近:回显已经完成,接下来收集用户最新改动,按接口文档整理数据,再用 PUT 提交到服务器保存。重点检查两件事:数据个数是否补齐、数据类型是否符合文档。

目标与使用场景 ​

这一节要完成的是:用户点击“提交”修改个人信息后,如果接口调用成功,就用 Bootstrap 的提示框(Toast)告诉用户“操作成功”。
为什么需要它?因为提交后页面不一定有明显变化,用户会怀疑“到底改没改成功”。提示框就是一种轻量的结果反馈:不打断操作,又能明确告知结果。

怎么想?提示框不能一提交就弹,否则请求失败也会误导用户;应当放在 axios 的成功回调里,等服务器确认成功后再显示。Bootstrap 的 Toast 默认隐藏,显示后会自动消失,我们只需要用 JS 控制“显示”,必要时通过 data-bs-delay 调整停留时间。

准备提示框结构 ​

页面里已经准备好 Bootstrap 提示框结构和提示内容。调试时可以右键检查元素,找到提示框对应的 DOM;它默认不可见,临时去掉隐藏样式能看到内容已经设置好。结构类似:

html
<div class="toast" data-bs-delay="1500">
  <div class="toast-body">
    操作成功
  </div>
</div>

data-bs-delay 的单位是毫秒,1500 就是 1.5 秒。转写稿中测试时把它改成 3000,提示框约 3 秒后消失,说明停留时间由这个属性控制。

在成功回调中用 JS 显示 ​

Bootstrap 弹框什么时候用 JS 方式控制显示?答案是:需要先执行一些其他 JS 逻辑,再去显示/隐藏弹框时。这里就是先等 axios 请求成功,再显示提示框。

做法分两步:先拿到提示框 DOM,再用 new bootstrap.Toast(dom元素) 实例化,最后调用 show()。代码放在修改信息接口的 .then 成功回调中:

js
axios({
  url: '修改个人信息的接口地址',
  method: 'put',
  data: data
}).then(res => {
  // 请求成功后再提示用户
  const toastEl = document.querySelector('.toast')
  const toast = new bootstrap.Toast(toastEl)
  toast.show()
})

注意:show() 只负责显示,不需要手动调用隐藏方法;Toast 会自动隐藏。若希望显示更久,改 HTML 上的 data-bs-delay 即可。

调试过程与易错点 ​

调试时先提交一次修改,打开网络面板确认接口返回“保存个人设置成功”,再看页面是否弹出提示框;随后等待一会儿,确认它自动消失。再把 data-bs-delay 改为 3000,观察是否约 3 秒后消失。

易错点:不要把 toast.show() 写在请求发出之前;不要用 display:block 手动控制,交给 Bootstrap;选择器要写页面中真实存在的类名;new bootstrap.Toast 中 Toast 首字母大写;data-bs-delay 单位是毫秒。最后记住本节小结:需要执行其他 JS 逻辑后再显示/隐藏弹框时,就用 JS 方式控制 Bootstrap 弹框。

课件图示 ​

课件第29页:总结 总结 1. 信息渲染 获取-> 渲染 2. 修改头像 调用接口-> 更新头

课件第24页:自己的用户信息: 给自己起个外号,并告诉服务器,获取对应的用户信息 信息渲染 获

课件第28页:修改信息 收集表单数据 调用修改信息接口 提示用户 <div class="to

课件第26页:修改头像 获取头像 调用修改头像接口 更新头像URL

代码示例 ​

代码 1:信息渲染(获取数据 + 回显) ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>个人信息设置 - 信息渲染</title>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
  <form class="user-form">
    <img class="avatar" src="" alt="头像">

    <label>邮箱</label>
    <input class="email" name="email" type="text">

    <label>昵称</label>
    <input class="name" name="name" type="text">

    <label>性别</label>
    <input class="gender" name="gender" type="radio" value="0"> 男
    <input class="gender" name="gender" type="radio" value="1"> 女

    <label>个人简介</label>
    <textarea class="desc" name="desc"></textarea>

    <button type="button" class="submit">提交</button>
  </form>

  <script>
    // ⚠️ 接口地址请从自己的接口文档里复制,这里只是占位
    const API = {
      profile: 'http://localhost:8080/api/user/profile' // 获取 / 修改个人信息
    }

    // 1. 外号存成变量,后面改头像、改信息都要用同一个
    const creator = '西兰花'

    // 2. 获取数据(query 参数用 params 传)
    axios({
      url: API.profile,
      params: { creator } // → ?creator=西兰花
    }).then(res => {
      const userInfo = res.data

      // 3. 渲染数据:数据属性名 ←→ 页面类名 一一对应
      Object.keys(userInfo).forEach(key => {
        if (key === 'avatar') {
          // 头像:img 的 src
          document.querySelector(`.${key}`).src = userInfo[key]
        } else if (key === 'gender') {
          // 性别:单选框的 checked,用返回的 0/1 当下标
          const genderRadios = document.querySelectorAll(`.${key}`)
          genderRadios[userInfo[key]].checked = true
        } else {
          // 邮箱 / 昵称 / 个人简介:value
          document.querySelector(`.${key}`).value = userInfo[key]
        }
      })
    })
  </script>
</body>
</html>

要点

  • params 才是 query 参数,写在 data 里会变成请求体。
  • 真正要用的用户数据是 res.data,不要直接拿 res。
  • 三类标签三种写法:img 用 src、radio 用 checked、input/textarea 用 value。
  • 性别是整数 0/1,恰好等于 querySelectorAll 结果的索引,所以 genderRadios[gender].checked = true 一步到位。
  • 易错点:性别有两个标签,必须用 querySelectorAll;querySelector 只会拿到第一个男的。模板字符串的反引号和 ${} 别漏。

代码 2:头像修改(立刻生效) ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>个人信息设置 - 修改头像</title>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
  <img class="avatar" src="" alt="头像">
  <label for="upload">更换头像</label>
  <input type="file" id="upload" hidden>

  <script>
    // ⚠️ 接口地址请从自己的接口文档里复制,这里只是占位
    const API = {
      avatar: 'http://localhost:8080/api/user/avatar' // 修改个人头像
    }
    const creator = '西兰花'

    document.querySelector('#upload').addEventListener('change', function () {
      // 1. 拿到用户选择的头像文件
      const file = this.files[0]

      // 2. 组装 FormData:接口要几个参数就 append 几个
      const fd = new FormData()
      fd.append('avatar', file)     // 新头像文件
      fd.append('creator', creator) // 外号:告诉服务器改谁的头像

      // 3. 提交到服务器
      axios({
        url: API.avatar,
        method: 'PUT',
        data: fd
      }).then(res => {
        // 4. 服务器保存成功,还要把返回的新 URL 设置回页面
        document.querySelector('.avatar').src = res.data.avatar
      })
    })
  </script>
</body>
</html>

要点

  • 事件是 change(选完文件才触发),回调用 function 才能用 this.files。
  • this.files 是伪数组,取 [0] 才是那个文件。
  • FormData 里 append 的参数名必须照接口文档抄,且别漏 creator,否则服务器不知道改谁的头像。
  • 请求方法是 PUT,data 直接传 fd,axios 会自动带正确的 Content-Type。
  • 最容易漏的一步:只调接口不改页面,用户会以为没成功。必须把 res.data.avatar 赋给 img.src;刷新后仍是新头像才算真正保存成功。

代码 3:信息修改 + Bootstrap 提示框 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>个人信息设置 - 修改信息</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/form-serialize/index.min.js"></script>
</head>
<body>
  <form class="user-form">
    <input class="email" name="email" type="text">
    <input class="name" name="name" type="text">
    <input class="gender" name="gender" type="radio" value="0"> 男
    <input class="gender" name="gender" type="radio" value="1"> 女
    <textarea class="desc" name="desc"></textarea>
    <button type="button" class="submit">提交</button>
  </form>

  <!-- 提示框结构:默认不可见,用 JS 控制显示 -->
  <div class="toast my-toast" data-bs-delay="3000">
    <div class="toast-body">操作成功</div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
  <script>
    // ⚠️ 接口地址请从自己的接口文档里复制,这里只是占位
    const API = {
      profile: 'http://localhost:8080/api/user/profile' // 获取 / 修改个人信息
    }
    const creator = '西兰花'

    document.querySelector('.submit').addEventListener('click', () => {
      // 1. 收集表单数据(name 属性必须齐全)
      const form = document.querySelector('.user-form')
      const formData = serialize(form, { hash: true, empty: true })

      // 2. 按接口文档处理数据
      formData.gender = +formData.gender        // 字符串 "1" → 整数 1
      const data = { ...formData, creator }     // 合并外号,4 个变 5 个

      // 3. 提交保存(PUT 表示更新)
      axios({
        url: API.profile,
        method: 'PUT',
        data
      }).then(res => {
        // 4. 用 JS 方式弹出提示框
        const toastDom = document.querySelector('.my-toast')
        const toast = new bootstrap.Toast(toastDom)
        toast.show()
      })
    })
  </script>
</body>
</html>

要点

  • serialize(form, { hash: true, empty: true }) 直接返回对象,前提是表单里每个元素都写了 name。
  • 收集到的 gender 是字符串(如 "1"),文档要 integer,用 + 转成数字;不转服务器会返回校验失败报错。
  • 接口要 5 个参数,表单只有 4 个,必须 { ...formData, creator } 合并,且外号要和获取数据时完全一致。
  • 更新用 PUT;成功后页面已经是用户刚填的数据,不需要重新请求刷新。
  • 提示框:new bootstrap.Toast(dom) 创建实例 → .show() 显示,不用手动隐藏,到 data-bs-delay 毫秒后自动消失。
  • 易错点:提交按钮要写 type="button",否则会触发表单默认提交导致页面刷新、请求被打断。

常见错误 ​

  1. 现象:页面回显的不是自己的数据,或修改后改到别人头上。
    原因: 获取信息、上传头像、提交信息时使用的 create 外号不一致或中途写死。
    修法: 全案例统一使用同一个 create 变量,获取、FormData.append、请求体合并都引用它。

  2. 现象:头像上传接口成功,但刷新后仍是旧头像。
    原因: 只更新了服务器,未用接口返回的头像 URL 更新页面 img.src。
    修法: 在成功回调中取返回头像地址,立即设置到页面头像元素上,再刷新验证服务器是否持久化。

  3. 现象:信息渲染后头像不显示、性别不选中,或输入框全空。
    原因: 遍历 Object.keys(userInfo) 后统一按 value 设置,未区分控件类型。
    修法: 按属性名分类处理:头像设 src,性别设对应 radio 的 checked,邮箱/昵称/简介设 value。

  4. 现象:性别男/女勾选反了,或两个都不选中。
    原因: 未按接口文档把 gender 的 0/1 与 radio 数组索引对应,或选错 radio 元素。
    修法: 用 gender 作为 radio 数组索引取元素,再设置 checked = true;对照文档确认 0 男、1 女。

  5. 现象:修改信息接口报“校验失败/请检查必填项字段”。
    原因: 直接把 serialize 结果提交,未把 gender 转成整数,也未合并 create。
    修法: 提交前执行 +formData.gender 转数字,并用展开运算符合并 {...formData, create},字段数量和类型对照接口文档。

  6. 现象:修改信息接口不生效或请求失败。
    原因: 请求方法写成 POST/PATCH,或请求体放错位置。
    修法: 按接口文档使用 method: 'put',请求体放到 data 中,并确认字段名与文档一致。

  7. 现象:提交成功后提示框不出现、不消失或停留时间不对。
    原因: 只创建 Toast 未调用 show();或误以为需要手动隐藏;或 data-bs-delay 单位/值写错。
    修法: new bootstrap.Toast(el).show();隐藏交给组件自动处理;通过 data-bs-delay 按毫秒调整停留时间。

  8. 现象:选择头像后 this.files 为空或拿不到文件。
    原因: change 事件未绑定到真正触发文件选择的 input[type=file]。
    修法: 确认事件源是 file input,取 this.files[0];若通过 label 关联,需定位到对应 input 再绑定/取值。

练习 ​

  1. 给定 userInfo = { avatar, email, nickname, gender, desc },写出渲染逻辑,要求按“头像、性别、其余输入项”三类分别设置正确的 DOM 属性。

  2. 写出头像修改从“选择文件”到“页面头像更新”的关键代码结构,并标明 FormData 中必须携带的两个字段及用途。

  3. 现有 formData = { email, nickname, gender: '1', desc } 和 create 变量,按接口文档要求构造 PUT 请求体;说明提交前需要做的两处数据加工。

  4. 在修改信息成功的回调中展示 Bootstrap Toast,并让提示框停留 3 秒;写出关键 JS 与 HTML 属性设置。

  5. 排错题:用户反馈“改了昵称并提交,刷新后仍是旧昵称”。请列出至少 3 个排查点,并说明如何快速定位。

原视频对照 ​

#视频时长
1案例-个人设置-获取数据08:58
2案例-个人设置-渲染数据13:30
3案例-个人设置-修改头像10:50
4案例-个人设置-修改信息13:37
5案例-个人设置-提示框07:12

小结 ​

  1. 本案例三条主线是:信息回显、头像修改、信息提交,三者都依赖同一个 create 外号贯穿请求。
  2. 复杂表单渲染先找规律再分类处理,不要按标签数量逐个硬写。
  3. 头像修改必须同时完成“服务器保存”和“页面 URL 更新”,否则用户会误判失败。
  4. 信息提交前要以接口文档为准做数据加工,重点检查字段数量、字段类型和请求方法。
  5. 提交成功后再触发用户提示;Toast 只需控制显示,隐藏与停留时间由组件和 data-bs-delay 管理。
  6. 验证修改是否真正成功,优先刷新页面看回显,而不是只看一次接口成功提示。
  7. 排查此类案例优先查:外号是否一致、参数名/类型是否符合文档、请求方法是否正确、页面是否同步更新。