问题形容

有一个简略的表格,产品要求实现双击可编辑

看了一下网上的帖子,大多数都是搞两局部dom,一块是输入框,用于编辑状态填写另一块是一般标签,用于在不编辑显示状态下出现单元格文字内容。再加上一个flag标识搭配v-if和v-else去管制编辑状态、还是显示状态。大抵代码如下:

  <el-table-column    align="center"    label="姓名"  >    <template slot-scope="scope">      <!--isClick就是标识状态,状态处于编辑时候,显示输入框,状态属于出现状态就显示文本内容-->      <el-input v-if="scope.row.isClick" v-model="scope.row.name"  @blur="blurFn(scope.row)"></el-input>      <span @click="clickCell(scope.row)" v-else>{{scope.row.name}}</span>    </template>  </el-table-column>

这种形式有其实用场景,然而得每个el-table-column列中都加上el-input和span以及v-if和v-else。咱们尝试一下动静增加el-input,就是点击那个单元格,给那个单元格增加el-input让其处于可编辑状态,而后适时移除即可。这样的话,很多列的时候,就不必加很多个v-if和v-else啦。咱们先看一下效果图

效果图

代码思路

  • 第1步:给el-table绑定双击事件 @cell-dblclick='dblclick',再双击事件的回调函数中,能够得悉点击的是哪一行、那一列、那个单元格dom,以及点击事件。dblclick(row, column, cell, event) {...},这个是饿了么官网提供的,没啥好说的
  • 第2步:重点来喽

    • 第2.1步:单元格双击事件当前,咱们首先创立一个el-input标签,而后把点击的这个单元格的值,作为参数props让这个el-input接管,这样的话el-input就会显示这个单元格的值了,就能够编辑了。问题一:如何创立一个el-input标签? ,客官稍等,下方会解答
    • 第2.2步:把创立好的el-input标签替换掉原来的单元格span标签,这样的话,就能够看到单元格变成了可输出的输入框了。问题二:如何把新创建的el-input标签,替换原有的span标签 ,客官稍等,下方会解答
    • 第2.3步,当用户编辑完了点击别处时候,即输入框失去焦点的时候,再把el-input输入框标签移除掉,复原默认的span标签(当然失去焦点的时候,就要发申请批改数据了)问题三:如何移除el-input标签,并复原原有的span标签,客官稍等,下方会解答
  • 这样的话,每次双击搞一个input标签用于批改,每次改完了失去焦点,就复原默认单元格展现状态了,性能就实现了

代码思路中的三个问题解答

问题一:如何创立一个el-input标签?

咱们晓得,如果是创立原生的input标签并指定一个值,比较简单,间接:

let input = document.createElement('input') // 创立一个input标签input.value = '孙悟空' // 给input标签赋值document.body.appendChild(input) // 把input标签追加到文档body中

不过el-input标签不能通过上述形式创立,因为document.createElement()办法尽管能够创立进去el-input标签,然而dom并不意识这个el-input标签,所以页面没有变动。毕竟饿了么的el-input也是把input标签做一个二次封装的

所以,这里咱们能够应用Vue.extend()办法去继承一个组件并裸露进来,而继承的这个组件中又有一个input标签,所以那个须要应用,那里就能够引入并new进去一个el-input了。对于Vue.extend()的定义啥的,这里不赘述,详情看官网文档。笔者之前也写过一篇Vue.extend文章,传送门:https://segmentfault.com/a/11...

首先搞一个.vue文件,用于继承

// input.vue文件<template>  <div class="cell">    <el-input      ref="elInputRef"      size="mini"      v-model.trim="cellValue"    ></el-input>  </div></template>props: {    cellValue: {      type: String | Number,      default: "",    },}

而后定义一个data.js文件,继承input.vue文件,并裸露

// data.jsimport Vue from "vue";import definedInput from "./input.vue";// vue继承这个input组件,就相当于一个构造函数了const inputC = Vue.extend(definedInput);// 裸露进来,哪里须要哪里引入export default {    inputC,}

