ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

el-table实现Excel式方向键移动单元格焦点

el-table实现Excel式方向键移动单元格焦点 做过表格类后台管理系统的人应该都遇到过这种需求客户拿着Excel的习惯来用网页非要你用键盘方向键在表格里挨个移动光标跟操作Excel一样录入数据。我第一次接到这个需求的时候第一反应是Element UI有没有现成属性查了一圈发现el-table根本没有提供键盘导航的能力网上方案也很零散。后来我在实际项目里踩了不少坑总算是把方向键移动光标这件事彻底搞明白了这篇就详细把实现思路和完整代码梳理出来。这个功能说白了就是用el-table渲染数据表格然后通过监听键盘的上下左右键让当前选中的单元格焦点跟随方向键移动从而实现类似Excel的键盘导航录入。适合用在库存盘点、单据录入、数据批量核对等需要高频连续输入的场景。我这里讲的是基于Vue 2 Element UI的实现方式Vue 3的写法在事件绑定和DOM获取上略有差异但核心思路完全一样。1. 需求场景与方案设计思路1.1 为什么会有这种操作需求不少业务系统表面上是个网页实际使用场景却完全对标Excel。比如仓库盘点场景操作员拿着扫码枪或者直接在键盘上敲数字一个格子一个格子地录入数量如果每录完一个数还要用鼠标点下一个格子一天下来工作量翻倍。这个时候方向键移动焦点就是刚需录入效率能提升一大截。还有做数据比对的场景比如财务核对多列账目需要通过上下键快速在同一列逐行扫描左右键在不同列之间切换。再比如简单的单元格批量填充先用上下键移动到目标行再输入内容回车确认。这些操作习惯都是从Excel时代带过来的你做B端产品迟早会碰到这类诉求。1.2 方案选型为什么最终选原生DOM方案面对这个需求我见过有人尝试用第三方库比如handsontable、x-spreadsheet之类的Excel表格组件功能确实强大但代价是要替换掉整个表格组件成本很高项目里其他地方都在用el-table不可能为了一个键盘导航推翻重来。也有人尝试在el-table的cell上逐个绑定keydown事件这样做的问题在于el-table的单元格数量一多事件绑定数量就非常庞大性能上先撑不住。而且单元格是动态渲染的新增数据行、排序、筛选后事件绑定很容易丢失维护起来很痛苦。我最终选择的是在el-table外层容器监听keydown事件通过事件冒泡捕获所有单元格上的键盘操作再结合DOM查询定位目标单元格。这个方案有几个明显优势只绑定一个事件监听器不依赖单元格数量不侵入el-table内部机制只操作渲染后的DOM结构数据和视图仍然由el-table管理我们只负责键盘导航这一层交互逻辑。实际上Element UI本身很多交互也是这么处理的组件内部大量使用事件委托。1.3 整体实现思路拆解核心思路可以拆成四步第一步在表格外层容器监听keydown事件判断按下的键是否是方向键第二步定位当前活跃的单元格这个“活跃”状态我用自定义的class和tabindex来维护第三步根据按下的方向键计算出目标单元格的下标第四步更新活跃状态并且如果目标单元格在可视区域之外需要触发滚动跟随。这里面最麻烦的是第三步的计算逻辑。el-table渲染出来的DOM结构是常规的table布局每一行是一个tr每个单元格是tdtd上有colspan和rowspan属性会影响行列布局。普通场景直接按下标走就行一旦涉及合并单元格坐标映射就变得复杂了。这一块我放到后面专门讲。2. 核心实现细节与重难点拆解2.1 键盘事件监听与拦截事件监听的代码不复杂关键在于挂载的位置和触发的时机。我是在el-table组件上加上ref然后在mounted钩子里用原生addEventListener监听keydown事件。mounted() { this.tableRef this.$refs.multipleTable this.tableRef.addEventListener(keydown, this.handleKeydown) }, beforeDestroy() { this.tableRef.removeEventListener(keydown, this.handleKeydown) }这里有一个容易踩的坑el-table渲染出来的DOM结构在外面还包了一层div.el-table真实的表格主体在内部的.el-table__body-wrapper里。如果监听器挂在外层div上事件冒泡能够正常收到但如果直接挂在body-wrapper上el-table内部调整布局或者重新渲染时可能会导致监听失效。所以我一般挂在整个el-table最外层也就是我们模板里写的那个带ref的div上。事件处理函数里第一件事就是判断按键是否被需要。方向键是ArrowUp、ArrowDown、ArrowLeft、ArrowRight注意这里不能用keyCode了虽然不少老代码还在用39这种数字但keyCode已经被标记为废弃新项目建议用event.key。handleKeydown(event) { const keyMap { ArrowUp: this.moveUp, ArrowDown: this.moveDown, ArrowLeft: this.moveLeft, ArrowRight: this.moveRight } if (!keyMap[event.key]) return event.preventDefault() keyMap[event.key].call(this) }调用preventDefault有两个作用一是阻止按上下键时页面滚动二是阻止左右键在某些浏览器里触发表格单元格的默认行为。如果不加这一步上下方向键一按整个页面跟着滚体验非常糟糕。2.2 当前活跃单元格的定位要实现“移动光标”前提是知道光标当前在哪个单元格上。这里需要维护一个当前活跃单元格的状态。我的做法是给所有数据行的td设置tabindex-1同时维护一个activeIndex记录当前活跃单元格在td集合中的下标。每次移动时把旧的activeIndex对应的td移除高亮class把新的activeIndex对应的td添加高亮class并调用focus方法。但这里有一个问题el-table的单元格是不带tabindex的直接调用focus()不会有任何效果。所以需要先给td加上tabindex属性。我用了el-table的cell-class-name回调这个回调可以为每个单元格追加类名但它不能直接设置tabindex属性。所以我改用了一个更直接的办法在mounted和updated钩子里遍历所有td批量设置tabindex。setTabIndex() { const tds this.tableRef.querySelectorAll(.el-table__body-wrapper tbody td) tds.forEach((td, index) { td.setAttribute(tabindex, index this.activeIndex ? 0 : -1) }) }这样做的好处是操作非常直接遍历td集合把当前活跃的td的tabindex设为0其余设为-1。用户按Tab键时焦点会从最后一个操作的单元格继续走而不是从头开始。坏处是每次数据更新后都要重新执行一次所以我把它绑定到updated生命周期里。不过这里也要注意td在el-table中可能是表头th、数据td甚至固定列时会有两套表格DOM。如果用了fixed属性固定列el-table会渲染多个table每个都是完整结构。我在遍历时用.el-table__body-wrapper tbody td来限定选择器只取非固定列的body区域数据单元格避免一个单元格被匹配多次。2.3 方向键移动计算逻辑移动逻辑的核心是当前活跃单元格在td集合中的下标以及它所在的tr行索引和td在行内的列索引。计算目标下标之前首先要能拿到这两个索引。我的实现是这样获取当前活跃的td元素通过parentNode拿到tr再用Array.prototype.indexOf找到当前td在这一行的位置。行的总数通过tableRef.querySelectorAll(.el-table__body-wrapper tbody tr)得到。getCurrentPosition() { const currentTd this.tableRef.querySelector(.el-table__body-wrapper td.active-cell) if (!currentTd) return { rowIndex: 0, colIndex: 0 } const tr currentTd.parentNode const trList Array.from(this.tableRef.querySelectorAll(.el-table__body-wrapper tbody tr)) const rowIndex trList.indexOf(tr) const colIndex Array.from(tr.children).indexOf(currentTd) return { rowIndex, colIndex } }拿到当前坐标之后上下左右移动就变成了简单的加减法。按下ArrowDown时目标行1列不变ArrowUp是行-1ArrowLeft是列-1ArrowRight是列1。然后需要检查目标坐标是否越界行超出总行数或者列超出当前行的列数都算越界这时直接返回不做任何操作。边界处理这里有一个细节每一行的td数量可能不一样特别是前面几行有合并单元格或者操作列的情况下。右移的时候如果当前行的列数比目标行的列数多按右移移动到目标行之后可能出现列下标越界。我在移动后对目标列做了修正如果目标列大于目标行的td数量减1就取目标行最后一个td。2.4 合并单元格场景的处理方案普通表格用上面的逻辑就够用了但一旦碰到合并单元格事情就变复杂了。el-table的合并单元格是通过table的span-method方法实现的底层渲染会把td设置上colspan或rowspan。比如一个td设置了colspan2这一行里实际的td数量就会减少因为一个td占了两个列的位置。这种情况下直接通过td的DOM下标去计算列位置会出错。比如第一行有4个td第二行某几个td合并后只有3个td此时第一行第2个td的“列位置”和第二行第2个td的“列位置”实际上不对应。我的处理方式是通过累加colspan来计算每个td的逻辑列位置。对于目标td数组做一个位置映射每个td的起始列索引等于前面所有兄弟节点的colspan之和。移动时先得到当前td在行内的逻辑列位置然后在目标行中找到包含该逻辑列的td。getLogicalColIndex(td) { let index 0 let sibling td while (sibling.previousElementSibling) { index sibling.previousElementSibling.colSpan || 1 sibling sibling.previousElementSibling } return index }getTdByLogicalCol(tr, logicalCol) { const tds Array.from(tr.children) let start 0 for (let i 0; i tds.length; i) { const end start (tds[i].colSpan || 1) - 1 if (logicalCol start logicalCol end) { return tds[i] } start end 1 } return tr.lastElementChild }对于rowspan的处理更麻烦一些因为合并行会让上下移动时列位置变化。我的策略是简单处理如果目标单元格是一个rowspan跨行的单元格那么上下移动时仍然定位到这个单元格只是视觉上看起来光标在同一位置。如果要实现跨过合并单元格移动到下一行真正对应的单元格需要额外记录每个行列的映射表复杂度会高很多我目前这个方案在大多数业务场景下已经够用了。 ### 2.5 跨行移动与滚动跟随 方向键移动时还有一个体验问题如果目标行超过了当前可视区域焦点虽然移动到了目标单元格但用户看不到。这种情况下需要调用scrollIntoView。但scrollIntoView有个坑它会滚动整个页面如果表格外层还有滚动容器可能导致页面整体跳动体验很怪。 更可控的做法是操作el-table内部的body-wrapper滚动容器 javascript scrollToCell(td) { const bodyWrapper this.tableRef.querySelector(.el-table__body-wrapper) const tr td.parentNode const trTop tr.offsetTop const wrapperTop bodyWrapper.scrollTop const wrapperHeight bodyWrapper.clientHeight const trHeight tr.offsetHeight if (trTop wrapperTop) { bodyWrapper.scrollTop trTop } else if (trTop trHeight wrapperTop wrapperHeight) { bodyWrapper.scrollTop trTop trHeight - wrapperHeight } }这段代码的逻辑是先判断目标行是否在当前滚动区域的上面或下面如果在上面就滚动到目标行顶端如果在下面就算出需要滚动的距离。实测下来滚动非常平滑也不会引起页面其他区域跳动。这里顺带提一句el-table的滚动条占位问题一般出现在设置了固定列的场景如果发现滚动时最右列被遮住可以用官方文档里的scrollbar-always-on属性或者手动给body-wrapper加padding-right来处理和方向键功能本身关系不大。3. 完整代码实现与实操过程3.1 先搭一个最小可用版本为了不把问题复杂化我先把最基础的带方向键移动的表格代码写出来。这个版本不处理合并单元格也不做跨行滚动跟随只覆盖核心的按键移动逻辑。template div reftableWrapper classtable-wrapper tabindex0 keydownhandleKeydown el-table refmultipleTable :datatableData border highlight-current-row el-table-column propdate label日期 / el-table-column propname label姓名 / el-table-column propaddress label地址 / /el-table /div /template script export default { data() { return { activeIndex: 0, tableData: [ { date: 2024-01-01, name: 张三, address: 北京市朝阳区 }, { date: 2024-01-02, name: 李四, address: 上海市浦东新区 }, { date: 2024-01-03, name: 王五, address: 广州市天河区 } ] } }, mounted() { this.setActiveCell() }, updated() { this.setActiveCell() }, methods: { handleKeydown(event) { const keyMap { ArrowUp: this.moveUp, ArrowDown: this.moveDown, ArrowLeft: this.moveLeft, ArrowRight: this.moveRight } if (!keyMap[event.key]) return event.preventDefault() keyMap[event.key].call(this) }, getTdList() { return this.$refs.multipleTable.$el.querySelectorAll(.el-table__body-wrapper tbody td) }, setActiveCell() { const tds this.getTdList() tds.forEach((td, index) { td.setAttribute(tabindex, index this.activeIndex ? 0 : -1) if (index this.activeIndex) { td.classList.add(active-cell) td.focus() } else { td.classList.remove(active-cell) } }) }, moveUp() { const trList this.$refs.multipleTable.$el.querySelectorAll(.el-table__body-wrapper tbody tr) const currentTr this.getCurrentTr() const currentTd this.getCurrentTd() const rowIndex Array.from(trList).indexOf(currentTr) if (rowIndex 0) return const targetTr trList[rowIndex - 1] const colIndex Array.from(currentTr.children).indexOf(currentTd) const targetTd targetTr.children[colIndex] || targetTr.lastElementChild this.moveToTd(targetTd) }, moveDown() { // 与moveUp对称目标行1 }, moveLeft() { const currentTd this.getCurrentTd() if (currentTd.previousElementSibling) { this.moveToTd(currentTd.previousElementSibling) } }, moveRight() { const currentTd this.getCurrentTd() if (currentTd.nextElementSibling) { this.moveToTd(currentTd.nextElementSibling) } }, getCurrentTd() { return this.$refs.multipleTable.$el.querySelector(.el-table__body-wrapper td.active-cell) }, getCurrentTr() { const td this.getCurrentTd() return td ? td.parentNode : this.$refs.multipleTable.$el.querySelector(.el-table__body-wrapper tbody tr) }, moveToTd(td) { const tds this.getTdList() const index Array.from(tds).indexOf(td) if (index ! -1) { this.activeIndex index this.setActiveCell() } } } } /script style .active-cell { background-color: #e6f7ff !important; outline: 2px solid #409eff; outline-offset: -2px; } /style这段代码中el-table的单元格被手动加上tabindex后我能用td.focus()把真实焦点移动到目标单元格上。focus后浏览器会把焦点元素滚动到可视区域但问题在于el-table内部有多层滚动容器这个默认滚动行为不太可控。所以我干脆在focus之前先判断一下目标元素是否在可视区内不在就手动滚动body-wrapper。3.2 封装成自定义指令提高复用性基础版本能跑通后我把这个逻辑封装成了自定义指令v-table-nav这样项目里任何需要键盘导航的el-table只要在标签上加上指令就行不需要每个页面都复制一遍逻辑。// directives/table-nav.js import Vue from vue Vue.directive(table-nav, { bind(el, binding) { const options binding.value || {} el.__tableNavHandler function(event) { const keyMap { ArrowUp: moveUp, ArrowDown: moveDown, ArrowLeft: moveLeft, ArrowRight: moveRight } if (!keyMap[event.key]) return // 如果正在编辑单元格内的输入框不触发导航 if (event.target.tagName INPUT || event.target.tagName TEXTAREA) return event.preventDefault() keyMap[event.key].call(this) } el.addEventListener(keydown, el.__tableNavHandler) }, unbind(el) { el.removeEventListener(keydown, el.__tableNavHandler) } })这里我加了一个很关键的判断如果事件源是input或者textarea就不触发表格导航。因为在表格编辑场景里按下方向键通常是想在输入框内部移动光标而不是跳到下一个单元格。如果不加这个判断你会发现输入框里根本没法用左右键移动文字光标按一下就直接跳到下一个格子了体验非常诡异。还有一个常见问题如果单元格内部有下拉选择器或者日期选择器点击后弹出浮层方向键会在浮层和表格之间产生冲突。稳妥的做法是只监听表格body区域的事件浮层的DOM节点通常挂在body下不会冒泡到表格外层所以这个问题在一般情况下不会出现。3.3 焦点样式的处理与::before失效问题光标移动的核心是视觉反馈。默认情况下td的focus样式非常不明显尤其是el-table已经使用了自带的边框和hover高亮很容易和焦点状态混淆。我给活跃单元格写了一套鲜明的样式蓝色背景加外描边。但这里也遇到过样式不生效的情况。很多人在给el-table写样式时发现通过.el-table::before修改边框样式没反应。这是因为el-table的::before是用来绘制表格底部那条横线的它真实存在于.el-table根元素上并且有固定的层级关系。如果你给.el-table设置overflow:hidden::before可能被裁掉。如果你用了自定义主题覆盖了边框颜色::before的背景色也需要一起改。.el-table::before { background-color: #ebeef5; /* 默认边框颜色 */ } .el-table--border::after { background-color: #ebeef5; }回到焦点样式我给活跃单元格加的是outline而不是border因为outline不参与盒模型计算不会导致单元格尺寸变化引发表格抖动。如果你用border做焦点边框表格会轻微跳动非常明显。outline-offset设为-2px可以确保描边在单元格内部看起来更像选中状态。还有一个小细节td.focus()之后Chrome会默认给元素添加一个focus-visible的样式有时会和我们的.active-cell样式冲突造成闪烁。解决办法是在样式里明确覆盖td:focus-visible的默认outline。.el-table__body tbody td:focus { outline: none; } .el-table__body tbody td.active-cell { background-color: #d9ecff !important; outline: 2px solid #409eff; outline-offset: -2px; }td在获得焦点后浏览器可能会把焦点滚动到可视区域如果表格内外有多个滚动容器滚动的表现会比较混乱。所以我在focus之前先手动调用scrollToCell确保目标单元格进入body-wrapper可视范围再执行focus这样能控制滚动行为不依赖浏览器默认逻辑。4. 常见问题与排查技巧实录4.1 问题速查表我把实际开发中遇到的典型问题和解决方案整理成一个表格方便排查。现象原因解决办法按上下键页面滚动单元格不移动keydown监听未生效或者preventDefault没执行确认监听器绑定在el-table外层DOM确认event.key值匹配方向键光标能移动但单元格没有高亮样式没有覆盖默认背景或者td没有正确添加active-cell类检查.active-cell样式优先级用!important提升覆盖能力点击输入框后按方向键光标跳到了别的单元格事件没有区分输入框场景在handleKeydown里判断event.target是否为input/textarea表格滚动时活跃单元格不见没有做滚动跟随实现scrollToCell逻辑目标行超出可视范围时手动滚动body-wrapper有固定列时分身单元格混乱固定列渲染了多套DOM选择器匹配到多个td限定选择器只用非固定列的body区域按方向键输入框内的光标也乱跳单元格内嵌input时默认行为被拦截在事件处理里排除INPUT/TEXTAREA/SELECT合并单元格后坐标错乱直接用td下标没有考虑colspan/rowspan使用逻辑列索引方式计算目标位置焦点样式设置了但看不见目标td没有真正获得焦点outline被浏览器默认样式覆盖检查tabindex设置和focus调用覆盖td:focus的outline4.2 排查思路实录一次实际排查中用户反馈“方向键上下移动是好用的但左右键在最后一列按了会导致表格出现横向空白”。我一开始以为是计算逻辑的问题后来通过控制台检查发现我的moveLeft和moveRight逻辑没问题但el-table本身在某一列设置了min-width后最后一列右侧有留白区域这个区域不在任何td上所以按键没有反应看起来像光标消失了。这个其实不是代码问题而是表格列宽分配导致的视觉现象处理方式是把最后一列改为直接设置width不设min-width或者给表格加border让留白有边框看起来更协调。还有一次遇到的是“上下键移动后当前行高亮和单元格高亮错位”的问题。排查后发现是el-table的highlight-current-row属性和自定义的active-cell叠加导致的。highlight-current-row会给当前行加背景色且这个逻辑有自己的currentRow状态和键盘导航的activeIndex不同步。解决方法是去掉highlight-current-row属性统一由键盘逻辑来维护高亮状态。如果你需要保留这个属性那就要在键盘移动时同步调用el-table的setCurrentRow方法让两种状态保持一致。4.3 方向键与其他编辑操作的冲突这个功能上线后我收到过“单元格里输入数字后按上下键数字被覆盖”的反馈。这也是一个经典冲突在可编辑表格中单元格内部嵌入了input用户输入数字后按上下方向键我们的keydown拦截逻辑把事件拦截了但此时焦点还在input里preventDefault会阻止输入框内的光标移动同时我又触发单元格跳转看起来就像“输入内容被覆盖”了。解决办法是判断事件来源。当事件目标是input且该input是可编辑状态时不触发单元格跳转。只有焦点在td本身才触发方向键导航。这也是我在封装指令时专门加INPUT校验的原因。还有一种情况如果表格里内嵌了Select组件Select展开后选项列表是挂在body上的按上下键本来是选择选项但由于事件冒泡到table-wrapper导致同时百分比移动单元格焦点。处理方式是在Select组件上添加keydown.stop阻断事件冒泡。el-select keydown.stop el-option label选项一 value1 / el-option label选项二 value2 / /el-select4.4 与浏览器默认快捷键的配合方向键在浏览器里有默认行为上下键滚动页面、左右键在部分控件中移动焦点。我们在表格区域已经把默认行为拦截了但如果用户意外按到组合键比如Ctrl方向键这个组合键通常用于在Mac上切换桌面或者Ctrl左右在文本中按词移动这种组合操作不应该被我们的表格导航吞掉。所以在handleKeydown里我需要额外判断如果event.ctrlKey、event.metaKey、event.altKey任一为true直接return不做任何处理。这样可以把组合键行为留给浏览器和用户系统。if (event.ctrlKey || event.metaKey || event.altKey) return还有一个需要处理的细节是Tab键。Tab键在表格中默认会离开当前焦点跳到下一个可聚焦元素。如果你的表格是纯查看场景Tab键跳转没问题但如果你希望Tab和回车也能像Excel一样右移/下移就需要扩展keyMap把Tab和Enter也加进去。这个我通常只在客户明确要求时才加因为Tab键跳过表格会破坏页面的无障碍访问逻辑不能想当然地全部拦截。5. 实测体验与后续扩展建议5.1 几轮实测下来的手感调优功能做完不是终点手感是关键。第一次跑通时光标移动虽然功能正常但体验很生硬移动速度完全靠按键连击没有按住方向键连续推进的效果。浏览器本身对keydown事件是有重复触发的按住方向键会自动持续触发所以这个不用额外做。真正需要调的是首次触发延迟默认浏览器会有几百毫秒的延迟才开始重复这个延迟让快速连续录入时感觉响应很慢。我一开始试图用定时器自己控制移动频率后来发现效果反而不好因为浏览器原生的事件频率已经足够用人为加定时器反而可能丢失输入节奏。最终我选择保留原生keydown的重复触发机制只在handleKeydown里做了简化不做额外限流。另外光标移动的动画是否要加我试过给.active-cell加transition但td的outline和background变化在低性能设备上会有明显闪烁后来放弃了动画效果用瞬时切换。这个操作场景下用户要的是“按一下就到位”不是看平滑过渡动画感反而拖慢效率。5.2 结合粘贴板快速录入的场景扩展方向键移动功能在实际项目中经常和批量粘贴一起用。比如操作员从Excel复制一列数据在网页表格中先选中起始单元格按下快捷键程序自动把剪贴板数据按行填充到表格下面的单元格里。这个功能可以和方向键导航无缝配合每填完一个单元格自动把光标移动到下一行继续等待粘贴或者输入。实现思路是监听paste事件拿到剪贴板文本后按换行符和制表符拆分成二维数组从当前活跃单元格开始按行列填充同时推进activeIndex。由于el-table的数据是受控的直接修改tableData数组对应字段即可不需要操作DOM数据更新后el-table会自动重绘。这里要留意的是el-table的单元格列顺序必须和剪贴板的列顺序对应如果表格有操作列、勾选列等非数据列需要先做列索引映射否则数据会错位。我通常用一个columnMap数组记录数据列在表格中的真实列索引粘贴时按这个映射写入对应字段。5.3 移动端与触屏设备的降级方案方向键导航明显是桌面端功能移动端没有键盘这个功能自动失效不需要做额外处理。但要注意的是在iPad外接键盘的场景下部分浏览器会对方向键默认滚动页面而el-table的滚动容器是多层嵌套的preventDefault未必能完全阻止所有滚动传播。实测时我遇到过iOS Safari上阻止不彻底的情况解决方法是给table-wrapper加上touch-action: none但这样会影响触屏滚动所以我又限制只在检测到键盘事件时才启用这个样式。还有一个容易忽视的细节如果页面里有多个el-table同时挂载了相同的键盘导航指令按方向键时所有表格都会响应造成焦点乱跳。这个问题在使用自定义指令时尤其容易出现因为指令是绑定在DOM上的只要DOM存在监听器就生效。如果确实需要在同一页面使用多个可键盘导航的表格我建议把指令改成接受一个active参数只有指定表格才响应导航或者在指令里加一个全局的焦点状态管理同一时间只允许一个表格响应方向键。做完这个功能我个人最大的体会是在el-table这种高度封装的组件上做交互增强尽量不要和组件内部逻辑硬碰硬而是利用渲染后的DOM结构和事件委托来搭一层自己的交互层。这样既能避开组件升级导致API变化的风险又能保持核心功能的高度可控。方向键移动光标只是一个起点在这套事件监听与坐标映射的框架上你还能很自然地扩展出回车跳格、双击编辑、批量填充、剪贴板导入这些能力对数据密集型的管理系统来说这一层交互优化带来的效率提升是肉眼可见的。
返回列表