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保證可讀寬度即可,別在外層再包滾動容器。