Table テーブル
列設定式テーブル。ソート、固定列、縞模様、セルスロットに加え、大規模データ向けの仮想スクロール(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 | 列幅ドラッグを有効化(列レベルの resizable の併用が必要) | boolean | false |
| defaultFilteredValues | デフォルトの絞り込み状態(非制御、キーは列の prop) | { [x: string]: (string | number)[]; } | () => ({}) |
| filteredValues | 制御された絞り込み状態(キーは列の prop) | { [x: string]: (string | number)[]; } | — |
| sx | sx クイックレイアウトプロパティ | 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
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 から)
/** ソート方向 */
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