写在前面
在现代的vue组件库上实现表格编辑其实不难,它们大都提供了作用域插槽,可以在插槽内插入input
组件来实现。但这样做有个弊端,太重了!在几十上百行的表格内铺满input
卡顿几乎不可避免,而且也很难做到美观。
思考
想实现动态编辑功能其实相当简单,我们只需在当前点击的单元格中渲染出input
,光标离开销毁这个input
即可
实现
这里借助 vue自定义指令来实现
因为指令不支持直接数据双向绑定,参数上设计了value
和input
2个钩子,一个用来取值,一个用来赋值
import Vue from 'vue';
const isFunc = v => typeof v === 'function';
const getTargetEl = (el, nodeName) =>
!nodeName || el.nodeName === nodeName ? el :
el.parentElement ? getTargetEl(el.parentElement, nodeName) : undefined;
function createInput(options) {
const input = document.createElement('input');
input.className = options.inputClass || 'v-field';
input.type = options.inputType || 'text';
input.value = isFunc(options.value) ? options.value() : options.value;
return input;
}
const targets = [];
function handle(el, binding) {
const options = binding.value || {};
const targetEl = getTargetEl(el, options.nodeName || 'TD');
if (targetEl) {
let target = targets.find(v => v.el === targetEl), inputEl, disabled;
if (!target) {
target = {options, el: targetEl};
targets.push(target);
targetEl.style.position = 'relative';
targetEl.addEventListener(options.event || 'click', () => {
disabled = isFunc(options.disabled) ? options.disabled() : options.disabled;
if (!disabled && !inputEl) {
inputEl = createInput(target.options);
targetEl.appendChild(inputEl);
inputEl.focus();
inputEl.onblur = () => {
target.options.input && target.options.input(inputEl.value);
targetEl.removeChild(inputEl);
inputEl = undefined;
};
}
});
} else {
target.options = options;
}
}
}
const VField = {
inserted: handle,
update: handle,
};
/**
* v-field="{value: () => row.name, input: v => row.name = v}"
*/
Vue.directive('field', VField);
代码写完了,我们需要给这个input
定义样式,我在createInput
的代码中默认给它加了.v-field
这个类,并提供了inputClass
这个配置项方便自定义扩展
<style>
.v-field {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
padding: 0 10px;
box-shadow: 1px 1px 20px rgba(0,0,0,.15);
box-sizing: border-box;
background-color: #FFF;
background-image: none;
border: 1px solid #DCDFE6;
display: inline-block;
outline: 0;
}
.td-field:focus {
border-color: #5FB878!important
}
</style>
使用
在作用域插槽内通过一个可以绑定指令的dom
来使用
v-field="{value: () => row.date, input: v => row.date = v }"
类似下面这个例子
<el-table
:data="tableData"
style="width: 100%">
<el-table-column
prop="date"
label="日期"
width="180">
<template slot-scope="{row}">
<span v-field="{value: () => row.date, input: v => row.date = v}">{{ row.date }}<span>
</template>
</el-table-column>
</el-table>
提供了一个event
配置项,指定触发事件,默认是单击click
,你也可以像下面这样设置成dblclick
来实现双击编辑
<span
v-field="{
event: 'dblclick',
value: () => row.date,
input: v => row.date = v
}"
>
{{ row.date }}
<span>
另外有一个配置项nodeName
用来筛选绑定事件对象,默认是TD
,如果想在表头使用,需改成TH
,参考下面element-ui
的例子
效果
我测试了比较热门的几个vue组件库,都能正确工作
element-ui
iview
antd-vue
通过npm使用
这个指令已被我打包到npm
安装
npm i --save v-field
在入口文件注册即可使用
import Vue from "vue";
import VField from "v-field";
Vue.use(VField); // 可通过第二个参数传入你想改的任意指令名,默认是`field`
通过npm使用的例子 https://codesandbox.io/s/v-field-hh8g2
已在github
开源,有想法的朋友可以提交代码
https://github.com/akebe/v-field
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。