跳转正文

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 阴影与表格视觉区域否
YTableRendererListDefinition → UTable / TanStack 适配;列、排序、选择列、Resize否
YListQuery / YQueryPanel / YListFilters查询区内部实现否
YMetrics独立统计条 / 统计卡片是,来自 metrics 入口

内部没有额外的 YDataTableView、YListSurface 或完整列表 DataTableConfig 转接层。

YDataTable 参数与事件 ​

入口类型说明
tableReturnType<typeof useYDataTable<T, Q>>必填;列表 Runtime
presentationDataTablePresentation<T>可选;只描述额外呈现选择
fillboolean填充已有高度;不自行创建视口高度
viewKeystring结构性视图切换时重建桌面 / 移动数据区
toolsMenuLabelstringToolbar 更多操作文案
@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
capabilities

capabilities ​

当前支持:

能力说明
paginationfalse 或 pageSize / sizes
selectionsingle / multiple;page / query scope
columnSettings列顺序、显隐、宽度、固定列
filterSettings筛选项顺序与显隐
exportselected / 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
  }
]

YoyoBoot · 从真实项目走向可复用能力