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:
parent
792d9ebe53
commit
f1a8b69c2a
|
|
@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { AlertCard } from "@/components/business/alert-card";
|
import { AlertCard } from "@/components/business/alert-card";
|
||||||
|
import { fetchWithAuth } from "@/lib/api/client";
|
||||||
import { Check } from "lucide-react";
|
import { Check } from "lucide-react";
|
||||||
|
|
||||||
interface ProjectResponse {
|
interface ProjectResponse {
|
||||||
|
|
@ -57,39 +58,20 @@ export default function NewProjectPage() {
|
||||||
const token = session?.accessToken as string | undefined;
|
const token = session?.accessToken as string | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const data = await fetchWithAuth(
|
||||||
`${process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"}/api/v1/lifecycle/projects/quick-start`,
|
"/api/v1/lifecycle/projects/quick-start",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token || ""}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
brand_name: brandName.trim(),
|
brand_name: brandName.trim(),
|
||||||
brand_url: brandUrl.trim(),
|
brand_url: brandUrl.trim(),
|
||||||
description: description.trim() || undefined,
|
description: description.trim() || undefined,
|
||||||
}),
|
}),
|
||||||
}
|
},
|
||||||
);
|
token
|
||||||
|
) as { project: ProjectResponse; message: string };
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
void data;
|
||||||
animateSteps();
|
animateSteps();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { reportsApi } from "@/lib/api/reports";
|
import { reportsApi } from "@/lib/api/reports";
|
||||||
|
import { fetchWithAuth } from "@/lib/api/client";
|
||||||
import type { QueryListResponse } from "@/lib/api/queries";
|
import type { QueryListResponse } from "@/lib/api/queries";
|
||||||
import type { CitationListResponse, CitationStats } from "@/lib/api/citations";
|
import type { CitationListResponse, CitationStats } from "@/lib/api/citations";
|
||||||
import { useApi } from "@/lib/hooks/use-api";
|
import { useApi } from "@/lib/hooks/use-api";
|
||||||
|
|
@ -39,8 +40,6 @@ import {
|
||||||
BarChart3,
|
BarChart3,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
|
|
||||||
|
|
||||||
export default function ReportsPage() {
|
export default function ReportsPage() {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const [selectedQuery, setSelectedQuery] = useState<string>("");
|
const [selectedQuery, setSelectedQuery] = useState<string>("");
|
||||||
|
|
@ -87,15 +86,7 @@ export default function ReportsPage() {
|
||||||
|
|
||||||
if (format === "csv") {
|
if (format === "csv") {
|
||||||
const query = `?query_id=${queryId}`;
|
const query = `?query_id=${queryId}`;
|
||||||
const url = `${API_BASE}/api/v1/reports/export/csv${query}`;
|
blob = await fetchWithAuth(`/api/v1/reports/export/csv${query}`, {}, session.accessToken, "blob") as unknown as Blob;
|
||||||
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();
|
|
||||||
filename = `report_${queryId}_${new Date().toISOString().split("T")[0]}.csv`;
|
filename = `report_${queryId}_${new Date().toISOString().split("T")[0]}.csv`;
|
||||||
} else {
|
} else {
|
||||||
blob = await reportsApi.exportPDF(session.accessToken, queryId);
|
blob = await reportsApi.exportPDF(session.accessToken, queryId);
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,14 @@ export function getApiUrl(path: string): string {
|
||||||
return `${API_BASE}${path}`;
|
return `${API_BASE}${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ResponseType = "json" | "blob";
|
||||||
|
|
||||||
export async function fetchWithAuth(
|
export async function fetchWithAuth(
|
||||||
url: string,
|
url: string,
|
||||||
options: RequestInit = {},
|
options: RequestInit = {},
|
||||||
token?: string
|
token?: string,
|
||||||
|
responseType: ResponseType = "json"
|
||||||
) {
|
) {
|
||||||
// 如果没有显式传入 token,尝试从 NextAuth session 获取
|
|
||||||
let authToken = token;
|
let authToken = token;
|
||||||
if (!authToken && typeof window !== "undefined") {
|
if (!authToken && typeof window !== "undefined") {
|
||||||
try {
|
try {
|
||||||
|
|
@ -33,7 +35,6 @@ export async function fetchWithAuth(
|
||||||
const res = await fetch(`${API_BASE}${url}`, { ...options, headers });
|
const res = await fetch(`${API_BASE}${url}`, { ...options, headers });
|
||||||
|
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
// 不要自动跳转登录页,让页面组件/layout自行处理认证状态
|
|
||||||
throw new Error("登录已过期,请重新登录");
|
throw new Error("登录已过期,请重新登录");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,5 +53,9 @@ export async function fetchWithAuth(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (responseType === "blob") {
|
||||||
|
return res.blob();
|
||||||
|
}
|
||||||
|
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,12 @@
|
||||||
import { API_BASE, fetchWithAuth } from "./client";
|
import { fetchWithAuth } from "./client";
|
||||||
|
|
||||||
export const reportsApi = {
|
export const reportsApi = {
|
||||||
exportCSV: (token: string, queryId?: string) => {
|
exportCSV: (token: string, queryId?: string) => {
|
||||||
const query = queryId ? `?query_id=${queryId}` : "";
|
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) => {
|
exportPDF: async (token: string, queryId?: string) => {
|
||||||
const query = queryId ? `?query_id=${queryId}` : "";
|
const query = queryId ? `?query_id=${queryId}` : "";
|
||||||
const res = await fetch(`${API_BASE}/api/v1/reports/export/pdf${query}`, {
|
return fetchWithAuth(`/api/v1/reports/export/pdf${query}`, {}, token, "blob");
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error("导出失败");
|
|
||||||
return res.blob();
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue