Files
users/app/data-market/api/docs/page.tsx

889 lines
34 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
Copy,
CheckCircle2,
Search,
Tags,
Brain,
Database,
FileText,
Package,
ArrowLeft,
Code,
Zap,
Key,
DollarSign,
ChevronDown,
ChevronRight,
Play,
Terminal,
BookOpen,
Server,
Download,
ExternalLink,
Link2,
Share2,
} from "lucide-react"
import Link from "next/link"
// API端点接口
interface APIEndpoint {
id: string
method: 'GET' | 'POST' | 'PUT' | 'DELETE'
path: string
name: string
description: string
category: string
auth: boolean
price: number
params?: { name: string; type: string; required: boolean; desc: string }[]
response?: string
example?: string
}
// API分类
const API_CATEGORIES = [
{ id: 'query', name: '用户查询', icon: Search, description: '查询用户画像和基本信息' },
{ id: 'tag', name: '标签服务', icon: Tags, description: '获取和应用用户标签' },
{ id: 'ai', name: 'AI服务', icon: Brain, description: 'AI对话、分析和智能打标' },
{ id: 'data', name: '数据服务', icon: Database, description: '数据源管理和数据导入' },
{ id: 'report', name: '报告服务', icon: FileText, description: '生成数据分析报告' },
{ id: 'package', name: '流量包', icon: Package, description: '创建和导出流量包' },
]
// 预定义API端点
const API_ENDPOINTS: APIEndpoint[] = [
// 用户查询
{
id: 'api_1',
method: 'GET',
path: '/api/shensheshou/user',
name: '用户画像查询',
description: '根据手机号或QQ查询完整用户画像返回字段根据API密钥权限决定',
category: 'query',
auth: true,
price: 1,
params: [
{ name: 'phone', type: 'string', required: false, desc: '11位手机号' },
{ name: 'qq', type: 'string', required: false, desc: 'QQ号码' },
{ name: 'fields', type: 'string', required: false, desc: '指定返回字段,逗号分隔' },
],
response: `{
"success": true,
"data": {
"phone": "138****8000",
"rfm_score": 85,
"user_level": "A",
"tags": ["高价值", "活跃用户"],
"last_active": "2026-01-30"
},
"credits_used": 5,
"credits_remaining": 995
}`,
example: `curl -X GET "https://api.shensheshou.com/api/shensheshou?endpoint=user&phone=13800138000&fields=rfm_score,tags" \\
-H "Authorization: Bearer sk-archer-xxxxx" \\
-H "X-API-Secret: sec-xxxxx"`,
},
{
id: 'api_2',
method: 'POST',
path: '/api/shensheshou/users/batch',
name: '批量用户查询',
description: '批量查询多个用户的画像信息最多支持100个用户/次,享受批量折扣',
category: 'query',
auth: true,
price: 0.8,
params: [
{ name: 'endpoint', type: 'string', required: true, desc: '固定值: users/batch' },
{ name: 'phones', type: 'array', required: false, desc: '手机号数组最多100个' },
{ name: 'qqs', type: 'array', required: false, desc: 'QQ号数组最多100个' },
{ name: 'fields', type: 'array', required: false, desc: '指定返回字段数组' },
],
response: `{
"success": true,
"data": [
{ "phone": "138****8000", "rfm_score": 85, "user_level": "A" },
{ "phone": "139****9000", "rfm_score": 72, "user_level": "B" }
],
"total": 2,
"credits_used": 8,
"credits_remaining": 992
}`,
example: `curl -X POST "https://api.shensheshou.com/api/shensheshou" \\
-H "Authorization: Bearer sk-archer-xxxxx" \\
-H "X-API-Secret: sec-xxxxx" \\
-H "Content-Type: application/json" \\
-d '{
"endpoint": "users/batch",
"phones": ["13800138000", "13900139000"],
"fields": ["rfm_score", "user_level", "tags"]
}'`,
},
// 标签服务
{
id: 'api_3',
method: 'GET',
path: '/api/shensheshou/tags',
name: '标签列表',
description: '获取系统中所有可用标签及其分类和使用统计',
category: 'tag',
auth: true,
price: 0.5,
params: [
{ name: 'category', type: 'string', required: false, desc: '标签分类筛选' },
],
response: `{
"success": true,
"data": [
{ "id": "tag_1", "name": "高价值", "category": "价值标签", "count": 12500 },
{ "id": "tag_2", "name": "活跃用户", "category": "行为标签", "count": 35800 }
],
"total": 45,
"credits_used": 0.5
}`,
example: `curl -X GET "https://api.shensheshou.com/api/shensheshou?endpoint=tags&category=价值标签" \\
-H "Authorization: Bearer sk-archer-xxxxx" \\
-H "X-API-Secret: sec-xxxxx"`,
},
{
id: 'api_4',
method: 'POST',
path: '/api/shensheshou/tags/apply',
name: '应用标签',
description: '为指定用户批量应用标签',
category: 'tag',
auth: true,
price: 2,
params: [
{ name: 'endpoint', type: 'string', required: true, desc: '固定值: tags/apply' },
{ name: 'user_id', type: 'string', required: true, desc: '用户ID或手机号' },
{ name: 'tags', type: 'array', required: true, desc: '要应用的标签ID数组' },
{ name: 'overwrite', type: 'boolean', required: false, desc: '是否覆盖现有标签默认false' },
],
response: `{
"success": true,
"data": {
"user_id": "13800138000",
"applied_tags": ["tag_1", "tag_2"],
"timestamp": "2026-01-31T14:30:00Z"
},
"credits_used": 2
}`,
example: `curl -X POST "https://api.shensheshou.com/api/shensheshou" \\
-H "Authorization: Bearer sk-archer-xxxxx" \\
-H "X-API-Secret: sec-xxxxx" \\
-H "Content-Type: application/json" \\
-d '{
"endpoint": "tags/apply",
"user_id": "13800138000",
"tags": ["tag_1", "tag_2"],
"overwrite": false
}'`,
},
// AI服务
{
id: 'api_5',
method: 'POST',
path: '/api/shensheshou/ai/chat',
name: 'AI智能对话',
description: '与神射手AI进行对话支持自然语言查询和数据分析',
category: 'ai',
auth: true,
price: 5,
params: [
{ name: 'endpoint', type: 'string', required: true, desc: '固定值: ai/chat' },
{ name: 'message', type: 'string', required: true, desc: '对话内容' },
{ name: 'context', type: 'object', required: false, desc: '上下文信息' },
{ name: 'model', type: 'string', required: false, desc: 'AI模型: qwen/deepseek' },
],
response: `{
"success": true,
"response": {
"content": "根据查询,共有 1,234 位高价值用户...",
"data": { "count": 1234, "avg_rfm": 82 },
"suggestions": ["可以进一步筛选活跃度", "建议导出为流量包"]
},
"credits_used": 5
}`,
example: `curl -X POST "https://api.shensheshou.com/api/shensheshou" \\
-H "Authorization: Bearer sk-archer-xxxxx" \\
-H "X-API-Secret: sec-xxxxx" \\
-H "Content-Type: application/json" \\
-d '{
"endpoint": "ai/chat",
"message": "帮我分析RFM评分大于80的用户群体",
"model": "qwen"
}'`,
},
{
id: 'api_6',
method: 'POST',
path: '/api/shensheshou/ai/analyze',
name: 'AI数据分析',
description: 'AI自动分析用户群体特征并生成洞察报告',
category: 'ai',
auth: true,
price: 10,
params: [
{ name: 'endpoint', type: 'string', required: true, desc: '固定值: ai/analyze' },
{ name: 'type', type: 'string', required: true, desc: '分析类型: rfm/behavior/preference/churn' },
{ name: 'filters', type: 'object', required: false, desc: '用户筛选条件' },
{ name: 'depth', type: 'string', required: false, desc: '分析深度: quick/standard/deep' },
],
response: `{
"success": true,
"data": {
"type": "rfm",
"summary": "RFM分析完成",
"insights": [
{ "metric": "RFM平均分", "value": 72, "trend": "+5%" },
{ "metric": "高价值用户", "value": 12500, "trend": "+8%" }
],
"recommendations": [
"建议对RFM分数65-75区间的用户进行激活营销"
]
},
"credits_used": 10
}`,
example: `curl -X POST "https://api.shensheshou.com/api/shensheshou" \\
-H "Authorization: Bearer sk-archer-xxxxx" \\
-H "X-API-Secret: sec-xxxxx" \\
-H "Content-Type: application/json" \\
-d '{
"endpoint": "ai/analyze",
"type": "rfm",
"filters": { "user_level": ["A", "B"] },
"depth": "standard"
}'`,
},
{
id: 'api_7',
method: 'POST',
path: '/api/shensheshou/ai/tag',
name: 'AI智能打标',
description: 'AI自动分析用户数据并智能打标签',
category: 'ai',
auth: true,
price: 8,
params: [
{ name: 'endpoint', type: 'string', required: true, desc: '固定值: ai/tag' },
{ name: 'user_ids', type: 'array', required: true, desc: '用户ID数组' },
{ name: 'tag_types', type: 'array', required: false, desc: '指定标签类型' },
{ name: 'model', type: 'string', required: false, desc: 'AI模型选择' },
],
response: `{
"success": true,
"data": {
"processed": 50,
"tagsApplied": 150,
"summary": "已为50个用户完成AI智能打标"
},
"credits_used": 25
}`,
},
// 数据服务
{
id: 'api_8',
method: 'GET',
path: '/api/shensheshou/sources',
name: '数据源列表',
description: '获取所有已接入的数据源及其连接状态和数据量',
category: 'data',
auth: true,
price: 0.5,
response: `{
"success": true,
"data": [
{ "id": "KR_腾讯", "name": "腾讯数据", "status": "connected", "records": 700000000 },
{ "id": "KR_微博", "name": "微博数据", "status": "connected", "records": 140000000 }
],
"credits_used": 0.5
}`,
example: `curl -X GET "https://api.shensheshou.com/api/shensheshou?endpoint=sources" \\
-H "Authorization: Bearer sk-archer-xxxxx" \\
-H "X-API-Secret: sec-xxxxx"`,
},
{
id: 'api_9',
method: 'POST',
path: '/api/shensheshou/ingest',
name: '数据导入',
description: '导入外部数据到神射手平台自动触发AI标签引擎处理',
category: 'data',
auth: true,
price: 3,
params: [
{ name: 'endpoint', type: 'string', required: true, desc: '固定值: ingest' },
{ name: 'source', type: 'string', required: true, desc: '数据源标识,如: cunkebao' },
{ name: 'data', type: 'array', required: true, desc: '用户数据数组' },
{ name: 'auto_tag', type: 'boolean', required: false, desc: '是否自动打标默认true' },
],
response: `{
"success": true,
"data": {
"source": "cunkebao",
"imported": 100,
"auto_tagged": 100,
"task_id": "task_1706698200000"
},
"credits_used": 40
}`,
example: `curl -X POST "https://api.shensheshou.com/api/shensheshou" \\
-H "Authorization: Bearer sk-archer-xxxxx" \\
-H "X-API-Secret: sec-xxxxx" \\
-H "Content-Type: application/json" \\
-d '{
"endpoint": "ingest",
"source": "cunkebao",
"data": [
{ "phone": "13800138000", "name": "张三", "source": "微信" },
{ "phone": "13900139000", "name": "李四", "source": "抖音" }
],
"auto_tag": true
}'`,
},
// 报告服务
{
id: 'api_10',
method: 'POST',
path: '/api/shensheshou/report/generate',
name: '生成分析报告',
description: 'AI自动生成数据分析报告支持多种模板和格式',
category: 'report',
auth: true,
price: 15,
params: [
{ name: 'endpoint', type: 'string', required: true, desc: '固定值: report/generate' },
{ name: 'template', type: 'string', required: true, desc: '报告模板: user_insight/rfm_analysis/trend_report' },
{ name: 'date_range', type: 'object', required: false, desc: '日期范围 {start, end}' },
{ name: 'format', type: 'string', required: false, desc: '输出格式: pdf/html/json' },
],
response: `{
"success": true,
"data": {
"template": "rfm_analysis",
"report_id": "report_1706698200000",
"status": "generating",
"estimatedTime": "5分钟",
"downloadUrl": null
},
"credits_used": 15
}`,
},
// 流量包
{
id: 'api_11',
method: 'GET',
path: '/api/shensheshou/packages',
name: '流量包列表',
description: '获取所有已创建的流量包',
category: 'package',
auth: true,
price: 0.5,
response: `{
"success": true,
"data": [
{ "id": "pkg_1", "name": "高价值用户包", "count": 12500, "createdAt": "2026-01-28" },
{ "id": "pkg_2", "name": "活跃用户包", "count": 35800, "createdAt": "2026-01-25" }
],
"credits_used": 0.5
}`,
example: `curl -X GET "https://api.shensheshou.com/api/shensheshou?endpoint=packages" \\
-H "Authorization: Bearer sk-archer-xxxxx" \\
-H "X-API-Secret: sec-xxxxx"`,
},
{
id: 'api_12',
method: 'POST',
path: '/api/shensheshou/packages/create',
name: '创建流量包',
description: '根据筛选条件创建用户流量包',
category: 'package',
auth: true,
price: 5,
params: [
{ name: 'endpoint', type: 'string', required: true, desc: '固定值: packages/create' },
{ name: 'name', type: 'string', required: true, desc: '流量包名称' },
{ name: 'criteria', type: 'object', required: true, desc: '筛选条件' },
{ name: 'export_fields', type: 'array', required: false, desc: '导出字段' },
],
response: `{
"success": true,
"data": {
"id": "pkg_1706698200000",
"name": "高价值活跃用户",
"criteria": { "rfm_score": { "$gte": 80 } },
"count": 8500,
"status": "created",
"createdAt": "2026-01-31T14:30:00Z"
},
"credits_used": 5
}`,
example: `curl -X POST "https://api.shensheshou.com/api/shensheshou" \\
-H "Authorization: Bearer sk-archer-xxxxx" \\
-H "X-API-Secret: sec-xxxxx" \\
-H "Content-Type: application/json" \\
-d '{
"endpoint": "packages/create",
"name": "高价值活跃用户",
"criteria": {
"rfm_score": { "$gte": 80 },
"user_level": ["A", "B"]
},
"export_fields": ["phone", "rfm_score", "tags"]
}'`,
},
{
id: 'api_13',
method: 'POST',
path: '/api/shensheshou/packages/export',
name: '导出流量包',
description: '导出流量包数据到指定目标邮箱、飞书、Webhook',
category: 'package',
auth: true,
price: 10,
params: [
{ name: 'endpoint', type: 'string', required: true, desc: '固定值: packages/export' },
{ name: 'package_id', type: 'string', required: true, desc: '流量包ID' },
{ name: 'target', type: 'string', required: true, desc: '导出目标: email/feishu/webhook' },
{ name: 'config', type: 'object', required: false, desc: '导出配置邮箱地址、Webhook URL等' },
],
response: `{
"success": true,
"data": {
"package_id": "pkg_1706698200000",
"target": "email",
"status": "exporting",
"task_id": "export_1706698200000",
"estimatedTime": "2分钟"
},
"credits_used": 10
}`,
},
]
export default function APIDocsPage() {
const [activeCategory, setActiveCategory] = useState('all')
const [apiBaseUrl, setApiBaseUrl] = useState('')
const [copiedId, setCopiedId] = useState<string | null>(null)
const [searchQuery, setSearchQuery] = useState('')
const [expandedEndpoints, setExpandedEndpoints] = useState<Set<string>>(new Set())
useEffect(() => {
const host = typeof window !== 'undefined' ? window.location.origin : ''
setApiBaseUrl(host)
}, [])
const copyToClipboard = (text: string, id: string) => {
navigator.clipboard.writeText(text)
setCopiedId(id)
setTimeout(() => setCopiedId(null), 2000)
}
const getMethodColor = (method: string) => {
switch (method) {
case 'GET': return 'bg-green-100 text-green-700 border-green-200'
case 'POST': return 'bg-blue-100 text-blue-700 border-blue-200'
case 'PUT': return 'bg-yellow-100 text-yellow-700 border-yellow-200'
case 'DELETE': return 'bg-red-100 text-red-700 border-red-200'
default: return 'bg-gray-100 text-gray-700 border-gray-200'
}
}
const toggleEndpoint = (id: string) => {
const newSet = new Set(expandedEndpoints)
if (newSet.has(id)) {
newSet.delete(id)
} else {
newSet.add(id)
}
setExpandedEndpoints(newSet)
}
const filteredEndpoints = API_ENDPOINTS.filter(e => {
const matchCategory = activeCategory === 'all' || e.category === activeCategory
const matchSearch = !searchQuery ||
e.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
e.path.toLowerCase().includes(searchQuery.toLowerCase()) ||
e.description.toLowerCase().includes(searchQuery.toLowerCase())
return matchCategory && matchSearch
})
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50/30 to-purple-50/20">
<div className="p-6 space-y-6">
{/* 顶部标题 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Link href="/data-market/api">
<Button variant="ghost" size="sm">
<ArrowLeft className="h-4 w-4 mr-2" />
</Button>
</Link>
<div>
<h1 className="text-2xl font-bold text-gray-900">API文档</h1>
<p className="text-sm text-gray-500 mt-1">API完整文档 · AI直接对接</p>
</div>
</div>
<div className="flex items-center gap-2">
<Badge>v1.0</Badge>
<Button
variant="outline"
size="sm"
onClick={() => copyToClipboard(`${apiBaseUrl}/api/docs?format=openapi`, 'api-link')}
>
{copiedId === 'api-link' ? (
<CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
) : (
<Link2 className="h-4 w-4 mr-2" />
)}
API链接
</Button>
<Button
variant="outline"
size="sm"
onClick={() => window.open(`${apiBaseUrl}/api/docs?format=download`, '_blank')}
>
<Download className="h-4 w-4 mr-2" />
OpenAPI
</Button>
<Button
variant="outline"
size="sm"
onClick={() => window.open(`${apiBaseUrl}/api/docs?format=markdown`, '_blank')}
>
<FileText className="h-4 w-4 mr-2" />
Markdown
</Button>
</div>
</div>
{/* AI对接提示卡片 */}
<Card className="border-0 shadow-sm bg-gradient-to-r from-cyan-50 to-blue-50 border-l-4 border-l-cyan-500">
<CardContent className="p-4">
<div className="flex items-start justify-between">
<div className="flex items-start gap-3">
<div className="p-2 rounded-lg bg-cyan-100">
<Brain className="h-5 w-5 text-cyan-600" />
</div>
<div>
<h3 className="font-semibold text-gray-900 mb-1">AI直接对接</h3>
<p className="text-sm text-gray-600 mb-2">AIChatGPTClaudeAPI</p>
<div className="flex items-center gap-2 p-2 rounded-lg bg-white/80 font-mono text-sm">
<code className="text-cyan-700 flex-1 truncate">{apiBaseUrl}/api/docs?format=openapi</code>
<Button
variant="ghost"
size="sm"
className="h-7 shrink-0"
onClick={() => copyToClipboard(`${apiBaseUrl}/api/docs?format=openapi`, 'openapi-link')}
>
{copiedId === 'openapi-link' ? (
<CheckCircle2 className="h-4 w-4 text-green-500" />
) : (
<Copy className="h-4 w-4" />
)}
</Button>
</div>
</div>
</div>
<div className="flex flex-col gap-2 text-xs">
<div className="flex items-center gap-2 p-2 rounded-lg bg-white/80">
<Badge variant="outline" className="text-xs">OpenAPI 3.0</Badge>
<span className="text-gray-500"></span>
</div>
<div className="flex items-center gap-2 p-2 rounded-lg bg-white/80">
<Badge variant="outline" className="text-xs">Markdown</Badge>
<span className="text-gray-500"></span>
</div>
</div>
</div>
</CardContent>
</Card>
{/* 快速入门 */}
<Card className="border-0 shadow-sm bg-gradient-to-r from-purple-50 to-blue-50">
<CardContent className="p-6">
<div className="flex items-start gap-4">
<div className="p-3 rounded-lg bg-purple-100">
<BookOpen className="h-6 w-6 text-purple-600" />
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold text-gray-900 mb-2"></h3>
<div className="grid grid-cols-3 gap-4 mb-4">
<div className="p-3 rounded-lg bg-white/80">
<div className="flex items-center gap-2 mb-1">
<span className="w-5 h-5 rounded-full bg-purple-500 text-white text-xs flex items-center justify-center">1</span>
<span className="font-medium text-sm"></span>
</div>
<p className="text-xs text-gray-500">API密钥</p>
</div>
<div className="p-3 rounded-lg bg-white/80">
<div className="flex items-center gap-2 mb-1">
<span className="w-5 h-5 rounded-full bg-purple-500 text-white text-xs flex items-center justify-center">2</span>
<span className="font-medium text-sm"></span>
</div>
<p className="text-xs text-gray-500">访</p>
</div>
<div className="p-3 rounded-lg bg-white/80">
<div className="flex items-center gap-2 mb-1">
<span className="w-5 h-5 rounded-full bg-purple-500 text-white text-xs flex items-center justify-center">3</span>
<span className="font-medium text-sm"></span>
</div>
<p className="text-xs text-gray-500">使API接口</p>
</div>
</div>
<div className="p-4 rounded-lg bg-gray-900">
<p className="text-xs text-gray-400 mb-2"></p>
<pre className="text-green-400 text-sm overflow-x-auto">
{`curl -X GET "${apiBaseUrl}/api/shensheshou?endpoint=user&phone=13800138000" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-H "X-API-Secret: YOUR_API_SECRET"`}
</pre>
</div>
</div>
</div>
</CardContent>
</Card>
{/* 搜索和分类 */}
<div className="flex items-center gap-4">
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
placeholder="搜索API..."
className="pl-10"
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
/>
</div>
<div className="flex items-center gap-2 overflow-x-auto">
<Button
variant={activeCategory === 'all' ? 'default' : 'outline'}
size="sm"
onClick={() => setActiveCategory('all')}
>
({API_ENDPOINTS.length})
</Button>
{API_CATEGORIES.map(cat => {
const count = API_ENDPOINTS.filter(e => e.category === cat.id).length
const Icon = cat.icon
return (
<Button
key={cat.id}
variant={activeCategory === cat.id ? 'default' : 'outline'}
size="sm"
onClick={() => setActiveCategory(cat.id)}
>
<Icon className="h-4 w-4 mr-1" />
{cat.name} ({count})
</Button>
)
})}
</div>
</div>
{/* API端点列表 */}
<div className="space-y-4">
{filteredEndpoints.map(endpoint => (
<Card key={endpoint.id} className="border-0 shadow-sm bg-white/80 overflow-hidden">
<div
className="p-4 cursor-pointer hover:bg-gray-50 transition-colors"
onClick={() => toggleEndpoint(endpoint.id)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Badge className={`${getMethodColor(endpoint.method)} font-mono border`}>
{endpoint.method}
</Badge>
<code className="text-sm font-mono text-gray-700">{endpoint.path}</code>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0"
onClick={(e) => {
e.stopPropagation()
copyToClipboard(`${apiBaseUrl}${endpoint.path}`, endpoint.id)
}}
>
{copiedId === endpoint.id ? (
<CheckCircle2 className="h-3 w-3 text-green-500" />
) : (
<Copy className="h-3 w-3" />
)}
</Button>
</div>
<div className="flex items-center gap-3">
<Badge variant="outline" className="text-xs">
<DollarSign className="h-3 w-3 mr-1" />
{endpoint.price}
</Badge>
{endpoint.auth && (
<Badge variant="outline" className="text-xs">
<Key className="h-3 w-3 mr-1" />
</Badge>
)}
{expandedEndpoints.has(endpoint.id) ? (
<ChevronDown className="h-4 w-4 text-gray-400" />
) : (
<ChevronRight className="h-4 w-4 text-gray-400" />
)}
</div>
</div>
<div className="mt-2">
<h3 className="font-semibold text-gray-900">{endpoint.name}</h3>
<p className="text-sm text-gray-500">{endpoint.description}</p>
</div>
</div>
{/* 展开详情 */}
{expandedEndpoints.has(endpoint.id) && (
<div className="border-t p-4 bg-gray-50 space-y-4">
{/* 参数 */}
{endpoint.params && endpoint.params.length > 0 && (
<div>
<h4 className="text-sm font-medium text-gray-700 mb-2"></h4>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="text-left text-gray-500 border-b">
<th className="pb-2 pr-4"></th>
<th className="pb-2 pr-4"></th>
<th className="pb-2 pr-4"></th>
<th className="pb-2"></th>
</tr>
</thead>
<tbody>
{endpoint.params.map((param, i) => (
<tr key={i} className="border-b last:border-0">
<td className="py-2 pr-4">
<code className="bg-gray-100 px-2 py-0.5 rounded text-purple-600">{param.name}</code>
</td>
<td className="py-2 pr-4">
<Badge variant="outline" className="text-xs">{param.type}</Badge>
</td>
<td className="py-2 pr-4">
{param.required ? (
<Badge className="bg-red-100 text-red-700 text-xs"></Badge>
) : (
<span className="text-gray-400"></span>
)}
</td>
<td className="py-2 text-gray-600">{param.desc}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{/* 调用示例 */}
{endpoint.example && (
<div>
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium text-gray-700"></h4>
<Button
variant="ghost"
size="sm"
onClick={() => copyToClipboard(endpoint.example!, endpoint.id + '_example')}
>
{copiedId === endpoint.id + '_example' ? (
<CheckCircle2 className="h-4 w-4 text-green-500" />
) : (
<Copy className="h-4 w-4" />
)}
<span className="ml-1"></span>
</Button>
</div>
<pre className="bg-gray-900 text-green-400 p-4 rounded-lg text-sm overflow-x-auto">
{endpoint.example}
</pre>
</div>
)}
{/* 响应示例 */}
{endpoint.response && (
<div>
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium text-gray-700"></h4>
<Badge className="bg-green-100 text-green-700">200 OK</Badge>
</div>
<pre className="bg-gray-100 p-4 rounded-lg text-sm font-mono text-gray-700 overflow-x-auto">
{endpoint.response}
</pre>
</div>
)}
</div>
)}
</Card>
))}
</div>
{/* 错误码说明 */}
<Card className="border-0 shadow-sm bg-white/80">
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent>
<table className="w-full text-sm">
<thead>
<tr className="text-left text-gray-500 border-b">
<th className="pb-2 pr-4">HTTP状态码</th>
<th className="pb-2 pr-4"></th>
<th className="pb-2"></th>
</tr>
</thead>
<tbody>
<tr className="border-b">
<td className="py-2 pr-4"><Badge className="bg-green-100 text-green-700">200</Badge></td>
<td className="py-2 pr-4"></td>
<td className="py-2 text-gray-600"></td>
</tr>
<tr className="border-b">
<td className="py-2 pr-4"><Badge className="bg-yellow-100 text-yellow-700">400</Badge></td>
<td className="py-2 pr-4"></td>
<td className="py-2 text-gray-600"></td>
</tr>
<tr className="border-b">
<td className="py-2 pr-4"><Badge className="bg-red-100 text-red-700">401</Badge></td>
<td className="py-2 pr-4"></td>
<td className="py-2 text-gray-600">API密钥无效或缺失</td>
</tr>
<tr className="border-b">
<td className="py-2 pr-4"><Badge className="bg-orange-100 text-orange-700">403</Badge></td>
<td className="py-2 pr-4"></td>
<td className="py-2 text-gray-600">访</td>
</tr>
<tr className="border-b">
<td className="py-2 pr-4"><Badge className="bg-gray-100 text-gray-700">404</Badge></td>
<td className="py-2 pr-4"></td>
<td className="py-2 text-gray-600"></td>
</tr>
<tr className="border-b">
<td className="py-2 pr-4"><Badge className="bg-purple-100 text-purple-700">429</Badge></td>
<td className="py-2 pr-4"></td>
<td className="py-2 text-gray-600"></td>
</tr>
<tr>
<td className="py-2 pr-4"><Badge className="bg-red-100 text-red-700">500</Badge></td>
<td className="py-2 pr-4"></td>
<td className="py-2 text-gray-600"></td>
</tr>
</tbody>
</table>
</CardContent>
</Card>
</div>
</div>
)
}