fix: unify frontend API client - add blob support to fetchWithAuth, eliminate raw fetch calls

- Extend fetchWithAuth with responseType parameter ('json' | 'blob')
- reports.ts: PDF/CSV export now uses fetchWithAuth blob mode
- reports/page.tsx: remove duplicate API_BASE, use fetchWithAuth for CSV export
- lifecycle/new/page.tsx: replace raw fetch with fetchWithAuth for quick-start POST
This commit is contained in:
chiguyong 2026-06-01 20:54:12 +08:00
parent 792d9ebe53
commit f1a8b69c2a
4 changed files with 20 additions and 46 deletions

View File

@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { AlertCard } from "@/components/business/alert-card";
import { fetchWithAuth } from "@/lib/api/client";
import { Check } from "lucide-react";
interface ProjectResponse {
@ -57,39 +58,20 @@ export default function NewProjectPage() {
const token = session?.accessToken as string | undefined;
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"}/api/v1/lifecycle/projects/quick-start`,
const data = await fetchWithAuth(
"/api/v1/lifecycle/projects/quick-start",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token || ""}`,
},
body: JSON.stringify({
brand_name: brandName.trim(),
brand_url: brandUrl.trim(),
description: description.trim() || undefined,
}),
}
);
if (!response.ok) {
let errorMessage = "请求失败,请稍后重试";
try {
const errData = await response.json();
errorMessage = errData.detail || errData.message || errorMessage;
} catch {
// ignore parse error
}
throw new Error(errorMessage);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _data = (await response.json()) as {
project: ProjectResponse;
message: string;
};
},
token
) as { project: ProjectResponse; message: string };
void data;
animateSteps();
} catch (err) {
setSubmitting(false);

View File

@ -22,6 +22,7 @@ import {
TableRow,
} from "@/components/ui/table";
import { reportsApi } from "@/lib/api/reports";
import { fetchWithAuth } from "@/lib/api/client";
import type { QueryListResponse } from "@/lib/api/queries";
import type { CitationListResponse, CitationStats } from "@/lib/api/citations";
import { useApi } from "@/lib/hooks/use-api";
@ -39,8 +40,6 @@ import {
BarChart3,
} from "lucide-react";
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
export default function ReportsPage() {
const { data: session } = useSession();
const [selectedQuery, setSelectedQuery] = useState<string>("");
@ -87,15 +86,7 @@ export default function ReportsPage() {
if (format === "csv") {
const query = `?query_id=${queryId}`;
const url = `${API_BASE}/api/v1/reports/export/csv${query}`;
const res = await fetch(url, {
headers: { Authorization: `Bearer ${session.accessToken}` },
});
if (!res.ok) {
const errorData = await res.json().catch(() => ({ detail: "导出失败" }));
throw new Error(errorData.detail || `HTTP ${res.status}`);
}
blob = await res.blob();
blob = await fetchWithAuth(`/api/v1/reports/export/csv${query}`, {}, session.accessToken, "blob") as unknown as Blob;
filename = `report_${queryId}_${new Date().toISOString().split("T")[0]}.csv`;
} else {
blob = await reportsApi.exportPDF(session.accessToken, queryId);

View File

@ -7,12 +7,14 @@ export function getApiUrl(path: string): string {
return `${API_BASE}${path}`;
}
export type ResponseType = "json" | "blob";
export async function fetchWithAuth(
url: string,
options: RequestInit = {},
token?: string
token?: string,
responseType: ResponseType = "json"
) {
// 如果没有显式传入 token尝试从 NextAuth session 获取
let authToken = token;
if (!authToken && typeof window !== "undefined") {
try {
@ -33,7 +35,6 @@ export async function fetchWithAuth(
const res = await fetch(`${API_BASE}${url}`, { ...options, headers });
if (res.status === 401) {
// 不要自动跳转登录页,让页面组件/layout自行处理认证状态
throw new Error("登录已过期,请重新登录");
}
@ -52,5 +53,9 @@ export async function fetchWithAuth(
return null;
}
if (responseType === "blob") {
return res.blob();
}
return res.json();
}

View File

@ -1,16 +1,12 @@
import { API_BASE, fetchWithAuth } from "./client";
import { fetchWithAuth } from "./client";
export const reportsApi = {
exportCSV: (token: string, queryId?: string) => {
const query = queryId ? `?query_id=${queryId}` : "";
return fetchWithAuth(`/api/v1/reports/export/csv${query}`, {}, token);
return fetchWithAuth(`/api/v1/reports/export/csv${query}`, {}, token, "blob");
},
exportPDF: async (token: string, queryId?: string) => {
const query = queryId ? `?query_id=${queryId}` : "";
const res = await fetch(`${API_BASE}/api/v1/reports/export/pdf${query}`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error("导出失败");
return res.blob();
return fetchWithAuth(`/api/v1/reports/export/pdf${query}`, {}, token, "blob");
},
};