From c7b8ba956b3d6e34bf3d6b0a2efb7dcec457a839 Mon Sep 17 00:00:00 2001 From: lkddi Date: Sat, 25 Apr 2026 14:22:59 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E5=8F=8C=E8=89=B2=E7=90=83?= =?UTF-8?q?=E5=BD=A9=E7=A5=A8=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/js/chat-room.js | 13 + resources/js/chat-room/lottery-panel.js | 343 ++++++++++++++++++ .../partials/games/lottery-panel.blade.php | 262 +------------ 3 files changed, 357 insertions(+), 261 deletions(-) create mode 100644 resources/js/chat-room/lottery-panel.js diff --git a/resources/js/chat-room.js b/resources/js/chat-room.js index de86365..53c5c0a 100644 --- a/resources/js/chat-room.js +++ b/resources/js/chat-room.js @@ -15,6 +15,7 @@ * - friend-panel.js:处理好友面板、搜索和好友快捷操作。 * - friend-notifications.js:监听好友通知和通用 BannerNotification。 * - lightbox.js:处理聊天图片预览灯箱。 + * - lottery-panel.js:提供双色球彩票 lotteryPanel Alpine 组件和全局开关入口。 * - mobile-drawer.js:处理移动端抽屉、房间列表和在线名单。 * - marriage-status.js:处理婚姻状态展示与用户名片联动。 * - toolbar.js:处理工具栏按钮和功能快捷入口。 @@ -56,6 +57,7 @@ export { bindChatToast } from "./chat-room/toast.js"; export { bindFriendPanelControls, closeFriendPanel, friendSearch, loadFriends, openFriendPanel, quickFriendAction } from "./chat-room/friend-panel.js"; export { bindFriendNotificationControls, setupBannerNotification, setupFriendNotification, showFriendBanner } from "./chat-room/friend-notifications.js"; export { closeChatImageLightbox, initChatImageLightboxEvents, openChatImageLightbox } from "./chat-room/lightbox.js"; +export { bindLotteryPanelControls, closeLotteryPanel, lotteryPanel, openLotteryPanel, showLotteryMsg } from "./chat-room/lottery-panel.js"; export { bindMobileDrawerControls, closeMobileDrawer, @@ -148,6 +150,7 @@ import { bindChatToast } from "./chat-room/toast.js"; import { bindFriendPanelControls, closeFriendPanel, friendSearch, loadFriends, openFriendPanel, quickFriendAction } from "./chat-room/friend-panel.js"; import { bindFriendNotificationControls, setupBannerNotification, setupFriendNotification, showFriendBanner } from "./chat-room/friend-notifications.js"; import { closeChatImageLightbox, initChatImageLightboxEvents, openChatImageLightbox } from "./chat-room/lightbox.js"; +import { bindLotteryPanelControls, closeLotteryPanel, lotteryPanel, openLotteryPanel, showLotteryMsg } from "./chat-room/lottery-panel.js"; import { bindMobileDrawerControls, closeMobileDrawer, @@ -240,6 +243,11 @@ if (typeof window !== "undefined") { sendToChatBot, bindGlobalDialogControls, bindDailySignInControls, + bindLotteryPanelControls, + closeLotteryPanel, + lotteryPanel, + openLotteryPanel, + showLotteryMsg, applyFontSize, bindChatFontSizeControl, bindChatImageUploadControl, @@ -382,9 +390,13 @@ if (typeof window !== "undefined") { window.closeGameHall = closeGameHall; window.fetchBankRanking = fetchBankRanking; window.fortunePanel = fortunePanel; + window.closeLotteryPanel = closeLotteryPanel; window.deferChatGameBootstrap = deferChatGameBootstrap; + window.lotteryPanel = lotteryPanel; window.openGameHall = openGameHall; + window.openLotteryPanel = openLotteryPanel; window.openBankModal = openBankModal; + window.showLotteryMsg = showLotteryMsg; window.switchBankTab = switchBankTab; window.toggleBankRankSort = toggleBankRankSort; window.applyFontSize = applyFontSize; @@ -395,6 +407,7 @@ if (typeof window !== "undefined") { bindAppointmentAnnouncementControls(); bindGlobalDialogControls(); bindDailySignInControls(); + bindLotteryPanelControls(); bindChatFontSizeControl(); bindChatImageUploadControl(); bindChatComposerControls(); diff --git a/resources/js/chat-room/lottery-panel.js b/resources/js/chat-room/lottery-panel.js new file mode 100644 index 0000000..8a005d0 --- /dev/null +++ b/resources/js/chat-room/lottery-panel.js @@ -0,0 +1,343 @@ +// 聊天室双色球彩票面板,提供 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(); + }); +} diff --git a/resources/views/chat/partials/games/lottery-panel.blade.php b/resources/views/chat/partials/games/lottery-panel.blade.php index 0f76c69..d4bdd6d 100644 --- a/resources/views/chat/partials/games/lottery-panel.blade.php +++ b/resources/views/chat/partials/games/lottery-panel.blade.php @@ -406,264 +406,4 @@ - +{{-- 双色球彩票 Alpine 组件已迁移到 resources/js/chat-room/lottery-panel.js --}}