数据可视化前端项目开发 - 表格

2021-12-22  本文已影响0人  VioletJack

在数据可视化项目中,除了图表,用到最多的就是表格了。

一开始,项目中使用了 element 自带的 table 组件,后来发现 element 的 table 对于 footer 的支持很少,而且可扩展性也不够。只适合用于一些简单的数据展示。后来我改用了 vxe-table

el-table vs vxe-table

两者的写法非常类似,大概率是 vxe-table 的作者借鉴了 el-table 来进行实现的。但还是有些不同之处。

vxe-table 在 IOS 上去除橡皮筋弹性效果

之前也提到过这个问题,在 IOS 端部分版本会出现表格边缘拖拽出橡皮筋效果。试了多种方案都不太满意,最后屏蔽了系统默认的滑动方式,自己写了一个。

    // 修改 vxe-table 中的滑动容器行为
    bindTouchEvents () {
      const element = document.getElementsByClassName(
        'vxe-table--body-wrapper'
      )[0]
      if (element) {
        element.addEventListener(
          'touchstart',
          event => {
            event.preventDefault()
            this.touchX = event.changedTouches[0].clientX
            this.touchY = event.changedTouches[0].clientY
          },
          true
        )
        element.addEventListener(
          'touchmove',
          event => {
            event.preventDefault()

            // 计算手指偏移量
            const offsetX = event.changedTouches[0].clientX - this.touchX
            const offsetY = event.changedTouches[0].clientY - this.touchY

            element.scrollLeft = element.scrollLeft - offsetX
            element.scrollTop = element.scrollTop - offsetY

            this.touchX = event.changedTouches[0].clientX
            this.touchY = event.changedTouches[0].clientY
          },
          true
        )
        element.addEventListener(
          'touchend',
          event => {
            event.preventDefault()

            this.touchX = event.changedTouches[0].clientX
            this.touchY = event.changedTouches[0].clientY
          },
          true
        )
      } else {
        console.warn('未获取到表格元素')
      }
    },

这样做的好处在于,我自己完全控制了拖拽滑动行为。坏处在于丢失了原生滑动的惯性滑动,看着没那么丝滑了。不过这点也可以后续通过 JavaScript 来优化。

vxe-table 实现惯性滑动

由于表格在移动端的拖拽移动行为被重写,所以没了惯性。于是后来也把拖拽惯性实现了一下。

        element.addEventListener(
          'touchstart',
          (event) => {
            // 表格惯性初始点
            this.positions = []
            this.positions.push({
              x: event.changedTouches[0].clientX,
              y: event.changedTouches[0].clientY,
              time: new Date().valueOf(),
            })
          },
          true
        )
        element.addEventListener(
          'touchmove',
          throttle((event) => {
            // 表格惯性 - 拖拽路径记录
            this.positions.push({
              x: event.changedTouches[0].clientX,
              y: event.changedTouches[0].clientY,
              time: new Date().valueOf(),
            })
            if (this.positions.length > 40) {
              this.positions.shift()
            }
          }, 30),
          true
        )
        element.addEventListener(
          'touchend',
          (event) => {
            // 惯性计算
            this.positions.push({
              x: event.changedTouches[0].clientX,
              y: event.changedTouches[0].clientY,
              time: new Date().valueOf(),
            })
            const endPosition = this.positions[this.positions.length - 1]
            let startPosition
            for (let i = this.positions.length - 1; i >= 0; i--) {
              if (endPosition.time - this.positions[i].time > 1000 || i === 0) {
                startPosition = this.positions[i]
                break
              }
            }
            const timeDiff = (endPosition.time - startPosition.time) / 1000
            this.speedX = (endPosition.x - startPosition.x) / timeDiff
            this.speedY = (endPosition.y - startPosition.y) / timeDiff
            clearInterval(this.timerId)
            const computeScroll = () => {
              const resistance = 0.5 // 惯性衰减速度
              this.speedX *= resistance
              this.speedY *= resistance

              element.scrollLeft = element.scrollLeft - this.speedX
              element.scrollTop = element.scrollTop - this.speedY
              if (Math.abs(this.speedX) < 1) this.speedX = 0
              if (Math.abs(this.speedY) < 1) this.speedY = 0
              if (this.speedX === 0 && this.speedY === 0) {
                clearInterval(this.timerId)
              }
            }
            computeScroll()
            this.timerId = setInterval(computeScroll, 30)
          },
          true
        )

原理是,通过数组记录手指拖拽的整个路径及每个点的时间,在手指放开的时候计算惯性,通过 setInterval 逐步惯性递减滑动。

vxe-table 在浏览器宽度发生变化时自适应宽度

这是开发中遇到的一个小 bug,当页面在 1/2 屏幕宽度下打开表格,然后再把页面宽度调整到整个屏幕宽度,表格出现左侧表格内容,而右侧留白的情况。即并没有因为浏览器页面宽度的变化而自适应。

解决方案:后来发现 vxe-table 有个 auto-resize 的布尔属性,设置为 true 即可。

vxe-table 出现 fixed 列错位

fixed 列,即锁列功能,其实是通过多个表格重叠来实现的。有时候在表格数据发生变化时会出现锁列的表格偏下,无法与正常表格重合的情况。

解决方案:这种情况一般出现在 axios 请求了表格数据后,简单的方法可以通过 v-if 控制表格重新渲染。背后原因尚未深究。

表格数据的下载

对于表格数据的下载,我们是后端同学提供的接口,下面是下载相关的代码:

使用数据流下载 tar.gz 压缩包的方式:

axios({
  method: 'get',
  url: '/api/download?format=tar.gz',
  headers: {
    'Content-Type': 'application/json; application/octet-steam',
  },
  responseType: 'blob', // 重点在于此
}).then(response => {
  let data = response.data
  const fileName = `PACKAGE-${new Date().valueOf()}`
  downloadTargz(data, fileName, 'tar.gz')
})

function downloadTargz (content, fileName, suffixName) {
  const blob = new Blob([content], {
    type: 'application/x-tar',
  })
  const url = window.URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = fileName + '.' + suffixName
  a.click()
}

下载 csv 表格的方式:

export function downloadCsv (content, fileName, suffixName) {
  const csv = '\uFEFF' + content
  const blob = new Blob([csv], {
    type: 'text/plain',
  })
  const url = window.URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = fileName + '.' + suffixName
  a.click()
}

最后,突然还发现 vxe-table 居然自带了表格导入导出功能,很好很强大。

最后

对于 Vue.js 的相关表格库,找了不少调研,最后还是感觉 vxe-table 最合适。它也的确帮我在数据可视化项目中实现了很多需求。

上一篇 下一篇

猜你喜欢

热点阅读