本文来自 @visactor/VTable 用户投稿,该用户获得VisActor奖励的精美礼品一份!
我们项目采用了 AngularJS 作为主要的技术框架,团队自行开发的表格列表及扩展功能,存在性能问题,而且扩展能力差,难以实现丰富表格功能。
我们调研各种开源解决方案,希望解决如下问题:
- 高性能,期望在十万数据级别,能够快速渲染,交互无卡顿,无白屏。
- 扩展能力强,在单元格渲染上支持一定程度的自定义。同时提供默认可用的丰富可视化效果
- 支持多维数据分析与展示
- 可以很方便的同目前技术框架(AngularJS)融合。
同时希望新表格的应用能做到如下开发体验:
- 功能一致性: 替换后的表格与原表格功能基本一致。
- 交互体验: 交互功能和使用体验得到显著提升。
我们调研了多个开源表格库,从实现方式上整体分为两类:
- 基于dom的表格。优点是自定义能力强,可以基于dom节点做灵活的自定义渲染和扩展;缺点是大数据渲染场景下,性能劣化明显。
- 基于Canvas的表格。优点是具有非常高的渲染性能;缺点是扩展能力普遍较弱,需要使用原生Canvas 绘图api进行自定义渲染。
为彻底解决我们的性能问题,我们决定采用Canvas 表格,在为数不多的Canvas表格中,我们最终选择了VTable,原因如下:
-
超高的性能表现,支持百万级数据的渲染与流程交互。
- 较好的扩展能力,支持JSX自定义单元格;React-VTable 支持自定义React组件的接入(https://www.visactor.io/vtable/guide/Developer\_Ecology/react-custom-component)
- 和VChart 完美融合,支持在一张画布上渲染成百上千个图表,极大提升可视化能力及渲染性能(单元格显示图表)
另外VTable虽然是开源项目,但是提供了多种沟通渠道,可以直接和开发者进行需求沟通,快速响应,确保我们项目的上线周期。
由于VTable默认功能比较完善,整个升级过程中基本没有技术上的障碍,下面将我们开发过程中自定义的几个点,简单总结一下。
全局基础配置
项目中使用表格的场景很多,但是基本风格一致,我们定义了一份全局配置,用来规定项目整体表格的一个基础风格,每个列表可以单独写个性化的配置,通过复制基础配置信息产生一个新的配置。
基础配置大致如下:
vtableOption = {
columns: [],
records: [],
frozenColCount: 5, //冻结列数
//rightFrozenColCount: 右侧冻结列数,默认为 0。
//bottomFrozenRowCount: 底部冻结行,默认为 0。
//autoWrapText: boolean:全局配置是否允许自动换行,默认值为false。如果设置为true,则当前列的单元格会根据其内容自动换行。
showFrozenIcon: false, // 是否显示固定列图标
widthMode: 'standard', //表格列宽度的计算模式,可以是 'standard'(标准模式)、'adaptive'(自适应容器宽度模式)或 'autoWidth'(自动宽度模式),默认为 'standard'。
dragHeaderMode: 'none', //拖拽表头换位置 'all' 所有表头均可换位 'none' 不可换位 'column' 只有换列表头可换位 'row' 只有换行表头可换位
// heightMode: 'autoHeight', //'standard'(标准模式)、'adaptive'(自适应容器高度模式)或 'autoHeight'(自动行高模式),默认为 'standard'。
// autoWrapText: true, //是否自动换行 heightMode需要设置为autoHeight
emptyTip: {
text: '数据为空,请重新设置搜索条件',
icon: {
width: 205,
height: 113,
image: '../assets/images/Group.png',
},
},
tooltip: {
//省略提示
isShowOverflowTextTooltip: true,
},
keyboardOptions: {
copySelected: true,
},
hover: {
highlightMode: 'cross', //四种hover交互模式,分别为:十字交叉('cross')、整('column')、整行('row')和单个单元格('cell')
},
menu: {
contextMenuItems: ['复制'],
},
theme: VTable.themes.DEFAULT.extends({
headerStyle: {
fontSize: 14,
color: '#666',
fontWeight: 'normal',
},
bodyStyle: {
color: '#666',
},
scrollStyle: {
visible: 'always',
},
checkboxStyle: {
size: 18,
},
}),
select: {
disableSelect: true,
},
}
下拉 菜单自定义
VTable默认提供了下拉菜单。
但是个人感觉不够灵活,得益于VTable提供了灵活的扩展能力,于是自己写了一个自定义下拉菜单。
首先自定义下拉菜单的icon,核心代码如下:
{
field: 'dropdown\_menu',
title: '操作',
width: 60,
disableHeaderSelect: true,
disableSelect: true,
disableColumnResize: true,
customLayout: (args) => {
const { table, row, col, rect } = args
var _rect
var _ref = (_rect = rect) !== null && _rect !== void 0 ? _rect : table.getCellRect(col, row)
const { height, width } = _ref
const container = new VTable.CustomLayout.Group({
height,
width,
display: 'flex',
flexDirection: 'row',
flexWrap: 'nowrap',
alignItems: 'center',
justifyContent: 'center',
})
const containerCenter = new VTable.CustomLayout.Group({
height: 20,
width: 20,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
})
container.add(containerCenter)
const bloggerName = new VTable.CustomLayout.Image({
height: 16,
width: 16,
image: '../../../../assets/images/dropdown.png',
})
containerCenter.add(bloggerName)
return {
rootContainer: container,
}
},
}
上面代码中,图标是通过自定义渲染customLayout方式实现的(好像通过VTable本身icon能力实现会比较简单)。
最后通过点击单元格,获取单元格位置的方式,将组件定位到相应位置,进而展现整个菜单,菜单是用html实现的,这里就不列详细代码了。
tableInstance.on('click\_cell', (args) => {
const { col, row, field } = args
let currentRowData = tableInstance.getRecordByCell(col, row) //获取当前行数据
if (currentRowData && field === 'dropdown\_menu') {
let rect = tableInstance.getCellRelativeRect(col, row)
//获取单元格坐标,展示下拉菜单
// 显示 自定义的菜单
})
最终效果如下:
右键复制单元格信息功能
右键复制单元格信息功能,与官网上demo实现的功能有所区别(https://www.visactor.io/vtable/demo/interaction/context-menu)。
官网demo没有禁用单元格选中,需要选中单元格才能复制单元格内的信息,好处是可以拖拽选中多个单元格进行复制,不灵活的地方是,单个单元格也必须要先单击选中,才能右键复制内容。我的项目禁用了单元格选中功能,通过监听右键点击事件,直接进行单元格内容的复制,不能拖拽选中多个单元格进行复制。应该可以有兼容两种方式的处理方式,个人觉得处理交互太麻烦了,就暂时简单粗暴的处理了。
基本思路是通过VTable实例获取要复制的数据。
let copyData = tableInstance.getCellValue(col, row)
然后将数据写入剪贴板,如果浏览器支持clipboard接口,直接写入:
if (navigator.clipboard && window.isSecureContext) {
// navigator clipboard 向剪贴板写文本
return navigator.clipboard.writeText(copyData)
}
如果不支持,则通过创建隐藏textArea的方式,将内容先写入textArea,
let textArea = document.createElement('textarea')
textArea.value = copyData
然后通过 copy
命令写入剪贴板。
textArea.focus()
textArea.select()
return new Promise((res, rej) => {
// 执行复制命令并移除文本框
document.execCommand('copy') ? res() : rej()
textArea.remove()
})
完整代码如下:
tableInstance.on('dropdown\_menu\_click', (args) => {
const { col, row } = args
if (args.menuKey === '复制') {
let copyData = tableInstance.getCellValue(col, row)
if (navigator.clipboard && window.isSecureContext) {
// navigator clipboard 向剪贴板写文本
return navigator.clipboard.writeText(copyData)
} else {
// 创建text area
let textArea = document.createElement('textarea')
textArea.value = copyData
// 使text area不在viewport,同时设置不可见
textArea.style.position = 'absolute'
textArea.style.opacity = 0
textArea.style.left = '-999999px'
textArea.style.top = '-999999px'
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
return new Promise((res, rej) => {
// 执行复制命令并移除文本框
document.execCommand('copy') ? res() : rej()
textArea.remove()
})
}
}
})
最终使用VTable 还原了原有表格功能:
基本表格样式:
下拉菜单:
同时性能得到了极大提升:
欢迎更多使用VisActor的用户联系我们,给我们投稿,交流业务场景,提建议,贡献代码,谢谢大家!
官方网站:www.visactor.io/
Discord:discord.gg/3wPyxVyH6m
Twiter:twitter.com/xuanhun1
github:github.com/VisActor