切换主题
YDataTable 列表组件说明
YDataTable 是 Yoyo Admin 列表工作台的公开 UI 入口。业务使用:
text
defineDataTable
↓
useYDataTable
↓
YDataTable查询草稿、请求、分页、选择、排序、成功快照和视图状态由 useYDataTable 持有;YDataTable 负责把这些状态呈现为统一的查询区、操作区、选择反馈、数据状态、桌面表格、移动记录和页脚。
页面标题、说明、统计等页面级内容使用 YListPageShell。弹窗、抽屉或嵌入区域也可以直接使用 YDataTable,不要求额外页面壳。
公开入口:
ts
import {
YDataTable,
defineDataTable,
useYDataTable,
type DataTablePresentation
} from '@yoyoboot/ui-patterns/list-workbench'
import { YListPageShell } from '@yoyoboot/ui-patterns/page-shells'不要从 packages/ui-patterns/src/** 或 list-workbench/internal/** 导入物理实现。
最小用法
下面示例使用本地集合 source。业务仍然通过同一套 useYDataTable Runtime,不再构造额外的 DataTableConfig。
vue
<script setup lang="ts">
import {
YDataTable,
defineDataTable,
useYDataTable
} from '@yoyoboot/ui-patterns/list-workbench'
import { YListPageShell } from '@yoyoboot/ui-patterns/page-shells'
interface Member {
id: string
name: string
}
type MemberQuery = {
keyword: string
}
const rows: Member[] = [
{ id: 'M-001', name: '成员甲' },
{ id: 'M-002', name: '成员乙' }
]
const definition = defineDataTable<Member, MemberQuery>({
key: 'members',
rowKey: 'id',
query: {
defaults: () => ({ keyword: '' }),
fields: [
{
key: 'keyword',
label: '搜索成员',
control: 'input',
placeholder: '输入姓名'
}
]
},
capabilities: {
pagination: { pageSize: 20 },
selection: { mode: 'multiple', scope: 'page' }
},
columns: [
{ id: 'name', field: 'name', label: '姓名' }
]
})
const table = useYDataTable({
definition,
contextKey: 'members-demo',
rowLabel: row => row.name,
source: {
mode: 'collection',
async read(intent) {
const keyword = intent.query.keyword.trim()
return {
items: keyword
? rows.filter(row => row.name.includes(keyword))
: rows
}
}
}
})
</script>
<template>
<YListPageShell
title="会员列表"
description="查询和选择成员。"
>
<YDataTable :table="table" />
</YListPageShell>
</template>collection source 返回过滤后的完整集合,Runtime 在未关闭分页时负责当前页切片。排序仍属于 source 对 intent.sorting 的处理责任;Runtime 不会替业务猜排序规则。后端分页使用 mode: 'paged',由 source 返回当前页 items 与 total。
当前组件关系
text
YListPageShell
└─ YDataTable
├─ Toolbar / Query
├─ Selection / Filter Chips
├─ Loading / Empty / Error
├─ Mobile Records
├─ Footer / Pagination / Column Settings
└─ YTableViewport
└─ YTableRenderer
└─ Nuxt UI UTable| 名称 | 职责 | 是否公开 |
|---|---|---|
| YListPageShell | 页面标题、说明、统计、页面动作和页面布局 | 是 |
| YDataTable | 完整列表工作面;直接消费 useYDataTable Runtime | 是 |
| YTableViewport | 滚动、密度、Sticky Header、Pinned Column 阴影与表格视觉区域 | 否 |
| YTableRenderer | ListDefinition → UTable / TanStack 适配;列、排序、选择列、Resize | 否 |
| YListQuery / YQueryPanel / YListFilters | 查询区内部实现 | 否 |
| YMetrics | 独立统计条 / 统计卡片 | 是,来自 metrics 入口 |
内部没有额外的 YDataTableView、YListSurface 或完整列表 DataTableConfig 转接层。
YDataTable 参数与事件
| 入口 | 类型 | 说明 |
|---|---|---|
table | ReturnType<typeof useYDataTable<T, Q>> | 必填;列表 Runtime |
presentation | DataTablePresentation<T> | 可选;只描述额外呈现选择 |
fill | boolean | 填充已有高度;不自行创建视口高度 |
viewKey | string | 结构性视图切换时重建桌面 / 移动数据区 |
toolsMenuLabel | string | Toolbar 更多操作文案 |
@action | (key: string) => void | 处理 presentation tools 或未由 Runtime action 执行的页面操作 |
scrollToIndex(index, rowHeight) | 暴露方法 | 固定行高场景滚动到从 0 开始的记录位置 |
列表数据、查询、分页、选择、请求状态、列偏好和排序都来自 table,不通过第二份 config 重复传递。
DataTablePresentation
DataTablePresentation<T> 只描述可选的 UI 呈现,不持有查询或请求状态:
ts
interface DataTablePresentation<T extends object> {
mobile?: {
title: (row: T) => string
fields: ListRecordField<T>[]
onOpen?: (row: T) => void
}
tools?: ListAction[]
policy?: ListPolicy
density?: 'compact' | 'comfortable'
table?: Omit<TableProps<T>, 'data' | 'columns'> & Record<string, unknown>
}mobile
用于标准窄屏记录卡:
title(row):记录主标题;fields:移动端字段;onOpen(row):可选详情入口。
记录 key、选择状态和选择操作直接来自 useYDataTable,不再接受另一套 mobile.key / selected / onSelect。
如果 defineDataTable.columns[].mobile 已声明 primary / field,且没有显式 presentation.mobile,YDataTable 可以从列定义生成标准移动呈现。
特殊移动页面可使用 #mobile-data 完整覆盖。
tools
追加页面级工具项。定义自身的 Toolbar 优先放在 defineDataTable.toolbar;需要由页面处理的额外工具可通过 presentation.tools + @action 接入。
policy
用于列与展示定义的权限 / Feature presentation 判断。它不替代后端授权,也不授予数据访问权限。
density
控制桌面表格的 compact / comfortable 行高和间距。
table
透传 Nuxt UI / TanStack Table 的高级呈现能力,例如:
- grouping;
- expanded;
- tree
getSubRows; - virtualize;
- sticky;
- 自定义
ui; - 原生表格事件。
data 和 columns 仍由工作台统一生成,不通过 presentation 重复传入。
defineDataTable
defineDataTable<T, Q>() 是列表静态合同 Owner,主要负责:
text
key / rowKey
query
columns
toolbar
row / bulk actions
mobile column hints
capabilitiescapabilities
当前支持:
| 能力 | 说明 |
|---|---|
pagination | false 或 pageSize / sizes |
selection | single / multiple;page / query scope |
columnSettings | 列顺序、显隐、宽度、固定列 |
filterSettings | 筛选项顺序与显隐 |
export | selected / filtered 导出范围 |
未声明的能力不会因为页面自己写了控件而自动进入 Runtime。
columns
普通列优先声明:
ts
{
id: 'status',
field: 'status',
label: '状态',
sortable: true,
display: {
kind: 'status',
translate: true
}
}公共 display 支持当前 YCellValue 已实现的通用展示。真正特殊的交互 Cell 使用 table.cell 或 #列ID-cell。
选择列、标准 sortable header、普通 Row Actions、Resize 不需要 Feature 再实现一遍。
useYDataTable
useYDataTable 是列表 Runtime Owner。业务提供:
ts
useYDataTable({
definition,
source,
contextKey,
rowLabel,
storage?,
exportRecords?,
actions?,
resolveSelected?
})Runtime 负责:
- 查询草稿、校验与提交;
- 请求取消与过期响应隔离;
- paged / collection 两种 source;
- 成功结果快照;
- 首次 Loading / Error 与刷新失败旧结果保留;
- 分页;
- 排序;
- Selection 与跨页选择 key;
- 视图偏好;
- Filter Chips;
- Row / Bulk action busy/error;
- 导出所需成功快照。
保存、删除、导入、业务确认、真实 Permission / Tenant / Identity 仍归 Feature。
两种数据 Source
collection
适合本地确定性集合或 Starter Lab:
ts
source: {
mode: 'collection',
async read(intent, signal) {
signal.throwIfAborted()
return {
items: filterRows(allRows, intent.query)
}
}
}启用分页时 Runtime 对过滤后的集合切片;关闭 pagination 时返回全部成功结果。
paged
适合真实后端分页:
ts
source: {
mode: 'paged',
async read(intent, signal) {
const result = await api.read({
query: intent.query,
page: intent.page!,
pageSize: intent.pageSize!,
sorting: intent.sorting
}, signal)
return {
items: result.items,
total: result.total
}
}
}paged source 不能把 capabilities.pagination 显式设为 false;未声明时 Runtime 使用默认分页。页面不能把当前页伪装成完整集合。
查询与筛选
definition.query.fields 使用 ListFilter<Q>。当前常用 control:
| control | 能力 |
|---|---|
input | 文本输入 |
select | 单选 |
multiple | 多选 |
tree | 单选/多选树形筛选;复用 YTreePicker / YTreePanel |
remote | 远程搜索与回显;multiple: true 时多选 |
date | 日期 |
date-range | 日期区间 |
number-range | 数值区间 |
slot | 特殊业务筛选 |
section 区分 basic / advanced。自定义字段通过 #filter-字段名 插槽接入,不需要替换整个查询区。
远程字段提供 load(search, signal) 返回 { label, value }[],可选 resolve(value, signal) 用于已选 ID 的回显。默认值为单选字符串;配置 multiple: true 后,查询值为 string[],清空返回 []。搜索结果变化不会丢弃已选项;选中名称会保留,加载失败提供原控件内的重试入口,迟到响应不会覆盖新搜索。搜索选项只更新字段草稿,列表请求仍按 query.mode 决定何时提交。
树形字段配置 items(稳定 key / label / children)与可选 multiple。例如 { key: 'permissionNames', label: '权限', control: 'tree', multiple: true, items: permissionTree }。它只修改查询草稿;Search 提交,Reset 清空。选择父节点不会自动提交所有后代,授权编辑的级联规则不适用于列表筛选。独立表单可以从 @yoyoboot/ui-patterns/tree 导入 YTreePicker,使用同一交互。
带 Tree Pane 的页面
组织导航等页面在 YListPageShell 内组合 YTreeSplitView,不另写固定 Grid 或手机树容器:
vue
<YListPageShell title="组织机构" fill max-width="full">
<YTreeSplitView :tree-pane="{ title: '组织导航', storageKey: 'organization-layout', defaultSize: 20, minSize: 14, maxSize: 26 }">
<template #tree="{ mobile, close }">
<YTreePanel v-model="selectedKey" :items="nodes" @update:model-value="mobile && close()" />
</template>
<YDataTable :table="table" />
</YTreeSplitView>
</YListPageShell>YTreePanel 来自 @yoyoboot/ui-patterns/tree,YTreeSplitView 与 TreePaneOptions 来自 @yoyoboot/ui-patterns/page-shells。尺寸单位为 rem,默认宽度/最小/最大为 18/14/26。桌面 ≥1024px 支持拖动、双击恢复默认、收起和重开;明确设置 storageKey 才记忆宽度。viewport ≤1023px 改用 Slideover,插槽 close() 只关闭 Tree Pane。选中组织、成员查询与业务保存仍由页面负责;普通列表页不引入 Split DOM。
Selection 与 Row Actions
Selection 来自 definition.capabilities.selection。
桌面选择列由 YTableRenderer 自动生成;移动记录复用同一 table.rowSelection。业务不要再建立第二套 checkbox state。
启用 selection 的表格默认支持点击普通区域即选择:
- multiple:点击普通单元格或空白区域切换 checkbox,再次点击取消;
- single:点击选中,重复点击已选记录保持选中(radio 语义);
- 点击按钮、链接、checkbox/radio、输入、下拉、label、可编辑内容、
[data-stop-row-selection]或正在拖选文字时不改变选择; - 桌面和移动共用同一个默认值(
presentation.selectOnRowClick,默认 true);已显式设置的mobile.selectOnClick优先; - 详情型表格用
selectOnRowClick: false关闭。关闭是真正的 opt-out:组件不向上游表格传onSelect,点击冒泡、preventDefault与 Tab 顺序和未启用行点击选择时一致。
当前可见记录中存在 selectable: false 的行时,桌面整表不启用行点击选择:上游表格只能整表控制行的 tabindex,逐行启用会让禁选行进入无效的 focus 顺序。显式 checkbox/radio 仍可用,移动记录按逐行 selectable 判断。
YRecordPicker 继续以确认作为父业务值的提交边界:行点击只改弹窗内的选择草稿,取消恢复打开前基线,确认只提交一次 keys。
Row / Bulk action 由 definition.actions 定义:
ts
actions: [
{
key: 'detail',
label: '查看详情',
placement: 'row'
},
{
key: 'delete',
label: '批量删除',
placement: 'bulk',
danger: true
}
]