/** 基于当前页面 URL 解析 api 路径,适配根目录或子目录部署 */ function apiUrl(endpoint) { return new URL(`api/${endpoint}`, window.location.href).href; } /** 静态资源追加版本号,避免手机浏览器缓存旧 CSS/JS/图片 */ function assetUrl(path) { if (!path || /^https?:\/\//i.test(path)) return path; const v = window.__ASSET_V__ || ""; if (!v) return path; const sep = path.includes("?") ? "&" : "?"; return `${path}${sep}v=${encodeURIComponent(v)}`; } async function parseResponse(res) { const text = await res.text(); if (!text) { return {}; } try { return JSON.parse(text); } catch { const hint = res.status === 404 ? "接口不存在,请确认 api 目录已上传" : res.status >= 500 ? "服务器错误,请检查 PHP 与 data 目录写权限" : "服务器返回异常,请确认 PHP 已启用"; throw new Error(`${hint}(HTTP ${res.status})`); } } async function apiGet(endpoint, useCredentials = false) { const res = await fetch(apiUrl(endpoint), { credentials: useCredentials ? "include" : "same-origin", }); const data = await parseResponse(res); if (!res.ok) { throw new Error(data.message || `请求失败(HTTP ${res.status})`); } return data; } async function apiPost(endpoint, body, useCredentials = false, timeoutMs = 30000) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); let res; try { res = await fetch(apiUrl(endpoint), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), credentials: useCredentials ? "include" : "same-origin", signal: controller.signal, }); } catch (e) { clearTimeout(timer); if (e.name === "AbortError") { throw new Error("请求超时,数据可能已保存,请稍后刷新或联系管理员"); } throw new Error("网络请求失败,请检查网络或域名是否正确"); } clearTimeout(timer); const data = await parseResponse(res); if (!res.ok) { throw new Error(data.message || `提交失败(HTTP ${res.status})`); } return data; } function showToast(message, duration = 2500) { const existing = document.querySelector(".toast"); if (existing) existing.remove(); const el = document.createElement("div"); el.className = "toast"; el.textContent = message; document.body.appendChild(el); setTimeout(() => el.remove(), duration); } /** 供管理端下载使用 */ const API_BASE = (() => { const u = new URL("api/", window.location.href); return u.pathname.replace(/\/$/, ""); })();