# Xóc Đĩa 游戏 - 前端 API 接口文档
> **版本**: v1.0.0
> **更新日期**: 2026-01-08
> **基础URL**: `http://yourdomain.com`
---
## 📋 目录
1. [概述](#概述)
2. [WebSocket 实时通信](#websocket-实时通信)
3. [HTTP REST API](#http-rest-api)
4. [数据结构定义](#数据结构定义)
5. [错误码说明](#错误码说明)
6. [前端对接流程](#前端对接流程)
---
## 概述
### 技术栈建议
- **WebSocket**: 实时游戏状态、投注更新、开奖推送
- **HTTP REST API**: 查询房间信息、历史记录、用户余额
### 认证方式
所有 API 请求需要用户已登录,Session Cookie 会自动携带。
```javascript
// 前端登录后 Session 自动保存
fetch('/api/auth/login', {
method: 'POST',
credentials: 'include', // 重要:携带 Cookie
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'xxx', password: 'xxx' })
})
```
---
## WebSocket 实时通信
### 连接信息
```
WebSocket URL: ws://yourdomain.com/ws/xocdia
或 (SSL): wss://yourdomain.com/ws/xocdia
```
### 连接示例
```javascript
const ws = new WebSocket('ws://yourdomain.com/ws/xocdia');
ws.onopen = () => {
console.log('WebSocket 连接成功');
// 订阅房间
ws.send(JSON.stringify({
action: 'SUBSCRIBE_ROOM',
data: { room_id: 1 }
}));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
handleServerEvent(message);
};
ws.onerror = (error) => {
console.error('WebSocket 错误:', error);
};
ws.onclose = () => {
console.log('WebSocket 连接关闭');
// 实现重连逻辑
};
```
---
## 🔽 服务器 → 客户端事件
### 1. GAME_START - 期号开始
**说明**: 新期号开始,进入下注阶段
```json
{
"event": "GAME_START",
"data": {
"period_id": 12345,
"period_number": "20260108-001",
"room_id": 1,
"state": "betting",
"countdown": 30,
"server_time": "2026-01-08T10:00:00Z",
"config": {
"odds": {
"even": 1.0,
"odd": 1.0,
"four_red": 10.0,
"four_white": 10.0,
"three_red": 3.5,
"three_white": 3.5
},
"limits": {
"min_bet": 10,
"max_bet": 50000,
"max_payout": 500000
},
"timing": {
"betting_duration": 30
}
}
}
}
```
**前端处理**:
```javascript
function handleGameStart(data) {
// 1. 显示期号信息
document.getElementById('period-number').textContent = data.period_number;
// 2. 启动倒计时
startCountdown(data.countdown);
// 3. 开放投注按钮
enableBettingButtons();
// 4. 清空上期投注记录
clearPreviousBets();
}
```
---
### 2. COUNTDOWN_UPDATE - 倒计时更新
**说明**: 每秒推送一次剩余时间
```json
{
"event": "COUNTDOWN_UPDATE",
"data": {
"period_id": 12345,
"remaining": 25,
"state": "betting"
}
}
```
**前端处理**:
```javascript
function handleCountdownUpdate(data) {
document.getElementById('countdown').textContent = data.remaining + '秒';
if (data.remaining <= 5) {
// 最后 5 秒闪烁提醒
document.getElementById('countdown').classList.add('urgent');
}
}
```
---
### 3. BET_SUCCESS - 投注成功
**说明**: 用户投注成功的确认
```json
{
"event": "BET_SUCCESS",
"data": {
"bet_id": 67890,
"user_id": 456,
"period_id": 12345,
"period_number": "20260108-001",
"bet_type": "odd",
"bet_amount": 100,
"odds": 1.0,
"potential_win": 100,
"remaining_balance": 9900,
"timestamp": "2026-01-08T10:00:15Z"
}
}
```
**前端处理**:
```javascript
function handleBetSuccess(data) {
// 1. 更新余额显示
document.getElementById('balance').textContent = data.remaining_balance;
// 2. 添加到投注列表
addBetToList({
type: data.bet_type,
amount: data.bet_amount,
potentialWin: data.potential_win
});
// 3. 显示成功提示
showToast('投注成功!', 'success');
}
```
---
### 4. BETTING_CLOSED - 封盘
**说明**: 下注阶段结束,不再接受投注
```json
{
"event": "BETTING_CLOSED",
"data": {
"period_id": 12345,
"period_number": "20260108-001",
"state": "shaking",
"message": "投注已封盘,等待开奖...",
"total_bets_count": 150,
"total_bets_amount": 25000
}
}
```
**前端处理**:
```javascript
function handleBettingClosed(data) {
// 1. 禁用投注按钮
disableBettingButtons();
// 2. 停止倒计时
stopCountdown();
// 3. 显示"等待开奖"动画
showShakingAnimation();
// 4. 显示本期投注统计
showBetStats(data.total_bets_count, data.total_bets_amount);
}
```
---
### 5. GAME_RESULT - 开奖结果
**说明**: 开奖结果公布及用户结算信息
```json
{
"event": "GAME_RESULT",
"data": {
"period_id": 12345,
"period_number": "20260108-001",
"result": {
"coins": [1, 1, 1, 0],
"pattern": "3_red_1_white",
"red_count": 3,
"white_count": 1,
"display_text": "3 Đen 1 Trắng"
},
"settlement": {
"your_bets": [
{
"bet_id": 67890,
"bet_type": "odd",
"bet_amount": 100,
"odds": 1.0,
"is_win": true,
"win_amount": 100,
"commission": 0,
"net_profit": 100,
"status": "win",
"special_rule_applied": false
}
],
"total_bet_amount": 100,
"total_win_amount": 100,
"net_profit": 100,
"new_balance": 10000
},
"statistics": {
"total_players": 50,
"total_bets_amount": 50000,
"big_winners": [
{
"username": "player***",
"win_amount": 5000
}
]
}
}
}
```
**前端处理**:
```javascript
function handleGameResult(data) {
// 1. 显示开奖动画
showResultAnimation(data.result.coins);
// 2. 更新余额
document.getElementById('balance').textContent = data.settlement.new_balance;
// 3. 显示输赢结果
data.settlement.your_bets.forEach(bet => {
if (bet.is_win) {
showWinNotification(bet.net_profit);
} else {
showLoseNotification(bet.bet_amount);
}
});
// 4. 更新投注历史
updateBetHistory(data);
// 5. 显示本期统计
showGameStatistics(data.statistics);
}
```
---
### 6. BET_STATS_UPDATE - 实时投注统计
**说明**: 实时推送当前期号的投注分布(可选功能)
```json
{
"event": "BET_STATS_UPDATE",
"data": {
"period_id": 12345,
"stats": {
"even": {
"amount": 10000,
"count": 50,
"percentage": 40
},
"odd": {
"amount": 8000,
"count": 40,
"percentage": 32
},
"four_red": {
"amount": 3000,
"count": 15,
"percentage": 12
}
},
"total_amount": 25000,
"total_count": 150
}
}
```
**前端处理**:
```javascript
function handleBetStatsUpdate(data) {
// 更新投注分布图表
updateBetDistributionChart(data.stats);
// 显示热门投注项
highlightPopularBets(data.stats);
}
```
---
### 7. CONFIG_UPDATED - 配置更新
**说明**: 房间配置被管理员修改
```json
{
"event": "CONFIG_UPDATED",
"data": {
"room_id": 1,
"new_config": {
"odds": {
"even": 1.0,
"odd": 1.0,
"four_red": 12.0,
"four_white": 12.0,
"three_red": 4.0,
"three_white": 4.0
},
"limits": {
"min_bet": 10,
"max_bet": 100000
}
},
"effective_from": "next_period",
"message": "房间配置已更新,下期生效"
}
}
```
**前端处理**:
```javascript
function handleConfigUpdated(data) {
// 显示配置更新通知
showNotification(data.message, 'info');
// 可选:提前显示新赔率
previewNewOdds(data.new_config.odds);
}
```
---
### 8. ERROR - 错误通知
**说明**: 服务器推送的错误信息
```json
{
"event": "ERROR",
"data": {
"code": "BET_AMOUNT_INVALID",
"message": "投注金额超出限制",
"details": {
"min_bet": 10,
"max_bet": 50000,
"your_bet": 100000
}
}
}
```
---
## 🔼 客户端 → 服务器事件
### 1. SUBSCRIBE_ROOM - 订阅房间
**说明**: 连接后立即订阅房间,接收该房间的实时消息
```json
{
"action": "SUBSCRIBE_ROOM",
"data": {
"room_id": 1
}
}
```
---
### 2. PLACE_BET - 下注
**说明**: 用户下注
```json
{
"action": "PLACE_BET",
"data": {
"room_id": 1,
"period_id": 12345,
"bet_type": "odd",
"bet_amount": 100,
"client_timestamp": "2026-01-08T10:00:15Z"
}
}
```
**服务器响应**: `BET_SUCCESS` 或 `ERROR` 事件
---
### 3. GET_GAME_STATE - 获取当前游戏状态
**说明**: 用户刷新页面或重连后,获取当前游戏状态
```json
{
"action": "GET_GAME_STATE",
"data": {
"room_id": 1
}
}
```
**服务器响应**:
```json
{
"event": "GAME_STATE",
"data": {
"room_id": 1,
"current_period": {
"period_id": 12345,
"period_number": "20260108-001",
"state": "betting",
"countdown": 18,
"started_at": "2026-01-08T10:00:00Z"
},
"your_bets": [
{
"bet_id": 67890,
"bet_type": "odd",
"bet_amount": 100,
"status": "pending"
}
],
"config": { /* 房间配置 */ }
}
}
```
---
### 4. GET_BET_HISTORY - 获取投注历史
**说明**: 查询用户历史投注记录
```json
{
"action": "GET_BET_HISTORY",
"data": {
"room_id": 1,
"limit": 20,
"offset": 0
}
}
```
**服务器响应**:
```json
{
"event": "BET_HISTORY",
"data": {
"bets": [
{
"period_number": "20260108-001",
"bet_type": "odd",
"bet_amount": 100,
"status": "win",
"net_profit": 95,
"result": {
"coins": [1, 1, 1, 0],
"pattern": "3_red_1_white"
},
"created_at": "2026-01-08T10:00:15Z"
}
],
"total": 150,
"limit": 20,
"offset": 0
}
}
```
---
## HTTP REST API
### 1. 获取房间列表
**请求**:
```http
GET /api/xocdia/rooms
```
**响应**:
```json
{
"success": true,
"data": [
{
"room_id": 1,
"room_name": "Bàn Thu Phế",
"room_type": "commission",
"status": 1,
"odds": {
"even": 1.0,
"odd": 1.0,
"four_red": 10.0,
"four_white": 10.0,
"three_red": 3.5,
"three_white": 3.5
},
"limits": {
"min_bet": 10,
"max_bet": 50000,
"max_payout": 500000
},
"online_players": 50,
"current_period": {
"period_number": "20260108-001",
"state": "betting",
"countdown": 25
}
}
]
}
```
**前端调用示例**:
```javascript
async function fetchRooms() {
const response = await fetch('/api/xocdia/rooms', {
credentials: 'include'
});
const data = await response.json();
if (data.success) {
renderRoomList(data.data);
}
}
```
---
### 2. 获取房间详情
**请求**:
```http
GET /api/xocdia/rooms/{room_id}
```
**响应**:
```json
{
"success": true,
"data": {
"room_id": 1,
"room_name": "Bàn Thu Phế",
"room_type": "commission",
"commission_rate": 0.05,
"odds": { /* ... */ },
"limits": { /* ... */ },
"timing": {
"betting_duration": 30,
"result_display_duration": 8
},
"statistics": {
"today_periods": 50,
"today_bets": 5000,
"today_turnover": 500000
}
}
}
```
---
### 3. 获取当前期号信息
**请求**:
```http
GET /api/xocdia/periods/current?room_id=1
```
**响应**:
```json
{
"success": true,
"data": {
"period_id": 12345,
"period_number": "20260108-001",
"room_id": 1,
"state": "betting",
"started_at": "2026-01-08T10:00:00Z",
"countdown": 25,
"config_snapshot": {
"odds": { /* ... */ },
"limits": { /* ... */ }
}
}
}
```
**前端调用示例**:
```javascript
async function getCurrentPeriod(roomId) {
const response = await fetch(`/api/xocdia/periods/current?room_id=${roomId}`, {
credentials: 'include'
});
const data = await response.json();
if (data.success && data.data) {
return data.data;
}
return null;
}
```
---
### 4. 下注(HTTP 方式,备用)
**请求**:
```http
POST /api/xocdia/bet
Content-Type: application/json
{
"room_id": 1,
"period_id": 12345,
"bet_type": "odd",
"bet_amount": 100
}
```
**响应**:
```json
{
"success": true,
"message": "投注成功",
"data": {
"bet_id": 67890,
"remaining_balance": 9900,
"potential_win": 100
}
}
```
**错误响应**:
```json
{
"success": false,
"code": "INSUFFICIENT_BALANCE",
"message": "余额不足",
"details": {
"current_balance": 50,
"required": 100
}
}
```
**前端调用示例**:
```javascript
async function placeBet(roomId, periodId, betType, betAmount) {
try {
const response = await fetch('/api/xocdia/bet', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
room_id: roomId,
period_id: periodId,
bet_type: betType,
bet_amount: betAmount
})
});
const data = await response.json();
if (data.success) {
// 更新余额
updateBalance(data.data.remaining_balance);
showToast('投注成功', 'success');
} else {
showToast(data.message, 'error');
}
return data;
} catch (error) {
console.error('投注失败:', error);
showToast('网络错误,请重试', 'error');
}
}
```
---
### 5. 获取开奖历史
**请求**:
```http
GET /api/xocdia/periods/history?room_id=1&limit=20&offset=0
```
**响应**:
```json
{
"success": true,
"data": {
"periods": [
{
"period_number": "20260108-001",
"result": {
"coins": [1, 1, 1, 0],
"pattern": "3_red_1_white",
"display_text": "3 Đen 1 Trắng"
},
"settled_at": "2026-01-08T10:01:30Z",
"total_bets": 150,
"total_amount": 25000
}
],
"total": 500,
"limit": 20,
"offset": 0
}
}
```
---
### 6. 获取用户投注记录
**请求**:
```http
GET /api/xocdia/my-bets?room_id=1&limit=20&offset=0
```
**响应**:
```json
{
"success": true,
"data": {
"bets": [
{
"bet_id": 67890,
"period_number": "20260108-001",
"bet_type": "odd",
"bet_amount": 100,
"odds": 1.0,
"status": "win",
"win_amount": 100,
"commission": 5,
"net_profit": 95,
"result": {
"coins": [1, 1, 1, 0],
"pattern": "3_red_1_white"
},
"created_at": "2026-01-08T10:00:15Z",
"settled_at": "2026-01-08T10:01:30Z"
}
],
"total": 150,
"limit": 20,
"offset": 0,
"summary": {
"total_bet_amount": 15000,
"total_win_amount": 8000,
"net_profit": -7000
}
}
}
```
---
### 7. 获取用户余额
**请求**:
```http
GET /api/user/balance
```
**响应**:
```json
{
"success": true,
"data": {
"balance": 10000.50,
"currency": "VND"
}
}
```
---
## 数据结构定义
### GameState(游戏状态)
```typescript
type GameState = 'waiting' | 'betting' | 'shaking' | 'settling' | 'showing';
```
### BetType(投注类型)
```typescript
type BetType =
| 'even' // 双 (Chẵn)
| 'odd' // 单 (Lẻ)
| 'four_red' // 4红 (4 Đen)
| 'four_white' // 4白 (4 Trắng)
| 'three_red' // 3红1白 (3 Đen 1 Trắng)
| 'three_white'; // 1红3白 (1 Đen 3 Trắng)
```
### BetStatus(投注状态)
```typescript
type BetStatus = 'pending' | 'win' | 'lose' | 'refund';
```
### GameResult(开奖结果)
```typescript
interface GameResult {
coins: [number, number, number, number]; // [1,1,1,0] = 3红1白
pattern: string; // '3_red_1_white'
red_count: number; // 3
white_count: number; // 1
display_text: string; // '3 Đen 1 Trắng'
}
```
### RoomConfig(房间配置)
```typescript
interface RoomConfig {
room_id: number;
room_name: string;
room_type: 'commission' | 'special_rule';
status: 0 | 1; // 0=关闭, 1=开放
odds: {
even: number;
odd: number;
four_red: number;
four_white: number;
three_red: number;
three_white: number;
};
limits: {
min_bet: number;
max_bet: number;
max_payout: number;
};
timing: {
betting_duration: number;
result_display_duration: number;
};
commission_rate?: number; // 抽水比例(0.05 = 5%)
special_rule?: {
enabled: boolean;
trigger_result: string;
affected_bet_types: string[];
action: 'refund' | 'half_win' | 'lose';
refund_percentage: number;
};
}
```
---
## 错误码说明
### 客户端错误 (4xx)
| 错误码 | 说明 | 解决方案 |
|-------|------|---------|
| `NOT_LOGGED_IN` | 用户未登录 | 跳转到登录页 |
| `INSUFFICIENT_BALANCE` | 余额不足 | 提示用户充值 |
| `BET_AMOUNT_INVALID` | 投注金额无效 | 检查最小/最大限制 |
| `BET_TYPE_INVALID` | 投注类型无效 | 检查投注类型是否正确 |
| `BETTING_CLOSED` | 已封盘 | 提示用户等待下一期 |
| `PERIOD_NOT_FOUND` | 期号不存在 | 刷新页面获取最新期号 |
| `DUPLICATE_BET` | 重复投注 | 防止重复提交 |
### 服务器错误 (5xx)
| 错误码 | 说明 | 解决方案 |
|-------|------|---------|
| `SETTLEMENT_FAILED` | 结算失败 | 联系客服 |
| `DATABASE_ERROR` | 数据库错误 | 稍后重试 |
| `WEBSOCKET_ERROR` | WebSocket 连接错误 | 尝试重连 |
---
## 前端对接流程
### 第一步:初始化
```javascript
class XocdiaGame {
constructor(roomId) {
this.roomId = roomId;
this.ws = null;
this.currentPeriod = null;
this.userBets = [];
this.balance = 0;
}
async init() {
// 1. 获取房间配置
await this.fetchRoomConfig();
// 2. 获取用户余额
await this.fetchBalance();
// 3. 连接 WebSocket
await this.connectWebSocket();
// 4. 订阅房间
this.subscribeRoom();
// 5. 获取当前游戏状态
this.getGameState();
}
async fetchRoomConfig() {
const response = await fetch(`/api/xocdia/rooms/${this.roomId}`, {
credentials: 'include'
});
const data = await response.json();
if (data.success) {
this.roomConfig = data.data;
this.renderRoomInfo();
}
}
async fetchBalance() {
const response = await fetch('/api/user/balance', {
credentials: 'include'
});
const data = await response.json();
if (data.success) {
this.balance = data.data.balance;
this.updateBalanceDisplay();
}
}
connectWebSocket() {
return new Promise((resolve, reject) => {
this.ws = new WebSocket('ws://yourdomain.com/ws/xocdia');
this.ws.onopen = () => {
console.log('WebSocket 连接成功');
resolve();
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleServerEvent(message);
};
this.ws.onerror = (error) => {
console.error('WebSocket 错误:', error);
reject(error);
};
this.ws.onclose = () => {
console.log('WebSocket 断开,尝试重连...');
setTimeout(() => this.connectWebSocket(), 3000);
};
});
}
subscribeRoom() {
this.ws.send(JSON.stringify({
action: 'SUBSCRIBE_ROOM',
data: { room_id: this.roomId }
}));
}
getGameState() {
this.ws.send(JSON.stringify({
action: 'GET_GAME_STATE',
data: { room_id: this.roomId }
}));
}
}
```
---
### 第二步:处理服务器事件
```javascript
class XocdiaGame {
// ... 上面的代码 ...
handleServerEvent(message) {
switch (message.event) {
case 'GAME_START':
this.handleGameStart(message.data);
break;
case 'COUNTDOWN_UPDATE':
this.handleCountdownUpdate(message.data);
break;
case 'BET_SUCCESS':
this.handleBetSuccess(message.data);
break;
case 'BETTING_CLOSED':
this.handleBettingClosed(message.data);
break;
case 'GAME_RESULT':
this.handleGameResult(message.data);
break;
case 'ERROR':
this.handleError(message.data);
break;
case 'GAME_STATE':
this.handleGameState(message.data);
break;
}
}
handleGameStart(data) {
this.currentPeriod = data;
this.userBets = [];
// UI 更新
document.getElementById('period-number').textContent = data.period_number;
this.startCountdown(data.countdown);
this.enableBettingButtons();
}
handleCountdownUpdate(data) {
document.getElementById('countdown').textContent = data.remaining;
if (data.remaining <= 5) {
document.getElementById('countdown').classList.add('urgent');
}
}
handleBetSuccess(data) {
// 更新余额
this.balance = data.remaining_balance;
this.updateBalanceDisplay();
// 添加到投注列表
this.userBets.push(data);
this.renderUserBets();
// 显示提示
this.showToast('投注成功!', 'success');
}
handleBettingClosed(data) {
this.disableBettingButtons();
this.stopCountdown();
this.showShakingAnimation();
}
handleGameResult(data) {
// 显示开奖动画
this.showResultAnimation(data.result);
// 更新余额
this.balance = data.settlement.new_balance;
this.updateBalanceDisplay();
// 显示输赢结果
this.showSettlementResult(data.settlement);
// 更新历史记录
this.updateHistory(data);
}
handleError(data) {
this.showToast(data.message, 'error');
}
handleGameState(data) {
// 恢复游戏状态(用于刷新后)
this.currentPeriod = data.current_period;
this.userBets = data.your_bets;
if (data.current_period.state === 'betting') {
this.startCountdown(data.current_period.countdown);
this.enableBettingButtons();
}
}
}
```
---
### 第三步:投注操作
```javascript
class XocdiaGame {
// ... 上面的代码 ...
async placeBet(betType, betAmount) {
// 前端验证
if (!this.currentPeriod || this.currentPeriod.state !== 'betting') {
this.showToast('当前不可下注', 'error');
return;
}
if (betAmount < this.roomConfig.limits.min_bet) {
this.showToast(`最小投注 ${this.roomConfig.limits.min_bet}`, 'error');
return;
}
if (betAmount > this.roomConfig.limits.max_bet) {
this.showToast(`最大投注 ${this.roomConfig.limits.max_bet}`, 'error');
return;
}
if (betAmount > this.balance) {
this.showToast('余额不足', 'error');
return;
}
// 通过 WebSocket 发送
this.ws.send(JSON.stringify({
action: 'PLACE_BET',
data: {
room_id: this.roomId,
period_id: this.currentPeriod.period_id,
bet_type: betType,
bet_amount: betAmount,
client_timestamp: new Date().toISOString()
}
}));
// 或通过 HTTP 发送(备用)
// const response = await fetch('/api/xocdia/bet', { ... });
}
// 快捷投注方法
betEven(amount) {
this.placeBet('even', amount);
}
betOdd(amount) {
this.placeBet('odd', amount);
}
betFourRed(amount) {
this.placeBet('four_red', amount);
}
betFourWhite(amount) {
this.placeBet('four_white', amount);
}
betThreeRed(amount) {
this.placeBet('three_red', amount);
}
betThreeWhite(amount) {
this.placeBet('three_white', amount);
}
}
```
---
### 第四步:UI 渲染
```javascript
class XocdiaGame {
// ... 上面的代码 ...
renderRoomInfo() {
document.getElementById('room-name').textContent = this.roomConfig.room_name;
// 渲染赔率
document.getElementById('odds-even').textContent = `1:${this.roomConfig.odds.even}`;
document.getElementById('odds-odd').textContent = `1:${this.roomConfig.odds.odd}`;
document.getElementById('odds-four-red').textContent = `1:${this.roomConfig.odds.four_red}`;
// ...
}
updateBalanceDisplay() {
document.getElementById('balance').textContent = this.balance.toLocaleString();
}
renderUserBets() {
const container = document.getElementById('user-bets-list');
container.innerHTML = '';
this.userBets.forEach(bet => {
const betElement = document.createElement('div');
betElement.className = 'bet-item';
betElement.innerHTML = `
${this.getBetTypeLabel(bet.bet_type)}
${bet.bet_amount}
可赢 ${bet.potential_win}
`;
container.appendChild(betElement);
});
}
showResultAnimation(result) {
const container = document.getElementById('result-animation');
container.innerHTML = '';
result.coins.forEach(coin => {
const coinElement = document.createElement('div');
coinElement.className = coin === 1 ? 'coin red' : 'coin white';
coinElement.textContent = '●';
container.appendChild(coinElement);
});
// 显示结果文字
document.getElementById('result-text').textContent = result.display_text;
}
showSettlementResult(settlement) {
if (settlement.net_profit > 0) {
this.showWinAnimation(settlement.net_profit);
} else if (settlement.net_profit < 0) {
this.showLoseAnimation(Math.abs(settlement.net_profit));
}
}
getBetTypeLabel(betType) {
const labels = {
'even': '双 (Chẵn)',
'odd': '单 (Lẻ)',
'four_red': '4红',
'four_white': '4白',
'three_red': '3红1白',
'three_white': '1红3白'
};
return labels[betType] || betType;
}
}
```
---
### 第五步:使用示例
```javascript
// 初始化游戏
const game = new XocdiaGame(1); // room_id = 1
game.init().then(() => {
console.log('游戏初始化完成');
// 绑定投注按钮
document.getElementById('btn-bet-even').addEventListener('click', () => {
const amount = parseFloat(document.getElementById('bet-amount').value);
game.betEven(amount);
});
document.getElementById('btn-bet-odd').addEventListener('click', () => {
const amount = parseFloat(document.getElementById('bet-amount').value);
game.betOdd(amount);
});
// 快捷金额按钮
document.getElementById('btn-bet-100').addEventListener('click', () => {
document.getElementById('bet-amount').value = 100;
});
document.getElementById('btn-bet-500').addEventListener('click', () => {
document.getElementById('bet-amount').value = 500;
});
}).catch(error => {
console.error('游戏初始化失败:', error);
});
```
---
## 📝 开发建议
### 1. 错误处理
```javascript
// 全局错误处理
window.addEventListener('error', (event) => {
console.error('全局错误:', event.error);
// 上报到错误监控平台
});
// WebSocket 重连策略
class WebSocketReconnect {
constructor(url, maxRetries = 5) {
this.url = url;
this.maxRetries = maxRetries;
this.retryCount = 0;
this.retryDelay = 1000;
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onclose = () => {
if (this.retryCount < this.maxRetries) {
this.retryCount++;
setTimeout(() => {
console.log(`重连中... (${this.retryCount}/${this.maxRetries})`);
this.connect();
}, this.retryDelay * this.retryCount);
} else {
console.error('WebSocket 重连失败');
// 提示用户刷新页面
}
};
this.ws.onopen = () => {
this.retryCount = 0;
};
}
}
```
### 2. 性能优化
```javascript
// 防抖投注(防止重复点击)
const debounceBet = debounce((betType, amount) => {
game.placeBet(betType, amount);
}, 300);
// 虚拟滚动(历史记录)
function renderHistoryWithVirtualScroll(records) {
// 使用虚拟滚动库,只渲染可见部分
}
// 图片懒加载
const lazyImages = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
imageObserver.unobserve(img);
}
});
});
lazyImages.forEach(img => imageObserver.observe(img));
```
### 3. 测试建议
```javascript
// 模拟 WebSocket 消息(用于测试)
function mockGameStart() {
const mockEvent = {
event: 'GAME_START',
data: {
period_id: 99999,
period_number: 'TEST-001',
room_id: 1,
state: 'betting',
countdown: 30,
config: { /* ... */ }
}
};
game.handleServerEvent(mockEvent);
}
function mockGameResult() {
const mockEvent = {
event: 'GAME_RESULT',
data: {
result: {
coins: [1, 1, 1, 0],
pattern: '3_red_1_white'
},
settlement: { /* ... */ }
}
};
game.handleServerEvent(mockEvent);
}
```
---
## 📞 技术支持
如有疑问,请联系后端开发团队。
**文档版本**: v1.0.0
**最后更新**: 2026-01-08
**维护者**: Backend Team