Admin 元件

Vxe Table

資料表格(vxe-table)。排序、勾選、分頁。

安裝

Terminal
pnpm dlx shadcn-vue add @admin/vxe-table
import {  AdminVxeTable,  AdminVxeTableCheckbox,  AdminVxeTableDragIcon,  AdminVxeTableSorter,  AdminVxeTableText,  AdminVxeTableTextButton,  AdminVxeTableTextButtonGroup,  AdminVxeTableTextImage,  useVxeTable} from '@/components/admin/vxe-table'

用法

基本欄位與資料即可渲染表格。

<script setup lang="ts">import type { VxeTableDefines } from 'vxe-table'import { ref } from 'vue'import { AdminVxeTable } from '@/components/admin/vxe-table'​interface User {  id: number  name: string  role: string  email: string}​const columns = [  { type: 'seq', width: 60, title: '#' },  { field: 'name', title: '名稱', minWidth: 120 },  { field: 'role', title: '角色', minWidth: 120 },  { field: 'email', title: 'Email', minWidth: 200 }] as const satisfies readonly VxeTableDefines.ColumnOptions<User>[]​const data = ref<User[]>([  { id: 1, name: 'Alice', role: 'Admin', email: 'alice@example.com' },  { id: 2, name: 'Bob', role: 'Editor', email: 'bob@example.com' },  { id: 3, name: 'Carol', role: 'Viewer', email: 'carol@example.com' }])</script>​<template>  <admin-vxe-table class="w-full" :columns="columns" :data="data" /></template>

場景

排序

以 AdminVxeTableSorter 取代上游預設排序圖示,並關閉 sort-config.showIcon。

<script setup lang="ts">import type { VxeTableDefines } from 'vxe-table'import { ref } from 'vue'import { AdminVxeTable, AdminVxeTableSorter } from '@/components/admin/vxe-table'​interface Product {  id: number  name: string  price: number  stock: number}​const columns = [  { type: 'seq', width: 60, title: '#' },  {    field: 'name',    title: '商品',    minWidth: 160,    sortable: true,    slots: { header: 'nameHeader' }  },  {    field: 'price',    title: '價格',    minWidth: 120,    sortable: true,    slots: { header: 'priceHeader' }  },  {    field: 'stock',    title: '庫存',    minWidth: 100,    sortable: true,    slots: { header: 'stockHeader' }  }] as const satisfies readonly VxeTableDefines.ColumnOptions<Product>[]​const data = ref<Product[]>([  { id: 1, name: '無線耳機', price: 3200, stock: 42 },  { id: 2, name: '藍牙鍵盤', price: 2500, stock: 18 },  { id: 3, name: '螢幕支架', price: 1800, stock: 73 },  { id: 4, name: 'USB-C Hub', price: 1600, stock: 120 }])</script>​<template>  <admin-vxe-table    class="w-full"    :columns="columns"    :data="data"    :sort-config="{ showIcon: false }"  >    <template #nameHeader="{ column }">      <span>商品</span>      <admin-vxe-table-sorter :column="column" />    </template>    <template #priceHeader="{ column }">      <span>價格</span>      <admin-vxe-table-sorter :column="column" />    </template>    <template #stockHeader="{ column }">      <span>庫存</span>      <admin-vxe-table-sorter :column="column" />    </template>  </admin-vxe-table></template>

勾選與多選

啟用 checkbox 欄並監聽勾選變更,顯示目前選取筆數。

<script setup lang="ts">import type { VxeTableDefines } from 'vxe-table'import { ref } from 'vue'import { AdminVxeTable } from '@/components/admin/vxe-table'​interface Task {  id: number  title: string  owner: string  status: string}​const columns = [  { type: 'checkbox', width: 56 },  { type: 'seq', width: 60, title: '#' },  { field: 'title', title: '任務', minWidth: 200 },  { field: 'owner', title: '負責人', minWidth: 120 },  { field: 'status', title: '狀態', minWidth: 100 }] as const satisfies readonly VxeTableDefines.ColumnOptions<Task>[]​const data = ref<Task[]>([  { id: 1, title: '撰寫 RFC 文件', owner: 'Alice', status: 'In progress' },  { id: 2, title: '補齊單元測試', owner: 'Bob', status: 'Todo' },  { id: 3, title: '檢查無障礙', owner: 'Carol', status: 'Done' },  { id: 4, title: '部署到 staging', owner: 'David', status: 'Blocked' }])​const selected = ref<Task[]>([])​function onCheckboxChange({ records }: { records: Task[] }) {  selected.value = records}</script>​<template>  <div class="flex flex-col gap-3">    <admin-vxe-table      class="w-full"      :columns="columns"      :data="data"      @checkbox-all="onCheckboxChange"      @checkbox-change="onCheckboxChange"    />    <p class="typo-caption-small text-text-50">      已選 {{ selected.length }} 筆:{{ selected.map((t) => t.title).join('、') || '—' }}    </p>  </div></template>

