需求:
vxe-table表格
1、新增的时候,vxe-table第一行的第一个输入框聚焦
2、输入完成后,按回车,自动跳到同一行的下一个输入框
3、当在同一行的最后一个输入框输入完成后,按回车跳回第一个输入框并选中状态且复选框为选中状态
4、点新增滚动条过长遮挡住第一列的话,要向左滚动到最开始的位置
5、表格中有两种下拉框,原生的lel-select及封装后的select组件,面板打开时的上下移动及回车赋值和直接点击赋值后要聚焦
6、表格中有封装的组件,输入框+el-table,输入值的时候显示el-table,使用的el-popover,实现类似于el-selelct下拉框面板的效果,增加了svg图标,点击打开弹窗,弹窗双击行或单击后点确定赋值之后,输入框要聚焦
需求+优化, 花了两天的时间,
踩过的坑:
1、el-select选择框面板打开,点击选值后,回车向右跳转后,el-seelct依然打开面板问题
2、列表同一行,任意位置开始按回车键,触发不了回车事件
3、回车事件中,获取不到自定义属性问题
4、列表同一行最后一列回车时跳到第一列中聚焦 且选中状态
5、列表横向过长,出现滚动条时新增,滚动条如何回到第一列且聚焦
6、回车向右跳到下一输入框聚焦时,滚动条遮挡导致下一输入框显示不全,滚动条如何滑至最右侧
7、vxe-table复选框如何手动选中且保持原有的勾选效果
实现思路;
1、在utils文件夹下定义一个js文件,全局注册后,给输入框,下拉框等编辑组件绑定回车事件
2、给所有列表Dom元素添加自定义索引
备注:示例代码只保留了和功能相关的代码
1、父页面中,vxe-table封装成子组件
<vxeTable
ref="vxeTableRef"
:columns="columns"
:data-source="list"
@selectAllEvent="selectAllEvent"
@getList="getList"
@addRow="addRow"
@handleDelList="handleDelList"
:formData="formData"
:enterFlag.sync="enterFlag"
></vxeTable>
//新增
handleAddList(){
this.list.unshift({
id: this.uuid(),
money: 0,
cash: 0,
checked:false
})
this.list.forEach(item=>{
item.checked = false
})
this.dealFirstInput()
},
dealFirstInput(index, flag) {
this.$nextTick(() => {
// vxe-body--row从1开始
let lastIndex
if (flag == 'up') {
lastIndex = index +1
} else if (flag == 'down') {
lastIndex = index + 2
}
// 增加下行
if (index>=0&&flag) {
// 获取第一个输入框元素
const lastElement = `.vxe-table--body-wrapper .vxe-table--body .vxe-body--row:nth-child(${lastIndex}) .vxe-body--column:nth-child(3) .el-input__inner`
const tolastInput = this.$refs.vxeTableRef.$refs.vxeTable.$el.querySelector(lastElement)
// // 聚焦第一个输入框
tolastInput.focus()
} else {
// 新增和增加上行
// 获取第一个输入框元素
const firstInput = this.$refs.vxeTableRef.$refs.vxeTable.$el.querySelector(
'.vxe-table--body-wrapper .vxe-table--body .vxe-body--row:nth-child(1) .vxe-body--column:nth-child(3) .el-input__inner'
)
// // 聚焦第一个输入框
firstInput.focus()
}
this.enterFlag = true
})
},
//列表操作列增加上行,增加下行
addRow(row, index, key) {
const newObj = { money: 0, cash: 0, id: this.uuid() }
if (index >= 0) {
this.enterFlag = !this.enterFlag
if (key === 'previous') {
this.list.splice(index, 0, newObj)
this.dealFirstInput(index, 'up')
return
}
this.list.splice(index + 1, 0, newObj)
this.dealFirstInput(index, 'down')
}
}
2、子组件vxe-table内部
<el-form :model="form" ref="form" :rules="rules" :show-message="false">
<vxe-table
ref="vxeTable"
class="mytable-scrollbar"
:row-config="{ height: 36 }"
border
@scroll="handleScroll"
show-overflow
resizable
@checkbox-all="selectAllEvent"
@checkbox-change="selectAllEvent"
:footer-method="footerMethod"
:data="list"
:column-config="{ resizable: true }"
@resizable-change="resizableChange"
:checkbox-config="{ checkAll: true }"
>
<vxe-column type="checkbox" width="60" fixed="left" :header-align="'center'" :align="'center'" :resizable="false"></vxe-column>
<template v-for="config in newColumns">
<vxe-column
:key="config.field"
:width="config.width"
:field="config.field"
:visible="config.visible"
:header-align="'center'"
:align="'center'"
:fixed="config.freeze === true ? 'left' : ''"
:min-width="config.field"
:resizable="true"
>
<template #default="{ row, $rowIndex, $columnIndex }" v-if="config.field == 'xxx'">
<el-form-item :prop="'list.' + $rowIndex + '.notes'" :rules="rules.xxx">
<el-input
ref="input"
v-model="row.xxx"
@keyup.enter.native="$event => tableKeydown($event, $rowIndex, $columnIndex)"
clearable
></el-input>
</el-form-item>
</template>
<template #default="{ row, $rowIndex, $columnIndex }" v-else-if="config.field == 'xxx'">
<el-select
:ref="'select' + $columnIndex + 'input' + $rowIndex"
v-model="row.xxx"
@keyup.enter.native="$event => tableKeydown($event, $rowIndex, $columnIndex)"
clearable
filterable
:disabled="isEnabled"
>
<el-option v-for="item in deptOption" :key="item.deptId" :value="item.deptId" :label="item.deptName"></el-option>
</el-select>
</template>
</vxe-column>
</vxe-table>
</el-form>
props: {
enterFlag: {
type: Boolean,
default: false,
},
}
watch:{
enterFlag: {
handler(newVal) {
if (newVal === true) {
this.getAllInput()
}
},
deep: true,
},
}
// 滚动事件
getAllInput() {
setTimeout(() => {
this.$nextTick(() => {
this.$refs.vxeTable.scrollTo(0)
})
}, 500)
},
最重要的是js
export function tableKeydown(e, rowIndex, columnIndex) {
//事件对象兼容
let ev = e || window.event || arguments.callee.caller.arguments[0]
//通过ev 获取 当前input 名称 用于判断属于哪列
let className = ev.target.offsetParent.className
//获取所有input
let inputAll = document.querySelectorAll('.mytable-scrollbar .el-input__inner')
//获取所有索引组成的数组
const inputIndexs = []
for (var i = 0; i < inputAll.length; i++) {
inputIndexs.push(i)
}
//按索引,增加自定义属性,这里遇到的坑是js中之前已经增加过自定义属性,代码复制过来的时候没有改,导致一直查找不到我现在增的
//自定义属性customFocusIndex,一定要保证自定义属性的唯一性
for (var i = 0; i < inputIndexs.length; i++) {
inputAll[i].setAttribute('customFocusIndex', inputIndexs[i])
}
let attrIndex = parseInt(ev.target.getAttribute('customFocusIndex'))
// ev.keyCode == 13代表按下的是回车,向上键33,向下键34
if (ev && ev.keyCode == 13) {
//每次回车+1,下一个输入框的索引
attrIndex += 1
//如果遇到禁用列,多加一次索引
for (var i = attrIndex; i < inputAll.length; i++) {
if (inputAll[i] && inputAll[i].disabled) {
attrIndex += 1
} else {
break
}
}
//一行一共有多少列,这里写死了
const rowline = rowIndex * 12
//第10列之后都是禁用列,这里也写死了,第10列代表最后一列
if (columnIndex == 10) {
// 最后一列跳转到第一列
inputAll[rowline].focus()
inputAll[rowline].select()
//调用scrollTo方法,手动将滚动条滚动到第一列
setTimeout(() => {
this.$nextTick(() => {
this.$refs.vxeTable.scrollTo(0)
})
}, 500)
//最后一列调整到第一列的时候要手动选中复选框,使用setCheckboxRow方法
const vxeTable = this.$refs.vxeTable
const data = this.$refs.vxeTable.getData()
vxeTable.setCheckboxRow(data[rowIndex],true)
this.$refs.vxeTable.loadData(data)
// 下拉框点击选择,下拉框比输入框多一个类名is-focus,通过此区分
if (className.indexOf('is-focus') != -1) {
//动态获取el-select的实例,防止重复,行索引+列索引都拼接上了
let DictselectRef = 'select' + columnIndex + 'input' + rowIndex
//封装的el-select组件有两层ref,根据自己定义的ref
if (this.$refs[DictselectRef][0].$refs.selectRef) {
// dictSelect
this.$refs[DictselectRef][0].$refs.selectRef.visible = false
this.$refs[DictselectRef][0].$refs.selectRef.blur()
this.$refs[DictselectRef][0].$refs.selectRef.$refs.reference.$refs.input.hidden = true
} else {
// el-select 原生el-select
this.$refs[DictselectRef].visible = false
this.$refs[DictselectRef][0].blur()
this.$refs[DictselectRef][0].$refs.reference.$refs.input.hidden = true
}
}
return
}
if (className.indexOf('el-input--suffix') != -1) {
// 下拉框点击选择
if (className.indexOf('is-focus') != -1) {
let DictselectRef = 'select' + columnIndex + 'input' + rowIndex
if (this.$refs[DictselectRef][0].$refs.selectRef) {
// dictSelect
this.$refs[DictselectRef][0].$refs.selectRef.visible = false
this.$refs[DictselectRef][0].$refs.selectRef.blur()
this.$refs[DictselectRef][0].$refs.selectRef.$refs.reference.$refs.input.hidden = true
} else {
// el-select
this.$refs[DictselectRef].visible = false
this.$refs[DictselectRef][0].blur()
this.$refs[DictselectRef][0].$refs.reference.$refs.input.hidden = true
}
}
//当第8列回车的时候,会因为滚动条遮挡看不见第九列是否光标已进入,所以加了滚动条向右滚动事件
if (columnIndex == 8) {
let scrollLeft = this.$refs.vxeTable.$el.scrollWidth
setTimeout(() => {
this.$nextTick(() => {
this.$refs.vxeTable.scrollTo(scrollLeft)
})
}, 500)
}
if (inputAll[attrIndex]) {
inputAll[attrIndex].focus()
}
}
}
}