vue封装虚拟表格思路以及其中遇到哪些问题?

2 篇文章 0 订阅

表头/内容/脚/ 三者中的宽度如何保持一致

摘要:198
科目:增大
借:207

不管增加还是变小,只有科目变化,其他不变

方案一:其他值固定,根据页面 百分比 - (摘要 + 借 + 币别 + 辅助核算), 使用display:flex;flex-direction:column;

参考表格:他们有一个默认宽度 80,header,body,footer都调用统一的函数, 所以会上下对齐

1.1滚动条的问题?

方案一:手写一个滚动条,那么之后鼠标按下之后才会计算滚动的距离,那如果滚轮滚动就无法实现了
滚动事件解决方案???

https://blog.csdn.net/seimeii/article/details/123092157
e.wheelDelta > 0 向上滚动
e.wheelDelta < 0 向下滚动

滚动条跟内容如何联动
 // 左侧的内容区跟右侧的滑块关联
    handleContentRelationBar() {
     
      const N = this.zingContent_DIV.offsetHeight - this.zingBody_DIV.offsetHeight, //  400 = 600 - 200
            n = this.zingDuration_DIV.offsetHeight - this.zingBar_DIV.offsetHeight,// 200 -30 = 170
            n1 = this.getTranslateYValue(this.zingBar_DIV), // transform = 20 距离顶部距离 20px
            contentScrollHeight = Math.floor(N / (n / n1))
          //   N     n
          // ---- = ----
          //   x     n1
            this.zingContent_DIV.style.transform = `translateY(${-contentScrollHeight}px)`
            this.handleStartAndEndPosition(contentScrollHeight)
    },
虚拟数据如何截取
     // 虚拟列表
    handleVirtual() {
      200 / 60 = 3.3 ~~ 4条
      content.length === 4 ? endIndex = 4
      content[4 + 2] === undefined ? endIndex = 4
      endIndex = 4 + 2 + 0
      
      this.bodyStoreRowCount = ~~(this.zingBody_DIV.offsetHeight / this.rowHeight)
      this.bodyShowRowCount = this.bodyStoreRowCount + 2
      const contentListLength = this.contentList && this.contentList.length
   
      if (this.bodyStoreRowCount === contentListLength) {
        this.isTableMixinShowScrollBar = false
        return this.endIndex = contentListLength
      }
      this.isTableMixinShowScrollBar = true
      if (!this.contentList[this.bodyShowRowCount]) {
        return this.endIndex = contentListLength - 1
      }
      this.endIndex = this.bodyShowRowCount + this.startIndex
      
    },

开始下标如何获取
    // 根据滚动条看表格中的内容,开始div跟结束div的位置
    handleStartAndEndPosition(ScrollHeight) {
     // 通过滚动的高度 / 行高 = 开始下标
      this.startIndex = ~~(ScrollHeight / this.rowHeight)
      let _endIndex = this.bodyShowRowCount + this.startIndex
      if (!this.contentList[_endIndex]) {
        this.endIndex = this.contentList.length - 1
      }
      this.endIndex = _endIndex
    },
方案二:使用默认的overflow:auto:然后右侧专门留好固定的距离, windiw 跟 mac 滚动条的宽度有出入

页面数据报错,也面中的数据还没有显示,但是需要获取div的高度,显示为0, 就报错

1.2滚动条的问题?
计算滚动条速度??

当通过内容跟固定的 div 计算出滚动条高度, 当数据也多滚动条高度 越小, 当滑动时每次高度 + 10 太小了,如何按比例加快速度

滚动条高度 280px 页面数据 6条
滚动条高度 260px 页面数据 7条
滚动条高度 240px 页面数据 8条
滚动条高度 220px 页面数据 10条
滚动条高度 200px 页面数据 11条
滚动条高度 190px 页面数据 12条

这些数据滚动条滚动的匀速 是 10 px

1.3、页面分辨率问题, 是全屏还是部分屏???

