实现目标:在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()
}
}
// ------------------------------上下左右功能键 功能(结束)------------------------------