Skip to content

黑马就业数据平台(案例实战)(三) ​

本页对应 黑马就业数据平台(案例实战),共 8 段视频,约 1 小时 0 分。

学习目标 ​

  • 能根据 DELETE 接口及路径参数完成学生删除,并在成功或失败时执行提示与页面重新渲染。
  • 能复用同一模态框实现编辑入口,通过事件委托区分新增、编辑、删除,并按操作类型动态修改模态框标题。
  • 能根据学生 ID 请求详情,使用 name 属性选择器回显姓名、年龄、组号、就业薪资、期望薪资等基础信息,并正确回显性别单选框。
  • 能按省→市→区的依赖顺序请求并渲染下拉列表,在列表生成后回显省市区,并保持级联切换可用。
  • 能在提交时区分新增与编辑,携带 ID 和表单数据调用 PUT 接口完成修改,成功后关闭弹窗、提示、重置表单、刷新列表并清理临时 ID。
  • 能使用 VS Code 源代码管理界面完成暂存、提交、同步或推送,并能识别 M 等状态标识及放弃更改的风险。

知识点 ​

需求与整体思路 ​

删除数据要完成两件事:点击删除按钮后删除对应数据,并重新获取最新数据刷新页面。效果如下:

删除数据效果示意图

为什么需要先想清楚整体步骤?因为删除按钮不是页面一开始就写死的,而是渲染学生列表时动态生成的。如果直接给删除按钮绑定事件,后渲染出来的按钮往往绑不上。所以核心思路是:用事件委托绑定删除事件,抽取删除函数,拿到渲染时保存的 id,调用删除接口,最后重新渲染。

笔记中的核心步骤是:绑定事件(事件委托)→抽取函数→获取 id 并调用接口→重新渲染。下面按这个顺序展开。

绑定删除事件:用事件委托 ​

为什么需要事件委托?删除按钮是列表渲染后追加的,直接 querySelector('.bi-trash').addEventListener 只能绑到当时存在的元素,后续渲染的按钮不会自动拥有事件。怎么想?把点击事件委托给稳定的父容器 .list,点击时判断实际点击的是不是删除图标。怎么做?在 student.js 中给 .list 绑定 click,用 e.target.classList.contains('bi-trash') 判断。易错点:不要直接给删除按钮绑定;判断类名时注意删除图标类名是 bi-trash;取 id 时层级要写对,笔记中使用 e.target.parentNode.parentNode.dataset.id,因为 id 保存在渲染时生成的 tr 的 data-id 上。

javascript
// 绑定事件
document.querySelector('.list').addEventListener('click', (e) => {
  // 点了删除标签-调用对应的函数
  if (e.target.classList.contains('bi-trash')) {
    // console.log(e.target.parentNode.parentNode.dataset.id)
    const id = e.target.parentNode.parentNode.dataset.id
    delStudent(id)
  }
})

抽取删除函数并调用接口 ​

为什么需要抽取 delStudent 函数?如果所有逻辑都写在事件回调里,后续修改删除逻辑会越来越乱;抽取后事件回调只负责“判断点击并传 id”,删除逻辑集中管理。怎么想?删除需要 id,接口请求方式是 delete,路径参数要拼到 URL 后面。怎么做?补充 axios 的别名方法:请求方法为 delete 时使用 axios.delete(url)。然后写 async function delStudent(id),内部 await axios.delete(/students/${id})。易错点:不要写成 axios.del;id 是路径参数,要拼在 URL 中,不是随便放在请求体;await 必须写在 async 函数里。

javascript
// 请求方法为delete
// 参数1 url地址
axios.delete(url)
javascript
// 抽取函数-删除数据
async function delStudent(id) {
  // console.log('点了删除')
  // console.log(id)
  // 获取id+调用接口
  await axios.delete(`/students/${id}`)

  // 重新渲染
  getData()
}

转写稿中还提到一个调试细节:删除接口一般后台不会返回响应体,所以这里不需要接收返回值,直接发请求即可。最初调试时如果漏写 await,因为请求是异步的,可能出现删除请求还没完成就继续往下执行的情况,删除逻辑会有小问题。加上 await 后,外层函数必须用 async 修饰,这一点不能漏。