1.4、div中滚动事件,不影响整个表格外面的事件 onmousewheel ???
https://www.ynpxrz.com/n823454c2025.aspx
e.preventDefault() // 取消事件的默认动作。阻止事件冒泡到全局

1.5、使用div布局之后,表格上下边线无法对齐??
因为每一列增加了右边框,导致样式错位,不准确 如何解决? 
排除法1、先看下全局是否设置 boder-sizing:border-box; 这样给每个div增加边框都会计入在内部,不会影响外面边框
2、看下适配的内容,必须使用 display: flex; flex-grow:1; 自动分配其他单据修改后的宽度
1.6 当自己手写滚动条之后, 页面中其他滚动条事件被禁用,如何处理
1.7overflow:hidden同样会隐藏position:absolute的子元素

https://blog.csdn.net/bclz_vs/article/details/13002301
解决方案:只能将要显示的div插入到全局,使用position:fixed; lef:;top的距离,滚动右侧滚动条需要重新计算

类似于element中 Popover ,绑定在当前页面document中,滚动条滚动,他也跟随滚动,先找到绑定他div的位置,然后向下或者向上绑定,应该取的 pageY / pageX轴的距离

当用户点击的时候才记录这个div的位置,然后给 popover, 其中遇到的问题,小屏幕聚焦以后会跟着移动,怎么做??

首先获取点击某个元素当前位置,这时候获取的是offsetLeft距离是固定的,在然后就是top的距离,全局的也行/offsetTop也行,看它定位在哪里,然后添加一个固定的距离,就定位到当前元素下边了。

1.8 使用 position:absolute; 会让页面重排跟重绘,改为 transform

https://zhuanlan.zhihu.com/p/78230297
https://www.coder.work/article/1119523

1.9 页面展示过多的数据, input输入内容有卡顿,是因为v-model绑定每次都重新渲染,怎么解决?

解决办法:把页面中的内容分离成组件,比如把输入框抽离成组件,组件之间的render是不影响的。

1.10 页面输入框很卡顿,

优化方案:1、v-model绑定lazy不可以,因为input框需要实时监测摘要的数据
2、列表虚拟滚动

1.11 实现虚拟滚动了, 可是左边的下标不对,如何解决呢?

每次新增删除一条数据都初始化 indexMap{ id: index }, 然后遍历对象,根据id查找下标

1.12 当表格删除一条数据后,会空出原来 表格的 高度, 需要将数据挤下来

思路:根据表格容器跟内容改变滚动条的高度,然后移动滚动条的距离,计算出内容区域可以移动的距离,
如何判断兜底呢,当表格内容的长度 等于虚拟列表最后一个长度的下标 或者 开始的下标 + 一页可以存放几条数据 等于 虚拟列表的长度
contentListLength === this.endIndex

遇到的问题:当最后一条为11条,删除最后一条页面没有变,怎么办?
当数据删除的时候,当前页面的startIndex 还是原来的, 6 + 4 = 10, 需要回到最底部,原理:先根据表格高跟内容求比例,在改变滚动条的高度,在移动滚动条的距离顶部的距离,在获取 startIndex下标,然后重新渲染,页面

相当与 向上 / 向下 按钮点击

原先一页10 条数据600px,增加1条,此时内容表格高度为 660px,

求出大容器 比例 = body(300) / content(660)
求小滚动条的高度 = 滚动条的高度 * 比例

