Skip to content
API 风格:

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斑馬紋booleanfalse
border儲存格邊框booleanfalse
size尺寸"small" | "default" | "large"'default'
rowKey列鍵欄位名string'id'
defaultSortProp預設排序欄string—
defaultSortOrder預設排序方向"asc" | "desc"—
sorter自訂排序函式(a: LhTableRow, b: LhTableRow) => number—
resizable開啟欄寬拖曳(仍需欄級 resizable 配合)booleanfalse
defaultFilteredValues預設篩選狀態(非受控,鍵為欄 prop){ [x: string]: (string | number)[]; }() => ({})
filteredValues受控篩選狀態(鍵為欄 prop){ [x: string]: (string | number)[]; }—
sxsx 快速版面配置屬性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 = 48
LH_TABLE_OVERSCAN (來自 @lh-ui/share/core/table)
ts
/** 視窗上下各多繪製幾列(捲動白屏緩衝) */
export const LH_TABLE_OVERSCAN = 3
LH_TABLE_MIN_COL_WIDTH (來自 @lh-ui/share/core/table)
ts
// ============================================================================
// 欄寬拖曳
// ============================================================================

/** 拖寬下限(px),再窄儲存格內容無法容納 */
export const LH_TABLE_MIN_COL_WIDTH = 40