实现 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>
相关推荐
郑州光合科技余经理10 分钟前
本地生活系统:多业务订单字段怎么分账本导出
java·开发语言·前端·数据库·uni-app·php·ai编程
怕浪猫7 小时前
FDE 如何正确解决现场项目工程性的问题
前端·后端·ai编程
西瓜太郎12347 小时前
外层模型卡片与分组详情成功率不一致,应该怎么排查?
前端·typescript·go·软件工程·react
GreenTea7 小时前
vLLM 与 SGLang KV Cache 底层实现机制深度调研报告
前端·后端·算法
毕业设计70310 小时前
(免费领源码) 基于微信小程序的预制菜商城的设计与实现25172-java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
vue.js·python·mysql·微信小程序·pycharm·微信开发者工具·推荐算法
xifangge202510 小时前
AGENTS.md 怎么写?涵盖 Java、Python、Vue、Go 的 8 套开箱即用模板
java·vue.js·python
几何心凉10 小时前
没有万能模型:我用 AiiOnly Token Plan,把多款大模型的长处拼进同一个项目
前端
kyriewen10 小时前
Claude Code 的额度今天缩水了17%——官方公告上写的是"永久提高25%"
前端·ai编程·claude
雪芽蓝域zzs11 小时前
vue解构平铺VS对象包裹
前端·javascript·vue.js
小葱炖豆腐12 小时前
python绘制excel折线图
python·excel·numpy·pandas·matplotlib