Files
smart-crop-ui/crop-x/src/app/api-example/page.tsx

654 lines
22 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 } from 'react';
import {
loginApiV1AuthLoginPost,
registerApiV1AuthRegisterPost,
getCurrentUserApiV1AuthMeGet,
getAllUsersApiV1AuthUsersGet,
logoutApiV1AuthLogoutPost,
rootGet,
healthCheckHealthGet,
type UserLogin,
type UserRegister,
type ApiResponse
} from '@/lib/api';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Badge } from '@/components/ui/badge';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator';
export default function ApiExamplePage() {
const [loading, setLoading] = useState(false);
const [results, setResults] = useState<any[]>([]);
const [errors, setErrors] = useState<string[]>([]);
// 登录表单状态
const [loginData, setLoginData] = useState<UserLogin>({
username: '',
password: ''
});
// 注册表单状态
const [registerData, setRegisterData] = useState<UserRegister>({
username: '',
password: ''
});
// 添加结果到显示列表
const addResult = (type: string, input: any, output: any, error?: string) => {
const result = {
id: Date.now(),
type,
input,
output,
error,
timestamp: new Date().toLocaleTimeString()
};
setResults(prev => [result, ...prev]);
if (error) {
setErrors(prev => [error, ...prev]);
}
};
// 清空结果
const clearResults = () => {
setResults([]);
setErrors([]);
};
// 用户登录
const handleLogin = async () => {
setLoading(true);
try {
addResult('用户登录', '登录请求', '发送中...');
const response = await loginApiV1AuthLoginPost({
body: loginData
});
if (response.data) {
addResult('用户登录', loginData, response.data);
} else if (response.error) {
addResult('用户登录', loginData, null, JSON.stringify(response.error));
}
} catch (error: any) {
addResult('用户登录', loginData, null, error.message);
} finally {
setLoading(false);
}
};
// 用户注册
const handleRegister = async () => {
setLoading(true);
try {
addResult('用户注册', '注册请求', '发送中...');
const response = await registerApiV1AuthRegisterPost({
body: registerData
});
if (response.data) {
addResult('用户注册', registerData, response.data);
} else if (response.error) {
addResult('用户注册', registerData, null, JSON.stringify(response.error));
}
} catch (error: any) {
addResult('用户注册', registerData, null, error.message);
} finally {
setLoading(false);
}
};
// 获取当前用户信息
const handleGetCurrentUser = async () => {
setLoading(true);
try {
addResult('获取当前用户', 'GET /api/v1/auth/me', '发送中...');
const response = await getCurrentUserApiV1AuthMeGet({});
if (response.data) {
addResult('获取当前用户', '无参数', response.data);
} else if (response.error) {
addResult('获取当前用户', '无参数', null, JSON.stringify(response.error));
}
} catch (error: any) {
addResult('获取当前用户', '无参数', null, error.message);
} finally {
setLoading(false);
}
};
// 获取所有用户
const handleGetAllUsers = async () => {
setLoading(true);
try {
addResult('获取所有用户', 'GET /api/v1/auth/users', '发送中...');
const response = await getAllUsersApiV1AuthUsersGet({});
if (response.data) {
addResult('获取所有用户', '无参数', response.data);
} else if (response.error) {
addResult('获取所有用户', '无参数', null, JSON.stringify(response.error));
}
} catch (error: any) {
addResult('获取所有用户', '无参数', null, error.message);
} finally {
setLoading(false);
}
};
// 用户登出
const handleLogout = async () => {
setLoading(true);
try {
addResult('用户登出', 'POST /api/v1/auth/logout', '发送中...');
const response = await logoutApiV1AuthLogoutPost({});
if (response.data) {
addResult('用户登出', '无参数', response.data);
} else if (response.error) {
addResult('用户登出', '无参数', null, JSON.stringify(response.error));
}
} catch (error: any) {
addResult('用户登出', '无参数', null, error.message);
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen bg-background p-6">
<div className="max-w-7xl mx-auto">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold">OpenAPI </h1>
<Button
variant="outline"
onClick={clearResults}
disabled={results.length === 0}
>
({results.length})
</Button>
</div>
<Tabs defaultValue="interactive" className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="interactive"></TabsTrigger>
<TabsTrigger value="examples"></TabsTrigger>
</TabsList>
<TabsContent value="interactive" className="space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 左侧API 操作面板 */}
<Card>
<CardHeader>
<CardTitle>API </CardTitle>
<CardDescription>
使 @hey-api/openapi-ts
</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="auth" className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="auth"></TabsTrigger>
<TabsTrigger value="user"></TabsTrigger>
</TabsList>
<TabsContent value="auth" className="space-y-4">
{/* 登录表单 */}
<div className="space-y-2">
<h4 className="font-medium"></h4>
<Input
placeholder="用户名"
value={loginData.username}
onChange={(e) => setLoginData(prev => ({ ...prev, username: e.target.value }))}
/>
<Input
type="password"
placeholder="密码"
value={loginData.password}
onChange={(e) => setLoginData(prev => ({ ...prev, password: e.target.value }))}
/>
<Button
onClick={handleLogin}
disabled={loading || !loginData.username || !loginData.password}
className="w-full"
>
{loading ? '登录中...' : '登录'}
</Button>
</div>
{/* 注册表单 */}
<div className="space-y-2">
<h4 className="font-medium"></h4>
<Input
placeholder="用户名"
value={registerData.username}
onChange={(e) => setRegisterData(prev => ({ ...prev, username: e.target.value }))}
/>
<Input
type="password"
placeholder="密码"
value={registerData.password}
onChange={(e) => setRegisterData(prev => ({ ...prev, password: e.target.value }))}
/>
<Button
onClick={handleRegister}
disabled={loading || !registerData.username || !registerData.password}
variant="outline"
className="w-full"
>
{loading ? '注册中...' : '注册'}
</Button>
</div>
</TabsContent>
<TabsContent value="user" className="space-y-4">
<div className="grid grid-cols-1 gap-2">
<Button
onClick={handleGetCurrentUser}
disabled={loading}
variant="outline"
>
</Button>
<Button
onClick={handleGetAllUsers}
disabled={loading}
variant="outline"
>
</Button>
<Button
onClick={handleLogout}
disabled={loading}
variant="outline"
>
</Button>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
{/* 右侧:结果显示面板 */}
<Card>
<CardHeader>
<CardTitle> & </CardTitle>
<CardDescription>
API
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4 max-h-[600px] overflow-y-auto">
{results.length === 0 ? (
<div className="text-center text-muted-foreground py-8">
<p> API </p>
<p className="text-sm"> API </p>
</div>
) : (
results.map((result) => (
<div key={result.id} className="border rounded-lg p-4 space-y-2">
<div className="flex items-center justify-between">
<h4 className="font-medium">{result.type}</h4>
<div className="flex items-center gap-2">
<Badge variant="outline">{result.timestamp}</Badge>
{result.error ? (
<Badge variant="destructive"></Badge>
) : (
<Badge variant="default"></Badge>
)}
</div>
</div>
{/* 输入数据 */}
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">:</p>
<pre className="text-xs bg-muted p-2 rounded overflow-x-auto">
{JSON.stringify(result.input, null, 2)}
</pre>
</div>
{/* 输出数据 */}
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">
{result.error ? '错误信息:' : '响应数据:'}
</p>
<pre className={`text-xs p-2 rounded overflow-x-auto ${
result.error ? 'bg-destructive/10 text-destructive' : 'bg-muted'
}`}>
{result.error || JSON.stringify(result.output, null, 2)}
</pre>
</div>
</div>
))
)}
</div>
</CardContent>
</Card>
</div>
{/* 错误提示 */}
{errors.length > 0 && (
<div className="mt-6">
<Alert>
<AlertDescription>
{errors.length}
</AlertDescription>
</Alert>
</div>
)}
</TabsContent>
<TabsContent value="examples" className="space-y-6">
<ApiExamplesPage />
</TabsContent>
</Tabs>
</div>
</div>
);
}
// API示例页面组件
function ApiExamplesPage() {
const [examplesLoading, setExamplesLoading] = useState<Record<string, boolean>>({});
const [examplesResults, setExamplesResults] = useState<Record<string, any>>({});
// API示例配置 - 基于openapi.json
const apiExamples = [
{
id: 'login',
method: 'POST',
path: '/api/v1/auth/login',
title: '用户登录',
description: '用户登录接口',
exampleParams: {
username: 'admin',
password: 'admin123'
},
expectedOutput: {
success: true,
message: '登录成功',
data: { token: 'jwt_token_here' }
}
},
{
id: 'register',
method: 'POST',
path: '/api/v1/auth/register',
title: '用户注册',
description: '用户注册接口',
exampleParams: {
username: 'newuser',
password: 'newpassword'
},
expectedOutput: {
success: true,
message: '注册成功',
data: { user_id: 1, username: 'newuser' }
}
},
{
id: 'me',
method: 'GET',
path: '/api/v1/auth/me',
title: '获取当前用户信息',
description: '获取当前登录用户的信息',
exampleParams: null,
expectedOutput: {
success: true,
message: '获取用户信息成功',
data: { user_id: 1, username: 'admin' }
}
},
{
id: 'logout',
method: 'POST',
path: '/api/v1/auth/logout',
title: '用户登出',
description: '用户登出接口',
exampleParams: null,
expectedOutput: {
success: true,
message: '登出成功',
data: null
}
},
{
id: 'users',
method: 'GET',
path: '/api/v1/auth/users',
title: '获取所有用户列表',
description: '获取系统中所有用户的列表 (仅用于演示)',
exampleParams: null,
expectedOutput: {
success: true,
message: '获取用户列表成功',
data: [{ user_id: 1, username: 'admin' }]
}
},
{
id: 'root',
method: 'GET',
path: '/',
title: '根路径',
description: 'API根路径用于测试连接',
exampleParams: null,
expectedOutput: {}
},
{
id: 'health',
method: 'GET',
path: '/health',
title: '健康检查',
description: 'API健康检查接口',
exampleParams: null,
expectedOutput: { status: 'healthy' }
}
];
// 调用示例API
const callExampleApi = async (example: typeof apiExamples[0]) => {
setExamplesLoading(prev => ({ ...prev, [example.id]: true }));
try {
let response;
switch (example.id) {
case 'login':
response = await loginApiV1AuthLoginPost({
body: example.exampleParams
});
break;
case 'register':
response = await registerApiV1AuthRegisterPost({
body: example.exampleParams
});
break;
case 'me':
response = await getCurrentUserApiV1AuthMeGet({});
break;
case 'logout':
response = await logoutApiV1AuthLogoutPost({});
break;
case 'users':
response = await getAllUsersApiV1AuthUsersGet({});
break;
case 'root':
response = await rootGet({});
break;
case 'health':
response = await healthCheckHealthGet({});
break;
default:
throw new Error('Unknown API example');
}
setExamplesResults(prev => ({
...prev,
[example.id]: {
success: !response.error,
data: response.data,
error: response.error,
input: example.exampleParams,
timestamp: new Date().toLocaleTimeString()
}
}));
} catch (error: any) {
setExamplesResults(prev => ({
...prev,
[example.id]: {
success: false,
error: error.message,
input: example.exampleParams,
timestamp: new Date().toLocaleTimeString()
}
}));
} finally {
setExamplesLoading(prev => ({ ...prev, [example.id]: false }));
}
};
// 批量调用所有示例
const callAllExamples = async () => {
// 先调用登录接口获取token
await callExampleApi(apiExamples[0]);
// 然后调用其他接口
for (const example of apiExamples.slice(1)) {
await new Promise(resolve => setTimeout(resolve, 100)); // 间隔100ms
await callExampleApi(example);
}
};
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<div>
<h2 className="text-2xl font-bold"></h2>
<p className="text-muted-foreground">
OpenAPI规范自动生成的所有接口示例
</p>
</div>
<Button onClick={callAllExamples} disabled={Object.values(examplesLoading).some(v => v)}>
{Object.values(examplesLoading).some(v => v) ? '测试中...' : '批量测试所有接口'}
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{apiExamples.map((example) => (
<Card key={example.id} className="h-fit">
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="text-base">{example.title}</CardTitle>
<CardDescription className="text-xs">
{example.method} {example.path}
</CardDescription>
</div>
<Badge variant="outline">{example.method}</Badge>
</div>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-muted-foreground">{example.description}</p>
{/* 示例参数 */}
<div className="space-y-2">
<h4 className="text-sm font-medium">:</h4>
{example.exampleParams ? (
<pre className="text-xs bg-muted p-2 rounded overflow-x-auto">
{JSON.stringify(example.exampleParams, null, 2)}
</pre>
) : (
<p className="text-xs text-muted-foreground italic"></p>
)}
</div>
{/* 预期输出 */}
<div className="space-y-2">
<h4 className="text-sm font-medium">:</h4>
<pre className="text-xs bg-muted p-2 rounded overflow-x-auto">
{JSON.stringify(example.expectedOutput, null, 2)}
</pre>
</div>
{/* 实际结果 */}
{examplesResults[example.id] && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<h4 className="text-sm font-medium">
<span className="ml-2 text-xs text-muted-foreground">
({examplesResults[example.id].timestamp})
</span>
</h4>
<Badge
variant={examplesResults[example.id].success ? "default" : "destructive"}
>
{examplesResults[example.id].success ? '成功' : '失败'}
</Badge>
</div>
<pre className={`text-xs p-2 rounded overflow-x-auto ${
examplesResults[example.id].success ? 'bg-green-50 text-green-800 border-green-200' : 'bg-red-50 text-red-800 border-red-200'
}`}>
{examplesResults[example.id].error
? JSON.stringify(examplesResults[example.id].error, null, 2)
: JSON.stringify(examplesResults[example.id].data, null, 2)
}
</pre>
</div>
)}
<Button
onClick={() => callExampleApi(example)}
disabled={examplesLoading[example.id]}
className="w-full"
variant={examplesResults[example.id]?.success ? "outline" : "default"}
>
{examplesLoading[example.id] ? '测试中...' : '测试此接口'}
</Button>
</CardContent>
</Card>
))}
</div>
{/* 统计信息 */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-3 gap-4 text-center">
<div>
<p className="text-2xl font-bold text-green-600">
{Object.values(examplesResults).filter(r => r?.success).length}
</p>
<p className="text-sm text-muted-foreground"></p>
</div>
<div>
<p className="text-2xl font-bold text-red-600">
{Object.values(examplesResults).filter(r => r?.success === false).length}
</p>
<p className="text-sm text-muted-foreground"></p>
</div>
<div>
<p className="text-2xl font-bold text-blue-600">
{apiExamples.length}
</p>
<p className="text-sm text-muted-foreground"></p>
</div>
</div>
</CardContent>
</Card>
</div>
);
}