// 后台全局 UI 能力,提供 Toast 状态工厂和 adminDialog 弹窗入口。 /** * 创建后台 Toast 状态工厂。 * * @param {Array} initialToasts * @returns {object} */ function createAdminToastStore(initialToasts = []) { return { toasts: [], visibleIds: [], nextToastId: Date.now(), /** * 初始化 Toast 列表,并挂载全局调用方法。 * * @returns {void} */ boot() { initialToasts.forEach((toast) => this.push(toast)); window.adminToast = { show: (message, type = "success", title = null, duration = 4200) => this.push({ message, type, title, duration, }), success: (message, title = "操作成功", duration = 4200) => this.push({ message, type: "success", title, duration, }), error: (message, title = "操作失败", duration = 4200) => this.push({ message, type: "error", title, duration, }), }; }, /** * 追加一条 Toast,并在指定时长后自动关闭。 * * @param {object} toast * @returns {void} */ push(toast) { if (!toast?.message) { return; } const normalizedToast = { id: this.nextToastId++, type: toast.type === "error" ? "error" : "success", title: toast.title, message: toast.message, duration: Number(toast.duration) > 0 ? Number(toast.duration) : 4200, }; this.toasts.push(normalizedToast); this.visibleIds.push(normalizedToast.id); window.setTimeout(() => { this.remove(normalizedToast.id); }, normalizedToast.duration); }, /** * 关闭指定 Toast,并在退场动画后清理节点。 * * @param {number} toastId * @returns {void} */ remove(toastId) { this.visibleIds = this.visibleIds.filter((visibleId) => visibleId !== toastId); window.setTimeout(() => { this.toasts = this.toasts.filter((toast) => toast.id !== toastId); }, 220); }, }; } /** * 创建后台全局弹窗组件。 * * @returns {{alert: Function, confirm: Function, close: Function}|null} */ function createAdminDialog() { const overlay = document.getElementById("admin-dialog-overlay"); const elIcon = document.getElementById("admin-dialog-icon"); const elTitle = document.getElementById("admin-dialog-title"); const elMsg = document.getElementById("admin-dialog-msg"); const elBtns = document.getElementById("admin-dialog-btns"); if (!overlay || !elIcon || !elTitle || !elMsg || !elBtns) { return null; } /** * 关闭当前弹窗。 * * @returns {void} */ function close() { overlay.style.display = "none"; } overlay.addEventListener("click", (event) => { if (event.target === overlay) { close(); } }); /** * 创建弹窗按钮。 * * @param {string} label * @param {string} color * @param {Function|null} onClick * @returns {HTMLButtonElement} */ function makeButton(label, color, onClick) { const button = document.createElement("button"); button.textContent = label; button.style.cssText = `padding:9px 24px; border-radius:8px; border:none; cursor:pointer; font-size:14px; font-weight:700; color:#fff; background:${color}; transition:opacity .15s; box-shadow:0 3px 10px rgba(0,0,0,.12);`; button.addEventListener("mouseover", () => { button.style.opacity = ".82"; }); button.addEventListener("mouseout", () => { button.style.opacity = "1"; }); button.addEventListener("click", () => { close(); if (onClick) { onClick(); } }); return button; } /** * 弹出提示框。 * * @param {string} message * @param {string} title * @param {string} icon * @param {Function|null} onOk * @returns {void} */ function alert(message, title = "提示", icon = "ℹ️", onOk = null) { elIcon.textContent = icon; elTitle.textContent = title; elMsg.innerHTML = message; elBtns.innerHTML = ""; elBtns.appendChild(makeButton("确定", "#4f46e5", onOk)); overlay.style.display = "flex"; } /** * 弹出确认框。 * * @param {string} message * @param {string} title * @param {Function|null} onConfirm * @param {string} icon * @returns {void} */ function confirm(message, title = "确认操作", onConfirm = null, icon = "⚠️") { elIcon.textContent = icon; elTitle.textContent = title; elMsg.innerHTML = message; elBtns.innerHTML = ""; elBtns.appendChild(makeButton("确定", "#4f46e5", onConfirm)); elBtns.appendChild(makeButton("取消", "#94a3b8", null)); overlay.style.display = "flex"; } return { alert, confirm, close, }; } /** * DOM 就绪后初始化后台全局弹窗。 * * @returns {void} */ function bootAdminDialog() { const adminDialog = createAdminDialog(); if (adminDialog) { window.adminDialog = adminDialog; } } window.createAdminToastStore = createAdminToastStore; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", bootAdminDialog, { once: true }); } else { bootAdminDialog(); }