API 文档网站
使用 VitePress 构建专业的 API 文档网站,提供清晰的接口说明、交互式示例和实时测试功能。
项目概述
API 文档网站是开发者必备的工具,它需要清晰地展示接口信息、参数说明、响应格式等。VitePress 凭借其出色的文档生成能力,是构建 API 文档的理想选择。
核心特性
- 📚 自动生成 - 从 OpenAPI/Swagger 规范自动生成文档
- 🔧 交互式测试 - 在线测试 API 接口
- 🎨 美观界面 - 现代化的文档界面设计
- 🔍 强大搜索 - 快速查找接口和参数
- 📱 响应式设计 - 适配各种设备
- 🌐 多语言支持 - 国际化文档
技术架构
核心技术栈
json
{
"framework": "VitePress",
"language": "TypeScript",
"styling": "CSS3 + Tailwind CSS",
"tools": [
"OpenAPI Generator",
"Swagger UI",
"Prism.js",
"Vue 3"
]
}
项目结构
api-docs/
├── docs/
│ ├── .vitepress/
│ │ ├── config.ts
│ │ ├── theme/
│ │ └── components/
│ ├── api/
│ │ ├── user.md
│ │ ├── auth.md
│ │ └── product.md
│ ├── guides/
│ └── examples/
├── scripts/
│ └── generate-docs.js
└── openapi.yaml
实现步骤
1. 项目初始化
bash
# 创建项目
npm create vitepress@latest api-docs
cd api-docs
# 安装依赖
npm install
npm install -D @types/swagger-ui swagger-ui-dist
2. 配置 VitePress
typescript
// .vitepress/config.ts
import { defineConfig } from 'vitepress'
export default defineConfig({
title: 'API 文档',
description: '完整的 API 接口文档',
themeConfig: {
nav: [
{ text: '首页', link: '/' },
{ text: 'API 参考', link: '/api/' },
{ text: '使用指南', link: '/guides/' },
{ text: '示例', link: '/examples/' }
],
sidebar: {
'/api/': [
{
text: '认证接口',
items: [
{ text: '用户登录', link: '/api/auth/login' },
{ text: '用户注册', link: '/api/auth/register' },
{ text: '刷新令牌', link: '/api/auth/refresh' }
]
},
{
text: '用户管理',
items: [
{ text: '获取用户信息', link: '/api/user/profile' },
{ text: '更新用户信息', link: '/api/user/update' },
{ text: '用户列表', link: '/api/user/list' }
]
}
]
}
}
})
3. 创建 API 文档组件
vue
<!-- .vitepress/components/ApiEndpoint.vue -->
<template>
<div class="api-endpoint">
<div class="method-url">
<span :class="['method', method.toLowerCase()]">{{ method }}</span>
<code class="url">{{ url }}</code>
</div>
<div class="description">
<p>{{ description }}</p>
</div>
<!-- 参数表格 -->
<div v-if="parameters" class="parameters">
<h3>请求参数</h3>
<table>
<thead>
<tr>
<th>参数名</th>
<th>类型</th>
<th>必填</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr v-for="param in parameters" :key="param.name">
<td><code>{{ param.name }}</code></td>
<td>{{ param.type }}</td>
<td>{{ param.required ? '是' : '否' }}</td>
<td>{{ param.description }}</td>
</tr>
</tbody>
</table>
</div>
<!-- 响应示例 -->
<div v-if="response" class="response">
<h3>响应示例</h3>
<pre><code>{{ JSON.stringify(response, null, 2) }}</code></pre>
</div>
<!-- 在线测试 -->
<div class="try-it">
<button @click="showTryIt = !showTryIt">
{{ showTryIt ? '隐藏' : '在线测试' }}
</button>
<div v-if="showTryIt" class="try-panel">
<!-- 测试表单 -->
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
defineProps({
method: String,
url: String,
description: String,
parameters: Array,
response: Object
})
const showTryIt = ref(false)
</script>
<style scoped>
.api-endpoint {
border: 1px solid var(--vp-c-border);
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.method-url {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
}
.method {
padding: 4px 8px;
border-radius: 4px;
font-weight: bold;
text-transform: uppercase;
font-size: 12px;
}
.method.get { background: #61affe; color: white; }
.method.post { background: #49cc90; color: white; }
.method.put { background: #fca130; color: white; }
.method.delete { background: #f93e3e; color: white; }
.url {
background: var(--vp-c-bg-mute);
padding: 8px 12px;
border-radius: 4px;
font-family: var(--vp-font-family-mono);
}
table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
th, td {
border: 1px solid var(--vp-c-border);
padding: 8px 12px;
text-align: left;
}
th {
background: var(--vp-c-bg-mute);
font-weight: 600;
}
pre {
background: var(--vp-c-bg-mute);
padding: 15px;
border-radius: 6px;
overflow-x: auto;
}
</style>
4. 编写 API 文档
markdown
<!-- docs/api/auth/login.md -->
# 用户登录
<ApiEndpoint
method="POST"
url="/api/auth/login"
description="用户登录接口,返回访问令牌"
:parameters="[
{ name: 'email', type: 'string', required: true, description: '用户邮箱' },
{ name: 'password', type: 'string', required: true, description: '用户密码' }
]"
:response="{
success: true,
data: {
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
user: {
id: 1,
email: 'user@example.com',
name: '张三'
}
}
}"
/>
## 详细说明
用户登录接口用于验证用户身份并返回访问令牌。
### 请求示例
```bash
curl -X POST https://api.example.com/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "password123"
}'
错误响应
状态码 | 说明 |
---|---|
400 | 请求参数错误 |
401 | 邮箱或密码错误 |
429 | 请求过于频繁 |
json
{
"success": false,
"error": {
"code": "INVALID_CREDENTIALS",
"message": "邮箱或密码错误"
}
}
### 5. 自动生成文档
```javascript
// scripts/generate-docs.js
const fs = require('fs')
const yaml = require('js-yaml')
// 读取 OpenAPI 规范
const openApiSpec = yaml.load(fs.readFileSync('openapi.yaml', 'utf8'))
// 生成 Markdown 文档
function generateApiDocs(spec) {
const paths = spec.paths
for (const [path, methods] of Object.entries(paths)) {
for (const [method, operation] of Object.entries(methods)) {
const markdown = generateMarkdown(path, method, operation)
const filename = `docs/api/${operation.operationId}.md`
fs.writeFileSync(filename, markdown)
}
}
}
function generateMarkdown(path, method, operation) {
return `# ${operation.summary}
<ApiEndpoint
method="${method.toUpperCase()}"
url="${path}"
description="${operation.description}"
:parameters="${JSON.stringify(operation.parameters || [])}"
:response="${JSON.stringify(operation.responses['200']?.content?.['application/json']?.example || {})}"
/>
${operation.description}
## 详细说明
${operation.description || ''}
`
}
generateApiDocs(openApiSpec)
高级功能
1. 交互式测试
vue
<!-- .vitepress/components/ApiTester.vue -->
<template>
<div class="api-tester">
<h3>在线测试</h3>
<form @submit.prevent="sendRequest">
<div v-for="param in parameters" :key="param.name" class="param-input">
<label>{{ param.name }} {{ param.required ? '*' : '' }}</label>
<input
v-model="formData[param.name]"
:type="getInputType(param.type)"
:required="param.required"
:placeholder="param.description"
/>
</div>
<button type="submit" :disabled="loading">
{{ loading ? '请求中...' : '发送请求' }}
</button>
</form>
<div v-if="response" class="response-panel">
<h4>响应结果</h4>
<div class="status" :class="statusClass">
{{ response.status }} {{ response.statusText }}
</div>
<pre><code>{{ JSON.stringify(response.data, null, 2) }}</code></pre>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import axios from 'axios'
const props = defineProps({
method: String,
url: String,
parameters: Array
})
const loading = ref(false)
const response = ref(null)
const formData = reactive({})
const statusClass = computed(() => {
if (!response.value) return ''
const status = response.value.status
if (status >= 200 && status < 300) return 'success'
if (status >= 400) return 'error'
return 'info'
})
function getInputType(type) {
switch (type) {
case 'integer':
case 'number':
return 'number'
case 'boolean':
return 'checkbox'
default:
return 'text'
}
}
async function sendRequest() {
loading.value = true
try {
const result = await axios({
method: props.method.toLowerCase(),
url: props.url,
data: props.method !== 'GET' ? formData : undefined,
params: props.method === 'GET' ? formData : undefined
})
response.value = {
status: result.status,
statusText: result.statusText,
data: result.data
}
} catch (error) {
response.value = {
status: error.response?.status || 0,
statusText: error.response?.statusText || 'Network Error',
data: error.response?.data || { message: error.message }
}
} finally {
loading.value = false
}
}
</script>
2. 代码生成器
vue
<!-- .vitepress/components/CodeGenerator.vue -->
<template>
<div class="code-generator">
<div class="language-tabs">
<button
v-for="lang in languages"
:key="lang"
:class="{ active: selectedLang === lang }"
@click="selectedLang = lang"
>
{{ lang }}
</button>
</div>
<div class="code-content">
<pre><code>{{ generatedCode }}</code></pre>
<button @click="copyCode" class="copy-btn">复制代码</button>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
method: String,
url: String,
parameters: Array
})
const selectedLang = ref('curl')
const languages = ['curl', 'javascript', 'python', 'java']
const generatedCode = computed(() => {
switch (selectedLang.value) {
case 'curl':
return generateCurl()
case 'javascript':
return generateJavaScript()
case 'python':
return generatePython()
case 'java':
return generateJava()
default:
return ''
}
})
function generateCurl() {
const params = props.parameters?.map(p => `"${p.name}": "value"`).join(',\n ')
return `curl -X ${props.method} ${props.url} \\
-H "Content-Type: application/json" \\
-d '{
${params}
}'`
}
function generateJavaScript() {
const params = props.parameters?.map(p => ` ${p.name}: 'value'`).join(',\n')
return `const response = await fetch('${props.url}', {
method: '${props.method}',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
${params}
})
})
const data = await response.json()
console.log(data)`
}
function copyCode() {
navigator.clipboard.writeText(generatedCode.value)
}
</script>
部署配置
GitHub Actions
yaml
# .github/workflows/deploy.yml
name: Deploy API Docs
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Generate API docs
run: npm run generate-docs
- name: Build
run: npm run build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/.vitepress/dist
最佳实践
1. 文档组织
- 按功能模块分组 - 将相关的 API 归类
- 统一命名规范 - 保持接口命名的一致性
- 版本管理 - 支持多版本 API 文档
2. 内容质量
- 详细的描述 - 每个接口都要有清晰的说明
- 完整的示例 - 提供请求和响应示例
- 错误处理 - 说明各种错误情况
3. 用户体验
- 快速搜索 - 支持全文搜索功能
- 交互测试 - 允许用户直接测试 API
- 代码生成 - 提供多种语言的代码示例
示例项目
完整的示例项目可以在 GitHub 上查看。
在线演示
- 文档网站: api-docs.example.com
- 源代码: GitHub Repository
通过这个案例,你可以学习如何使用 VitePress 构建专业的 API 文档网站,提供完整的接口说明和交互式测试功能。