---
title: "介绍"
description: "基于 TanStack Table 的声明式数据表格，含列配置、选择、展开、树形、分页、加载更多与完整 API 参考。"
seo_title: "DataTable"
seo_description: "A declarative, type-safe DataTable for Nuxt built on TanStack Table — column config, selection, expansion, tree data, pagination and load-more, with full props, emits, slots and exposed API reference."
canonical_url: "https://nuxt.mhaibaraai.cn/docs/data-table"
---
# 介绍

> 基于 TanStack Table 的声明式数据表格，含列配置、选择、展开、树形、分页、加载更多与完整 API 参考。

## 简介

`MDataTable` 是基于 [`@tanstack/vue-table`](https://tanstack.com/table){rel="[\"nofollow\"]"} 与 Nuxt UI `UTable` 封装的声明式数据表格。它把列定义、排序、列固定、列宽拖拽、选择、展开、树形、分页与加载更多统一收敛到一个 `columns` 数组与少量 props，并提供完整的 TypeScript 类型推断。

> \[\!NOTE\]
> 
> 核心理念：
> 
> - **声明式列** —— 用对象数组描述列，数据列、选择列、索引列、展开列、操作列、分组列等通过 `accessorKey` 或 `type` 区分。
> - **全局 + 列级开关** —— `sortable`、`pinable`、`resizable`、`truncate`、`tooltip` 等既可全局启用，也可按列覆盖或传函数动态决定。
> - **类型安全** —— 列、单元格回调、事件处理均由 `DataTableColumn<T>`、`DataTableProps<T>` 等类型驱动。

## 用法

一个 `columns` 数组即可声明选择列、索引列、固定列、单元格渲染与操作列，再配合 `row-key` 标识行、`sortable`/`pinable`/`resizable` 启用排序与列固定切换、列宽拖拽、`v-model:pagination` 分页，无需逐项拼装即得到完整能力：

```vue [DataTableBasicExample.vue]
<script setup lang="ts">
import type { DataTableColumn, DataTableDataColumn, PaginationState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
import { UBadge } from '#components'

const data = makePeople(40)
const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 6 })
const toast = useToast()
const notify = (msg: string): void => { toast.add({ title: msg, duration: 1500 }) }

const STATUS_LABEL: Record<Person['status'], string> = {
  active: '在职',
  leave: '休假',
  offboarded: '已离职'
}
const STATUS_COLOR: Record<Person['status'], 'success' | 'warning' | 'neutral'> = {
  active: 'success',
  leave: 'warning',
  offboarded: 'neutral'
}

const statusCell: DataTableDataColumn<Person>['cell'] = ({ getValue }) => {
  const v = getValue<Person['status']>()
  return h(UBadge, { color: STATUS_COLOR[v], variant: 'subtle' }, () => STATUS_LABEL[v])
}
const moneyCell: DataTableDataColumn<Person>['cell'] = ({ getValue }) => `¥${getValue<number>().toLocaleString()}`

const columns: DataTableColumn<Person>[] = [
  { type: 'selection' },
  { type: 'index' },
  { accessorKey: 'name', header: '姓名', size: 120, fixed: 'left', pinable: true },
  { accessorKey: 'department', header: '部门', size: 100 },
  { accessorKey: 'role', header: '岗位', size: 160, resizable: true },
  { accessorKey: 'status', header: '状态', size: 100, cell: statusCell },
  { accessorKey: 'salary', header: '薪资', size: 120, align: 'right', cell: moneyCell },
  {
    type: 'actions',
    size: 100,
    maxInline: 2,
    actions: [
      { key: 'edit', buttonProps: { icon: 'i-lucide-pencil', variant: 'ghost', size: 'xs' }, onClick: ({ row }) => notify(`编辑 ${row.name}`) },
      { key: 'delete', buttonProps: { icon: 'i-lucide-trash-2', variant: 'ghost', color: 'error', size: 'xs' }, onClick: ({ row }) => notify(`删除 ${row.name}`) }
    ]
  }
]
</script>

<template>
  <MDataTable
    v-model:pagination="pagination"
    row-key="id"
    :columns="columns"
    :data="data"
    sortable
    bordered
  />
</template>
```

## 示例数据

本章所有示例的 `data` 均由以下工具函数派生，可直接复制到项目中复用：

```ts [app/composables/useTableMock.ts]
export interface Person {
  id: string
  name: string
  email: string
  department: '研发' | '设计' | '产品' | '运营' | '市场'
  role: string
  level?: 'P5' | 'P6' | 'P7' | 'P8'
  status: 'active' | 'leave' | 'offboarded'
  salary: number
  joinedAt: string
  address: string
  bio: string
  children?: Person[]
}

const FIRST = ['张', '李', '王', '陈', '刘', '杨', '黄', '赵', '吴', '周', '徐', '孙']
const LAST = ['伟', '芳', '娜', '敏', '静', '丽', '强', '磊', '军', '洋', '勇', '涛', '明', '超']
const DEPARTMENTS: Person['department'][] = ['研发', '设计', '产品', '运营', '市场']
const ROLES = ['前端工程师', '后端工程师', '全栈工程师', 'UI 设计师', '产品经理', '数据分析师', '运营专员']
const LEVELS: Person['level'][] = ['P5', 'P6', 'P7', 'P8']
const STATUS: Person['status'][] = ['active', 'leave', 'offboarded']
const ADDRESSES = [
  '上海市浦东新区张江高科技园区博云路 2 号 IBM 大厦 7 层 708',
  '北京市海淀区中关村大街 27 号中关村大厦 A 座 1502',
  '深圳市南山区科技园南区高新南九道 9 号深圳湾科技生态园 10 栋 B 座 2301',
  '广州市天河区珠江新城华夏路 16 号富力盈凯广场 38 楼',
  '杭州市余杭区文一西路 969 号阿里巴巴西溪园区 6 号楼 318',
  '成都市高新区天府大道中段 1199 号天府软件园 D 区 5 号楼 1207',
  '南京市建邺区江东中路 222 号高科荣域大厦 19 层',
  '武汉市洪山区光谷大道 70 号光谷国际企业中心 3 期 B 座 1808'
]
const BIO_TEMPLATES = [
  '在跨境电商团队负责供应链与履约链路建设，主导过日均百万单的实时调度系统重构，关注稳定性与成本平衡。',
  '长期投入大数据平台与离线计算调度，搭建过 PB 级数据仓库与数据治理体系，对数据质量与权限治理有较强经验。',
  '聚焦设计系统与组件库建设，推动跨产品视觉一致性，主导多次 Design Tokens 与暗色主题落地。',
  '负责自研低代码平台与 BFF 中间层，输出过若干内部脚手架与 CLI 工具，关注研发效率与可维护性。',
  '深耕实时音视频与 WebRTC 互动场景，主导过千万级直播间的延迟优化与弱网降级方案。',
  '在 To B SaaS 团队负责权限、计费与多租户体系，对 RBAC、ABAC 与审计合规有完整落地经验。',
  '负责风控与反作弊算法工程化，搭建过实时特征平台与规则引擎，覆盖支付、营销、社交多场景。',
  '聚焦移动端性能优化与跨端渲染框架，主导过启动耗时、内存与包体积的全链路治理。'
]

function pick<T>(list: readonly T[], seed: number): T {
  return list[seed % list.length] as T
}

export function makePerson(seed: number): Person {
  const first = pick(FIRST, seed)
  const last = pick(LAST, seed * 31 + 7)
  const dept = pick(DEPARTMENTS, seed * 7)
  return {
    id: `P${String(seed).padStart(4, '0')}`,
    name: `${first}${last}`,
    email: `user${seed}@movk.dev`,
    department: dept,
    role: pick(ROLES, seed * 3),
    level: pick(LEVELS, seed * 11),
    status: pick(STATUS, seed * 13),
    salary: 8000 + (seed * 257) % 50000,
    joinedAt: new Date(2018 + (seed % 7), seed % 12, 1 + (seed % 27)).toISOString().slice(0, 10),
    address: pick(ADDRESSES, seed * 17),
    bio: pick(BIO_TEMPLATES, seed * 19)
  }
}

export function makePeople(count: number, offset = 0): Person[] {
  return Array.from({ length: count }, (_, i) => makePerson(offset + i + 1))
}

export function makePeopleTree(rootCount = 5, childPerRoot = 3, depth = 1): Person[] {
  const build = (seed: number, level: number): Person => {
    const person = makePerson(seed)
    if (level >= depth) return person
    return {
      ...person,
      role: '团队负责人',
      level: undefined,
      children: Array.from({ length: childPerRoot }, (_, j) =>
        build(seed * 100 + j + 1, level + 1))
    }
  }
  return Array.from({ length: rootCount }, (_, i) => build(i + 1, 0))
}
```

## API

### Props

```ts
/**
 * Props for the MDataTable component
 */
interface MDataTableProps {
  /**
   * 行唯一标识字段，自动派生 getRowId；与 getRowId 同传时后者优先
   */
  rowKey?: (string & {}) | (keyof T & string) | undefined;
  columns?: DataTableColumn<T>[] | undefined;
  loading?: boolean | undefined;
  /**
   * 斑马纹
   */
  stripe?: boolean | undefined;
  /**
   * 纵向边框，传对象可定制 color/width/style
   */
  bordered?: boolean | { color?: string | undefined; width?: string | undefined; style?: "solid" | "dashed" | "dotted" | "double" | undefined; } | undefined;
  /**
   * 表格宽度由列宽内容决定（w-fit）
   */
  fitContent?: boolean | undefined;
  /**
   * 空值占位符
   * @default "\"-\""
   */
  emptyCell?: false | ColumnDefTemplate<CellContext<T, unknown>> | undefined;
  /**
   * 启用列固定按钮，传函数可按列动态决定
   */
  pinable?: boolean | ((col: DataTableDataColumn<T>) => boolean) | undefined;
  pinButtonProps?: DataTableDynamic<ButtonProps, DataTablePinButtonContext<T>> | undefined;
  /**
   * 启用列排序，传函数可按列动态决定
   */
  sortable?: boolean | ((col: DataTableDataColumn<T>) => boolean) | undefined;
  sortButtonProps?: DataTableDynamic<ButtonProps, DataTableSortButtonContext<T>> | undefined;
  /**
   * 全局 action 按钮 props，与列级 action.buttonProps 深度合并，列级优先
   */
  actionButtonProps?: DataTableDynamic<ButtonProps, DataTableActionButtonContext<T>> | undefined;
  /**
   * 行内最多展示多少 action 按钮，超出折叠到 overflow
   */
  actionsMaxInline?: number | undefined;
  actionsOverflowTrigger?: DataTableDynamic<ButtonProps, CellContext<T, unknown>> | undefined;
  /**
   * 启用列宽拖拽，传函数可按列动态决定
   */
  resizable?: boolean | ((col: DataTableDataColumn<T>) => boolean) | undefined;
  /**
   * - 'onChange' 拖动中实时重排
   * - 'onEnd' 释放后才更新
   * @default "\"onChange\""
   */
  columnResizeMode?: "onChange" | "onEnd" | undefined;
  /**
   * 单元格内边距密度
   */
  density?: DataTableDensityPreset | { th?: string | ((cell: Header<T, unknown>) => string) | undefined; td?: string | ((cell: Cell<T, unknown>) => string) | undefined; } | undefined;
  meta?: TableMeta<T> | undefined;
  rowClass?: string | ((row: T) => string) | undefined;
  rowStyle?: string | Record<string, string> | ((row: T) => string | Record<string, string>) | undefined;
  /**
   * 单元格溢出 Tooltip：true 单行 / number 多行 / false 禁用 / 函数 动态
   */
  tooltip?: number | boolean | ((ctx: CellContext<T, unknown>) => number | boolean) | undefined;
  tooltipProps?: OmitByKey<TooltipProps, "text"> | undefined;
  /**
   * 单元格文本截断：true 单行 / number 多行 / false 禁用 / 函数 动态
   * @default "true"
   */
  truncate?: number | boolean | ((ctx: CellContext<T, unknown>) => number | boolean) | undefined;
  sortingOptions?: Omit<SortingOptions<T>, "getSortedRowModel" | "onSortingChange"> | undefined;
  columnSizingOptions?: Omit<ColumnSizingOptions, "onColumnSizingChange" | "onColumnSizingInfoChange"> | undefined;
  columnPinningOptions?: Omit<ColumnPinningOptions, "onColumnPinningChange"> | undefined;
  rowSelectionOptions?: Omit<RowSelectionOptions<T>, "onRowSelectionChange"> | undefined;
  /**
   * 子行字段名，设置后启用树形模式
   */
  childrenKey?: (string & {}) | (keyof T & string) | undefined;
  /**
   * 树形缩进：number 每层缩进 px / string CSS 值 / 函数 动态返回 CSS
   * @default "\"1rem\""
   */
  indentSize?: string | number | ((ctx: CellContext<T, unknown>) => string) | undefined;
  /**
   * 树形模式下的默认展开行为，仅在未提供 expanded / expandedKeys 时生效。true 展开全部父级行，number 展开 depth 小于该值的父级行，函数按行与深度自定义
   */
  defaultExpanded?: number | boolean | ((row: T, depth: number) => boolean) | undefined;
  expandedOptions?: Omit<ExpandedOptions<T>, "getExpandedRowModel" | "onExpandedChange"> | undefined;
  expandOnRowClick?: boolean | undefined;
  selectOnRowClick?: boolean | undefined;
  /**
   * 可见列白名单（数组形）
   */
  columnVisibilityKeys?: string[] | undefined;
  /**
   * 隐藏列黑名单（数组形），与 columnVisibilityKeys 互斥，同传时白名单优先
   */
  columnVisibilityExcludeKeys?: string[] | undefined;
  /**
   * 选中行 id 列表（数组形）
   */
  rowSelectionKeys?: string[] | undefined;
  /**
   * 展开行 id 列表（数组形）
   */
  expandedKeys?: string[] | undefined;
  onSelect?: DataTableSelectHandler<T> | undefined;
  onHover?: DataTableHoverHandler<T> | undefined;
  onRowContextmenu?: DataTableContextmenuHandler<T> | undefined;
  /**
   * 分页配置，透传给 TanStack / UTable
   * - 客户端分页：传入即启用，自动注入 getPaginationRowModel
   * - 服务端分页：manualPagination=true 并提供 rowCount 或 pageCount
   */
  paginationOptions?: Omit<PaginationOptions, "onPaginationChange"> | undefined;
  /**
   * 粘性表头
   * @default "true"
   */
  sticky?: boolean | "header" | "footer" | undefined;
  paginationUi?: (DataTablePaginationUi & { ui?: { root?: SlotClass; summary?: SlotClass; summaryText?: SlotClass; selectedCount?: SlotClass; actions?: SlotClass; pageSizeSelect?: SlotClass; pagination?: SlotClass; } | undefined; }) | undefined;
  /**
   * 触底加载回调，传入即启用无限滚动模式（自动隐藏内置分页、async 期间派生 loading）
   */
  loadMore?: (() => void | Promise<void>) | undefined;
  /**
   * 是否还能加载更多
   */
  canLoadMore?: boolean | undefined;
  /**
   * 触发 loadMore 的距底像素阈值
   */
  loadMoreDistance?: number | undefined;
  ui?: (Record<string, C> & { wrapper?: SlotClass; base?: SlotClass; tbody?: SlotClass; th?: SlotClass; td?: SlotClass; root?: SlotClass; caption?: SlotClass; thead?: SlotClass; tfoot?: SlotClass; tr?: SlotClass; separator?: SlotClass; empty?: SlotClass; loading?: SlotClass; }) | undefined;
  /**
   * The element or component this component should render as.
   */
  as?: any;
  data?: T[] | undefined;
  caption?: string | undefined;
  /**
   * Enable virtualization for large datasets.
   * Note: row pinning is not supported when virtualization is enabled.
   */
  virtualize?: boolean | (Partial<Omit<VirtualizerOptions<Element, Element>, "count" | "estimateSize" | "overscan">> & { getScrollElement?: (() => Element | null) | undefined; overscan?: number | undefined; estimateSize?: number | ((index: number) => number) | undefined; }) | undefined;
  /**
   * The text to display when the table is empty.
   */
  empty?: string | undefined;
  loadingColor?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "important" | "neutral" | undefined;
  loadingAnimation?: "carousel" | "carousel-inverse" | "swing" | "elastic" | undefined;
  /**
   * Use the `watchOptions` prop to customize reactivity (for ex: disable deep watching for changes in your data or limiting the max traversal depth). This can improve performance by reducing unnecessary re-renders, but it should be used with caution as it may lead to unexpected behavior if not managed properly.
   */
  watchOptions?: WatchOptions<boolean> | undefined;
  globalFilterOptions?: Omit<GlobalFilterOptions<T>, "onGlobalFilterChange"> | undefined;
  columnFiltersOptions?: Omit<ColumnFiltersOptions<T>, "getFilteredRowModel" | "onColumnFiltersChange"> | undefined;
  visibilityOptions?: Omit<VisibilityOptions, "onColumnVisibilityChange"> | undefined;
  groupingOptions?: Omit<GroupingOptions, "onGroupingChange"> | undefined;
  rowPinningOptions?: Omit<RowPinningOptions<T>, "onRowPinningChange"> | undefined;
  facetedOptions?: FacetedOptions<T> | undefined;
  state?: Partial<TableState> | undefined;
  onStateChange?: ((updater: Updater<TableState>) => void) | undefined;
  renderFallbackValue?: any;
  /**
   * An array of extra features that you can add to the table instance.
   */
  _features?: TableFeature<any>[] | undefined;
  /**
   * Set this option to override any of the `autoReset...` feature options.
   */
  autoResetAll?: boolean | undefined;
  /**
   * Set this option to `true` to output all debugging information to the console.
   */
  debugAll?: boolean | undefined;
  /**
   * Set this option to `true` to output cell debugging information to the console.
   */
  debugCells?: boolean | undefined;
  /**
   * Set this option to `true` to output column debugging information to the console.
   */
  debugColumns?: boolean | undefined;
  /**
   * Set this option to `true` to output header debugging information to the console.
   */
  debugHeaders?: boolean | undefined;
  /**
   * Set this option to `true` to output row debugging information to the console.
   */
  debugRows?: boolean | undefined;
  /**
   * Set this option to `true` to output table debugging information to the console.
   */
  debugTable?: boolean | undefined;
  /**
   * Default column options to use for all column defs supplied to the table.
   */
  defaultColumn?: Partial<ColumnDef<T, unknown>> | undefined;
  /**
   * This optional function is used to derive a unique ID for any given row. If not provided the rows index is used (nested rows join together with `.` using their grandparents' index eg. `index.index.index`). If you need to identify individual rows that are originating from any server-side operations, it's suggested you use this function to return an ID that makes sense regardless of network IO/ambiguity eg. a userId, taskId, database ID field, etc.
   */
  getRowId?: ((originalRow: T, index: number, parent?: Row<T> | undefined) => string) | undefined;
  /**
   * This optional function is used to access the sub rows for any given row. If you are using nested rows, you will need to use this function to return the sub rows object (or undefined) from the row.
   */
  getSubRows?: ((originalRow: T, index: number) => T[] | undefined) | undefined;
  /**
   * Use this option to optionally pass initial state to the table. This state will be used when resetting various table states either automatically by the table (eg. `options.autoResetPageIndex`) or via functions like `table.resetRowSelection()`. Most reset function allow you optionally pass a flag to reset to a blank/default state instead of the initial state.
   * 
   * Table state will not be reset when this object changes, which also means that the initial state object does not need to be stable.
   */
  initialState?: InitialTableState | undefined;
  /**
   * This option is used to optionally implement the merging of table options.
   */
  mergeOptions?: ((defaultOptions: TableOptions<T>, options: Partial<TableOptions<T>>) => TableOptions<T>) | undefined;
  cellpadding?: Numberish | undefined;
  cellspacing?: Numberish | undefined;
  summary?: string | undefined;
  width?: Numberish | undefined;
  pagination?: PaginationState | undefined;
  columnVisibility?: VisibilityState | undefined;
  columnPinning?: ColumnPinningState | undefined;
  columnSizing?: ColumnSizingState | undefined;
  rowSelection?: RowSelectionState | undefined;
  /**
   * @default "{ top: [], bottom: [] }"
   */
  rowPinning?: RowPinningState | undefined;
  /**
   * @default "[]"
   */
  sorting?: SortingState | undefined;
  expanded?: ExpandedState | undefined;
}
```

### Emits

```ts
/**
 * Emitted events for the MDataTable component
 */
interface MDataTableEmits {
  update:pagination: (payload: [value: PaginationState | undefined]) => void;
  update:columnVisibility: (payload: [value: VisibilityState | undefined]) => void;
  update:columnPinning: (payload: [value: ColumnPinningState | undefined]) => void;
  update:columnSizing: (payload: [value: ColumnSizingState | undefined]) => void;
  update:rowSelection: (payload: [value: RowSelectionState | undefined]) => void;
  update:rowPinning: (payload: [value: RowPinningState]) => void;
  update:sorting: (payload: [value: SortingState]) => void;
  update:expanded: (payload: [value: ExpandedState | undefined]) => void;
  update:columnVisibilityKeys: (payload: [value: string[] | undefined]) => void;
  update:columnVisibilityExcludeKeys: (payload: [value: string[] | undefined]) => void;
  update:rowSelectionKeys: (payload: [value: string[] | undefined]) => void;
  update:expandedKeys: (payload: [value: string[] | undefined]) => void;
}
```

### Slots

```ts
/**
 * Slots for the MDataTable component
 */
interface MDataTableSlots {
  expanded(): any;
  empty(): any;
  loading(): any;
  caption(): any;
  body-top(): any;
  body-bottom(): any;
  pagination(): any;
  pagination-summary(): any;
  pagination-actions(): any;
}
```

除上述具名插槽外，还支持按列 id 命名的动态插槽 `#<column>-header`、`#<column>-cell`、`#<column>-footer`，详见[列配置 · 表头与单元格插槽](/docs/data-table/columns#%E8%A1%A8%E5%A4%B4%E4%B8%8E%E5%8D%95%E5%85%83%E6%A0%BC%E6%8F%92%E6%A7%BD)。

### Expose

通过 [`useTemplateRef`](https://vuejs.org/api/composition-api-helpers.html#usetemplateref){rel="[\"nofollow\"]"} 访问类型化的组件实例（`DataTableExposed<T>`）。

| Name                    | Type                                                                                               |
| ----------------------- | -------------------------------------------------------------------------------------------------- |
| `tableRef`              | `HTMLTableElement | null`   
 原生 table 元素                                                          |
| `tableApi`              | `Table<T> | null`   
 TanStack Table 实例                                                            |
| `el`                    | `HTMLElement | null`   
 UTable 根元素（滚动容器）                                                          |
| `scrollToTop(options?)` | `void`   
 滚动到顶部                                                                                   |
| `clearSelection()`      | `void`   
 清空行选择                                                                                   |
| `expandToDepth(depth)`  | `void`   
 展开到指定层级，`depth=0` 等价收起全部                                                                |
| `collapseAll()`         | `void`   
 收起全部行                                                                                   |
| `treeSelection`         | `TreeSelectionResult<T>`   
 树形选中派生：`selected`、`leaves`、`parents`、`halfSelected`、`strictlyChecked` |

### 列类型

`columns` 数组的每一项是 `DataTableColumn<T>`，按形态区分：

| 类型                             | 判定                    | 说明                             |
| ------------------------------ | --------------------- | ------------------------------ |
| `DataTableDataColumn<T>`       | 含 `accessorKey`       | 数据列：排序、固定、截断、tooltip、可见性、自定义渲染 |
| `DataTableGroupColumn<T>`      | 含 `children`          | 分组表头                           |
| `DataTableSelectionColumn<T>`  | `type: 'selection'`   | 选择列（单/多选、树形策略）                 |
| `DataTableIndexColumn<T>`      | `type: 'index'`       | 索引列                            |
| `DataTableExpandColumn<T>`     | `type: 'expand'`      | 展开列                            |
| `DataTableRowPinningColumn<T>` | `type: 'row-pinning'` | 行固定列                           |
| `DataTableActionsColumn<T>`    | `type: 'actions'`     | 操作列                            |

### 事件处理类型

事件处理器使用以下类型，参数由 contextual typing 推断：

| 类型                               | 用途                 |
| -------------------------------- | ------------------ |
| `DataTableSelectHandler<T>`      | `@select`          |
| `DataTableHoverHandler<T>`       | `@hover`           |
| `DataTableContextmenuHandler<T>` | `@row-contextmenu` |

## Theme

<component-theme slug="DataTable"></component-theme>## Changelog

See commit history for [src/runtime/components/介绍.vue](https://github.com/mhaibaraai/movk-nuxt/commits/main/src/runtime/components/介绍.vue).


## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
