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 をネストすると多段見出しになり、無限にネストできます;表体は葉列に合わせて整列します。

2 段見出し
名前
プロジェクト情報
役割
都市
沈砚
アーキテクト
杭州
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

国風の美で、コードの基を築く