求出虚拟列表:
// 虚拟列表
handleVirtual() {
一页可以放几条数据 = 容器高度 / 每行高度(60)
this.bodyStoreRowCount = ~~(this.zingBody_DIV.offsetHeight / this.rowHeight)
this.bodyShowRowCount = this.bodyStoreRowCount + 2
const contentListLength = this.contentList && this.contentList.length

如果每页面存放的数据相等了,那么隐藏滚动条
  if (this.bodyStoreRowCount === contentListLength) {
    this.isTableMixinShowScrollBar = false
    return this.endIndex = contentListLength
  }
  this.isTableMixinShowScrollBar = true

获取超出的数据是否存在,不存在说明最大就是每页条数
  if (!this.contentList[this.bodyShowRowCount]) {
    return this.endIndex = contentListLength - 1
  }
  this.endIndex = this.bodyShowRowCount + this.startIndex
  
},
 const COLUMN_WIDTH_MAP = {
  'serialNumberW': '40',
  'abstractW': '200',
  'subjectW': '428',
  // 'amountAndCurrencyW': '174',
  'borrowerW': '208',
  'lenderW': '208',
  'caozuoW': '80'
 }

 const tableMixin = {
  data() {
    return {
      tableMixinColumnWidthMap: COLUMN_WIDTH_MAP,
      tableAddVoucher_DIV: null,
      tableBody_DIV: null,
      tableContent_DIV: null,
      tableDuration_DIV: null,
      tableBar_DIV: null,
      tableVoucher_DIV: null,
      tableSubjectContent_DIV: null,
      tableAbstractContent_DIV: null,
      rowHeight: 60,
      bodyStoreRowCount: 0, // 页面存放的数量
      bodyShowRowCount: 0, // 页面需要显示的数量
      startIndex: 0, // 页面开始显示的div位置
      endIndex: 0, // 页面开始结尾的div位置
      virtualMapKey: {}, // 虚拟列表voucherId在原始数组中的位置
      isTableMixinShowScrollBar: false
    }
  },
  computed: {
    showVirtualData() {
      return this.contentList.slice(this.startIndex, this.endIndex)
    },
    blankFillPadding() {
      return {
        paddingTop: this.startIndex * this.rowHeight + 'px',
        paddingBottom: (this.contentList.length - this.endIndex) * this.rowHeight + 'px'
      }
    }
  },
  methods: {
    /**
     * @description: 初始化表格滚动条的高度,跟滚轮滑动速度
     * @param {number} speed
     */    
    async initTableMixin(speed) {
      this.$nextTick(() => {
        this.tableBody_DIV = document.getElementById('tableBody')
        this.handleMixinTableContainerHeight()

        this.tableContent_DIV = document.getElementById('tableContent')
        this.tableDuration_DIV = document.getElementById('tableDuration')
        this.tableBar_DIV = document.getElementById('tableBar')
        this.tableSubjectContent_DIV = document.getElementById('subjectContent')
        this.tableAbstractContent_DIV = document.getElementById('abstractContent')

        const container_proportion = this.tableBody_DIV.offsetHeight / this.tableContent_DIV.offsetHeight // 大容器的比例
        this.tableBar_DIV.style.height = Math.floor(this.tableDuration_DIV.offsetHeight * container_proportion) + 'px' // 滚动条的高度
        this.handleSetTableW()
        this.handleVirtual()
        this.handleWheel(speed)

       
  
        let page_Y = 0
        //拖拽: 鼠标按下,鼠标拖动,鼠标离开
        this.tableBar_DIV.onmousedown = (e) => {
          e.preventDefault()
          page_Y = e.pageY
    
          document.onmousemove = (e) => {
            let page_Proportion = e.pageY - page_Y
            this.tableBar_DIV.style.transform = `translateY(${this.getTranslateYValue(this.tableBar_DIV) + page_Proportion}px)`
            page_Y = e.pageY
    
            this.handleBarMove()
            // 内容移动
            this.handleContentRelationBar()
           
          }
          document.onmouseup = () => {
            document.onmousemove = null
          }
        }
      })
    },
    // 鼠标按在滚动条开始移动内容
    handleBarMove() {
      const translateY = this.getTranslateYValue(this.tableBar_DIV)
      let barScrollHeight = translateY
      if (translateY <= 0) {
        barScrollHeight = 0
        this.tableBar_DIV.style.transform = 'translateY(0px)'
      } else if (this.tableBar_DIV.offsetHeight + translateY > this.tableDuration_DIV.offsetHeight){
        barScrollHeight = this.tableDuration_DIV.offsetHeight - this.tableBar_DIV.offsetHeight
        this.tableBar_DIV.style.transform = `translateY(${barScrollHeight}px)`
      }
    },
    // 左侧的内容区跟右侧的滑块关联
    handleContentRelationBar() {
      const N = this.tableContent_DIV.offsetHeight - this.tableBody_DIV.offsetHeight,
            n = this.tableDuration_DIV.offsetHeight - this.tableBar_DIV.offsetHeight,
            n1 = this.getTranslateYValue(this.tableBar_DIV),
            contentScrollHeight = Math.floor(N / (n / n1))
            this.tableContent_DIV.style.transform = `translateY(${-contentScrollHeight}px)`
            this.handleStartAndEndPosition(contentScrollHeight)
    },
    /**
     * @description: 页面滚动事件,会随着数量的增加移速增加
     * @param {number} speed
     */    
    handleWheel(speed = 10) {
      if (!this.isTableMixinShowScrollBar) {
        return
      }
      this.tableBody_DIV.onmousewheel = (e) => {
        e.preventDefault()
        if (e.wheelDelta) {
          const translateY = this.getTranslateYValue(this.tableBar_DIV)
          // console.log('translateY', translateY)
        //  / console.log('e.wheelDelta ', e.wheelDelta )
          if (e.wheelDelta > 0) { //当滑轮向上滚动时
            this.tableBar_DIV.style.transform = `translateY(${translateY - speed}px)`
            this.handleBarMove()
          } else if (e.wheelDelta < 0) { //当滑轮向下滚动时  
            //当滚动的时候页面还会重新加载,优化
            this.tableBar_DIV.style.transform = `translateY(${translateY + speed}px)`
            this.handleBarMove()
          } 
          this.handleContentRelationBar()
        }
      }
    },
    // 计算科目的宽度
    handleSetTableW() {
      this.tableVoucher_DIV = document.getElementById('addVoucherContentTop')
      const tableVoucher_W = Math.floor(this.tableVoucher_DIV.offsetWidth)
      let surplus = 0
      const filterTitleList = this.titleList.filter(item => item.key !== 'subject')
      filterTitleList.forEach(item => {
        surplus += +this.tableMixinColumnWidthMap[item.key + 'W']
      })
      this.tableMixinColumnWidthMap.subjectW = Math.floor(tableVoucher_W - surplus + 21)// 21(外边距 ) filterTitleList.length(每列会有1px边线,导致样式错乱)
    },
    /**
     * @description: 获取TranslateY移动的距离
     * @param {Object} ele 页面元素
     * @return {number} 返回距离
     */    
    getTranslateYValue(ele){
      if (ele && ele.style && ele.style.transform) {
        const transform = ele.style.transform,
              n = transform.indexOf("("),
              n1 = transform.indexOf(")")
        return parseInt(transform.slice(n + 1, n1 - 2))
      }
      return 0
    },
    // 虚拟列表
    handleVirtual() {
      this.bodyStoreRowCount = ~~(this.tableBody_DIV.offsetHeight / this.rowHeight)
      this.bodyShowRowCount = this.bodyStoreRowCount + 2
      const contentListLength = this.contentList && this.contentList.length
      // console.log('startIndex---', this.startIndex)
      // console.log('bodyStoreRowCount---', this.bodyStoreRowCount)
      // console.log('contentListLength---', contentListLength)
      // console.log('contentList[this.bodyShowRowCount]---: ', this.contentList[this.bodyShowRowCount]);
      if (this.bodyStoreRowCount === contentListLength) {
        this.isTableMixinShowScrollBar = false
        return this.endIndex = contentListLength
      }
      this.isTableMixinShowScrollBar = true
      if (!this.contentList[this.bodyShowRowCount]) {
        return this.endIndex = contentListLength - 1
      }
      this.endIndex = this.bodyShowRowCount + this.startIndex
      
    },
    // 根据滚动条看表格中的内容,开始div跟结束div的位置
    handleStartAndEndPosition(ScrollHeight) {
      // console.log('ScrollHeight', ScrollHeight)
      // console.log('33399--->', ~~(ScrollHeight / this.rowHeight))
      this.startIndex = ~~(ScrollHeight / this.rowHeight)
      let _endIndex = this.bodyShowRowCount + this.startIndex
      if (!this.contentList[_endIndex]) {
        this.endIndex = this.contentList.length - 1
      }
      this.endIndex = _endIndex
    },
    /**
     * @description: 保存原始数组下标,序列号使用
     * @param {Array} arr
     */    
    handleTableMixinResetShowDataMap(){
      const arr = this.contentList
      this.virtualMapKey = {}
      if (arr && arr.length > 0) {
        let arrLenght = arr.length
        for (let i = 0; i < arrLenght; i ++) {
          this.virtualMapKey[arr[i].voucherId] = i
        }
      }
    },
    // 根据页面计算出表格最大高度
    handleMixinTableContainerHeight() {
      const tableAddVoucherBody_DIV = document.getElementById('addVoucherBody'),
            contentHeight = ~~(tableAddVoucherBody_DIV.offsetHeight - 350),
            contentListLenght = this.contentList && this.contentList.length
      if (contentHeight <= contentListLenght * 60) {
        this.tableBody_DIV.style.height = contentHeight + 'px'// 头部 + 制单人高度
      } else {
        this.tableBody_DIV.style.height = contentListLenght * 60 + 'px'// 默认内容的高度
      }
    },
    // 计算滚轮滚动速度
    handleMixinTableComputedSpeed() {
      let speed = 10
      if (this.contentList && this.contentList.length) {
        speed += Math.floor(this.contentList.length / 10)
      }
      this.initTableMixin(speed)
    },
    // 内容区域跟滚动条滚动到最底部
    handleDown() {
      const scrollTop = this.tableDuration_DIV.offsetHeight - this.tableBar_DIV.offsetHeight
      this.tableBar_DIV.style.transform = `translateY(${scrollTop}px)`
      this.handleContentRelationBar()
    },
    // 内容区域跟滚动条滚动到最顶部
    handleUp() {
      this.tableBar_DIV.style.transform = `translateY(0px)`
      this.handleContentRelationBar()
    },
    // 判断是调用顶部函数,还是底部函数
    handleContentAndBarScroll() {
      // if(!this.startIndex) {
      //   this.handleUp()
      //   return
      // }
      const contentListLength = this.contentList && this.contentList.length 
      if (contentListLength === this.endIndex || (this.startIndex + this.bodyStoreRowCount) === contentListLength) {
        this.handleDown()
        return
      }
    }
  }
}
export default tableMixin
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue封装通用表格组件实现增删改查功能是一种常见的开发需求。下面我将用300字回答这个问题。 首先,在Vue中创建一个通用表格组件,可以用来展示数据、实现分页、排序等功能。该组件采用props属性接收传入需要展示的数据数组,并在表格中循环渲染显示。同时,该组件也包含一些相关的方法来支持增删改查功能。 对于数据的增加,可以在表格上方添加一个“新增”按钮,在点击按钮后触发一个对话框组件,用来输入新增的数据。在对话框的确认按钮中,调用父组件传入的一个方法,将新增的数据添加到数据数组中,然后更新表格的显示。 对于数据的删除,可以在表格的每一行数据后面添加一个“删除”按钮。在点击按钮后,调用父组件传入的一个方法,传入当前行的数据索引或ID,然后在父组件中删除该数据,并更新表格的显示。 对于数据的修改,可以在表格的每一行数据后面添加一个“编辑”按钮,在点击按钮后触发一个对话框组件,用来显示当前行的数据并进行编辑。在对话框的确认按钮中,调用父组件传入的一个方法,传入当前行的数据索引或ID和编辑后的数据,然后在父组件中更新该数据,并更新表格的显示。 对于数据的查询,可以在表格上方添加一个搜索框组件,在输入关键字后触发一个方法,传入关键字作为参数进行查询。在父组件中根据传入的关键字来筛选匹配的数据,并更新表格的显示。 以上是对Vue封装通用表格组件实现增删改查功能的简要介绍,具体的实现细节还需要根据具体需求进行定制。希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值