解决vue3中from表单嵌套el-table时填充el-input,v-model不唯一问题

寻技术 Html/CSS / JS脚本 2023年12月04日 75

今天,在项目中遇到这样一个效果,具体如下

布局

相信这个布局应该不难,无非就是在表单中嵌套表格而已,代码结构我也发放在下面

那么老板要什么效果呢?

其实就是,当你点击批量填充的时候,将从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这个数组里keyid值的项,

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。这样可以简化对数组元素的赋值过程,使每个元素都具有相同的数值。

这样,就完成了数据批量填充。

关闭

用微信“扫一扫”