组件库文档
本教程将详细介绍如何为 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.tsMonorepo 工作区结构
对于大型组件库,推荐使用 Monorepo:
my-ui/
├── packages/
│ ├── components/ # 组件源码
│ │ ├── src/
│ │ │ ├── button/
│ │ │ └── index.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── utils/ # 工具函数
│ ├── theme/ # 主题包
│ └── docs/ # 文档站点
│ ├── .vitepress/
│ ├── components/
│ ├── package.json
│ └── tsconfig.json
├── pnpm-workspace.yaml
├── package.json
└── tsconfig.jsonpnpm-workspace.yaml:
packages:
- 'packages/*'packages/docs/package.json:
{
"name": "@my-ui/docs",
"private": true,
"scripts": {
"dev": "vitepress dev",
"build": "vitepress build",
"preview": "vitepress preview"
},
"devDependencies": {
"@my-ui/components": "workspace:*",
"vitepress": "^2.0.0",
"vue": "^3.4.0"
}
}VitePress 配置别名:
// packages/docs/.vitepress/config.mts
import { defineConfig } from 'vitepress'
import { resolve } from 'path'
export default defineConfig({
vite: {
resolve: {
alias: {
// 直接引用组件源码,无需先构建
'@my-ui/components': resolve(__dirname, '../../components/src')
}
}
}
})基础配置
VitePress 配置
// 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 组件
创建一个可交互的组件演示容器:
<!-- 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:
# 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 文档的表格组件:
<!-- 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 表格
## 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' }
]" />Props 自动生成
手动维护 API 表格容易遗漏和出错,推荐使用工具从 TypeScript 类型自动生成。
方案一:vue-component-meta
vue-component-meta 可以从 Vue 组件的 TypeScript 定义中提取 Props 信息:
npm install -D vue-component-meta创建提取脚本:
// scripts/gen-api-table.ts
import { createComponentMetaChecker } from 'vue-component-meta'
import fs from 'fs'
import path from 'path'
const checker = createComponentMetaChecker(
path.resolve(__dirname, '../tsconfig.json')
)
interface PropMeta {
name: string
type: string
default?: string
required: boolean
description?: string
tags?: string[]
}
function extractProps(componentPath: string): PropMeta[] {
const meta = checker.getComponentMeta(componentPath)
return meta.props
.filter(prop => !prop.name.startsWith('_') && !prop.name.startsWith('on'))
.map(prop => ({
name: prop.name,
type: prop.type || 'any',
default: prop.default,
required: prop.required || false,
description: prop.description,
tags: prop.tags
}))
}
function extractEvents(componentPath: string) {
const meta = checker.getComponentMeta(componentPath)
return meta.events
.filter(event => !event.name.startsWith('_'))
.map(event => ({
name: event.name,
type: event.type || '(...args: any[]) => void',
description: event.description
}))
}
function extractSlots(componentPath: string) {
const meta = checker.getComponentMeta(componentPath)
return meta.slots
.filter(slot => !slot.name.startsWith('_'))
.map(slot => ({
name: slot.name,
description: slot.description
}))
}
// 生成 API 文档 Markdown 片段
function generateApiSection(
componentName: string,
componentPath: string
): string {
const props = extractProps(componentPath)
const events = extractEvents(componentPath)
const slots = extractSlots(componentPath)
let md = '## API\n\n'
// Props
if (props.length) {
md += '### Props\n\n'
md += '| 属性名 | 说明 | 类型 | 默认值 |\n'
md += '|-------|------|------|-------|\n'
for (const prop of props) {
const required = prop.required ? ' *(必填)*' : ''
md += `| \`${prop.name}\`${required} | ${prop.description || '-'} | \`${prop.type}\` | ${prop.default ? `\`${prop.default}\`` : '-'} |\n`
}
md += '\n'
}
// Events
if (events.length) {
md += '### Events\n\n'
md += '| 事件名 | 说明 | 回调参数 |\n'
md += '|-------|------|---------|\n'
for (const event of events) {
md += `| \`${event.name}\` | ${event.description || '-'} | \`${event.type}\` |\n`
}
md += '\n'
}
// Slots
if (slots.length) {
md += '### Slots\n\n'
md += '| 插槽名 | 说明 |\n'
md += '|-------|------|\n'
for (const slot of slots) {
md += `| \`${slot.name}\` | ${slot.description || '-'} |\n`
}
}
return md
}
// 批量生成
const components = [
{ name: 'Button', path: 'src/components/Button/Button.vue' },
{ name: 'Input', path: 'src/components/Input/Input.vue' },
{ name: 'Select', path: 'src/components/Select/Select.vue' }
]
for (const comp of components) {
const md = generateApiSection(comp.name, comp.path)
const outPath = path.join('docs', 'api-snippets', `${comp.name.toLowerCase()}-api.md`)
fs.mkdirSync(path.dirname(outPath), { recursive: true })
fs.writeFileSync(outPath, md, 'utf-8')
console.log(`✅ Generated API docs for ${comp.name}`)
}在组件文档中引入生成的片段:
# Button 按钮
常用的操作按钮,提供多种样式、尺寸和状态。
## 代码演示
### 基础用法
...
<<< @/api-snippets/button-api.md方案二:vue-docgen-api
vue-docgen-api 通过解析 JSDoc 注释提取组件信息:
npm install -D vue-docgen-api// scripts/gen-api-docgen.ts
import { parse } from 'vue-docgen-api'
import fs from 'fs'
import path from 'path'
async function generateApiDocs(componentPath: string) {
const doc = await parse(componentPath)
let md = '## API\n\n'
// Props
if (doc.props?.length) {
md += '### Props\n\n'
md += '| 属性名 | 说明 | 类型 | 默认值 |\n'
md += '|-------|------|------|-------|\n'
for (const prop of doc.props) {
const type = prop.type?.name || 'any'
const desc = prop.description || '-'
const defaultVal = prop.defaultValue?.value || '-'
const required = prop.required ? ' *(必填)*' : ''
md += `| \`${prop.name}\`${required} | ${desc} | \`${type}\` | \`${defaultVal}\` |\n`
}
md += '\n'
}
// Events
if (doc.events?.length) {
md += '### Events\n\n'
md += '| 事件名 | 说明 | 回调参数 |\n'
md += '|-------|------|---------|\n'
for (const event of doc.events) {
md += `| \`${event.name}\` | ${event.description || '-'} | - |\n`
}
md += '\n'
}
// Slots
if (doc.slots?.length) {
md += '### Slots\n\n'
md += '| 插槽名 | 说明 |\n'
md += '|-------|------|\n'
for (const slot of doc.slots) {
md += `| \`${slot.name}\` | ${slot.description || '-'} |\n`
}
}
return md
}方案三:构建时自动注入
将 API 提取集成到 VitePress 构建流程中:
// docs/.vitepress/plugins/api-table-plugin.ts
import type { Plugin } from 'vite'
import { createComponentMetaChecker } from 'vue-component-meta'
import path from 'path'
export function apiTablePlugin(): Plugin {
return {
name: 'vitepress-plugin-api-table',
// 在开发模式下动态提供 API 数据
resolveId(id) {
if (id.startsWith('virtual:api-table:')) {
return id
}
return null
},
async load(id) {
if (!id.startsWith('virtual:api-table:')) return null
const componentName = id.replace('virtual:api-table:', '')
const checker = createComponentMetaChecker(
path.resolve(process.cwd(), 'tsconfig.json')
)
const componentPath = `src/components/${componentName}/${componentName}.vue`
const meta = checker.getComponentMeta(componentPath)
const props = meta.props
.filter(p => !p.name.startsWith('_') && !p.name.startsWith('on'))
.map(p => ({
name: p.name,
type: p.type,
default: p.default,
required: p.required,
description: p.description
}))
return `export default ${JSON.stringify(props)}`
}
}
}在 VitePress 配置中使用:
// docs/.vitepress/config.mts
import { apiTablePlugin } from './plugins/api-table-plugin'
export default defineConfig({
vite: {
plugins: [apiTablePlugin()]
}
})在组件中使用:
<script setup lang="ts">
import buttonProps from 'virtual:api-table:Button'
</script>
<template>
<ApiTable title="Props" :data="buttonProps" />
</template>组件沙箱集成
StackBlitz 集成
在文档中嵌入 StackBlitz 在线编辑器,让用户直接体验组件:
<!-- docs/.vitepress/theme/components/StackBlitzEmbed.vue -->
<script setup lang="ts">
interface Props {
projectId?: string
githubRepo?: string
file?: string
title?: string
}
const props = withDefaults(defineProps<Props>(), {
projectId: '',
githubRepo: '',
file: 'src/App.vue',
title: '在线编辑'
})
const src = props.projectId
? `https://stackblitz.com/edit/${props.projectId}?embed=1&file=${props.file}`
: `https://stackblitz.com/github/${props.githubRepo}?embed=1&file=${props.file}`
</script>
<template>
<div class="stackblitz-embed">
<p class="embed-title">{{ title }}</p>
<iframe
:src="src"
frameborder="0"
loading="lazy"
allow="clipboard-read; clipboard-write"
style="width: 100%; height: 500px; border-radius: 8px; border: 1px solid var(--vp-c-divider);"
/>
</div>
</template>
<style scoped>
.embed-title {
font-weight: 600;
margin-bottom: 8px;
color: var(--vp-c-text-1);
}
</style>CodeSandbox 集成
<!-- docs/.vitepress/theme/components/CodeSandboxEmbed.vue -->
<script setup lang="ts">
interface Props {
sandboxId: string
title?: string
module?: string
}
const props = withDefaults(defineProps<Props>(), {
title: '在线编辑',
module: '/src/App.vue'
})
const src = `https://codesandbox.io/embed/${props.sandboxId}?module=${props.module}&fontsize=14&hidenavigation=1&theme=dark`
</script>
<template>
<div class="codesandbox-embed">
<p class="embed-title">{{ title }}</p>
<iframe
:src="src"
frameborder="0"
loading="lazy"
style="width: 100%; height: 500px; border-radius: 8px; border: 1px solid var(--vp-c-divider);"
allow="accelerometer; clipboard-write"
/>
</div>
</template>一键打开编辑器按钮
不嵌入 iframe,改为按钮跳转:
<!-- docs/.vitepress/theme/components/EditOnline.vue -->
<script setup lang="ts">
interface Props {
type?: 'stackblitz' | 'codesandbox'
url: string
}
const props = withDefaults(defineProps<Props>(), {
type: 'stackblitz'
})
const label = props.type === 'stackblitz' ? 'StackBlitz' : 'CodeSandbox'
const icon = props.type === 'stackblitz' ? '⚡' : '📦'
</script>
<template>
<a
:href="url"
target="_blank"
rel="noopener noreferrer"
class="edit-online-btn"
>
<span class="btn-icon">{{ icon }}</span>
<span>在 {{ label }} 中打开</span>
</a>
</template>
<style scoped>
.edit-online-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
border: 1px solid var(--vp-c-brand-1);
border-radius: 8px;
color: var(--vp-c-brand-1);
font-size: 14px;
text-decoration: none;
transition: all 0.2s ease;
}
.edit-online-btn:hover {
background: var(--vp-c-brand-soft);
}
</style>主题定制
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;
}主题配置页面
创建主题配置演示页面,让用户实时预览主题变化:
<!-- docs/.vitepress/theme/components/ThemeCustomizer.vue -->
<script setup lang="ts">
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>使用 JSDoc 注释
在组件源码中添加 JSDoc 注释,为自动生成 API 文档提供信息:
<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
})
/**
* 点击按钮时触发
* @event click
* @type {(event: MouseEvent) => void}
*/
const emit = defineEmits<{
(e: 'click', event: MouseEvent): void
}>()
</script>完整组件文档模板
---
title: Button 按钮
description: 常用的操作按钮组件
---
# Button 按钮
常用的操作按钮,提供多种样式、尺寸和状态。
## 何时使用
- 需要用户执行某个操作时
- 表单提交、确认/取消等场景
- 需要强调某个操作时
## 代码演示
### 基础用法
最基本的按钮用法。
```vue
<template>
<Button>默认按钮</Button>
<Button type="primary">主要按钮</Button>
<Button type="success">成功按钮</Button>
</template>按钮尺寸
提供三种尺寸:小、中、大。
<template>
<Button size="small">小按钮</Button>
<Button size="medium">中按钮</Button>
<Button size="large">大按钮</Button>
</template>禁用状态
按钮不可用状态。
<template>
<Button disabled>禁用按钮</Button>
</template>加载状态
显示加载中的按钮。
<template>
<Button loading>加载中</Button>
</template>API
Props
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| type | 按钮类型 | 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'default' |
| size | 按钮尺寸 | 'small' | 'medium' | 'large' | '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/distMonorepo 的 CI 配置
# .github/workflows/docs.yml (Monorepo 版本)
name: Deploy Docs
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
# 先构建组件包
- run: pnpm --filter @my-ui/components build
# 再构建文档
- run: pnpm --filter @my-ui/docs build
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: packages/docs/.vitepress/dist最佳实践总结
| 实践 | 说明 |
|---|---|
| 一致的文档结构 | 每个组件文档遵循相同格式 |
| 丰富的示例 | 提供多种使用场景 |
| 清晰的 API | 完整记录 props、events、slots |
| 自动生成 API | 从 TypeScript 类型自动提取,避免遗漏 |
| 可交互演示 | 让用户直接体验组件 |
| 在线编辑 | 嵌入 StackBlitz/CodeSandbox 一键体验 |
| 主题定制器 | 实时预览主题变化 |
| 自动化部署 | 每次提交自动更新文档 |
| Monorepo | 大型组件库使用工作区管理 |
相关主题
- API 文档站 — 构建自动化 API 文档
- 性能优化 — 提升文档站点性能
- CI/CD 自动化部署 — 实现自动发布
- 插件开发指南 — 开发文档增强插件
下一步
- 查看 API 文档站 学习构建 API 文档
- 了解 性能优化 提升文档站点性能
- 学习 CI/CD 自动化部署 实现自动发布
🎯 进阶挑战
完成基础教程后,尝试以下挑战来提升你的技能:
挑战 1:组件 Props 编辑器 ⭐⭐⭐
目标:创建一个可视化的 Props 编辑器,实时预览组件变化。
提示:
- 使用 Vue 的
defineProps类型推断 - 动态渲染表单控件
- 双向绑定实现实时预览
参考思路:
<script setup lang="ts">
import { reactive } from 'vue'
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<Record<string, any>>({})
</script>挑战 2:组件代码在线编辑 ⭐⭐⭐⭐
目标:集成 Monaco Editor,实现在线编辑组件代码。
提示:
- 使用
@monaco-editor/loader - 实现代码热更新
- 添加错误提示
挑战 3:组件主题切换器 ⭐⭐⭐
目标:创建实时主题切换器,让用户自定义组件库主题。
提示:
- 使用 CSS 变量
- 实时预览主题变化
- 导出主题配置
挑战 4:组件使用统计 ⭐⭐⭐
目标:统计每个组件文档的访问量,展示热门组件。
提示:
- 使用数据加载器读取访问数据
- 创建热度排行榜
- 可视化展示统计数据
挑战 5:组件版本差异对比 ⭐⭐⭐⭐
目标:展示不同版本组件的 API 差异。
提示:
- 使用 diff 算法对比
- 高亮变化部分
- 添加版本选择器
挑战 6:组件可访问性检测 ⭐⭐⭐⭐
目标:为组件演示添加 a11y 检测功能。
提示:
- 使用 axe-core 库
- 在演示容器中运行检测
- 展示可访问性问题列表