注册
web

el-table实现可编辑表格的开发历程

写在前面的话


  想直接看代码的朋友可以省略下面的历程直接翻到最底下,我把完整示例代码放在最下面


引子


  笔者最近在做项目中遇到了一件事,某个迭代我们需要对项目进行UI改造,特别是把当前正在使用的一个可编辑表格换一下UI。说是换UI,其实是换表格,因为当前在用的表格组件是项目组花钱买的,但老板应该是对这个组件的UI有别的想法(其实就是觉得丑),然后经过老大的决定,我们需要换成Element-UI的组件(Element打钱~~ )。
  虽说组件要换,但是我们要尽可能的保留原先的功能,原来的组件,在使用上面非常贴近于Excel表格。然后,笔者开始库库干了。。。


初步实现


  为了快速实现功能,我们首先选择的是把这个可编辑表格的所有编辑项全部展示出来,这样用户就可以直接进行表格的编辑,就像这样:


PixPin_2025-09-23_01-08-19.gif
  但很快,我们就发现了第一个问题。
  我们的表格中,有两列下拉框使用了远端搜索功能,同时使用了一个封装的下拉选择组件。这就使得当下拉框有值的时候,它会尝试用value在下拉选项中去匹配对应的label,而下拉选项需要通过远端搜索即调接口获取。这两列调的是同一个接口,哪怕这里做了分页并且默认一页10条,仍然默认会调同一个接口20次,这是一个很影响性能的问题,如果切换成一页20条、30条、50条的话,后果不堪设想。。。
  考虑到这种情况,我们首先采取的方法是只调一次接口,把选项数据全部拉回来本地,然后让使用这些选项的下拉框直接引用。但在这里,我们又发现,这些下拉框是这样的:


PixPin_2025-09-23_01-47-28.gif
  是的,label和value同时展示出来,而且在远程搜索中,可以搜索label或value来找对应项。
  那这里我们就得使用filter-method自定义搜索方法咯,但这里有个问题,那就是搜索结果得要是独立的才行,即:第一次搜索的选项结果,不能出现在第二次的搜索选项里,意思就是每次搜索完,需要把选项还原到默认状态。这好办,visible-change事件可以实现。
  当我们把实现的功能交付出去后,产品给我们带来了一个噩耗:用户非得要跟Excel一样的,也就是说为了满足用户的使用习惯,我们需要尽可能还原出原来的表格组件来


image.png


解决之道


第一步


  事已至此,先吃饭吧,啊,不是,先百度吧
  在某次冲浪中,我发现了一篇文章,里面提到使用el-table组件的cell-dblclick事件来实现双击进入编辑状态的做法,也就是下面这样:
  通过列的prop和行的id一起来定位到双击选中的单元格的位置,然后通过v-if使得输入框渲染出来


PixPin_2025-09-23_02-48-59.gif


<template>
<el-table
:data="tableData"
style="width: 100%"
@cell-dblclick="cellDblclick"
>

<el-table-column prop="name" label="姓名" width="180">
<template slot-scope="scope">
<el-input v-if="formViewMethod(scope)" v-model="scope.row.name"></el-input>
<span v-else>{{ scope.row.name }}</span>
</template>
</el-table-column>
</el-table>

</template>
<script>
export default {
data() {
return {
editColumnProp: null,
editRowId: null
}
},
methods: {
cellDblclick(row, column, cell) {
cell.style.background = 'pink'
this.editColumnProp = column.property
this.editRowId = row.id
},
formViewMethod(scope) {
const { row, column } = scope
return (
row.id === this.editRowId &&
this.editColumnProp === column.property
)
}
}
}
</script>


  这方法确实可行!在默认是text的情况下,也就不会去调接口,这样,哪怕是用回远端搜索功能,也能保证对性能没有那种压力。
  第一步走出了,另一个问题就摆在眼前了:当我点击编辑框以外的地方,该怎么让它恢复默认那种文本状态呢?文章的作者并没有给出答案,那就得自己去寻找了


新的曙光


  最近在冲浪中,我了解到有一个名为ClickOutside的指令,这是一个vue3中的自定义指令,顾名思义,就是点击外面的意思。这下子灵感就来了:在cell-dblclick事件中,我们可以获取到当前单元格的dom,那如果我们在获取dom的时候,给它加上一个点击事件,当点击到外面的时候,就清空当前单元格的选中状态,那是不是就可以实现了呢?说干就干,上代码:


cellDblclick(row, column, cell) {
cell.style.background = 'pink'
cell.__clickOutside__ = (e) => {
if (cell.contains(e.target)) {
return console.log('点击了自己')
}
console.log('点击了外面')
cell.__clickOutside__ && document.removeEventListener('click', cell.__clickOutside__)
}
document.addEventListener('click', cell.__clickOutside__)
this.editColumnProp = column.property
this.editRowId = row.id
}

