Vue: 批量更新操作实现与踩坑记录
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 应该如何解决
(1)方案一:既然每次刷新之后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)
(2)方案二:既然刷新列表后,watch 是因为地址的变化导致父元素列表加满,那我们在watch 中判断一下新旧值的地址是否相同就好了,如果相同,发生了变化才通知父元素,如果不相同,则不通知。
5. 解决方案
综上所述,最后的解决方案跟第3点实现思路中的方案极为相似,只有一处代码不同,RoomUnit 中watch 监听的写法
watch: {
// 监测item的值是否被修改
item: {
handler: function (newV, oldV) {
// 判断是否是刷新导致的变化,地址相同说明不是刷新
if (newV == oldV) {
this.$emit('updateRoom', newV)
}
},
deep: true
}
}
上一篇: 我常用的cmd命令
下一篇: PHP中foreach循环语句