分頁

需要底部頁碼與每頁筆數選擇時,開啟 pagination 並綁定目前頁資料與全量筆數。

<script setup lang="ts">import type { VxeTableDefines } from 'vxe-table'import { computed, ref } from 'vue'import { AdminVxeTable } from '@/components/admin/vxe-table'​interface Order {  id: number  title: string  owner: string  amount: number}​const columns = [  { type: 'seq', width: 60, title: '#' },  { field: 'title', title: '訂單', minWidth: 200 },  { field: 'owner', title: '負責人', minWidth: 120 },  { field: 'amount', title: '金額', minWidth: 100 }] as const satisfies readonly VxeTableDefines.ColumnOptions<Order>[]​const allRows = Array.from({ length: 48 }, (_, i) => ({  id: i + 1,  title: `訂單 #${String(i + 1).padStart(3, '0')}`,  owner: ['Alice', 'Bob', 'Carol', 'David'][i % 4]!,  amount: 1000 + i * 50}))​const page = ref(1)const itemsPerPage = ref(10)​const pageRows = computed(() => {  const start = (page.value - 1) * itemsPerPage.value  return allRows.slice(start, start + itemsPerPage.value)})</script>​<template>  <admin-vxe-table    v-model:items-per-page="itemsPerPage"    v-model:page="page"    class="w-full"    pagination    :columns="columns"    :data="pageRows"    :total="allRows.length"  /></template>

所有子組件總覽

全部搭配子元件的版型一覽。

<script setup lang="ts">import type { VxeTableDefines } from 'vxe-table'import { ref } from 'vue'import {  AdminVxeTable,  AdminVxeTableCheckbox,  AdminVxeTableDragIcon,  AdminVxeTableSorter,  AdminVxeTableText,  AdminVxeTableTextButton,  AdminVxeTableTextButtonGroup,  AdminVxeTableTextImage} from '@/components/admin/vxe-table'​interface Product {  id: number  image: string  name: string  sku: string  category: string  price: number  stock: number}​const columns = [  {    type: 'checkbox',    width: 56,    slots: { checkbox: 'rowCheckbox', header: 'headerCheckbox' }  },  {    width: 44,    title: '',    dragSort: true  },  {    field: 'product',    title: '商品',    minWidth: 240,    slots: { default: 'product' }  },  {    field: 'category',    title: '分類',    minWidth: 180,    slots: { default: 'category' }  },  {    field: 'stock',    title: '庫存',    minWidth: 120,    sortable: true,    slots: { header: 'stockHeader' }  },  {    field: 'actions',    title: '操作',    minWidth: 160,    slots: { default: 'actions' }  }] as const satisfies readonly VxeTableDefines.ColumnOptions<Product>[]​const data = ref<Product[]>([  {    id: 1,    image: 'https://picsum.photos/seed/headphones/80/60',    name: '無線降噪耳機',    sku: 'SKU-00001',    category: '音訊',    price: 4990,    stock: 32  },  {    id: 2,    image: 'https://picsum.photos/seed/keyboard/80/60',    name: '機械式鍵盤',    sku: 'SKU-00002',    category: '周邊',    price: 2890,    stock: 18  },  {    id: 3,    image: 'https://picsum.photos/seed/monitor/80/60',    name: '4K 顯示器',    sku: 'SKU-00003',    category: '顯示',    price: 12990,    stock: 7  }])​function onView(_row: Product) {}function onEdit(_row: Product) {}</script>​<template>  <div class="w-full overflow-x-auto">    <admin-vxe-table      class="w-full"      :columns="columns"      :data="data"      :row-config="{ keyField: 'id', drag: true }"      :sort-config="{ showIcon: false }"    >      <template #headerCheckbox="{ checked, indeterminate }">        <admin-vxe-table-checkbox :checked="checked" :indeterminate="indeterminate" />      </template>​      <template #rowCheckbox="{ row, checked, indeterminate }">        <admin-vxe-table-checkbox :checked="checked" :indeterminate="indeterminate" :row="row" />      </template>​      <template #rowDragIcon>        <admin-vxe-table-drag-icon />      </template>​      <template #product="{ row }">        <admin-vxe-table-text-image :image="row.image" :name="row.name" />      </template>​      <template #category="{ row }">        <admin-vxe-table-text>          <template #title>{{ row.category }}</template>          <template #subtitle>{{ row.sku }}</template>        </admin-vxe-table-text>      </template>​      <template #stockHeader="{ column }">        <span>庫存</span>        <admin-vxe-table-sorter :column="column" />      </template>​      <template #actions="{ row }">        <admin-vxe-table-text-button-group>          <admin-vxe-table-text-button @click="onView(row)">檢視</admin-vxe-table-text-button>          <admin-vxe-table-text-button @click="onEdit(row)">編輯</admin-vxe-table-text-button>        </admin-vxe-table-text-button-group>      </template>    </admin-vxe-table>  </div></template>

