element+vue3 table上下左右键切换input和select
实现目标:在table里不需要鼠标,仅依靠键盘即可完成所有数据输入
<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"filterableremoteclearable@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-optionv-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代码
// ------------------------------上下左右功能键 功能(开始)------------------------------// 组件引用存储
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 inputElementif (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 = truekeyCodeOpen13 = true}console.log(type,hasVisibleDropdown,'设置所有需要键盘导航的组件引用', e.keyCode)if(e.keyCode === 13){if(keyCodeOpen13){hasVisibleDropdown = falsekeyCodeOpen13 = false}else{hasVisibleDropdown = truekeyCodeOpen13 = 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 = keyboardHandlerinputElement._keyboardNavInfo = { index, type }}}} else {// 清理引用和事件监听const key = `${index}-${type}`const component = inputRefs.get(key)if (component && component.$el) {let inputElementif (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._keyboardNavListenerdelete 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 = falsekeyCodeOpen13 = 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到input1TargetfocusComponent(index, 'input1Target')}else if(currentType == 'input1Target'){// 从input1Target到input2TargetfocusComponent(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到input2TargetfocusComponent(index, 'input2Target')}else if(currentType == 'input2Target'){ // 从input2Target到select1TargetfocusComponent(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()}
}
// ------------------------------上下左右功能键 功能(结束)------------------------------
