mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-07 23:51:17 +08:00
Compare commits
7 Commits
feature/us
...
feature/lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b09828bcc2 | ||
|
|
8751a7707c | ||
|
|
f91b240802 | ||
|
|
062b289f7a | ||
|
|
c1dc77f6db | ||
|
|
cea60175c2 | ||
|
|
2bd3630512 |
65
docs/components/api-overview.tsx
Normal file
65
docs/components/api-overview.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { getOpenAPIOperations } from "@/lib/openapi-operations";
|
||||
|
||||
const methodColors: Record<string, string> = {
|
||||
GET: "bg-emerald-100 text-emerald-700",
|
||||
POST: "bg-blue-100 text-blue-700",
|
||||
PUT: "bg-amber-100 text-amber-700",
|
||||
PATCH: "bg-purple-100 text-purple-700",
|
||||
DELETE: "bg-rose-100 text-rose-700",
|
||||
};
|
||||
|
||||
function MethodBadge({ method }: { method: string }) {
|
||||
const color = methodColors[method] ?? "bg-slate-100 text-slate-700";
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`font-semibold uppercase tracking-wide text-xs px-2 py-1 rounded ${color}`}
|
||||
>
|
||||
{method}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function APIOverviewTable() {
|
||||
const operations = getOpenAPIOperations();
|
||||
|
||||
if (operations.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="not-prose mt-6 overflow-x-auto">
|
||||
<table className="w-full border-separate border-spacing-y-2 text-sm">
|
||||
<thead className="text-left text-muted-foreground">
|
||||
<tr>
|
||||
<th className="px-3 py-2 font-medium">路径</th>
|
||||
<th className="px-3 py-2 font-medium">方法</th>
|
||||
<th className="px-3 py-2 font-medium">摘要</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{operations.map((operation) => (
|
||||
<tr
|
||||
key={`${operation.method}-${operation.route}`}
|
||||
className="bg-muted/30"
|
||||
>
|
||||
<td className="px-3 py-2 align-top font-mono">
|
||||
<Link className="hover:underline" href={operation.href}>
|
||||
{operation.route}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="px-3 py-2 align-top">
|
||||
<MethodBadge method={operation.method} />
|
||||
</td>
|
||||
<td className="px-3 py-2 align-top text-muted-foreground">
|
||||
{operation.summary || "—"}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,3 +2,11 @@
|
||||
title: API 概览
|
||||
description: Open API 接口文档
|
||||
---
|
||||
|
||||
import { APIOverviewTable } from "@/components/api-overview";
|
||||
|
||||
# 接口列表
|
||||
|
||||
以下列表聚合了所有已生成的接口页面,展示对应的路径、请求方法以及摘要,便于快速检索和跳转。
|
||||
|
||||
<APIOverviewTable />
|
||||
|
||||
68
docs/lib/openapi-operations.ts
Normal file
68
docs/lib/openapi-operations.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import matter from "gray-matter";
|
||||
|
||||
import { source } from "@/lib/source";
|
||||
|
||||
interface OperationFrontmatter {
|
||||
title?: string;
|
||||
description?: string;
|
||||
_openapi?: {
|
||||
method?: string;
|
||||
route?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface OpenAPIOperation {
|
||||
href: string;
|
||||
method: string;
|
||||
route: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
function parseFrontmatter(content: string): OperationFrontmatter {
|
||||
const result = matter(content);
|
||||
|
||||
return result.data as OperationFrontmatter;
|
||||
}
|
||||
|
||||
function normalizeSummary(frontmatter: OperationFrontmatter): string {
|
||||
return frontmatter.title ?? frontmatter.description ?? "";
|
||||
}
|
||||
|
||||
export function getOpenAPIOperations(): OpenAPIOperation[] {
|
||||
return source
|
||||
.getPages()
|
||||
.filter((page) =>
|
||||
page.url.startsWith("/openapi/") && page.url !== "/openapi"
|
||||
)
|
||||
.map((page) => {
|
||||
if (typeof page.data.content !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const frontmatter = parseFrontmatter(page.data.content);
|
||||
|
||||
const method = frontmatter._openapi?.method?.toUpperCase();
|
||||
const route = frontmatter._openapi?.route;
|
||||
const summary = normalizeSummary(frontmatter);
|
||||
|
||||
if (!method || !route) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
href: page.url,
|
||||
method,
|
||||
route,
|
||||
summary,
|
||||
} satisfies OpenAPIOperation;
|
||||
})
|
||||
.filter((operation): operation is OpenAPIOperation => Boolean(operation))
|
||||
.sort((a, b) => {
|
||||
const routeCompare = a.route.localeCompare(b.route);
|
||||
if (routeCompare !== 0) {
|
||||
return routeCompare;
|
||||
}
|
||||
|
||||
return a.method.localeCompare(b.method);
|
||||
});
|
||||
}
|
||||
@@ -1,10 +1,18 @@
|
||||
import { rmSync } from "node:fs";
|
||||
|
||||
import { generateFiles } from "fumadocs-openapi";
|
||||
import { openapi } from "@/lib/openapi";
|
||||
|
||||
const outputDir = "./content/docs/openapi/(generated)";
|
||||
|
||||
rmSync(outputDir, { recursive: true, force: true });
|
||||
|
||||
void generateFiles({
|
||||
input: openapi,
|
||||
output: "./content/docs/openapi/(generated)",
|
||||
output: outputDir,
|
||||
// we recommend to enable it
|
||||
// make sure your endpoint description doesn't break MDX syntax.
|
||||
includeDescription: true,
|
||||
per: "operation",
|
||||
groupBy: "route",
|
||||
});
|
||||
|
||||
@@ -121,6 +121,19 @@ body {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* .vditor {
|
||||
--textarea-background-color: transparent;
|
||||
border: none !important;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="timeline" :class="{ 'hover-enabled': hover }">
|
||||
<div class="timeline">
|
||||
<div class="timeline-item" v-for="(item, idx) in items" :key="idx">
|
||||
<div
|
||||
class="timeline-icon"
|
||||
@@ -26,7 +26,6 @@ export default {
|
||||
name: 'BaseTimeline',
|
||||
props: {
|
||||
items: { type: Array, default: () => [] },
|
||||
hover: { type: Boolean, default: false },
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -46,12 +45,6 @@ export default {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.hover-enabled .timeline-item:hover {
|
||||
background-color: var(--menu-selected-background-color);
|
||||
transition: background-color 0.2s;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.timeline-icon {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
发布评论
|
||||
<span class="shortcut-icon" v-if="!isMobile"> {{ isMac ? '⌘' : 'Ctrl' }} ⏎ </span>
|
||||
</template>
|
||||
<template v-else> <loading-four /> 发布中... </template>
|
||||
<template v-else> <loading-four class="loading-icon" /> 发布中... </template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
发送
|
||||
<span class="shortcut-icon" v-if="!isMobile"> {{ isMac ? '⌘' : 'Ctrl' }} ⏎ </span>
|
||||
</template>
|
||||
<template v-else> <loading-four /> 发送中... </template>
|
||||
<template v-else> <loading-four class="loading-icon" /> 发送中... </template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<div v-else class="login-page-button-primary disabled">
|
||||
<div class="login-page-button-text">
|
||||
<loading-four />
|
||||
<loading-four class="loading-icon" />
|
||||
登录中...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
>
|
||||
发布
|
||||
</div>
|
||||
<div v-else class="post-submit-loading"><loading-four /> 发布中...</div>
|
||||
<div v-else class="post-submit-loading loading-icon"><loading-four /> 发布中...</div>
|
||||
</div>
|
||||
</div>
|
||||
<LotteryForm v-if="postType === 'LOTTERY'" :data="lottery" />
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
>
|
||||
更新
|
||||
</div>
|
||||
<div v-else class="post-submit-loading"><loading-four /> 更新中...</div>
|
||||
<div v-else class="post-submit-loading loading-icon"><loading-four /> 更新中...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</div>
|
||||
<div v-else class="signup-page-button-primary disabled">
|
||||
<div class="signup-page-button-text">
|
||||
<loading-four />
|
||||
<loading-four class="loading-icon" />
|
||||
发送中...
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,7 +56,7 @@
|
||||
</div>
|
||||
<div v-else class="signup-page-button-primary disabled">
|
||||
<div class="signup-page-button-text">
|
||||
<loading-four />
|
||||
<loading-four class="loading-icon" />
|
||||
验证中...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user