编辑单元格
当涉及到编辑表格的业务场景时,VTable 库提供了相应的编辑器,使你可以轻松地实现各种编辑需求。
使用步骤
1. 引用 VTable 的编辑器包:
使用 NPM 包
首先,确保已经正确安装了 VTable 库@visactor/vtable 和相关的编辑器包@visactor/vtable-editors。你可以使用以下命令来安装它们:
# 使用 npm 安装 npm install @visactor/vtable-editors # 使用 yarn 安装 yarn add @visactor/vtable-editors
在代码中引入所需类型的编辑器模块:
import { DateInputEditor, InputEditor, ListEditor, TextAreaEditor } from '@visactor/vtable-editors';
使用 CDN
你还可以通过 CDN 获取构建好的 VTable-Editor 文件。
<script src="https://unpkg.com/@visactor/vtable-editors@latest/dist/vtable-editors.min.js"></script> <script> const inputEditor = new VTable.editors.InputEditor(); </script>
2. 创建编辑器:
VTable-ediotrs 库中目前提供了四种编辑器类型,包括文本输入框、多行文本输入框、日期选择器、下拉列表等。你可以根据需要选择合适的编辑器。(下拉列表编辑器效果还在优化中,目前比较丑哈)
以下是创建编辑器的示例代码:
const inputEditor = new InputEditor(); const textAreaEditor = new TextAreaEditor(); const dateInputEditor = new DateInputEditor(); const listEditor = new ListEditor({ values: ['女', '男'] });
在上面的示例中,我们创建了一个文本输入框编辑器(InputEditor)、一个多行文本框编辑器(TextAreaEditor)、 一个日期选择器编辑器(DateInputEditor)和一个下拉列表编辑器(ListEditor)。你可以根据实际需求选择适合的编辑器类型。
3. 注册并使用编辑器:
在使用编辑器前,需要将编辑器实例注册到 VTable 中:
// 注册编辑器到VTable VTable.register.editor('name-editor', inputEditor); VTable.register.editor('name-editor2', inputEditor2); VTable.register.editor('textArea-editor', textAreaEditor); VTable.register.editor('number-editor', numberEditor); VTable.register.editor('date-editor', dateInputEditor); VTable.register.editor('list-editor', listEditor);
接下来需要再 columns 配置中指定使用的编辑器(如果是透视表则在 indicators 配置 editor):
columns: [ { title: 'name', field: 'name', editor(args)=>{ if(args.row%2==0) return 'name-editor'; else return 'name-editor2'; } }, { title: 'age', field: 'age', editor: 'number-editor' }, { title: 'gender', field: 'gender', editor: 'list-editor' }, { title: 'address', field: 'address', editor: 'textArea-editor' }, { title: 'birthday', field: 'birthDate', editor: 'date-editor' }, ]
在表格中,用户可以通过双击单元格来开始编辑,然后选择使用的编辑器进行输入。
注意:VTable-ediotrs 内置的这三种编辑器都是基于浏览器的原生输入框实现的,因此在某些特殊情况下可能会出现问题,如输入法输入、输入法弹窗等。你可以根据实际需求进行调整和优化。
editor 配置可以在 columns 中,也可以在全局 options 中定义,同时可以支持自定义函数写法:
interface ColumnDefine { // ... editor?: string | IEditor | ((args: BaseCellInfo & { table: BaseTableAPI }) => string | IEditor); }
4. 自定义实现一个编辑器:
如果 VTable-ediotrs 库提供的几种编辑器无法满足你的需求,你可以自定义实现一个编辑器。为此,你需要创建一个类,实现编辑器接口(IEditor)的要求,并提供必要的方法和逻辑。
可以结合下面这个流程图来理解编辑器和 VTable 之间的关系:

以下是一个自定义编辑器的示例代码:
class DateEditor implements IEditor { editorConfig: any; element: HTMLInputElement; container: HTMLElement; successCallback: Function; picker: any; constructor(editorConfig: any) { this.editorConfig = editorConfig; } onStart({ container, value, referencePosition, endEdit }: EditContext) { const that = this; this.container = container; this.successCallback = endEdit; // const cellValue = luxon.DateTime.fromFormat(value, 'yyyy年MM月dd日').toFormat('yyyy-MM-dd'); const input = document.createElement('input'); input.setAttribute('type', 'text'); input.style.padding = '4px'; input.style.width = '100%'; input.style.boxSizing = 'border-box'; input.style.position = 'absolute'; input.value = value as string; this.element = input; container.appendChild(input); // Pikaday是一个第三方日历组件 const picker = new Pikaday({ field: input, format: 'D/M/YYYY', toString(date, format) { // you should do formatting based on the passed format, // but we will just return 'D/M/YYYY' for simplicity const day = date.getDate(); const month = date.getMonth() + 1; const year = date.getFullYear(); return `${year}年${month}月${day}日`; }, parse(dateString, format) { // dateString is the result of `toString` method const parts = dateString.split('/'); const day = parseInt(parts[0], 10); const month = parseInt(parts[1], 10) - 1; const year = parseInt(parts[2], 10); return new Date(year, month, day); }, onSelect: function () { const date = this.getDate(); that.successCallback(); } }); this.picker = picker; if (referencePosition?.rect) { this.adjustPosition(referencePosition.rect); } this.picker.show(); } adjustPosition(rect: RectProps) { this.element.style.top = rect.top + 'px'; this.element.style.left = rect.left + 'px'; this.element.style.width = rect.width + 'px'; this.element.style.height = rect.height + 'px'; } getValue() { return this.element.value; } onEnd() { this.picker.destroy(); this.container.removeChild(this.element); } isEditorElement(target: HTMLElement) { if (target === this.element || this.picker.el.contains(target)) { return true; } return false; } } const custom_date_editor = new DateEditor({}); VTable.register.editor('custom-date', custom_date_editor);
在上面的示例中,我们创建了一个名为DateEditor的自定义编辑器,并实现了IEditor接口所要求的方法。然后,我们通过VTable.register.editor方法将自定义编辑器注册到 VTable 中,以便在表格中使用。
IEditor 接口定义:
export interface IEditor<V = any> { /** 准备编辑环境,vtable中编辑时机eeditCellTrigger为keydown时,鼠标点击单元格将调用此方法,否则中文输入法第一个 字符会被当做英文字符 */ prepareEdit?: (context: PrepareEditContext<V>) => void; /** * 单元格进入编辑状态时调用 */ onStart: (context: EditContext<V>) => void; /** * 单元格退出编辑状态时调用 */ onEnd: () => void; /** * 如果提供了此函数,VTable 将会在用户点击其他地方时调用此函数。 * 如果此函数返回了一个假值,VTable 将会调用 `onEnd` 并退出编辑状态。 * 如果未定义此函数或此函数返回了一个真值, VTable 将不会做任何事。 * 这意味着,你需要手动调用 `onStart` 中提供的 `endEdit` 来结束编辑模式。 */ isEditorElement?: (target: HTMLElement) => boolean; /** 获取编辑器当前值。将在 `onEnd` 调用后调用。 */ getValue: () => V; /** * 校验输入新值是否合法, * true: 校验通过,退出编辑状态。 * false: 校验失败,保留在编辑状态。 * ValidateEnum.validateExit: 校验通过,退出编辑状态。 * ValidateEnum.invalidateExit: 校验不通过,退出编辑状态,保留旧值。 * ValidateEnum.validateNotExit: 校验通过,不退出编辑状态。 * ValidateEnum.invalidateNotExit: 校验不通过,不退出编辑状态。 */ validateValue?: (newValue?: any, oldValue?: any, position?: CellAddress, table?: any) => boolean | ValidateEnum; } export interface EditContext<V = any> { /** VTable 实例所处的容器元素 */ container: HTMLElement; /** 正在编辑的单元格位置信息 */ referencePosition: ReferencePosition; /** 正在进入编辑状态的单元格当前值 */ value: V; /** * 用于结束编辑状态的回调。 * * 大多数情况下你不需要使用此回调,因为 VTable 已经自带了 Enter 键按下 * 来结束编辑状态的行为;而鼠标点击其他位置来结束编辑状态的行为你也 * 可以通过 `isEditorElement` 函数来获得。 * * 然而,如果你有特殊的需求,比如你想在编辑器内部提供一个“完成”按钮, * 或者你有像 Tooltip 这样无法获取到的外部元素, * 这时你可以保存这个回调并在你需要的时候来手动结束编辑状态。 */ endEdit: () => void; col: number; row: number; }
5. 编辑事件监听:
VTable 提供了编辑事件监听的功能,你可以监听编辑数据事件,并在事件回调中执行相应的逻辑。
以下是一个编辑事件监听的示例代码:
const tableInstance = new VTable.ListTable(option); tableInstance.on('change_cell_value', () => { // 编辑单元格数据 });
6. 编辑后数据获取:
当用户完成编辑并提交数据后,你可以获取编辑后的数据以进行后续处理。可以直接取 records 值
// 获取当前表格的全量数据 tableInstance.records;
7. 编辑触发时机
编辑触发时机支持:双击单元格进入编辑,单击单元格进入编辑,调用 api 手动开启编辑.
interface ListTableConstructorOptions { /** 编辑触发时机 双击事件 单击事件 api手动开启编辑。默认为双击'doubleclick' */ editCellTrigger?: 'doubleclick' | 'click' | 'api' | 'keydown' | ('doubleclick' | 'click' | 'api' | 'keydown')[]; // ... }
8. 编辑值校验
需要校验的情况 请自定义编辑器实现校验函数validateValue
如未定义该接口则编辑值值默认不做校验,返回值支持:
- true: 校验通过,退出编辑状态。
- false: 校验失败,保留在编辑状态。
- ValidateEnum.validateExit: 校验通过,退出编辑状态。
- ValidateEnum.invalidateExit: 校验不通过,退出编辑状态,保留旧值。
- ValidateEnum.validateNotExit: 校验通过,不退出编辑状态。
- ValidateEnum.invalidateNotExit: 校验不通过,不退出编辑状态。
若需要实现异步校验,可以返回一个 Promise 对象,该 Promise 对象在校验成功时以真值解析,校验失败时以假值解析。
同时粘贴单元格数据时,也会调用校验函数validateValue。
9. 相关 api
interface ListTableAPI { /** 设置单元格的value值,注意对应的是源数据的原始值,vtable实例records会做对应修改 */ changeCellValue: ( col: number, row: number, value: string | number | null, workOnEditableCell?: boolean, triggerEvent?: boolean, noTriggerChangeCellValuesEvent?: boolean ) => void; /** * 批量更新多个离散选区内的单元格数据。 * 当前仅支持将所有选区内的单元格统一修改为同一个 value。 */ changeCellValuesByRanges: ( ranges: CellRange[], value: string | number | null, workOnEditableCell?: boolean, triggerEvent?: boolean, noTriggerChangeCellValuesEvent?: boolean ) => void; /** * 批量更新多个单元格的数据 * @param col 粘贴数据的起始列号 * @param row 粘贴数据的起始行号 * @param values 多个单元格的数据数组 * @param workOnEditableCell 是否仅允许更改可编辑单元格的值,默认为 false * @param gerEvent 是否在值发生改变的时候触发change_cell_value事件 */ changeCellValues: ( startCol: number, startRow: number, values: string[][], workOnEditableCell?: boolean, triggerEvent?: boolean, noTriggerChangeCellValuesEvent?: boolean ) => Promise<boolean[][]>; /** 获取单元格配置的编辑器 */ getEditor: (col: number, row: number) => IEditor; /** 开启单元格编辑 */ startEditCell: (col?: number, row?: number, value?: string | number) => void; /** 结束编辑 */ completeEditCell: () => void; /** 取消编辑 */ cancelEditCell: () => void; // ... }
基本表格表头编辑注意事项
基本表格可支持编辑表头显示标题 title,在全局或者在 column 中配置headerEditor来开启,具体用法同editor。
透视表编辑注意事项
透视表的表头编辑会对应修改 records 中的 field 名称;
在透视表中,当 body 中某个单元格对应的源数据 records 只有一条的时候当编辑后 会对应修改 record 的字段值。但是当单元格对应聚合了多条 records 数据的指标值时,是不支持对应修改到源数据的。
具体单元格对应 的源数据可以通过接口getCellOriginRecord来获取。
编辑时机为keydown时注意事项
中文输入法下如果出现第一个拼音被识别成了英文字符,那么请检查是否有prepareEdit 函数。在编辑时机为keydown时,鼠标点击单元格将调用此方法,提前将创建输入框,并将输入框设置为不可见,等待用户输入。
总结
通过以上步骤,你可以创建一个具有编辑功能的表格,并根据业务需求选择合适的编辑器类型、自定义编辑器、监听编辑事件以及获取编辑后的数据。这样,用户就可以方便地编辑表格中的数据,并且你可以对编辑后的数据进行相应的处理。