防止出现为什么都是出现Vue3.x了还在用vue2.x言论,先提前打下预防针,一个老的稳定的系统是不会去做跨版本的升级操作,如要做也是重头开始从0开始
环境
- Vue2.x
- JavaScript
- Element2.x
开始
之前接到一个需求,要求使用键盘方向键控制表格中的输入框焦点,接到这个需求我的第一想法,客户就这么懒吗?用鼠标点吧点吧就能解决的事件为什么非要用方向键控制?原来他们老系统上就可以使用方向键控制输入框焦点,他们的老系统使用的是CS架构跑在客户端,我们系统为BS跑在网页端。既然答应的客户那我们还能怎么帮,就去实现呗。
思路
既然要使用移动我们就得监听方向键事件,好在Vue2.x为我们提供了指令
- @keydown.up 键盘上键
- @keydown.down 键盘下键
- @keydown.left 键盘左键
- @keydown.right 键盘右键
由于是在表格里移动来获取输入框焦点,我们需要知道当前是在那个输入框内,需要定义几个参数来标明
- vertical 垂直方向:判断是上移还是下移,移动几行(基本上都是一行一行移动),上移传参【-1】,下移传参【1】
- horizontal 水平方法:判断是左移还是右移,移动几个单元格,左移传参【-1】,右移传参【1】
- index 表格索引:判断是在表格那一行
- columnFile 列名:判断是在哪一列
获取元素的方法
- 使用Ref的方法获取输入框的获取焦点事件focus
编码
开编码前还需要分情况,就是这个表格是静态的还是动态的?为什么考虑这个,如果是静态水平方向移动几个单元格是可以手动直接写死,如果是动态的就需要动态去计算需要移动几个单元格,废话不说了直接开整。
先为每个输入框都绑定事件并取名move(vertical, horizontal, index, column)
- @keydown.up
- @keydown.down
- @keydown.left
- @keydown.right
<el-table :data="tableData" style="width: 100%" ref="tableRef" border>
<el-table-column prop="name" label="姓名" width="180">
<template slot-scope="scope">
<el-input v-model="scope.row.name" placeholder="请输入内容"></el-input>
</template>
</el-table-column>
<el-table-column prop="address" label="地址">
<template slot-scope="scope">
<el-input
@keydown.up.native="move(-1, 0, scope.$index, 'address')"
@keydown.down.native="move(1, 0, scope.$index, 'address')"
@keydown.left.native="move(0, -1, scope.$index, 'address')"
@keydown.right.native="move(0, 2, scope.$index, 'address')"
v-model="scope.row.address"
placeholder="请输入内容"
></el-input>
</template>
</el-table-column>
<el-table-column prop="number" label="数量">
<template slot-scope="scope">
<el-input v-model="scope.row.number" placeholder="请输入内容"></el-input>
</template>
</el-table-column>
<el-table-column prop="email" label="邮箱">
<template slot-scope="scope">
<el-input
@keydown.up.native="move(-1, 0, scope.$index, 'email')"
@keydown.down.native="move(1, 0, scope.$index, 'email')"
@keydown.left.native="move(0, -2, scope.$index, 'email')"
@keydown.right.native="move(0, 1, scope.$index, 'email')"
v-model="scope.row.email"
placeholder="请输入内容"
></el-input>
</template>
</el-table-column>
<el-table-column prop="phoneNumber" label="手机号">
<template slot-scope="scope">
<el-input
@keydown.up.native="move(-1, 0, scope.$index, 'phoneNumber')"
@keydown.down.native="move(1, 0, scope.$index, 'phoneNumber')"
@keydown.left.native="move(0, -1, scope.$index, 'phoneNumber')"
@keydown.right.native="move(0, 0, scope.$index, 'phoneNumber')"
v-model="scope.row.phoneNumber"
placeholder="请输入内容"
></el-input>
</template>
</el-table-column>
<el-table-column prop="phoneNumber" label="性别">
<template slot-scope="scope">
<el-input v-model="scope.row.sex" placeholder="请输入内容"></el-input>
</template>
</el-table-column>
</el-table>
export default {
name: 'HelloWorld',
data() {
return {
columns: [null, 'address', null, 'email', 'phoneNumber'],
tableData: [
{ name: 'A', address: '地址A', number: 1, email: 'a.qq', phoneNumber: '', sex: '' },
{ name: 'B', address: '地址B', number: 2, email: 'b.qq', phoneNumber: '', sex: '' },
{ name: 'C', address: '地址C', number: 3, email: 'c.qq', phoneNumber: '', sex: '' },
{ name: 'D', address: '地址D', number: 4, email: '', phoneNumber: '', sex: '' },
{ name: 'E', address: '地址E', number: 5, email: '', phoneNumber: '', sex: '' },
],
};
},
methods: {
/**
* 键盘方向键移动
* @param {*} vertical 垂直方向
* @param {*} horizontal 水平方法
* @param {*} index 表格索引
* @param {*} columnFile 列名
*/
move(vertical, horizontal, index, columnFile) {
const rowIndex = index + vertical;
const columnIndex = this.columns.indexOf(columnFile) + horizontal;
console.log(rowIndex, columnIndex, 'columnIndex');
// 控制在上下左右移动时,在需要移动的输入框内
if (rowIndex >= 0 && rowIndex < this.tableData.length && columnIndex >= 1) {
this.$nextTick(() => {
const el = this.$refs['tableRef'].$refs.bodyWrapper.querySelector(
`tr:nth-child(${rowIndex + 1}) td:nth-child(${columnIndex + 1}) input`
);
el.focus();
this.timeout = setTimeout(() => {
el.select();
});
});
}
},
},
};
代码解释
首先需要把表格想象成一个二维表格,左上角为(0,0),第一行第二列为(0,1)以此类推
- 首先确定需要移动哪些列,在代码里的
columns存放的就是需要移动的列 move函数- 第一个参数因为上下移动都是一行一行的,所有每个传参都是
1,正负值是区分是往上移动,还是往下移动 - 第二个参数左右移动可能存在跨行移动,基本的左右移动都是一列一列传参都是一,正负值是区分是往左移动,还是往右移动,像
地址着向右移动需要跨一列,在传参的时候就是2 - 第三个参数
index表格索引主要是来计算当前移到是哪一行,通过传入的值与当前表格索引进行计算,得出光标在一行就是x - 第四个参数
columnFile列名,之前已经定义需要移动的列columns,通过传入的字段与columns做比较计算出是在哪一列,得出光标在一列就是y
- 第一个参数因为上下移动都是一行一行的,所有每个传参都是
- 为什么这样写
rowIndex >= 0 && rowIndex < this.tableData.length && columnIndex >= 1rowIndex >= 0 && rowIndex < this.tableData.length:控制在垂直方向上不会越界columnIndex >= 1:控制在水平方向上不会越界,至于这里为什么是>=1,因为它是从第二列开始做移动,如果是第一列就改为0,如果是第三列就改为2
- 获取元素就是一层一层去获取,目前我是用
elementui,如果你是用的其它ui框架就找到相应的方法去获取
动态表格案例
每一列都是动态的,思路和上面的差不多
<el-table :data="tableData" style="width: 100%" ref="report-table" border>
<el-table-column type="selection" width="55" v-if="isSelection"></el-table-column>
<template v-for="(item, index) in tableColumn">
<el-table-column v-if="item.display" :label="item.title" :key="index" :prop="item.fieldName">
<template slot-scope="scope">
<el-input
v-if="item.inputType === 'input'"
v-model="scope.row[item.fieldName]"
placeholder="请输入内容"
@keydown.up.native="move(-1, 0, scope.$index, item.fieldName)"
@keydown.down.native="move(1, 0, scope.$index, item.fieldName)"
@keydown.left.native="
move(
0,
nextMoveFile(tableColumn, tableNeedMoveFieldNames, item.fieldName, -1),
scope.$index,
item.fieldName
)
"
@keydown.right.native="
move(
0,
nextMoveFile(tableColumn, tableNeedMoveFieldNames, item.fieldName, 1),
scope.$index,
item.fieldName
)
"
/>
<el-input
v-else-if="item.inputType === 'textarea'"
type="textarea"
v-model="scope.row[item.fieldName]"
placeholder="请输入内容"
@keydown.up.native="move(-1, 0, scope.$index, item.fieldName)"
@keydown.down.native="move(1, 0, scope.$index, item.fieldName)"
@keydown.left.native="
move(
0,
nextMoveFile(tableColumn, tableNeedMoveFieldNames, item.fieldName, -1),
scope.$index,
item.fieldName
)
"
@keydown.right.native="
move(
0,
nextMoveFile(tableColumn, tableNeedMoveFieldNames, item.fieldName, 1),
scope.$index,
item.fieldName
)
"
/>
<span v-else>{{ scope.row[item.fieldName] }}</span>
</template>
</el-table-column>
</template>
</el-table>
动态表格左右移动
- 由于表格是动态显示,它的列是不固定的所有要动态去计算,也是最重要的一步
/**
* 获取下上两个字段中间相隔多少步长
* @param {*} column 表格显示列
* @param {*} needMoveFiles 需要移动的字段
* @param {*} file 当前字段
* @param {*} horizontal 水平移动 left:-1 right:1
*/
nextMoveFile(column, needMoveFiles, file, horizontal) {
// 获取页面上显示的列表字段
const showLists = column.filter((item) => item.display).map((item) => item.fieldName);
// 需要移动的字段
const needMoveFile = showLists.filter((item) => needMoveFiles.includes(item));
// 当前按下去的字段
const findNowFileIndex = needMoveFile.indexOf(file);
// 移动到下一个字段
const nextFile = needMoveFile[findNowFileIndex + horizontal];
// 需要移动的步数,
const nowFileIndex = showLists.indexOf(file); // 当前按下字段索引
const nextFileIndex = showLists.indexOf(nextFile); // 移动到下一个字段的索引
const step = nextFileIndex - nowFileIndex;
return step;
}
data() {
return {
isSelection: false,
tableNeedMoveFieldNames: ['phoneNumber', 'email', 'address'],
tableData: [
{ name: 'A', address: '地址A', number: 1, email: 'a.qq', phoneNumber: '', sex: '男' },
{ name: 'B', address: '地址B', number: 2, email: 'b.qq', phoneNumber: '', sex: '女' },
{ name: 'C', address: '地址C', number: 3, email: 'c.qq', phoneNumber: '', sex: '女' },
{ name: 'D', address: '地址D', number: 4, email: '', phoneNumber: '', sex: '男' },
{ name: 'E', address: '地址E', number: 5, email: '', phoneNumber: '', sex: '男' },
],
// 模拟动态列
tableColumn: [
{
title: '性别',
fieldName: 'sex',
width: '105',
sort: 0,
display: true,
},
{
title: '手机号',
fieldName: 'phoneNumber',
width: '105',
sort: 1,
display: true,
inputType: 'textarea',
},
{
title: '邮箱',
fieldName: 'email',
width: '105',
sort: 2,
display: true,
inputType: 'input',
},
{
title: '数量',
fieldName: 'number',
width: '105',
sort: 3,
display: true,
},
{
title: '地址',
fieldName: 'address',
width: '105',
sort: 4,
display: true,
inputType: 'input',
},
{
title: '姓名',
fieldName: 'name',
width: '105',
sort: 5,
display: true,
},
],
};
}
-
通过
nextMoveFile方法计算出左右间隔之后,再去调用move方法实现的形式和静态没有区别 -
isSelection控制是否有多选列,如果有在左右移动加2,没有左右移动加一
/**
* 键盘方向键移动
* @param {*} vertical 垂直方向
* @param {*} horizontal 水平方法
* @param {*} index 表格索引
* @param {*} columnFile 列名
*/
move(vertical, horizontal, index, columnFile) {
let rowIndex = index + vertical;
let columnIndex = 0;
columnIndex = this.returnCloumnIndex(this.tableColumn, columnFile) + horizontal;
if (rowIndex >= 0 && rowIndex < this.tableData.length && columnIndex >= 0)) {
this.moveFocus('report-table', rowIndex + 1, columnIndex + (this.isSelection ? 2 : 1));
}
},
/**
* 输入框获取焦点
* @param {*} refName 元素节点
* @param {*} rowIndex 行索引
* @param {*} columnIndex 列索引
*/
moveFocus(refName, rowIndex, columnIndex) {
this.$nextTick(() => {
const element =
this.$refs[refName].$refs.bodyWrapper.querySelector(
`tr:nth-child(${rowIndex}) td:nth-child(${columnIndex}) input`
) ||
this.$refs[refName].$refs.bodyWrapper.querySelector(
`tr:nth-child(${rowIndex}) td:nth-child(${columnIndex}) textarea`
);
if (!element) {
return;
}
element.focus();
});
},
/**
* 返回列索引
* @param {*} column 表格显示列
* @param {*} field 字段
* @returns number
*/
returnCloumnIndex(column, field) {
if (column.length && field) {
return column
.filter((item) => item.display)
.map((item) => item.fieldName)
.indexOf(field);
}
return 0;
},
结尾
上面就是整个实现过程,只要思路清楚,其实现实起来还是比较容易的