element+vue3 table上下左右键切换input和select

实现目标:在table里不需要鼠标,仅依靠键盘即可完成所有数据输入

html 复制代码
<el-table :data="tableData" border>
  <el-table-column prop="productName" label="远程请求select" min-width="300">
    <template #default="{ row, $index }">
      <div class="flex-s">
        <el-select
          :ref="(el) => setAllKeyDownRef(el, $index, 'productNameTarget')"
          @keydown="(e) => handleKeyDown(e, $index, 'productNameTarget')"
          v-model="row.productName"
          filterable
          remote
          clearable
          @focus="tableInputFocus2('materialNameSearch', row.productName, $index)"
          @change="tableInputChange2('materialNameSearch', row.productName, $index)"
          placeholder="请输入关键词搜索"
          :remote-method="remoteObjectMethod2.bind(extraParam, row.productName, 'materialNameSearch', $index )"
          :loading="loading">
          <el-option
              v-for="item in row.materialNameArray || []"
              :key="item.skuName"
              :label="item.skuName"
              :value="item.skuName"
          />
        </el-select>
      </div>
    </template>
  </el-table-column>
  <el-table-column label="input1" min-width="120">
    <template #default="{ row, $index }">
      <div class="flex-s">
        <el-input
          :ref="(el) => setAllKeyDownRef(el, $index, 'input1Target')"
          @keydown="(e) => handleKeyDown(e, $index, 'input1Target')"
          v-model="row.input1"
        />
      </div>
    </template>
  </el-table-column>
  <el-table-column label="select1" min-width="120">
    <template #default="{ row, $index }">
      <div>
        <el-select
          :ref="(el) => setAllKeyDownRef(el, $index, 'select1Target')"
          @keydown="(e) => handleKeyDown(e, $index, 'select1Target')"
          v-model="row.select1" 
        >
          <el-option label="itemUnit" value="itemUnit" />
        </el-select>
      </div>
    </template>
  </el-table-column>
  <el-table-column label="input2" min-width="120">
    <template #default="{ row, $index }">
      <div class="flex-s">
        <el-input
          :ref="(el) => setAllKeyDownRef(el, $index, 'input2Target')"
          @keydown="(e) => handleKeyDown(e, $index, 'input2Target')"
          v-model="row.input2"
        />
      </div>
    </template>
  </el-table-column>
  <el-table-column label="select2" min-width="120">
    <template #default="{ row, $index }">
      <div>
        <el-select
          :ref="(el) => setAllKeyDownRef(el, $index, 'select2Target')"
          @keydown="(e) => handleKeyDown(e, $index, 'select2Target')"
          v-model="row.select2" 
        >
          <el-option label="itemUnit" value="itemUnit" />
        </el-select>
      </div>
    </template>
  </el-table-column>
</el-table>

js代码

javascript 复制代码
// ------------------------------上下左右功能键 功能(开始)------------------------------


// 组件引用存储
const inputRefs = reactive(new Map());

// 存储所有select组件的输入元素
const selectInputs = new Set();

// 标记当前是否有可见的下拉菜单
let hasVisibleDropdown = false;
let keyCodeOpen13 = false;  //检测是否按下了13键

// 设置所有需要键盘导航的组件引用
const setAllKeyDownRef = (el, index, type) => {
  if (el) {
    const key = `${index}-${type}`
    inputRefs.set(key, el)
    
    // 判断是否为select类型组件
    const isSelectType = type === 'productName' || type === 'select1Target' || type === 'select2Target'
    
    // 查找input元素
    let inputElement
    if (el.$el) {
      if (isSelectType && el.$el.querySelector('.el-select__input')) {
        inputElement = el.$el.querySelector('.el-select__input')
        // 存储select组件的输入元素
        if (inputElement) {
          selectInputs.add(inputElement)
          inputElement._keyboardNavInfo = { index, type }
        }
      } else {
        inputElement = el.$el.tagName === 'INPUT' ? el.$el : el.$el.querySelector('input')
      }
      
      // 为非select组件或select组件添加键盘事件监听
      if (inputElement && !inputElement._keyboardNavListener) {
        const keyboardHandler = (e) => {
          // // 处理左右箭头键 - 始终执行自定义导航
          // if (e.keyCode === 37 || e.keyCode === 39) {
          //   e.preventDefault()
          //   handleKeyDown(e, index, type)
          // }else 
          // 处理上下箭头键 - 根据下拉菜单状态决定行为

          if((type === 'productName') && (e.keyCode != 37 && e.keyCode != 39 && e.keyCode != 40 && e.keyCode != 38)){
            hasVisibleDropdown = true
              keyCodeOpen13 = true
          }
        console.log(type,hasVisibleDropdown,'设置所有需要键盘导航的组件引用', e.keyCode)


          if(e.keyCode === 13){
            if(keyCodeOpen13){
              hasVisibleDropdown = false
              keyCodeOpen13 = false
            }else{
              hasVisibleDropdown = true
              keyCodeOpen13 = true
            }
          }
          if ((e.keyCode === 38 || e.keyCode === 40) && isSelectType) {
            if (!hasVisibleDropdown) {
              // 检查是否有可见的下拉菜单(无下拉情况触发)
              e.preventDefault()
              handleKeyDown(e, index, type)
            }else{
              // 如果有可见的下拉菜单,不做任何处理,让原生行为执行
            }
          }
        }
        
        inputElement.addEventListener('keydown', keyboardHandler, false) // 使用冒泡阶段
        inputElement._keyboardNavListener = keyboardHandler
        inputElement._keyboardNavInfo = { index, type }
      }
    }
  } else {
    // 清理引用和事件监听
    const key = `${index}-${type}`
    const component = inputRefs.get(key)
    
    if (component && component.$el) {
      let inputElement
      if (component.$el.querySelector('.el-select__input')) {
        inputElement = component.$el.querySelector('.el-select__input')
        if (inputElement) {
          selectInputs.delete(inputElement)
        }
      } else {
        inputElement = component.$el.tagName === 'INPUT' ? component.$el : component.$el.querySelector('input')
      }
      
      if (inputElement && inputElement._keyboardNavListener) {
        inputElement.removeEventListener('keydown', inputElement._keyboardNavListener, false)
        delete inputElement._keyboardNavListener
        delete inputElement._keyboardNavInfo
      }
    }
    
    inputRefs.delete(key)
  }
}