页面中引入并应用

// page.vueimport extendComponents from "./threeC/data"; // 1. 引入new extendComponents.inputC({ // 2. 实例化    propsData: {      // 应用propsData对象传递参数,子组件在props中能够接管到      cellValue: cellValue, // 传递单元格的值    },  }).$mount(cell.children[0]);// 3. 挂载
propsData对象用于给继承的组件传递参数,也能够传递一个函数,从而继承组件通过这个函数告诉内部应用组件,详情见后续残缺代码

问题二三:el-input标签和span标签的来回替换复原

应用$mount办法去做来回替换,$mount能够把一个子dom元素追加到父dom元素外部,相当于appendChild

而后这里须要有一个替换的机会,就是实例化的组件中的el-input失去焦点的时候,去告诉内部应用的组件,所以能够在内部应用是,在propsData中传递一个函数到继承的组件,如:

// 内部组件传递new extendComponents.inputC({    propsData: {      cellValue: cellValue, // 传递单元格的值      saveRowData: this.saveRowData, // 传递回调函数用于告诉,继承组件中能够触发之    },}).$mount(cell.children[0]); saveRowData(params){    console.log('收到继承组件音讯告诉啦参数为:',params)}
// 外部组件失去焦点时候告诉<el-input  ref="elInputRef"  size="mini"  v-model.trim="cellValue"  @blur="blurFn"></el-input>props: {    cellValue: {      type: String | Number,      default: "",    },    saveRowData: Function, // 内部,传递进来一个函数,当这个el-input失去焦点的时候,通过此函数告诉内部}blurFn() {  // 失去焦点,再抛出去,告诉内部  this.saveRowData({    cellValue: this.cellValue,    // 其余参数  });},

所以当内层失去焦点的时候,就能够告诉外层去做一个替换了,就是把单元格dom从新做一个$mount挂载,就把el-input替换成了span了,为了进一步了解,这里的span咱们也能够应用继承的形式,是new实例化应用,详情见下方残缺代码

残缺代码

目录构造

threeC-- data.js-- input.vue-- span.vuethree.vue

用于继承的el-input组件

input.vue

