實現 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>