Lzh on GitHub

基础使用例子

<script setup lang="ts">
import {
  FlexRender,
  getCoreRowModel,
  useVueTable,
  createColumnHelper
} from '@tanstack/vue-table'
import { ref } from 'vue'

// 定义数据结构

type Person = {
  firstName: string
  lastName: string
  age: number
  visits: number
  status: string
  progress: number
}

const defaultData: Person[] = [
  {
    firstName: 'tanner',
    lastName: 'linsley',
    age: 24,
    visits: 100,
    status: 'In Relationship',
    progress: 50
  },
  {
    firstName: 'tandy',
    lastName: 'miller',
    age: 40,
    visits: 40,
    status: 'Single',
    progress: 80
  },
  {
    firstName: 'joe',
    lastName: 'dirte',
    age: 45,
    visits: 20,
    status: 'Complicated',
    progress: 10
  }
]

// 定义列
const columnHelper = createColumnHelper<Person>()

const columns = [
  // 定义列分组
  columnHelper.group({
    header: 'Name',
    footer: props => props.column.id,
    columns: [
      // 定义列的访问方式
      columnHelper.accessor('firstName', {
        cell: info => info.getValue(),
        footer: props => props.column.id
      }),
      columnHelper.accessor(row => row.lastName, {
        id: 'lastName',
        cell: info => info.getValue(),
        header: () => 'Last Name',
        footer: props => props.column.id
      })
    ]
  }),
  columnHelper.group({
    header: 'Info',
    footer: props => props.column.id,
    columns: [
      columnHelper.accessor('age', {
        header: () => 'Age',
        footer: props => props.column.id
      }),
      columnHelper.group({
        header: 'More Info',
        footer: 'test',
        columns: [
          columnHelper.accessor('visits', {
            header: () => 'Visits',
            footer: props => props.column.id
          }),
          columnHelper.accessor('status', {
            header: 'Status',
            footer: props => props.column.id
          }),
          columnHelper.accessor('progress', {
            header: 'Profile Progress',
            footer: props => props.column.id
          })
        ]
      })
    ]
  })
]

// 响应式的数据源
const data = ref(defaultData)

// 重新渲染表格的函数,用于重置 data,模拟刷新表格数据
const rerender = () => {
  data.value = defaultData
}

// 这是 TanStack Table 的核心钩子,用于创建表格实例,接收数据、列和行模型等配置项。通过 getCoreRowModel() 获取行模型,用于渲染表格数据。
const table = useVueTable({
  get data() {
    return data.value
  },
  columns,
  getCoreRowModel: getCoreRowModel()
})
</script>

<template>
  <div class="p-2">
    <table>
      <thead>
        <tr
          v-for="headerGroup in table.getHeaderGroups()"
          :key="headerGroup.id"
        >
          <th
            v-for="header in headerGroup.headers"
            :key="header.id"
            :colSpan="header.colSpan"
          >
            <FlexRender
              v-if="!header.isPlaceholder"
              :render="header.column.columnDef.header"
              :props="header.getContext()"
            />
          </th>
        </tr>
      </thead>
      <tbody>
        <tr
          v-for="row in table.getRowModel().rows"
          :key="row.id"
        >
          <td
            v-for="cell in row.getVisibleCells()"
            :key="cell.id"
          >
            <FlexRender
              :render="cell.column.columnDef.cell"
              :props="cell.getContext()"
            />
          </td>
        </tr>
      </tbody>
      <tfoot>
        <tr
          v-for="footerGroup in table.getFooterGroups()"
          :key="footerGroup.id"
        >
          <th
            v-for="header in footerGroup.headers"
            :key="header.id"
            :colSpan="header.colSpan"
          >
            <FlexRender
              v-if="!header.isPlaceholder"
              :render="header.column.columnDef.footer"
              :props="header.getContext()"
            />
          </th>
        </tr>
      </tfoot>
    </table>
    <div class="h-4" />
    <button
      class="border p-2"
      @click="rerender"
    >
      Rerender
    </button>
  </div>
</template>

<style>
html {
  font-family: sans-serif;
  font-size: 14px;
}

table {
  border: 1px solid lightgray;
}

tbody {
  border-bottom: 1px solid lightgray;
}

th {
  border-bottom: 1px solid lightgray;
  border-right: 1px solid lightgray;
  padding: 2px 4px;
}

tfoot {
  color: gray;
}

tfoot th {
  font-weight: normal;
}
</style>

页脚统计

如果你想保持对表头、body 结构由 table.getHeaderGroups()table.getRowModel() 生成的统一性,但又需要 tfoot 高度自定义:

  • 推荐手动写 <tfoot>,只用 TanStack 提供的 row.getValue().original 来提取原始数据统计结果。
<script setup lang="ts">
import {
  FlexRender,
  getCoreRowModel,
  useVueTable,
  createColumnHelper
} from '@tanstack/vue-table'
import { ref } from 'vue'

