Skip to content

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 上查看。

在线演示


通过这个案例,你可以学习如何使用 VitePress 构建专业的 API 文档网站,提供完整的接口说明和交互式测试功能。

vitepress开发指南