<template>  <div class="cell">    <el-input      ref="elInputRef"      size="mini"      v-model.trim="cellValue"      @blur="blurFn"    ></el-input>  </div></template><script>export default {  props: {    cellValue: {      type: String | Number,      default: "",    },    saveRowData: Function, // 内部,传递进来一个函数,当这个el-input失去焦点的时候,通过此函数告诉内部    cellDom: Node, // 单元格dom    row: Object, // 单元格所在行数据    property: String, // 单元格的key  },  mounted() {    // 用户双击后,让其处于获取焦点的状态    this.$refs.elInputRef.focus();  },  methods: {    blurFn() {      // 失去焦点,再抛出去,告诉内部      this.saveRowData({        cellValue: this.cellValue,        cellDom: this.cellDom,        row: this.row,        property: this.property,      });    },  },};</script><style>.cell {  width: 100%;  height: 100%;  display: flex;  justify-content: center;  align-items: center;  box-sizing: border-box;  padding: 0 8px;}</style>

用于继承的span组件

span.vue

<template>  <span class="cell">{{ cellValue }}</span></template><script>export default {  props: {    cellValue: {      type: String | Number,      default: "",    },  },};</script>

对立继承并裸露data.js文件

import Vue from "vue";import definedInput from "./input.vue";import definedSpan from "./span.vue";const inputC = Vue.extend(definedInput);const spanC = Vue.extend(definedSpan);export default {    inputC,    spanC,}

应用继承的three.vue组件

<template>  <div id="app">    <el-table      @cell-dblclick="dblclick"      :cell-class-name="cellClassName"      height="480"      :data="tableData"      border    >      <el-table-column align="center" type="index" label="序号" width="50">      </el-table-column>      <el-table-column align="center" prop="name" label="姓名" width="100">      </el-table-column>      <el-table-column align="center" prop="age" label="年龄" width="100">      </el-table-column>      <el-table-column align="center" prop="home" label="他乡">      </el-table-column>    </el-table>  </div></template><script>// 引入继承组件对象,可取其身上的inputC构造函数、或spanC构造函数生成组件domimport extendComponents from "./threeC/data";export default {  data() {    return {      tableData: [        {          name: "孙悟空",          age: 500,          home: "花果山水帘洞",        },        {          name: "猪八戒",          age: 88,          home: "高老庄",        },        {          name: "沙和尚",          age: 1000,          home: "通天河",        },      ],      /**       * 存一份旧的值,用于校验是否发生变化,是否批改       * */      oldCellValue: null,    };  },  methods: {    cellClassName({ row, column, rowIndex, columnIndex }) {      row.index = rowIndex; // 自定义指定一个索引,下方可能用到    },    dblclick(row, column, cell, event) {      // 1. 序号列单元格不容许编辑,别的列单元格能够编辑      if (column.label == "序号") {        this.$message({          type: "warning",          message: "序号列不容许编辑",        });        return;      }      // 2. 存一份旧的单元格的值      this.oldCellValue = row[column.property];      // 3. 而后把单元格的值,作为参数传递给实例化的input组件      let cellValue = row[column.property];      // 4. 实例化组件当前,带着参数,再挂载到对应地位      new extendComponents.inputC({        propsData: {          // 应用propsData对象传递参数,子组件在props中能够接管到          cellValue: cellValue, // 传递单元格的值          saveRowData: this.saveRowData, // 传递回调函数用于保留行数据,组件中能够触发之          cellDom: cell, // 传递这个dom元素          row: row, // 传递双击的行的数据          property: column.property, // 传递双击的是哪个字段        },      }).$mount(cell.children[0]); // 5. $mount办法,用于将某个dom挂载到某个dom上    },    /**     * 失去焦点的时候有以下操作     *    1. 校验新值是否等于原有值,若等于,阐明用户未修改,就不发申请。若不等于就发申请,而后更新tableData数据     *    2. 而后应用$mount办法,挂载一个新的span标签dom在页面上,即复原原样,而span标签也是实例化的哦     * */    saveRowData(params) {      console.log("继承的子组件传递过去的数据", params);      // 1. 看看用户是否批改了      if (params.cellValue == this.oldCellValue) {        console.log("未修改数据,不必发申请");      } else {        params.row[params.property] = params.cellValue;        // 这里模仿一下发了申请,失去最新表体数据当前,更新tableData        setTimeout(() => {          //        给那个数组的     第几项            批改为什么值          this.$set(this.tableData, params.row.index, params.row);        }, 300);      }      // 2. 复原dom节点成为原来的样子,有上面两种形式      /**       * 形式一:应用官网举荐的$mount去挂载到某个节点上,上方也是       * */      new extendComponents.spanC({        propsData: {          cellValue: params.cellValue,        },      }).$mount(params.cellDom.children[0]);      /**       * 形式二:应用原生js去清空原节点内容,同时再增加子元素       * */      // let span = document.createElement("span"); // 创立一个span标签      // span.innerHTML = params.cellValue; // 指定span标签的内容的值      // span.classList.add("cell"); // 给span标签增加class为cell      // params.cellDom.innerHTML = ""; // 清空刚操作的input标签的内容      // params.cellDom.appendChild(span); // 再把span标签给追加下来,复原原样    },  },};</script><style lang="less" scoped>#app {  width: 100%;  height: 100vh;  box-sizing: border-box;  padding: 50px;}</style>

总结

应用Vue.extend()办法,能够继承一些组件,甚至继承一些简单的组件,在理论业务场景中会有奇妙的应用。具体业务场景具体分析。

此外,上述代码中是el-input的继承,其实,咱们也能够做el-select的继承,思路和上方相似,这样就能够在表格中双击单元格,抉择并更改对应的下拉框更改el-table的单元值了,比方如果有性别这一列,那是下拉框的模式的。道友们能够依照这个思路发散哦...

好忘性不如烂笔头,记录一下吧 ^_^