Initial commit: 心理咨询预约系统

PHP 前后端一体:首页咨询师列表、预约表单、管理后台、二维码准入与邮件通知;敏感配置与提交数据通过 .gitignore 排除。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
litianming
2026-06-04 10:17:09 +08:00
commit 08ce60344d
49 changed files with 3490 additions and 0 deletions

85
js/api.js Normal file
View File

@@ -0,0 +1,85 @@
/** 基于当前页面 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(/\/$/, "");
})();