Files
pk10/Docs/XOCDIA_API_DOCUMENTATION.md

1481 lines
32 KiB
Markdown
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 = `
<span class="bet-type">${this.getBetTypeLabel(bet.bet_type)}</span>
<span class="bet-amount">${bet.bet_amount}</span>
<span class="potential-win">可赢 ${bet.potential_win}</span>
`;
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