在这里我们使用了dom的contains()方法,这个方法用于检测一个元素是否包含另一个元素,返回的是一个布尔值。也就是当点击的时候,判断被点击元素B是否在双击的时候绑定点击事件的元素A之内,如果返回true的话,就是点击自己了,否则就是点击外面,这样就能实现清空选中状态的方法了。就像下面这样子:


PixPin_2025-09-23_03-54-37.gif
到这里,可编辑表格的功能就算实现了,谢谢大家观看,下面会贴上完整的示例代码,大伙儿可以直接复制粘贴来看看效果。


完整代码


<template>
<div class="irregular-table-container">
<div class="custom-table">
<!-- 表格区域 -->
<el-table
:data="tableData"
style="width: 100%"
@cell-dblclick="cellDblclick"
>

<el-table-column
prop="date"
label="日期"
width="180">

</el-table-column>
<el-table-column prop="name" label="姓名" width="180">
<template slot-scope="scope">
<el-input v-if="formViewMethod(scope)" v-model="scope.row.name"></el-input>
<span v-else>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column
prop="gender"
label="性别"
width="120"
:formatter="formatGender"
>

</el-table-column>
<el-table-column
prop="city"
label="城市"
width="200"
:formatter="formatCity"
>

</el-table-column>
<el-table-column prop="address" label="地址"/>
</el-table>
</div>
</div>

</template>

<script>
export default {
name: "EditableTable",
data() {
return {
tableData: [
{
id: 1,
date: '2016-05-02',
name: '王小虎',
gender: '男',
city: 'Beijing',
address: '上海市普陀区金沙江路 1518 弄'
}, {
id: 2,
date: '2016-05-04',
name: '王小虎',
gender: '男',
city: 'Nanjing',
address: '上海市普陀区金沙江路 1517 弄'
}, {
id: 3,
date: '2016-05-01',
name: '王小虎',
gender: '男',
city: 'Guangzhou',
address: '上海市普陀区金沙江路 1519 弄'
}, {
id: 4,
date: '2016-05-03',
name: '王小虎',
gender: '男',
city: 'Shanghai',
address: '上海市普陀区金沙江路 1516 弄'
}
],
options: [
{ label: '男', value: 1 },
{ label: '女', value: 0 }
],
cities: [
{
value: 'Beijing',
label: '北京'
}, {
value: 'Shanghai',
label: '上海'
}, {
value: 'Nanjing',
label: '南京'
}, {
value: 'Chengdu',
label: '成都'
}, {
value: 'Shenzhen',
label: '深圳'
}, {
value: 'Guangzhou',
label: '广州'
}
],
editColumnProp: null,
editRowId: null
}
},
computed: {},
created() {},
methods: {
formatName(row) {
const input = (
<el-input v-model={row.name} clearable />
)
return input
},
formatGender(row) {
const select = (
<el-select v-model={row.gender}>
{this.options.map(item => {
return (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>

)
})}
</el-select>

)
return select
},
formatCity(row) {
const select = (
<el-select v-model={row.city}>
{this.cities.map(item => {
return (
<el-option
key={item.value}
label={item.label}
value={item.value}
>

<span style="float: left">{ item.label }</span>
<span style="float: right; color: #8492a6; font-size: 13px">{ item.value }</span>
</el-option>
)
})}
</el-select>

)
return select
},
cellDblclick(row, column, cell) {
cell.style.background = 'pink'
cell.__clickOutside__ = (e) => {
if (cell.contains(e.target)) {
return console.log('点击了自己')
}
// console.log('点击了外面')
this.editColumnProp = null
this.editRowId = null
cell.__clickOutside__ && document.removeEventListener('click', cell.__clickOutside__)
}
document.addEventListener('click', cell.__clickOutside__)
this.editColumnProp = column.property
this.editRowId = row.id
},
formViewMethod(scope) {
const { row, column } = scope
return (
row.id === this.editRowId &&
this.editColumnProp === column.property
)
}
}
}
</script>



感谢名单


  写完一看时间,嚯,好家伙,凌晨4点了,赶紧碎觉,狗命要紧~~
  最后的最后,这里要感谢两位给我提供灵感和思路的大大,我把他们的文章链接放到下面了,感兴趣的小伙伴可以过去学习下。
vue对el-table的二次封装,双击单元格编辑,避免表格输入框过多卡顿
vue自定义指令(v-clickoutside)-点击当前区域之外的位置


作者:午言
来源:juejin.cn/post/7552789573735907328

0 个评论

要回复文章请先登录注册