• 【vxe-table】@enter.keyup.native实现在列表中回车光标向右移动聚焦及vxe-table的一些方法的使用(具体实现+踩坑篇)


    需求:
    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>
    
       
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    //新增
    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
          })
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    //列表操作列增加上行,增加下行
        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')
            }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
     props: {
        enterFlag: {
          type: Boolean,
          default: false,
        },
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    watch:{
        enterFlag: {
          handler(newVal) {
            if (newVal === true) {
              this.getAllInput()
            }
          },
          deep: true,
        },
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
        // 滚动事件
        getAllInput() {
          setTimeout(() => {
            this.$nextTick(() => {
              this.$refs.vxeTable.scrollTo(0)
            })
          }, 500)
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    最重要的是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()
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
  • 相关阅读:
    Linux进程相关管理(ps、top、kill)
    【Servlet】超详细开发步骤|在idea上配置Tomcat|网页显示当前系统时间
    【二维前缀和】
    Day24-事务和数据库连接池
    RuntimeError: Error compiling objects for extension手把手带你解决(超详细)
    (自学)黑客技术——网络安全
    SpringBoot-配置高级
    安全性表现不错 ORICO双C口pd100w数据线怎么样
    数据仓库DW-理论知识储备
    中国户外休闲家具及用品市场发展规划趋势及运营状况研究报告2022年版
  • 原文地址:https://blog.csdn.net/weixin_49668076/article/details/132629362