实现 el-table 中键盘方向键导航功能vue2+vue3(类似 Excel)
实现 el-table 中键盘方向键导航功能vue2+vue3(类似 Excel)
功能需求
在 Element UI 的 el-table 表格中实现以下功能:
- 使用键盘上下左右键在可编辑的 el-input/el-select 之间移动焦点
- 焦点移动时自动定位到对应单元格
- 支持光标位置自动调整,提升编辑体验
完整解决方案(vue2)
1. 表格结构修改
在 el-table 中添加键盘事件监听,并为可编辑元素添加定位标识:
<template><el-table :data="tableData" border style="width: 100%" size="small"><el-table-column prop="account_id" label="结算账户" width="180" align="center"><template slot-scope="scope"><el-select v-model="scope.row.account_id" placeholder="请选择账户" clearable filterable @keydown.native.stop="onKeyDown(scope.$index, 'account_id', $event)" :ref="getInputRef(scope.$index, 'account_id')"><el-option label="a" value="1" /><el-option label="b" value="2" /></el-select></template></el-table-column><el-table-column prop="money" label="结算金额" width="180" align="center"><template slot-scope="scope"><el-input v-model="scope.row.money" clearable @keydown.native.stop="onKeyDown(scope.$index, 'money', $event)" :ref="getInputRef(scope.$index, 'money')"/></template></el-table-column><el-table-column prop="remark" label="备注" align="center"><template slot-scope="scope"><el-input v-model="scope.row.remark" @keydown.native.stop="onKeyDown(scope.$index, 'remark', $event)" :ref="getInputRef(scope.$index, 'remark')"></el-input></template></el-table-column></el-table>
</template>
2. 核心 JavaScript 逻辑
在 Vue 组件的 methods 中添加焦点导航控制方法:
<script>
export default {data() {return {tableData: [{},{},{},{}],columns: [{prop: 'account_id'},{prop: 'money'},{prop: 'remark'}],refList: {}}},methods: {getInputRef(rowIndex, columnProp) {return `input-${rowIndex}-${columnProp}`},onKeyDown(rowIndex, columnProp, event) {// 当前列在columns数组中的索引const columnIndex = this.columns.findIndex((c) => c.prop === columnProp)// 计算下一个输入框的位置,如果是当前行的最后一个输入框则移到下一行的第一个输入框let nextColumnIndex = (columnIndex + 1) % this.columns.length;let nextRowIndex;switch(event.keyCode) {case 38:nextRowIndex = rowIndex - 1;nextColumnIndex = columnIndex;break;case 40:nextRowIndex = rowIndex + 1;nextColumnIndex = columnIndex;break;case 37:nextRowIndex = columnIndex === 0 ? rowIndex - 1 : rowIndexnextColumnIndex = columnIndex === 0 ? this.columns.length - 1 : columnIndex - 1break;case 39:nextRowIndex = columnIndex === this.columns.length - 1 ? rowIndex + 1 : rowIndexbreak;}const nextInputRef = `input-${nextRowIndex}-${this.columns[nextColumnIndex].prop}`const currentInputRef = `input-${rowIndex}-${this.columns[columnIndex].prop}`this.$nextTick(() => {if (this.$refs[nextInputRef]) {this.$refs[nextInputRef].focus()if (this.$refs[currentInputRef]) {this.$refs[currentInputRef].blur()}}})}}
}
</script>
完整解决方案(vue3)
<template><el-table :data="tableData" border style="width: 100%" size="small"><el-table-column prop="account_id" label="结算账户" width="180" align="center"><template #default="{ row, $index,column }"><el-select v-model="row.account_id" placeholder="请选择账户" clearable filterable @keydown="onKeyDown($index,column.property,$event)" :ref="(el)=>setRef(el,`input-${$index}-${column.property}`)"><el-option label="a" value="1" /><el-option label="b" value="2" /></el-select></template></el-table-column><el-table-column prop="money" label="结算金额" width="180" align="center"><template #default="{ row, $index,column }"><el-input v-model="row.money" clearable @keydown="onKeyDown($index,column.property,$event)" :ref="(el)=>setRef(el,`input-${$index}-${column.property}`)"/></template></el-table-column><el-table-column prop="remark" label="备注" align="center"><template #default="{ row,$index,column }"><el-input v-model="row.remark" @keydown="onKeyDown($index,column.property,$event)" :ref="(el)=>setRef(el,`input-${$index}-${column.property}`)"></el-input></template></el-table-column></el-table>
</template>
<script setup>
import { nextTick, ref } from 'vue'const tableData = [{},{},{},{}
]const columns = [{prop:'account_id'},{prop:'money'},{prop:'remark'}
]
const refList = ref({})
const setRef = (el,key)=>{refList.value[key] = el
}
function onKeyDown(rowIndex,columnProp,event){// 当前列在columns数组中的索引const columnIndex = columns.findIndex((c) => c.prop === columnProp)// 计算下一个输入框的位置,如果是当前行的最后一个输入框则移到下一行的第一个输入框let nextColumnIndex = (columnIndex + 1) % columns.length;let nextRowIndex;switch(event.keyCode){case 38:nextRowIndex = rowIndex - 1 ;nextColumnIndex = columnIndex;break;case 40:nextRowIndex = rowIndex + 1 ;nextColumnIndex = columnIndex;break;case 37:nextRowIndex = columnIndex === 0 ? rowIndex - 1 : rowIndexnextColumnIndex = columnIndex === 0 ? columns.length - 1 : columnIndex - 1break;case 39:nextRowIndex = columnIndex === columns.length - 1 ? rowIndex + 1 : rowIndexbreak;}const nextInputRef = `input-${nextRowIndex}-${columns[nextColumnIndex].prop}`const currentInputRef = `input-${rowIndex}-${columns[columnIndex].prop}`nextTick(() => {if (refList.value[nextInputRef]) {refList.value[nextInputRef].focus()refList.value[currentInputRef].blur()}})}
</script>