重新渲染与提示 ​

为什么删除成功后要重新渲染?因为页面上的列表还是旧数据,手动删 DOM 容易和真实数据不一致。怎么想?删除成功后重新调用获取数据的函数,让页面以接口返回的最新数据为准。怎么做?在 delStudent 中 await axios.delete(...) 之后调用 getData()。转写稿中还补充了提示逻辑:删除成功可提示“删除成功”再重新渲染;失败则提示“删除失败,请检查网络”。易错点:不要先渲染再等待删除完成,否则可能拿到旧列表;提示和渲染的顺序要清楚;失败处理不要吞掉错误。

提交记录 ​

完成删除功能后提交代码:

bash
git add .
git commit -m"信息管理-删除数据"

这一节的关键不是“会写一个 delete 请求”,而是理解动态列表为什么要用事件委托、为什么要把删除逻辑抽成函数、为什么要等接口完成后再重新渲染。把这几个点串起来,删除功能才稳定。

需求与整体思路 ​

编辑数据弹框效果

本节要完成“编辑数据”逻辑:点击修改按钮,弹出编辑框并渲染需要修改的数据;修改后点击确认能正常保存。注意项目里新增和编辑共用同一个弹框,所以既要完成数据回显,也要处理“当前到底是新增还是编辑”的区分。

为什么需要先想清楚共用弹框?因为弹框只有一个,标题、表单内容、籍贯下拉、按钮行为都会互相影响。怎么想?把编辑拆成三段:弹出编辑框并回显数据、保存修改、调整新增/编辑共用弹框的清理逻辑。怎么做?先抽取 editStudent(id),再抽取 saveEdit(),最后在新增按钮点击时重置弹框状态。易错点:只改编辑不回改新增,会导致点新增也显示“修改学员”;编辑完不清空 dataset.id,新增会误走修改接口。

转写稿里的调试过程也说明了这一点:先点编辑只弹框,标题仍是“添加学员”,于是编辑函数里改标题;改完后发现新增也变成“修改学员”,所以新增点击逻辑里必须把标题改回“添加学员”。

弹出编辑框与数据回显 ​

为什么需要函数抽取?列表里的编辑按钮是后追加的,要用事件委托;如果删除、编辑逻辑都堆在事件里,代码会很长。怎么想?在 .list 的点击事件里判断 bi-pen,拿到当前行 dataset.id,调用 editStudent(id)。怎么做?编辑函数里先 axios.get('/students/'+id) 获取详情,再改标题、设置输入框、设置性别、设置籍贯,最后 modal.show() 并保存 modalDom.dataset.id = id。

基础输入框回显可以用属性名数组:['name','age','group','hope_salary','salary'],循环用 [name=key] 找到输入框并赋 value。性别是单选框/复选框组,转写稿中通过 document.querySelectorAll('[name=gender]') 拿到伪数组,再用 chks[gender].checked = true,其中 0 男、1 女。易错点:性别不能直接赋 value,要设置 checked;选择器属性名要加引号或模板字符串,别把变量当字符串。

籍贯回显是重点:省可以直接 proSelect.value = province,因为省份列表在初始化时已经渲染;市和区必须先根据省、市请求列表并渲染 option,再设置 value。为什么?转写稿强调:select 的 value 赋值必须在对应 option 列表存在之后,否则找不到匹配项,赋值不成功。怎么做?先请求 /api/city 渲染城市,再 citySelect.value = city;再请求 /api/area 渲染地区,再 areaSelect.value = area。易错点:先赋值后渲染会失败;城市、地区下拉的默认项要保留。

保存修改与共用弹框清理 ​

