这里写自定义目录标题
Vue 实现表格单元格的原位编辑
这里是 Vue 初学者的笔记,老鸟请回避。如果您是热心的前辈或高手,热切期盼您留下宝贵的意见。
起初
有一个小需求,我希望通过浏览器读入 excel 文件,并可以在网页上修改数据,查了一些 grid 编辑的 JS 插件,真是多如牛毛,导致了我的选择困难症发作。而为了编辑几个简单的数据,专门引入插件似乎有点不划算,我决定自己写。花一个小时认真阅读 Vue 介绍后开始动手码砖,半个下午成果如下:
功能
Vue 果然比较犀利,几行代码就初步实现了基本功能:
- 单击任意单元格,在原位修改它的值。
- 单元格数据修改完毕,可以回车确定。
源码 在这里_ html + css + JS 混合的单文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 表格原位编辑</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.min.js"></script>
<style>
.ctab td {
height: 30px;
font-size: 14px;
text-align: center;
border-left: 1px solid #555;
border-top: 1px solid #555;
border-spacing: 0;
overflow: hidden;
}
table {
background-color: #ffd;
border-right: 1px solid #555;
border-bottom: 1px solid #555;
table-layout: fixed;
}
input {
background-color: #fff;
text-align: center;
font-size: 14px;
border: 0;
outline: none;
}
</style>
</head>
<body>
<div id="app">
<!-- 在这里捕捉 input 的按键冒泡事件开支最小,tr 和 td 在循环体内 -->
<table class="ctab" width="400" cellspacing="0" @keyup.enter="endata($event)">
<tr v-for="(item, r) in tab">
<!-- 使用 self 事件修饰符,防止点击单元格内部 input 元素时,发生函数调用事件 -->
<td v-for="(v, c) in item" :key="cdkey" @click.self="getvar(r, c, v, $event)">{{v}}</td>
</tr>
</table>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
tab: [['序号', 1, 2, 3], ['姓名', '张三', '李四', '王五'], ['性别', '', '', ''], ['年龄', 22, 30, 18]],
// cdkey 用处有二,1 通过 cdkey 变值重新渲染单元格数据;2 Vue 无法监测数组元素改变,但可以监测 cdkey
cdkey: 1
},
methods: {
getvar: function (r, c, v, e) {
const d = e.target;
const [w, h] = [d.offsetWidth - 4, d.offsetHeight - 4];
$(d).css('border', '2px solid #111').html('<input type="text">');
$('input').css({ 'width': w, 'height': h }).val(v).select()
.blur(f => {
this.tab[r][c] = f.target.value;
this.cdkey += 1;
});
},
endata: function (e) {
// 判断是否来自 input 的冒泡事件
if (e.target.nodeName == 'INPUT') this.cdkey += 1;
}
}
});
</script>
</body>
</html>
问题
核心代码就 getvar 函数和 endata 函数,getvar 函数添加了一个 input 元素,设置 input 失去焦点事件,更新 JS 数组,重新渲染表格数据。 endata 函数捕获回车按键,更新 cdkey,渲染页面,使 input 失去焦点。
虽然代码简单,但是问题仍然存在。
- 某个单元格修改完毕,click 第二个单元格时,并不会编辑第二个单元格,程序运行了上一个单元格的 blur 事件。
- 单击单元格修改时,在单元格原位添加 input 元素后,会引起整个表格向下微移几个像素。扣掉 input 少量的宽度和高度,但问题依旧。
第一个问题可以回避,将 click 改为 dblclick,单击变双击后就没有问题了。双击事件响应时,被 blur 偷吃一个,并不影响第二个单元格的编辑。
第二个问题影响不大,可以默默忍受。
当然,不用 Vue 也有简单的插件实现:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>html表格编辑示例</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.core.min.js"></script>
<style>
table td {
width: 80px;
text-align: center;
border: 1px solid #aaa;
}
span {padding: 0 10px;}
</style>
</head>
<body>
<div id="container"></div>
<script>
var sheet = [['1', '张三', '男', 82], ['2', '李四', '女', 89], ['3', '王麻子', '男', 70]];
var ws = XLSX.utils.aoa_to_sheet(sheet);
var html_string = XLSX.utils.sheet_to_html(ws, { id: "data-table", editable: true });
document.getElementById("container").innerHTML = html_string;
</script>
</body>
</html>