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)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容