Files
pk10/App/Views/Web/live.php
T
li 48cfe4be04 fix: 修复结算后余额不更新和流水缺失问题
1. 去掉PK10结算中is_virtual过滤,统一更新余额和写入transactions
   - PK10PeriodController.php (手动结算)
   - auto_period_task.php (自动结算)
2. live.php轮询回调增加余额同步显示
2026-02-25 19:09:20 +08:00

1833 lines
73 KiB
PHP
Executable File

<html lang="vi">
<?php
// Odds Lookup Helper
$getOdds = function($type, $target = 'all') use ($oddsList) {
if (!empty($oddsList)) {
foreach ($oddsList as $item) {
if ($item['type'] == $type && $item['target'] == $target) {
return (float)$item['odds'] == (int)$item['odds'] ? (int)$item['odds'] : $item['odds'];
}
}
}
// Fallbacks
if ($type == 'tai' || $type == 'xiu' || $type == 'chan' || $type == 'le' || $type == 'dice') return 0.97;
if ($type == 'combo') return 120;
if ($type == 'sum') {
if (in_array($target, [4, 17])) return 30;
if (in_array($target, [5, 16])) return 18;
if (in_array($target, [6, 15])) return 14;
if (in_array($target, [7, 14])) return 12;
if (in_array($target, [8, 13])) return 8;
return 6;
}
return 1;
};
?>
<head>
<title><?= htmlspecialchars($settings['site_title'] ?? '') ?></title>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="description" content="<?= htmlspecialchars($settings['site_description'] ?? '') ?>">
<meta name="keywords" content="<?= htmlspecialchars($settings['site_keywords'] ?? '') ?>">
<!-- CSS -->
<link rel="stylesheet" href="/Static/css/style.css">
<link rel="stylesheet" href="/Static/ckplayer/css/ckplayer.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<!-- Layui -->
<link rel="stylesheet" href="/Static/layui/css/layui.css">
<style>
/* 隐藏ckplayer控制条和所有操作元素 */
.ckplayer-ckplayer .ck-bar {
display: none !important;
}
.ckplayer-ckplayer .ck-center-play {
display: none !important;
}
.ckplayer-ckplayer .ck-controls {
display: none !important;
}
/* PC端终极还原 - 严格匹配原图结构 */
@media (min-width: 769px) {
html,
body {
height: 100%;
overflow: hidden;
margin: 0;
padding: 0;
background: #000;
}
.live-wrapper {
height: 111vh;
/* 配合 scale(0.9) */
width: 98%;
padding: 5px 10px;
box-sizing: border-box;
display: flex;
flex-direction: column;
max-width: 1900px;
margin: 0 auto;
/* 还原用户习惯的缩放感 */
transform: scale(0.9);
transform-origin: top center;
}
.live-topbar {
height: 50px;
flex: 0 0 auto;
margin-bottom: 10px;
}
/* 使用 Grid 强制复原两栏布局 */
.live-layout {
flex: 1;
display: grid !important;
/* 调整比例:左侧进一步缩小 (1.8),右侧进一步扩大 (3.2) */
grid-template-columns: 1.8fr 3.2fr !important;
/* 行高:第一行视频自适应,第二行路图自适应 */
grid-template-rows: auto 1fr !important;
gap: 15px !important;
overflow: hidden !important;
min-height: 0;
}
/* 1. 视频区域 -> 左上 (Row 1, Col 1) */
.live-left {
grid-column: 1 / 2 !important;
grid-row: 1 / 2 !important;
display: flex;
flex-direction: column;
overflow: hidden;
}
.live-stream {
width: 100%;
aspect-ratio: 16 / 9;
max-height: 60vh;
background: #000;
border-radius: 12px;
}
/* 2. 路图区域 -> 左下 (Row 2, Col 1)
这是关键!强行把 HTML 里排在最后的元素搬到左下角 */
.live-history {
grid-column: 1 / 2 !important;
grid-row: 2 / 3 !important;
margin-top: 0 !important;
background: rgba(255, 255, 255, 0.95) !important;
border-radius: 12px;
display: flex !important;
flex-direction: row !important;
overflow: hidden;
min-height: 200px;
padding: 0 !important;
}
.live-roadmap-v2 {
flex: 1 1 auto !important;
width: auto !important;
min-width: 0 !important;
overflow-x: auto !important;
overflow-y: hidden !important;
padding: 5px;
border-right: 1px solid rgba(0, 0, 0, 0.1);
display: block !important;
}
/* 还原Mini Table - 显示在路图右侧 */
.live-mini-table-v2 {
display: flex !important;
flex: 0 0 320px !important;
width: 320px !important;
border-radius: 0 !important;
margin: 0 !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
}
.live-mini-table-v2::before {
display: none !important;
}
/* 4. 投注格子 PC端重排序 (蛇形/特定顺序) */
.bet-grid-v2 {
display: grid !important;
grid-template-columns: repeat(8, 1fr) !important;
gap: 6px !important;
}
/* 正常顺序 4-10 */
.bet-grid-v2>button:nth-child(1) {
order: 1;
}
/* 4 */
.bet-grid-v2>button:nth-child(2) {
order: 2;
}
/* 5 */
.bet-grid-v2>button:nth-child(3) {
order: 3;
}
/* 6 */
.bet-grid-v2>button:nth-child(4) {
order: 4;
}
/* 7 */
.bet-grid-v2>button:nth-child(5) {
order: 5;
}
/* 8 */
.bet-grid-v2>button:nth-child(6) {
order: 6;
}
/* 9 */
.bet-grid-v2>button:nth-child(7) {
order: 7;
}
/* 10 */
/* CHẴN (HTML第12个) -> 第一行末尾 (Order 8) */
.bet-grid-v2>button:nth-child(12) {
order: 8;
}
/* 下面一行倒序 17-11 */
.bet-grid-v2>button:nth-child(15) {
order: 9;
}
/* 17 */
.bet-grid-v2>button:nth-child(14) {
order: 10;
}
/* 16 */
.bet-grid-v2>button:nth-child(13) {
order: 11;
}
/* 15 */
.bet-grid-v2>button:nth-child(11) {
order: 12;
}
/* 14 */
.bet-grid-v2>button:nth-child(10) {
order: 13;
}
/* 13 */
.bet-grid-v2>button:nth-child(9) {
order: 14;
}
/* 12 */
.bet-grid-v2>button:nth-child(8) {
order: 15;
}
/* 11 */
/* LẺ (HTML第16个) -> 第二行末尾 (Order 16) */
.bet-grid-v2>button:nth-child(16) {
order: 16;
}
/* 3. 投注区域 -> 右侧全高 (Row 1-3, Col 2) */
.bet-panel-v2 {
position: relative; /* 必需:让 betOverlay 相对于此容器定位 */
grid-column: 2 / 3 !important;
grid-row: 1 / 3 !important;
/* 跨越两行,独占右侧 */
height: 100% !important;
overflow: hidden !important; /* 改为 hidden,让内层容器处理滚动 */
display: flex;
flex-direction: column;
padding: 0 !important; /* 移除 padding,由内层容器控制 */
background: rgba(255, 255, 255, 0.98) !important;
border-radius: 12px;
margin: 0 !important;
}
/* 内层滚动容器 */
.bet-panel-v2 > *:not(#betOverlay):not(#activeCountdown) {
padding: 0 14px; /* 应用原来的 padding */
}
.bet-panel-content-wrapper {
flex: 1;
overflow-y: auto;
padding: 10px 14px;
}
.bet-top {
display: grid !important;
/* 缩小侧边栏宽度 140 -> 110 */
grid-template-columns: 110px 1fr 110px !important;
gap: 8px !important;
margin-bottom: 10px !important;
min-height: 100px;
}
/* 缩小中间的骰子历史记录骰子大小 */
.bet-dice-combo .bet-dice-cell {
width: 24px !important;
height: 24px !important;
}
.bet-dice-combo .bet-dice-cell img {
width: 20px !important;
height: 20px !important;
}
/* 缩小下注格子 (4-17) */
.bet-cell {
min-height: 44px !important;
padding: 4px !important;
font-size: 14px !important;
}
.bet-odds-mini {
font-size: 10px !important;
}
/* 筹码区域 - 弹性换行,避免太小 */
.bet-chips-v2 {
display: flex !important;
flex-wrap: wrap !important;
justify-content: center !important;
gap: 6px !important;
margin-top: 10px !important;
padding-bottom: 5px;
}
/* 缩小筹码按钮 60 -> 48 */
.bet-chip-btn {
width: 48px !important;
height: 48px !important;
flex: 0 0 auto;
}
.bet-chip-display {
width: 44px !important;
height: 44px !important;
font-size: 11px !important;
border-width: 2px !important;
}
/* 滚动条 */
.bet-panel-v2::-webkit-scrollbar {
width: 6px;
}
.bet-panel-v2::-webkit-scrollbar-thumb {
background: #bbb;
border-radius: 3px;
}
}
/* 移动端筹码 - Grid 2行4列布局 */
@media (max-width: 768px) {
.bet-chips,
.bet-chips-v2 {
display: grid !important;
grid-template-columns: repeat(4, 1fr) !important;
grid-template-rows: repeat(2, 1fr) !important;
gap: 6px !important;
padding: 8px !important;
margin-top: 8px !important;
justify-items: center !important;
align-items: center !important;
}
.bet-chip-btn {
width: 100% !important;
display: flex !important;
justify-content: center !important;
align-items: center !important;
}
.bet-chip-display {
width: 44px !important;
height: 44px !important;
font-size: 10px !important;
border-width: 2px !important;
}
.bet-chip-display i {
font-size: 14px !important;
}
}
/* Betting Overlay - 状态遮罩层 */
.bet-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.85));
display: flex !important; /* 强制显示 */
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 9999 !important; /* 提高到最高层级 */
border-radius: 12px;
color: white;
font-weight: bold;
pointer-events: auto; /* Block clicks */
backdrop-filter: blur(4px);
transition: opacity 0.3s ease;
}
.bet-overlay.hidden {
display: none !important; /* 强制隐藏 */
pointer-events: none;
opacity: 0;
visibility: hidden;
}
.countdown-number {
font-size: 72px;
color: #ffeb3b;
font-family: 'Arial Black', monospace;
font-weight: 900;
text-shadow:
0 0 10px rgba(255, 235, 59, 0.8),
0 0 20px rgba(255, 235, 59, 0.6),
0 4px 8px rgba(0, 0, 0, 0.5);
animation: pulse 1s infinite;
}
.status-text {
font-size: 28px;
margin-top: 20px;
text-transform: uppercase;
letter-spacing: 3px;
text-shadow:
0 2px 4px rgba(0, 0, 0, 0.8),
0 0 10px rgba(255, 255, 255, 0.3);
padding: 12px 24px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
border: 2px solid rgba(255, 255, 255, 0.2);
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
/* 通用隐藏类 */
.hidden {
display: none !important;
visibility: hidden !important;
}
</style>
<!-- Favicon -->
<link rel="shortcut icon" href="<?= htmlspecialchars($settings['site_favicon'] ?? '') ?>" type="image/x-icon">
<!-- Web App Manifest -->
<!-- iOS PWA Support -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="TM68">
<link rel="apple-touch-icon" href="/Static/css/favicon-192x192.png">
</head>
<body class="live-body" style="background-color: black;">
<img id="bg-img" src="/Static/tm68/background.png" alt="Background">
<div class="live-wrapper">
<div class="live-topbar">
<div class="live-topbar-left">
<button class="live-circle-btn" type="button" aria-label="Back">
<i class="fa-solid fa-arrow-left"></i>
</button>
<div class="live-balance">
<div class="live-balance-coin">
<i class="fa-solid fa-dollar-sign"></i>
</div>
<div class="live-balance-text">
<div class="live-balance-id">ID: <?= htmlspecialchars($user['username'] ?? '') ?></div>
<div class="live-balance-amount"><?= number_format(($user['balance'] ?? 0) / 1000, 0, ',', '.') ?>k</div>
</div>
<button class="live-circle-btn live-refresh" type="button" aria-label="Refresh">
<i class="fa-solid fa-rotate-right"></i>
</button>
</div>
<button class="live-pill-btn" type="button">NẠP RÚT</button>
</div>
<div class="live-topbar-right">
<button class="lobby-icon-btn" type="button" aria-label="Account">
<i class="fa-solid fa-user"></i>
</button>
<button class="lobby-icon-btn" type="button" aria-label="Home">
<i class="fa-solid fa-house"></i>
</button>
<button class="lobby-icon-btn" type="button" id="soundToggle" aria-label="Sound">
<i class="fa-solid fa-volume-xmark"></i>
</button>
<button class="lobby-icon-btn" type="button" id="headerMenuToggle" aria-label="Menu">
<i class="fa-solid fa-bars"></i>
</button>
<div class="header-menu" id="headerMenu">
<button class="header-menu-item" type="button">
<span class="header-menu-icon"><i class="fa-solid fa-lock"></i></span>
<span>Đổi mật khẩu</span>
</button>
<button class="header-menu-item" type="button">
<span class="header-menu-icon"><i class="fa-solid fa-clock-rotate-left"></i></span>
<span>Lịch sử</span>
</button>
<button class="header-menu-item" type="button">
<span class="header-menu-icon"><i class="fa-solid fa-book-open"></i></span>
<span>Luật chơi</span>
</button>
<a href="/logout" class="header-menu-item" style="text-decoration: none;">
<span class="header-menu-icon"><i class="fa-solid fa-right-from-bracket"></i></span>
<span>Đăng xuất</span>
</a>
</div>
</div>
</div>
<div class="live-layout live-layout-v2">
<!-- 左侧播放器 -->
<div class="live-left">
<?php if ($hasStreamUrl ?? false): ?>
<div class="live-panel live-stream live-stream-v2">
<div class="live-stream-main">
<div id="tm68LivePlayerLeft" class="live-player" data-stream="<?= htmlspecialchars($streamUrl ?? '') ?>">
</div>
<!-- 左上角Logo -->
<?php if (!empty($settings['site_logo'] ?? '')): ?>
<div class="live-player-logo">
<img src="<?= htmlspecialchars($settings['site_logo']) ?>" alt="Logo">
</div>
<?php endif; ?>
<!-- 右下角期号和日期 -->
<div class="live-player-info">
<div class="live-player-period">G1202512200001</div>
<div class="live-player-datetime">
<span class="live-player-date">20/12/2025</span>
<span class="live-player-time">17:20:17</span>
</div>
</div>
</div>
</div>
<?php else: ?>
<div class="live-panel live-stream live-stream-v2" style="display: none;">
</div>
<?php endif; ?>
</div>
<?php if ($gameType === 'xocdia'): ?>
<!-- Xóc Đĩa 投注面板 -->
<?php include __DIR__ . '/components/xocdia-bet-panel.php'; ?>
<?php else: ?>
<!-- 骰子游戏投注面板(默认) -->
<div class="live-panel bet-panel bet-panel-v2" <?= ($hasStreamUrl ?? false) ? '' : 'style="pointer-events: none; opacity: 0.5;"' ?>>
<!-- Status Overlay -->
<div id="betOverlay" class="bet-overlay">
<div id="countdownDisplay" class="countdown-number" style="display: none;"></div>
<div id="statusDisplay" class="status-text">Đang tải...</div>
</div>
<!-- Active Countdown (visible when betting is open) -->
<div id="activeCountdown" class="hidden" style="position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,0.5); color: #ffeb3b; padding: 5px 10px; border-radius: 5px; font-weight: bold; font-family: monospace; z-index: 40;">
<i class="fa-regular fa-clock"></i> <span id="activeTimer">60</span>s
</div>
<div class="bet-top">
<div class="bet-side bet-xiu" data-bet-type="xiu" data-odds="1:<?= $getOdds('xiu') ?>">
<div class="bet-side-decoration bet-side-decoration-top-left"></div>
<div class="bet-side-decoration bet-side-decoration-top-right"></div>
<div class="bet-side-decoration bet-side-decoration-bottom-left"></div>
<div class="bet-side-decoration bet-side-decoration-bottom-right"></div>
<div class="bet-side-title">XỈU</div>
<div class="bet-side-odds">1:<?= $getOdds('xiu') ?></div>
</div>
<div class="bet-dice-history">
<div class="bet-dice-combo" data-bet-type="combo" data-combo-value="3" data-odds="1:<?= $getOdds('combo', 'specific_triple') ?>">
<div class="bet-dice-combo-dices">
<div class="bet-dice-cell"><img src="/Static/tm68/1.png" alt=""></div>
<div class="bet-dice-cell"><img src="/Static/tm68/1.png" alt=""></div>
<div class="bet-dice-cell"><img src="/Static/tm68/1.png" alt=""></div>
</div>
<div class="bet-dice-combo-odds">1:<?= $getOdds('combo', 'specific_triple') ?></div>
</div>
<div class="bet-dice-combo" data-bet-type="combo" data-combo-value="18" data-odds="1:<?= $getOdds('combo', 'specific_triple') ?>">
<div class="bet-dice-combo-dices">
<div class="bet-dice-cell"><img src="/Static/tm68/6.png" alt=""></div>
<div class="bet-dice-cell"><img src="/Static/tm68/6.png" alt=""></div>
<div class="bet-dice-cell"><img src="/Static/tm68/6.png" alt=""></div>
</div>
<div class="bet-dice-combo-odds">1:<?= $getOdds('combo', 'specific_triple') ?></div>
</div>
<div class="bet-dice-combo" data-bet-type="combo" data-combo-value="6" data-odds="1:<?= $getOdds('combo', 'specific_triple') ?>">
<div class="bet-dice-combo-dices">
<div class="bet-dice-cell"><img src="/Static/tm68/2.png" alt=""></div>
<div class="bet-dice-cell"><img src="/Static/tm68/2.png" alt=""></div>
<div class="bet-dice-cell"><img src="/Static/tm68/2.png" alt=""></div>
</div>
<div class="bet-dice-combo-odds">1:<?= $getOdds('combo', 'specific_triple') ?></div>
</div>
<div class="bet-dice-combo" data-bet-type="combo" data-combo-value="15" data-odds="1:<?= $getOdds('combo', 'specific_triple') ?>">
<div class="bet-dice-combo-dices">
<div class="bet-dice-cell"><img src="/Static/tm68/5.png" alt=""></div>
<div class="bet-dice-cell"><img src="/Static/tm68/5.png" alt=""></div>
<div class="bet-dice-cell"><img src="/Static/tm68/5.png" alt=""></div>
</div>
<div class="bet-dice-combo-odds">1:<?= $getOdds('combo', 'specific_triple') ?></div>
</div>
<div class="bet-dice-combo" data-bet-type="combo" data-combo-value="3" data-odds="1:<?= $getOdds('combo', 'specific_triple') ?>">
<div class="bet-dice-combo-dices">
<div class="bet-dice-cell "><img src="/Static/tm68/3.png" alt=""></div>
<div class="bet-dice-cell "><img src="/Static/tm68/3.png" alt=""></div>
<div class="bet-dice-cell "><img src="/Static/tm68/3.png" alt=""></div>
</div>
<div class="bet-dice-combo-odds">1:<?= $getOdds('combo', 'specific_triple') ?></div>
</div>
<div class="bet-dice-combo" data-bet-type="combo" data-combo-value="12" data-odds="1:<?= $getOdds('combo', 'specific_triple') ?>">
<div class="bet-dice-combo-dices">
<div class="bet-dice-cell "><img src="/Static/tm68/4.png" alt=""></div>
<div class="bet-dice-cell "><img src="/Static/tm68/4.png" alt=""></div>
<div class="bet-dice-cell "><img src="/Static/tm68/4.png" alt=""></div>
</div>
<div class="bet-dice-combo-odds">1:<?= $getOdds('combo', 'specific_triple') ?></div>
</div>
</div>
<div class="bet-side bet-tai" data-bet-type="tai" data-odds="1:<?= $getOdds('tai') ?>">
<div class="bet-side-decoration bet-side-decoration-top-left"></div>
<div class="bet-side-decoration bet-side-decoration-top-right"></div>
<div class="bet-side-decoration bet-side-decoration-bottom-left"></div>
<div class="bet-side-decoration bet-side-decoration-bottom-right"></div>
<div class="bet-side-title">TÀI</div>
<div class="bet-side-odds">1:<?= $getOdds('tai') ?></div>
</div>
</div>
<div class="bet-grid bet-grid-v2">
<button class="bet-cell bet-cell-white" type="button" data-bet-number="4" data-odds="1:<?= $getOdds('sum', '4') ?>">4<div
class="bet-odds-mini">1:<?= $getOdds('sum', '4') ?></div></button>
<button class="bet-cell bet-cell-white" type="button" data-bet-number="5" data-odds="1:<?= $getOdds('sum', '5') ?>">5<div
class="bet-odds-mini">1:<?= $getOdds('sum', '5') ?></div></button>
<button class="bet-cell bet-cell-white" type="button" data-bet-number="6" data-odds="1:<?= $getOdds('sum', '6') ?>">6<div
class="bet-odds-mini">1:<?= $getOdds('sum', '6') ?></div></button>
<button class="bet-cell bet-cell-white" type="button" data-bet-number="7" data-odds="1:<?= $getOdds('sum', '7') ?>">7<div
class="bet-odds-mini">1:<?= $getOdds('sum', '7') ?></div></button>
<button class="bet-cell bet-cell-white" type="button" data-bet-number="8" data-odds="1:<?= $getOdds('sum', '8') ?>">8<div
class="bet-odds-mini">1:<?= $getOdds('sum', '8') ?></div></button>
<button class="bet-cell bet-cell-white" type="button" data-bet-number="9" data-odds="1:<?= $getOdds('sum', '9') ?>">9<div
class="bet-odds-mini">1:<?= $getOdds('sum', '9') ?></div></button>
<button class="bet-cell bet-cell-white" type="button" data-bet-number="10" data-odds="1:<?= $getOdds('sum', '10') ?>">10<div
class="bet-odds-mini">1:<?= $getOdds('sum', '10') ?></div></button>
<button class="bet-cell bet-cell-green" type="button" data-bet-number="11" data-odds="1:<?= $getOdds('sum', '11') ?>">11<div
class="bet-odds-mini">1:<?= $getOdds('sum', '11') ?></div></button>
<button class="bet-cell bet-cell-green" type="button" data-bet-number="12" data-odds="1:<?= $getOdds('sum', '12') ?>">12<div
class="bet-odds-mini">1:<?= $getOdds('sum', '12') ?></div></button>
<button class="bet-cell bet-cell-red" type="button" data-bet-number="13" data-odds="1:<?= $getOdds('sum', '13') ?>">13<div
class="bet-odds-mini">1:<?= $getOdds('sum', '13') ?></div></button>
<button class="bet-cell bet-cell-red" type="button" data-bet-number="14" data-odds="1:<?= $getOdds('sum', '14') ?>">14<div
class="bet-odds-mini">1:<?= $getOdds('sum', '14') ?></div></button>
<button class="bet-cell bet-cell-outline-red" type="button" data-bet-type="chan" data-odds="1:<?= $getOdds('chan') ?>">CHẴN<div
class="bet-odds-mini">1:<?= $getOdds('chan') ?></div></button>
<button class="bet-cell bet-cell-red" type="button" data-bet-number="15" data-odds="1:<?= $getOdds('sum', '15') ?>">15<div
class="bet-odds-mini">1:<?= $getOdds('sum', '15') ?></div></button>
<button class="bet-cell bet-cell-red" type="button" data-bet-number="16" data-odds="1:<?= $getOdds('sum', '16') ?>">16<div
class="bet-odds-mini">1:<?= $getOdds('sum', '16') ?></div></button>
<button class="bet-cell bet-cell-red" type="button" data-bet-number="17" data-odds="1:<?= $getOdds('sum', '17') ?>">17<div
class="bet-odds-mini">1:<?= $getOdds('sum', '17') ?></div></button>
<button class="bet-cell bet-cell-outline-green" type="button" data-bet-type="le" data-odds="1:<?= $getOdds('le') ?>">LẺ<div
class="bet-odds-mini">1:<?= $getOdds('le') ?></div></button>
</div>
<div class="bet-dice-row-v2">
<button class="bet-dice-single" type="button" data-dice="1" data-odds="1:<?= $getOdds('dice', '1') ?>">
<div class="bet-dice-cell bet-dice-cell-gray "><img src="/Static/tm68/1.png" alt="1"></div>
<div class="bet-dice-single-odds">1:<?= $getOdds('dice', '1') ?></div>
</button>
<button class="bet-dice-single" type="button" data-dice="2" data-odds="1:<?= $getOdds('dice', '2') ?>">
<div class="bet-dice-cell bet-dice-cell-gray"><img src="/Static/tm68/2.png" alt="2"></div>
<div class="bet-dice-single-odds">1:<?= $getOdds('dice', '2') ?></div>
</button>
<button class="bet-dice-single" type="button" data-dice="3" data-odds="1:<?= $getOdds('dice', '3') ?>">
<div class="bet-dice-cell bet-dice-cell-gray"><img src="/Static/tm68/3.png" alt="3"></div>
<div class="bet-dice-single-odds">1:<?= $getOdds('dice', '3') ?></div>
</button>
<button class="bet-dice-single" type="button" data-dice="4" data-odds="1:<?= $getOdds('dice', '4') ?>">
<div class="bet-dice-cell bet-dice-cell-gray "><img src="/Static/tm68/4.png" alt="4"></div>
<div class="bet-dice-single-odds">1:<?= $getOdds('dice', '4') ?></div>
</button>
<button class="bet-dice-single" type="button" data-dice="5" data-odds="1:<?= $getOdds('dice', '5') ?>">
<div class="bet-dice-cell bet-dice-cell-gray"><img src="/Static/tm68/5.png" alt="5"></div>
<div class="bet-dice-single-odds">1:<?= $getOdds('dice', '5') ?></div>
</button>
<button class="bet-dice-single" type="button" data-dice="6" data-odds="1:<?= $getOdds('dice', '6') ?>">
<div class="bet-dice-cell bet-dice-cell-gray"><img src="/Static/tm68/6.png" alt="6"></div>
<div class="bet-dice-single-odds">1:<?= $getOdds('dice', '6') ?></div>
</button>
</div>
<div class="bet-actions bet-actions-v2">
<button class="bet-action bet-action-green" type="button" id="confirmBetBtn">
<i class="fa-solid fa-check"></i>
<span>ĐẶT CƯỢC</span>
</button>
<button class="bet-action bet-action-red" type="button" id="cancelBetBtn">
<i class="fa-solid fa-xmark"></i>
<span>HỦY</span>
</button>
</div>
<div class="bet-chips bet-chips-v2">
<button class="bet-chip-btn" type="button" data-amount="100000" data-label="100K">
<div class="bet-chip-display chip-red">100K</div>
</button>
<button class="bet-chip-btn" type="button" data-amount="200000" data-label="200K">
<div class="bet-chip-display chip-purple">200K</div>
</button>
<button class="bet-chip-btn" type="button" data-amount="500000" data-label="500K">
<div class="bet-chip-display chip-blue">500K</div>
</button>
<button class="bet-chip-btn" type="button" data-amount="1000000" data-label="1M">
<div class="bet-chip-display chip-gold">1M</div>
</button>
<button class="bet-chip-btn" type="button" data-amount="2000000" data-label="2M">
<div class="bet-chip-display chip-orange">2M</div>
</button>
<button class="bet-chip-btn" type="button" data-amount="5000000" data-label="5M">
<div class="bet-chip-display chip-green">5M</div>
</button>
<button class="bet-chip-btn" type="button" data-amount="10000000" data-label="10M">
<div class="bet-chip-display chip-lime">10M</div>
</button>
<button class="bet-chip-btn bet-chip-custom" type="button" data-amount="custom" data-label="+">
<div class="bet-chip-display chip-add">
<i class="fa-solid fa-plus"></i>
</div>
</button>
</div>
</div>
<?php endif; ?>
<div class="live-panel live-history live-history-v2" style="margin-top: 10px;">
<div class="live-roadmap live-roadmap-v2">
<div class="road-grid">
<?php
// 动态生成历史开奖记录
if (!empty($recentPeriods) && is_array($recentPeriods)):
$row = 1;
$col = 1;
// 调试:输出期号数量
// echo "<!-- 历史期号数量: " . count($recentPeriods) . ", 游戏类型: $gameType -->";
foreach (array_reverse($recentPeriods) as $period):
// 判断游戏类型并设置显示内容
if ($gameType === 'xocdia'):
// Xóc Đĩa: 显示红色硬币数量
$redCount = $period['dice1'] ?? 0;
$dotClass = ($redCount == 0 || $redCount == 2 || $redCount == 4) ? 'blue' : 'red';
$displayText = $redCount . 'Đ';
else:
// 骰子游戏: 显示总和
$total = $period['total'] ?? 0;
$dotClass = ($total >= 4 && $total <= 10) ? 'blue' : 'red';
$displayText = $total;
endif;
// 调试:输出当前位置
// echo "<!-- 位置: col=$col, row=$row, 显示: $displayText, dice1={$period['dice1']}, result={$period['result']} -->";
?>
<div class="road-cell" style="grid-column: <?= $col ?>; grid-row: <?= $row ?>;"><span class="road-dot <?= $dotClass ?>"><?= $displayText ?></span></div>
<?php
// 更新列和行(在循环外部,确保每次都执行)
$col++;
if ($col > 15) {
$col = 1;
$row++;
}
endforeach;
else:
// 如果没有历史记录,显示占位符
for ($i = 1; $i <= 15; $i++):
?>
<div class="road-cell" style="grid-column: <?= $i ?>; grid-row: 1;"><span class="road-dot gray">-</span></div>
<?php
endfor;
endif;
?>
</div>
</div>
<div class="live-mini-table live-mini-table-v2">
<div class="live-mini-table-header">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<div class="live-mini-table-body">
<?php
// 显示最近5期详细记录
if (!empty($recentPeriods) && is_array($recentPeriods)):
$displayCount = 0;
foreach ($recentPeriods as $period):
if ($displayCount >= 5) break;
if ($gameType === 'xocdia'):
// Xóc Đĩa: 显示硬币颜色
$coins = json_decode($period['result'] ?? '[]', true);
if (!is_array($coins) || count($coins) !== 4) continue;
$redCount = count(array_filter($coins, fn($c) => $c === 'red'));
$resultClass = ($redCount == 0 || $redCount == 2 || $redCount == 4) ? 'blue' : 'red';
$resultText = ($redCount == 0 || $redCount == 2 || $redCount == 4) ? 'Chẵn' : 'Lẻ';
?>
<div class="live-mini-row">
<span><?= $coins[0] === 'red' ? 'Đ' : 'T' ?></span>
<span><?= $coins[1] === 'red' ? 'Đ' : 'T' ?></span>
<span><?= $coins[2] === 'red' ? 'Đ' : 'T' ?></span>
<span><?= $coins[3] === 'red' ? 'Đ' : 'T' ?></span>
<span class="<?= $resultClass ?>"><?= $resultText ?></span>
</div>
<?php
else:
// 骰子游戏: 显示骰子点数
$dice1 = $period['dice1'] ?? 0;
$dice2 = $period['dice2'] ?? 0;
$dice3 = $period['dice3'] ?? 0;
$total = $period['total'] ?? 0;
$result = $period['result'] ?? '';
$resultClass = ($total >= 4 && $total <= 10) ? 'blue' : 'red';
?>
<div class="live-mini-row">
<span><?= $dice1 ?></span>
<span><?= $dice2 ?></span>
<span><?= $dice3 ?></span>
<span class="<?= $resultClass ?>"><?= $total ?></span>
<span><?= htmlspecialchars($result) ?></span>
</div>
<?php
endif;
$displayCount++;
endforeach;
endif;
?>
</div>
</div>
</div>
<!-- 投注面板 -->
</div>
</div>
</div>
<script src="/Static/ckplayer/hls.js/hls.min.js"></script>
<script src="/Static/ckplayer/flv.js/flv.min.js"></script>
<script src="/Static/ckplayer/mpegts.js/mpegts.min.js"></script>
<script src="/Static/ckplayer/js/ckplayer.js"></script>
<script src="/Static/layui/layui.js"></script>
<script>
layui.use(['layer'], function () {
var layer = layui.layer;
(function () {
var playerLogo = <?= !empty($settings['site_logo'] ?? '') ? json_encode($settings['site_logo']) : "''" ?>;
var currentGameId = <?= isset($game['id']) ? (int)$game['id'] : 1 ?>;
// 获取后端传来的当前期号信息
var serverPeriodNumber = <?= !empty($currentPeriod['period_number']) ? json_encode($currentPeriod['period_number']) : "''" ?>;
// 更新播放器期号和日期时间
function updatePlayerInfo() {
var periodEl = document.querySelector('.live-player-period');
var dateEl = document.querySelector('.live-player-date');
var timeEl = document.querySelector('.live-player-time');
if (periodEl) {
// 如果有后端真实期号,直接显示
if (serverPeriodNumber) {
periodEl.textContent = serverPeriodNumber;
} else {
// 如果没有期号,显示等待提示,不随机生成
periodEl.textContent = 'Đang chờ...';
}
}
if (dateEl && timeEl) {
var now = new Date();
// 更新日期:DD/MM/YYYY
var day = String(now.getDate()).padStart(2, '0');
var month = String(now.getMonth() + 1).padStart(2, '0');
var year = now.getFullYear();
dateEl.textContent = day + '/' + month + '/' + year;
// 更新时间:HH:MM:SS(红色)
var hours = String(now.getHours()).padStart(2, '0');
var minutes = String(now.getMinutes()).padStart(2, '0');
var seconds = String(now.getSeconds()).padStart(2, '0');
timeEl.textContent = hours + ':' + minutes + ':' + seconds;
}
}
// 初始化显示
updatePlayerInfo();
// 每秒更新一次时间
setInterval(updatePlayerInfo, 1000);
// 初始化左侧播放器(仅在有流地址时初始化)
var hasStreamUrl = <?= ($hasStreamUrl ?? false) ? 'true' : 'false' ?>;
var leftPlayerContainer = document.getElementById('tm68LivePlayerLeft');
if (leftPlayerContainer && typeof ckplayer === 'function' && hasStreamUrl) {
var leftStreamUrl = leftPlayerContainer.getAttribute('data-stream') || '';
// 如果没有配置流地址,不初始化播放器
if (!leftStreamUrl) {
return;
}
window.tm68LivePlayerLeft = ckplayer({
container: '#tm68LivePlayerLeft',
live: true,
autoplay: true,
volume: 0,
controls: false,
rightBar: false,
bar: false,
video: leftStreamUrl,
type: 'hls',
playsinline: true
});
// 拦截CKPlayer的pause方法,禁止暂停
if (window.tm68LivePlayerLeft && typeof window.tm68LivePlayerLeft.pause === 'function') {
var originalLeftPause = window.tm68LivePlayerLeft.pause;
window.tm68LivePlayerLeft.pause = function () {
// 禁止暂停,直接返回
return;
};
}
// 确保播放器自动播放并禁止暂停
setTimeout(function () {
try {
// 直接查找并操作video元素
var leftVideo = document.querySelector('#tm68LivePlayerLeft video');
if (leftVideo) {
leftVideo.muted = true;
leftVideo.play().catch(function (e) {
console.log('Left video autoplay failed:', e);
});
// 禁止暂停:监听pause事件并立即恢复播放
leftVideo.addEventListener('pause', function (e) {
e.preventDefault();
e.stopPropagation();
var video = this;
setTimeout(function () {
video.play().catch(function (err) {
console.log('Left video resume failed:', err);
});
}, 10);
}, true);
// 重写pause方法,禁止暂停
var originalPause = leftVideo.pause;
leftVideo.pause = function () {
// 不执行暂停操作,直接返回
return;
};
} else if (window.tm68LivePlayerLeft && typeof window.tm68LivePlayerLeft.play === 'function') {
window.tm68LivePlayerLeft.play();
}
} catch (e) {
console.log('Left player autoplay failed:', e);
}
}, 800);
}
// 初始化右侧主播放器(仅在有流地址时初始化)
var playerContainer = document.getElementById('tm68LivePlayer');
if (playerContainer && typeof ckplayer === 'function' && hasStreamUrl) {
var streamUrl = playerContainer.getAttribute('data-stream') || '';
// 如果没有配置流地址,不初始化播放器
if (!streamUrl) {
return;
}
window.tm68LivePlayer = ckplayer({
container: '#tm68LivePlayer',
live: true,
autoplay: true,
volume: 0,
controls: false,
rightBar: false,
bar: false,
video: streamUrl,
type: 'hls',
logo: playerLogo || undefined,
playsinline: true
});
// 拦截CKPlayer的pause方法,禁止暂停
if (window.tm68LivePlayer && typeof window.tm68LivePlayer.pause === 'function') {
var originalMainPause = window.tm68LivePlayer.pause;
window.tm68LivePlayer.pause = function () {
// 禁止暂停,直接返回
return;
};
}
// 确保播放器自动播放并禁止暂停
setTimeout(function () {
try {
// 直接查找并操作video元素
var rightVideo = document.querySelector('#tm68LivePlayer video');
if (rightVideo) {
rightVideo.muted = true;
rightVideo.play().catch(function (e) {
console.log('Main video autoplay failed:', e);
});
// 禁止暂停:监听pause事件并立即恢复播放
rightVideo.addEventListener('pause', function (e) {
e.preventDefault();
e.stopPropagation();
var video = this;
setTimeout(function () {
video.play().catch(function (err) {
console.log('Main video resume failed:', err);
});
}, 10);
}, true);
// 重写pause方法,禁止暂停
var originalPause = rightVideo.pause;
rightVideo.pause = function () {
// 不执行暂停操作,直接返回
return;
};
} else if (window.tm68LivePlayer && typeof window.tm68LivePlayer.play === 'function') {
window.tm68LivePlayer.play();
}
} catch (e) {
console.log('Main player autoplay failed:', e);
}
}, 800);
}
var toggle = document.getElementById('headerMenuToggle');
var menu = document.getElementById('headerMenu');
if (toggle && menu) {
toggle.addEventListener('click', function (e) {
e.stopPropagation();
menu.classList.toggle('is-open');
});
document.addEventListener('click', function () {
menu.classList.remove('is-open');
});
menu.addEventListener('click', function (e) {
e.stopPropagation();
});
}
// 返回按钮 - 返回上一页
var backBtn = document.querySelector('.live-circle-btn[aria-label="Back"]');
if (backBtn) {
backBtn.addEventListener('click', function (e) {
e.preventDefault();
window.history.back();
});
}
// 首页按钮 - 跳转到首页
var homeBtn = document.querySelector('.lobby-icon-btn[aria-label="Home"]');
if (homeBtn) {
homeBtn.addEventListener('click', function (e) {
e.preventDefault();
window.location.href = '/';
});
}
var soundToggle = document.getElementById('soundToggle');
if (soundToggle) {
var icon = soundToggle.querySelector('i');
var muted = true; // 默认静音
soundToggle.addEventListener('click', function (e) {
e.stopPropagation();
muted = !muted;
if (muted) {
icon.classList.remove('fa-volume-high');
icon.classList.add('fa-volume-xmark');
} else {
icon.classList.remove('fa-volume-xmark');
icon.classList.add('fa-volume-high');
}
try {
document.dispatchEvent(new CustomEvent('tm68SoundToggle', {
detail: { muted: muted }
}));
} catch (err) {
console.warn('tm68SoundToggle event dispatch failed', err);
}
});
// 初始化时发送静音事件
try {
document.dispatchEvent(new CustomEvent('tm68SoundToggle', {
detail: { muted: true }
}));
} catch (err) {
console.warn('tm68SoundToggle initial event dispatch failed', err);
}
}
document.addEventListener('tm68SoundToggle', function (e) {
if (!hasStreamUrl) return; // 如果没有流地址,不处理音量控制
// 控制右侧主播放器音量
var player = window.tm68LivePlayer;
if (player) {
if (e && e.detail && e.detail.muted) {
player.muted();
} else {
player.exitMuted();
player.volume(0.8);
}
}
// 控制左侧播放器音量
var leftPlayer = window.tm68LivePlayerLeft;
if (leftPlayer) {
if (e && e.detail && e.detail.muted) {
leftPlayer.muted();
} else {
leftPlayer.exitMuted();
leftPlayer.volume(0.8);
}
}
});
// 页面加载完成后再次尝试播放(确保自动播放,仅在有流地址时)
if (hasStreamUrl) {
window.addEventListener('load', function () {
setTimeout(function () {
try {
// 左侧播放器
if (window.tm68LivePlayerLeft) {
// 直接设置video元素的muted属性确保自动播放
var leftVideo = document.querySelector('#tm68LivePlayerLeft video');
if (leftVideo) {
leftVideo.muted = true;
leftVideo.play().catch(function (e) {
console.log('Left video autoplay failed:', e);
});
// 再次确保禁止暂停
if (!leftVideo._pauseDisabled) {
leftVideo._pauseDisabled = true;
leftVideo.addEventListener('pause', function (e) {
e.preventDefault();
var video = this;
setTimeout(function () {
video.play().catch(function (err) { });
}, 10);
}, true);
var originalPause = leftVideo.pause;
leftVideo.pause = function () { return; };
}
} else if (typeof window.tm68LivePlayerLeft.play === 'function') {
window.tm68LivePlayerLeft.play();
}
}
// 右侧播放器
if (window.tm68LivePlayer) {
// 直接设置video元素的muted属性确保自动播放
var rightVideo = document.querySelector('#tm68LivePlayer video');
if (rightVideo) {
rightVideo.muted = true;
rightVideo.play().catch(function (e) {
console.log('Main video autoplay failed:', e);
});
// 再次确保禁止暂停
if (!rightVideo._pauseDisabled) {
rightVideo._pauseDisabled = true;
rightVideo.addEventListener('pause', function (e) {
e.preventDefault();
var video = this;
setTimeout(function () {
video.play().catch(function (err) { });
}, 10);
}, true);
var originalPause = rightVideo.pause;
rightVideo.pause = function () { return; };
}
} else if (typeof window.tm68LivePlayer.play === 'function') {
window.tm68LivePlayer.play();
}
}
} catch (e) {
console.log('Autoplay retry failed:', e);
}
}, 1000);
});
}
// 定期检查播放状态,如果暂停则立即恢复(防止意外暂停,仅在有流地址时)
if (hasStreamUrl) {
setInterval(function () {
try {
// 检查左侧播放器
var leftVideo = document.querySelector('#tm68LivePlayerLeft video');
if (leftVideo && leftVideo.paused) {
leftVideo.play().catch(function (e) { });
}
// 检查右侧播放器
var rightVideo = document.querySelector('#tm68LivePlayer video');
if (rightVideo && rightVideo.paused) {
rightVideo.play().catch(function (e) { });
}
} catch (e) {
// 忽略错误
}
}, 500); // 每500ms检查一次
}
// ========== 下注功能模块 ==========
(function (layer) {
// 如果没有流地址,禁止投注功能
var hasStreamUrlForBet = <?= ($hasStreamUrl ?? false) ? 'true' : 'false' ?>;
if (!hasStreamUrlForBet) {
// 禁用所有投注按钮
var betButtons = document.querySelectorAll('.bet-cell, .bet-side, .bet-dice-single, .bet-dice-combo, .bet-chip-btn, #confirmBetBtn, #cancelBetBtn');
betButtons.forEach(function (btn) {
btn.disabled = true;
btn.style.cursor = 'not-allowed';
btn.style.opacity = '0.5';
});
return; // 不初始化投注功能
}
// ========== Period Polling & Countdown ==========
var periodState = {
status: 'loading',
remaining: 0,
periodNumber: serverPeriodNumber
};
var betOverlay = document.getElementById('betOverlay');
var countdownDisplay = document.getElementById('countdownDisplay');
var statusDisplay = document.getElementById('statusDisplay');
var activeCountdown = document.getElementById('activeCountdown');
var activeTimer = document.getElementById('activeTimer');
var periodEl = document.querySelector('.live-player-period');
// 调试:检查元素是否存在
console.log('DOM 元素检查:', {
betOverlay: !!betOverlay,
countdownDisplay: !!countdownDisplay,
statusDisplay: !!statusDisplay,
activeCountdown: !!activeCountdown,
activeTimer: !!activeTimer,
periodEl: !!periodEl
});
// 调试:检查遮罩层的初始样式
if (betOverlay) {
console.log('betOverlay 初始状态:', {
className: betOverlay.className,
display: getComputedStyle(betOverlay).display,
position: getComputedStyle(betOverlay).position,
zIndex: getComputedStyle(betOverlay).zIndex,
visibility: getComputedStyle(betOverlay).visibility
});
}
// 测试函数:强制显示遮罩层
window.testShowOverlay = function() {
if (betOverlay) {
betOverlay.classList.remove('hidden');
betOverlay.style.display = 'flex';
betOverlay.style.visibility = 'visible';
betOverlay.style.zIndex = '9999';
if (statusDisplay) statusDisplay.textContent = '测试遮罩层';
console.log('测试:强制显示遮罩层');
}
};
function updatePeriodUI() {
console.log('更新期号状态:', periodState);
if (periodState.status === 'pending') {
if (periodState.remaining > 0) {
// Betting Open - 投注开放
console.log('✅ 投注开放,剩余时间:', periodState.remaining + 's');
if (betOverlay) {
betOverlay.classList.add('hidden');
betOverlay.style.display = 'none';
betOverlay.style.visibility = 'hidden';
}
if (activeCountdown) {
activeCountdown.classList.remove('hidden');
activeCountdown.style.display = 'block';
}
if (activeTimer) activeTimer.textContent = periodState.remaining;
// Enable buttons
var confirmBtn = document.getElementById('confirmBetBtn');
if (confirmBtn) confirmBtn.disabled = false;
} else {
// Betting Closed - 封盘
console.log('⏸️ 封盘 - 停止下注');
if (betOverlay) {
betOverlay.classList.remove('hidden');
betOverlay.style.display = 'flex';
betOverlay.style.visibility = 'visible';
betOverlay.style.opacity = '1';
console.log('遮罩层显示:', {
display: getComputedStyle(betOverlay).display,
classes: betOverlay.className,
zIndex: getComputedStyle(betOverlay).zIndex
});
}
if (activeCountdown) activeCountdown.classList.add('hidden');
if (countdownDisplay) {
countdownDisplay.style.display = 'none'; // 不显示倒计时数字
}
if (statusDisplay) statusDisplay.textContent = '⏸️ ĐÓNG CƯỢC'; // Stop Betting
// Disable buttons
var confirmBtn = document.getElementById('confirmBetBtn');
if (confirmBtn) confirmBtn.disabled = true;
}
} else if (periodState.status === 'locked') {
// Locked - 锁定(开奖前锁定)
console.log('🔒 锁定期号');
if (betOverlay) {
betOverlay.classList.remove('hidden');
betOverlay.style.display = 'flex';
betOverlay.style.visibility = 'visible';
betOverlay.style.opacity = '1';
}
if (activeCountdown) activeCountdown.classList.add('hidden');
if (countdownDisplay) countdownDisplay.style.display = 'none';
if (statusDisplay) statusDisplay.textContent = '🔒 ĐÃ KHÓA'; // Locked
var confirmBtn = document.getElementById('confirmBetBtn');
if (confirmBtn) confirmBtn.disabled = true;
} else if (periodState.status === 'drawn') {
// Drawing/Payout - 正在开奖/派奖
console.log('💰 正在派奖');
if (betOverlay) {
betOverlay.classList.remove('hidden');
betOverlay.style.display = 'flex';
betOverlay.style.visibility = 'visible';
betOverlay.style.opacity = '1';
}
if (activeCountdown) activeCountdown.classList.add('hidden');
if (countdownDisplay) countdownDisplay.style.display = 'none';
if (statusDisplay) statusDisplay.textContent = '💰 ĐANG TRẢ THƯỞNG'; // Payout
var confirmBtn = document.getElementById('confirmBetBtn');
if (confirmBtn) confirmBtn.disabled = true;
} else if (periodState.status === 'settled') {
// Settled - 已结算,等待下一局
console.log('⏳ 等待新一局, remaining:', periodState.remaining);
if (betOverlay) {
betOverlay.classList.remove('hidden');
// 强制设置样式
betOverlay.style.display = 'flex';
betOverlay.style.visibility = 'visible';
betOverlay.style.opacity = '1';
console.log('settled状态 - 遮罩层强制显示:', {
classes: betOverlay.className,
display: getComputedStyle(betOverlay).display,
zIndex: getComputedStyle(betOverlay).zIndex
});
}
if (activeCountdown) activeCountdown.classList.add('hidden');
if (countdownDisplay) countdownDisplay.style.display = 'none';
if (statusDisplay) {
statusDisplay.textContent = '⏳ CHỜ VÁN MỚI'; // Waiting for next round
console.log('状态文本已更新:', statusDisplay.textContent);
}
var confirmBtn = document.getElementById('confirmBetBtn');
if (confirmBtn) confirmBtn.disabled = true;
} else {
// Loading or Error - 加载中或错误
console.log('⏱️ 加载中... 状态:', periodState.status);
if (betOverlay) {
betOverlay.classList.remove('hidden');
betOverlay.style.display = 'flex';
betOverlay.style.visibility = 'visible';
betOverlay.style.opacity = '1';
}
if (countdownDisplay) countdownDisplay.style.display = 'none';
if (statusDisplay) statusDisplay.textContent = '⏱️ ĐANG TẢI...';
var confirmBtn = document.getElementById('confirmBetBtn');
if (confirmBtn) confirmBtn.disabled = true;
}
// Update Period Number Display
if (periodEl && periodState.periodNumber) {
periodEl.textContent = periodState.periodNumber;
}
// Update global serverPeriodNumber for bet submission
serverPeriodNumber = periodState.periodNumber;
}
var _lastPollPeriodId = 0;
var _lastPollStatus = '';
function checkPeriodStatus() {
fetch('/api/period/current?game_id=' + currentGameId)
.then(res => res.json())
.then(data => {
if (data.success) {
var serverRemaining = data.data.remaining_seconds || 0;
var periodChanged = (data.data.id && data.data.id !== _lastPollPeriodId);
var statusChanged = (data.data.status !== _lastPollStatus);
var driftTooMuch = Math.abs(periodState.remaining - serverRemaining) > 3;
periodState.status = data.data.status;
periodState.periodNumber = data.data.period_number;
// 只在必要时同步服务端倒计时(防止跳数)
if (periodChanged || statusChanged || driftTooMuch || periodState.remaining <= 0) {
periodState.remaining = serverRemaining;
}
_lastPollPeriodId = data.data.id || 0;
_lastPollStatus = data.data.status;
// 同步余额(结算后中奖加款)
if (data.balance !== null && data.balance !== undefined) {
var balanceEl = document.querySelector('.live-balance-amount');
if (balanceEl) balanceEl.textContent = formatAmount(data.balance);
}
updatePeriodUI();
} else {
periodState.status = 'settled';
periodState.periodNumber = 'Đang chờ...';
updatePeriodUI();
}
})
.catch(err => console.error('Poll error:', err));
}
// 本地倒计时 —— 每秒 -1,丝滑不跳
setInterval(function() {
if (periodState.remaining > 0) {
periodState.remaining--;
// 只更新倒计时数字,不触发完整UI更新
if (activeTimer) activeTimer.textContent = periodState.remaining;
}
}, 1000);
// 轮询:3秒同步一次(比原来1秒降低66%请求量)
setInterval(checkPeriodStatus, 3000);
checkPeriodStatus(); // Initial check
// 初始化或重置全局投注状态
window.betState = {
selectedChip: null, // 当前选中的筹码 {amount, label, color}
bets: {} // 每个投注选项的金额 {betKey: {amount, chipInfo, betInfo}}
};
// 创建局部变量引用,方便后续代码使用
var betState = window.betState;
// 清理可能残留的旧数据
console.log('投注状态已初始化:', betState);
// 初始化筹码按钮
var chipButtons = document.querySelectorAll('.bet-chip-btn');
chipButtons.forEach(function (btn) {
btn.addEventListener('click', function () {
var amount = this.getAttribute('data-amount');
var label = this.getAttribute('data-label');
// 移除其他选中状态
chipButtons.forEach(function (b) {
b.classList.remove('chip-selected');
});
if (amount === 'custom') {
// 自定义金额
layer.prompt({
formType: 0,
title: 'Nhập số tiền cược',
placeholder: 'Nhập số tiền',
area: ['300px', 'auto']
}, function (value, index, elem) {
if (value && !isNaN(value) && parseFloat(value) > 0) {
betState.selectedChip = {
amount: parseFloat(value),
label: formatAmount(parseFloat(value)),
color: 'custom'
};
}
layer.close(index);
});
} else {
this.classList.add('chip-selected');
var chipDisplay = this.querySelector('.bet-chip-display');
var colorClass = chipDisplay.className.match(/chip-(\w+)/);
betState.selectedChip = {
amount: parseInt(amount),
label: label,
color: colorClass ? colorClass[1] : 'default'
};
}
});
});
// 获取投注选项的唯一标识
function getBetKey(element) {
var betInfo = parseBetOption(element);
if (!betInfo) return null;
return betInfo.type + '_' + betInfo.value;
}
// 初始化投注选项按钮
var betOptions = document.querySelectorAll('.bet-cell, .bet-side, .bet-dice-single, .bet-dice-combo');
betOptions.forEach(function (option) {
// 为每个投注选项添加筹码显示容器
if (!option.querySelector('.bet-amount-display')) {
var amountDisplay = document.createElement('div');
amountDisplay.className = 'bet-amount-display';
option.appendChild(amountDisplay);
}
option.addEventListener('click', function () {
if (!betState.selectedChip) {
layer.msg('Vui lòng chọn số tiền cược trước!', { icon: 0 });
return;
}
var betKey = getBetKey(this);
if (!betKey) return;
// 解析投注信息
var betInfo = parseBetOption(this);
if (!betInfo) {
console.error('无法解析投注选项:', this);
layer.msg('Loại cược không hợp lệ!', { icon: 0 });
return;
}
// 累加金额
if (!betState.bets[betKey]) {
betState.bets[betKey] = {
amount: 0,
chipInfo: betState.selectedChip,
betInfo: betInfo
};
}
// 累加当前选择的筹码金额
betState.bets[betKey].amount += betState.selectedChip.amount;
// 更新显示
updateBetDisplay(this, betState.bets[betKey]);
// 添加选中状态
this.classList.add('bet-selected');
});
});
// 更新投注选项的显示
function updateBetDisplay(element, betData) {
var amountDisplay = element.querySelector('.bet-amount-display');
if (!amountDisplay) return;
var totalAmount = betData.amount;
var chipInfo = betData.chipInfo;
// 清空现有内容
amountDisplay.innerHTML = '';
// 创建筹码显示 - 只显示一个筹码图标
var chipWrapper = document.createElement('div');
chipWrapper.className = 'bet-chip-wrapper';
// 只显示一个筹码图标
var chipEl = document.createElement('div');
chipEl.className = 'bet-chip-icon chip-' + chipInfo.color;
chipWrapper.appendChild(chipEl);
// 显示总金额(根据累计金额更新)
var amountText = document.createElement('div');
amountText.className = 'bet-amount-text';
amountText.textContent = formatAmount(totalAmount);
amountDisplay.appendChild(chipWrapper);
amountDisplay.appendChild(amountText);
amountDisplay.style.display = 'flex';
}
// 确认下注
var confirmBtn = document.getElementById('confirmBetBtn');
if (confirmBtn) {
confirmBtn.addEventListener('click', function () {
var betKeys = Object.keys(betState.bets);
if (betKeys.length === 0) {
layer.msg('Vui lòng chọn ít nhất một loại cược!', { icon: 0 });
return;
}
// 准备下注数据
var betsToSubmit = [];
betKeys.forEach(function (betKey) {
var betData = betState.bets[betKey];
// 兼容两种数据结构
if (betData && betData.amount > 0) {
// 标准结构:{amount, chipInfo, betInfo: {type, value, odds}}
if (betData.betInfo && betData.betInfo.type) {
betsToSubmit.push({
type: betData.betInfo.type,
value: betData.betInfo.value,
amount: betData.amount,
odds: betData.betInfo.odds
});
}
// 兼容旧结构:{type, value, amount, odds, chipInfo}
else if (betData.type) {
betsToSubmit.push({
type: betData.type,
value: betData.value,
amount: betData.amount,
odds: betData.odds
});
} else {
console.warn('跳过无效投注 (缺少type):', betKey, betData);
}
} else {
console.warn('跳过无效投注 (金额<=0或无数据):', betKey, betData);
}
});
if (betsToSubmit.length === 0) return;
// 使用服务端传来的期号,或者从界面获取(必须确保不是模拟数据)
var periodNumber = serverPeriodNumber;
// 如果服务端期号为空,尝试从界面获取,但要排除模拟数据
if (!periodNumber) {
var periodEl = document.querySelector('.live-player-period');
var text = periodEl ? periodEl.textContent.trim() : '';
// 简单的校验:必须以G开头,且不能包含#,且长度要合理
if (text && text.indexOf('G') === 0 && text.indexOf('#') === -1 && text !== 'Đang chờ...') {
periodNumber = text;
}
}
if (!periodNumber) {
layer.msg('Đang chờ kỳ quay mới...', {icon: 0});
return;
}
// 锁定按钮
confirmBtn.disabled = true;
confirmBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> <span>ĐANG XỬ LÝ...</span>';
// 发送请求
fetch('/api/bet', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
bets: betsToSubmit,
period_number: periodNumber,
game_id: currentGameId
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
layer.msg('Đặt cược thành công!', { icon: 1 });
// 更新余额显示
if (data.new_balance !== undefined) {
var balanceEl = document.querySelector('.live-balance-amount');
if (balanceEl) {
balanceEl.textContent = formatAmount(data.new_balance);
}
}
resetBetSelection();
} else {
layer.msg(data.message || 'Đặt cược thất bại', { icon: 2 });
}
})
.catch(error => {
console.error('Bet error:', error);
layer.msg('Lỗi kết nối máy chủ', { icon: 2 });
})
.finally(() => {
confirmBtn.disabled = false;
confirmBtn.innerHTML = '<i class="fa-solid fa-check"></i> <span>ĐẶT CƯỢC</span>';
});
});
}
// 取消下注
var cancelBtn = document.getElementById('cancelBetBtn');
if (cancelBtn) {
cancelBtn.addEventListener('click', function () {
resetBetSelection();
});
}
// 解析投注选项
function parseBetOption(element) {
var betType = null;
var betValue = null;
var odds = null;
if (element.classList.contains('bet-side')) {
if (element.classList.contains('bet-xiu')) {
betType = 'xiu';
betValue = 'XỈU';
odds = '1:0.97';
} else if (element.classList.contains('bet-tai')) {
betType = 'tai';
betValue = 'TÀI';
odds = '1:0.97';
}
} else if (element.classList.contains('bet-dice-combo')) {
// 处理骰子组合投注
betType = element.getAttribute('data-bet-type') || 'combo';
betValue = element.getAttribute('data-combo-value') || '0';
odds = element.getAttribute('data-odds') || '1:120';
} else if (element.classList.contains('bet-dice-single')) {
betType = 'dice';
betValue = parseInt(element.getAttribute('data-dice'));
odds = element.getAttribute('data-odds') || '1:0.97';
} else if (element.classList.contains('bet-cell-outline-red')) {
betType = 'chan';
betValue = 'CHẴN';
odds = '1:0.97';
} else if (element.classList.contains('bet-cell-outline-green')) {
betType = 'le';
betValue = 'LẺ';
odds = '1:0.97';
} else if (element.classList.contains('bet-cell')) {
// 优先使用data属性
var betNumber = element.getAttribute('data-bet-number');
var betTypeAttr = element.getAttribute('data-bet-type');
if (betTypeAttr) {
betType = betTypeAttr;
betValue = element.textContent.trim().split('\n')[0];
} else if (betNumber) {
betType = 'number';
betValue = parseInt(betNumber);
} else {
// 回退到文本解析
var text = element.textContent.trim();
var numberMatch = text.match(/^(\d+)/);
if (numberMatch) {
betType = 'number';
betValue = parseInt(numberMatch[1]);
}
}
// 获取赔率
var oddsAttr = element.getAttribute('data-odds');
if (oddsAttr) {
odds = oddsAttr;
} else {
var oddsEl = element.querySelector('.bet-odds-mini');
if (oddsEl) {
odds = oddsEl.textContent.trim();
}
}
}
if (betType && betValue !== null) {
return { type: betType, value: betValue, odds: odds };
}
return null;
}
// 重置选择
function resetBetSelection() {
betState.bets = {};
betState.selectedChip = null;
betOptions.forEach(function (o) {
o.classList.remove('bet-selected');
var amountDisplay = o.querySelector('.bet-amount-display');
if (amountDisplay) {
amountDisplay.innerHTML = '';
amountDisplay.style.display = 'none';
}
});
chipButtons.forEach(function (b) {
b.classList.remove('chip-selected');
});
}
// 格式化金额
function formatAmount(amount) {
if (amount >= 1000000) {
return (amount / 1000000).toFixed(0) + 'M';
} else if (amount >= 1000) {
return (amount / 1000).toFixed(0) + 'K';
}
return amount.toString();
}
})(layer);
})();
});
</script>
</body>
</html>