迁移后台全局弹窗脚本
This commit is contained in:
@@ -0,0 +1,211 @@
|
|||||||
|
// 后台全局 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();
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import './bootstrap';
|
import './bootstrap';
|
||||||
|
import './admin/admin-ui.js';
|
||||||
import { bindAdminAiProvidersControls } from './admin/ai-providers.js';
|
import { bindAdminAiProvidersControls } from './admin/ai-providers.js';
|
||||||
import { bindAdminAutoactControls } from './admin/autoact.js';
|
import { bindAdminAutoactControls } from './admin/autoact.js';
|
||||||
import { bindAdminFishingEventsControls } from './admin/fishing-events.js';
|
import { bindAdminFishingEventsControls } from './admin/fishing-events.js';
|
||||||
|
|||||||
@@ -295,180 +295,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* 后台全局 Toast 工厂。
|
|
||||||
*
|
|
||||||
* 负责统一渲染右上角成功/失败提示,并提供全局调用入口。
|
|
||||||
*
|
|
||||||
* @param initialToasts 初始提示列表
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
function createAdminToastStore(initialToasts = []) {
|
|
||||||
return {
|
|
||||||
toasts: [],
|
|
||||||
visibleIds: [],
|
|
||||||
nextToastId: Date.now(),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化 Toast 列表,并挂载全局调用方法。
|
|
||||||
*/
|
|
||||||
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 toast 待展示的提示对象
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
remove(toastId) {
|
|
||||||
this.visibleIds = this.visibleIds.filter((visibleId) => visibleId !== toastId);
|
|
||||||
|
|
||||||
window.setTimeout(() => {
|
|
||||||
this.toasts = this.toasts.filter((toast) => toast.id !== toastId);
|
|
||||||
}, 220);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 后台全局弹窗组件。
|
|
||||||
*
|
|
||||||
* 提供 alert / confirm 两种模式,替换原生 alert/confirm。
|
|
||||||
*/
|
|
||||||
window.adminDialog = (function() {
|
|
||||||
const overlay = document.getElementById('admin-dialog-overlay');
|
|
||||||
const box = document.getElementById('admin-dialog-box');
|
|
||||||
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');
|
|
||||||
|
|
||||||
/** 关闭弹窗 */
|
|
||||||
function close() {
|
|
||||||
overlay.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 点击遮罩层关闭 */
|
|
||||||
overlay.addEventListener('click', function(e) {
|
|
||||||
if (e.target === overlay) close();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建按钮元素
|
|
||||||
*
|
|
||||||
* @param {string} label 按钮文字
|
|
||||||
* @param {string} color 按钮背景色
|
|
||||||
* @param {Function} onClick 点击回调
|
|
||||||
*/
|
|
||||||
function makeBtn(label, color, onClick) {
|
|
||||||
const btn = document.createElement('button');
|
|
||||||
btn.textContent = label;
|
|
||||||
btn.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);`;
|
|
||||||
btn.onmouseover = () => btn.style.opacity = '.82';
|
|
||||||
btn.onmouseout = () => btn.style.opacity = '1';
|
|
||||||
btn.addEventListener('click', () => {
|
|
||||||
close();
|
|
||||||
if (onClick) onClick();
|
|
||||||
});
|
|
||||||
return btn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 弹出提示框(仅「确定」按钮)
|
|
||||||
*
|
|
||||||
* @param {string} message 消息内容(支持 HTML)
|
|
||||||
* @param {string} title 标题
|
|
||||||
* @param {string} icon 图标 Emoji
|
|
||||||
* @param {Function} onOk 确定回调
|
|
||||||
*/
|
|
||||||
function alert(message, title = '提示', icon = 'ℹ️', onOk = null) {
|
|
||||||
elIcon.textContent = icon;
|
|
||||||
elTitle.textContent = title;
|
|
||||||
elMsg.innerHTML = message;
|
|
||||||
elBtns.innerHTML = '';
|
|
||||||
elBtns.appendChild(makeBtn('确定', '#4f46e5', onOk));
|
|
||||||
overlay.style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 弹出确认框(「确定」+「取消」按钮)
|
|
||||||
*
|
|
||||||
* @param {string} message 消息内容
|
|
||||||
* @param {string} title 标题
|
|
||||||
* @param {Function} onConfirm 确认回调
|
|
||||||
* @param {string} icon 图标 Emoji
|
|
||||||
*/
|
|
||||||
function confirm(message, title = '确认操作', onConfirm = null, icon = '⚠️') {
|
|
||||||
elIcon.textContent = icon;
|
|
||||||
elTitle.textContent = title;
|
|
||||||
elMsg.innerHTML = message;
|
|
||||||
elBtns.innerHTML = '';
|
|
||||||
|
|
||||||
const confirmBtn = makeBtn('确定', '#4f46e5', onConfirm);
|
|
||||||
const cancelBtn = makeBtn('取消', '#94a3b8', null);
|
|
||||||
elBtns.appendChild(confirmBtn);
|
|
||||||
elBtns.appendChild(cancelBtn);
|
|
||||||
overlay.style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
alert,
|
|
||||||
confirm,
|
|
||||||
close
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user