PHP 前后端一体:首页咨询师列表、预约表单、管理后台、二维码准入与邮件通知;敏感配置与提交数据通过 .gitignore 排除。 Co-authored-by: Cursor <cursoragent@cursor.com>
86 lines
2.6 KiB
JavaScript
86 lines
2.6 KiB
JavaScript
/** 基于当前页面 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(/\/$/, "");
|
||
})();
|