目录
今天,在项目中遇到这样一个效果,具体如下
布局
相信这个布局应该不难,无非就是在表单中嵌套表格而已,代码结构我也发放在下面
那么老板要什么效果呢?
其实就是,当你点击批量填充的时候,将从form中收集到的服务费率和推广结束时间填充到组件table对应位置
问题分析
底部table的数据是请求得到的,或者是组件传值得到的,我们具体也不知道有多少条。
那么对应的输入框和时间选择器显然不可以绑定同一个v-model
否则,修改其中一个输入框的v-model,就会影响其他的输入框
提交表单时,table里面的输入框和时间选择器依旧可以正常输入和选择
也就是说我既可以批量修改,也可以单独修改,
尝试
第一次尝试的时候,我在table底下的输入框和时间选择器的v-model上分别在form表单的prop声明上同时声明了各自的v-model,并且获取scope.row.id用于绑定每一行唯一的v-model,代码如下:
<el-table-column prop="product_number" label="服务费率" width="200"> <template #default="scope"> <div> <el-input size="small" v-model="tableServiceRate[scope.row.id]" ></el-input> </div> </template> </el-table-column>
绑定的v-model
interface IForm { serviceRate: String | null; time: Date; activeLink: string | null; phone: number; tableServiceRate: string[]; tableTime: Date[]; } const productForm = ref<IForm>({ serviceRate: '', time: '', activeLink: '', phone: null, tableServiceRate:[]//服务费率 tableTime:[]//table时间选择器 });
之后给批量填充绑定点击事件
因为,批量填充要求服务费率或者推广结束时间至少填写一个!
所以,我们先去做一个简单的判断,并且加上对应的消息提示
// 批量填充 const handleBatchFill = () => { if (productForm.serviceRate === '' && productForm.time === '') { ElMessage({ type: 'warning', message: '服务费率或者推广结束时间至少填写一个!', }); return; } };
接下来才是正题,
因为之前我们给table的输入框绑定的是带有当前行数据id标识的v-model
这样确保了每一行循环出来的输入框的v-model的唯一性
所以这里,我们在点击事件里,去循环table的数据,找到id,然后去table外部form表单项的输入框的值,赋值给productForm.tableServiceRate这个数组里key为id值的项,
ProductSaleListData.forEach((item) => { productForm.tableServiceRate[item.id] = productForm.serviceRate; productForm.tableTime[item.id] = productForm.time; });
这时候出现一个报错信息,它提示声明的productForm.tableServiceRate数组里不能设置未定义的key id
因为这时候遍历出来的id值是数字,例如:8
后面修改为字符串之后还是不行,
这时,我想到,使用re'reactive去声明,修改之后,果然,不报错了,
我刷新之后,发现,咦!为什么打印修改之后的数组,里面需要的值正常填进去了,但是,视图没有更新,一直处于空状态,
后面了解到,在vue3中reactive有时会失去响应,
基于此,我又将代码修改如下:
const productFormNew = toRef(productForm)
后面再循环赋值时,使用toRef后的响应式对象
但是,效果依旧没有实现,还是死驴不动弹!!!
转换思路
我们可以利用nextTick立即获取组件更新后的值
全局 API:常规 | Vue.js (vuejs.org)
官方解释:当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。
nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。
ProductSaleListData.forEach((item) => { console.log(item.id); nextTick(() => { productForm.tableServiceRate[item.id] = productForm.serviceRate; productForm.tableTime[item.id] = productForm.time; }); console.log(productForm.tableServiceRate, '-d-d-d-d--'); });
后面发现这里不应该用nextTick
晕死~~~~
继续转换思路
我们可以使用 watchEffect监听值的更新,每次点击都将form表单的输入框的值赋给table
ProductSaleListData.forEach((item) => { console.log(item.id); watchEffect(() => { productForm.tableServiceRate[item.id] = productForm.serviceRate; productForm.tableTime[item.id] = productForm.time; }); console.log(productForm.tableServiceRate, '-d-d-d-d--'); });
惊奇的发现效果可以实现
我以为到此就可以结束了
但是,bug来了
实际效果是,当我点击批量填充后,table没有变化,但是,当我继续点击tablel里面的输入框时,它才会将form的值更新到table项上
出错原因:因为我将weatchEffect写在了点击事件里面 每次点击之后,它才会开启监听数据并赋值
那我将weatchEffect单独提出来,在监听响应式数据的变化,
呃,一切回到解放前,底部table又没有了变化
继续转换思路
经过,数次打磨,
这次
<el-table-column prop="product_number" label="服务费率" width="200"> <template #default="scope"> <div> <el-input size="small" v-model="tableServiceRate[scope.$index]" :value="productForm.serviceRate"></el-input> </div> </template> </el-table-column> <el-table-column prop="wait_delivery_num" label="推广结束时间" width="264"> <template #default="scope"> <el-date-picker size="small" v-model="tableTime[scope.$index]" :value="productForm.time" style="width: 220px" type="date" /> </template> </el-table-column>
v-model="tableTime[scope.$index]"
:使用Vue.js的双向数据绑定,将日期选择器的值和tableTime数组中的指定索引位置进行绑定。即当日期选择器的值发生变化时,tableTime数组中对应位置的值也会改变。:value="productForm.time"
:将productForm对象中的time属性的值作为日期选择器的默认值。然后,去把table输入框绑定的v-modle的值从form里面提出来,单独声明ref的响应式数据
利用内部value的修改,去强制修改v-model的值
并且不再利用当前行数据的id去做唯一性处理,而是利用当前行的索引,
并且,在点击事件里面修改如下:
const tableServiceRate = ref<string[]>([]); // 设置表格的input const tableTime = ref<Date[]>([]); // 设置表格的time const handleBatchFill = () => { if (productForm.serviceRate === '' && productForm.time === '') { ElMessage({ type: 'warning', message: '服务费率或者推广结束时间至少填写一个!', }); return; } else { if (productForm.serviceRate) { tableServiceRate.value = Array(ProductSaleListData.length).fill(productForm.serviceRate); } if (productForm.time) { tableTime.value = Array(ProductSaleListData.length).fill(productForm.time); } } console.log('批量填充', tableServiceRate.value); };
将一个具有相同元素的数组赋值给名为tableServiceRate
的变量。该数组的长度由ProductSaleListData
数组的长度决定,并且每个元素的值都是productForm.serviceRate
。
换句话说,如果ProductSaleListData
数组长度为n,那么tableServiceRate
数组将包含n个元素,每个元素的值都是productForm.serviceRate
。这样可以简化对数组元素的赋值过程,使每个元素都具有相同的数值。
这样,就完成了数据批量填充。