dataTable/README.md
2026-07-07 00:17:32 +08:00

14 KiB
Raw Permalink Blame History

@apigo.cc/datatable

@apigo.cc/datatable 是建立在 @apigo.cc/base 上的同步数据表格组件,重点不是“能显示表格”,而是“能直接被页面首屏加载并完成渲染、筛选、编辑和保存”。

目录

先看依赖

datatable 依赖 statebootstrapbase,加载顺序仍然必须是同步的。

推荐加载顺序

<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/state@1.0.23/dist/state.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/bootstrap@1.0.9/dist/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/base@1.0.23/dist/base.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/datatable@1.0.16/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' 固定列,leftright
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,因此这些类型都能直接编辑:

  • text
  • number
  • select
  • checkbox
  • radio
  • switch
  • date
  • datetime
  • textarea
  • TagsInput
  • DatePicker
  • ColorPicker
  • IconPicker

字段编辑器默认可选类型

字段管理弹窗里,默认由 DataTable.getFieldTypes() 提供的类型是:

  • text
  • number
  • select
  • checkbox
  • switch
  • datetime
  • textarea

重要区别

  • dateradio 这些类型在单元格编辑里是可用的
  • 但默认字段编辑器列表里不一定出现
  • 如果你要让它们出现在字段管理弹窗里,直接用 registerFieldType() 补进去

默认字段类型的格式化规则

类型 显示规则
text 原样字符串化
number 支持 decimalsprefixsuffixthousandSep
select 显示匹配项的 label
checkbox 把多选项 label 用逗号拼接
switch true 显示 labelOnYesfalse 显示 labelOffNo
datetime 默认字符串化
textarea 默认字符串化

筛选、排序、固定列

排序

  • 点击表头右侧下拉按钮
  • 选择 ASCDESC
  • 排序状态写在 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 一样,所以这里也能用:

  • name
  • label
  • type
  • setting
  • options
  • if

适合注册的场景

  • 你要让字段编辑器认识一个新的 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