实现 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>
相关推荐
Setsuna_F_Seiei9 小时前
前端转型 Agent 开发 06 之 Agent Memory 记忆系统(让 Agent 更智能,更懂你)
前端·agent·ai编程
志尊宝9 小时前
Vue3 零基础每日笔记(073):keep-alive 页面缓存实战——列表页返回不丢状态
vue.js·笔记·缓存·#vue #前端 #前端开发·#javascript·#vue.js
zhangzeyuaaa9 小时前
Ruby 多线程、GVL(GIL)与 Mutex 完全指南
开发语言·前端·ruby
默_笙10 小时前
🌊 向量库和 ES 都查不出"关系",我只好给奶茶建了一张人脉网
前端·javascript
优选资讯10 小时前
标签打印软件怎么对接 Excel 数据
前端·excel
一个风轻云淡11 小时前
GCC 和 GDB命令简单解读
java·linux·前端
u01110267512 小时前
图片处理接口如何统一错误信息 前端提示与后端错误码设计
前端·状态模式
福兮说12 小时前
浏览器里把 iPhone 的 HEIC 转成 JPG:Chrome 解不开、转完大了七成、拍摄时间全丢,六个坑实测
前端·javascript·图像处理·chrome·ios·iphone·heic
libokaifa12 小时前
把语音模型塞进手机:sherpa-onnx 在 Android 上的 ASR / TTS
前端
deli00713 小时前
DLA 枝晶生长实测:5000 个粒子集体停摆之后,分形维数收敛到 1.70
前端