Ensure compatibility with both default and named imports for hook. #VERCEL_SKIP Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
665 lines
24 KiB
TypeScript
665 lines
24 KiB
TypeScript
"use client"
|
||
|
||
import { useState } from "react"
|
||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||
import { Button } from "@/components/ui/button"
|
||
import { Badge } from "@/components/ui/badge"
|
||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
|
||
import { Copy, Check, Code, FileJson, Play } from "lucide-react"
|
||
import { useToast } from "@/components/ui/use-toast"
|
||
|
||
interface ApiEndpoint {
|
||
id: string
|
||
name: string
|
||
method: "GET" | "POST" | "PUT" | "DELETE"
|
||
path: string
|
||
description: string
|
||
parameters: {
|
||
name: string
|
||
type: string
|
||
required: boolean
|
||
description: string
|
||
}[]
|
||
requestBody?: {
|
||
type: string
|
||
example: string
|
||
}
|
||
responses: {
|
||
code: string
|
||
description: string
|
||
example: string
|
||
}[]
|
||
authentication: "API Key" | "OAuth 2.0" | "None"
|
||
}
|
||
|
||
export function ApiDocumentation() {
|
||
const { toast } = useToast()
|
||
const [activeTab, setActiveTab] = useState("user-data")
|
||
const [copiedEndpoint, setCopiedEndpoint] = useState<string | null>(null)
|
||
|
||
// 模拟API端点数据
|
||
const apiEndpoints: Record<string, ApiEndpoint[]> = {
|
||
"user-data": [
|
||
{
|
||
id: "get-users",
|
||
name: "获取用户列表",
|
||
method: "GET",
|
||
path: "/api/users",
|
||
description: "获取系统中的用户列表,支持分页、筛选和排序",
|
||
parameters: [
|
||
{
|
||
name: "page",
|
||
type: "number",
|
||
required: false,
|
||
description: "页码,默认为1",
|
||
},
|
||
{
|
||
name: "limit",
|
||
type: "number",
|
||
required: false,
|
||
description: "每页数量,默认为20,最大为100",
|
||
},
|
||
{
|
||
name: "sort",
|
||
type: "string",
|
||
required: false,
|
||
description: "排序字段,例如:name,-createdAt(-表示降序)",
|
||
},
|
||
{
|
||
name: "filter",
|
||
type: "string",
|
||
required: false,
|
||
description: "筛选条件,例如:status=active",
|
||
},
|
||
],
|
||
responses: [
|
||
{
|
||
code: "200",
|
||
description: "成功",
|
||
example: `{
|
||
"data": [
|
||
{
|
||
"id": "user_123",
|
||
"name": "张三",
|
||
"phoneNumber": "138****1234",
|
||
"registrationDate": "2023-01-15T08:30:00Z",
|
||
"lastActiveTime": "2023-07-20T14:25:30Z",
|
||
"tags": ["高价值", "活跃用户"]
|
||
},
|
||
// 更多用户...
|
||
],
|
||
"pagination": {
|
||
"page": 1,
|
||
"limit": 20,
|
||
"total": 156,
|
||
"pages": 8
|
||
}
|
||
}`,
|
||
},
|
||
{
|
||
code: "400",
|
||
description: "请求参数错误",
|
||
example: `{
|
||
"error": "Bad Request",
|
||
"message": "Invalid filter format",
|
||
"code": "INVALID_FILTER"
|
||
}`,
|
||
},
|
||
{
|
||
code: "401",
|
||
description: "未授权",
|
||
example: `{
|
||
"error": "Unauthorized",
|
||
"message": "API key is invalid or expired",
|
||
"code": "INVALID_API_KEY"
|
||
}`,
|
||
},
|
||
],
|
||
authentication: "API Key",
|
||
},
|
||
{
|
||
id: "get-user",
|
||
name: "获取用户详情",
|
||
method: "GET",
|
||
path: "/api/users/{id}",
|
||
description: "根据用户ID获取用户详细信息",
|
||
parameters: [
|
||
{
|
||
name: "id",
|
||
type: "string",
|
||
required: true,
|
||
description: "用户ID",
|
||
},
|
||
],
|
||
responses: [
|
||
{
|
||
code: "200",
|
||
description: "成功",
|
||
example: `{
|
||
"id": "user_123",
|
||
"name": "张三",
|
||
"phoneNumber": "138****1234",
|
||
"identityNumber": "310******1234",
|
||
"email": "zhangsan@example.com",
|
||
"registrationDate": "2023-01-15T08:30:00Z",
|
||
"lastActiveTime": "2023-07-20T14:25:30Z",
|
||
"tags": ["高价值", "活跃用户"],
|
||
"devices": [
|
||
{
|
||
"id": "device_456",
|
||
"type": "mobile",
|
||
"model": "iPhone 13",
|
||
"imei": "123456789012345",
|
||
"lastActiveTime": "2023-07-20T14:25:30Z"
|
||
}
|
||
],
|
||
"userValue": {
|
||
"rfm": {
|
||
"recency": 5,
|
||
"frequency": 4,
|
||
"monetary": 5,
|
||
"score": 4.7
|
||
},
|
||
"lifetimeValue": 12500
|
||
}
|
||
}`,
|
||
},
|
||
{
|
||
code: "404",
|
||
description: "用户不存在",
|
||
example: `{
|
||
"error": "Not Found",
|
||
"message": "User with ID user_999 not found",
|
||
"code": "USER_NOT_FOUND"
|
||
}`,
|
||
},
|
||
],
|
||
authentication: "API Key",
|
||
},
|
||
],
|
||
"user-portrait": [
|
||
{
|
||
id: "get-user-portrait",
|
||
name: "获取用户画像",
|
||
method: "GET",
|
||
path: "/api/user-portrait/{id}",
|
||
description: "根据用户ID获取用户画像数据",
|
||
parameters: [
|
||
{
|
||
name: "id",
|
||
type: "string",
|
||
required: true,
|
||
description: "用户ID",
|
||
},
|
||
],
|
||
responses: [
|
||
{
|
||
code: "200",
|
||
description: "成功",
|
||
example: `{
|
||
"userId": "user_123",
|
||
"basicInfo": {
|
||
"name": "张三",
|
||
"age": 28,
|
||
"gender": "male",
|
||
"location": "上海市"
|
||
},
|
||
"behaviorTags": ["夜间活跃", "周末购物", "高频App使用"],
|
||
"interestTags": ["科技", "旅游", "美食"],
|
||
"consumptionPattern": {
|
||
"averageOrderValue": 320,
|
||
"purchaseFrequency": "每周2-3次",
|
||
"preferredCategories": ["电子产品", "服装"]
|
||
},
|
||
"rfmAnalysis": {
|
||
"recency": 5,
|
||
"frequency": 4,
|
||
"monetary": 5,
|
||
"score": 4.7,
|
||
"segment": "高价值用户"
|
||
},
|
||
"deviceInfo": [
|
||
{
|
||
"type": "mobile",
|
||
"model": "iPhone 13",
|
||
"osVersion": "iOS 16.5",
|
||
"usageFrequency": "高"
|
||
}
|
||
],
|
||
"channelPreference": ["App", "微信小程序"],
|
||
"riskScore": 0.2
|
||
}`,
|
||
},
|
||
],
|
||
authentication: "API Key",
|
||
},
|
||
],
|
||
"ai-analysis": [
|
||
{
|
||
id: "analyze-user-behavior",
|
||
name: "用户行为分析",
|
||
method: "POST",
|
||
path: "/api/ai/analyze-behavior",
|
||
description: "使用AI分析用户行为数据,生成洞察报告",
|
||
parameters: [],
|
||
requestBody: {
|
||
type: "application/json",
|
||
example: `{
|
||
"userId": "user_123",
|
||
"timeRange": {
|
||
"start": "2023-01-01T00:00:00Z",
|
||
"end": "2023-07-20T23:59:59Z"
|
||
},
|
||
"analysisDepth": "deep",
|
||
"includeTags": true,
|
||
"includeRecommendations": true
|
||
}`,
|
||
},
|
||
responses: [
|
||
{
|
||
code: "200",
|
||
description: "成功",
|
||
example: `{
|
||
"userId": "user_123",
|
||
"analysisTime": "2023-07-21T10:15:30Z",
|
||
"timeRange": {
|
||
"start": "2023-01-01T00:00:00Z",
|
||
"end": "2023-07-20T23:59:59Z"
|
||
},
|
||
"behaviorPatterns": [
|
||
{
|
||
"pattern": "夜间活跃",
|
||
"confidence": 0.92,
|
||
"description": "用户主要在晚上9点至凌晨1点活跃",
|
||
"supportingData": {
|
||
"activeTimeDistribution": {
|
||
"morning": 0.15,
|
||
"afternoon": 0.25,
|
||
"evening": 0.60
|
||
}
|
||
}
|
||
},
|
||
// 更多行为模式...
|
||
],
|
||
"insights": [
|
||
{
|
||
"type": "preference",
|
||
"description": "用户对科技类产品有强烈兴趣,尤其是智能家居设备",
|
||
"confidence": 0.85
|
||
},
|
||
// 更多洞察...
|
||
],
|
||
"recommendations": [
|
||
{
|
||
"type": "marketing",
|
||
"description": "建议在晚间时段推送智能家居相关促销信息",
|
||
"expectedImpact": "高"
|
||
},
|
||
// 更多建议...
|
||
]
|
||
}`,
|
||
},
|
||
],
|
||
authentication: "API Key",
|
||
},
|
||
],
|
||
}
|
||
|
||
const handleCopyCode = (code: string, endpointId: string) => {
|
||
navigator.clipboard.writeText(code)
|
||
setCopiedEndpoint(endpointId)
|
||
toast({
|
||
title: "已复制到剪贴板",
|
||
description: "代码已成功复制到剪贴板",
|
||
})
|
||
setTimeout(() => setCopiedEndpoint(null), 2000)
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>API文档</CardTitle>
|
||
<CardDescription>用户数据资产中台API接口文档和使用说明</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4">
|
||
<TabsList className="grid grid-cols-3 w-full max-w-md">
|
||
<TabsTrigger value="user-data">用户数据</TabsTrigger>
|
||
<TabsTrigger value="user-portrait">用户画像</TabsTrigger>
|
||
<TabsTrigger value="ai-analysis">AI分析</TabsTrigger>
|
||
</TabsList>
|
||
|
||
{Object.entries(apiEndpoints).map(([category, endpoints]) => (
|
||
<TabsContent key={category} value={category} className="space-y-4">
|
||
{endpoints.map((endpoint) => (
|
||
<Card key={endpoint.id} className="border shadow-sm">
|
||
<CardHeader className="pb-2">
|
||
<div className="flex justify-between items-start">
|
||
<div>
|
||
<CardTitle className="text-lg">{endpoint.name}</CardTitle>
|
||
<CardDescription>{endpoint.description}</CardDescription>
|
||
</div>
|
||
<Badge
|
||
className={
|
||
endpoint.method === "GET"
|
||
? "bg-blue-100 text-blue-800"
|
||
: endpoint.method === "POST"
|
||
? "bg-green-100 text-green-800"
|
||
: endpoint.method === "PUT"
|
||
? "bg-yellow-100 text-yellow-800"
|
||
: "bg-red-100 text-red-800"
|
||
}
|
||
>
|
||
{endpoint.method}
|
||
</Badge>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent className="pb-2">
|
||
<div className="flex items-center gap-2 mb-4">
|
||
<code className="bg-muted px-2 py-1 rounded text-sm font-mono">{endpoint.path}</code>
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
onClick={() => handleCopyCode(endpoint.path, `path-${endpoint.id}`)}
|
||
>
|
||
{copiedEndpoint === `path-${endpoint.id}` ? (
|
||
<Check className="h-4 w-4" />
|
||
) : (
|
||
<Copy className="h-4 w-4" />
|
||
)}
|
||
</Button>
|
||
</div>
|
||
|
||
<Accordion type="single" collapsible className="w-full">
|
||
{endpoint.parameters.length > 0 && (
|
||
<AccordionItem value="parameters">
|
||
<AccordionTrigger>请求参数</AccordionTrigger>
|
||
<AccordionContent>
|
||
<div className="rounded-md border">
|
||
<table className="min-w-full divide-y divide-border">
|
||
<thead>
|
||
<tr className="bg-muted/50">
|
||
<th className="px-4 py-2 text-left text-sm font-medium">参数名</th>
|
||
<th className="px-4 py-2 text-left text-sm font-medium">类型</th>
|
||
<th className="px-4 py-2 text-left text-sm font-medium">必填</th>
|
||
<th className="px-4 py-2 text-left text-sm font-medium">描述</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-border">
|
||
{endpoint.parameters.map((param, index) => (
|
||
<tr key={index}>
|
||
<td className="px-4 py-2 text-sm font-mono">{param.name}</td>
|
||
<td className="px-4 py-2 text-sm">{param.type}</td>
|
||
<td className="px-4 py-2 text-sm">
|
||
{param.required ? (
|
||
<Badge className="bg-red-100 text-red-800">必填</Badge>
|
||
) : (
|
||
<Badge className="bg-gray-100 text-gray-800">可选</Badge>
|
||
)}
|
||
</td>
|
||
<td className="px-4 py-2 text-sm">{param.description}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</AccordionContent>
|
||
</AccordionItem>
|
||
)}
|
||
|
||
{endpoint.requestBody && (
|
||
<AccordionItem value="request-body">
|
||
<AccordionTrigger>请求体</AccordionTrigger>
|
||
<AccordionContent>
|
||
<div className="space-y-2">
|
||
<div className="flex items-center gap-2">
|
||
<Badge className="bg-purple-100 text-purple-800">{endpoint.requestBody.type}</Badge>
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
className="h-7"
|
||
onClick={() => handleCopyCode(endpoint.requestBody!.example, `req-${endpoint.id}`)}
|
||
>
|
||
{copiedEndpoint === `req-${endpoint.id}` ? (
|
||
<Check className="h-3 w-3 mr-1" />
|
||
) : (
|
||
<Copy className="h-3 w-3 mr-1" />
|
||
)}
|
||
复制
|
||
</Button>
|
||
</div>
|
||
<pre className="bg-muted p-4 rounded-md overflow-x-auto text-sm font-mono">
|
||
{endpoint.requestBody.example}
|
||
</pre>
|
||
</div>
|
||
</AccordionContent>
|
||
</AccordionItem>
|
||
)}
|
||
|
||
<AccordionItem value="responses">
|
||
<AccordionTrigger>响应</AccordionTrigger>
|
||
<AccordionContent>
|
||
<div className="space-y-4">
|
||
{endpoint.responses.map((response, index) => (
|
||
<div key={index} className="space-y-2">
|
||
<div className="flex items-center gap-2">
|
||
<Badge
|
||
className={
|
||
response.code.startsWith("2")
|
||
? "bg-green-100 text-green-800"
|
||
: response.code.startsWith("4")
|
||
? "bg-red-100 text-red-800"
|
||
: "bg-yellow-100 text-yellow-800"
|
||
}
|
||
>
|
||
{response.code}
|
||
</Badge>
|
||
<span className="text-sm font-medium">{response.description}</span>
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
className="h-7"
|
||
onClick={() => handleCopyCode(response.example, `res-${endpoint.id}-${index}`)}
|
||
>
|
||
{copiedEndpoint === `res-${endpoint.id}-${index}` ? (
|
||
<Check className="h-3 w-3 mr-1" />
|
||
) : (
|
||
<Copy className="h-3 w-3 mr-1" />
|
||
)}
|
||
复制
|
||
</Button>
|
||
</div>
|
||
<pre className="bg-muted p-4 rounded-md overflow-x-auto text-sm font-mono">
|
||
{response.example}
|
||
</pre>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</AccordionContent>
|
||
</AccordionItem>
|
||
|
||
<AccordionItem value="authentication">
|
||
<AccordionTrigger>认证方式</AccordionTrigger>
|
||
<AccordionContent>
|
||
<div className="p-2">
|
||
<Badge className="bg-blue-100 text-blue-800">{endpoint.authentication}</Badge>
|
||
{endpoint.authentication === "API Key" && (
|
||
<div className="mt-2 text-sm">
|
||
<p>在请求头中添加以下字段:</p>
|
||
<code className="bg-muted px-2 py-1 rounded text-sm font-mono mt-1 block">
|
||
X-API-Key: your_api_key_here
|
||
</code>
|
||
</div>
|
||
)}
|
||
{endpoint.authentication === "OAuth 2.0" && (
|
||
<div className="mt-2 text-sm">
|
||
<p>在请求头中添加以下字段:</p>
|
||
<code className="bg-muted px-2 py-1 rounded text-sm font-mono mt-1 block">
|
||
Authorization: Bearer your_access_token_here
|
||
</code>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</AccordionContent>
|
||
</AccordionItem>
|
||
|
||
<AccordionItem value="code-examples">
|
||
<AccordionTrigger>代码示例</AccordionTrigger>
|
||
<AccordionContent>
|
||
<div className="space-y-4">
|
||
<div>
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Badge className="bg-blue-100 text-blue-800">JavaScript</Badge>
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
className="h-7"
|
||
onClick={() => handleCopyCode(jsExample, `js-${endpoint.id}`)}
|
||
>
|
||
{copiedEndpoint === `js-${endpoint.id}` ? (
|
||
<Check className="h-3 w-3 mr-1" />
|
||
) : (
|
||
<Copy className="h-3 w-3 mr-1" />
|
||
)}
|
||
复制
|
||
</Button>
|
||
</div>
|
||
<pre className="bg-muted p-4 rounded-md overflow-x-auto text-sm font-mono">
|
||
{jsExample}
|
||
</pre>
|
||
</div>
|
||
|
||
<div>
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<Badge className="bg-green-100 text-green-800">Python</Badge>
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
className="h-7"
|
||
onClick={() => handleCopyCode(pythonExample, `py-${endpoint.id}`)}
|
||
>
|
||
{copiedEndpoint === `py-${endpoint.id}` ? (
|
||
<Check className="h-3 w-3 mr-1" />
|
||
) : (
|
||
<Copy className="h-3 w-3 mr-1" />
|
||
)}
|
||
复制
|
||
</Button>
|
||
</div>
|
||
<pre className="bg-muted p-4 rounded-md overflow-x-auto text-sm font-mono">
|
||
{pythonExample}
|
||
</pre>
|
||
</div>
|
||
</div>
|
||
</AccordionContent>
|
||
</AccordionItem>
|
||
</Accordion>
|
||
</CardContent>
|
||
</Card>
|
||
))}
|
||
</TabsContent>
|
||
))}
|
||
</Tabs>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>API使用指南</CardTitle>
|
||
<CardDescription>如何开始使用用户数据资产中台API</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="space-y-2">
|
||
<h3 className="text-lg font-medium">1. 获取API密钥</h3>
|
||
<p className="text-sm text-muted-foreground">
|
||
在开始使用API之前,您需要获取API密钥。请联系系统管理员申请API密钥。
|
||
</p>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<h3 className="text-lg font-medium">2. 认证</h3>
|
||
<p className="text-sm text-muted-foreground">所有API请求都需要进行认证。请在请求头中添加您的API密钥:</p>
|
||
<pre className="bg-muted p-4 rounded-md overflow-x-auto text-sm font-mono">
|
||
X-API-Key: your_api_key_here
|
||
</pre>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<h3 className="text-lg font-medium">3. 请求格式</h3>
|
||
<p className="text-sm text-muted-foreground">API支持JSON格式的请求和响应。请在请求头中设置:</p>
|
||
<pre className="bg-muted p-4 rounded-md overflow-x-auto text-sm font-mono">
|
||
Content-Type: application/json Accept: application/json
|
||
</pre>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<h3 className="text-lg font-medium">4. 错误处理</h3>
|
||
<p className="text-sm text-muted-foreground">
|
||
API使用标准的HTTP状态码表示请求的结果。错误响应会包含错误详情:
|
||
</p>
|
||
<pre className="bg-muted p-4 rounded-md overflow-x-auto text-sm font-mono">
|
||
{`{
|
||
"error": "错误类型",
|
||
"message": "错误详细信息",
|
||
"code": "错误代码"
|
||
}`}
|
||
</pre>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<h3 className="text-lg font-medium">5. 速率限制</h3>
|
||
<p className="text-sm text-muted-foreground">
|
||
API有速率限制,默认为每分钟100个请求。超过限制会返回429状态码。
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex justify-between items-center mt-4">
|
||
<Button variant="outline" className="gap-2">
|
||
<FileJson className="h-4 w-4" />
|
||
下载OpenAPI规范
|
||
</Button>
|
||
<Button variant="outline" className="gap-2">
|
||
<Code className="h-4 w-4" />
|
||
下载SDK
|
||
</Button>
|
||
<Button className="gap-2">
|
||
<Play className="h-4 w-4" />
|
||
API测试工具
|
||
</Button>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 示例代码
|
||
const jsExample = `// 使用fetch API调用
|
||
const apiKey = 'your_api_key_here';
|
||
|
||
fetch('https://api.example.com/api/users', {
|
||
method: 'GET',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-API-Key': apiKey
|
||
}
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => console.log(data))
|
||
.catch(error => console.error('Error:', error));`
|
||
|
||
const pythonExample = `# 使用requests库调用
|
||
import requests
|
||
|
||
api_key = 'your_api_key_here'
|
||
headers = {
|
||
'Content-Type': 'application/json',
|
||
'X-API-Key': api_key
|
||
}
|
||
|
||
response = requests.get('https://api.example.com/api/users', headers=headers)
|
||
data = response.json()
|
||
print(data)`
|