397 lines
9.2 KiB
Markdown
Executable File
397 lines
9.2 KiB
Markdown
Executable File
# 多游戏独立期号系统更新说明
|
||
|
||
## 更新日期
|
||
2026-01-08
|
||
|
||
## 问题描述
|
||
|
||
原系统存在以下问题:
|
||
1. **所有游戏共用一个期号队列** - 骰子游戏和 Xóc Đĩa 无法同时运行
|
||
2. **开始下注时没有游戏选择** - 系统自动使用上一期的游戏ID
|
||
3. **前台获取期号不区分游戏** - 导致不同游戏显示相同期号
|
||
|
||
## 解决方案
|
||
|
||
实现**每个游戏独立维护自己的期号队列**,允许多个游戏同时运行。
|
||
|
||
---
|
||
|
||
## 修改的文件
|
||
|
||
### 1. 后端控制器
|
||
|
||
#### `App/Controllers/Admin/PeriodController.php`
|
||
|
||
**修改 `start()` 方法**:
|
||
- 添加 `game_id` 参数验证
|
||
- 检查该游戏是否有未结束的期号(而不是全局检查)
|
||
- 生成期号时传递 `game_id`
|
||
|
||
```php
|
||
public function start() {
|
||
// 接收 game_id 参数
|
||
$gameId = isset($data['game_id']) ? (int)$data['game_id'] : 0;
|
||
|
||
// 检查该游戏是否有未结束的期号
|
||
$activePeriod = $db->get('periods', '*', [
|
||
'game_id' => $gameId,
|
||
'status' => ['pending', 'locked', 'drawn']
|
||
]);
|
||
|
||
// 生成期号时传递 game_id
|
||
$periodNumber = $this->generatePeriodNumber($db, $gameId);
|
||
}
|
||
```
|
||
|
||
#### `App/Controllers/Web/PeriodController.php`
|
||
|
||
**修改 `getCurrent()` 方法**:
|
||
- 添加 `game_id` 参数验证
|
||
- 按游戏ID过滤期号
|
||
|
||
```php
|
||
public function getCurrent() {
|
||
$gameId = isset($_GET['game_id']) ? (int)$_GET['game_id'] : 0;
|
||
|
||
// 获取该游戏最新的期号
|
||
$currentPeriod = $db->get('periods', '*', [
|
||
'game_id' => $gameId,
|
||
'ORDER' => ['id' => 'DESC']
|
||
]);
|
||
}
|
||
```
|
||
|
||
#### `App/Controllers/Web/HomeController.php`
|
||
|
||
**已经正确实现**:
|
||
- `live()` 方法已经按 `game_id` 过滤期号
|
||
|
||
---
|
||
|
||
### 2. 后台视图
|
||
|
||
#### `App/Views/Admin/periods.php`
|
||
|
||
**添加游戏选择下拉框**:
|
||
|
||
```html
|
||
<!-- 当前期号卡片 -->
|
||
<select id="startGameSelect" class="px-3 py-2 border border-gray-300 rounded-lg text-sm bg-white">
|
||
<option value="">选择游戏</option>
|
||
<?php foreach ($games as $gid => $gname): ?>
|
||
<option value="<?= $gid ?>"><?= htmlspecialchars($gname) ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<button class="period-start-btn">开始下注</button>
|
||
```
|
||
|
||
**修改开奖模态框**:
|
||
- 添加游戏类型隐藏字段
|
||
- 添加 Xóc Đĩa 硬币输入界面
|
||
- 根据游戏类型动态显示输入界面
|
||
|
||
```html
|
||
<input type="hidden" id="drawGameType" name="game_type">
|
||
|
||
<!-- 骰子游戏输入 -->
|
||
<div id="diceInputSection" class="grid grid-cols-3 gap-4">
|
||
<!-- 骰子1, 2, 3 -->
|
||
</div>
|
||
|
||
<!-- Xóc Đĩa 硬币输入 -->
|
||
<div id="xocdiaInputSection" class="hidden space-y-3">
|
||
<select id="coin1">
|
||
<option value="red">红色</option>
|
||
<option value="white">白色</option>
|
||
</select>
|
||
<!-- coin2, coin3, coin4 -->
|
||
</div>
|
||
```
|
||
|
||
**修改JavaScript**:
|
||
|
||
```javascript
|
||
// 开始下注
|
||
async function startPeriod() {
|
||
const gameId = document.getElementById('startGameSelect').value;
|
||
if (!gameId) {
|
||
layer.msg('请先选择游戏', {icon: 2});
|
||
return;
|
||
}
|
||
|
||
fetch('/admin/periods/start', {
|
||
method: 'POST',
|
||
body: JSON.stringify({game_id: parseInt(gameId)})
|
||
});
|
||
}
|
||
|
||
// 打开开奖模态框
|
||
function openDrawPeriodModal(periodId) {
|
||
// 获取期号信息
|
||
fetch(`/admin/periods/${periodId}`)
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
const gameId = data.data.game_id;
|
||
|
||
// 获取游戏类型
|
||
fetch(`/admin/games/${gameId}`)
|
||
.then(res => res.json())
|
||
.then(gameData => {
|
||
const gameType = gameData.data.type;
|
||
|
||
// 根据游戏类型显示不同界面
|
||
if (gameType === 'xocdia') {
|
||
document.getElementById('diceInputSection').classList.add('hidden');
|
||
document.getElementById('xocdiaInputSection').classList.remove('hidden');
|
||
} else {
|
||
document.getElementById('diceInputSection').classList.remove('hidden');
|
||
document.getElementById('xocdiaInputSection').classList.add('hidden');
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 提交开奖
|
||
async function submitDrawPeriod() {
|
||
const gameType = document.getElementById('drawGameType').value;
|
||
const data = {id: periodId, auto: false};
|
||
|
||
if (gameType === 'xocdia') {
|
||
// 收集硬币颜色
|
||
data.coins = [
|
||
document.getElementById('coin1').value,
|
||
document.getElementById('coin2').value,
|
||
document.getElementById('coin3').value,
|
||
document.getElementById('coin4').value
|
||
];
|
||
} else {
|
||
// 收集骰子点数
|
||
data.dice1 = parseInt(drawDice1.value);
|
||
data.dice2 = parseInt(drawDice2.value);
|
||
data.dice3 = parseInt(drawDice3.value);
|
||
}
|
||
|
||
fetch('/admin/periods/draw', {
|
||
method: 'POST',
|
||
body: JSON.stringify(data)
|
||
});
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 前台视图
|
||
|
||
#### `App/Views/Web/live.php`
|
||
|
||
**修改期号轮询**:
|
||
|
||
```javascript
|
||
function checkPeriodStatus() {
|
||
fetch('/api/period/current?game_id=' + currentGameId)
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
// 更新期号状态
|
||
});
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 使用流程
|
||
|
||
### 后台管理员操作
|
||
|
||
1. **开始新一期**
|
||
- 进入后台 → 期号管理
|
||
- 从下拉框选择游戏(骰子游戏 或 Xóc Đĩa)
|
||
- 点击"开始下注"按钮
|
||
- 系统创建该游戏的新期号
|
||
|
||
2. **录入开奖结果**
|
||
- 点击期号列表中的"录入开奖"按钮
|
||
- 系统自动识别游戏类型:
|
||
- **骰子游戏**:显示3个骰子输入框
|
||
- **Xóc Đĩa**:显示4个硬币颜色选择框
|
||
- 输入开奖结果并确认
|
||
|
||
3. **结算**
|
||
- 点击"确认结算"按钮
|
||
- 系统自动结算所有投注
|
||
- 可以继续为该游戏或其他游戏开始新一期
|
||
|
||
### 前台用户体验
|
||
|
||
1. **进入游戏**
|
||
- 访问首页,点击游戏卡片
|
||
- 系统自动获取该游戏的当前期号
|
||
|
||
2. **投注**
|
||
- 页面显示该游戏的倒计时和期号
|
||
- 只能对当前游戏的期号进行投注
|
||
|
||
3. **多游戏并行**
|
||
- 骰子游戏和 Xóc Đĩa 可以同时运行
|
||
- 每个游戏独立维护期号队列
|
||
- 互不干扰
|
||
|
||
---
|
||
|
||
## 数据库变更
|
||
|
||
**无需修改数据库结构**,现有的 `periods` 表已经包含 `game_id` 字段。
|
||
|
||
---
|
||
|
||
## 验证步骤
|
||
|
||
### 1. 后台验证
|
||
|
||
```bash
|
||
# 1. 访问后台期号管理
|
||
http://yourdomain.com/admin/periods
|
||
|
||
# 2. 选择"骰子游戏",点击"开始下注"
|
||
# 应该看到:新期号创建成功,期号格式为 G1YYYYMMDDNNNN
|
||
|
||
# 3. 选择"Xóc Đĩa",点击"开始下注"
|
||
# 应该看到:新期号创建成功,期号格式为 G2YYYYMMDDNNNN
|
||
|
||
# 4. 期号列表应该显示两个游戏的期号
|
||
```
|
||
|
||
### 2. 前台验证
|
||
|
||
```bash
|
||
# 1. 访问骰子游戏
|
||
http://yourdomain.com/live?game_id=1
|
||
|
||
# 应该看到:骰子游戏的期号和倒计时
|
||
|
||
# 2. 访问 Xóc Đĩa 游戏
|
||
http://yourdomain.com/live?game_id=2
|
||
|
||
# 应该看到:Xóc Đĩa 游戏的期号和倒计时
|
||
|
||
# 3. 两个游戏的期号应该不同
|
||
```
|
||
|
||
### 3. 开奖验证
|
||
|
||
```bash
|
||
# 1. 后台点击骰子游戏的"录入开奖"
|
||
# 应该看到:3个骰子输入框
|
||
|
||
# 2. 后台点击 Xóc Đĩa 的"录入开奖"
|
||
# 应该看到:4个硬币颜色选择框
|
||
```
|
||
|
||
---
|
||
|
||
## 故障排查
|
||
|
||
### 问题1:点击"开始下注"提示"请先选择游戏"
|
||
|
||
**原因**:未选择游戏
|
||
|
||
**解决**:从下拉框选择游戏后再点击按钮
|
||
|
||
---
|
||
|
||
### 问题2:前台显示"缺少游戏ID参数"
|
||
|
||
**原因**:URL 中没有 `game_id` 参数
|
||
|
||
**解决**:
|
||
```bash
|
||
# 错误的URL
|
||
http://yourdomain.com/live
|
||
|
||
# 正确的URL
|
||
http://yourdomain.com/live?game_id=1 # 骰子游戏
|
||
http://yourdomain.com/live?game_id=2 # Xóc Đĩa
|
||
```
|
||
|
||
---
|
||
|
||
### 问题3:开奖模态框显示错误的输入界面
|
||
|
||
**原因**:游戏类型获取失败
|
||
|
||
**解决**:
|
||
1. 检查游戏记录的 `type` 字段是否正确
|
||
```sql
|
||
SELECT id, name, type FROM games;
|
||
-- 应该看到:
|
||
-- 1 | Tài Xỉu | dice
|
||
-- 2 | Xóc Đĩa | xocdia
|
||
```
|
||
|
||
2. 如果类型不正确,执行修复SQL
|
||
```sql
|
||
UPDATE games SET type='xocdia' WHERE name LIKE '%Xóc%';
|
||
```
|
||
|
||
---
|
||
|
||
## 技术细节
|
||
|
||
### 期号格式
|
||
|
||
```
|
||
G{GameID}{YYYYMMDD}{NNNN}
|
||
|
||
示例:
|
||
G120260108001 # 骰子游戏(game_id=1)2026年1月8日第1期
|
||
G220260108001 # Xóc Đĩa(game_id=2)2026年1月8日第1期
|
||
```
|
||
|
||
### 期号状态流转
|
||
|
||
```
|
||
pending (待开奖)
|
||
↓
|
||
locked (已封盘)
|
||
↓
|
||
drawn (已开奖)
|
||
↓
|
||
settled (已结算)
|
||
```
|
||
|
||
### 并发控制
|
||
|
||
- 每个游戏独立检查是否有未结束的期号
|
||
- 允许多个游戏同时处于 `pending` 状态
|
||
- 不允许同一游戏有多个未结束的期号
|
||
|
||
---
|
||
|
||
## 兼容性说明
|
||
|
||
### 向后兼容
|
||
|
||
- 现有的骰子游戏功能完全保留
|
||
- 数据库结构无变更
|
||
- 前台URL需要添加 `game_id` 参数
|
||
|
||
### 升级建议
|
||
|
||
1. **更新前台链接**
|
||
- 将所有 `/live` 链接改为 `/live?game_id=X`
|
||
- 首页游戏卡片已经正确传递 `game_id`
|
||
|
||
2. **清理旧期号**(可选)
|
||
- 如果有未关联游戏的期号,建议手动关联
|
||
```sql
|
||
UPDATE periods SET game_id=1 WHERE game_id IS NULL OR game_id=0;
|
||
```
|
||
|
||
---
|
||
|
||
## 最后更新
|
||
2026-01-08
|
||
|
||
## 相关文档
|
||
- [Xóc Đĩa 集成指南](./XOCDIA_INTEGRATION_GUIDE.md)
|
||
- [宝塔快速部署修复指南](./BAOTA_DEPLOYMENT_QUICK_FIX.md)
|