有一个简单的表格,产品要求实现双击可编辑
看了一下网上的帖子,大多数都是搞两部分dom,
一块是输入框,用于编辑状态填写
;
另一块是普通标签,用于在不编辑显示状态下呈现单元格文字内容
。再加上一个flag标识搭配
v-if和v-else
去控制编辑状态、还是显示状态。大致代码如下:
<el-table-column
align="center"
label="姓名"
<template slot-scope="scope">
<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.value = '孙悟空'
document.body.appendChild(input)
不过el-input标签不能通过上述方式创建,因为document.createElement()方法虽然可以创建出来el-input标签,但是dom并不认识这个el-input标签,所以页面没有变化。毕竟饿了么的el-input也是把input标签做一个二次封装的
所以,这里我们可以使用Vue.extend()方法去继承一个组件并暴露出去,而继承的这个组件中又有一个input标签,所以那个需要使用,那里就可以引入并new出来一个el-input了
。关于Vue.extend()的定义啥的,这里不赘述,详情看官方文档。笔者之前也写过一篇Vue.extend文章,传送门:juejin.cn/post/702172…
首先搞一个.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文件,并暴露
import Vue from "vue";
import definedInput from "./input.vue";
const inputC = Vue.extend(definedInput);
export default {
inputC,
页面中引入并使用
import extendComponents from "./threeC/data";
new extendComponents.inputC({
propsData: {
cellValue: cellValue,
}).$mount(cell.children[0]);
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,
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,
cellDom: Node,
row: Object,
property: String,
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>
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) {
if (column.label == "序号") {
this.$message({
type: "warning",
message: "序号列不允许编辑",
return;
this.oldCellValue = row[column.property];
let cellValue = row[column.property];
new extendComponents.inputC({
propsData: {
cellValue: cellValue,
saveRowData: this.saveRowData,
cellDom: cell,
row: row,
property: column.property,
}).$mount(cell.children[0]);
* 失去焦点的时候有以下操作
* 1. 校验新值是否等于原有值,若等于,说明用户未修改,就不发请求。若不等于就发请求,然后更新tableData数据
* 2. 然后使用$mount方法,挂载一个新的span标签dom在页面上,即恢复原样,而span标签也是实例化的哦
saveRowData(params) {
console.log("继承的子组件传递过来的数据", params);
if (params.cellValue == this.oldCellValue) {
console.log("未修改数据,不用发请求");
} else {
params.row[params.property] = params.cellValue;
setTimeout(() => {
this.$set(this.tableData, params.row.index, params.row);
}, 300);
* 方式一:使用官方推荐的$mount去挂载到某个节点上,上方也是
new extendComponents.spanC({
propsData: {
cellValue: params.cellValue,
}).$mount(params.cellDom.children[0]);
* 方式二:使用原生js去清空原节点内容,同时再添加子元素
</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的单元值了,比如如果有性别这一列,那是下拉框的形式的。道友们可以按照这个思路发散哦...
好记性不如烂笔头,记录一下吧 ^_^
2022-08-25补充~单元格校验功能
首先感谢大家的点赞鼓励,笔者会继续加油的,争取少写一点水文(手动狗头)。有道友在评论区说,要加上校验功能,这里笔者提供了一种思路:
指定校验函数和校验函数未通过的提示文字
然后将其作为参数传递到需要实例化的el-input上
需要实例化的el-input再使用传递进来的校验函数再传递出去
根据校验函数的结果是否通过,做相应处理
假设表格有如下校验规则
姓名列单元格不能超过6个字
年龄列单元格不能大于1000岁(且为数字类型)
家乡列单元格不做限制
效果图如下:
主要更改的代码:
function nameMaxLength6(str) {
return str.length <= 6;
function ageMaxValue1000(str) {
return Number(str) <= 1000;
function noValidate(params) {
return true;
tableData: [ ... ],
oldCellValue: null,
columnAddValidate: {
name: {
validateFn: nameMaxLength6,
validateMessage: "名字最大不能超过6个字符",
age: {
validateFn: ageMaxValue1000,
validateMessage: "注意!年龄是数字类型,且最大不能超过1000岁",
home: {
validateFn: noValidate,
new extendComponents.inputC({
propsData: {
cellValue: cellValue,
saveRowData: this.saveRowData,
cellDom: cell,
row: row,
property: column.property,
* 新加的动态校验函数
* 只需要指定校验的函数,以及校验未通过时候的文字提示(对象匹配方式)
validateFn: this.columnAddValidate[column.property].validateFn,
validateMessage:
this.columnAddValidate[column.property].validateMessage,
}).$mount(cell.children[0]);
blurFn() {
this.saveRowData({
cellValue: this.cellValue,
cellDom: this.cellDom,
row: this.row,
property: this.property,
isPassValidate: this.validateFn(this.cellValue),
validateMessage: this.validateMessage,
......
完整代码地址
个人建议,直接去笔者的github仓库拉下来完整代码,跑起来看一下,更加有助于理解。仓库地址:github.com/shuirongshu…
另外本仓库是笔者仿写饿了么UI组件的代码仓库,如果也能帮到您,欢迎大手一挥给个star哦^_^
- 1791
-
max冰狼
Element
Vue.js
- 2.0w
-
bilimumu
Vue.js
JavaScript