实现 el-table 中键盘方向键导航功能vue2+vue3(类似 Excel)

实现 el-table 中键盘方向键导航功能vue2+vue3(类似 Excel)

功能需求

在 Element UI 的 el-table 表格中实现以下功能:

  • 使用键盘上下左右键在可编辑的 el-input/el-select 之间移动焦点
  • 焦点移动时自动定位到对应单元格
  • 支持光标位置自动调整,提升编辑体验
完整解决方案(vue2)
1. 表格结构修改

在 el-table 中添加键盘事件监听,并为可编辑元素添加定位标识:

VUE 复制代码
<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 中添加焦点导航控制方法:

js 复制代码
<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 : rowIndex
          nextColumnIndex = columnIndex === 0 ? this.columns.length - 1 : columnIndex - 1
          break;
        case 39:
          nextRowIndex = columnIndex === this.columns.length - 1 ? rowIndex + 1 : rowIndex
          break;
      }

      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)
vue 复制代码
 <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 : rowIndex
      nextColumnIndex = columnIndex === 0 ? columns.length - 1 : columnIndex - 1
      break;
    case 39:
      nextRowIndex = columnIndex === columns.length - 1 ? rowIndex + 1 : rowIndex
      break;
  }
 
  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>
相关推荐
OEC小胖胖3 小时前
去中心化身份:2025年Web3身份验证系统开发实践
前端·web3·去中心化·区块链
vvilkim4 小时前
Electron 进程间通信(IPC)深度优化指南
前端·javascript·electron
ai小鬼头6 小时前
百度秒搭发布:无代码编程如何让普通人轻松打造AI应用?
前端·后端·github
漂流瓶jz6 小时前
清除浮动/避开margin折叠:前端CSS中BFC的特点与限制
前端·css·面试
前端 贾公子6 小时前
在移动端使用 Tailwind CSS (uniapp)
前端·uni-app
散步去海边6 小时前
Cursor 进阶使用教程
前端·ai编程·cursor
清幽竹客6 小时前
vue-30(理解 Nuxt.js 目录结构)
前端·javascript·vue.js
知性的小mahua6 小时前
echarts+vue实现中国地图板块渲染+省市区跳转渲染
vue.js
weiweiweb8886 小时前
cesium加载Draco几何压缩数据
前端·javascript·vue.js
幼儿园技术家6 小时前
微信小店与微信小程序简单集成指南
前端