API 參考

組成

<template>  <admin-vxe-table :columns="columns" :data="data" :sort-config="{ showIcon: false }">    <template #headerCheckbox="{ checked, indeterminate }">      <admin-vxe-table-checkbox :checked="checked" :indeterminate="indeterminate" />    </template>    <template #rowCheckbox="{ row, checked, indeterminate }">      <admin-vxe-table-checkbox :checked="checked" :indeterminate="indeterminate" :row="row" />    </template>    <template #rowDragIcon>      <admin-vxe-table-drag-icon />    </template>    <template #stockHeader="{ column }">      <span>庫存</span>      <admin-vxe-table-sorter :column="column" />    </template>    <template #product="{ row }">      <admin-vxe-table-text-image :image="row.image" :name="row.name" />    </template>    <template #category="{ row }">      <admin-vxe-table-text>        <template #title>{{ row.category }}</template>        <template #subtitle>{{ row.sku }}</template>      </admin-vxe-table-text>    </template>    <template #actions>      <admin-vxe-table-text-button-group>        <admin-vxe-table-text-button>編輯</admin-vxe-table-text-button>        <admin-vxe-table-text-button>刪除</admin-vxe-table-text-button>      </admin-vxe-table-text-button-group>    </template>  </admin-vxe-table></template>

admin-vxe-table

Props

名稱型別預設值說明
classClassValue—根元素之自訂 class
columnsC | undefined—表格欄位定義(型別會被泛型 C 收斂以推導 column slots 名稱)
paginationboolean | undefined—啟用分頁
pagenumber | undefined—當前頁碼 (v-model:page)
itemsPerPagenumber | undefined—每頁筆數 (v-model:items-per-page)
totalnumber | undefined—總筆數
perPageOptionsnumber[] | undefined[10, 20, 50, 100]每頁筆數選項
showPaginationInfoboolean | undefinedtrue顯示分頁資訊
showPerPageSelectorboolean | undefinedtrue顯示每頁筆數選擇器

Events

名稱Payload說明
update:pagenumber使用者切換頁碼時觸發
update:itemsPerPagenumber使用者切換每頁筆數時觸發;切換後 `page` 會自動重置為 1

admin-vxe-table-checkbox

Props

名稱型別預設值說明
checkedboolean | undefined—顯示為已勾選狀態
indeterminateboolean | undefined—顯示為「部分選取」中間態
rowRecord<string, any> | undefined—帶入該列資料時點擊切換該列;省略則切換全選
classunknown——

admin-vxe-table-sorter

Props

名稱型別預設值說明
column*VxeTableDefines.ColumnInfo<any>—帶入 header slot 提供的 `column`,驅動三階段排序切換
classunknown——

* 代表必填

admin-vxe-table-text-image

Props

名稱型別預設值說明
image*string—圖片 URL
name*string—圖片右側顯示的名稱文字
classunknown——

* 代表必填

admin-vxe-table-text-button

Props

名稱型別預設值說明
classClassValue—根元素之自訂 class

Slots

名稱Bindings說明
defaultany按鈕文字或內容

admin-vxe-table-text-button-group

Props

名稱型別預設值說明
classClassValue—根元素之自訂 class

Slots

名稱Bindings說明
defaultany一組文字按鈕

admin-vxe-table-drag-icon

Props

名稱型別預設值說明
classunknown——

admin-vxe-table-text

Props

名稱型別預設值說明
classunknown——

Slots

名稱Bindings說明
titleany主標題文字
subtitleany副標題文字

注意事項

  • 不支援 SSR:vxe-table 依賴瀏覽器 DOM;元件殼不含 <ClientOnly>。Nuxt 請自行以 <ClientOnly>(或其他框架等價機制)包覆;純 CSR 不需處理。見 安裝 · SSR。
  • loading 由 admin overlay 接管:傳入 loading 時會顯示 v-admin-loading 遮罩,不會轉發至 vxe-grid 原生 loading/loading slot。
  • 自訂 cell 子元件須置於 AdminVxeTable 內:AdminVxeTableCheckbox、AdminVxeTableSorter 等透過 useVxeTable 取得內部 gridRef;寫自訂 cell 時也可直接呼叫 useVxeTable()。
  • 分頁資料由消費端切片:pagination 只負責 UI 與 page/itemsPerPage 雙向綁定;表格 data 仍須傳入當前頁資料,total 為全量筆數。

更新紀錄

  • fix: 補上 Vue 核心 API 的顯式 import3c2c030
  • fix: 補齊 code review 指出的 expose、文件與 sorter 問題4f68e1f
  • fix: 將多個元件的按鈕類型設為 type="button",避免在表單中觸發提交事件f4f1bdd