// 定义数据结构

type Person = {
  firstName: string
  lastName: string
  age: number
  visits: number
  status: string
  progress: number
}

const defaultData: Person[] = [
  {
    firstName: 'tanner',
    lastName: 'linsley',
    age: 24,
    visits: 100,
    status: 'In Relationship',
    progress: 50
  },
  {
    firstName: 'tandy',
    lastName: 'miller',
    age: 40,
    visits: 40,
    status: 'Single',
    progress: 80
  },
  {
    firstName: 'joe',
    lastName: 'dirte',
    age: 45,
    visits: 20,
    status: 'Complicated',
    progress: 10
  }
]

// 定义列
const columnHelper = createColumnHelper<Person>()

const columns = [
  // 定义列分组
  columnHelper.group({
    header: 'Name',
    columns: [
      // 定义列的访问方式
      columnHelper.accessor('firstName', {
        cell: info => info.getValue()
      }),
      columnHelper.accessor(row => row.lastName, {
        id: 'lastName',
        cell: info => info.getValue(),
        header: () => 'Last Name'
      })
    ]
  }),
  columnHelper.group({
    header: 'Info',
    columns: [
      columnHelper.accessor('age', {
        header: () => 'Age'
      }),
      columnHelper.group({
        header: 'More Info',
        columns: [
          columnHelper.accessor('visits', {
            header: () => 'Visits'
          }),
          columnHelper.accessor('status', {
            header: 'Status'
          }),
          columnHelper.accessor('progress', {
            header: 'Profile Progress'
          })
        ]
      })
    ]
  })
]

// 响应式的数据源
const data = ref(defaultData)

// 重新渲染表格的函数,用于重置 data,模拟刷新表格数据
const rerender = () => {
  data.value = defaultData
}

const totalVisits = computed(() => {
  return table.getRowModel().rows.reduce((sum, row) => sum + row.getValue('visits'), 0)
})

const averageAge = computed(() => {
  const rows = table.getRowModel().rows
  if (!rows.length) return 0
  return (
    rows.reduce((sum, row) => sum + row.getValue('age'), 0) / rows.length
  ).toFixed(1)
})

const averageProgress = computed(() => {
  const rows = table.getRowModel().rows
  if (!rows.length) return 0
  return (
    rows.reduce((sum, row) => sum + row.getValue('progress'), 0) / rows.length
  ).toFixed(1)
})

// 这是 TanStack Table 的核心钩子,用于创建表格实例,接收数据、列和行模型等配置项。通过 getCoreRowModel() 获取行模型,用于渲染表格数据。
const table = useVueTable({
  get data() {
    return data.value
  },
  columns,
  getCoreRowModel: getCoreRowModel()
})
</script>

<template>
  <div class="p-2">
    <table>
      <thead>
        <tr
          v-for="headerGroup in table.getHeaderGroups()"
          :key="headerGroup.id"
        >
          <th
            v-for="header in headerGroup.headers"
            :key="header.id"
            :colSpan="header.colSpan"
          >
            <FlexRender
              v-if="!header.isPlaceholder"
              :render="header.column.columnDef.header"
              :props="header.getContext()"
            />
          </th>
        </tr>
      </thead>
      <tbody>
        <tr
          v-for="row in table.getRowModel().rows"
          :key="row.id"
        >
          <td
            v-for="cell in row.getVisibleCells()"
            :key="cell.id"
          >
            <FlexRender
              :render="cell.column.columnDef.cell"
              :props="cell.getContext()"
            />
          </td>
        </tr>
      </tbody>
      <tfoot>
        <tr>
          <!-- 合并前3列2行 -->
          <td
            colspan="3"
            rowspan="2"
            class="text-right font-bold"
          >
            统计信息:
          </td>
          <td class="text-center font-bold">
            访问次数合计
          </td>
          <td class="text-center font-bold">
            平均年龄
          </td>
          <td class="text-center font-bold">
            平均进度
          </td>
        </tr>
        <tr>
          <td class="text-center">
            {{ totalVisits }}
          </td>
          <td class="text-center">
            {{ averageAge }}
          </td>
          <td class="text-center">
            {{ averageProgress }}
          </td>
        </tr>
      </tfoot>
    </table>
    <div class="h-4" />
    <button
      class="border p-2"
      @click="rerender"
    >
      Rerender
    </button>
  </div>
</template>

<style>
html {
  font-family: sans-serif;
  font-size: 14px;
}

table {
  border: 1px solid lightgray;
}

tbody {
  border-bottom: 1px solid lightgray;
}

th {
  border-bottom: 1px solid lightgray;
  border-right: 1px solid lightgray;
  padding: 2px 4px;
}

tfoot {
  color: gray;
}

tfoot th {
  font-weight: normal;
}
</style>