Таблица
Таблица на основе конфигурации столбцов. Поддерживает сортировку, зафиксированные столбцы, полосатую раскраску, слоты для ячеек, а также виртуальную прокрутку для больших объёмов данных (видимое окно вычисляется чистыми функциями core, собственная реализация без внешних библиотек).
Базовое использование
<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><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>Сортировка и зафиксированные столбцы
<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><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 должен совпадать с фактической высотой строки.
<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><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>Пользовательские ячейки
<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><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 в конфигурации столбцов создаёт многоуровневые заголовки с неограниченной вложенностью; тело таблицы выравнивается по листьевым столбцам.
<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><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 задаёт пользовательскую проверку; управляемый вариант использования показан в примере ниже.
Текущий фильтр: {}
<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><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.
Последнее изменение: (без изменений)
<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><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 — начальный фильтр.
<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><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 | Включить перетаскивание ширины столбцов (по-прежнему требуется column-level resizable) | boolean | false |
| 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
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)
/** Направление сортировки */
export type LhTableSortOrder = 'asc' | 'desc'LhTableRow (из @lh-ui/share/core/table)
/** Тип данных строки */
export type LhTableRow = Record<string, any>LhTableColumn (из @lh-ui/share/core/table)
/** Конфигурация столбца */
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)
/** Параметр фильтра */
export interface LhTableFilterOption {
/** Текст опции */
text: string
/** Значение опции (участвует в сравнении filterMethod) */
value: string | number
}LhTableAlign (из @lh-ui/share/core/table)
/** Выравнивание ячейки */
export type LhTableAlign = 'left' | 'center' | 'right'Константы
Далее приведены публичные константы, используемые компонентом (в столбце «Тип / По умолчанию» таблицы Props можно перейти к одноимённому элементу):
LH_TABLE_ROW_HEIGHT (из @lh-ui/share/core/table)
/** Высота строки по умолчанию для виртуальной прокрутки */
export const LH_TABLE_ROW_HEIGHT = 48LH_TABLE_OVERSCAN (из @lh-ui/share/core/table)
/** Сколько дополнительных строк рендерится сверху и снизу окна (буфер от белых полос при прокрутке) */
export const LH_TABLE_OVERSCAN = 3LH_TABLE_MIN_COL_WIDTH (из @lh-ui/share/core/table)
// ============================================================================
// Перетаскивание ширины столбца
// ============================================================================
/** Минимальная ширина при перетаскивании (px); при меньшей ширине содержимое ячейки перестаёт помещаться */
export const LH_TABLE_MIN_COL_WIDTH = 40