阅读 3355

解决页面需要上下左右选中元素的问题,富操作

前言

纯粹是因为无聊吧,然后就是突发奇想。

我以前开发过web端的收银系统。之前更早的时候也是做过零售软件方面的实施,通常在门店的时候收银员为了更为快速的去收银,是很少会去用鼠标的。都是键盘解决。但是web操作系统页面有一个问题。就是你的通过tab等方式其实不是很方便。然后我们还有商品列表也需要通过键盘方式选中返回。如果通过鼠标,在一些门店每天都是几万的销售额中,是非常累人,且效率低下的。这次开发的这个插件也是为了解决这方面的问题。

目前做到了基本的上下左右,属于初步的完成。未经业务实战。但是我想还是比较OK的。

源码地址:https://github.com/ht-sauce/dream/blob/master/src/components/CustomCom/RichOperate/index.vue

原理:

首先,我们想要键盘上面操作选中dom元素。有本身的tap,上下左右等等,也可以使用tabindex,方式。但是这些都无法说使得操作统一方便。规则较为混乱,并且我们都知道div是么有焦点这个状态的。

那么我们想要实现的情况下,那么就需要自己造轮子了。我们自己来定义并且实现这一套规则。

1、监听键盘的上下左右

2、随着元素的上下左右进行数据移动。

吐槽一下,如果是jquery时代,怕是要麻烦很多。

实现左右移动

左右移动其实很简单,我们只要操作元素的数组的index对应的增加减就可以了。所以代码简单的不行。

同理,我们要操作元素的上下也可以这样实现。但是我的目的是要普适性的实现。那么左右我们是根据既定规则来实现的。

定义插入的数组的元素结构

// 设置绑定的初始数据
this.dhtSetList({
  bindData: value, // 绑定数据
  self: this, // 本身
  select: false, // 是否被选中
})
复制代码

这里其实比较简单能看出来,第一个是绑定的数据。第二个是vue当前实例本身。第三个就是状态,可以不管。

插入的顺序决定了左右移动的顺序

上下移动

上下移动最麻烦的是我们不能按数组的index进行移动。要根据元素本身的上下位置进行移动。

代码原理讲解:

1、过滤同级别元素

2、上过滤掉比自己低的,下过滤比自己高的

3、对元素的高低进行排序,上最小,下最小的元素。

4、如果上下存在一样的Y轴数据,那么比较X大小,得到最小X进行返回,X如果还有多个,那么默认返回第一个

// 上下移动
topOrbottomMove(type) {
  let currentIndex = this.lastTimeLi() // 上一次的index,也是当前的index
  const list = this.list
  // 当前
  const currentLi = list[currentIndex]
  const currentLiDom = currentLi.self.$el.getBoundingClientRect()
  const currentY = currentLiDom.y
  const currentX = currentLiDom.x

  const relativeList = []
  list.forEach((item, index) => {
    const { y, x } = item.self.$el.getBoundingClientRect()
    relativeList.push({
      li: item,
      y: currentY - y,
      x: Math.abs(currentX - x),
      index,
    })
  })
  // 过滤通等级元素
  const eliminate = relativeList.filter(item => item.y !== 0)
  if (type === 'top') {
    // 往上过滤比自己低的
    const topEliminate = eliminate.filter(item => item.y > 0)
    if (topEliminate.length === 0) {
      // 最终发送确认值
      this.sendEmit({ item: list[currentIndex], index: currentIndex })
    } else {
      topEliminate.sort((a, b) => a.y - b.y)
      const xArr = topEliminate.filter(item => item.y === topEliminate[0].y)
      if (xArr.length > 1) {
        xArr.sort((a, b) => a.x - b.x)
        this.sendEmit({ item: xArr[0], index: xArr[0].index })
      } else {
        this.sendEmit({ item: xArr[0], index: xArr[0].index })
      }
    }
  }
  if (type === 'bottom') {
    // 往下过滤比自己高的
    const bottomEliminate = eliminate.filter(item => item.y < 0)
    if (bottomEliminate.length === 0) {
      // 最终发送确认值
      this.sendEmit({ item: list[currentIndex], index: currentIndex })
    } else {
      bottomEliminate.sort((a, b) => b.y - a.y)
      const xArr = bottomEliminate.filter(item => item.y === bottomEliminate[0].y)
      if (xArr.length > 1) {
        xArr.sort((a, b) => a.x - b.x)
        this.sendEmit({ item: xArr[0], index: xArr[0].index })
      } else {
        this.sendEmit({ item: xArr[0], index: xArr[0].index })
      }
    }
  }
},
复制代码

使用

昨天看了下文章被掘金大大推首页了,不好意思太糊弄。这里贴一下使用方式。

核心使用方式:

RichOperate: () => import('@/components/CustomCom/RichOperate/index'),
复制代码

这个是组件的核心内容,会暴露出props和一个provide分发的dhtSetList函数,函数本身已经处理好了当前操作列表的是否重复问题

这段代码会判断你的组件的本身是否重复

// 判断是否已经存在数据列表内部
this.list.forEach(item => {  
if (item.self === self) {    
item.bindData = bindData 
   alike = true  
}
})
复制代码

然后我的给的源代码的文件边上还有一个item文件,这个是子组件。一般推荐大家按这样的方式去封装一下自己的内容。

<template>
  <div class="rich-operate-item" ref="richOperateItem">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: 'RichOperateItem',
  inject: ['dhtSetList'],
  props: {
    value: null, // 当前绑定的数据
  },
  data() {
    return {
      select: false,
    }
  },
  watch: {
    value: {
      deep: true,
      immediate: true,
      handler(value) {
        // 设置绑定的初始数据
        this.dhtSetList({
          bindData: value, // 绑定数据
          self: this, // 本身
          select: false, // 是否被选中
        })
      },
    },
  },
  mounted() {},
}
</script
复制代码

导入为RichOperateItme: () => import('@/components/CustomCom/RichOperate/item'),

最后使用的时候就像这样

<rich-operate switch :index="5" class="test">
  <rich-operate-itme :value="shiyan">
    <div class="ceshi"></div>
  </rich-operate-itme>
  <rich-operate-itme>
    <div class="ceshi"></div>
  </rich-operate-itme>
</rich-operate>
复制代码

但是这样存在一个问题,我不知道是否对于组件库的组件本身的结构是否产生影响。

如果产生影响的话,那么建议使用

this.dhtSetList({ 
 bindData: value, // 绑定数据
  self: this, // 本身 
 select: false, // 是否被选中})
复制代码

不过这个也存在一定的问题。我这里传递进去了是组件本身this。这样做的目的是为了等vuedom渲染完成之后自动绑定dom节点。

主要是这些。关于props传值,大家看源码吧。我习惯写注释,还是挺全面的。

总结

基本原理其实挺简单的。

注意我这里为了保证每个子组件用了provide、inject方式进行数据分发。示例中是单独定义了一个子的组件,但是只要根据规则传递数据,那么也就没有什么问题了。

注意,使用了provide、inject分发数据会导致全局存在。意思是子代组件都能得到inject的内容。

主要是提供一个思路。并且给予一定的源码。

今年也是自己够任性,又太高傲。生活依旧不稳,但是接下来我主要目标是vue3和typescript。两者结合使用。