35
头图

Problem Description

There is a simple form, and the product requirements can be double-clicked to be edited

I looked at the online posts, most of them are two-part dom, 一块是输入框,用于编辑状态填写 ; 另一块是普通标签,用于在不编辑显示状态下呈现单元格文字内容 . Add a flag with a flag v-if和v-else to control the editing state or the display state. The approximate code is as follows:

 <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>

This method has its applicable scenarios, 但是得每个el-table-column列中都加上el-input和span以及v-if和v-else . Let's try adding el-input dynamically, that is, click on that cell, add el-input to that cell to make it editable, and then remove it in time. In this case, when there are many columns, there is no need to add a lot of v-if and v-else. Let's take a look at the renderings

renderings

code ideas

  • Step 1: Bind the double-click event to el-table @cell-dblclick='dblclick' , and then in the callback function of the double-click event, you can know which row, which column, which cell dom was clicked, and the click event. dblclick(row, column, cell, event) {...} , this is officially provided by Ele.me, nothing to say
  • Step 2: Here comes the point

    • Step 2.1: After the cell double-click event, we first create an el-input tag, and then use the value of the clicked cell as a parameter props for the el-input to receive, so that the el-input will display the cell value, you can edit it. 问题一:如何创建一个el-input标签? , please wait a moment, the answer will be below
    • Step 2.2: Replace the created el-input tag with the original cell span tag, so that you can see that the cell becomes an input box that can be entered. 问题二:如何把新创建的el-input标签,替换原有的span标签 , please wait a moment, the answer will be below
    • Step 2.3, when the user finishes editing and clicks elsewhere, that is, when the input box loses focus, remove the el-input input box label and restore the default span label (of course, when the focus is lost, a request to modify the data must be sent 问题三:如何移除el-input标签,并恢复原有的span标签 , please wait a moment, the answer will be below
  • In this case, every time you double-click an input tag for modification, and every time you lose the focus after the modification, the default cell display state will be restored, and the function will be realized.

Answers to three questions in the code idea

Question 1: How to create an el-input tag?

We know that if it is to create a native input tag and specify a value, it is relatively simple and straightforward:

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

However, the el-input tag cannot be created by the above method, because although the document.createElement() method can create the el-input tag, the dom does not recognize the el-input tag, so the page does not change. After all, Ele.me's el-input also encapsulates the input tag as a secondary package.

So, 这里我们可以使用Vue.extend()方法去继承一个组件并暴露出去,而继承的这个组件中又有一个input标签,所以那个需要使用,那里就可以引入并new出来一个el-input了 . Regarding the definition of Vue.extend(), I will not go into details here. For details, see the official documentation. The author also wrote a Vue.extend article before, portal: https://segmentfault.com/a/1190000040848258

First make a .vue file for inheritance

 // 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: "",
    },
}

Then define a data.js file, inherit the input.vue file, and expose

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

Introduced and used in the page

 // page.vue
import extendComponents from "./threeC/data"; // 1. 引入

new extendComponents.inputC({ // 2. 实例化
    propsData: {
      // 使用propsData对象传递参数,子组件在props中可以接收到
      cellValue: cellValue, // 传递单元格的值
    },
  }).$mount(cell.children[0]);// 3. 挂载
The propsData object is used to pass parameters to the inherited component, or a function can be passed, so that the inherited component notifies the external component through this function. For details, see the subsequent complete code

Question 2 and 3: Back-and-forth replacement and restoration of el-input tags and span tags

Use the $mount method to replace back and forth, $mount can append a child DOM element to the parent DOM element, which is equivalent to appendChild

Then there needs to be a replacement time, that is, when the el-input in the instantiated component loses focus, it will notify the externally used component, so it can be used externally by passing a function in propsData to the inherited component, such as :

 // 外部组件传递
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,
    // 其他参数
  });
},

So when the inner layer loses focus, you can notify the outer layer to do a replacement, that is, to remake the cell dom $mount mount, and replace el-input with span, in order to For further understanding, we can also use the inheritance method for span here, which is instantiated by new. For details, see the complete code below.

full code

Directory Structure

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

el-input component for inheritance

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 component for inheritance

span.vue

 <template>
  <span class="cell">{{ cellValue }}</span>
</template>

<script>
export default {
  props: {
    cellValue: {
      type: String | Number,
      default: "",
    },
  },
};
</script>

Unified inheritance and exposure of the data.js file

 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,
}

Use inherited three.vue components

 <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构造函数生成组件dom
import 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>

Summarize

Using the Vue.extend() method, you can inherit some components, and even inherit some complex components, which will be cleverly used in actual business scenarios. Specific analysis of specific business scenarios.

In addition, the above code is el-input的继承 , in fact, we can also do el-select的继承 , the idea is similar to the above, so that you can double-click the cell in the table, select and change the corresponding drop-down box The cell value of el-table has been changed. For example, if there is a column of gender, it is in the form of a drop-down box. Daoists can follow this line of thinking...

Good memory is not as good as bad writing, record it ^_^

2022-08-25 Supplementary ~ cell verification function

For example, the input box cell has rules for the input content

Thought analysis

  • Specify the verification function and the prompt text that the verification function fails
  • Then pass it as a parameter to the el-input that needs to be instantiated
  • The el-input that needs to be instantiated is then passed out using the passed in validation function
  • According to whether the result of the verification function passes, do the corresponding processing

Suppose the table has the following validation rules

  • Name column cells cannot exceed 6 characters
  • Age column cell cannot be greater than 1000 years old (and is of numeric type)
  • Hometown column cells are not limited

The effect diagram is as follows

In order to facilitate everyone to use the code, the author specially uploaded it to the author's github repository. The code address is as follows: https://github.com/shuirongshuifu/elementSrcCodeStudy/blob/main/src/views/otherViews/clickTableCanEdit.vue

Personal suggestion, go directly to the author's github repository to pull down the complete code, run it and take a look. In addition, this repository is the code repository of the author's imitation of the Ele.me UI components. If it can help you, please give a star with a big hand ^_^

水冗水孚
1.1k 声望588 粉丝

每一个不曾起舞的日子,都是对生命的辜负