feat: new file

This commit is contained in:
Daniel
2026-03-18 18:57:58 +08:00
commit d0ff049899
31 changed files with 1507 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
import { NextRequest } from "next/server";
const BACKEND_URL = process.env.BACKEND_URL || "http://localhost:8000";
export async function GET(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
const { path } = await ctx.params;
const url = new URL(req.url);
const upstream = `${BACKEND_URL}/api/${path.join("/")}${url.search}`;
return await safeUpstreamFetch(() => fetch(upstream, { headers: forwardHeaders(req) }));
}
export async function POST(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
const { path } = await ctx.params;
const url = new URL(req.url);
const upstream = `${BACKEND_URL}/api/${path.join("/")}${url.search}`;
const body = await req.text();
return await safeUpstreamFetch(() =>
fetch(upstream, {
method: "POST",
headers: { ...forwardHeaders(req), "content-type": req.headers.get("content-type") || "application/json" },
body
})
);
}
function forwardHeaders(req: NextRequest) {
const h = new Headers();
const auth = req.headers.get("authorization");
if (auth) h.set("authorization", auth);
return h;
}
async function forwardResponse(r: Response) {
const headers = new Headers(r.headers);
headers.delete("access-control-allow-origin");
return new Response(await r.arrayBuffer(), { status: r.status, headers });
}
async function safeUpstreamFetch(doFetch: () => Promise<Response>) {
const maxAttempts = 3;
for (let i = 0; i < maxAttempts; i++) {
try {
const r = await doFetch();
return await forwardResponse(r);
} catch (e: any) {
const code = e?.cause?.code || e?.code;
const retryable = code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "EAI_AGAIN";
if (!retryable || i === maxAttempts - 1) {
return new Response(
JSON.stringify({
detail: "上游后端不可达backend 容器可能正在重启或未就绪)。",
error: String(code || e?.message || e)
}),
{ status: 503, headers: { "content-type": "application/json; charset=utf-8" } }
);
}
await new Promise((r) => setTimeout(r, 200 * (i + 1)));
}
}
return new Response(JSON.stringify({ detail: "unknown" }), { status: 503 });
}

15
frontend/app/layout.tsx Normal file
View File

@@ -0,0 +1,15 @@
export const metadata = {
title: "Crawl BI Dashboard",
description: "Sales BI + Trend Engine + AI insight"
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN">
<body style={{ margin: 0, fontFamily: "ui-sans-serif, system-ui, -apple-system" }}>
{children}
</body>
</html>
);
}

6
frontend/app/page.tsx Normal file
View File

@@ -0,0 +1,6 @@
import Dashboard from "../components/Dashboard";
export default function Page() {
return <Dashboard />;
}