Skip to content
API 风格:

Таблица ​

Таблица на основе конфигурации столбцов. Поддерживает сортировку, зафиксированные столбцы, полосатую раскраску, слоты для ячеек, а также виртуальную прокрутку для больших объёмов данных (видимое окно вычисляется чистыми функциями 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Включить перетаскивание ширины столбцов (по-прежнему требуется column-level resizable)booleanfalse
defaultFilteredValuesСостояние фильтра по умолчанию (неуправляемое, ключи — prop столбцов){ [x: string]: (string | number)[]; }() => ({})
filteredValuesУправляемое состояние фильтра (ключи — prop столбцов){ [x: string]: (string | number)[]; }—
sxСвойство sx для быстрой вёрсткиLhSxProps—

События ​

СобытиеОписаниеПараметры обратного вызова
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
  /** Включить перетаскивание ширины столбцов (по-прежнему требуется column-level 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

Красота традиций — основа кода