212 lines
5.9 KiB
JavaScript
212 lines
5.9 KiB
JavaScript
// 后台全局 UI 能力,提供 Toast 状态工厂和 adminDialog 弹窗入口。
|
||
|
||
/**
|
||
* 创建后台 Toast 状态工厂。
|
||
*
|
||
* @param {Array<object>} 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();
|
||
}
|