// 页内弹框原语:替代原生 alert/confirm/prompt。分两类(对标 Element 的 dialog / message): // - dialogConfirm / dialogPrompt:需要交互(确认 / 输入)→ 遮罩 + 卡片 modal,复用 .modal/.card。 // - message:单向提示(成功 / 报错 / 信息)→ 顶部居中轻量 toast,自动消失。 // 复用改密码 modal 那套头/体/脚布局;单例容器 #app-dialog 每次调用重建内容,同一时刻只开一个。 import { $ } from "./dom.js"; import { escapeHtml } from "./format.js"; // ───── 交互式 modal(confirm / prompt)───── let _resolve = null; // 当前打开弹框的 resolve let _onKey = null; // 当前绑定的 keydown 处理器(关闭时解绑) function closeDialog(result) { const wrap = $("app-dialog"); wrap.classList.remove("show"); wrap.innerHTML = ""; if (_onKey) { document.removeEventListener("keydown", _onKey, true); _onKey = null; } const r = _resolve; _resolve = null; if (r) r(result); } // 内部通用打开:kind = "confirm" | "prompt" function openDialog(kind, opts) { const { title = "", message = "", label = "", value = "", placeholder = "", okText = "确认", cancelText = "取消", danger = false, } = opts || {}; // 若已有弹框未关(极少见:非阻塞下重复触发),先把旧的按取消收掉 if (_resolve) closeDialog(kind === "prompt" ? null : false); const wrap = $("app-dialog"); const isPrompt = kind === "prompt"; const bodyHtml = isPrompt ? `${label ? `` : ""} ` : `
${escapeHtml(message)}
`; wrap.innerHTML = ` `; wrap.classList.add("show"); const inp = isPrompt ? $("app-dialog-input") : null; if (inp) { inp.value = value; inp.placeholder = placeholder; } const ok = () => closeDialog(isPrompt ? (inp.value) : true); const cancel = () => closeDialog(isPrompt ? null : false); $("app-dialog-ok").onclick = ok; $("app-dialog-cancel").onclick = cancel; wrap.onclick = (e) => { if (e.target === wrap) cancel(); }; // 点遮罩 = 取消 _onKey = (e) => { if (e.key === "Escape") { e.stopPropagation(); cancel(); } else if (e.key === "Enter" && (!isPrompt || e.target === inp)) { e.preventDefault(); ok(); } }; document.addEventListener("keydown", _onKey, true); // 聚焦:输入框选中已有文字(方便重命名整段改),否则聚焦确认键 if (inp) { inp.focus(); inp.select(); } else $("app-dialog-ok").focus(); return new Promise((res) => { _resolve = res; }); } // 确认框 → Promise export function dialogConfirm(opts) { return openDialog("confirm", typeof opts === "string" ? { message: opts } : opts); } // 输入框 → Promise(取消返回 null;确认返回原始输入,调用方自行 trim) export function dialogPrompt(opts) { return openDialog("prompt", opts); } // 供 main.js Esc 栈查询 / 关闭(与其它 modal 统一) export function isDialogOpen() { return !!_resolve; } export function closeTopDialog() { if (_resolve) closeDialog(false); } // ───── 单向提示 toast(message)───── // type: "info" | "success" | "error";顶部居中,自动消失。 export function message(text, type = "info", duration = 3200) { let wrap = $("app-messages"); if (!wrap) { wrap = document.createElement("div"); wrap.id = "app-messages"; document.body.appendChild(wrap); } const el = document.createElement("div"); el.className = "app-msg " + type; el.textContent = text; wrap.appendChild(el); // 触发进入动画 requestAnimationFrame(() => el.classList.add("in")); const kill = () => { el.classList.remove("in"); el.addEventListener("transitionend", () => el.remove(), { once: true }); setTimeout(() => el.remove(), 400); // 兜底(transition 未触发时) }; setTimeout(kill, duration); el.onclick = kill; // 点一下立刻消 }