为什么保存修改也要抽取?确认按钮新增、编辑共用,点击时要知道走哪个接口。怎么想?编辑时把 id 存到弹框 DOM 的自定义属性 modalDom.dataset.id,新增时清空它;保存时判断有没有 id。转写稿里调试时曾用标题是否包含“添加/编辑”判断,后来笔记代码用 dataset.id 更直接。怎么做?saveEdit() 里用 serialize 收集表单,把 age、gender、hope_salary、salary、group 转成数字,然后 axios.put(/students/${modalDom.dataset.id}, data)。成功时提示、关闭弹框、重新获取数据;失败时提示并关闭弹框。补充:axios.put(url,data) 就是 PUT 请求别名,参数1是地址,参数2是提交数据。易错点:PUT 不要写成 POST;URL 要拼 id;数字字段要转换。

新增按钮点击时必须调整弹框逻辑:标题改“添加学员”、form.reset()、清空城市和地区下拉、modalDom.dataset.id = ''。为什么?共用弹框会残留上次编辑的标题、表单值、籍贯选项和 id。易错点:reset() 不一定清掉动态追加的城市/地区 option,所以还要手动设置 innerHTML;dataset.id 不清空,新增会误走修改。

关键代码 ​

student.js

javascript
document.querySelector('#openModal').addEventListener('click', () => {
  // 修改标题
  document.querySelector('.modal-title').innerText = '添加学员'

  // 表单重置
  document.querySelector('#form').reset()

  // 清空籍贯(城市,地区)
  citySelect.innerHTML = '<option value="">--城市--</option>'
  areaSelect.innerHTML = '<option value="">--地区--</option>'

  // 删除自定义属性
  modalDom.dataset.id = ''

  modal.show()
})
// 绑定事件
document.querySelector('.list').addEventListener('click', (e) => {
  // 点了删除标签-调用对应的函数
  if (e.target.classList.contains('bi-trash')) {
    // console.log(e.target.parentNode.parentNode.dataset.id)
    const id = e.target.parentNode.parentNode.dataset.id
    delStudent(id)
  }
  // 点了编辑标签-调用对应的函数
  if (e.target.classList.contains('bi-pen')) {
    const id = e.target.parentNode.parentNode.dataset.id
    editStudent(id)
  }
})

// 抽取函数-编辑学员数据
async function editStudent(id) {
  // console.log('编辑', id)
  const res = await axios.get(`/students/${id}`)

  // 修改标题
  document.querySelector('.modal-title').innerText = '修改学员'

  // 设置输入框(姓名,年龄,组号,期望薪资,就业薪资)
  // 属性名数组
  const keyArr = ['name', 'age', 'group', 'hope_salary', 'salary']
  keyArr.forEach(key => {
    document.querySelector(`[name=${key}]`).value = res.data[key]
  })

  // 设置性别(0 男,1 女)
  const { gender } = res.data
  // 伪数组[男checkbox,女checkbox]
  const chks = document.querySelectorAll('[name=gender]')
  chks[gender].checked = true

  // 设置籍贯
  const { province, city, area } = res.data

  // 设置省(设置select标签的value属性,对应的option会被选中)
  proSelect.value = province

  // 设置市
  const cityRes = await axios.get('/api/city', {
    params: {
      pname: province
    }
  })
  const cityHtml = cityRes.list.map(v => {
    return `<option value="${v}">${v}</option>`
  }).join('')
  citySelect.innerHTML = `<option value="">--城市--</option>${cityHtml}`
  citySelect.value = city

  // 设置区
  const areaRes = await axios.get('/api/area', {
    params: {
      pname: province,
      cname: city
    }
  })
  const areaHtml = areaRes.list.map(v => {
    return `<option value="${v}">${v}</option>`
  }).join('')
  areaSelect.innerHTML = `<option value="">--地区--</option>${areaHtml}`
  areaSelect.value = area

  // 弹框
  modal.show()

  // 保存学员id,用来却分 新增(dataset.id不存在) 编辑(dataset.id有值)
  modalDom.dataset.id = id
}

