# Handsontable介绍

Handsontable是一个前端excel页面化插件 Handsontable官网 (opens new window)

# vue3 npm引入

npm install handsontable @handsontable/vue3
1

# 基础组件

<hot-table :data="data" :rowHeaders="true" :colHeaders="true"></hot-table>
1

# 配置项

# 基础配置

# data

表格内容,必填项

# 列相关配置

# colHeaders

配置列表头

可选类型:boolean|string[]|functon

//为boolean时默认为[A,B,C...],与Excel类似
colHeaders: true,
1
2

1.png

//为string[]时会显示数组中的数据
colHeaders: ['ID', 'Full name', 'Position','Country', 'City', 'Address']
1
2

2.png

//为function时每一列表头会根据该方法生成内容
colHeaders(index) {
    return 'Col ' + (index + 1);
},
1
2
3
4

3.png

# nestedHeaders

配置多层级自定义表头

可选类型:array

nestedHeaders: [
    ['A', { label: 'B', colspan: 8 }, 'C'],
    ['D', { label: 'E', colspan: 4 }, { label: 'F', colspan: 4 }, 'G'],
    ['H', { label: 'I', colspan: 2 }, { label: 'J', colspan: 2 }, { label: 'K', colspan: 2 }, { label: 'L', colspan: 2 }, 'M'],
    ['N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W']
],
1
2
3
4
5
6

多层级自定义表头

# hiddenColumns

隐藏列

可选类型:boolean|object

//设置为boolean时所有列都生效
hiddenColumns: true,
1
2
//如下配置为索引为3,5,9的列隐藏
hiddenColumns: {
    // specify columns hidden by default
    columns: [3, 5, 9]
}
1
2
3
4
5

# manualColumnMove

列数据支持拖拽移动

可选类型:boolean

manualColumnMove: true,
1

如果你配置了colHeaders并是一个string[],那么你只能拖拽数组里面label所对应的列

# fixedColumnsStart

配置表格列冻结

可选类型:number

//此配置是表格索引为1之前的列都冻结
fixedColumnsStart: 1,
1
2

# colWidths

设置列宽

可选类型:number|number[]|function

//列宽统一设置为100px
colWidths: 100,
1
2
//初始化前四列列宽为50px,100px,200px,400px,后面的列宽为默认值
colWidths: [50, 100, 200, 400],
1
2
//列宽为方法返回值
colWidths(index) {
    return (index + 1) * 40;
},
1
2
3
4

# manualColumnResize

列宽是否支持用户手动调整

可选类型:boolean

//配置后用户可鼠标拖拽跳转列宽
manualColumnResize: true,
1
2

# stretchH

列宽自适应方式

stype:string

//所有列都自动拉伸
stretchH: 'all', // 'none'为默认值
1
2

5.png

//最后一列拉伸
stretchH: 'last',
1
2

6.png

# columnSummary

整列计算

可选类型:[]

