// 百乐加强买单活动管理弹层,替代 input-bar 中的内联管理脚本。 import { escapeHtml } from "./html.js"; let baccaratLossCoverAdminEventsBound = false; /** * 获取买单活动管理弹层元素。 * * @returns {HTMLElement|null} */ function getModal() { return document.getElementById("baccarat-loss-cover-admin-modal"); } /** * 获取 CSRF Token。 * * @returns {string} */ function csrf() { return document.querySelector('meta[name="csrf-token"]')?.content || ""; } /** * 将日期格式化为 datetime-local 输入框需要的值。 * * @param {Date} date 日期对象 * @returns {string} */ function formatDateInput(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); const hour = String(date.getHours()).padStart(2, "0"); const minute = String(date.getMinutes()).padStart(2, "0"); return `${year}-${month}-${day}T${hour}:${minute}`; } /** * 读取弹层 data 属性中由 Blade 注入的接口 URL。 * * @returns {{summaryUrl:string,storeUrl:string,closeUrlTemplate:string}|null} */ function resolveAdminUrls() { const modal = getModal(); if (!modal) { return null; } return { summaryUrl: modal.dataset.blcSummaryUrl || "", storeUrl: modal.dataset.blcStoreUrl || "", closeUrlTemplate: modal.dataset.blcCloseUrlTemplate || "", }; } /** * 生成关闭活动接口地址。 * * @param {number|string} eventId 活动 ID * @returns {string} */ function resolveCloseUrl(eventId) { const urls = resolveAdminUrls(); return urls?.closeUrlTemplate.replace("__EVENT__", encodeURIComponent(String(eventId))) || ""; } /** * 安全格式化服务端日期。 * * @param {string|null|undefined} value 日期字符串 * @returns {string} */ function formatServerDate(value) { const date = value ? new Date(value) : null; return date && !Number.isNaN(date.getTime()) ? date.toLocaleString("zh-CN") : ""; } /** * 加载并渲染当前买单活动状态。 * * @returns {Promise} */ export async function loadAdminCurrentLossCoverEvent() { const box = document.getElementById("blc-admin-current"); const urls = resolveAdminUrls(); if (!box || !urls?.summaryUrl) { return; } box.innerHTML = "正在加载当前活动…"; try { const response = await fetch(urls.summaryUrl, { headers: { Accept: "application/json", }, }); const data = await response.json(); const event = data.event; if (!event) { box.innerHTML = '
当前没有进行中、待开始或待领取的买单活动。
'; return; } // 待开始、进行中和待结算活动允许管理员手动结束,已结束/已关闭状态不再展示按钮。 const canClose = ["scheduled", "active", "settlement_pending"].includes(event.status); const closeButton = canClose ? `` : ""; // 服务端字段进入 innerHTML 前统一转义,避免活动标题或开启人污染 DOM。 box.innerHTML = `
${escapeHtml(String(event.title || ""))}
开启人:${escapeHtml(String(event.creator_username || ""))}
${escapeHtml(String(event.status_label || ""))}
活动时间:${escapeHtml(formatServerDate(event.starts_at))} - ${escapeHtml(formatServerDate(event.ends_at))}
最终已发补偿:${Number(event.total_claimed_amount || 0).toLocaleString()} 金币
${closeButton} `; } catch (error) { box.innerHTML = '
当前活动加载失败,请稍后再试。
'; } } /** * 打开管理弹层,并填入默认活动时间。 * * @returns {Promise} */ export async function openAdminBaccaratLossCoverModal() { const modal = getModal(); if (!modal) { return; } const now = new Date(); // 默认活动 30 分钟,领奖截止延后 24 小时,便于管理员打开后直接微调。 const end = new Date(now.getTime() + 30 * 60 * 1000); const claimDeadline = new Date(end.getTime() + 24 * 60 * 60 * 1000); document.getElementById("blc-admin-starts-at").value = formatDateInput(now); document.getElementById("blc-admin-ends-at").value = formatDateInput(end); document.getElementById("blc-admin-claim-deadline-at").value = formatDateInput(claimDeadline); modal.style.display = "flex"; await loadAdminCurrentLossCoverEvent(); } /** * 关闭管理弹层。 * * @returns {void} */ export function closeAdminBaccaratLossCoverModal() { const modal = getModal(); if (modal) { modal.style.display = "none"; } } /** * 提交创建买单活动请求。 * * @param {Event} event 表单提交事件 * @returns {Promise} */ export async function submitBaccaratLossCoverEvent(event) { event.preventDefault(); const urls = resolveAdminUrls(); if (!urls?.storeUrl) { return; } // 前端只收集表单字段;权限、时间范围和字段合法性仍以后端校验为准。 const payload = { title: document.getElementById("blc-admin-title")?.value || "", description: document.getElementById("blc-admin-description")?.value || "", starts_at: document.getElementById("blc-admin-starts-at")?.value || "", ends_at: document.getElementById("blc-admin-ends-at")?.value || "", claim_deadline_at: document.getElementById("blc-admin-claim-deadline-at")?.value || "", }; try { const response = await fetch(urls.storeUrl, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", "X-CSRF-TOKEN": csrf(), }, body: JSON.stringify(payload), }); const data = await response.json(); if (data.ok) { await window.chatDialog?.alert(data.message || "活动创建成功", "系统通知", "#16a34a"); await loadAdminCurrentLossCoverEvent(); return; } await window.chatDialog?.alert(data.message || "活动创建失败", "提示", "#f59e0b"); } catch (error) { await window.chatDialog?.alert("活动创建失败,请稍后重试。", "提示", "#dc2626"); } } /** * 关闭当前买单活动。 * * @param {number|string} eventId 活动 ID * @returns {Promise} */ export async function closeCurrentBaccaratLossCoverEvent(eventId) { const closeUrl = resolveCloseUrl(eventId); if (!closeUrl) { return; } try { const response = await fetch(closeUrl, { method: "POST", headers: { Accept: "application/json", "X-CSRF-TOKEN": csrf(), }, }); const data = await response.json(); await window.chatDialog?.alert(data.message || "活动状态已更新", "系统通知", "#16a34a"); await loadAdminCurrentLossCoverEvent(); } catch (error) { await window.chatDialog?.alert("活动关闭失败,请稍后重试。", "提示", "#dc2626"); } } /** * 绑定买单活动管理弹层事件。 * * @returns {void} */ export function bindBaccaratLossCoverAdminControls() { if (baccaratLossCoverAdminEventsBound || typeof document === "undefined") { return; } baccaratLossCoverAdminEventsBound = true; document.addEventListener("click", (event) => { if (!(event.target instanceof Element)) { return; } if (event.target.closest("[data-blc-admin-close]")) { event.preventDefault(); closeAdminBaccaratLossCoverModal(); return; } const closeCurrentButton = event.target.closest("[data-blc-close-current]"); if (closeCurrentButton) { event.preventDefault(); void closeCurrentBaccaratLossCoverEvent(closeCurrentButton.getAttribute("data-blc-close-current") || ""); return; } const modal = event.target.closest("#baccarat-loss-cover-admin-modal"); // 遮罩点击关闭只响应背景层,避免误关内容区。 if (modal && event.target === modal) { closeAdminBaccaratLossCoverModal(); } }); document.addEventListener("submit", (event) => { if (!(event.target instanceof HTMLFormElement) || !event.target.matches("[data-blc-admin-form]")) { return; } void submitBaccaratLossCoverEvent(event); }); }