Files
chatroom/resources/js/chat-room/lottery-panel.js
T
2026-04-25 14:22:59 +08:00

344 lines
9.8 KiB
JavaScript

// 聊天室双色球彩票面板,提供 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<void>}
*/
async open() {
this.show = true;
await this.loadData();
this.startTimer();
},
/**
* 加载当期状态、我的购票和历史开奖。
*
* @returns {Promise<void>}
*/
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<void>}
*/
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<void>}
*/
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();
});
}