// 抽取函数-保存修改
async function saveEdit() {
  console.log('保存修改')
  // 数据收集+转换+提交
  const form = document.querySelector('#form')
  const data = serialize(form, { hash: true, empty: true })
  data.age = +data.age
  data.gender = +data.gender
  data.hope_salary = +data.hope_salary
  data.salary = +data.salary
  data.group = +data.group

  try {
    // 修改成功
    const saveRes = await axios.put(`/students/${modalDom.dataset.id}`, data)
    showToast(saveRes.message)
    getData()
  } catch (error) {
    // 修改失败
    // console.dir(error)
    showToast(error.response.data.message)
  }
  modal.hide()
}
javascript
// 请求方法为put
// 参数1 url地址
// 参数2 提交的数据
axios.put(url,data)

Git 记录 ​

bash
git add .
git commit -m"信息管理-编辑数据"

使用准备:直接打开项目根目录 ​

为什么需要: VSCode 的 Git 面板会以当前打开的工作区为范围显示更改。如果打开项目时多套了一层文件夹,Git 状态、文件路径和提交范围就容易看乱,后续暂存和提交都不直观。
怎么想: 先让编辑器的工作区根目录就是项目根目录,再让 VSCode 识别 Git 仓库。
怎么做: 使用 VSCode 直接打开项目,不要额外嵌套文件夹,例如不要打开“项目外层目录”后再在里面找项目。
易错点: 如果发现 Git 面板里的文件路径多出一层,先检查是不是打开错了目录,而不是急着提交。

打开项目时不要额外嵌套文件夹

本地记录:暂存后提交 ​

为什么需要: 命令行提交需要先 git add 再 git commit,VSCode 把这两步做成了可视化按钮,适合日常快速记录。
怎么想: “更改”区域里的文件是工作区修改;点“更改”右侧的 + 相当于暂存;填写提交信息后点“提交”相当于生成一次本地记录。
怎么做: 左侧找到第三个 Git 选项;点击“更改”右侧的 +;在输入框输入本次记录的信息;点击“提交”。转写稿中演示时,文件后面的 M 表示文件已修改、可以提交;点加号后按钮会变成减号,减号表示取消暂存。
易错点: 不要点“放弃更改”,它的意思是删除当前修改、回到上一个版本;提交信息要写清楚,否则记录会是空的或难以辨认。

bash
# 等同于如下命令
git add .
git commit -m"记录的信息"

源代码管理面板中的暂存与提交

提交后可以验证。转写稿中打开 Git 窗口执行单行日志查看:

bash
git log --oneline

能看到刚才的“开发完成”提交,说明本地记录成功。

分支记录:切换与分支操作 ​

为什么需要: 开发中经常要在不同分支间切换,或在本地创建、合并、删除分支。VSCode 把常用分支操作放到了界面里。
怎么想: 左下角显示当前分支,点它可以根据提示切换分支;更多分支操作在 Git 面板的菜单里找。
怎么做: 点击 VSCode 左下角的分支名称,根据提示切换分支。如果没有状态栏,通过“菜单 → 查看 → 外观 → 状态栏”打开。分支合并、删除等操作,按图中顺序依次点击即可找到。
易错点: 切换分支前先确认当前修改是否已提交或暂存,避免把未完成修改带到另一个分支;分支操作要看清当前所在分支。

VSCode左下角切换分支

在VSCode中操作分支的菜单

拉取和推送:用“同步更改”完成远端同步 ​

为什么需要: 本地提交只记录在本机,团队协作还需要把本地提交推送到远端,同时把远端新提交拉下来。
怎么想: 记录完毕之后,如果 VSCode 提示“同步更改”,说明本地和远端不一致;点击它会先拉取再推送。转写稿中提示“同步更改两个”,表示本地有两次提交要推送到 origin。
怎么做: 记录完毕之后,点击“同步更改”按钮。转写稿中弹窗提示“此操作会将 origin 中拉取并向其推送提交”,即先拉取、后推送。完成后到远程仓库查看,能看到“开发完成”的最新提交。
易错点: 不要只看到本地提交成功就以为远端已更新;推送前留意是否需要先拉取。更多功能可点 Git 面板的三个点,例如创建分支、合并分支;讲师也说明自己更习惯命令行,界面功能知道即可。

点击同步更改进行拉取和推送

课件图示 ​

