1.使用步骤
1.1 安装(个人建议安装12.2及之后的版本)
npm install handsontable @handsontable/vue
1.2 使用
import { HotTable } from '@handsontable/vue'
<hot-table :settings="hotSettings" class="w-100 h-100"></hot-table>
hotSettings里面配置需要的属性和回调函数
例如:
hotSettings: {
data: [
[1, 2, 3, 4],
[a, b, c, d]
]
}
2.基础配置
2.1 data
data里面有两种形式:
a. 二维数组 [[], []] (里面的每个数组代表一行数据)
b. 对象数组 [{}, {}] (里面的每个对象代表一行数据, 但是这是需要结合columns属性一起使用)
2.2 columns
是一个对象数组([{}, {}]), 里面的每一个对象数据都代表对应的这一列(hot-table的列排列位置就是根据这个)
例如:
{
type: 'numeric', // 限制这一列的值得类型(data配置项中数据第一次渲染的时候不会报错,修改的时候会提示)
readOnly: true, // 这一列只读
data: 'column property' // 如果data配置项中数据是对象数组类型的话,就可以使用这个属性来制定这一列是对应哪个属性
title: 'column title', // 这一列的标题(体现在表头上)
dateFormat: 'YYYY-MM-DD', // 这一列如果type: date(会出现一个日期插件),这一列的选择后的日期的形式就是'2022-11-09'
}
2.3 列宽, 行高
colWidths: 150, // 有多种格式(可以是数字,也可以是字符串('150px'), 也可以是数组([100, 200, ...])给每一列都设置一个宽度,也可以是方法(需要有个返回值return), 也可以是undefined(由modifyColWidth钩子使用))
rowHeights: '150px', // 同上
autoRowSize: true/false, // 给每一列设置一个自适应宽度
autoColumnSize: true/false // 同上
2.4 表头
rowHeaders: true/false/[]/function, // 行表头 true采用默认的表头('1', '2', '3', ...) false禁用表头 []自定义表头([1, 2, 3, 4]) function自定义表头(function(){ return 'AB'})
colHeaders: true/false/[]/function, // 列表头 同上
2.5 hot-table根据宽度横向扩展
stretchH: 'all'/'last'/'none', // all扩展全部列 last只扩展最后一列 none默认不扩展
2.6 viewportColumnRenderingOffset
在网格视口之外呈现的列数
hot-table为了渲染的效率,它采用的默认渲染方式是只渲染可视区域部分的内容,有超出可视区域内容通过横向滚动条来加载,就会出现几个问题(1: 用户体验。横向滚动条会反复横跳;2:预先设置的一些样式不起作用(比如:合并行))
viewportColumnRenderingOffset: 100, // 可以通过设置超出最大列数的值,来让hot-table一次性渲染所有的数据
2.7 设置hot-table的宽高
width: 500, // 可以是数字,方法(需要一个返回值return),CSS Unit
height: 500, // 同上
2.8 mergeCells(合并单元格)
是一个对象数组([{}, {}, ...]),里面的对象包含四个属性:
row: 1, // 合并部分开头的行索引
col: 1, // 合并部分开头的列索引
rowspan: 2, // 合并的行数
colspan: 2, // 合并的列数
2.9 选中拖拽复制
fillHandle: true/false/'vertical'/'horizontal', // 'vertical'启用垂直自动填充 'horizontal'启用横向自动填充
2.10 回调钩子函数
beforeCreateRow(index) {} // index新行索引
新增行之前回调,return false代表取消掉本次新增行操作
afterCreateRow(index, amount) {} // index新行索引 amount新增行数目
添加行后被调用
afterChange(changes, source) {} // changes是一个二维数组([[row, prop, oldValue, newValue]]) row修改单元格所在的行 prop修改单元格列对应的属性 oldValue修改前的值 newValue修改后的值
在一个或多个单元格被更改后触发,当使用编辑器输入值或使用API更改值时,会在任何情况下触发更改
beforeRemoveRow(index, amount, physicalRows) {} // index删除行索引 amount删除行数量 physicalRows从数据源中删除的物理行数组
行删除前回调,return false代表取消掉本次删除行操作
afterOnCellMouseDown(event, value) {} // event点击事件 value单元格对应的位置信息
单元格点击回调(包括左键,右键,滚轮)
2.11 右键功能
contextMenu: false/true/[]/{items: {}} // 给右键配置功能
false 禁用ContextMenu插件(禁止掉右键功能)
true 启用ContextMenu插件(使用默认设置的右键功能)
['row_above', 'row_below'] 修改单个菜单选项('row_above'既是名字也是对应的功能)
{items: {}} 可以自定义每个键
{
items: {
row_above: {
name: '上面插入一行'
}, // 键名是插件原本就有(也有已经对应好的操作)
remove_row: {
name: '移除当前行'
}, // 键名是插件原本就有(也有已经对应好的操作)
insert_above_rows: {
name() {
const insertBox = document.getElementsByClassName('insert_input')[0]
if(insertBox) {
insertBox.addEventListener('keyup', (e) => {
console.log('e', e)
const event = e || window.event
const keyValue = event.which || event.keyCode || event.charCodes
if (keyValue === 13) {
const number = Number(event.target.value)
if (number !== NaN && number >= 0) {
this.alter('insert_row_above', window.clickRowIndex, number) // this指向hot-table alter方法允许你通过在指定位置添加或删除行个列来更改网格结构
const elMenu = document.getElementsByClassName('htMenu')[0]
elMenu.style.display = 'none' // 关闭右键的菜单栏
} else {
this.$message.warning('请输入正确的数字')
}
} // 监听回车事件
})
}
return `在上方插入<input class="insert_input" style="width: 40px; height: 20px;"></input>行`
},
isCommand: false, // 防止后续点击命令关闭菜单
// 自定义时renderer与name的区别
renderer(hot, wrapper, row, col, prop, itemValue){
// 参数里面的hot是指向菜单栏的,所以没办法调用alter方法
}
}, // 键名是自定义的(操作也是自定义的--插入多行)
}
}
2.11 定义某些单元格特殊的配置或逻辑
cells(roww, column, prop) {} // row单元格所在行 column单元格所在列 prop(如果data是二维数组的话,就是与column相同的数字;如果是对象数组的话,就是所在列的属性)
使用案例:
cells() {
const cellProperties = {
readOnly: false
}
return cellProperties
}
cells会出现重复渲染的问题
3. 其它用法
handsontable类似于el-table,对列表可以实现其它操作
3.1 hot-column类似于el-table的el-column
<hot-table :settings="hotSettings">
<hot-column :width="120" title="headerTitle" data="dataColumnProperty"></hot-column>
</hot-table>
这一块比较灵活,它可以单独设置某一些属性,像title、data这种,也可以像hot-table一样一个settings挂在所有的参数配置项,
还是单独设置一部分属性,再使用settings属性,hot-table也可以同样如此操作。
3.2 类似于插槽的操作
要将组件标记为Handsontable渲染器,只需为其添加一个hot-renderer属性,通常作为渲染函数的参数将被注入到渲染组件的$data对象中,有以下几个数据:
row 行索引
col 列索引
prop 如果data是二维数组的话,就是与column相同的数字;如果是对象数组的话,就是所在列的属性
TD 单元格HTML节点
cellProperties 已编辑单元格的cellProperties对象
例子:
<hot-table :settings="hotSettings">
<hot-column>
<div hot-renderer>类似于插槽操作</div>
</hot-column>
</hot-table>
特殊点:使用这种操作,并且想拿到数据(插槽数据),就不能直接将节点挂载上去,需要如下操作:
1. 定义一个组件实例
const CustomRenderer = {
template: '<div>{{ row }} {{ col }} {{ value }}</div>'
}
2. 将这个组件实例在components里声明
components: {
CustomRenderer
}
3. 使用
<hot-table :settings="hotSettings">
<hot-column>
<CustomRenderer hot-renderer>类似于插槽操作</CustomRenderer>
</hot-column>
</hot-table>
插槽数据会被自动注入到渲染组件的$data对象中
3.3 还可以通过配置项columns实现类似于插槽的操作
columns: [
{
renderer(instance, td, row, col, prop, value) {
const img = document.createElement('img')
img.src = value
td.innerText = ''
td.appendChild(img)
return td
}
}
]
4. 注意点
4.1 表头数据只能进行列合并,不能进行行合并
4.2 获取hot-table数据this.$refs.hotTable.hotInstance.getData() // 这个方法拿到的只是每个单元格里面的数据
所以这儿如果使用的是简单类型的表格,并且数据还是对象数组类型的,可以采用这种方法:
采用事先定义好的columns,然后使用this.$set(this.hotSettings, 'data', this.dataList)就可以在dataList中拿到实时变化的数据
4.3 如果一个单元格要区分单击事件和双击事件:
可以使用afterOnCellMouseDown(单元格点击回调)回调方法,再利用 节流 方法来分别执行
例子:
hotSettings: {
afterOnCellMouseDown: (event, val) => {
// event.button 判断是左键点击
if (event.button === 0) {
this.indexCount += 1 // indexCount是data中声明的变量,用来记录点击了几次
if (this.indexCount % 2 === 0) {
// 点击次数为双数
this.preTime = this.newTime // preTime是data中声明的变量,用来记录上一次点击的时间
this.newTime = Date.now() // preTime是data中声明的变量,用来记录当前点击的时间
} else {
this.newTime = Date.now()
}
// event事件 val坐标位置
console.log('val', val)
this.debouncedTip(event, val) // debouncedTip是data中声明的变量,用来绑定节流函数返回的结果(闭包)
}
}, // 单元格点击回调
}
在created中:
const throttle = (fn, wait) => {
let timer = null
return (event, val) => {
if (!this.preTime) {
this.preTime = 0
}
console.log('nowTime - preTime', this.newTime, this.preTime)
if (!timer) {
timer = setTimeout(() => {
if (this.newTime - this.preTime > wait) {
fn(event, val)
} // wait是在debouncedTip设置的节流时间,如果this.newTime - this.preTime < wait则判断定位单击
timer = null
}, 300)
}
}
} // 节流函数
this.debouncedTip = throttle((event, val) => {
// 要执行的操作
}, 300)