Table 表格
欄配置式表格。支援排序、固定欄、斑馬紋、儲存格插槽,以及大資料量的虛擬捲動(core 純函式計算可視視窗,自研不引庫)。
基礎用法
基礎用法
名稱
職責
城市
沈硯
架構
杭州
蘇晚
前端
蘇州
陸離
設計
北京
vue
<script setup>
const cols = [
{ prop: 'name', label: '名称', width: 140 },
{ prop: 'role', label: '职责' },
{ prop: 'city', label: '城市', width: 120 },
]
const rows = [
{ id: 1, name: '沈砚', role: '架构', city: '杭州' },
{ id: 2, name: '苏晚', role: '前端', city: '苏州' },
{ id: 3, name: '陆离', role: '设计', city: '北京' },
]
</script>
<template>
<LhTable :data="rows" :columns="cols" />
</template>vue
<script>
export default {
data() {
return {
cols: [
{ prop: 'name', label: '名称', width: 140 },
{ prop: 'role', label: '职责' },
{ prop: 'city', label: '城市', width: 120 },
],
rows: [
{ id: 1, name: '沈砚', role: '架构', city: '杭州' },
{ id: 2, name: '苏晚', role: '前端', city: '苏州' },
{ id: 3, name: '陆离', role: '设计', city: '北京' },
],
}
},
}
</script>
<template>
<LhTable :data="rows" :columns="cols" />
</template>排序與固定欄
排序 + 固定欄 + 斑馬紋
名稱
職責
城市
沈硯
34
架構
杭州
蘇晚
28
前端
蘇州
陸離
31
設計
北京
vue
<script setup>
const sortCols = [
{ prop: 'name', label: '名称', width: 140, fixed: 'left' },
{ prop: 'age', label: '年龄', width: 100, sortable: true, align: 'center' },
{ prop: 'role', label: '职责' },
{ prop: 'city', label: '城市', width: 120 },
]
</script>
<template>
<LhTable :data="rows" :columns="sortCols" stripe border size="small" />
</template>vue
<script>
export default {
data() {
return {
sortCols: [
{ prop: 'name', label: '名称', width: 140, fixed: 'left' },
{ prop: 'age', label: '年龄', width: 100, sortable: true, align: 'center' },
{ prop: 'role', label: '职责' },
{ prop: 'city', label: '城市', width: 120 },
],
}
},
}
</script>
<template>
<LhTable :data="rows" :columns="sortCols" stripe border size="small" />
</template>虛擬捲動(大資料量)
設定 height 即啟用:只繪製可視視窗內的列,rowHeight 需與實際列高一致。
虛擬捲動 1000 列
名稱
職責
城市
行 1
架構
杭州
行 2
前端
杭州
行 3
架構
杭州
行 4
前端
杭州
行 5
架構
杭州
行 6
前端
杭州
vue
<script setup lang="ts">
const cols = [
{ prop: 'name', label: '名称', width: 140 },
{ prop: 'role', label: '职责' },
{ prop: 'city', label: '城市', width: 120 },
]
const bigRows = Array.from({ length: 1000 }, (_, i) => ({
id: i + 1,
name: `行 ${i + 1}`,
role: i % 2 === 0 ? '架构' : '前端',
city: '杭州',
}))
</script>
<template>
<LhTable :data="bigRows" :columns="cols" :height="240" :row-height="48" />
</template>vue
<script>
export default {
data() {
return {
cols: [
{ prop: 'name', label: '名称', width: 140 },
{ prop: 'role', label: '职责' },
{ prop: 'city', label: '城市', width: 120 },
],
bigRows: Array.from({ length: 1000 }, (_, i) => ({
id: i + 1,
name: `行 ${i + 1}`,
role: i % 2 === 0 ? '架构' : '前端',
city: '杭州',
})),
}
},
}
</script>
<template>
<LhTable :data="bigRows" :columns="cols" :height="240" :row-height="48" />
</template>自訂儲存格
插槽自訂
名稱
職責
沈硯
架構
蘇晚
前端
陸離
設計
vue
<script setup lang="ts">
const rows = [
{ id: 1, name: '沈砚', age: 34, role: '架构', city: '杭州' },
{ id: 2, name: '苏晚', age: 28, role: '前端', city: '苏州' },
{ id: 3, name: '陆离', age: 31, role: '设计', city: '北京' },
]
const slotCols = [
{ prop: 'name', label: '名称', width: 140 },
{ prop: 'role', label: '职责', slot: 'role' },
]
</script>
<template>
<!-- 職責欄插槽自訂 -->
<LhTable :data="rows" :columns="slotCols">
<template #role="{ value }">
<LhTag :type="value === '架构' ? 'primary' : 'info'">{{ value }}</LhTag>
</template>
</LhTable>
</template>vue
<script>
export default {
data() {
return {
rows: [
{ id: 1, name: '沈砚', age: 34, role: '架构', city: '杭州' },
{ id: 2, name: '苏晚', age: 28, role: '前端', city: '苏州' },
{ id: 3, name: '陆离', age: 31, role: '设计', city: '北京' },
],
slotCols: [
{ prop: 'name', label: '名称', width: 140 },
{ prop: 'role', label: '职责', slot: 'role' },
],
}
},
}
</script>
<template>
<!-- 職責欄插槽自訂 -->
<LhTable :data="rows" :columns="slotCols">
<template #role="{ value }">
<LhTag :type="value === '架构' ? 'primary' : 'info'">{{ value }}</LhTag>
</template>
</LhTable>
</template>多層表頭
欄配置巢狀 children 即得多層表頭,可無限巢狀;表體按葉子欄對齊。
兩層表頭
名稱
項目信息
職責
城市
沈硯
架構
杭州
34
蘇晚
前端
蘇州
28
陸離
設計
北京
31
vue
<script setup>
const nestedCols = [
{ prop: 'name', label: '名称', width: 140, fixed: 'left' },
{
label: '项目信息',
children: [
{ prop: 'role', label: '职责' },
{ prop: 'city', label: '城市', width: 120 },
],
},
{ prop: 'age', label: '年龄', sortable: true, align: 'center' },
]
</script>
<template>
<LhTable :data="rows" :columns="nestedCols" border />
</template>vue
<script>
export default {
data() {
return {
nestedCols: [
{ prop: 'name', label: '名称', width: 140, fixed: 'left' },
{
label: '项目信息',
children: [
{ prop: 'role', label: '职责' },
{ prop: 'city', label: '城市', width: 120 },
],
},
{ prop: 'age', label: '年龄', sortable: true, align: 'center' },
],
}
},
}
</script>
<template>
<LhTable :data="rows" :columns="nestedCols" border />
</template>篩選
欄配置 filters 後表頭出現篩選觸發器;勾選選項後點「確定」生效、「重設」清空。 filterMultiple: false 為單選,filterMethod 自訂判定,受控用法見下例。
欄篩選(受控)
名稱
職責
沈硯
架構
蘇晚
前端
陸離
設計
當前篩選:{}
vue
<script setup>
import { ref } from 'vue'
const filteredValues = ref({})
const filterCols = [
{ prop: 'name', label: '名称', width: 140 },
{
prop: 'role', label: '职责', filters: [
{ text: '架构', value: '架构' },
{ text: '前端', value: '前端' },
{ text: '设计', value: '设计' },
],
},
]
function onFilterChange({ prop, values }) {
console.log('filterChange', prop, values)
}
</script>
<template>
<!-- 受控篩選 -->
<LhTable
:data="rows"
:columns="filterCols"
v-model:filtered-values="filteredValues"
@filter-change="onFilterChange"
/>
</template>vue
<script>
export default {
data() {
return {
filteredValues: {},
filterCols: [
{ prop: 'name', label: '名称', width: 140 },
{
prop: 'role', label: '职责', filters: [
{ text: '架构', value: '架构' },
{ text: '前端', value: '前端' },
{ text: '设计', value: '设计' },
],
},
],
}
},
methods: {
onFilterChange({ prop, values }) {
console.log('filterChange', prop, values)
},
},
}
</script>
<template>
<!-- 受控篩選 -->
<LhTable
:data="rows"
:columns="filterCols"
v-model:filtered-values="filteredValues"
@filter-change="onFilterChange"
/>
</template>欄寬拖曳
開啟 resizable(總開關)並在欄上標記 resizable: true(葉子欄且為數字 width)。 表頭右緣出現拖曳控點,聚焦後 ←/→ 每次 ±10px,拖曳下限 40px。
拖曳調整欄寬
名稱
職責
城市
沈硯
架構
杭州
蘇晚
前端
蘇州
陸離
設計
北京
最近調整:(未調整)
vue
<script setup>
import { ref } from 'vue'
const resizeLog = ref('(未调整)')
const resizeCols = [
{ prop: 'name', label: '名称', width: 140, resizable: true, fixed: 'left' },
{ prop: 'role', label: '职责' },
{ prop: 'city', label: '城市', width: 120, resizable: true },
]
function onColumnResize({ prop, width }) {
resizeLog.value = `${prop} → ${width}px`
}
</script>
<template>
<!-- 欄寬拖曳 -->
<LhTable :data="rows" :columns="resizeCols" resizable border @column-resize="onColumnResize" />
</template>vue
<script>
export default {
data() {
return {
resizeLog: '(未调整)',
resizeCols: [
{ prop: 'name', label: '名称', width: 140, resizable: true, fixed: 'left' },
{ prop: 'role', label: '职责' },
{ prop: 'city', label: '城市', width: 120, resizable: true },
],
}
},
methods: {
onColumnResize({ prop, width }) {
this.resizeLog = `${prop} → ${width}px`
},
},
}
</script>
<template>
<!-- 欄寬拖曳 -->
<LhTable :data="rows" :columns="resizeCols" resizable border @column-resize="onColumnResize" />
</template>列鍵、最大高度與預設排序
row-key 指定列唯一鍵(推薦);max-height 限高後表頭吸頂、表體捲動;sorter 自訂排序函式;default-sort-prop / default-sort-order 設定初始排序;default-filtered-values 設定初始篩選。
rowKey / maxHeight / 預設排序
名稱
職責
沈硯
34
架構
陸離
31
設計
蘇晚
28
前端
vue
<script setup>
const sorterCols = [
{ prop: 'name', label: '名称', width: 140 },
{ prop: 'age', label: '年龄', sortable: true, sorter: (a, b) => a.age - b.age },
{ prop: 'role', label: '职责' },
]
</script>
<template>
<LhTable
:data="rows"
:columns="sorterCols"
row-key="name"
max-height="200"
default-sort-prop="age"
default-sort-order="desc"
/>
</template>vue
<script>
export default {
data() {
return {
sorterCols: [
{ prop: 'name', label: '名称', width: 140 },
{ prop: 'age', label: '年龄', sortable: true, sorter: (a, b) => a.age - b.age },
{ prop: 'role', label: '职责' },
],
}
},
}
</script>
<template>
<LhTable
:data="rows"
:columns="sorterCols"
row-key="name"
max-height="200"
default-sort-prop="age"
default-sort-order="desc"
/>
</template>API
LhTable Props
| 屬性名 | 說明 | 類型 | 預設值 |
|---|---|---|---|
| data | 列資料 | [LhTableRow](#type-LhTableRow)[] | — |
| columns | 欄配置 | [LhTableColumn](#type-LhTableColumn)[] | — |
| height | 表體高度(設定後啟用虛擬捲動;數字為 px) | string | number | — |
| maxHeight | 非虛擬模式下的最大高度 | string | number | — |
| rowHeight | 虛擬捲動列高(需與實際列高一致) | number | [LH_TABLE_ROW_HEIGHT](#const-LH_TABLE_ROW_HEIGHT) |
| stripe | 斑馬紋 | boolean | false |
| border | 儲存格邊框 | boolean | false |
| size | 尺寸 | "small" | "default" | "large" | 'default' |
| rowKey | 列鍵欄位名 | string | 'id' |
| defaultSortProp | 預設排序欄 | string | — |
| defaultSortOrder | 預設排序方向 | "asc" | "desc" | — |
| sorter | 自訂排序函式 | (a: LhTableRow, b: LhTableRow) => number | — |
| resizable | 開啟欄寬拖曳(仍需欄級 resizable 配合) | boolean | false |
| defaultFilteredValues | 預設篩選狀態(非受控,鍵為欄 prop) | { [x: string]: (string | number)[]; } | () => ({}) |
| filteredValues | 受控篩選狀態(鍵為欄 prop) | { [x: string]: (string | number)[]; } | — |
| sx | sx 快速版面配置屬性 | LhSxProps | — |
Events
| 事件名 | 說明 | 回呼參數 |
|---|---|---|
| sortChange | 排序變化時觸發(換欄從 asc 重新開始,同欄循環 asc → desc → 取消) | (payload: { prop: string, order: 'asc' | 'desc' | undefined }) |
| rowClick | 點選某一列時觸發 | (row: LhTableRow, index: number) |
| filterChange | 篩選「確定」或「重設」提交後觸發 | (payload: { prop: string, values: (string | number)[] }) |
| update:filteredValues | 篩選狀態變化時觸發(v-model:filtered-values) | (values: Record<string, (string | number)[]>) |
| columnResize | 欄寬調整完成後觸發(拖曳結束或鍵盤 ←/→) | (payload: { prop: string, width: number }) |
插槽:empty、<column.slot>(作用域參數 { row, column, index, value })。
LhTableColumn
| 欄位 | 說明 | 類型 |
|---|---|---|
| prop | 資料欄位名 | string |
| label | 欄標題 | string |
| width | 欄寬(數字為 px,未設則均分) | number | string |
| align | 對齊 | 'left' | 'center' | 'right' |
| fixed | 固定欄(需配合 width) | 'left' | 'right' |
| sortable | 可排序 | boolean |
| slot | 自訂儲存格插槽名 | string |
| formatter | 值格式化 | (row, value) => string |
| children | 子欄(配置即得多層表頭) | LhTableColumn[] |
| filters | 欄篩選選項(僅葉子欄生效) | LhTableFilterOption[] |
| filterMethod | 自訂篩選判定(預設按 prop 全等比對) | (value, row) => boolean |
| filterMultiple | 篩選面板是否多選 | boolean |
| resizable | 該欄可拖寬(需為數字 width 的葉子欄) | boolean |
無障礙
role="grid"+aria-rowcount/aria-colcount;列row、欄頭columnheader、儲存格gridcell- 排序欄頭暴露
aria-sort(none/ascending/descending),排序觸發為真實<button> - 固定欄用 CSS
position: sticky(不自製吸附計算) - 篩選觸發器
aria-haspopup+aria-expanded,面板為role="group" - 欄寬控點為
role="separator",可聚焦並用 ←/→ 鍵 ±10px 調整
類型定義
本元件對外匯出的 TypeScript 類型結構如下(自動提取自 packages/components/table/src/types.ts 與 @lh-ui/share/core/* 原始碼,點選各條目展開檢視完整定義;屬性說明見上方 Props 表):
LhTableProps
ts
export interface LhTableProps {
/** 列資料 */
data: LhTableRow[]
/** 欄配置 */
columns: LhTableColumn[]
/** 表體高度(設定後啟用虛擬捲動;數字為 px) */
height?: number | string
/** 非虛擬模式下的最大高度 */
maxHeight?: number | string
/** 虛擬捲動列高(需與實際列高一致) */
rowHeight?: number
/** 斑馬紋 */
stripe?: boolean
/** 儲存格邊框 */
border?: boolean
/** 尺寸 */
size?: 'small' | 'default' | 'large'
/** 列鍵欄位名 */
rowKey?: string
/** 預設排序欄 */
defaultSortProp?: string
/** 預設排序方向 */
defaultSortOrder?: LhTableSortOrder
/** 自訂排序函式 */
sorter?: (a: LhTableRow, b: LhTableRow) => number
/** 開啟欄寬拖曳(仍需欄級 resizable 配合,且欄須為數字 width) */
resizable?: boolean
/** 預設篩選狀態(非受控,鍵為欄 prop) */
defaultFilteredValues?: Record<string, (string | number)[]>
/** 受控篩選狀態(鍵為欄 prop) */
filteredValues?: Record<string, (string | number)[]>
/** sx 快速版面配置屬性 */
sx?: LhSxProps
}LhTableSortOrder (來自 @lh-ui/share/core/table)
ts
/** 排序方向 */
export type LhTableSortOrder = 'asc' | 'desc'LhTableRow (來自 @lh-ui/share/core/table)
ts
/** 列資料類型 */
export type LhTableRow = Record<string, any>LhTableColumn (來自 @lh-ui/share/core/table)
ts
/** 欄配置 */
export interface LhTableColumn {
/** 資料欄位名 */
prop: string
/** 欄標題 */
label: string
/** 欄寬(數字為 px) */
width?: number | string
/** 對齊 */
align?: LhTableAlign
/** 固定欄(需配合 width) */
fixed?: 'left' | 'right'
/** 可排序 */
sortable?: boolean
/** 自訂儲存格插槽名 */
slot?: string
/** 值格式化 */
formatter?: (row: LhTableRow, value: any) => string
/** 子欄(配置即得多層表頭,可無限巢狀) */
children?: LhTableColumn[]
/** 欄篩選選項(僅葉子欄生效) */
filters?: LhTableFilterOption[]
/** 自訂篩選判定(預設按 prop 全等比對) */
filterMethod?: (value: string | number, row: LhTableRow) => boolean
/** 篩選面板是否多選(預設 true) */
filterMultiple?: boolean
/** 該欄可拖寬(需為數字 width 的葉子欄) */
resizable?: boolean
}LhTableFilterOption (來自 @lh-ui/share/core/table)
ts
/** 篩選選項 */
export interface LhTableFilterOption {
/** 選項文案 */
text: string
/** 選項值(參與 filterMethod 比對) */
value: string | number
}LhTableAlign (來自 @lh-ui/share/core/table)
ts
/** 儲存格對齊 */
export type LhTableAlign = 'left' | 'center' | 'right'常數
本元件用到的公開常數如下(可在 Props 表的「類型 / 預設值」欄點選同名條目跳轉至此):
LH_TABLE_ROW_HEIGHT (來自 @lh-ui/share/core/table)
ts
/** 虛擬捲動預設列高 */
export const LH_TABLE_ROW_HEIGHT = 48LH_TABLE_OVERSCAN (來自 @lh-ui/share/core/table)
ts
/** 視窗上下各多繪製幾列(捲動白屏緩衝) */
export const LH_TABLE_OVERSCAN = 3LH_TABLE_MIN_COL_WIDTH (來自 @lh-ui/share/core/table)
ts
// ============================================================================
// 欄寬拖曳
// ============================================================================
/** 拖寬下限(px),再窄儲存格內容無法容納 */
export const LH_TABLE_MIN_COL_WIDTH = 40