数据可视化前端项目开发 - 表格
在数据可视化项目中,除了图表,用到最多的就是表格了。
一开始,项目中使用了 element 自带的 table 组件,后来发现 element 的 table 对于 footer 的支持很少,而且可扩展性也不够。只适合用于一些简单的数据展示。后来我改用了 vxe-table。
el-table vs vxe-table
两者的写法非常类似,大概率是 vxe-table 的作者借鉴了 el-table 来进行实现的。但还是有些不同之处。
- vxe-table 对于 footer 的支持更好,el-table 只有对 header 和 body 的插槽,而 vxe-table 提供了 footer 的插槽。
- 由于项目需要应用到移动端,vxe-table 在锁列、锁头、锁尾功能上兼容性更好,而 el-table 在某些机型上无法做到固定表格列。
- 可扩展性更强大,el-table 只是组件库中的一个组件,而 vxe-table 主要的做 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 最合适。它也的确帮我在数据可视化项目中实现了很多需求。