import { dom } from "./dom.js"; export function setStatus(text) { dom.statusText.textContent = text; } // ── Toast ───────────────────────────────────────── function getContainer() { let el = document.getElementById("toast-container"); if (!el) { el = document.createElement("div"); el.id = "toast-container"; document.body.appendChild(el); } return el; } const ICONS = { error: "✕", warning: "!", success: "✓", info: "i" }; /** * 显示 toast 通知。 * @param {string} title 主要文字 * @param {object} [opts] * @param {string} [opts.message] 次要说明文字 * @param {"error"|"warning"|"success"|"info"} [opts.level="error"] * @param {number} [opts.duration=4000] 自动关闭毫秒数,0 表示不自动关闭 * @param {boolean} [opts.shake=false] 出现时加抖动动画 * @returns {{ dismiss: () => void }} */ export function showToast(title, { message, level = "error", duration = 4000, shake = false } = {}) { const container = getContainer(); const el = document.createElement("div"); el.className = `toast ${level}${shake ? " shake" : ""}`; el.innerHTML = ` ${ICONS[level] ?? "i"}
${title}
${message ? `
${message}
` : ""}
`; const dismiss = () => { if (!el.parentNode) return; el.classList.add("hiding"); el.addEventListener("animationend", () => el.remove(), { once: true }); }; el.addEventListener("click", dismiss); container.appendChild(el); if (duration > 0) setTimeout(dismiss, duration); return { dismiss }; } // ── apiFetch ────────────────────────────────────── export async function apiFetch(url, options = {}) { const response = await fetch(url, { headers: { "Content-Type": "application/json" }, ...options, }); if (!response.ok) { const text = (await response.text()) || response.statusText; showToast(`请求失败 ${response.status}`, { message: text }); throw new Error(text); } if (response.status === 204) { return null; } const contentType = response.headers.get("content-type") || ""; if (contentType.includes("application/json")) { return response.json(); } return response.text(); } export function withStatus(task) { return task.catch((error) => { setStatus(error.message || "请求失败"); }); }