主题
黑马就业数据平台(案例实战)(三)
本页对应 黑马就业数据平台(案例实战),共 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 提示“同步更改”,说明本地和远端不一致;点击它会先拉取再推送。转写稿中提示“同步更改两个”,表示本地有两次提交要推送到 origin。
怎么做: 记录完毕之后,点击“同步更改”按钮。转写稿中弹窗提示“此操作会将 origin 中拉取并向其推送提交”,即先拉取、后推送。完成后到远程仓库查看,能看到“开发完成”的最新提交。
易错点: 不要只看到本地提交成功就以为远端已更新;推送前留意是否需要先拉取。更多功能可点 Git 面板的三个点,例如创建分支、合并分支;讲师也说明自己更习惯命令行,界面功能知道即可。

课件图示








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