春节过后公司需要上线一个抽奖优惠活动,在后台管理页面中涉及到表单的动态添加、修改dom等复杂操作。开发的过程中遇到许多的知识点和困难点,于是总结了以下一些技巧经验。
一、业务需求
如图所示,在抽奖页面中,涉及到获取奖品、添加奖品、修改奖品、删除奖品(增删改查)、上传图片等功能。
- 奖品类型分别是租期、实物、谢谢惠顾。谢谢惠顾类型由后台返回,不可添加只能修改,且类型不能修改,只能修改奖品名称和中奖概率和奖品展示。
- 租期类型没有图片字段、实物类型没有天数字段。
- 每一列上传图片功能。
- 当对当前列的数据进行修改时,按钮文案从修改变成保存,并且显示不同的按钮背景颜色。
- 删除数据时要区分dom数据还是服务端返回的真实数据,前者则不需要调用删除接口。
二、vue版本
基于vue2.x语法和element-ui框架。
三、难点分析
- 如何区分前端追加的dom数据和服务端返回的真实数据?(即添加和修改的区别)
- 每一列的表单编辑中状态如何实现?(修改按钮-->保存按钮)
四、关键代码实现
// data数据
lotteryList: [
// {
// hl_name: "",
// hl_type: null,
// hl_days: "",
// hl_lnums: "", //库存
// hl_shownum: "", //展示数量
// hl_img: "",
// isImgUpload: false, // 上传图片开关 前端加的判断
// isEdit: false, // 是否编辑状态 前端加的判断
// hl_lucky: true, // 1开 2关 提交的时候需要转换一下
// hl_rate: "", // 概率
// },
],
watchLotteryList: [], // 用来监听列表的数据变化
originLotteryList: [], // 源数据 深拷贝后的
curIndex: null
// 动态添加dom
onAdd() {
// 默认追加的dom数据是没有id的 因此根据id来区别真假数据
this.lotteryList.push({
hl_name: "",
hl_type: null,
hl_days: "",
hl_lnums: "",
hl_shownum: "",
hl_img: "",
isImgUpload: false, // 前端加的判断
isEdit: false, // 前端加的判断
hl_lucky: true,
hl_rate: "",
});
},
// 获取抽奖列表
getList() {
getLottery({}).then(res => {
console.log('res: ', res);
if(res.state == 0){
if (res.data) {
let data = res.data
data.filter(item => {
item.hl_type = Number(item.hl_type) // 需要number类型 否则el-select不会回显
item.hl_lucky = item.hl_lucky == 1 ? true : false;
item.isEdit = false // 追加一个编辑状态
})
console.log('data: ', data);
this.lotteryList = data
// this.watchLotteryList = JSON.parse(JSON.stringify(data))
// 保持一份初始数据 不被双向绑定给影响到
this.originLotteryList = JSON.parse(JSON.stringify(data))
// 这一份数据用来监听列表数据的 因为直接监听lotteryList再修改原数据lotteryList会造成监听无限执行
this.watchLotteryList = data
} else {
this.lotteryList = []
}
}
})
},
// 删除
onDelete(index) {
if (this.lotteryList[index].hl_id) {
// 真实数据
console.log('真实数据: ');
delLottery({
hl_id: this.lotteryList[index].hl_id
}).then(res => {
if (res.state == 0) {
this.$message({
message: '删除成功',
type: 'success'
});
this.getList()
}
})
} else {
// dom数据
console.log('dom数据: ');
this.lotteryList.splice(index, 1);
}
},
watch: {
// lotteryList: {
// 不要监听列表用的lotteryList数据 因为最终要修改lotteryList数据 会造成监听无限执行
watchLotteryList: {
// 深度监听会造成val和oldVal的值相同
handler(val, oldVal) {
setTimeout(() => {
console.log('---', this.curIndex, this.lotteryList[this.curIndex])
// 存在id 即编辑状态下
if (this.curIndex !=null && this.lotteryList[this.curIndex].hl_id) {
console.log('originLotteryList: ', this.originLotteryList[this.curIndex]);
console.log('val: ', val[this.curIndex]);
if (this.isEqual(this.originLotteryList[this.curIndex], val[this.curIndex])) {
console.log('2个对象数据一致')
this.lotteryList[this.curIndex].isEdit = false
// 原始数据也要同步修改
this.originLotteryList[this.curIndex].isEdit = false
} else {
console.log('2个对象数据不一致!!!')
this.lotteryList[this.curIndex].isEdit = true
this.originLotteryList[this.curIndex].isEdit = true
}
console.log('最后', this.lotteryList[this.curIndex])
// this.curIndex = null
}
}, 50)
},
deep: true
},
},
// 记录当前选中的数据 select则用@change事件 其他元素添加@click事件
onItem(index) {
console.log('选中下标', index)
this.curIndex = index
},
// 对比2个对象是否一致
isEqual(objA, objB){
//相等
if(objA === objB) return objA !== 0 || 1/objA === 1/objB;
//空判断
if(objA == null || objB == null) return objA === objB;
//类型判断
if(Object.prototype.toString.call(objA) !== Object.prototype.toString.call(objB)) return false;
switch(Object.prototype.toString.call(objA)){
case '[object RegExp]':
case '[object String]':
//字符串转换比较
return '' + objA ==='' + objB;
case '[object Number]':
//数字转换比较,判断是否为NaN
if(+objA !== +objA){
return +objB !== +objB;
}
return +objA === 0?1/ +objA === 1/objB : +objA === +objB;
case '[object Date]':
case '[object Boolean]':
return +objA === +objB;
case '[object Array]':
//判断数组
for(let i = 0; i < objA.length; i++){
if (!this.isEqual(objA[i],objB[i])) return false;
}
return true;
case '[object Object]':
//判断对象
let keys = Object.keys(objA);
for(let i = 0; i < keys.length; i++){
if (!this.isEqual(objA[keys[i]],objB[keys[i]])) return false;
}
keys = Object.keys(objB);
for(let i = 0; i < keys.length; i++){
if (!this.isEqual(objA[keys[i]],objB[keys[i]])) return false;
}
return true;
default :
return false;
}
},
// 防抖函数
debounce(func, wait = 300, timer, immediate = false) {
// 移除定时器
if (timer !== null) {
clearTimeout(timer)
}
// 是否立即执行
if (immediate) {
// 立即执行
const callNow = !timer
timer = setTimeout(() => {
timer = null
}, wait)
if (callNow) typeof func === 'function' && func()
} else {
// 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
timer = setTimeout(() => {
typeof func === 'function' && func()
}, wait)
}
},