课件第13页:核心步骤: 信息管理-编辑数据 保存修改 弹出编辑框 调整弹框逻辑 修改标题 表

课件第10页:核心步骤: 信息管理-删除数据 抽取函数 绑定事件 获取id+调用接口 axio

课件第11页:核心步骤: 信息管理-编辑数据 保存修改 弹出编辑框 调整弹框逻辑 函数抽取 根

课件第12页:核心步骤: 信息管理-编辑数据 保存修改 弹出编辑框 调整弹框逻辑 函数抽取+调

课件第14页:常用Git操作: VSCode的git 分支操作 本地记录 代码推送

课件第5页:信息管理-公共逻辑整合 需求: 整合抽取的公共逻辑 渲染用户名 登录校验 退出登

课件第6页:核心步骤: 信息管理-学员信息渲染 axios.get(url, { param

课件第7页:核心步骤: 信息管理-新增学员 省市区联动 点击弹框 数据新增 实例化Modal

代码示例 ​

示例 1:删除数据(事件委托 + axios.delete + 重新渲染) ​

javascript
// student.js —— 信息管理:删除学员
const listDom = document.querySelector('.list')

// 1. 绑定事件(事件委托:删除按钮是渲染后追加的,直接绑不上)
listDom.addEventListener('click', (e) => {
  // 点了删除标签 —— 调用对应的函数
  if (e.target.classList.contains('bi-trash')) {
    // 2. 获取 id(渲染时保存在 tr 的 data-id 上)
    const id = e.target.parentNode.parentNode.dataset.id
    delStudent(id)
  }
})

// 3. 抽取函数 —— 删除数据
async function delStudent(id) {
  try {
    // 4. 调用接口:delete 请求,id 走路径参数
    await axios.delete(`/students/${id}`)

    // 5. 重新渲染(删除接口一般不返回响应体,直接提示 + 重新获取最新数据)
    showToast('删除成功')
    getData()
  } catch (error) {
    showToast('删除失败,请检查网络')
  }
}

关键行与易错点

  • e.target.classList.contains('bi-trash'):删除图标是 getData() 渲染后追加的,只能在父级 .list 上做事件委托,不能给每个图标单独绑定。
  • e.target.parentNode.parentNode.dataset.id:从图标往上两级才是 tr,data-id 是渲染时保存的,删除时直接取用,不要重新查接口。
  • axios.delete(url) 只传一个 url,id 拼在路径上(路径参数),不要写成 params。
  • delStudent 必须用 async 修饰,内部 await 才能生效;删除后要调用 getData() 重新渲染,否则页面还停留在旧数据。
  • 删除接口没有响应体,所以不用 const res = await ... 接收;失败提示写 catch 里即可。

示例 2:编辑数据 —— 弹出弹框并回显(基础信息 / 性别 / 省市区) ​

javascript
// student.js —— 信息管理:点击编辑,弹出同一个弹框并回显数据
const modalDom = document.querySelector('#modal')
const modal = new bootstrap.Modal(modalDom)
const proSelect = document.querySelector('[name=province]')
const citySelect = document.querySelector('[name=city]')
const areaSelect = document.querySelector('[name=area]')

// 事件委托里再判断一次:点了编辑图标
document.querySelector('.list').addEventListener('click', (e) => {
  if (e.target.classList.contains('bi-trash')) {
    delStudent(e.target.parentNode.parentNode.dataset.id)
  }
  if (e.target.classList.contains('bi-pen')) {
    editStudent(e.target.parentNode.parentNode.dataset.id)
  }
})

// 抽取函数 —— 编辑学员数据(获取详情 + 回显)
async function editStudent(id) {
  // 1. 根据 id 获取学生详情
  const res = await axios.get(`/students/${id}`)

  // 2. 修改标题(和新增共用同一个弹框)
  document.querySelector('.modal-title').innerText = '修改学员'

  // 3. 设置输入框:属性名数组,利用 name 属性选择器批量回显
  const keyArr = ['name', 'age', 'group', 'hope_salary', 'salary']
  keyArr.forEach((key) => {
    document.querySelector(`[name=${key}]`).value = res.data[key]
  })

  // 4. 设置性别(0 男,1 女):伪数组下标刚好对应 gender 值
  const { gender } = res.data
  const chks = document.querySelectorAll('[name=gender]')
  chks[gender].checked = true

  // 5. 设置籍贯
  const { province, city, area } = res.data

  // 5.1 省:select 的 value 赋值,对应 option 会被选中
  proSelect.value = province

  // 5.2 市:先按省拿到城市列表,渲染完再赋值
  const cityRes = await axios.get('/api/city', {
    params: { pname: province }
  })
  const cityHtml = cityRes.list
    .map((v) => `<option value="${v}">${v}</option>`)
    .join('')
  citySelect.innerHTML = `<option value="">--城市--</option>${cityHtml}`
  citySelect.value = city

  // 5.3 区:按省 + 市拿到地区列表,渲染完再赋值
  const areaRes = await axios.get('/api/area', {
    params: { pname: province, cname: city }
  })
  const areaHtml = areaRes.list
    .map((v) => `<option value="${v}">${v}</option>`)
    .join('')
  areaSelect.innerHTML = `<option value="">--地区--</option>${areaHtml}`
  areaSelect.value = area

  // 6. 弹框,并把 id 存到弹框 Dom 上(用来区分新增 / 编辑)
  modal.show()
  modalDom.dataset.id = id
}

关键行与易错点

  • document.querySelector([name=${key}]):页面输入框的 name 和数据字段名一致(name、age、group…),用属性选择器批量回显,比一个个 querySelector 简洁。
  • 性别是单选框,不能写 .value = ...,要用 chks[gender].checked = true;querySelectorAll 拿到的是伪数组,[0] 是男、[1] 是女,正好对应 0/1。
  • select 的 value 赋值必须在 option 列表渲染之后,列表为空时赋值无效,这是最容易出错的地方:先 innerHTML = ...,再 .value = city。
  • 城市、地区要用 axios.get(url, { params: {...} }) 传查询参数(pname、cname),且这两次请求都要 await,否则拿不到 list。
  • modalDom.dataset.id = id 一定要在最后存上,它是后面保存修改时区分「新增」和「编辑」的唯一依据。
  • 编辑按钮同样要放在 .list 的事件委托里判断(bi-pen),因为编辑按钮也是渲染后追加的。

示例 3:保存修改(区分新增 / 编辑)+ 调整弹框逻辑 ​

javascript
// student.js —— 保存修改 + 弹框复用时的清理

// 1. 确认按钮:同一个按钮,要区分走「新增」还是「编辑」
document.querySelector('#submit').addEventListener('click', () => {
  const title = document.querySelector('.modal-title').innerText
  if (title.includes('添加')) {
    addStudent()      // 原来的新增逻辑(获取数据 + 提交 + 重新渲染)
  } else if (title.includes('编辑')) {
    saveEdit()
  }
})

// 2. 抽取函数 —— 保存修改
async function saveEdit() {
  // 2.1 数据收集 + 转换
  const form = document.querySelector('#form')
  const data = serialize(form, { hash: true, empty: true })
  data.age = +data.age
  data.gender = +data.gender
  data.hope_salary = +data.hope_salary
  data.salary = +data.salary
  data.group = +data.group

  try {
    // 2.2 提交:put 请求,url 带路径参数 id,第二个参数是数据
    const saveRes = await axios.put(`/students/${modalDom.dataset.id}`, data)
    // 2.3 修改成功:提示 + 重新渲染
    showToast(saveRes.message)
    getData()
  } catch (error) {
    // 2.4 修改失败:提示(后端返回的错误信息)
    showToast(error.response.data.message)
  }

  // 2.5 关闭弹框,并清掉保存的 id
  modal.hide()
  modalDom.dataset.id = ''
}

// 3. 调整弹框逻辑:点「新增」时把共用弹框彻底重置
document.querySelector('#openModal').addEventListener('click', () => {
  // 修改标题
  document.querySelector('.modal-title').innerText = '添加学员'

  // 表单重置
  document.querySelector('#form').reset()

  // 清空籍贯(城市、地区)
  citySelect.innerHTML = '<option value="">--城市--</option>'
  areaSelect.innerHTML = '<option value="">--地区--</option>'

  // 删除自定义属性
  modalDom.dataset.id = ''

  modal.show()
})

关键行与易错点

  • 新增和编辑共用同一个弹框、同一个确认按钮,所以必须用 modal-title 文本(或 dataset.id 是否存在)来判断走哪个分支;漏掉这一步,点编辑的确认会走新增接口。
  • axios.put(url, data) 是 put 的别名方法:参数 1 是地址(带 id),参数 2 是提交的数据,两个都要给。
  • 收集数据后要把 age / gender / hope_salary / salary / group 转成数字(+),否则后端收到的是字符串。
  • 成功和失败都要 modal.hide(),所以 modal.hide() 写在 try...catch 外面;失败提示用 error.response.data.message。
  • 编辑成功后 modalDom.dataset.id = '' 必须清掉,否则下次点「新增」会带着上一次的 id 去发 put 请求。
  • 点「新增」时的四步重置(改标题、form.reset()、清空市/区下拉、删 dataset.id)一个都不能少,form.reset() 只能还原输入框,不会清 select 的 option 列表和自定义属性。
  • 省份下拉不用清空:selectInit() 初始化时已经把省份列表渲染好了,回显和新增都能直接用。

常见错误 ​

1. 删除提示成功,但列表数据没变化

  • 现象:点删除后弹“删除成功”,被删的那行还在页面上,刷新才消失。
  • 原因:只调了 axios.delete,忘了在接口返回后重新执行渲染函数;或渲染调用写在 await 之外,实际比删除请求先跑完。
  • 修法:delStudent 声明为 async,await axios.delete(...) 之后再调 getData(),保证“先删库、后渲染”的时序。

2. 删除请求 404 / 405

  • 现象:控制台报错,网络面板里 URL 是 /students 或 /students/undefined。
  • 原因:把 id 当成查询参数或请求体传了,而接口是路径传参;或取 id 时 e.target 取错层级,拿到 undefined。
  • 修法:用模板字符串拼路径 `/students/${id}`;id 从行元素的 dataset.id 取,图标点击时用 closest 或固定父级链定位到 tr,打印确认后再发请求。

3. 编辑完再点新增,表单里还是上一个人的数据

  • 现象:先点“修改”,关掉后点“新增”,姓名、籍贯、性别都还是上一条的。
  • 原因:新增与编辑共用一个弹框,只改了标题,没做表单重置和籍贯下拉清空。
  • 修法:在打开“新增”弹框的处理函数里依次做四件事——改标题、form.reset()、把 city/area 两个 select 的 innerHTML 重置为占位 option、清掉弹框上的 dataset.id。

4. 省市区回显时下拉框没选中,仍显示“--城市--”

  • 现象:编辑弹框里省份能选中,市和区是空的,但 console.log 明明拿到了 city/area 的值。
  • 原因:select.value = xxx 写在了渲染 option 列表之前。select 在 option 不存在时赋值无效,后面的列表渲染又把已选状态冲掉。
  • 修法:顺序固定为“先请求 → 先 innerHTML 渲染 option → 再 select.value = 目标值”;省、市、区逐级串行 await,不能并行。

5. 点“确定”永远走新增逻辑,改不动数据

  • 现象:编辑弹框里改完点确定,提示新增成功,列表里多了一条或原数据没变。
  • 原因:提交按钮只有一个,共用弹框下没区分当前是新增还是编辑。
  • 修法:编辑函数末尾给弹框 Dom 写 modalDom.dataset.id = id,提交时判断 dataset.id 有值走 axios.put(url, data),无值走新增;编辑成功后记得把 dataset.id 清空,否则下次新增会误走修改。

6. 性别回显不生效

  • 现象:数据里 gender 是 1,弹框里“男”被选中或两个都没选中。
  • 原因:把 gender 当成普通输入框用 .value 赋值,单选框要用 checked;且 querySelectorAll('[name=gender]') 拿到的是伪数组,要用索引对应 0/1。
  • 修法:chks[gender].checked = true,前提是 HTML 里“男”在前、“女”在后,与后端 0/1 的顺序一致。

7. VSCode 源代码管理面板里看不到任何更改

  • 现象:明明改了文件,左侧 Git 图标上没有数字角标,列表为空。
  • 原因:VSCode 打开的目录不是仓库根目录(多套了一层文件夹),Git 找不到 .git。
  • 修法:直接打开项目根目录,让资源管理器顶层就是 .git 所在层级。

练习 ​

练习 1|删除的健壮性改造 给删除加上二次确认(confirm),确认后才发请求;同时把删除失败的 catch 分支补全,失败时提示错误信息且不重新渲染。改完后故意把 URL 拼错一次,验证失败分支的表现是否符合预期。

练习 2|用 dataset 统一新增/编辑入口 把“点确定”里的 if 判断从“读标题文字”改成“读 modalDom.dataset.id”,并整理成一个 submitForm() 函数:有 id 走 put、无 id 走 post。改完连续执行“新增 → 编辑 → 再新增”三个动作,观察是否还有状态残留。

练习 3|级联回显的顺序验证 在 editStudent 里,把 citySelect.value = city 故意挪到 innerHTML 赋值之前,运行后记录现象;再挪回去。用一句话写出你对“select 赋值必须在 option 渲染之后”的理解,并说明为什么省、市、区三步不能写成 Promise.all。

练习 4|字段类型转换清单 列出编辑提交时所有需要 + 转数字的字段,说明不转会怎样(用网络面板对比请求体)。再检查一遍新增逻辑里是否有遗漏的字段。

练习 5|VSCode Git 全流程演练 用 VSCode 的源代码管理面板完成一次完整流程:暂存 → 写提交信息 → 提交 → 查看本地日志确认 → 点“同步更改”推送。然后新建一个分支,随便改一行再切回原分支,观察工作区文件是否跟着变。

原视频对照 ​

#视频时长
1学生信息案例-删除业务完成04:02
2学生信息案例-编辑功能-弹出模态框及标题修改06:56
3意外暂停03:08
4学生信息案例-获取学生详情02:14
5学生信息案例-回显基础信息及性别10:29
6学生信息-省市区回显11:26
7学生信息-编辑数据提交16:43
8vscode-git界面窗口操作05:25

小结 ​

  1. 删除是“一请求 + 一刷新”的最小闭环,复杂度和风险都低于新增/编辑,先做它能快速验证列表渲染链路是通的。
  2. 所有“后追加的元素”都只能靠事件委托绑定,委托容器要选那个从页面加载起就存在的祖先(tbody),且判断条件写在 classList.contains 上而不是元素下标上。
  3. 共用弹框的代价是状态污染,凡是“打开弹框”的入口,都要负责把上一次留下的表单值、下拉列表、自定义属性全部复位——这是入口的职责,不是提交时的补救。
  4. dataset 是共用弹框场景下最轻量的状态载体:写入表示“我是编辑”,清空表示“我是新增”,读写同一处、随 Dom 生命周期自动销毁,比全局变量安全。
  5. 级联下拉的回显必须严格串行:省列表已存在 → 赋省值 → 请求市列表 → 渲染市 option → 赋市值 → 请求区列表 → 渲染区 option → 赋区值,任何一步提前都会静默失败(不报错、只是没选中)。
  6. 表单回显要按控件类型分流:文本框/下拉用 value,单选/复选用 checked,下拉还要区分“渲染列表”和“选中某项”两个动作。
  7. 提交类函数的固定骨架是 try { 请求 → 提示 → 关闭 → 清状态 → 重渲染 } catch { 提示 },成功分支要清的东西和打开弹框时要清的东西是同一份清单,两边要对齐。
  8. 命令行 Git 与 VSCode Git 面板是同一套操作的两种入口,面板只是把 add/commit/push 图形化;命令行的优势在于每一步都可见、可回滚,面板适合快速提交,但“放弃更改”这类破坏性按钮要格外小心。