columnSummary: [
    {
      sourceColumn: 0,
      type: 'sum', //使用求和函数
      destinationRow: 3,
      destinationColumn: 0,
      forceNumeric: true
    },
    {
      sourceColumn: 1,
      type: 'min', //使用取最小值函数
      destinationRow: 3,
      destinationColumn: 1
    },
    {
      sourceColumn: 2,
      type: 'max', //使用取最大值函数
      destinationRow: 3,
      destinationColumn: 2
    },
    {
      sourceColumn: 3,
      type: 'count', //使用取当前列行数函数
      destinationRow: 3,
      destinationColumn: 3
    },
    {
      sourceColumn: 4,
      type: 'average', //使用求平均值函数
      destinationRow: 3,
      destinationColumn: 4
    }
  ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

7.png

列控制器 <font id="dropdown_menu"> </font>

可选类型:boolean|string[]

//开启默认的控制器
dropdownMenu: true,
1
2

8.png

//根据关键词自定义配置,其中'---------'为分割
dropdownMenu: [
    'remove_col',
    '---------',
    'make_read_only',
    '---------',
    'alignment'
]
1
2
3
4
5
6
7
8

9.png

可选关键字

键名 行为
row_above (opens new window) 上面插入一行
row_below (opens new window) 下面插入一行
col_left (opens new window) 插入到左边一列
col_right (opens new window) 插入一列
--------- (opens new window) 菜单项分隔符
remove_row (opens new window) 删除选中的行
clear_column (opens new window) 删除所选列的数据
undo (opens new window) 复原上次的动作 (UndoRedo (opens new window))
redo (opens new window) 重做最后的动作 (UndoRedo (opens new window))
make_read_only (opens new window) 将选中的单元格只读
alignment (opens new window) 文本对齐
cut (opens new window) 裁剪选中的内容到剪贴板(CopyPaste (opens new window))
copy (opens new window) 复制选中的内容到剪贴板(CopyPaste (opens new window))
copy_with_column_headers (opens new window) 带表头复制 (CopyPaste (opens new window))
copy_with_column_group_headers (opens new window) 带多级表头复制 (CopyPaste (opens new window), NestedHeaders (opens new window))
copy_column_headers_only (opens new window) 复制所选内容和他们离最近的表头 (CopyPaste (opens new window))
freeze_column (opens new window) 冻结所选列 (ManualColumnFreeze (opens new window))
unfreeze_column (opens new window) 解冻所选列 (ManualColumnFreeze (opens new window))
borders (opens new window) 所选单元格加边框 (CustomBorders (opens new window))
commentsAddEdit (opens new window) 添加或编辑注释 (Comments (opens new window))
commentsRemove (opens new window) 删除注释 (Comments (opens new window))
commentsReadOnly (opens new window) 设置备注为只读 (Comments (opens new window))
mergeCells (opens new window) 合并或拆分所选单元格 (MergeCells (opens new window))
add_child (opens new window) 插入一个子行 (NestedRows (opens new window))
detach_from_parent (opens new window) 从其上级分离所选行 (NestedRows (opens new window))
hidden_columns_hide (opens new window) 隐藏所选列 (HiddenColumns (opens new window))
hidden_columns_show (opens new window) 显示隐藏列 (HiddenColumns (opens new window))
hidden_rows_hide (opens new window) 隐藏所选行 (HiddenRows (opens new window))
hidden_rows_show (opens new window) 显示隐藏行 (HiddenRows (opens new window))
filter_by_condition (opens new window) 添加一个筛选条件 (Filters (opens new window))
filter_by_condition2 (opens new window) 添加第二个筛选条件 (Filters (opens new window))
filter_operators (opens new window) 选择一个筛选参数 (Filters (opens new window))
filter_by_value (opens new window) 添加一个筛选项 (Filters (opens new window))
filter_action_bar (opens new window) 应用配置的筛选器 (Filters (opens new window))

# 行相关配置

# rowHeaders

配置列表头

可选类型:boolean|string[]|functon

//为boolean时默认为[A,B,C...],与Excel类似
rowHeaders: true,
1
2
//为string[]时会显示数组中的数据
rowHeaders: ['ID', 'Full name', 'Position','Country', 'City', 'Address']
1
2
//为function时每一行表头会根据该方法生成内容
rowHeaders(index) {
    return 'Col ' + (index + 1);
},
1
2
3
4

# parent-child

该效果需要在data中设置

其格式如下

[
    {
        category: '父级标签名',
        artist: null,
        title: null,
        label: null,
        //在此处配置子级数据
        __children:[
            {
            },
            {
            }
        ]
    }
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

10.png

# hiddenRows

隐藏行

可选类型:boolean|object

//设置为boolean时所有行都生效
hiddenRows: true,
1
2
//如下配置为索引为3,5,9的行隐藏
hiddenRows: {
    // specify columns hidden by default
    columns: [3, 5, 9]
}
1
2
3
4
5

# manualRowMove

行数据支持拖拽移动

可选类型:boolean

manualRowMove: true,
1

# fixedRowsTop

配置表格行冻结

可选类型:number

fixedRowsTop: 2, //表格索引为2之前的行都冻结
1

# fixedRowsBottom

配置表格行冻结

可选类型:number

fixedRowsBottom: 2, //表格索引为2之后的行都冻结
1

# rowHeights

配置表格行高

可选类型:number|number[]|function

//行高统一设置为100px
rowHeights: 100,
1
2
//初始化前四行行高为50px,100px,200px,400px,后面的行高为默认值
rowHeights: [50, 100, 200, 400],
1
2
//行高为方法返回值
rowHeights(index) {
    return (index + 1) * 40;
},
1
2
3
4

# columnSorting

配置行排序

可选类型:boolean|object

//所有列默认开启排序(此时还没有排序,需要用户点击表头手动排序)
columnSorting: true,
1
2
columnSorting: {
    //是否开启排序
    sortEmptyCells: true,
    //排序初始化
    initialConfig: {
        column: 2, //按第几列属性排序
        sortOrder: 'asc' //设置为升序 'desc'为降序
    }
},
1
2
3
4
5
6
7
8
9

# trimRows

裁剪指定行数据

可选类型:number[]

 //索引为1,2,5项不展示 
 data: [
    ['A1', 'B1', 'C1', 'D1'],
    ['A2', 'B2', 'C2', 'D2'],
    ['A3', 'B3', 'C3', 'D3'],
    ['A4', 'B4', 'C4', 'D4'],
    ['A5', 'B5', 'C5', 'D5'],
    ['A6', 'B6', 'C6', 'D6'],
    ['A7', 'B7', 'C7', 'D7'],
    ['A8', 'B8', 'C8', 'D8'],
    ['A9', 'B9', 'C9', 'D9'],
  ],
  trimRows: [1, 2, 5],
1
2
3
4
5
6
7
8
9
10
11
12
13

11.png

# beforeChange

最后一行输入内容可添加新的一行

beforeChange(changes) {
    const instance = hot;
    const columns = instance.countCols();
    const rowColumnSeen = {};
    const rowsToFill = {};

    for (let i = 0; i < changes.length; i++) {
        // if oldVal is empty
        if (changes[i][2] === null && changes[i][3] !== null) {
            if (isEmptyRow(instance, changes[i][0])) {
                // add this row/col combination to the cache so it will not be overwritten by the template
                rowColumnSeen[changes[i][0] + '/' + changes[i][1]] = true;
                rowsToFill[changes[i][0]] = true;
            }
        }
    }

    for (var r in rowsToFill) {
        if (rowsToFill.hasOwnProperty(r)) {
            for (let c = 0; c < columns; c++) {
                // if it is not provided by user in this change set, take the value from the template
                if (!rowColumnSeen[r + '/' + c]) {
                    changes.push([r, c, null, templateValues[c]]);
                }
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 单元格特性

# Clipboard

单元格内容与剪贴板交互

1.通过键盘调用剪贴板

  • Ctrl/Cmd + C - 复制所选区域内容
  • Ctrl/Cmd + X - 剪切所选区域内容

2.通过Context menu调用剪贴板

  • Copy - 调用复制
  • Cut - 调用剪切

3.通过api手动调用

document.execCommand('copy')
document.execCommand('cut')
1
2

# Selection

选中区域

1.设置选中模式

selectionMode: 'multiple', // 'single', 'range' or 'multiple',
1
设置 描述
'single' 允许用户一次只选择一个单元格
'range' 允许用户选择一个范围的单元格
'multiple' 允许用户选择多个范围的单元格

2.获取用户选中内容

//hot为excel实例
getButton.addEventListener('click', event => {
  //获取所选区域
  const selected = hot.getSelected() || [];
  const data = [];
  for (let i = 0; i < selected.length; i += 1) {
    const item = selected[i];
    //getData方法获取指定单元格内容
    data.push(hot.getData(...item));
  }
  output.innerText = JSON.stringify(data);
});
1
2
3
4
5
6
7
8
9
10
11
12

3.快捷键

Windows macOS Action Excel Sheets
Ctrl + A Cmd + A 选择所有单元格和标题
Ctrl + Shift + Cmd + Shift + 向上延长当前选择第一个单元格的行
Ctrl + Shift + Cmd + Shift + 向下延长当前选择第一个单元格的行
Ctrl + Shift + Cmd + Shift + 向左延长当前选择第一个单元格的列
Ctrl + Shift + Cmd + Shift + 向右延长当前选择第一个单元格的列
Shift + Arrow keys Shift + Arrow keys 根据对应方向单个延长
Shift + Home Shift + Home 扩展选择第一个当前行上单元格中
Shift + End Shift + End 扩展选择当前行上单元格中
Shift + Page Up Shift + Page Up Ctrl + Shift +
Shift + Page Down Shift + Page Down Ctrl + Shift +
Ctrl + Enter Cmd + Enter 所选范围的单元格充满活动单元格的值
Delete Delete 清除所选单元格
Backspace Backspace 清除高亮(所选单元格中最粗边框那个)单元格

Merge cells

合并单元格

1.用户操作快捷键

Windows macOS Action Excel Sheets
Ctrl + M Ctrl + M 合并所选单元格(单元格值会取左上角单元格的值)

2.初始化中配置

  mergeCells: [
    { row: 1, col: 1, rowspan: 3, colspan: 3 },
    { row: 3, col: 4, rowspan: 2, colspan: 2 },
    { row: 5, col: 6, rowspan: 3, colspan: 3 }
  ],
1
2
3
4
5

12.png

# Conditional formatting

设置表格样式

//设置文字样式
function firstRowRenderer(instance, td, row, col, prop, value, cellProperties) {
  Handsontable.renderers.TextRenderer.apply(this, arguments);
  td.style.fontWeight = 'bold';
  td.style.color = 'green';
  td.style.background = '#CEC';
}
1
2
3
4
5
6
7

2.设置边框样式

# Text alignment

通过类目设置文字位置

//设置整体文字样式
className: 'htCenter',
//单独设置文字样式
cell: [
    { row: 0, col: 0, className: 'htRight' },
    { row: 1, col: 1, className: 'htLeft htMiddle' },
    { row: 3, col: 4, className: 'htLeft htBottom' }
],
1
2
3
4
5
6
7
8

支持的类名

  • Horizontal: htLeft, htCenter, htRight, htJustify,
  • Vertical: htTop, htMiddle, htBottom.

# Disabled cells

单元格禁用模式(readOnly|editor)

Read-only cell readOnly: true Non-editable cell editor: false
有一个附加的类名 (htDimmed) 没有这个附加的类目
只读模式不支持复制单元格内容 支持
不支持拖拽补全 支持拖拽补全
不支持使用 populateFromArray() (opens new window)api修改 支持使用 populateFromArray() (opens new window)修改
//初始化配置方式设置
columns: [
    //设置属性值为car的一列内容为只读
    {
        data: 'car',
        readOnly: true
    },
    {
        data: 'year'
    },
    {
        data: 'chassis'
    },
    {
        data: 'bumper'
    }
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//通过updateSettings方法设置
hot.updateSettings({
  cells(row, col) {
    const cellProperties = {};

    if (hot.getData()[row][col] === 'Nissan') {
      cellProperties.readOnly = true;
    }
    return cellProperties;
  }
});
1
2
3
4
5
6
7
8
9
10
11

# Comments

设置tooltip

//全局设置
const hot = new Handsontable(container, {
  data: [
    ['A1', 'B1', 'C1'],
    ['A2', 'B2', 'C2'],
  ],
  comments: true
});
1
2
3
4
5
6
7
8
//局部设置
cell: [
    { row: 1, col: 1, comment: { value: 'Some comment' } },
    { row: 2, col: 2, comment: { value: 'More comments' } }
],
1
2
3
4
5

效果展示

# Autofill values

拖拽新增行/列

可选类型:boolean|object

fillHandle: true, // possible values: true, false, "horizontal", "vertical",
1
//垂直方向开启拖拽新增
fillHandle: {
    direction: 'vertical',
    autoInsertRow: true
},
1
2
3
4
5

# Formatting cells

格式化单元格样式

1.设置单元格背景色

//设置指定单元格背景色
td.custom-cell {
    color: #fff;
    background-color: #37bc6c;
}
//设置表头样式
.custom-table thead th:nth-child(even),
.custom-table tbody tr:nth-child(odd) th {
    background-color: #d7f1e1;
}
1
2
3
4
5
6
7
8
9
10
  	//给表格添加类目
	className: 'custom-table',
    //给指定位置单元格添加类目
    cell: [
        {
            row: 0,
            col: 0,
            className: 'custom-cell',
        },
    ],
1
2
3
4
5
6
7
8
9
10

2.设置边框样式

customBorders: [
    {
        range: {
            from: {
                row: 1,
                col: 1
            },
            to: {
                row: 3,
                col: 4
            }
        },
        top: {
            width: 2,
            color: '#5292F7'
        },
        bottom: {
            width: 2,
            color: 'red'
        },
        start: {
            width: 2,
            color: 'orange'
        },
        end: {
            width: 2,
            color: 'magenta'
        }
    },
    {
        row: 2,
        col: 2,
        start: {
            width: 2,
            color: 'red'
        },
        end: {
            width: 1,
            color: 'green'
        }
    }
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

3.自定义表格支持的所有配置项

# 单元格方法

# Cell functions

const container = document.querySelector('#container');
const hot = new Handsontable(container, {
  columns: [{
    type: 'numeric' //renderer、editor等配置选项均可在此配置
  }]
});
//获取坐标[0,0]单元格
const cellProperties = hot.getCellMeta(0, 0);

cellProperties.renderer; // 获取单元格renderer属性
cellProperties.editor; // 获取单元格editor属性
cellProperties.validator; // 获取单元格合法性
cellProperties.type; // 获取单元格类型
1
2
3
4
5
6
7
8
9
10
11
12
13

单元格相关属性也可通过api获取

相关api

# Cell renderer

当你创建一个渲染器,可以为其取一个别名,后续可以通过别名引用这个渲染器的功能。Handsontable定义了默认10别名:

  • autocomplete for Handsontable.renderers.AutocompleteRenderer
  • base for Handsontable.renderers.BaseRenderer
  • checkbox for Handsontable.renderers.CheckboxRenderer
  • date for Handsontable.renderers.DateRenderer
  • dropdown for Handsontable.renderers.DropdownRenderer
  • html for Handsontable.renderers.HtmlRenderer
  • numeric for Handsontable.renderers.NumericRenderer
  • password for Handsontable.renderers.PasswordRenderer
  • text for Handsontable.renderers.TextRenderer
  • time for Handsontable.renderers.TimeRenderer

除了使用默认别名外,还可以写函数创建自定义渲染器

此为还能为自定义渲染器创建别名

//asterixDecoratorRenderer为方法创建一个别名为asterix
Handsontable.renderers.registerRenderer('asterix', asterixDecoratorRenderer);
1
2

如何如添加渲染器?

const container = document.querySelector('#example4');
const hot = new Handsontable(container, {
  data,
  colWidths: [200, 200, 200, 80],
  colHeaders: ['Title', 'Description', 'Comments', 'Cover'],
  height: 'auto',
  columns: [
    //使用默认别名
    { data: 'title', renderer: 'html' },
    //使用上个代码块创建的自定义别名
    { data: 'description', renderer: 'asterix' },
    //使用自定义函数
    { data: 'comments', renderer: safeHtmlRenderer },
    //使用自定义函数
    { data: 'cover', renderer: coverRenderer }
  ],
  licenseKey: 'non-commercial-and-evaluation'
});
//自定义渲染器
function safeHtmlRenderer(instance, td, row, col, prop, value, cellProperties) {
  // WARNING: Be sure you only allow certain HTML tags to avoid XSS threats.
  // Sanitize the "value" before passing it to the innerHTML property.
  td.innerHTML = value;
}
//自定义渲染器
function coverRenderer(instance, td, row, col, prop, value, cellProperties) {
  const img = document.createElement('img');
  img.src = value;
  img.addEventListener('mousedown', event => {
    event.preventDefault();
  });
  td.innerText = '';
  td.appendChild(img);
  return td;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# Cell editor

EditorManager是一个负责处理Handontable中所有可用编辑器的类。如果Handontable需要与编辑器交互,它将使用EditorManager对象。在第一次调用Handontable()构造函数后,EditorManager对象在运行的init()方法中实例化。EditorManager对象的引用在Handontable实例中是私有的,无法被访问。但是,有一些方法可以更改EditorManager的默认行为

EditorManager (opens new window) 的四个主要作用

  • 为激活单元格选择适当的编辑器
  • 准备编辑器显示
  • 打开编辑器
  • 关闭编辑器
//方法扩展
const CustomEditor = Handsontable.editors.BaseEditor.prototype.extend();

// This won't alter BaseEditor.prototype.beginEditing()
CustomEditor.prototype.beginEditing = function() {};
1
2
3
4
5
//例子
//拦截set和get
class CalendarEditor extends TextEditor {
  constructor(hotInstance) {
    super(hotInstance);
  }

  getValue() {
    // returns currently selected date, for example "2023/09/15"
    return calendar.getDate();
  }

  setValue() {
    // highlights given date on calendar
    calendar.highlightDate(newValue);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Cell validator

使用预定义或自定义规则验证用户添加或更改的数据。验证有助于确保数据与预期格式相匹配。

单元格校验同样有提前预设的别名 参考Cell renderer

  • autocomplete for Handsontable.validators.AutocompleteValidator
  • date for Handsontable.validators.DateValidator
  • dropdown for Handsontable.validators.DropdownValidator
  • numeric for Handsontable.validators.NumericValidator
  • time for Handsontable.validators.TimeValidator
//注册校验别名
Handsontable.validators.registerValidator('credit-card', creditCardValidator);
1
2
//为指定列添加校验
const container = document.querySelector('#container')
const hot = new Handsontable(container, {
  columns: [{
    validator: 'credit-card'
  }]
});
1
2
3
4
5
6
7

# 单元格类型

# Cell type

使用Handontable的内置单元格类型,如自动完成、日期、时间等,在单元格渲染器、编辑器和验证器之间实现一致的UI

内置别名 参考Cell renderer

//注册单元格类型
Handsontable.cellTypes.registerCellType('my.custom', {
  editor: MyEditor,
  renderer: customRenderer,
  validator: customValidator,
  // You can add additional options to the cell type
  // based on Handsontable settings
  className: 'my-cell',
  allowInvalid: true,
  // Or you can add custom properties which
  // will be accessible in `cellProperties`
  myCustomCellState: 'complete',
});
1
2
3
4
5
6
7
8
9
10
11
12
13
//使用别名
const hot = new Handsontable(container, {
  columns: [{
    type: 'my.custom'
  }]
});
1
2
3
4
5
6

# Numeric cell type

numeric类型

Handontable中的默认单元格类型为文本。文本单元格的数据被处理为字符串类型,该字符串类型对应于文本编辑器的内部<textarea>元素的值。但是,在许多情况下,您需要将单元格值视为数字类型。数字单元格类型允许您很好地格式化显示的数字并对其进行正确排序。

//整个表格设置
type: 'numeric',
//某列单独设置
columns: [
  {
    type: 'numeric',
  },
]
//某个单元格单独设置
cell: [
  {
    row: 0,
    col: 0,
    type: 'numeric',
  }
],
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

numeric类型支持排序

# Date cell type

date类型

使用日期单元格类型可以显示、格式化和验证日期值。使用交互式弹出式编辑器选择日期。

//设置日期类型配置如下
dateFormat: 'YYYY-MM-DD', //日期处理格式
correctFormat: true, //开启
defaultDate: '01/01/1900', //默认日期(非必填)
datePickerConfig: {
	// First day of the week (0: Sunday, 1: Monday, etc)
    firstDay: 0,
    showWeekNumber: true,
    numberOfMonths: 3,
    disableDayFn(date) {
    	// Disable Sunday and Saturday
        return date.getDay() === 0 || date.getDay() === 6;
    },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Time cell type

time类型

使用时间单元类型可以将值显示、格式化和验证为时间。时间单元类型使用Moment.js作为时间格式化程序。

//该功能依赖项
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script>
1
2
//配置方式
columns: [
    {
      type: 'time',
      //格式化规则
      timeFormat: 'h:mm:ss a',
      //如果启用correctFormat配置选项,则会自动格式化值以匹配所需的时间格式。
      correctFormat: true
    },
  ]
1
2
3
4
5
6
7
8
9
10

# Checkbox cell type

checkbox类型

这是默认的使用场景,其中列数据具有true或false值,并且我们只想显示复选框。

columns: [
    {
        data: 'available',
        type: 'checkbox'
    }
],
1
2
3
4
5
6

# Select cell type

select类型

使用select单元格类型来收集具有HTML<select>元素的用户输入,该元素可创建多项目下拉列表。

columns: [
    {},
    {
        editor: 'select',
        selectOptions: ['Kia', 'Nissan', 'Toyota', 'Honda']
    },
    {}
],
1
2
3
4
5
6
7
8

dropdown类型

通过使用下拉单元格类型,收集具有可搜索选项列表的用户输入。

columns: [
    {},
    { type: 'numeric' },
    {
        type: 'dropdown',
        source: ['yellow', 'red', 'orange', 'green', 'blue', 'gray', 'black', 'white']
    },
    {
        type: 'dropdown',
        source: ['yellow', 'red', 'orange', 'green', 'blue', 'gray', 'black', 'white']
    }
],
1
2
3
4
5
6
7
8
9
10
11
12

# Autocomplete cell type

autocomplete类型

通过使用自动完成单元格类型,收集具有选项列表的用户输入。

  columns: [
    {
      type: 'autocomplete',
      source: ['BMW', 'Chrysler', 'Nissan', 'Suzuki', 'Toyota', 'Volvo'],
      strict: false
    },
    { type: 'numeric' },
    {
      type: 'autocomplete',
      source: colors,
      strict: false,
      visibleRows: 4
    },
    {
      type: 'autocomplete',
      source: colors,
      strict: false,
      trimDropdown: false
    }
  ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Password cell type

password类型

使用密码单元格类型,通过将输入的字符呈现为符号来屏蔽机密值。

  columns: [
    { data: 'id' },
    { data: 'name.first' },
    { data: 'name.last' },
    { data: 'password', type: 'password' }
  ]
1
2
3
4
5
6

//可配置加密图标长度
{ data: 'password', type: 'password', hashLength: 10 }
//可配置加密图标样式
{ data: 'password', type: 'password', hashSymbol: '&#9632;' }
1
2
3
4

# Handsontable cell type

handsontable类型

使用可手持单元格类型,在弹出窗口中添加电子表格编辑器。

没看出来有啥用

# 单元格公式

# Formula calculation

使用强大的计算引擎对单元格的值执行计算,该引擎可处理380+个函数、自定义函数、命名表达式等。

在data中配置

const data1 = [
  ['10.26', null, 'Sum', '=SUM(A:A)'],
  ['20.12', null, 'Average', '=AVERAGE(A:A)'],
  ['30.01', null, 'Median', '=MEDIAN(A:A)'],
  ['40.29', null, 'MAX', '=MAX(A:A)'],
  ['50.18', null, 'MIN', '=MIN(A1:A5)'],
];

const data2 = [
  ['Is A1 in Sheet1 > 10?', '=IF(Sheet1!A1>10,"TRUE","FALSE")'],
  ['Is A:A in Sheet > 150?', '=IF(SUM(Sheet1!A:A)>150,"TRUE","FALSE")'],
  ['How many blank cells are in the Sheet1?', '=COUNTBLANK(Sheet1!A1:D5)'],
  ['Generate a random number', '=RAND()'],
  ['Number of sheets in this workbook', '=SHEETS()'],
];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 附件和菜单

# Context menu

可选类型:boolean|string[]|object

可参考 dropdownMenu

//使用默认模板
contextMenu: true,
//使用别名自定义配置
contextMenu: ['row_above', 'row_below', 'remove_row'],
//自定义配置
contextMenu: {
    callback(key, selection, clickEvent) {
      // Common callback for all options
      console.log(key, selection, clickEvent);
    },
    items: {
      row_above: {
        disabled() { // `disabled` can be a boolean or a function
          // Disable option when first row was clicked
          return this.getSelectedLast()[0] === 0; // `this` === hot
        }
      },
      // A separator line can also be added like this:
      // 'sp1': { name: '---------' }
      // and the key has to be unique
      sp1: '---------',
      row_below: {
        name: 'Click to add row below' // Set custom text for predefined option
      },
      about: { // Own custom option
        name() { // `name` can be a string or a function
          return '<b>Custom option</b>'; // Name can contain HTML
        },
        hidden() { // `hidden` can be a boolean or a function
          // Hide the option when the first column was clicked
          return this.getSelectedLast()[1] == 0; // `this` === hot
        },
        callback(key, selection, clickEvent) { // Callback for specific option
          setTimeout(() => {
            alert('Hello world!'); // Fire alert after menu close (with timeout)
          }, 0);
        }
      },
      colors: { // Own custom option
        name: 'Colors...',
        submenu: {
          // Custom option with submenu of items
          items: [
            {
              // Key must be in the form 'parent_key:child_key'
              key: 'colors:red',
              name: 'Red',
              callback(key, selection, clickEvent) {
                setTimeout(() => {
                  alert('You clicked red!');
                }, 0);
              }
            },
            { key: 'colors:green', name: 'Green' },
            { key: 'colors:blue', name: 'Blue' }
          ]
        }
      },
      credits: { // Own custom property
        // Custom rendered element in the context menu
        renderer(hot, wrapper, row, col, prop, itemValue) {
          const elem = document.createElement('marquee');

          elem.style.cssText = 'background: lightgray;';
          elem.textContent = 'Brought to you by...';

          return elem;
        },
        disableSelection: true, // Prevent mouseoever from highlighting the item for selection
        isCommand: false // Prevent clicks from executing command and closing the menu
      }
    }
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

自定义配置

contextMenu有如下别名

键名 行为
row_above (opens new window) 上面插入一行
row_below (opens new window) 下面插入一行
col_left (opens new window) 插入到左边一列
col_right (opens new window) 插入一列
--------- (opens new window) 菜单项分隔符
remove_row (opens new window) 删除选中的行
clear_column (opens new window) 删除所选列的数据
undo (opens new window) 复原上次的动作 (UndoRedo (opens new window))
redo (opens new window) 重做最后的动作 (UndoRedo (opens new window))
make_read_only (opens new window) 将选中的单元格只读
alignment (opens new window) 文本对齐
cut (opens new window) 裁剪选中的内容到剪贴板(CopyPaste (opens new window))
copy (opens new window) 复制选中的内容到剪贴板(CopyPaste (opens new window))
copy_with_column_headers (opens new window) 带表头复制 (CopyPaste (opens new window))
copy_with_column_group_headers (opens new window) 带多级表头复制 (CopyPaste (opens new window), NestedHeaders (opens new window))
copy_column_headers_only (opens new window) 复制所选内容和他们离最近的表头 (CopyPaste (opens new window))
freeze_column (opens new window) 冻结所选列 (ManualColumnFreeze (opens new window))
unfreeze_column (opens new window) 解冻所选列 (ManualColumnFreeze (opens new window))
borders (opens new window) 所选单元格加边框 (CustomBorders (opens new window))
commentsAddEdit (opens new window) 添加或编辑注释 (Comments (opens new window))
commentsRemove (opens new window) 删除注释 (Comments (opens new window))
commentsReadOnly (opens new window) 设置备注为只读 (Comments (opens new window))
mergeCells (opens new window) 合并或拆分所选单元格 (MergeCells (opens new window))
add_child (opens new window) 插入一个子行 (NestedRows (opens new window))
detach_from_parent (opens new window) 从其上级分离所选行 (NestedRows (opens new window))
hidden_columns_hide (opens new window) 隐藏所选列 (HiddenColumns (opens new window))
hidden_columns_show (opens new window) 显示隐藏列 (HiddenColumns (opens new window))
hidden_rows_hide (opens new window) 隐藏所选行 (HiddenRows (opens new window))
hidden_rows_show (opens new window) 显示隐藏行 (HiddenRows (opens new window))
filter_by_condition (opens new window) 添加一个筛选条件 (Filters (opens new window))
filter_by_condition2 (opens new window) 添加第二个筛选条件 (Filters (opens new window))
filter_operators (opens new window) 选择一个筛选参数 (Filters (opens new window))
filter_by_value (opens new window) 添加一个筛选项 (Filters (opens new window))
filter_action_bar (opens new window) 应用配置的筛选器 (Filters (opens new window))

# Undo and redo

使用撤消和重做功能恢复和恢复更改。

Windows macOS Action Excel Sheets
Ctrl + Z Cmd + Z 复原上次的动作
Ctrl + Y Cmd + Y 重做最后的动作
Ctrl + Shift + Z Cmd + Shift + Z 重做最后的动作

# Keyboard shortcuts

使用内置键盘快捷键,类似于Google Sheets或Microsoft Excel在网格中导航。创建自己的快捷方式。

//使用选定上下文的方法
const gridContext = hot.getShortcutManager().getContext('grid');

//在网格上下文中使用addShortcut()方法
//简单例子
gridContext.addShortcut({
  group: 'group_ID',
  keys: [['enter']],
  callback: () => {},
});
//更完整例子
gridContext.addShortcut({
  group: 'customNumericEditor',
  position: 'before',
  relativeToGroup: 'editorManager.handlingEditor',
  runOnlyIf: () => { hot.getSelected() !== void 0 },
  keys: [['F2']],
  callback: () => {
    if (hot.getActiveEditor().cellProperties.type === 'numeric') {
      return false; // the `F2` shortcut won't work for `numeric` cells
    }

    // another action
  },
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

每个键盘操作都在特定的上下文中注册:

Context 描述 Type
grid 当用户浏览数据网格(初始上下文)时激活 Built-in
editor 当用户打开单元格编辑器时激活 Built-in
menu 当用户打开单元格的上下文菜单时激活 Built-in
Custom 自定义 custom context (opens new window) Custom

# Searching values

使用搜索插件的内置API方法(search),并实现自己的搜索UI,在Handontable上搜索数据。

//获取dom节点
const searchField = document.querySelector('#search_field');
//时间监听
searchField.addEventListener('keyup', function(event) {
  // get the `Search` plugin's instance
  const search = hot.getPlugin('search');
  // use the `Search` plugin's `query()` method
  const queryResult = search.query(event.target.value);
  console.log(queryResult);
  hot.render();
});
1
2
3
4
5
6
7
8
9
10
11

//可通过给匹配的搜索项添加类名来控制样式
search: {
    // add your custom CSS class
    searchResultClass: 'my-custom-search-result-class'
},
//css部分
.my-custom-search-result-class{
  color: #ff0000;
  font-weight: 900;
}
1
2
3
4
5
6
7
8
9
10

# Icon pack

使用我们的一组图标创建工具栏、菜单栏和上下文菜单,这些图标是专门为表格、电子表格和数据丰富的组件设计的。

npm install @handsontable/spreadsheet-icons
1

# Export to CSV

将网格的数据导出为CSV格式,作为可下载的文件、blob或字符串。使用Handontable的配置选项自定义导出。

ExportFile (opens new window)

//导出为文件
button.addEventListener('click', () => {
  exportPlugin.downloadFile('csv', {
    bom: false,
    columnDelimiter: ',',
    columnHeaders: false,
    exportHiddenColumns: true,
    exportHiddenRows: true,
    fileExtension: 'csv',
    filename: 'Handsontable-CSV-file_[YYYY]-[MM]-[DD]',
    mimeType: 'text/csv',
    rowDelimiter: '\r\n',
    rowHeaders: true
  });
});
//导出为Blob
button.addEventListener('click', () => {
  const exportedBlob = exportPlugin.exportAsBlob('csv', {
    bom: false,
    columnDelimiter: ',',
    columnHeaders: false,
    exportHiddenColumns: true,
    exportHiddenRows: true,
    mimeType: 'text/csv',
    rowDelimiter: '\r\n',
    rowHeaders: true
  });
});
//导出为字符串
button.addEventListener('click', () => {
  const exportedString = exportPlugin.exportAsString('csv', {
    bom: false,
    columnDelimiter: ',',
    columnHeaders: false,
    exportHiddenColumns: true,
    exportHiddenRows: true,
    rowDelimiter: '\r\n',
    rowHeaders: true
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 国际化

# Language

将Handontable的UI语言设置为内置翻译之一,或者使用我们的模板创建您自己的语言集。

1.ES modules (ESM)

import Handsontable from 'handsontable/base';
import { registerLanguageDictionary, deDE } from 'handsontable/i18n';

registerLanguageDictionary(deDE);

const hot = new Handsontable(container, {
  language: deDE.languageCode,
});
1
2
3
4
5
6
7
8

2.CommonJS (CJS)

const Handsontable = require('handsontable/base').default;
const { registerLanguageDictionary, deDE } = require('handsontable/i18n');

registerLanguageDictionary(deDE);

const hot = new Handsontable(container, {
  language: deDE.languageCode,
});
1
2
3
4
5
6
7
8

3.Universal Module Definition (UMD)

以这种方式包含的语言可以在加载文件后立即使用。每个文件都包含一个UMD加载程序,该加载程序在全局/外部上下文中查找“Handontable”。如果“Handontable”可用,则它会在适当的上下文中注册自己。

<script type="text/javascript" src="dist/handsontable.full.js"></script>
<script type="text/javascript" src="dist/languages/de-DE.js"></script>
<script>
  const hot = new Handsontable(container, {
    language: 'zh-CN',
  });
</script>
1
2
3
4
5
6
7

# Locale

配置Handontable的区域设置,以正确处理与区域设置相关的数据和操作,如筛选、搜索或排序。

//以表格为单位配置
const hot = new Handsontable(container, {
  // set the entire grid's locale to Polish
  locale: 'pl-PL',
});
//以列为单位配置
const hot = new Handsontable(container, {
  columns: [
    {
      // set the first column's locale to Polish
      locale: 'pl-PL',
    },
    {
      // set the second column's locale to German
      locale: 'de-DE',
    },
    {
      // set the third column's locale to Japanese
      locale: 'ja-JP',
    },
  ],
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# Layout direction

设置从右到左语言的布局方向。自动继承HTML文件的dir属性,或手动设置布局方向。

//方法1(全局注册)
import { registerLanguageDictionary, arAR } from 'handsontable/i18n';
registerLanguageDictionary(arAR);
//方法2(data项中配置)
layoutDirection: 'inherit', //可选参数 'inherit'|'rtl'|'ltr'
1
2
3
4
5

# IME support

使用输入法编辑器将击键转换为键盘上不可用的字符。此功能始终处于启用状态,并且可用于单元格编辑器。

# 工具和项目构建

# Packages

使用预先构建的JavaScript和CSS UMD包,立即将Handontable添加到您的网络应用程序中。

<!-- 完整引入 -->
<script src="dist/handsontable.full.js"></script>
<link href="dist/handsontable.full.css" rel="stylesheet
<!-- 压缩版本 -->
<script src="dist/handsontable.full.min.js"></script>
<link href="dist/handsontable.full.min.css" rel="stylesheet">
<!-- 按需引入 -->
<link href="https://cdn.jsdelivr.net/npm/pikaday@1.8.2/css/pikaday.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pikaday@1.8.2/pikaday.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/numbro@2.1.2/dist/numbro.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@2.4.0/dist/purify.js"></script>
1
2
3
4
5
6
7
8
9
10
11
12

# Modules

通过只导入所需的模块来减少JavaScript捆绑包的大小。基本模块是必需的,所有其他模块都是可选的。

支持的打包工具

  • webpack
  • Parcel
  • Rollup
  • Vite

# Custom plugins

通过编写自定义插件扩展Handontable的功能。使用BasePlugin快速入门。

1.引入基础插件和注册插件函数

import { BasePlugin, registerPlugin } from 'handsontable/plugins';
1

2.扩展基础插件

export class CustomPlugin extends BasePlugin {
  static get PLUGIN_KEY() {
    return 'customPlugin';
  }
  static get SETTING_KEYS() {
    return true;
  }
  constructor(hotInstance) {
    super(hotInstance);
    this.configuration = {
      enabled: false,
      msg: ''
    };
  }
  getUnifiedConfig() {
    const pluginSettings = this.hot.getSettings()[CustomPlugin.PLUGIN_KEY];

    if (pluginSettings === true) {
      return {
        enabled: true,
        msg: 'default msg boolean'
      };
    }
    if (Object.prototype.toString.call(pluginSettings) === '[object Object]') {
      return {
        enabled: true,
        msg: 'default msg obj',
        ...pluginSettings
      };
    }
    if (pluginSettings === false) {
      return {
        enabled: false,
        msg: ''
      };
    }

    throw new Error(
      `${CustomPlugin.PLUGIN_KEY} - incorrect plugins configuration.
      Passed:
        - type: ${typeof pluginSettings}
        - value: ${JSON.stringify(pluginSettings, null, ' ')}

      Expected:
        - boolean
        - object
      `
    );
  }
  isEnabled() {
    const pluginSettings = this.getUnifiedConfig();

    return pluginSettings.enabled;
  }
  enablePlugin() {
    this.configuration = this.getUnifiedConfig();
    this.addHook('afterChange', (changes, source) => this.onAfterChange(changes, source));
    super.enablePlugin();
  }
  disablePlugin() {
    this.configuration = null;
    super.disablePlugin();
  }
  updatePlugin() {
    const { enabled, msg } = this.getUnifiedConfig();
    if (enabled === false && this.enabled === true) {
      this.disablePlugin();

    } else if (enabled === true && this.enabled === false) {
      this.enablePlugin();
    }
    if (this.configuration !== null && msg && this.configuration.msg !== msg) {
      this.configuration.msg = msg;
    }

    super.updatePlugin();
  }
  externalMethodExample() {
    // Method definition.
  }
  onAfterChange(changes, source) {
    console.log(
      `${CustomPlugin.PLUGIN_KEY}.onAfterChange - ${this.configuration.msg}`,
      changes,
      source
    );
  }
  destroy() {
    super.destroy();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

3.注册插件

//名字默认为函数名
registerPlugin(CustomPlugin);
//自定义名称
registerPlugin('CustomAlias', CustomPlugin);
1
2
3
4

4.使用自定义插件

import Handsontable from 'handsontable';
import { CustomPlugin } from './customPlugin';

const hotInstance = new Handsontable(container, {
  // Pass `true` to enable the plugin with default options.
  [CustomPlugin.PLUGIN_KEY]: true,
  // You can also enable the plugin by passing an object with options.
  [CustomPlugin.PLUGIN_KEY]: {
    msg: 'user-defined message',
  },
  // You can also initialize the plugin without enabling it at the beginning.
  [CustomPlugin.PLUGIN_KEY]: false,
});
1
2
3
4
5
6
7
8
9
10
11
12
13

5.获取对插件实例的引用

const pluginInstance = hotInstance.getPlugin(CustomPlugin.PLUGIN_KEY);
pluginInstance.externalMethodExample();
1
2

# Custom builds

Handontable的构建过程将位于代码存储库中的源文件转换为专用包。

Handsontable是一个单存储库,包含以下项目:

项目 路径 描述
handsontable /handsontable Main Handsontable project
@handsontable/react /wrappers/react React wrapper (opens new window)
@handsontable/angular /wrappers/angular Angular wrapper (opens new window)
@handsontable/vue /wrappers/vue Vue 2 wrapper (opens new window)
@handsontable/vue3 /wrappers/vue3 Vue 3 wrapper (opens new window)

构建过程将位于/handontable/src/目录中的源文件转换为以下输出文件:

  • /handsontable/dist/handsontable.js
  • /handsontable/dist/handsontable.css
  • /handsontable/dist/handsontable.full.js
  • /handsontable/dist/handsontable.full.css
  • /handsontable/dist/handsontable.full.min.js
  • /handsontable/dist/handsontable.full.min.css
  • /handsontable/dist/languages/*

构建要求

每个Handontable项目都在自己的package.json文件中定义了自己的构建过程。除此之外,根目录还有自己的package.json文件:

File Holds tasks for building:
/package.json - All the packages at once - Individual packages
/handsontable/package.json The JavaScript package
/wrappers/react/package.json The React package
/wrappers/angular/package.json The Angular package
/wrappers/vue/package.json The Vue 2 package
/wrappers/vue3/package.json The Vue 3 package

# Testing

使用Handontable为Jasmine和Puppeter提供的现成命令运行一个或多个测试。

要从monorepo的根目录运行所有测试,请使用以下命令:

  • npm run test

要运行单独的测试,请转到“/handstandable”目录,并使用以下命令:

  • npm test - 运行所有测试用例(Handontable和Walkontable)
  • npm test:unit - 运行所有单元测试
  • npm test:walkontable - 仅运行Walkontable测试
  • npm test:e2e - 运行所有端到端测试
  • npm run test:e2e --testPathPattern=selection - 只运行与文件名“选择”匹配的端到端测试和套件
  • npm run test:unit --testPathPattern=array - 只运行与文件名“array”匹配的端到端测试和套件
  • npm run test:e2e.dump - 生成“test/E2ERunner.html”文件,该文件可以在浏览器环境(Chrome、Firefox等)中执行,以检查测试是否通过
  • npm run test:e2e.dump -- --watch - 在测试文件中检测到的每个更改都会生成“E2ERunner.html”文件。这可能有助于调试建议。

# Folder structure

目录结构

├── bin                                     # Binary files
├── docs                                    # Documentation files
├── examples                                # Code examples
└── handsontable                            # Handsontable project directory
    ├── dist                                # Compiled files
    ├── languages                           # Translations (i18n)
    ├── scripts                             # Handsontable scripts
    ├── src                                 # Source files
    ├── test                                # Automated tests
    └── types                               # Handsontable TypeScript definitions files
├── resources                               # Static files for README.md
├── scripts                                 # Monorepo scripts
└── wrappers                                # Wrapper files
    ├── angular                             # Wrapper for Angular
    ├── react                               # Wrapper for React
    └── vue                                 # Wrapper for Vue 2
    └── vue3                                # Wrapper for Vue 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 技术规范

# 支持的浏览器

桌面端 移动端
Chrome Chrome
Firefox Firefox for Android
Safari Firefox for iOS
Edge Safari iOS
Opera Opera
QQ browser Samsung Internet
Last Updated: 3/21/2023, 8:58:05 PM