116 lines
4.3 KiB
JavaScript
116 lines
4.3 KiB
JavaScript
// 页内弹框原语:替代原生 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 ? `<label for="app-dialog-input">${escapeHtml(label)}</label>` : ""}
|
|
<input id="app-dialog-input" type="text" autocomplete="off" />`
|
|
: `<div class="msg">${escapeHtml(message)}</div>`;
|
|
|
|
wrap.innerHTML = `
|
|
<div class="card" role="dialog" aria-modal="true">
|
|
${title ? `<h3>${escapeHtml(title)}</h3>` : ""}
|
|
<div class="body">${bodyHtml}</div>
|
|
<div class="actions">
|
|
<button id="app-dialog-cancel">${escapeHtml(cancelText)}</button>
|
|
<button id="app-dialog-ok" class="primary${danger ? " danger" : ""}">${escapeHtml(okText)}</button>
|
|
</div>
|
|
</div>`;
|
|
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<boolean>
|
|
export function dialogConfirm(opts) {
|
|
return openDialog("confirm", typeof opts === "string" ? { message: opts } : opts);
|
|
}
|
|
|
|
// 输入框 → Promise<string|null>(取消返回 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; // 点一下立刻消
|
|
}
|