SwTable
SwTable 是管理台数据表格:columns 配置驱动表头与列序,单元格默认渲染 item[key],复杂内容(缩略图 / 状态徽章 / 操作按钮组)走 #cell-<key> 作用域插槽由业务自渲染;内置管理台高频三大件——受控列排序(v-model:sort)、行选择批量(v-model:selected)、loading 过渡,以及斑马纹 / 密度 / 粘性表头视觉旋钮。视觉对齐 shadcn Table 基调。
语义 <table> 直出(SEO / a11y),根包一层 .sw-table 容器做 overflow-x: auto——窄屏时表格自身横向滚动、不撑爆页面。基线样式(边框 + 圆角 / 表头弱化底 / 行 hover / 单元格 padding)走 --sw-table-* token,applier 覆盖变量即换肤。
边界:分页 / 筛选不内置——用 SwPagination / SwSegmented / SwSelect 页面层组合;排序只管交互不排数据(受控,见下);固定列 / 列宽拖拽 / 树形 / 虚拟滚动等重功能等真实数据点再议。这对齐 framework「只管布局不绑业务 UI」铁律。
基础用法
基础列(缺省单元格取 item[key])
<script setup lang="ts">
const COLUMNS = [
{ key: 'name', label: '名称' },
{ key: 'category', label: '分类' },
{ key: 'count', label: '集数', width: '4rem', align: 'center' },
]
const rows = [{ id: 1, name: '未来都市', category: '动画', count: 12 }]
</script>
<template>
<SwTable :columns="COLUMNS" :items="rows" />
</template>
单元格 / 表头插槽(运营台列表形态)
#cell-<key> 按列自定义单元格(作用域 { item, value, index }),#header-<key> 自定义表头(作用域 { column }):
状态徽章 + 行内操作(#cell-* 插槽)
空态占位
#empty 在 items 为空时渲染为一行整宽占位;不传则空 tbody:
#empty 空态占位
列排序(受控)
column.sortable 让列头可点,三态循环 asc → desc → 无 并经 v-model:sort 受控回显(表头渲染指示箭头 + aria-sort)。组件不排数据——业务拿到排序态后本地排(toSorted)或带参调后端(管理台主流是服务端排序):
列排序(受控三态,本 demo 本地排序)
<script setup lang="ts">
const sort = ref<SwTableSort | undefined>()
// 服务端排序:排序态变化即带参重新拉数据
watch(sort, (s) => refetch({ orderBy: s?.key, order: s?.order }))
</script>
<template>
<SwTable v-model:sort="sort" :columns="COLUMNS" :items="rows" />
</template>
行选择与批量操作
selectable 渲染 checkbox 选择列 + 表头全选(部分选中时半选 indeterminate),选中集合走 v-model:selected(rowKey 值数组):
行选择 + 批量操作
loading 过渡态
loading 让表格半透明 + 遮罩转圈,保留结构不闪白——服务「已有数据、翻页 / 筛选刷新中」的过渡;首屏无数据的 loading 仍归页面层四态(骨架屏),两者互补:
翻页刷新过渡(loading + SwPagination 组合)
视觉旋钮:斑马纹与密度
striped 偶数行弱化底、size="compact" 收紧单元格 padding,数据密集页常用组合:
斑马纹 + 紧凑密度(可切换对比)
粘性表头(长列表容器内滚动)
sticky-header + --sw-table-max-height 让容器变滚动区、表头钉在顶部:
粘性表头(30 行容器内滚动)
管理台综合形态
排序 + 行选批量 + loading + 行点击 + 状态徽章 + 行内操作的完整组合(真实运营台列表页形态):
管理台综合(全能力组合)
与分页组合(页面层数据流)
表格不管分页——页码翻页用 SwPagination 受控替换 items:
<template>
<SwTable :columns="COLUMNS" :items="pageItems">
<template #empty>暂无数据</template>
</SwTable>
<SwPagination v-model:page="page" :total="total" :page-size="20" />
</template>
Props
| 名称 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
columns | SwTableColumn[] | ✓ | — | 列配置,每项 { key, label?, width?, align?, sortable? } |
items | T[] | ✓ | — | 行数据(泛型,组件不关心形状) |
rowKey | string | ((item: T) => string | number) | 'id' | 行 key 字段名或取值函数;解析不出回退 index | |
striped | boolean | false | 斑马纹(偶数行弱化底) | |
size | 'default' | 'compact' | 'default' | 密度(compact 收紧单元格 padding)。⚠️ 与 SwThemeSwitch / SwLangSwitch 的 size(图标 px 数值)不是一回事——那两个是尺寸、这个是密度枚举,对齐 Ant / Element 的 Table.size | |
stickyHeader | boolean | false | 粘性表头(配 --sw-table-max-height 让容器变滚动区) | |
loading | boolean | false | 刷新过渡态(遮罩 + 半透明,保留结构) | |
selectable | boolean | false | 渲染 checkbox 选择列 + 表头全选 | |
sort(v-model:sort) | SwTableSort | undefined | undefined | 当前排序态;undefined = 无排序 | |
selected(v-model:selected) | (string | number)[] | [] | 选中行的 rowKey 值集合 |
SwTableColumn:{ key: string; label?: string; width?: string; align?: 'left' \| 'center' \| 'right'; sortable?: boolean }——key 既是缺省单元格取值字段,也是 #cell-<key> / #header-<key> 插槽名后缀;width 落到 <th>(CSS 宽度值);align 默认 'left';sortable 让列头可点排序。
SwTableSort:{ key: string; order: 'asc' \| 'desc' }。两个类型均可从包名导入:import type { SwTableColumn, SwTableSort } from '@swift-web/framework'。
Events
| 名称 | 携带 | 说明 |
|---|---|---|
update:sort | SwTableSort | undefined | 点 sortable 列头触发(asc → desc → undefined 三态循环) |
update:selected | (string | number)[] | 行选 / 全选变化 |
row-click | (item, index) | 点击行触发(行内 button / a / input 等 interactive 元素的点击不触发);有监听时行自动 cursor: pointer |
行内操作(编辑 / 上下架 / 删除)仍在 #cell-<key> 插槽内用 SwButton 等自带事件。
Slots
| 名称 | 作用域参数 | 说明 |
|---|---|---|
cell-<key> | { item, value, index } | 逐列自定义单元格。不传则渲染 String(item[key] ?? '') |
header-<key> | { column } | 逐列自定义表头。不传则渲染 column.label。⚠️ 可排序列的插槽内容会被包进 <button>(键盘可达),故只能放 phrasing content——塞 <button> / <a> 会构成嵌套交互元素 |
empty | — | items 为空时渲染为一行整宽占位(可选;不传则空 tbody) |
样式定制 Token
表格皮肤经 --sw-table-* token 覆盖(fallback 即默认视觉):
| Token | 默认值 | 作用点 |
|---|---|---|
--sw-table-bg | var(--card) | 表格底色 |
--sw-table-border | var(--border) | 外框与行分隔线颜色 |
--sw-table-radius | var(--radius) | 表格圆角 |
--sw-table-head-color | var(--muted-foreground) | 表头文字色 |
--sw-table-head-bg | color-mix(in srgb, var(--foreground) 6%, transparent) | 表头底色(foreground 叠加:任意主题下与行底可辨) |
--sw-table-hover-bg | color-mix(in srgb, var(--primary) 4%, transparent) | 行 hover 底色 |
--sw-table-striped-bg | color-mix(in srgb, var(--foreground) 3%, transparent) | 斑马纹偶数行底色(striped) |
--sw-table-cell-padding | 0.75rem 1rem | 单元格内边距 |
--sw-table-cell-padding-compact | 0.375rem 0.75rem | 紧凑密度内边距(size="compact") |
--sw-table-font-size | inherit | 表格字号 |
--sw-table-max-height | none | 容器最大高(设值即容器内滚动,配 stickyHeader) |
注意事项
- 可排序表头是真
<button>:sortable列的表头内容被包进<button>(W3C ARIA APG 的 Sortable Table 范式),键盘可 Tab 聚焦、Enter / Space 排序,aria-sort仍在<th>上。想改 hover 视觉请选button.th-inner:hover。 row-click是便捷层,不是唯一通路:行本身有意不做成可聚焦元素(长表会凭空多出等量 Tab 停靠点)。行内必须有承担同一动作的真实可聚焦元素(详情链接 / 操作按钮),否则键盘与读屏用户根本触达不了该动作——那是 WCAG 2.1.1 Keyboard(Level A)不达标,且鼠标测试永远看不出来。dev 期会console.warn提示,生产构建整段被移除。- 排序不排数据:
v-model:sort只是受控交互态——业务必须自己排(本地toSorted或带参调后端),否则点了列头「只有箭头变、数据不动」。 - loading 与四态的分工:
loading服务「已有数据刷新中」的过渡;首屏无数据的 loading / error / empty 仍归页面层四态(骨架屏 / 重试入口),#empty仅是空数据行的轻量兜底。 - 行内操作不触发 row-click:
#cell-*内的 button / a / input 点击不会冒泡成行点击,无需手动stopPropagation。 - 不内置分页 / 筛选:用 SwPagination / SwSegmented / SwSelect 页面层组合。
- 行内异步操作记得锁 pending:
#cell-*里的操作按钮遵守 SB-001(async@click配:loading),如上下架按行 id 锁。 - 窄屏横向滚动是内置行为:
.sw-table容器自带overflow-x: auto,列多时给列设width保证可读宽度即可,别在外层再包滚动容器。