1. 功能需求
从后端获取到的房间列表包含房间号、房间类型、房间人数、性别四个参数。用户在列表中可以修改任意记录的任意参数,点击保存修改的时候将修改提交到服务器,实现批量更新。
2. 组件树结构
Page
|--LocationText
|--SideBar
|--MainContent
|--HeadLine
|--RoomUnit(一个单元包含一条完整的记录)
|--BottomButton
3. 实现思路
3.1 列表初始化
- MainContent 中发送ajax 请求获取房间及其信息列表
GetRoomInfo () { this.apiGet('/admin/room', {}) .then((res) => { console.log(res) if (res.errcode == 0) { this.roomList = res.data } else { alert('获取用户列表失败!') } }, (err) => { console.log(err) }) }
- 使用v-for 遍历的到的结果,并传值给RoomUnit 子组件
<RoomUnit class="unit-margin" v-for="(item, index, key) in roomList" @updateRoom="UpdateRoom" :item="item" :index="index" :key="key"></RoomUnit>
- RoomUnit 接收父组件传值并初始化数据
props: { item: { type: Object } }
3.2 监听列表修改
- RoomUnit 组件监听item 对象的值,监测到修改时通知父组件
watch: { // 监测item的值是否被修改 item: { handler: function (newV, oldV) { this.$emit('updateRoom', newV) }, deep: true } }
- 父组件接收子组件传来的值,并将其添加至待批量更新的json 对象中
UpdateRoom (...data) { let id = data[0].id this.updateRoomList[id] = data[0] }
3.3 批量更新
- 发送多次ajax 请求对待更新json 对象中的记录依次进行更新
SaveChange () { let allUpdated = true // count计数 let count = 0 // 获取对象长度 let len = Object.getOwnPropertyNames(this.updateRoomList).length - 1 // 批量更新 for (let index in this.updateRoomList) { this.apiPost('/admin/room/update/' + index, this.updateRoomList[index]) .then((res) => { // 操作成功 if (res.errcode == 0) { count++ // 最后一次操作时候判断是否成功并刷新页面 if (count == len) { if (allUpdated) { this.$message({ message: '更新成功', type: 'success' }) // 重新获取列表数据,刷新页面 this.GetRoomInfo() // 清空upade列表 this.updateRoomList = {} } else { this.$message.error('更新失败,请稍后重试') } } } else { allUpdated = false } }, (err) => { console.log(err) }) } }
4. 潜在Bug
以上方案基本上已经可以解决批量更新的问题了。
但是,可能会有一个潜在的bug,是否会出现这个bug 跟后端程序员的写法有关。
4.1 产生bug的原因
会产生这个bug 的原因在于:mysql 针对一次没有任何内容变化的更新操作会返回结果0(正常是1)
也就是说,如果前端提交的更新列表里面包含了未修改的记录,而后端程序员又将结果为0判断为更新失败,那么就会产生bug。
4.2 前端为什么会提交未经修改的数据,我明明已经在每次更新成功之后将待更新列表清空了
在3.3 的批量更新过程中我们可以看到,每次确定更新成功以后会重新发送ajax 请求获取房间列表,刷新RoomUnit 组件列表。RoomUnit 刷新之后会触发其中的watch 事件,导致所有的item 记录都被添加到父组件的updateRoomList 中(此处是因为所有item 的地址都发生了变化)。用户这个时候如果再点击更新,那么实际上向服务器发送的是所有未经修改的记录,因此就会报错,更新失败!
4.3 应该如何解决
- 方案一:既然每次刷新之后updateRoomList 之中会填充所有数据,那么我在刷新之后将updateRoomList 重新清空一下就好了。考虑到ajax 的异步问题,使用setTimeOut 设定时间,确保在ajax 请求完成之后再清空列表。于是SaveChange 局部就变成了以下的写法
if (res.errcode == 0) { count++ // 最后一次操作时候判断是否成功并刷新页面 if (count == len) { if (allUpdated) { this.$message({ message: '更新成功', type: 'success' }) // 刷新页面 this.GetRoomInfo() // 清空upade列表 // this.updateRoomList = {} setTimeout(function () { for (let key in this.updateRoomList) { delete this.updateRoomList[key] } }, 500) } else { this.$message.error('更新失败,请稍后重试') } } } else { allUpdated = false }
代码怎么看都没问题,可是还是有bug,updateRoomList 中的数据清不掉!!为什么会清不掉,delete 操作没毛病啊,而且还是在ajax 请求完成之后进行的。查了半天,原来是this 指向的问题。setTimeOut 中设置一层function 之后,this 已经不再指向Vue 实例,而是指向它所在的function,所以当然清不掉Vue 中的updateRoomList了。
解决办法,修改setTimeOut 的写法
setTimeout(() => { for (let key in this.updateRoomList) { delete this.updateRoomList[key] } }, 500)
- 方案二:既然刷新列表后,watch 是因为地址的变化导致父元素列表加满,那我们在watch 中判断一下新旧值的地址是否相同就好了,如果相同,发生了变化才通知父元素,如果不相同,则不通知。
5. 解决方案
综上所述,最后的解决方案跟第3点实现思路中的方案极为相似,只有一处代码不同,RoomUnit 中watch 监听的写法
watch: { // 监测item的值是否被修改 item: { handler: function (newV, oldV) { // 判断是否是刷新导致的变化,地址相同说明不是刷新 if (newV == oldV) { this.$emit('updateRoom', newV) } }, deep: true } }