共计 7002 个字符,预计需要花费 18 分钟才能阅读完成。
问题形容
有一个简略的表格,产品要求实现双击可编辑
看了一下网上的帖子,大多数都是搞两局部 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 标签
,客官稍等,下方会解答
- 第 2.1 步:单元格双击事件当前,咱们首先创立一个 el-input 标签,而后把点击的这个单元格的值,作为参数 props 让这个 el-input 接管,这样的话 el-input 就会显示这个单元格的值了,就能够编辑了。
- 这样的话,每次双击搞一个 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.js
import Vue from "vue";
import definedInput from "./input.vue";
// vue 继承这个 input 组件,就相当于一个构造函数了
const inputC = Vue.extend(definedInput);
// 裸露进来,哪里须要哪里引入
export default {inputC,}
页面中引入并应用
// page.vue
import 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.vue
three.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 构造函数生成组件 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>
总结
应用 Vue.extend()
办法,能够继承一些组件,甚至继承一些简单的组件,在理论业务场景中会有奇妙的应用。具体业务场景具体分析。
此外,上述代码中是el-input 的继承
,其实,咱们也能够做el-select 的继承
,思路和上方相似,这样就能够在表格中双击单元格,抉择并更改对应的下拉框更改 el-table 的单元值了,比方如果有性别这一列,那是下拉框的模式的。道友们能够依照这个思路发散哦 …
好忘性不如烂笔头,记录一下吧
^_^