[Nuxt3] Data Table 練習 (shadcn-vue)

在上一篇中介紹了 開源免費可自訂的元件庫 shadcn-vue ,今天就來練習使用它裡面的 Data Table。不過他並不是從頭寫元件,而是使用 TanStack 的 Table 的 Vue 的版本,也是一個可以完全控制樣式的開源套件。

安裝套件

依照上一篇安裝好 shadcn-vue 後要安裝 Data Table 就很簡單:
    
npx shadcn-vue@latest add table
    

因為是使用到 @tanstack/vue-table ,所以還是必須要安裝套件:
    
npm install @tanstack/vue-table
    

安裝好後會發現 components\ui\table 資料夾內多了很多檔案。

基本表格

為了方便複用,我們先把最基本的樣板程式碼和其他 DataTable 元件一樣放到 components\ui\table 資料夾中,檔名就叫做 DataTable.vue
    
<script setup lang="ts" generic="TData, TValue">
import type { ColumnDef } from '@tanstack/vue-table'
import {
  FlexRender,
  getCoreRowModel,
  useVueTable,
} from '@tanstack/vue-table'

import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table'

const props = defineProps<{
  columns: ColumnDef<TData, TValue>[]
  data: TData[]
}>()

const table = useVueTable({
  get data() { return props.data },
  get columns() { return props.columns },
  getCoreRowModel: getCoreRowModel(),
})
</script>

<template>
  <div class="border rounded-md">
    <Table>
      <TableHeader>
        <TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
          <TableHead v-for="header in headerGroup.headers" :key="header.id">
            <FlexRender
                v-if="!header.isPlaceholder" :render="header.column.columnDef.header"
                :props="header.getContext()"
            />
          </TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        <template v-if="table.getRowModel().rows?.length">
          <TableRow
              v-for="row in table.getRowModel().rows" :key="row.id"
              :data-state="row.getIsSelected() ? 'selected' : undefined"
          >
            <TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
              <FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
            </TableCell>
          </TableRow>
        </template>
        <template v-else>
          <TableRow>
            <TableCell :colspan="columns.length" class="h-24 text-center">
              No results.
            </TableCell>
          </TableRow>
        </template>
      </TableBody>
    </Table>
  </div>
</template>
    



在 components 資料夾中建立 UserTable.vue 檔案,先在 script 區塊中定義表格資料物件和欄位定義
    
import type {ColumnDef} from "@tanstack/vue-table";

/**
 * 定義使用者資料
 */
interface User {
    id: string
    userName: string
    displayName: string
    email: string
}


/**
 * 使用者資料表的欄位定義
 */
const columns: ColumnDef<User>[] = [
    {
        accessorKey: 'id',
        header: 'ID',
    },
    {
        accessorKey: 'userName',
        header: '帳號',
    },
    {
        accessorKey: 'displayName',
        header: '名稱',
    },
    {
        accessorKey: 'email',
        header: 'Email',
    },
]
    

加入 Data Table 和資料的定義,使用 onMounted 的寫法方便未來替換為從 API 中取得資料:
    
<template>
  <DataTable :columns="columns" :data="data"/>
</template>

<script setup lang="ts">
import {onMounted, ref} from 'vue'

import type {ColumnDef} from "@tanstack/vue-table";
import DataTable from '@/components/ui/table/DataTable.vue'

/**
 * 定義使用者資料
 */
interface User {
    id: string
    userName: string
    displayName: string
    email: string
}


/**
 * 使用者資料表的欄位定義
 */
const columns: ColumnDef<User>[] = [
    {
        accessorKey: 'id',
        header: 'ID',
    },
    {
        accessorKey: 'userName',
        header: '帳號',
    },
    {
        accessorKey: 'displayName',
        header: '名稱',
    },
    {
        accessorKey: 'email',
        header: 'Email',
    },
]

const data = ref<User[]>([])

async function getData(): Promise<User[]> {
  // 可以替換為從 API 取得資料
  return [
    {
      id: 'a001',
      userName: 'ruyut',
      displayName: 'Ruyut',
      email: 'a@ruyut.com'
    },
    {
      id: 'a002',
      userName: 'qwer123',
      displayName: '小明',
      email: 'qwer123@ruyut.com'
    },
  ]
}

onMounted(async () => {
  data.value = await getData()
})

</script>
    

在其他頁面要使用這個表格很簡單:
    
<template>
  <UserTable></UserTable>
</template>

<script setup lang="ts">
import UserTable from '@/components/user-table/UserTable.vue'
</script>
    


自訂欄位樣式

在前面的 UserTable.vue 檔案中有建立資料表欄位定義,我們可以透過修改定義將 id 欄位變成粗體:
    
import type {ColumnDef} from "@tanstack/vue-table";

/**
 * 使用者資料表的欄位定義
 */
const columns: ColumnDef<User>[] = [
    {
        accessorKey: 'id',
        header: 'ID',
        cell: ({ row }) => {
            return h('strong', row.getValue('id'))
        },
    },
]
    


也可以自訂條件,例如 email 欄位長度超過 15 個字元就使用紅色粗體顯示:
    
import type {ColumnDef} from "@tanstack/vue-table";

/**
 * 使用者資料表的欄位定義
 */
const columns: ColumnDef<User>[] = [
    {
        accessorKey: 'id',
        header: 'ID',
        cell: ({ row }) => {
            return h('strong', row.getValue('id'))
        },
    },
    {
        accessorKey: 'email',
        header: 'Email',
        cell: ({row}) => {
            const email: string = row.getValue('email')
            // 如果長度超過 15 個字元,就用紅色粗體
            if (email.length > 15) {
                return h('strong', {class: 'text-red-500'}, email)
            }
            return email
        },
    },
]
    




參考資料:
npm - @tanstack/vue-table
TanStack - Vue Table
shadcn-vue - Data Table

留言