@apigo.cc/datatable
v1.0.16
Published
`@apigo.cc/datatable` 是建立在 `@apigo.cc/base` 上的同步数据表格组件,重点不是“能显示表格”,而是“能直接被页面首屏加载并完成渲染、筛选、编辑和保存”。
Readme
@apigo.cc/datatable
@apigo.cc/datatable 是建立在 @apigo.cc/base 上的同步数据表格组件,重点不是“能显示表格”,而是“能直接被页面首屏加载并完成渲染、筛选、编辑和保存”。
目录
先看依赖
datatable 依赖 state、bootstrap 和 base,加载顺序仍然必须是同步的。
推荐加载顺序
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/[email protected]/dist/state.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/[email protected]/dist/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/[email protected]/dist/base.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/[email protected]/dist/datatable.min.js"></script>全局对象
DataTable
DataTable 是本包挂到全局的对象。
<script>
console.log(DataTable.getFieldTypes())
</script>方法
| 方法 | 说明 |
|---|---|
| registerFieldType(config) | 注册字段类型 |
| getFieldTypes() | 获取当前所有已注册字段类型 |
<DataTable>
<DataTable> 负责渲染、编辑、筛选、排序和虚拟滚动。内部会用到 <AutoForm>, <Modal>, <Resizer>,这些都来自 base。
绑定方式
| 名称 | 写法示例 | 说明 |
|---|---|---|
| editable | <DataTable editable> | 开启编辑、删除、字段管理、保存按钮 |
| $.state.fields | $.state.fields="window.fields" | 字段定义数组 |
| $.state.list | $.state.list="window.list" | 数据行数组 |
事件
| 事件 | 触发时机 | detail |
|---|---|---|
| save | 点击底部保存按钮 | { list, fields } |
| savefields | 字段新增 / 编辑 / 删除后 | fields |
| remove | 删除行后 | { items } |
常用状态
| 名称 | 说明 |
|---|---|
| editable | 是否开启编辑模式 |
| list | 当前生效的数据列表,排序和筛选后会更新这里 |
| fields | 字段定义数组 |
| _renderedList | 当前 DOM 实际渲染的片段 |
| prevHeight | 虚拟滚动顶部占位高度 |
| postHeight | 虚拟滚动底部占位高度 |
| _listStartIndex | 当前渲染片段在原列表中的起始索引 |
| selectedRowCount | 当前选中行数 |
| _originalList | 原始数据快照,排序 / 筛选前的数据源 |
| sortConfig | { fieldId, direction } |
| filterConfig | { [fieldId]: { mode, value, value2, selectedValues } } |
| activeFieldId | 当前打开列菜单的字段 id |
| activeField | 当前打开列菜单的字段对象 |
| activeModes | 当前列允许的筛选模式 |
| _columnStats | 当前列高频值统计 |
| _internalUpdate | 内部同步标记,避免列表回写时触发二次筛选 |
| _appliedHash | 内部哈希缓存 |
| _fieldsDirty | 字段变更标记 |
| _masterCellNodes | 用于动态重建列模板的内部缓存 |
| isDirty | 是否存在未保存修改 |
| isBulkEdit | 是否处于批量编辑状态 |
方法
| 方法 | 写法示例 | 说明 |
|---|---|---|
| addRow() | table.addRow() | 新增一行空数据 |
| deleteSelectedRow() | table.deleteSelectedRow() | 删除当前选区对应的行 |
| saveChanges() | table.saveChanges() | 派发 save 事件 |
| addField() | table.addField() | 新增字段 |
| editField() | table.editField() | 编辑当前打开菜单的字段 |
| deleteField() | table.deleteField() | 删除当前打开菜单的字段 |
| showColumnMenu(field, event) | table.showColumnMenu(field, event) | 打开列菜单 |
| hideColumnMenu() | table.hideColumnMenu() | 关闭列菜单 |
| editCell(row, field, cellNode) | table.editCell(row, field, cellNode) | 打开单元格编辑器 |
| onScroll() | table.onScroll() | 滚动时刷新虚拟列表 |
推荐示例
<script>
window.fields = [
{ id: 'id', name: 'ID', type: 'string', settings: { width: 70, formType: 'text', pinned: 'left' } },
{ id: 'name', name: 'Name', type: 'string', settings: { width: 160, formType: 'text', pinned: 'left' } },
{ id: 'role', name: 'Role', type: 'string', settings: { width: 120, formType: 'select', options: [{ label: 'Admin', value: 'admin' }, { label: 'Editor', value: 'editor' }] } },
{ id: 'active', name: 'Active', type: 'boolean', settings: { width: 90, formType: 'switch', labelOn: 'YES', labelOff: 'NO' } },
{ id: 'score', name: 'Score', type: 'number', settings: { width: 120, formType: 'number', prefix: '$', decimals: 2, thousandSep: true } },
{ id: 'tags', name: 'Tags', type: 'object', settings: { width: 180, formType: 'TagsInput' } },
{ id: 'created', name: 'Created', type: 'date', settings: { width: 140, formType: 'date' } },
{ id: 'actions', name: 'Actions', type: 'string', settings: { width: 120, formType: 'text', pinned: 'right' }, formatter: () => 'Edit' }
]
window.list = [
{ id: 1, name: 'Alice', role: 'admin', active: true, score: 12800.5, tags: ['Vue', 'AI'], created: '2026-06-01', actions: '' },
{ id: 2, name: 'Bob', role: 'editor', active: false, score: 980.25, tags: ['Docs'], created: '2026-06-02', actions: '' }
]
DataTable.registerFieldType({
value: 'TagsInput',
label: '{#Tags#}',
typeForDB: 'v4096',
schema: [{ name: 'placeholder', label: 'Placeholder', type: 'text' }],
formatter: val => Array.isArray(val) ? val.join(', ') : val
})
</script>
<div style="height: 600px" class="border rounded overflow-hidden bg-body">
<DataTable editable
$.state.fields="window.fields"
$.state.list="window.list"
$onsave="UI.toast('save: ' + JSON.stringify(event.detail.list))"
$onsavefields="UI.toast('fields changed')"
$onremove="UI.toast('removed: ' + event.detail.items.length)">
</DataTable>
</div>字段定义
fields 是表格最重要的配置。每个字段都建议写完整,不要只写一个 id 就了事。
字段结构
| 字段 | 写法示例 | 说明 |
|---|---|---|
| id | id: 'name' | 列唯一标识,同时也是数据行中的字段名 |
| name | name: 'Name' | 表头显示名 |
| type | type: 'string' | 列的原始类型 |
| memo | memo: 'User name' | 字段备注,字段编辑器可见 |
| isIndex | isIndex: true | 是否索引字段 |
| settings | settings: { width: 160, formType: 'text' } | 编辑与展示配置 |
| formatter | formatter: v => String(v) | 自定义显示格式 |
settings 字段
| 字段 | 写法示例 | 说明 |
|---|---|---|
| width | width: 160 | 列宽,默认 150 |
| pinned | pinned: 'left' | 固定列,left 或 right |
| formType | formType: 'select' | 编辑器类型 |
| options | options: [{ label: 'A', value: 'a' }] | 选择类选项 |
| decimals | decimals: 2 | 数字小数位 |
| prefix | prefix: '$' | 数字前缀 |
| suffix | suffix: '%' | 数字后缀 |
| thousandSep | thousandSep: true | 数字千分位 |
| labelOn | labelOn: 'YES' | switch 为真时显示 |
| labelOff | labelOff: 'NO' | switch 为假时显示 |
| format | format: 'YYYY-MM-DD' | 日期时间格式说明 |
| placeholder | placeholder: 'Enter value' | 文本占位符 |
字段示例
{
id: 'score',
name: 'Score',
type: 'number',
settings: {
width: 120,
formType: 'number',
decimals: 2,
prefix: '$',
thousandSep: true
},
formatter: value => value == null ? '' : String(value)
}内置编辑能力
单元格编辑支持的 formType
editCell() 会把 settings.formType || type || 'text' 交给 AutoForm,因此这些类型都能直接编辑:
textnumberselectcheckboxradioswitchdatedatetimetextareaTagsInputDatePickerColorPickerIconPicker
字段编辑器默认可选类型
字段管理弹窗里,默认由 DataTable.getFieldTypes() 提供的类型是:
textnumberselectcheckboxswitchdatetimetextarea
重要区别
date、radio这些类型在单元格编辑里是可用的- 但默认字段编辑器列表里不一定出现
- 如果你要让它们出现在字段管理弹窗里,直接用
registerFieldType()补进去
默认字段类型的格式化规则
| 类型 | 显示规则 |
|---|---|
| text | 原样字符串化 |
| number | 支持 decimals、prefix、suffix、thousandSep |
| select | 显示匹配项的 label |
| checkbox | 把多选项 label 用逗号拼接 |
| switch | true 显示 labelOn 或 Yes,false 显示 labelOff 或 No |
| datetime | 默认字符串化 |
| textarea | 默认字符串化 |
筛选、排序、固定列
排序
- 点击表头右侧下拉按钮
- 选择
ASC或DESC - 排序状态写在
state.sortConfig
筛选
列菜单支持三类筛选:
- 文本类:
contains,equals,starts,ends - 数字类:
=,>,<,between - 值列表:直接勾选统计出来的高频值
支持的列筛选模式
| formType | 可用模式 |
|---|---|
| text | contains, equals, starts, ends |
| textarea | contains, equals, starts, ends |
| number | =, >, <, between |
| date | =, >, <, between |
| select | contains, equals |
| TagsInput | contains, equals, starts, ends |
| switch, checkbox, radio | 选项勾选,不走文本筛选模式 |
固定列
字段上设置:
settings: { pinned: 'left' }或:
settings: { pinned: 'right' }就会固定在对应侧边。
复制粘贴与批量编辑
选择
- 鼠标拖选单元格区域
selectedRowCount会同步更新- 选区会用于复制、粘贴和批量编辑
复制
Ctrl/Cmd + C把选区复制成 TSV- 带引号、换行、制表符的值会自动转义
粘贴
Ctrl/Cmd + V会把剪贴板 TSV 回填到选区- 单格选中时会把后续数据按行列铺开
批量编辑
- 在已选中的区域内双击一个单元格
- 如果选区跨多行,会进入批量编辑
- 保存时会把新值写入范围内每一行
自定义字段类型
DataTable.registerFieldType(config)
DataTable.registerFieldType({
value: 'MyTags',
label: '{#Tags#}',
typeForDB: 'v4096',
schema: [
{ name: 'placeholder', label: 'Placeholder', type: 'text' }
],
formatter: val => Array.isArray(val) ? val.join(' | ') : val
})schema 说明
schema 的格式和 base 里的 AutoForm.schema 一样,所以这里也能用:
namelabeltypesettingoptionsif
适合注册的场景
- 你要让字段编辑器认识一个新的
formType - 你要给某种值定义专用
formatter - 你要把扩展控件复用到表格内联编辑里
推荐示例
可编辑表格
<script>
window.fields = [
{ id: 'id', name: 'ID', type: 'string', settings: { width: 70, formType: 'text', pinned: 'left' } },
{ id: 'name', name: 'Name', type: 'string', settings: { width: 160, formType: 'text', pinned: 'left' } },
{ id: 'role', name: 'Role', type: 'string', settings: { width: 120, formType: 'select', options: [{ label: 'Admin', value: 'admin' }, { label: 'Editor', value: 'editor' }] } },
{ id: 'active', name: 'Active', type: 'boolean', settings: { width: 90, formType: 'switch', labelOn: 'YES', labelOff: 'NO' } },
{ id: 'score', name: 'Score', type: 'number', settings: { width: 120, formType: 'number', prefix: '$', decimals: 2, thousandSep: true } },
{ id: 'created', name: 'Created', type: 'date', settings: { width: 140, formType: 'date' } }
]
window.list = [
{ id: 1, name: 'Alice', role: 'admin', active: true, score: 12800.5, created: '2026-06-01' },
{ id: 2, name: 'Bob', role: 'editor', active: false, score: 980.25, created: '2026-06-02' }
]
</script>
<div style="height: 560px" class="border rounded overflow-hidden">
<DataTable editable
$.state.fields="window.fields"
$.state.list="window.list"
$onsave="UI.toast('save')"
$onremove="UI.toast('remove: ' + event.detail.items.length)">
</DataTable>
</div>只读大表 + 虚拟滚动
<script>
window.fields = [
{ id: 'id', name: 'ID', type: 'string', settings: { width: 70, formType: 'text', pinned: 'left' } },
{ id: 'name', name: 'Name', type: 'string', settings: { width: 160, formType: 'text', pinned: 'left' } },
{ id: 'role', name: 'Role', type: 'string', settings: { width: 120, formType: 'select', options: [{ label: 'Admin', value: 'admin' }, { label: 'Editor', value: 'editor' }] } },
{ id: 'active', name: 'Active', type: 'boolean', settings: { width: 90, formType: 'switch', labelOn: 'YES', labelOff: 'NO' } },
{ id: 'score', name: 'Score', type: 'number', settings: { width: 120, formType: 'number', prefix: '$', decimals: 2, thousandSep: true } },
{ id: 'created', name: 'Created', type: 'date', settings: { width: 140, formType: 'date' } }
]
window.bigList = Array.from({ length: 2000 }, (_, i) => ({
id: i + 1,
name: 'User ' + (i + 1),
role: i % 2 ? 'admin' : 'editor',
active: i % 3 === 0,
score: Math.round(Math.random() * 10000) / 100,
created: '2026-06-01'
}))
</script>
<div style="height: 560px" class="border rounded overflow-hidden">
<DataTable
$.state.fields="window.fields"
$.state.list="window.bigList">
</DataTable>
</div>自定义字段类型
<script>
DataTable.registerFieldType({
value: 'TagsInput',
label: '{#Tags#}',
typeForDB: 'v4096',
schema: [{ name: 'placeholder', label: 'Placeholder', type: 'text' }],
formatter: val => Array.isArray(val) ? val.join(', ') : val
})
window.fields = [
{ id: 'tags', name: 'Tags', type: 'object', settings: { width: 180, formType: 'TagsInput' } }
]
window.list = [
{ id: 1, tags: ['Vue', 'AI'] }
]
</script>
<DataTable $.state.fields="window.fields" $.state.list="window.list"></DataTable>备注
- 字段定义和数据列表建议放在 JS 里
- 外层容器最好明确给高度
- 需要保存时,监听
save;需要字段变更时,监听savefields
