Skip to main content
Glama
mcp-swagger-ui-development-guide.md16 kB
# MCP Swagger UI 开发指南 ## 快速开始 ### 环境要求 - **Node.js**: 18.0+ (推荐使用 LTS 版本) - **npm/pnpm**: 最新版本 - **TypeScript**: 5.0+ - **现代浏览器**: Chrome 90+, Firefox 88+, Safari 14+ ### 安装和启动 ```bash # 1. 进入项目目录 cd packages/mcp-swagger-ui # 2. 安装依赖 npm install # 3. 启动开发服务器 npm run dev # 4. 在浏览器中访问 # http://localhost:3000 ``` ### 项目脚本 ```bash npm run dev # 启动开发服务器 npm run build # 构建生产版本 npm run preview # 预览生产构建 npm run type-check # TypeScript 类型检查 npm run lint # ESLint 代码检查 npm run lint:fix # 自动修复 ESLint 问题 ``` ## 开发环境配置 ### VS Code 推荐扩展 创建 `.vscode/extensions.json`: ```json { "recommendations": [ "Vue.volar", // Vue 3 语言支持 "Vue.vscode-typescript-vue-plugin", // Vue TypeScript 支持 "bradlc.vscode-tailwindcss", // Tailwind CSS 支持 "esbenp.prettier-vscode", // 代码格式化 "dbaeumer.vscode-eslint", // ESLint 集成 "ms-vscode.vscode-typescript-next", // TypeScript 支持 "formulahendry.auto-rename-tag", // 自动重命名标签 "christian-kohler.path-intellisense" // 路径智能提示 ] } ``` ### VS Code 工作区设置 创建 `.vscode/settings.json`: ```json { "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "[vue]": { "editor.defaultFormatter": "Vue.volar" }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "typescript.preferences.importModuleSpecifier": "relative", "volar.completion.autoImportComponent": true } ``` ## 项目结构详解 ### 目录组织原则 ``` src/ ├── components/ # 可复用组件 │ ├── common/ # 通用组件 │ ├── forms/ # 表单组件 │ └── display/ # 展示组件 ├── views/ # 页面组件 ├── stores/ # Pinia 状态管理 ├── utils/ # 工具函数 ├── types/ # TypeScript 类型定义 ├── assets/ # 静态资源 ├── styles/ # 全局样式 └── router/ # 路由配置 ``` ### 命名规范 - **组件文件**: PascalCase (如 `ApiPreview.vue`) - **工具文件**: camelCase (如 `apiUtils.ts`) - **类型文件**: PascalCase 接口 + camelCase 文件 (如 `types/index.ts`) - **样式类**: kebab-case (如 `.api-preview`) ## 核心开发模式 ### 1. 组件开发模式 #### 单文件组件结构 ```vue <template> <!-- 模板,使用 Element Plus 组件 --> </template> <script setup lang="ts"> // Composition API,TypeScript 优先 import { ref, computed, watch } from 'vue' import type { ApiEndpoint } from '@/types' // Props 定义 interface Props { endpoints: ApiEndpoint[] loading?: boolean } const props = withDefaults(defineProps<Props>(), { loading: false }) // Emits 定义 interface Emits { update: [endpoints: ApiEndpoint[]] error: [message: string] } const emit = defineEmits<Emits>() // 响应式状态 const selectedEndpoint = ref<ApiEndpoint | null>(null) // 计算属性 const filteredEndpoints = computed(() => { return props.endpoints.filter(ep => !ep.deprecated) }) // 方法 const handleSelect = (endpoint: ApiEndpoint) => { selectedEndpoint.value = endpoint emit('update', [endpoint]) } </script> <style scoped> /* 组件作用域样式 */ .api-endpoint { @apply p-4 rounded-lg border border-gray-200; } </style> ``` #### 组件测试 ```typescript // components/__tests__/ApiPreview.test.ts import { mount } from '@vue/test-utils' import ApiPreview from '../ApiPreview.vue' import type { ApiEndpoint } from '@/types' describe('ApiPreview', () => { const mockEndpoints: ApiEndpoint[] = [ { method: 'GET', path: '/users', summary: 'Get users' } ] it('renders endpoints correctly', () => { const wrapper = mount(ApiPreview, { props: { endpoints: mockEndpoints } }) expect(wrapper.text()).toContain('Get users') }) }) ``` ### 2. 状态管理模式 #### Store 结构 ```typescript // stores/feature.ts import { defineStore } from 'pinia' export const useFeatureStore = defineStore('feature', { state: () => ({ data: [] as FeatureItem[], loading: false, error: null as string | null }), getters: { itemCount: (state) => state.data.length, hasError: (state) => !!state.error }, actions: { async fetchData() { this.loading = true this.error = null try { const response = await api.getData() this.data = response.data } catch (error) { this.error = error.message } finally { this.loading = false } }, updateItem(id: string, updates: Partial<FeatureItem>) { const index = this.data.findIndex(item => item.id === id) if (index !== -1) { this.data[index] = { ...this.data[index], ...updates } } } } }) ``` #### 在组件中使用 Store ```vue <script setup lang="ts"> import { storeToRefs } from 'pinia' import { useFeatureStore } from '@/stores/feature' const featureStore = useFeatureStore() const { data, loading, error } = storeToRefs(featureStore) // 响应式地使用 store 数据 const handleRefresh = () => { featureStore.fetchData() } </script> ``` ### 3. API 服务模式 #### API 服务结构 ```typescript // services/api.ts import axios from 'axios' import type { ApiResponse, PaginatedResponse } from '@/types' class ApiService { private readonly baseURL: string private readonly timeout: number constructor(baseURL: string, timeout = 10000) { this.baseURL = baseURL this.timeout = timeout } // 通用请求方法 private async request<T>( method: 'GET' | 'POST' | 'PUT' | 'DELETE', url: string, data?: any ): Promise<ApiResponse<T>> { try { const response = await axios({ method, url: `${this.baseURL}${url}`, data, timeout: this.timeout, headers: { 'Content-Type': 'application/json' } }) return { success: true, data: response.data } } catch (error) { return { success: false, error: error.message } } } // 具体 API 方法 async validateOpenApi(spec: string): Promise<ApiResponse<ValidationResult>> { return this.request('POST', '/validate', { spec }) } async convertToMcp(config: ConvertConfig): Promise<ApiResponse<McpResult>> { return this.request('POST', '/convert', config) } } export const apiService = new ApiService( import.meta.env.VITE_API_BASE_URL || '/api' ) ``` ### 4. 类型定义模式 #### 类型组织 ```typescript // types/api.ts - API 相关类型 export interface ApiResponse<T = any> { success: boolean data?: T error?: string message?: string } export interface PaginatedResponse<T> extends ApiResponse<T[]> { pagination: { page: number pageSize: number total: number totalPages: number } } // types/domain.ts - 业务领域类型 export interface User { id: string name: string email: string createdAt: Date updatedAt: Date } export interface CreateUserRequest { name: string email: string } // types/ui.ts - UI 相关类型 export interface TabItem { key: string label: string icon?: string disabled?: boolean } export interface FormState { values: Record<string, any> errors: Record<string, string> touched: Record<string, boolean> } ``` ## 样式开发指南 ### Apple 风格设计系统 #### 颜色系统 ```scss // styles/variables.scss :root { // 主色调 --color-primary: #667eea; --color-primary-dark: #5a67d8; --color-primary-light: #7c3aed; // 功能色 --color-success: #10b981; --color-warning: #f59e0b; --color-error: #ef4444; --color-info: #3b82f6; // 中性色 --color-gray-50: #f9fafb; --color-gray-100: #f3f4f6; --color-gray-200: #e5e7eb; --color-gray-300: #d1d5db; --color-gray-400: #9ca3af; --color-gray-500: #6b7280; --color-gray-600: #4b5563; --color-gray-700: #374151; --color-gray-800: #1f2937; --color-gray-900: #111827; // 渐变 --gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%); --gradient-surface: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%); } ``` #### 组件样式模式 ```scss // styles/components.scss .card { background: var(--gradient-surface); border-radius: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); border: 1px solid var(--color-gray-200); transition: all 0.3s ease; &:hover { box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); transform: translateY(-2px); } &__header { padding: 20px 24px; border-bottom: 1px solid var(--color-gray-200); font-weight: 600; color: var(--color-gray-800); } &__body { padding: 24px; } } .button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 12px 24px; border-radius: 8px; font-weight: 500; transition: all 0.2s ease; cursor: pointer; border: none; &--primary { background: var(--gradient-primary); color: white; &:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); } } &--secondary { background: white; color: var(--color-gray-700); border: 1px solid var(--color-gray-300); &:hover { background: var(--color-gray-50); border-color: var(--color-gray-400); } } } ``` #### 响应式设计 ```scss // styles/responsive.scss .container { max-width: 1200px; margin: 0 auto; padding: 0 20px; @media (max-width: 768px) { padding: 0 16px; } } .grid { display: grid; gap: 24px; &--auto-fit { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); } &--responsive { grid-template-columns: repeat(3, 1fr); @media (max-width: 1024px) { grid-template-columns: repeat(2, 1fr); } @media (max-width: 768px) { grid-template-columns: 1fr; } } } ``` ## 调试和测试 ### 开发调试 #### Vue DevTools ```javascript // 在开发环境中启用 Vue DevTools if (import.meta.env.DEV) { const { createApp } = await import('vue') const { createPinia } = await import('pinia') const app = createApp(App) app.use(createPinia()) // 启用 Vue DevTools app.config.devtools = true } ``` #### 控制台调试 ```typescript // utils/debug.ts export const debug = { log: (message: string, data?: any) => { if (import.meta.env.DEV) { console.log(`[DEBUG] ${message}`, data) } }, error: (message: string, error?: any) => { if (import.meta.env.DEV) { console.error(`[ERROR] ${message}`, error) } }, table: (data: any) => { if (import.meta.env.DEV) { console.table(data) } } } // 在组件中使用 import { debug } from '@/utils/debug' const handleApiCall = async () => { debug.log('Starting API call', { endpoint: '/api/convert' }) try { const result = await apiService.convert(config) debug.log('API call successful', result) } catch (error) { debug.error('API call failed', error) } } ``` ### 单元测试 #### 测试配置 ```typescript // vitest.config.ts import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' export default defineConfig({ plugins: [vue()], test: { environment: 'jsdom', setupFiles: ['./src/test/setup.ts'], globals: true }, resolve: { alias: { '@': resolve(__dirname, 'src') } } }) ``` #### 测试示例 ```typescript // src/components/__tests__/ApiPreview.test.ts import { describe, it, expect, vi } from 'vitest' import { mount } from '@vue/test-utils' import { createPinia, setActivePinia } from 'pinia' import ApiPreview from '../ApiPreview.vue' describe('ApiPreview', () => { beforeEach(() => { setActivePinia(createPinia()) }) it('displays API information correctly', () => { const props = { apiInfo: { title: 'Test API', version: '1.0.0', description: 'Test description' } } const wrapper = mount(ApiPreview, { props }) expect(wrapper.find('[data-testid="api-title"]').text()).toBe('Test API') expect(wrapper.find('[data-testid="api-version"]').text()).toBe('1.0.0') }) it('emits events correctly', async () => { const wrapper = mount(ApiPreview) await wrapper.find('[data-testid="refresh-button"]').trigger('click') expect(wrapper.emitted('refresh')).toHaveLength(1) }) }) ``` ## 性能优化 ### 代码分割 ```typescript // router/index.ts import { createRouter, createWebHistory } from 'vue-router' const routes = [ { path: '/', name: 'Home', component: () => import('@/views/Home.vue'), // 懒加载 }, { path: '/settings', name: 'Settings', component: () => import('@/views/Settings.vue'), } ] ``` ### 组件懒加载 ```vue <!-- 在组件中使用 Suspense --> <template> <Suspense> <template #default> <AsyncComponent /> </template> <template #fallback> <div class="loading">Loading...</div> </template> </Suspense> </template> <script setup lang="ts"> import { defineAsyncComponent } from 'vue' const AsyncComponent = defineAsyncComponent( () => import('@/components/HeavyComponent.vue') ) </script> ``` ### 虚拟滚动 ```vue <!-- 对于大列表使用虚拟滚动 --> <template> <el-virtual-list :data="largeDataset" :height="400" :item-size="50" > <template #default="{ item }"> <div class="list-item">{{ item.name }}</div> </template> </el-virtual-list> </template> ``` ## 部署指南 ### 环境变量配置 ```bash # .env.production VITE_APP_TITLE=MCP Swagger Server VITE_API_BASE_URL=https://api.example.com VITE_ENABLE_DEMO_MODE=false ``` ### Docker 部署 ```dockerfile # Dockerfile FROM node:18-alpine as builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` ### 静态部署 ```bash # 构建生产版本 npm run build # 部署到静态文件服务器 # dist/ 目录包含所有需要的文件 ``` ## 常见问题和解决方案 ### 1. TypeScript 类型错误 ```typescript // 使用类型断言 const element = document.getElementById('app') as HTMLElement // 使用可选链 const value = response.data?.user?.name || 'Unknown' // 定义完整的类型 interface ApiResponse<T> { success: boolean data?: T error?: string } ``` ### 2. Element Plus 样式问题 ```vue <!-- 使用 :deep() 修改第三方组件样式 --> <style scoped> :deep(.el-button) { border-radius: 8px; } :deep(.el-input__inner) { border-color: var(--color-primary); } </style> ``` ### 3. 路由导航问题 ```typescript // 使用 router.push 进行编程式导航 import { useRouter } from 'vue-router' const router = useRouter() const navigateToHome = () => { router.push({ name: 'Home' }) } ``` ### 4. 状态持久化 ```typescript // 使用 localStorage 持久化状态 import { defineStore } from 'pinia' export const useAppStore = defineStore('app', { state: () => ({ userPreferences: {} }), actions: { loadFromStorage() { const saved = localStorage.getItem('app-state') if (saved) { this.$patch(JSON.parse(saved)) } }, saveToStorage() { localStorage.setItem('app-state', JSON.stringify(this.$state)) } } }) ``` 这个开发指南提供了完整的开发流程、最佳实践和常见问题解决方案,帮助开发者快速上手并高效地开发 MCP Swagger UI 项目。

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zaizaizhao/mcp-swagger-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server