handsontable的简单使用

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)

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容