组件库文档
本教程将详细介绍如何为 Vue 组件库搭建一个专业、美观的文档站点。
项目规划
目录结构
一个完整的组件库文档项目结构:
my-ui/
├── src/ # 组件源码
│ ├── components/
│ │ ├── Button/
│ │ │ ├── index.ts
│ │ │ ├── Button.vue
│ │ │ └── Button.test.ts
│ │ ├── Input/
│ │ └── index.ts
│ └── index.ts
├── docs/ # 文档目录
│ ├── .vitepress/
│ │ ├── config.mts
│ │ └── theme/
│ │ ├── index.ts
│ │ └── components/
│ │ ├── DemoPreview.vue
│ │ ├── ApiTable.vue
│ │ └── ComponentCard.vue
│ ├── components/ # 组件文档
│ │ ├── button.md
│ │ ├── input.md
│ │ └── index.md
│ ├── guide/ # 指南文档
│ │ ├── getting-started.md
│ │ ├── installation.md
│ │ └── theming.md
│ ├── public/
│ │ └── logo.svg
│ └── index.md
├── package.json
└── vite.config.ts基础配置
VitePress 配置
ts
// docs/.vitepress/config.mts
import { defineConfig } from 'vitepress'
import { resolve } from 'path'
export default defineConfig({
title: 'My UI',
description: '一个现代化的 Vue 3 组件库',
lang: 'zh-CN',
// 配置别名,方便在文档中引入组件
vite: {
resolve: {
alias: {
'@': resolve(__dirname, '../../src')
}
}
},
themeConfig: {
logo: '/logo.svg',
nav: [
{ text: '首页', link: '/' },
{ text: '指南', link: '/guide/getting-started' },
{ text: '组件', link: '/components/' },
{
text: '相关链接',
items: [
{ text: 'GitHub', link: 'https://github.com/user/my-ui' },
{ text: 'npm', link: 'https://npmjs.com/package/my-ui' }
]
}
],
sidebar: {
'/guide/': [
{
text: '开始',
items: [
{ text: '快速开始', link: '/guide/getting-started' },
{ text: '安装', link: '/guide/installation' },
{ text: '主题定制', link: '/guide/theming' }
]
}
],
'/components/': [
{
text: '基础组件',
collapsed: false,
items: [
{ text: 'Button 按钮', link: '/components/button' },
{ text: 'Input 输入框', link: '/components/input' },
{ text: 'Icon 图标', link: '/components/icon' }
]
},
{
text: '表单组件',
collapsed: false,
items: [
{ text: 'Form 表单', link: '/components/form' },
{ text: 'Select 选择器', link: '/components/select' }
]
}
]
},
search: {
provider: 'local'
},
outline: {
level: [2, 3],
label: '目录'
}
}
})组件演示系统
DemoPreview 组件
创建一个可交互的组件演示容器:
vue
<!-- docs/.vitepress/theme/components/DemoPreview.vue -->
<script setup lang="ts">
import { ref } from 'vue'
interface Props {
title?: string
description?: string
}
const props = withDefaults(defineProps<Props>(), {
title: '',
description: ''
})
const showCode = ref(false)
const copied = ref(false)
const copyCode = async () => {
const codeEl = document.querySelector('.demo-code pre code')
if (codeEl) {
await navigator.clipboard.writeText(codeEl.textContent || '')
copied.value = true
setTimeout(() => {
copied.value = false
}, 2000)
}
}
</script>
<template>
<div class="demo-preview">
<div v-if="title || description" class="demo-header">
<h4 v-if="title" class="demo-title">{{ title }}</h4>
<p v-if="description" class="demo-desc">{{ description }}</p>
</div>
<div class="demo-container">
<slot name="demo" />
</div>
<div class="demo-actions">
<button
class="action-btn"
:class="{ active: showCode }"
@click="showCode = !showCode"
>
{{ showCode ? '隐藏代码' : '显示代码' }}
</button>
<button class="action-btn" @click="copyCode">
{{ copied ? '已复制!' : '复制代码' }}
</button>
</div>
<div v-show="showCode" class="demo-code">
<slot name="code" />
</div>
</div>
</template>
<style scoped>
.demo-preview {
margin: 24px 0;
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
overflow: hidden;
}
.demo-header {
padding: 16px 20px;
border-bottom: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
}
.demo-title {
margin: 0 0 4px;
font-size: 16px;
font-weight: 600;
color: var(--vp-c-text-1);
}
.demo-desc {
margin: 0;
font-size: 14px;
color: var(--vp-c-text-2);
}
.demo-container {
padding: 32px;
background: var(--vp-c-bg);
}
.demo-actions {
display: flex;
gap: 8px;
padding: 12px 16px;
border-top: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
}
.action-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg);
color: var(--vp-c-text-2);
font-size: 13px;
cursor: pointer;
transition: all 0.25s ease;
}
.action-btn:hover {
border-color: var(--vp-c-brand-1);
color: var(--vp-c-brand-1);
}
.demo-code {
border-top: 1px solid var(--vp-c-divider);
background: var(--vp-code-block-bg);
}
</style>在文档中使用
在组件文档中使用 DemoPreview:
markdown
# Button 按钮
常用的操作按钮,提供多种样式和尺寸。
## 基础用法
<DemoPreview title="基础按钮" description="使用 type 属性定义按钮样式">
<template #demo>
<div class="button-demo">
<Button>默认按钮</Button>
<Button type="primary">主要按钮</Button>
</div>
</template>
<template #code>
```vue
<template>
<Button>默认按钮</Button>
<Button type="primary">主要按钮</Button>
</template>
```
</template>
</DemoPreview>注意
在 Markdown 中使用组件时,确保代码块中的 Vue 模板正确缩进,并且闭合标签正确。
API 文档组件
ApiTable 组件
用于展示组件 API 文档的表格组件:
vue
<!-- docs/.vitepress/theme/components/ApiTable.vue -->
<script setup lang="ts">
interface PropItem {
name: string
description: string
type: string
default?: string
required?: boolean
}
interface Props {
title?: string
data: PropItem[]
}
defineProps<Props>()
</script>
<template>
<div class="api-table">
<h4 v-if="title" class="api-title">{{ title }}</h4>
<table>
<thead>
<tr>
<th>属性名</th>
<th>说明</th>
<th>类型</th>
<th>默认值</th>
</tr>
</thead>
<tbody>
<tr v-for="item in data" :key="item.name">
<td>
<code class="prop-name">{{ item.name }}</code>
<span v-if="item.required" class="required-tag">必填</span>
</td>
<td>{{ item.description }}</td>
<td><code class="type-code">{{ item.type }}</code></td>
<td>
<code v-if="item.default" class="default-code">{{ item.default }}</code>
<span v-else class="empty">-</span>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<style scoped>
.api-table {
margin: 24px 0;
}
.api-title {
font-size: 18px;
font-weight: 600;
color: var(--vp-c-text-1);
margin: 0 0 16px;
}
.api-table table {
width: 100%;
border-collapse: collapse;
border-radius: 8px;
overflow: hidden;
}
.api-table th,
.api-table td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid var(--vp-c-divider);
}
.api-table th {
background: var(--vp-c-bg-soft);
font-weight: 600;
}
.prop-name {
padding: 2px 8px;
background: var(--vp-c-brand-soft);
color: var(--vp-c-brand-1);
border-radius: 4px;
font-size: 13px;
}
.required-tag {
display: inline-block;
margin-left: 6px;
padding: 1px 6px;
background: #fef3c7;
color: #b45309;
border-radius: 4px;
font-size: 11px;
}
</style>使用 API 表格
markdown
## API
### Props
<ApiTable :data="[
{ name: 'type', description: '按钮类型', type: 'string', default: 'default' },
{ name: 'size', description: '按钮尺寸', type: 'string', default: 'medium' },
{ name: 'disabled', description: '是否禁用', type: 'boolean', default: 'false' }
]" />主题定制
CSS 变量
组件库使用 CSS 变量实现主题定制:
css
/* docs/.vitepress/theme/style.css */
:root {
/* 品牌色 */
--my-primary-color: #6366f1;
--my-success-color: #10b981;
--my-warning-color: #f59e0b;
--my-danger-color: #ef4444;
/* 边框圆角 */
--my-border-radius-sm: 4px;
--my-border-radius-md: 8px;
--my-border-radius-lg: 12px;
/* 阴影 */
--my-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--my-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--my-shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
}
.dark {
--my-primary-color: #818cf8;
--my-success-color: #34d399;
--my-warning-color: #fbbf24;
--my-danger-color: #f87171;
}主题配置页面
创建主题配置演示页面,让用户实时预览主题变化:
vue
<!-- docs/.vitepress/theme/components/ThemeCustomizer.vue -->
<script setup>
import { ref, watch } from 'vue'
const primaryColor = ref('#6366f1')
const borderRadius = ref(8)
watch([primaryColor, borderRadius], ([color, radius]) => {
document.documentElement.style.setProperty('--my-primary-color', color)
document.documentElement.style.setProperty('--my-border-radius-md', radius + 'px')
})
</script>
<template>
<div class="theme-customizer">
<div class="customizer-item">
<label>主题色</label>
<input type="color" v-model="primaryColor" />
</div>
<div class="customizer-item">
<label>圆角大小: {{ borderRadius }}px</label>
<input type="range" v-model="borderRadius" min="0" max="20" />
</div>
</div>
</template>自动生成 API 文档
使用 JSDoc 注释
在组件源码中添加 JSDoc 注释:
vue
<script setup lang="ts">
/**
* Button 组件
* @component Button
* @description 常用的操作按钮组件
*/
interface ButtonProps {
/**
* 按钮类型
* @values default, primary, success, warning, danger
*/
type?: 'default' | 'primary' | 'success' | 'warning' | 'danger'
/**
* 按钮尺寸
* @values small, medium, large
*/
size?: 'small' | 'medium' | 'large'
/**
* 是否禁用
*/
disabled?: boolean
}
const props = withDefaults(defineProps<ButtonProps>(), {
type: 'default',
size: 'medium',
disabled: false
})
</script>完整组件文档模板
markdown
---
title: Button 按钮
description: 常用的操作按钮组件
---
# Button 按钮
常用的操作按钮,提供多种样式、尺寸和状态。
## 何时使用
- 需要用户执行某个操作时
- 表单提交、确认/取消等场景
- 需要强调某个操作时
## 代码演示
### 基础用法
最基本的按钮用法。
```vue
<template>
<Button>默认按钮</Button>
<Button type="primary">主要按钮</Button>
<Button type="success">成功按钮</Button>
</template>按钮尺寸
提供三种尺寸:小、中、大。
vue
<template>
<Button size="small">小按钮</Button>
<Button size="medium">中按钮</Button>
<Button size="large">大按钮</Button>
</template>禁用状态
按钮不可用状态。
vue
<template>
<Button disabled>禁用按钮</Button>
</template>加载状态
显示加载中的按钮。
vue
<template>
<Button loading>加载中</Button>
</template>API
Props
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| type | 按钮类型 | string | default |
| size | 按钮尺寸 | string | medium |
| disabled | 是否禁用 | boolean | false |
| loading | 是否加载中 | boolean | false |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| click | 点击按钮时触发 | (event: MouseEvent) |
Slots
| 插槽名 | 说明 |
|---|---|
| default | 按钮内容 |
| icon | 按钮图标 |
设计指南
按钮层级
- 主要按钮:用于主要操作,一个页面最多一个
- 次要按钮:用于次要操作
- 文字按钮:用于最低优先级的操作
最佳实践
- ✅ 保持按钮文案简洁明了
- ✅ 危险操作使用红色警示
- ❌ 不要在一个区域使用过多按钮
- ❌ 不要使用过长的按钮文案
## 部署与发布
### 自动化部署
```yaml
# .github/workflows/docs.yml
name: Deploy Docs
on:
push:
branches: [main]
paths:
- 'docs/**'
- 'src/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run docs:build
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/.vitepress/dist最佳实践总结
| 实践 | 说明 |
|---|---|
| 一致的文档结构 | 每个组件文档遵循相同格式 |
| 丰富的示例 | 提供多种使用场景 |
| 清晰的 API | 完整记录 props、events、slots |
| 可交互演示 | 让用户直接体验组件 |
| 自动化部署 | 每次提交自动更新文档 |
下一步
- 查看 API 文档站 学习构建 API 文档
- 了解 性能优化 提升文档站点性能
- 学习 CI/CD 自动化部署 实现自动发布
🎯 进阶挑战
完成基础教程后,尝试以下挑战来提升你的技能:
挑战 1:组件 Props 编辑器 ⭐⭐⭐
目标:创建一个可视化的 Props 编辑器,实时预览组件变化。
提示:
- 使用 Vue 的
defineProps类型推断 - 动态渲染表单控件
- 双向绑定实现实时预览
参考思路:
vue
<script setup>
const propsConfig = [
{ name: 'type', type: 'select', options: ['default', 'primary', 'danger'] },
{ name: 'size', type: 'select', options: ['small', 'medium', 'large'] },
{ name: 'disabled', type: 'boolean' }
]
const currentProps = reactive({})
</script>挑战 2:组件代码在线编辑 ⭐⭐⭐⭐
目标:集成 Monaco Editor,实现在线编辑组件代码。
提示:
- 使用
@monaco-editor/loader - 实现代码热更新
- 添加错误提示
挑战 3:组件主题切换器 ⭐⭐⭐
目标:创建实时主题切换器,让用户自定义组件库主题。
提示:
- 使用 CSS 变量
- 实时预览主题变化
- 导出主题配置
挑战 4:组件使用统计 ⭐⭐⭐
目标:统计每个组件文档的访问量,展示热门组件。
提示:
- 使用数据加载器读取访问数据
- 创建热度排行榜
- 可视化展示统计数据
挑战 5:组件版本差异对比 ⭐⭐⭐⭐
目标:展示不同版本组件的 API 差异。
提示:
- 使用 diff 算法对比
- 高亮变化部分
- 添加版本选择器
挑战 6:组件可访问性检测 ⭐⭐⭐⭐
目标:为组件演示添加 a11y 检测功能。
提示:
- 使用 axe-core 库
- 在演示容器中运行检测
- 展示可访问性问题列表