389 lines
13 KiB
TypeScript
389 lines
13 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useEffect } from "react";
|
||
import {
|
||
Table,
|
||
TableBody,
|
||
TableCell,
|
||
TableHead,
|
||
TableHeader,
|
||
TableRow,
|
||
} from "@/components/ui/table";
|
||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||
import { Badge } from "@/components/ui/badge";
|
||
import { Skeleton } from "@/components/ui/skeleton";
|
||
import { motion } from "framer-motion";
|
||
import { ExternalLink, GitCommit } from "lucide-react";
|
||
|
||
export default function ApiDocsComponent() {
|
||
const [activePage, setActivePage] = useState("home");
|
||
const [imageCount, setImageCount] = useState<number | null>(null);
|
||
const [announcements, setAnnouncements] = useState<
|
||
Array<{ date: string; content: string }>
|
||
>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
useEffect(() => {
|
||
const fetchData = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const [imageCountResponse, announcementsResponse] = await Promise.all([
|
||
fetch("/?api=total-pic"),
|
||
fetch("/?api=announcements"),
|
||
]);
|
||
const imageCountData = await imageCountResponse.json();
|
||
const announcementsData = await announcementsResponse.json();
|
||
setImageCount(imageCountData.count);
|
||
setAnnouncements(announcementsData.announcements);
|
||
} catch (error) {
|
||
console.error("Failed to fetch data:", error);
|
||
setImageCount(null);
|
||
setAnnouncements([]);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
fetchData();
|
||
}, []);
|
||
|
||
const apiEndpoints = [
|
||
{
|
||
id: 1,
|
||
device: "手机",
|
||
method: "GET",
|
||
url: "/phone",
|
||
description: "竖屏随机图片API",
|
||
},
|
||
{
|
||
id: 2,
|
||
device: "电脑",
|
||
method: "GET",
|
||
url: "/pc",
|
||
description: "横屏随机图片API",
|
||
},
|
||
{
|
||
id: 3,
|
||
device: "All",
|
||
method: "GET",
|
||
url: "/favicon",
|
||
description: "获取本站favicon",
|
||
},
|
||
{
|
||
id: 4,
|
||
device: "All",
|
||
method: "GET",
|
||
url: "/bj",
|
||
description: "获取静态页面装饰图",
|
||
},
|
||
{
|
||
id: 5,
|
||
device: "All",
|
||
method: "GET",
|
||
url: "/fox",
|
||
description: "获取狐狸图",
|
||
},
|
||
];
|
||
|
||
const parameters = [
|
||
{
|
||
id: 1,
|
||
param: "time",
|
||
value: "Boolean",
|
||
description: "是否区分时段,默认为false",
|
||
},
|
||
{
|
||
id: 2,
|
||
param: "mode",
|
||
value: "String",
|
||
description:
|
||
"填json则返回一串数组;填redirect则会重定向至图片URL;不填则返回一张图片",
|
||
},
|
||
{
|
||
id: 3,
|
||
param: "source",
|
||
value: "Boolean",
|
||
description: "是否使用原图(加载较慢),默认为true",
|
||
},
|
||
];
|
||
|
||
const returnData = [
|
||
{ id: 1, data: "code", description: "状态码" },
|
||
{ id: 2, data: "image_url", description: "图片地址" },
|
||
{ id: 3, data: "time", description: "是否分时段" },
|
||
{ id: 4, data: "endpoint_type", description: "适用设备" },
|
||
{ id: 5, data: "is_source", description: "是否为原图" },
|
||
{ id: 6, data: "mode", description: "输出模式" },
|
||
];
|
||
|
||
const friendlyLinks = [
|
||
{ name: "mei的网络日志", url: "https://mmeiblog.cn" }
|
||
];
|
||
|
||
const HomePage = () => (
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.5 }}
|
||
className="space-y-6"
|
||
>
|
||
<Card className="bg-white shadow-lg rounded-lg overflow-hidden">
|
||
<CardHeader className="bg-gradient-to-r from-purple-500 to-pink-500 text-white">
|
||
<CardTitle className="text-2xl font-bold">网站简介</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="p-6">
|
||
<p className="text-gray-700">
|
||
欢迎使用{" "}
|
||
<code className="bg-gray-100 p-1 rounded text-pink-600 font-mono">
|
||
Cat API
|
||
</code>
|
||
, 由 mei 开发并维护。
|
||
</p>
|
||
<p className="mt-4 text-gray-600">
|
||
用于实践我最新学到的技术,也能勉强保证服务可用性(但被打了可扛不住)。
|
||
</p>
|
||
<p className="mt-4 text-gray-600">
|
||
如果你觉得这个API有什么不完善的地方或者说你有什么更好的想♂法,可以发送邮箱至{" "}
|
||
<a
|
||
href="mailto:i@mmeiblog.cn"
|
||
className="text-blue-600 hover:underline"
|
||
>
|
||
i@mmeiblog.cn
|
||
</a>
|
||
。
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="bg-white shadow-lg rounded-lg overflow-hidden">
|
||
<CardHeader className="bg-gradient-to-r from-green-500 to-teal-500 text-white">
|
||
<CardTitle className="text-2xl font-bold">站点公告</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="p-6">
|
||
{loading ? (
|
||
<Skeleton className="h-20 w-full" />
|
||
) : announcements.length > 0 ? (
|
||
announcements.map((announcement, index) => (
|
||
<div
|
||
key={index}
|
||
className="mb-4 last:mb-0 bg-gray-50 p-4 rounded-lg"
|
||
>
|
||
<Badge
|
||
variant="outline"
|
||
className="bg-teal-100 text-teal-800 mb-2"
|
||
>
|
||
{announcement.date}
|
||
</Badge>
|
||
<p className="text-gray-700">{announcement.content}</p>
|
||
</div>
|
||
))
|
||
) : (
|
||
<p className="text-gray-500 italic">暂无公告</p>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card className="bg-white shadow-lg rounded-lg overflow-hidden">
|
||
<CardHeader className="bg-gradient-to-r from-blue-500 to-indigo-500 text-white">
|
||
<CardTitle className="text-2xl font-bold">友情链接</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="p-6">
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||
{friendlyLinks.map((link, index) => (
|
||
<a
|
||
key={index}
|
||
href={link.url}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="flex items-center p-4 bg-gray-50 rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200 hover:bg-gray-100"
|
||
>
|
||
<ExternalLink className="w-5 h-5 mr-2 text-indigo-500" />
|
||
<span className="text-indigo-600 hover:underline">
|
||
{link.name}
|
||
</span>
|
||
</a>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</motion.div>
|
||
);
|
||
|
||
const RandomImagePage = () => (
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.5 }}
|
||
className="space-y-8"
|
||
>
|
||
<Card className="bg-white shadow-lg rounded-lg overflow-hidden">
|
||
<CardHeader className="bg-gradient-to-r from-yellow-400 to-orange-500 text-white">
|
||
<CardTitle className="text-2xl font-bold">随机图片 API</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="p-6">
|
||
<p className="text-gray-700">
|
||
此 API 提供随机二次元图片,不定期更新!
|
||
</p>
|
||
<p className="mt-4 text-gray-600">
|
||
图片总数:{" "}
|
||
{loading ? (
|
||
<Skeleton className="h-4 w-[100px] inline-block ml-2" />
|
||
) : (
|
||
<Badge
|
||
variant="secondary"
|
||
className="ml-2 bg-orange-100 text-orange-800"
|
||
>
|
||
{imageCount !== null ? imageCount : "获取失败"}
|
||
</Badge>
|
||
)}
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Tabs
|
||
defaultValue="endpoints"
|
||
className="bg-white shadow-lg rounded-lg overflow-hidden"
|
||
>
|
||
<TabsList className="grid w-full grid-cols-3 bg-gradient-to-r from-purple-500 to-pink-500 p-1 gap-1">
|
||
<TabsTrigger
|
||
value="endpoints"
|
||
className="text-white data-[state=active]:bg-white data-[state=active]:text-purple-500"
|
||
>
|
||
API 地址
|
||
</TabsTrigger>
|
||
<TabsTrigger
|
||
value="parameters"
|
||
className="text-white data-[state=active]:bg-white data-[state=active]:text-purple-500"
|
||
>
|
||
参数列表
|
||
</TabsTrigger>
|
||
<TabsTrigger
|
||
value="returnData"
|
||
className="text-white data-[state=active]:bg-white data-[state=active]:text-purple-500"
|
||
>
|
||
返回数据
|
||
</TabsTrigger>
|
||
</TabsList>
|
||
<TabsContent value="endpoints" className="p-6">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead className="w-[50px]">#</TableHead>
|
||
<TableHead>适用设备</TableHead>
|
||
<TableHead>请求方式</TableHead>
|
||
<TableHead>请求地址</TableHead>
|
||
<TableHead>说明</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{apiEndpoints.map((endpoint) => (
|
||
<TableRow key={endpoint.id}>
|
||
<TableCell>{endpoint.id}</TableCell>
|
||
<TableCell>{endpoint.device}</TableCell>
|
||
<TableCell>{endpoint.method}</TableCell>
|
||
<TableCell>{endpoint.url}</TableCell>
|
||
<TableCell>{endpoint.description}</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
</TabsContent>
|
||
<TabsContent value="parameters" className="p-6">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead className="w-[50px]">#</TableHead>
|
||
<TableHead>参数</TableHead>
|
||
<TableHead>值</TableHead>
|
||
<TableHead>说明</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{parameters.map((param) => (
|
||
<TableRow key={param.id}>
|
||
<TableCell>{param.id}</TableCell>
|
||
<TableCell>{param.param}</TableCell>
|
||
<TableCell>{param.value}</TableCell>
|
||
<TableCell>{param.description}</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
</TabsContent>
|
||
<TabsContent value="returnData" className="p-6">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead className="w-[50px]">#</TableHead>
|
||
<TableHead>数据</TableHead>
|
||
<TableHead>说明</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{returnData.map((data) => (
|
||
<TableRow key={data.id}>
|
||
<TableCell>{data.id}</TableCell>
|
||
<TableCell>{data.data}</TableCell>
|
||
<TableCell>{data.description}</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
</TabsContent>
|
||
</Tabs>
|
||
</motion.div>
|
||
);
|
||
|
||
return (
|
||
<div className="flex flex-col min-h-screen bg-gray-100">
|
||
<div className="flex flex-1">
|
||
<nav className="w-64 bg-white shadow-md">
|
||
<div className="p-6">
|
||
<h1 className="text-2xl font-bold mb-6 text-gray-800">
|
||
Cat API 文档
|
||
</h1>
|
||
<ul className="space-y-2">
|
||
<li>
|
||
<Button
|
||
variant={activePage === "home" ? "default" : "ghost"}
|
||
className="w-full justify-start text-left"
|
||
onClick={() => setActivePage("home")}
|
||
>
|
||
首页
|
||
</Button>
|
||
</li>
|
||
<li>
|
||
<Button
|
||
variant={activePage === "random-image" ? "default" : "ghost"}
|
||
className="w-full justify-start text-left"
|
||
onClick={() => setActivePage("random-image")}
|
||
>
|
||
随机图片
|
||
</Button>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</nav>
|
||
<main className="flex-1 p-8 overflow-auto">
|
||
{activePage === "home" ? <HomePage /> : <RandomImagePage />}
|
||
</main>
|
||
</div>
|
||
<footer className="bg-white shadow-md mt-8">
|
||
<div className="container mx-auto px-6 py-4">
|
||
<div className="flex justify-between items-center">
|
||
<p className="text-gray-600">
|
||
© 2024 mei. All rights reserved.
|
||
</p>
|
||
<div className="flex items-center text-gray-500">
|
||
<GitCommit className="w-5 h-5 mr-2" />
|
||
<span className="text-sm">{process.env.NEXT_PUBLIC_BUILD_ID || 'dev'}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
);
|
||
}
|