feat: expand platform management, admin controls, and learning workflows

这个提交包含在:
Codex CLI
2026-02-15 15:41:56 +08:00
父节点 ad29a9f62d
当前提交 f209ae82da
修改 75 个文件,包含 9663 行新增794 行删除

查看文件

@@ -2,6 +2,12 @@ export const API_BASE =
process.env.NEXT_PUBLIC_API_BASE ??
(process.env.NODE_ENV === "development" ? "http://localhost:8080" : "/admin139");
function uiText(zhText: string, enText: string): string {
if (typeof window === "undefined") return enText;
const lang = window.localStorage.getItem("csp.ui.language");
return lang === "zh" ? zhText : enText;
}
type ApiEnvelope<T> =
| { ok: true; data?: T; [k: string]: unknown }
| { ok: false; error?: string; [k: string]: unknown };
@@ -17,11 +23,45 @@ export async function apiFetch<T>(
headers.set("Content-Type", "application/json");
}
const resp = await fetch(`${API_BASE}${path}`, {
...init,
headers,
cache: "no-store",
});
const method = (init?.method ?? "GET").toUpperCase();
const retryable = method === "GET" || method === "HEAD";
let resp: Response;
try {
resp = await fetch(`${API_BASE}${path}`, {
...init,
headers,
cache: "no-store",
});
} catch (err) {
if (!retryable) {
throw new Error(
uiText(
`网络请求失败,请检查后端服务或代理连接(${err instanceof Error ? err.message : String(err)}`,
`Network request failed. Please check backend/proxy connectivity (${err instanceof Error ? err.message : String(err)}).`
)
);
}
await new Promise((resolve) => setTimeout(resolve, 400));
try {
resp = await fetch(`${API_BASE}${path}`, {
...init,
headers,
cache: "no-store",
});
} catch (retryErr) {
throw new Error(
uiText(
`网络请求失败,请检查后端服务或代理连接(${
retryErr instanceof Error ? retryErr.message : String(retryErr)
}`,
`Network request failed. Please check backend/proxy connectivity (${
retryErr instanceof Error ? retryErr.message : String(retryErr)
}).`
)
);
}
}
const text = await resp.text();
let payload: unknown = null;