refactor: overhaul UI for streamlined user experience

Redesign navigation, home overview, user portrait, and valuation pages
with improved functionality and responsive design.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2025-07-18 13:47:12 +00:00
parent 440b310c6f
commit 2408d50cb0
316 changed files with 55785 additions and 0 deletions

View File

@@ -0,0 +1,664 @@
"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有速率限制100429
</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)`