// 处理键盘导航
const handleKeyDown = (e, index, currentType) => {
  if(e.keyCode != 37 && e.keyCode != 39 && e.keyCode != 40 && e.keyCode != 38){
    return
  }
  console.log('handleKeyDown111', e, index, currentType)
  hasVisibleDropdown = false
  keyCodeOpen13 = false
  // 获取当前数据列表长度
  const dataLength = priceAdjustmentInfo.value?.skus.length || 0
  
  // 上箭头 - 移动到上一行同一组件
  if (e.keyCode === 38 && index > 0) {
    focusComponent(index - 1, currentType)
  }
  
  // 下箭头 - 移动到下一行同一组件
  if (e.keyCode === 40 && index < dataLength - 1) {
    focusComponent(index + 1, currentType)
  }
  
  // 检查光标是否在输入内容的最左端
  const isCursorAtStart = (inputElement) => {
    if (!inputElement || !inputElement.value) return true; // 空输入也认为在最左端
    return inputElement.selectionStart === 0 && inputElement.selectionEnd === 0;
  };
  // 左箭头 - 移动到左侧组件
  const isInputType = currentType === 'input1Target' || currentType === 'input2Target';
  if (e.keyCode === 37) {
    // 对于输入框类型的组件,只有当光标在最左端时才执行导航
    const targetElement = e.target;
    if (isInputType && !isCursorAtStart(targetElement)) {
      return; // 光标不在最左端,不执行导航,保留默认行为
    }
    if(currentType == 'productNameTarget'){
      // 从productNameTarget到input1Target
      focusComponent(index, 'input1Target')
    }else if(currentType == 'input1Target'){
      // 从input1Target到input2Target
      focusComponent(index, 'select1Target')
    }else if(currentType == 'select1Target'){
      // 从input2Target到 ...
      focusComponent(index, 'input2Target')
    }
    // ...

  }
  
  // 检查光标是否在输入内容的最右端
  const isCursorAtEnd = (inputElement) => {
    if (!inputElement || !inputElement.value) return true; // 空输入也认为在最右端
    return inputElement.selectionStart === inputElement.value.length &&
           inputElement.selectionEnd === inputElement.value.length;
  };


  // 右箭头 - 移动到右侧组件
  if (e.keyCode === 39) {
    // 对于输入框类型的组件,只有当光标在最右端时才执行导航
    const targetElement = e.target;
    if (isInputType && !isCursorAtEnd(targetElement)) {
      return; // 光标不在最右端,不执行导航,保留默认行为
    }


    if(currentType == 'select2Target'){ 
      // 从select2Target到input2Target
      focusComponent(index, 'input2Target')
    }else if(currentType == 'input2Target'){ 
      // 从input2Target到select1Target
      focusComponent(index, 'select1Target')
    }else if(currentType == 'select1Target'){
      // 从select1Target到 ...
      focusComponent(index, 'input1Target')
    }
    // ...
  }
}

// 聚焦到指定组件
const focusComponent = (index, type) => {
  
  // 再尝试查找输入框
  const inputKey = `${index}-${type}`
  const inputEl = inputRefs.get(inputKey)
  
  if (inputEl) {
    inputEl.focus()
  }
}
// ------------------------------上下左右功能键 功能(结束)------------------------------
相关推荐
西西学代码4 小时前
Flutter---CupertinoPicker滚动选择器
1024程序员节
colus_SEU4 小时前
【计算机网络笔记】第一章 计算机网络导论
笔记·计算机网络·1024程序员节
会联营的陆逊4 小时前
JavaScript 如何优雅的实现一个时间处理插件
javascript
over6974 小时前
浏览器里的AI魔法:用JavaScript玩转自然语言处理
前端·javascript
请你喝好果汁6414 小时前
ArchR——TSS_by_Unique_Frags.pdf
1024程序员节
Amy_cx4 小时前
搭建React Native开发环境
javascript·react native·react.js
代码AI弗森4 小时前
Python × NumPy」 vs 「JavaScript × TensorFlow.js」生态全景图
javascript·python·numpy
安当加密4 小时前
如何通过掌纹识别实现Windows工作站安全登录:从技术原理到企业级落地实践
windows·安全·1024程序员节
m0_564264184 小时前
SEO优化策略:从入门到精通的排名提升指南
搜索引擎·1024程序员节·seo策略