Tableau
Tableau configuré par colonnes. Prend en charge le tri, les colonnes fixes, les rayures de zèbre, les slots de cellule, ainsi que le défilement virtuel pour les gros volumes de données (fenêtre visible calculée par des fonctions pures du core, implémentation maison sans bibliothèque externe).
Utilisation de base
<script setup>
const cols = [
{ prop: 'name', label: 'Nom', width: 140 },
{ prop: 'role', label: 'Rôle' },
{ prop: 'city', label: 'Ville', width: 120 },
]
const rows = [
{ id: 1, name: 'Shen Yan', role: 'Architecte', city: 'Hangzhou' },
{ id: 2, name: 'Su Wan', role: 'Frontend', city: 'Suzhou' },
{ id: 3, name: 'Lu Li', role: 'Designer', city: 'Pékin' },
]
</script>
<template>
<LhTable :data="rows" :columns="cols" />
</template><script>
export default {
data() {
return {
cols: [
{ prop: 'name', label: 'Nom', width: 140 },
{ prop: 'role', label: 'Rôle' },
{ prop: 'city', label: 'Ville', width: 120 },
],
rows: [
{ id: 1, name: 'Shen Yan', role: 'Architecte', city: 'Hangzhou' },
{ id: 2, name: 'Su Wan', role: 'Frontend', city: 'Suzhou' },
{ id: 3, name: 'Lu Li', role: 'Designer', city: 'Pékin' },
],
}
},
}
</script>
<template>
<LhTable :data="rows" :columns="cols" />
</template>Tri et colonnes fixes
<script setup>
const sortCols = [
{ prop: 'name', label: 'Nom', width: 140, fixed: 'left' },
{ prop: 'age', label: 'Âge', width: 100, sortable: true, align: 'center' },
{ prop: 'role', label: 'Rôle' },
{ prop: 'city', label: 'Ville', width: 120 },
]
</script>
<template>
<LhTable :data="rows" :columns="sortCols" stripe border size="small" />
</template><script>
export default {
data() {
return {
sortCols: [
{ prop: 'name', label: 'Nom', width: 140, fixed: 'left' },
{ prop: 'age', label: 'Âge', width: 100, sortable: true, align: 'center' },
{ prop: 'role', label: 'Rôle' },
{ prop: 'city', label: 'Ville', width: 120 },
],
}
},
}
</script>
<template>
<LhTable :data="rows" :columns="sortCols" stripe border size="small" />
</template>Défilement virtuel (gros volumes de données)
Définissez height pour l'activer : seules les lignes de la fenêtre visible sont rendues ; rowHeight doit correspondre à la hauteur réelle des lignes.
<script setup lang="ts">
const cols = [
{ prop: 'name', label: 'Nom', width: 140 },
{ prop: 'role', label: 'Rôle' },
{ prop: 'city', label: 'Ville', width: 120 },
]
const bigRows = Array.from({ length: 1000 }, (_, i) => ({
id: i + 1,
name: `Ligne ${i + 1}`,
role: i % 2 === 0 ? 'Architecte' : 'Frontend',
city: 'Hangzhou',
}))
</script>
<template>
<LhTable :data="bigRows" :columns="cols" :height="240" :row-height="48" />
</template><script>
export default {
data() {
return {
cols: [
{ prop: 'name', label: 'Nom', width: 140 },
{ prop: 'role', label: 'Rôle' },
{ prop: 'city', label: 'Ville', width: 120 },
],
bigRows: Array.from({ length: 1000 }, (_, i) => ({
id: i + 1,
name: `Ligne ${i + 1}`,
role: i % 2 === 0 ? 'Architecte' : 'Frontend',
city: 'Hangzhou',
})),
}
},
}
</script>
<template>
<LhTable :data="bigRows" :columns="cols" :height="240" :row-height="48" />
</template>Cellule personnalisée
<script setup lang="ts">
const rows = [
{ id: 1, name: 'Shen Yan', age: 34, role: 'Architecte', city: 'Hangzhou' },
{ id: 2, name: 'Su Wan', age: 28, role: 'Frontend', city: 'Suzhou' },
{ id: 3, name: 'Lu Li', age: 31, role: 'Designer', city: 'Pékin' },
]
const slotCols = [
{ prop: 'name', label: 'Nom', width: 140 },
{ prop: 'role', label: 'Rôle', slot: 'role' },
]
</script>
<template>
<!-- Slot personnalisé pour la colonne Rôle -->
<LhTable :data="rows" :columns="slotCols">
<template #role="{ value }">
<LhTag :type="value === 'Architecte' ? 'primary' : 'info'">{{ value }}</LhTag>
</template>
</LhTable>
</template><script>
export default {
data() {
return {
rows: [
{ id: 1, name: 'Shen Yan', age: 34, role: 'Architecte', city: 'Hangzhou' },
{ id: 2, name: 'Su Wan', age: 28, role: 'Frontend', city: 'Suzhou' },
{ id: 3, name: 'Lu Li', age: 31, role: 'Designer', city: 'Pékin' },
],
slotCols: [
{ prop: 'name', label: 'Nom', width: 140 },
{ prop: 'role', label: 'Rôle', slot: 'role' },
],
}
},
}
</script>
<template>
<!-- Slot personnalisé pour la colonne Rôle -->
<LhTable :data="rows" :columns="slotCols">
<template #role="{ value }">
<LhTag :type="value === 'Architecte' ? 'primary' : 'info'">{{ value }}</LhTag>
</template>
</LhTable>
</template>En-têtes multi-niveaux
Imbriquez children dans la configuration des colonnes pour obtenir des en-têtes multi-niveaux, avec une imbrication illimitée ; le corps du tableau s'aligne sur les colonnes feuilles.
<script setup>
const nestedCols = [
{ prop: 'name', label: 'Nom', width: 140, fixed: 'left' },
{
label: 'Informations du projet',
children: [
{ prop: 'role', label: 'Rôle' },
{ prop: 'city', label: 'Ville', width: 120 },
],
},
{ prop: 'age', label: 'Âge', sortable: true, align: 'center' },
]
</script>
<template>
<LhTable :data="rows" :columns="nestedCols" border />
</template><script>
export default {
data() {
return {
nestedCols: [
{ prop: 'name', label: 'Nom', width: 140, fixed: 'left' },
{
label: 'Informations du projet',
children: [
{ prop: 'role', label: 'Rôle' },
{ prop: 'city', label: 'Ville', width: 120 },
],
},
{ prop: 'age', label: 'Âge', sortable: true, align: 'center' },
],
}
},
}
</script>
<template>
<LhTable :data="rows" :columns="nestedCols" border />
</template>Filtrage
Configurez filters sur une colonne pour faire apparaître un déclencheur de filtrage dans l'en-tête ; cochez les options puis cliquez sur « OK » pour appliquer ou « Réinitialiser » pour effacer. filterMultiple: false correspond à une sélection unique et filterMethod permet une logique de filtrage personnalisée ; l'usage contrôlé est illustré ci-dessous.
Filtre actuel : {}
<script setup>
import { ref } from 'vue'
const filteredValues = ref({})
const filterCols = [
{ prop: 'name', label: 'Nom', width: 140 },
{
prop: 'role', label: 'Rôle', filters: [
{ text: 'Architecte', value: 'Architecte' },
{ text: 'Frontend', value: 'Frontend' },
{ text: 'Designer', value: 'Designer' },
],
},
]
function onFilterChange({ prop, values }) {
console.log('filterChange', prop, values)
}
</script>
<template>
<!-- Filtrage contrôlé -->
<LhTable
:data="rows"
:columns="filterCols"
v-model:filtered-values="filteredValues"
@filter-change="onFilterChange"
/>
</template><script>
export default {
data() {
return {
filteredValues: {},
filterCols: [
{ prop: 'name', label: 'Nom', width: 140 },
{
prop: 'role', label: 'Rôle', filters: [
{ text: 'Architecte', value: 'Architecte' },
{ text: 'Frontend', value: 'Frontend' },
{ text: 'Designer', value: 'Designer' },
],
},
],
}
},
methods: {
onFilterChange({ prop, values }) {
console.log('filterChange', prop, values)
},
},
}
</script>
<template>
<!-- Filtrage contrôlé -->
<LhTable
:data="rows"
:columns="filterCols"
v-model:filtered-values="filteredValues"
@filter-change="onFilterChange"
/>
</template>Redimensionnement des colonnes
Activez resizable (commutateur global) et marquez les colonnes avec resizable: true (colonnes feuilles dont le width est un nombre). Une poignée de redimensionnement apparaît sur le bord droit de l'en-tête ; une fois focalisée, les touches ←/→ ajustent la largeur de ±10 px, avec un minimum de 40 px lors du glissement.
Dernier ajustement : (aucun ajustement)
<script setup>
import { ref } from 'vue'
const resizeLog = ref('(aucun ajustement)')
const resizeCols = [
{ prop: 'name', label: 'Nom', width: 140, resizable: true, fixed: 'left' },
{ prop: 'role', label: 'Rôle' },
{ prop: 'city', label: 'Ville', width: 120, resizable: true },
]
function onColumnResize({ prop, width }) {
resizeLog.value = `${prop} → ${width}px`
}
</script>
<template>
<!-- Redimensionnement des colonnes -->
<LhTable :data="rows" :columns="resizeCols" resizable border @column-resize="onColumnResize" />
</template><script>
export default {
data() {
return {
resizeLog: '(aucun ajustement)',
resizeCols: [
{ prop: 'name', label: 'Nom', width: 140, resizable: true, fixed: 'left' },
{ prop: 'role', label: 'Rôle' },
{ prop: 'city', label: 'Ville', width: 120, resizable: true },
],
}
},
methods: {
onColumnResize({ prop, width }) {
this.resizeLog = `${prop} → ${width}px`
},
},
}
</script>
<template>
<!-- Redimensionnement des colonnes -->
<LhTable :data="rows" :columns="resizeCols" resizable border @column-resize="onColumnResize" />
</template>Clé de ligne, hauteur maximale et tri par défaut
row-key définit la clé unique d'une ligne (recommandé) ; avec max-height, l'en-tête reste fixe et le corps défile ; sorter fournit une fonction de tri personnalisée ; default-sort-prop / default-sort-order définissent le tri initial ; default-filtered-values définit le filtrage initial.
<script setup>
const sorterCols = [
{ prop: 'name', label: 'Nom', width: 140 },
{ prop: 'age', label: 'Âge', sortable: true, sorter: (a, b) => a.age - b.age },
{ prop: 'role', label: 'Rôle' },
]
</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: 'Nom', width: 140 },
{ prop: 'age', label: 'Âge', sortable: true, sorter: (a, b) => a.age - b.age },
{ prop: 'role', label: 'Rôle' },
],
}
},
}
</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
| Prop | Description | Type | Valeur par défaut |
|---|---|---|---|
| data | Données des lignes | [LhTableRow](#type-LhTableRow)[] | — |
| columns | Configuration des colonnes | [LhTableColumn](#type-LhTableColumn)[] | — |
| height | Hauteur du corps du tableau (le défilement virtuel est activé une fois défini ; un nombre correspond à des px) | string | number | — |
| maxHeight | Hauteur maximale hors mode virtuel | string | number | — |
| rowHeight | Hauteur de ligne du défilement virtuel (doit correspondre à la hauteur réelle) | number | [LH_TABLE_ROW_HEIGHT](#const-LH_TABLE_ROW_HEIGHT) |
| stripe | Rayures de zèbre | boolean | false |
| border | Bordures des cellules | boolean | false |
| size | Taille | "small" | "default" | "large" | 'default' |
| rowKey | Nom du champ servant de clé de ligne | string | 'id' |
| defaultSortProp | Colonne triée par défaut | string | — |
| defaultSortOrder | Direction du tri par défaut | "asc" | "desc" | — |
| sorter | Fonction de tri personnalisée | (a: LhTableRow, b: LhTableRow) => number | — |
| resizable | Activer le redimensionnement des colonnes (requiert également resizable au niveau des colonnes) | boolean | false |
| defaultFilteredValues | État de filtrage par défaut (non contrôlé, clés = prop des colonnes) | { [x: string]: (string | number)[]; } | () => ({}) |
| filteredValues | État de filtrage contrôlé (clés = prop des colonnes) | { [x: string]: (string | number)[]; } | — |
| sx | Props de mise en page sx | LhSxProps | — |
Events
| Événement | Description | Paramètre |
|---|---|---|
| sortChange | Déclenché lors d'un changement de tri (une nouvelle colonne recommence à asc ; une même colonne boucle asc → desc → annulation) | (payload: { prop: string, order: 'asc' | 'desc' | undefined }) |
| rowClick | Déclenché lors du clic sur une ligne | (row: LhTableRow, index: number) |
| filterChange | Déclenché après la soumission du filtrage (« OK » ou « Réinitialiser ») | (payload: { prop: string, values: (string | number)[] }) |
| update:filteredValues | Déclenché lors d'un changement de l'état de filtrage (v-model:filtered-values) | (values: Record<string, (string | number)[]>) |
| columnResize | Déclenché à la fin d'un redimensionnement de colonne (fin du glisser ou touches ←/→) | (payload: { prop: string, width: number }) |
Slots : empty, <column.slot> (paramètres déportés { row, column, index, value }).
LhTableColumn
| Champ | Description | Type |
|---|---|---|
| prop | Nom du champ de données | string |
| label | Titre de la colonne | string |
| width | Largeur de la colonne (un nombre correspond à des px ; en l'absence de valeur, la largeur est répartie) | number | string |
| align | Alignement | 'left' | 'center' | 'right' |
| fixed | Colonne fixe (requiert width) | 'left' | 'right' |
| sortable | Triable | boolean |
| slot | Nom du slot de cellule personnalisée | string |
| formatter | Formatage de la valeur | (row, value) => string |
| children | Colonnes enfants (produit des en-têtes multi-niveaux) | LhTableColumn[] |
| filters | Options de filtrage de la colonne (colonnes feuilles uniquement) | LhTableFilterOption[] |
| filterMethod | Logique de filtrage personnalisée (par défaut, comparaison d'égalité stricte sur prop) | (value, row) => boolean |
| filterMultiple | Sélection multiple dans le panneau de filtrage | boolean |
| resizable | La colonne est redimensionnable (doit être une colonne feuille avec un width numérique) | boolean |
Accessibilité
role="grid"+aria-rowcount/aria-colcount; lignesrow, en-têtes de colonnecolumnheader, cellulesgridcell- Les en-têtes de colonne triables exposent
aria-sort(none/ascending/descending), et le déclencheur de tri est un véritable<button> - Les colonnes fixes utilisent
position: stickyen CSS (pas de calcul d'adhésion maison) - Le déclencheur de filtrage porte
aria-haspopup+aria-expanded, et le panneau arole="group" - La poignée de redimensionnement a
role="separator", est focalisable et s'ajuste par ±10 px avec les touches ←/→
Définitions de types
Ce composant exporte les structures de types TypeScript suivantes (extraites automatiquement de packages/components/table/src/types.ts et des sources @lh-ui/share/core/* ; cliquez sur chaque entrée pour déplier la définition complète ; la description des props se trouve dans le tableau Props ci-dessus) :
LhTableProps
export interface LhTableProps {
/** Données des lignes */
data: LhTableRow[]
/** Configuration des colonnes */
columns: LhTableColumn[]
/** Hauteur du corps du tableau (le défilement virtuel est activé une fois défini ; un nombre correspond à des px) */
height?: number | string
/** Hauteur maximale hors mode virtuel */
maxHeight?: number | string
/** Hauteur de ligne du défilement virtuel (doit correspondre à la hauteur réelle) */
rowHeight?: number
/** Rayures de zèbre */
stripe?: boolean
/** Bordures des cellules */
border?: boolean
/** Taille */
size?: 'small' | 'default' | 'large'
/** Nom du champ servant de clé de ligne */
rowKey?: string
/** Colonne triée par défaut */
defaultSortProp?: string
/** Direction du tri par défaut */
defaultSortOrder?: LhTableSortOrder
/** Fonction de tri personnalisée */
sorter?: (a: LhTableRow, b: LhTableRow) => number
/** Active le redimensionnement des colonnes (requiert également `resizable` au niveau des colonnes, et un `width` numérique) */
resizable?: boolean
/** État de filtrage par défaut (non contrôlé, clés = prop des colonnes) */
defaultFilteredValues?: Record<string, (string | number)[]>
/** État de filtrage contrôlé (clés = prop des colonnes) */
filteredValues?: Record<string, (string | number)[]>
/** Props de mise en page sx */
sx?: LhSxProps
}LhTableSortOrder (provenant de @lh-ui/share/core/table)
/** Direction du tri */
export type LhTableSortOrder = 'asc' | 'desc'LhTableRow (provenant de @lh-ui/share/core/table)
/** Type de données d'une ligne */
export type LhTableRow = Record<string, any>LhTableColumn (provenant de @lh-ui/share/core/table)
/** Configuration d'une colonne */
export interface LhTableColumn {
/** Nom du champ de données */
prop: string
/** Titre de la colonne */
label: string
/** Largeur de la colonne (un nombre correspond à des px) */
width?: number | string
/** Alignement */
align?: LhTableAlign
/** Colonne fixe (requiert width) */
fixed?: 'left' | 'right'
/** Triable */
sortable?: boolean
/** Nom du slot de cellule personnalisée */
slot?: string
/** Formatage de la valeur */
formatter?: (row: LhTableRow, value: any) => string
/** Colonnes enfants (produit des en-têtes multi-niveaux, imbrication illimitée) */
children?: LhTableColumn[]
/** Options de filtrage de la colonne (colonnes feuilles uniquement) */
filters?: LhTableFilterOption[]
/** Logique de filtrage personnalisée (par défaut, comparaison d'égalité stricte sur prop) */
filterMethod?: (value: string | number, row: LhTableRow) => boolean
/** Sélection multiple dans le panneau de filtrage (true par défaut) */
filterMultiple?: boolean
/** La colonne est redimensionnable (doit être une colonne feuille avec un width numérique) */
resizable?: boolean
}LhTableFilterOption (provenant de @lh-ui/share/core/table)
/** Option de filtrage */
export interface LhTableFilterOption {
/** Libellé de l'option */
text: string
/** Valeur de l'option (prise en compte par filterMethod) */
value: string | number
}LhTableAlign (provenant de @lh-ui/share/core/table)
/** Alignement des cellules */
export type LhTableAlign = 'left' | 'center' | 'right'Constantes
Les constantes publiques utilisées par ce composant sont les suivantes (vous pouvez cliquer sur une entrée de même nom dans les colonnes « Type / Valeur par défaut » du tableau Props pour sauter jusqu'ici) :
LH_TABLE_ROW_HEIGHT (provenant de @lh-ui/share/core/table)
/** Hauteur de ligne par défaut du défilement virtuel */
export const LH_TABLE_ROW_HEIGHT = 48LH_TABLE_OVERSCAN (provenant de @lh-ui/share/core/table)
/** Nombre de lignes supplémentaires rendues au-dessus et au-dessous de la fenêtre (tampon anti-scintillement) */
export const LH_TABLE_OVERSCAN = 3LH_TABLE_MIN_COL_WIDTH (provenant de @lh-ui/share/core/table)
// ============================================================================
// Redimensionnement des colonnes
// ============================================================================
/** Largeur minimale lors du redimensionnement (px) ; en deçà, le contenu des cellules n'est plus tenable */
export const LH_TABLE_MIN_COL_WIDTH = 40