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