// 聊天室双色球彩票面板,提供 lotteryPanel Alpine 组件和全局开关入口。 let lotteryPanelEventsBound = false; /** * 读取 CSRF Token。 * * @returns {string} */ function csrf() { return document.querySelector('meta[name="csrf-token"]')?.content || ""; } /** * 显示彩票面板内联消息。 * * @param {string} message * @param {boolean} success * @returns {void} */ export function showLotteryMsg(message, success) { const element = document.getElementById("lottery-buy-msg"); if (!element) { return; } element.style.background = success ? "#f0fdf4" : "#fff5f5"; element.style.border = success ? "1px solid #86efac" : "1px solid #fecaca"; element.style.color = success ? "#16a34a" : "#dc2626"; element.textContent = message; element.style.display = "block"; element.style.opacity = "1"; window.clearTimeout(element._t); element._t = window.setTimeout(() => { element.style.opacity = "0"; window.setTimeout(() => { element.style.display = "none"; }, 400); }, 3000); } /** * 创建双色球彩票面板 Alpine 组件。 * * @returns {object} */ export function lotteryPanel() { return { show: false, loading: true, ruleOpen: false, buying: false, issueNo: "--", status: "open", isOpen: false, isSuperIssue: false, poolAmount: 0, secondsLeft: 0, drawAt: null, drawRed1: null, drawRed2: null, drawRed3: null, drawBlue: null, selectedReds: [], selectedBlue: null, cart: [], myTickets: [], history: [], _timer: null, /** * 格式化倒计时文字。 * * @returns {string} */ get countdownText() { const seconds = this.secondsLeft; if (seconds <= 0) { return "即将开奖"; } if (seconds >= 3600) { return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`; } const minutes = Math.floor(seconds / 60); const restSeconds = seconds % 60; return `${String(minutes).padStart(2, "0")}:${String(restSeconds).padStart(2, "0")}`; }, /** * 打开面板并加载数据。 * * @returns {Promise} */ async open() { this.show = true; await this.loadData(); this.startTimer(); }, /** * 加载当期状态、我的购票和历史开奖。 * * @returns {Promise} */ async loadData() { this.loading = true; try { const [currentResponse, historyResponse] = await Promise.all([ fetch("/lottery/current", { headers: { Accept: "application/json", }, }), fetch("/lottery/history", { headers: { Accept: "application/json", }, }), ]); const current = await currentResponse.json(); const history = await historyResponse.json(); if (current.issue) { const issue = current.issue; this.issueNo = issue.issue_no; this.status = issue.status; this.isOpen = current.is_open; this.isSuperIssue = issue.is_super_issue; this.poolAmount = issue.pool_amount; this.secondsLeft = issue.seconds_left; this.drawRed1 = issue.red1; this.drawRed2 = issue.red2; this.drawRed3 = issue.red3; this.drawBlue = issue.blue; } this.myTickets = current.my_tickets ?? []; this.history = history.issues ?? []; } catch (error) { // 网络异常时保留现有面板数据,避免清空用户正在选择的号码。 } this.loading = false; }, /** * 启动开奖倒计时。 * * @returns {void} */ startTimer() { window.clearInterval(this._timer); this._timer = window.setInterval(() => { if (this.secondsLeft > 0) { this.secondsLeft -= 1; } }, 1000); }, /** * 切换红球选择状态。 * * @param {number} number * @returns {void} */ toggleRed(number) { if (this.selectedReds.includes(number)) { this.selectedReds = this.selectedReds.filter((red) => red !== number); return; } if (this.selectedReds.length < 3) { this.selectedReds = [...this.selectedReds, number]; } }, /** * 服务端机选号码并加入购物车。 * * @param {number} [count] * @returns {Promise} */ async doQuickPick(count = 1) { try { const response = await fetch(`/lottery/quick-pick?count=${count}`); const data = await response.json(); for (const number of data.numbers) { if (this.cart.length < 10) { this.cart.push({ reds: number.reds, blue: number.blue, quick: true, }); } } this.selectedReds = []; this.selectedBlue = null; } catch (error) { // 机选失败保持当前选号状态,用户仍可手动选择。 } }, /** * 将当前选号加入购物车。 * * @returns {void} */ addToCart() { if (this.selectedReds.length !== 3 || !this.selectedBlue) { showLotteryMsg("⚠️ 请选满 3 个红球和 1 个蓝球", false); return; } if (this.cart.length >= 10) { showLotteryMsg("⚠️ 单次最多加入 10 注", false); return; } this.cart.push({ reds: [...this.selectedReds].sort((a, b) => a - b), blue: this.selectedBlue, quick: false, }); this.selectedReds = []; this.selectedBlue = null; }, /** * 提交购物车并批量购票。 * * @returns {Promise} */ async submitCart() { if (this.cart.length === 0 || this.buying) { return; } this.buying = true; try { const response = await fetch("/lottery/buy", { method: "POST", headers: { "X-CSRF-TOKEN": csrf(), "Content-Type": "application/json", "Accept": "application/json", }, body: JSON.stringify({ numbers: this.cart.map((item) => ({ reds: item.reds, blue: item.blue, })), quick_pick: false, }), }); const data = await response.json(); if (response.ok && data.status === "success") { showLotteryMsg(`✅ ${data.message}`, true); this.cart = []; // 购票成功后同步全局金币,避免其他面板继续显示旧余额。 if (window.chatContext) { window.chatContext.userJjb = Math.max(0, (window.chatContext.userJjb ?? 0) - data.count * 100); } await this.loadData(); } else { showLotteryMsg(`❌ ${data.message || "购票失败"}`, false); } } catch (error) { showLotteryMsg("🌐 网络异常,请稍后重试", false); } finally { this.buying = false; } }, }; } /** * 打开彩票面板。 * * @returns {void} */ export function openLotteryPanel() { const panel = document.getElementById("lottery-panel"); if (panel && window.Alpine) { void window.Alpine.$data(panel)?.open?.(); } } /** * 关闭彩票面板并清理倒计时。 * * @returns {void} */ export function closeLotteryPanel() { const panel = document.getElementById("lottery-panel"); if (!panel || !window.Alpine) { return; } const data = window.Alpine.$data(panel); data.show = false; window.clearInterval(data._timer); } /** * 绑定彩票面板全局入口和关闭按钮事件。 * * @returns {void} */ export function bindLotteryPanelControls() { if (typeof window === "undefined") { return; } window.lotteryPanel = lotteryPanel; window.openLotteryPanel = openLotteryPanel; window.closeLotteryPanel = closeLotteryPanel; window.showLotteryMsg = showLotteryMsg; if (lotteryPanelEventsBound || typeof document === "undefined") { return; } lotteryPanelEventsBound = true; document.addEventListener("click", (event) => { if (!(event.target instanceof Element) || !event.target.closest("[data-lottery-panel-close]")) { return; } event.preventDefault(); closeLotteryPanel(); }); }