Handsontable 日期单元格类型(date / intl-date)完全指南:格式化、校验、排序与过滤
【免费下载链接】handsontableJavaScript Data Grid / Data Table with a Spreadsheet Look & Feel. Works with React, Angular, and Vue. Supported by the Handsontable team ⚡项目地址: https://gitcode.com/gh_mirrors/ha/handsontable
导读
本文以 Handsontable 官方文档 date-cell-type.md 为核心,系统讲解日期单元格类型(date/intl-date)的配置与实战:如何用Intl.DateTimeFormat选项对象控制显示格式、如何保证 ISO 8601 源数据与校验、原生日期选择器(date picker)的编辑行为,以及排序、过滤如何依赖底层 ISO 值。文中结合当前仓库的源码(如 intlDateType.ts、dateRenderer.ts、dateValidator.ts、dateEditor.ts)逐层拆解实现原理,读完你可以直接在项目中落地一个"显示本地化、存储标准化"的日期表格。
日期单元格类型概述
日期单元格类型(date cell type)让你的单元格值以日期的方式被对待:按照配置格式化显示、校验输入合法性,并在编辑时弹出交互式日期选择器。
在 Handsontable 中,日期相关的单元格类型有两个入口:
intl-date:基于原生Intl.DateTimeFormatAPI 的推荐类型(Handsontable 18.0 起主推)。date:与intl-date共享同一套渲染、校验、编辑与格式化逻辑的别名类型。
两者配合 ISO 8601 日期字符串(YYYY-MM-DD)使用:源数据必须是 ISO 8601 格式,显示格式则由dateFormat对象独立控制。这一"源数据标准化、显示本地化"的设计,让排序、过滤、导出等依赖底层值的功能始终稳定可靠。
从源码结构看,intl-date是一个典型的"组合型"单元格类型。在 intlDateType.ts 中可以看到它把编辑、渲染、校验三个环节组装在一起:
export const CELL_TYPE = 'intl-date'; export const IntlDateCellType = { CELL_TYPE, editor: IntlDateEditor, renderer: intlDateRenderer, validator: intlDateValidator, sourceDataValidator, sourceDataWarningMessage: SOURCE_DATA_WARNING_MESSAGE, valueFormatter, };其中editor用于编辑、renderer负责显示格式化、validator与sourceDataValidator负责两套校验(编辑时校验与批量源数据校验)、valueFormatter负责把 ISO 值转成显示文本。date类型则对应 dateCellType 中的同名实现,二者复用同一批底层组件。
日期单元格类型演示
官方文档提供了一个多列演示(example1),演示三种不同的格式化风格,全部基于 ISO 8601 源数据:
- Product date(产品日期):
dateStyle: 'short'短样式格式化; - Payment date(付款日期):自定义
month/day/year组合格式化; - Registration date(注册日期):自定义包含
weekday(星期)、month、day、year的完整格式化。
以 JavaScript 版本的 example1.js 为例,核心配置如下:
const data = [ { car: 'Mercedes A 160', product_date: '2002-06-15', payment_date: '2002-05-20', registration_date: '2002-07-01', }, // ... 更多行 ]; const hot = new Handsontable(container, { data, colHeaders: ['Car', 'Product date', 'Payment date', 'Registration date'], columns: [ { type: 'text', data: 'car' }, { type: 'intl-date', data: 'product_date', dateFormat: { dateStyle: 'short' }, }, { type: 'intl-date', data: 'payment_date', dateFormat: { month: 'long', day: 'numeric', year: 'numeric' }, }, { type: 'intl-date', data: 'registration_date', dateFormat: { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }, }, ], columnSorting: true, filters: true, dropdownMenu: true, height: 'auto', licenseKey: 'non-commercial-and-evaluation', autoWrapRow: true, autoWrapCol: true, });注意演示中同时开启了columnSorting: true、filters: true与dropdownMenu: true,配合日期列展示"排序和过滤基于 ISO 底层值"的行为。演示还包含一个语言(locale)切换下拉菜单:点击菜单项后调用hot.updateSettings({ locale: item.dataset.value }),即可实时切换整张表的显示语言(例如en-US、de-DE),这正是"显示格式与源数据解耦"的直观体现——切换 locale 只改变显示文本,单元格底层值始终是 ISO 8601 字符串。该演示在不同框架下的等价实现分别位于 react/example1.jsx、angular/example1.ts、vue/example1.vue。
使用日期单元格类型
使用**对象风格(object-style)**配置:把type设置为'intl-date'或'date',把dateFormat设置为一个对象。语言环境(locale)由独立的locale选项控制。
日期类型可以在三个粒度上配置:
1. 整个表格(grid 级)
type: 'intl-date', locale: 'en-US', dateFormat: { year: 'numeric', month: '2-digit', day: '2-digit' },2. 单个列(column 级)
columns: [ { type: 'intl-date', locale: 'en-US', dateFormat: { dateStyle: 'short' } } ],3. 单个单元格(cell 级)
cell: [ { row: 0, col: 2, type: 'intl-date', locale: 'en-US', dateFormat: { dateStyle: 'medium' } } ],React 中使用 JSX 属性写法:
<HotTable type="intl-date" locale="en-US" dateFormat={{ year: 'numeric', month: '2-digit', day: '2-digit' }} columns={[{ type: 'intl-date', locale: 'en-US', dateFormat: { dateStyle: 'short' } }]} />源数据格式要求
对于intl-date和date单元格,源数据必须使用 ISO 8601 日期格式(YYYY-MM-DD),日期才能正常工作。dateFormat对象只影响显示;排序和过滤依赖的是底层的 ISO 值,而不是格式化后的显示文本。
这一点在源码中得到严格印证:
- dateValidator.ts 中的
dateValidator直接调用isValidISODate(value)判定合法性:export function dateValidator(this: CellMeta, value: unknown, callback: (valid: boolean) => void): void { if (this.allowEmpty && isEmpty(value)) { callback(true); return; } callback(isValidISODate(value)); } - 同时导出的
sourceDataValidator用于批量源数据校验,它除了放行allowEmpty的空值和 Formulas 插件的公式表达式(以=开头的字符串)外,同样要求isValidISODate(value)为真:export function sourceDataValidator(value: unknown, cellMeta: CellMeta): boolean { if (cellMeta.allowEmpty && isEmpty(value)) { return true; } if (typeof value === 'string' && value.startsWith('=')) { return true; } return isValidISODate(value); }有趣的是,该函数被标记为
sourceDataValidator.rowIndependent = true,注释说明它的结果只依赖列级/全局 meta(如allowEmpty),从不依赖行级 meta,因此源码数据校验运行器可以跨行复用同一个列级 meta 对象,避免为每个单元格物化 meta——这是批量校验性能上的一个优化细节。 - 校验失败时,intlDateValidator.ts 会给出明确的警告文案
SOURCE_DATA_WARNING_MESSAGE,提示"期望与 ISO 8601 日期格式(YYYY-MM-DD)兼容的值"。
另外,isValidISODate与parseToLocalDate等日期解析工具集中在 helpers/dateTime.ts,是渲染、校验、编辑共用的底层基础设施。
格式化日期
要控制日期在单元格渲染器中的显示效果,使用dateFormat选项。
从 Handsontable 18.0 开始,intl-date和date单元格类型必须使用对象形式的dateFormat,它基于原生Intl.DateTimeFormatAPI;语言环境由独立的locale选项控制。
::: tip 提示 与时间相关的dateFormat选项(hour、minute、second、timeStyle、hour12、hourCycle、fractionalSecondDigits)只影响显示。由于date/intl-date的源数据只含日期,这些选项渲染出的时间永远是午夜(00:00:00)。如果需要编辑并存储"日期 + 时间",请使用日期时间单元格类型(intl-datetime)。这一约束在 metaSchema.ts 的dateFormat文档注释中也有明确说明。 :::
渲染与格式化:源码如何工作
dateFormat在渲染阶段如何生效?看 dateRenderer.ts 中的valueFormatter实现:
const DEFAULT_INTL_FORMAT: Intl.DateTimeFormatOptions = { year: 'numeric', month: '2-digit', day: '2-digit', }; export function valueFormatter(value: unknown, cellProperties: CellProperties): unknown { const { dateFormat, locale, allowEmpty, instance } = cellProperties; if (isEmpty(value)) { return allowEmpty ? value : BAD_VALUE_TEXT; // 空值:允许为空则原样返回,否则显示 '#bad-value#' } if (typeof dateFormat === 'string') { // 字符串形式的 dateFormat 已不支持,仅警告一次并原样返回 ... return value; } const date = parseToLocalDate(value); if (date === null) { return BAD_VALUE_TEXT; // 非法 ISO 值显示 '#bad-value#' } const intlFormat = isObject(dateFormat) ? dateFormat as Intl.DateTimeFormatOptions : DEFAULT_INTL_FORMAT; return new Intl.DateTimeFormat(locale, intlFormat).format(date); }几个关键点:
- 默认格式:即使不配置
dateFormat,也会使用{ year: 'numeric', month: '2-digit', day: '2-digit' }作为兜底(对应YYYY-MM-DD样式的本地化显示)。 - 字符串形式已废弃:如果传入字符串形式的
dateFormat,渲染器会对每个实例仅警告一次"请改用Intl.DateTimeFormatOptions对象",然后原样返回值。 - 非法值占位:空值且不允许为空、或无法解析为日期时,显示
#bad-value#(定义于 helpers/constants.ts 的BAD_VALUE_TEXT)。 intlDateRenderer(intlDateRenderer.ts)直接委托给dateRenderer并把valueFormatter挂载为静态属性,供编辑器等其他环节复用同一套格式化逻辑。
使用 Intl.DateTimeFormat 选项
dateFormat选项接受Intl.DateTimeFormatoptions 的全部属性,配合type: 'intl-date'或type: 'date'使用。不同列可以搭配不同 locale 与格式:
columns: [ { type: 'intl-date', locale: 'en-US', dateFormat: { year: 'numeric', month: '2-digit', day: '2-digit' } }, { type: 'intl-date', locale: 'de-DE', dateFormat: { dateStyle: 'long' } } ]React 等价写法:
<HotTable columns={[{ type: 'intl-date', locale: 'en-US', dateFormat: { year: 'numeric', month: '2-digit', day: '2-digit' } }, { type: 'intl-date', locale: 'de-DE', dateFormat: { dateStyle: 'long' } }]} />日期专用选项速查表
样式快捷方式(Style shortcuts):
| 属性 | 可选值 | 说明 |
|---|---|---|
dateStyle | 'full'、'long'、'medium'、'short' | 日期格式化样式(星期、日、月、年、纪元) |
timeStyle | 'full'、'long'、'medium'、'short' | 时间部分样式(时、分、秒、时区名);用于日期 + 时间场景 |
日期时间分量选项(Date-time component options):
| 属性 | 可选值 | 说明 |
|---|---|---|
weekday | 'long'、'short'、'narrow' | 星期的表示方式 |
era | 'long'、'short'、'narrow' | 纪元的表示方式 |
year | 'numeric'、'2-digit' | 年份表示方式 |
month | 'numeric'、'2-digit'、'long'、'short'、'narrow' | 月份表示方式 |
day | 'numeric'、'2-digit' | 日表示方式 |
dayPeriod | 'narrow'、'short'、'long' | 日周期(例如 "am") |
hour | 'numeric'、'2-digit' | 小时(若包含时间) |
minute | 'numeric'、'2-digit' | 分钟 |
second | 'numeric'、'2-digit' | 秒 |
fractionalSecondDigits | 1、2、3 | 秒的小数位数 |
timeZoneName | 'long'、'short'、'shortOffset'、'longOffset'、'shortGeneric'、'longGeneric' | 时区显示方式 |
语言环境与其他选项(Locale and other options):
| 属性 | 可选值 | 说明 |
|---|---|---|
localeMatcher | 'best fit'(默认)、'lookup' | 区域匹配算法 |
calendar | 'chinese'、'gregory'、'persian'等 | 使用的日历系统 |
numberingSystem | 'latn'、'arab'、'hans'等 | 数字系统 |
timeZone | IANA 时区(如'UTC'、'America/New_York') | 格式化使用的时区 |
hour12 | true、false | 12 小时制 vs 24 小时制 |
hourCycle | 'h11'、'h12'、'h23'、'h24' | 小时周期 |
formatMatcher | 'basic'、'best fit'(默认) | 格式匹配算法 |
完整的属性参考见dateFormatAPI 文档 与 MDN: Intl.DateTimeFormat。
编辑器行为
dateFormat控制的是单元格内的显示。编辑器(日期选择器或文本输入)可能以归一化形式展示该值;对于intl-date和date,底层值始终保持 ISO 8601 格式。
这一点在 dateEditor.ts 中有非常清晰的实现:
DateEditor继承自TextEditor,但其createElements()把文本域改造成原生日期输入:createElements(type?: string): void { super.createElements('input'); this.TEXTAREA.setAttribute('type', 'date'); }prepare()阶段,编辑器拿到的显示值已经过valueFormatter格式化,但它会把originalValue替换为原始 ISO 源数据,让原生日期输入框始终收到YYYY-MM-DD字符串:prepare(row, col, prop, td, value, cellProperties) { super.prepare(row, col, prop, td, value, cellProperties); ... const physicalRow = this.hot.toPhysicalRow(row); this.originalValue = this.hot.getSourceDataAtCell(physicalRow, col); }setValue()在值为空时回退到defaultDate(如果配置了),并对非 ISO 值发出警告后清空输入:setValue(value?: unknown): void { if (isEmpty(value)) { value = this.cellProperties.defaultDate; } if (!isValidISODate(value)) { warn('DateEditor: value must be in ISO date format ("YYYY-MM-DD") ...'); super.setValue(''); return; } super.setValue(value); }open()通过showPicker()程序化唤起浏览器的原生日期选择器;focus()时全选输入内容方便直接键入。- 另外,
init()中注册了afterSetTheme钩子:切换主题(非首次运行时)会关闭编辑器,避免主题切换时编辑器状态错乱。
defaultDate选项配置日期选择器在单元格为空时预选的日期(例如defaultDate: '2015-02-02'),它只影响选择器的初始选中值,不影响已填写单元格的值,详见 metaSchema.ts 中defaultDate的说明。
结果与行为验证
完成日期单元格类型配置后:
- 单元格按你的
dateFormat配置显示格式化后的日期文本; - 点击
intl-date或date单元格会打开浏览器的原生日期选择器; - 无论显示格式如何,源数据始终以 ISO 8601 格式(
YYYY-MM-DD)存储。
排序与过滤依赖 ISO 底层值
文档明确指出:"排序和过滤依赖于底层的 ISO 值"。源码同样印证:
- 列排序插件为
intl-date提供了专门的比较函数工厂 intlDate.ts,其compareFunctionFactory调用 columnSorting/utils.ts 中的createIntlDateCompareFunction——基于 ISO 日期构造可比较的值,从而保证按真实时间顺序排序,而不是按显示文本的字典序。 - 过滤插件在 filters/constants.ts 中为
intl-date注册了完整的条件集合(before、after、between、today、yesterday、tomorrow等),对应的单测位于 filters/tests/condition/intlDate;filters/sortComparators.ts则复用了与列排序一致的比较逻辑。
因此,只要源数据保持 ISO 8601,无论显示成2/15/12还是February 15, 2012,排序和过滤的结果都是正确的。
导出与注册
intl-date同样参与了导出(XLSX)与模块注册体系:xlsx 导出类型映射见 plugins/exportFile/types/xlsx.ts,模块全量注册测试见 registry/registerAllCellTypes.unit.js。如果你使用按需注册方式,需要显式registerCellType(intlDateCellType)或调用registerAllModules()(演示代码中即采用后者)。
键盘快捷键
intl-date和date单元格编辑器打开的是浏览器原生日期选择器。选择器内部的键盘导航行为由浏览器提供,因此在不同浏览器与操作系统之间存在差异。在日期选择器之外,Handsontable 标准的编辑类键盘快捷键依然生效(如 Enter 开始编辑、Esc 取消编辑、Tab 切换单元格等)。
相关资源
相关指南
- 单元格类型(Cell type)
配置选项
dateFormatlocaletypedefaultDatevalueFormattervalueParservalueSettervalueGetter
核心方法
getCellMeta()getCellMetaAtRow()getCellsMeta()getDataType()setCellMeta()setCellMetaObject()removeCellMeta()
钩子(Hooks)
afterGetCellMetaafterSetCellMetabeforeGetCellMetabeforeSetCellMeta
【免费下载链接】handsontableJavaScript Data Grid / Data Table with a Spreadsheet Look & Feel. Works with React, Angular, and Vue. Supported by the Handsontable team ⚡项目地址: https://gitcode.com/gh_mirrors/ha/handsontable
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考