Files
pk10/Docs/XOCDIA_API_DOCUMENTATION.md

32 KiB
Executable File
Raw Permalink Blame History

Xóc Đĩa 游戏 - 前端 API 接口文档

版本: v1.0.0 更新日期: 2026-01-08 基础URL: http://yourdomain.com


📋 目录

  1. 概述
  2. WebSocket 实时通信
  3. HTTP REST API
  4. 数据结构定义
  5. 错误码说明
  6. 前端对接流程

概述

技术栈建议

  • WebSocket: 实时游戏状态、投注更新、开奖推送
  • HTTP REST API: 查询房间信息、历史记录、用户余额

认证方式

所有 API 请求需要用户已登录,Session Cookie 会自动携带。

// 前端登录后 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

连接示例

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 - 期号开始

说明: 新期号开始,进入下注阶段

{
    "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
            }
        }
    }
}

前端处理:

function handleGameStart(data) {
    // 1. 显示期号信息
    document.getElementById('period-number').textContent = data.period_number;

    // 2. 启动倒计时
    startCountdown(data.countdown);

    // 3. 开放投注按钮
    enableBettingButtons();

    // 4. 清空上期投注记录
    clearPreviousBets();
}

2. COUNTDOWN_UPDATE - 倒计时更新

说明: 每秒推送一次剩余时间

{
    "event": "COUNTDOWN_UPDATE",
    "data": {
        "period_id": 12345,
        "remaining": 25,
        "state": "betting"
    }
}

前端处理:

function handleCountdownUpdate(data) {
    document.getElementById('countdown').textContent = data.remaining + '秒';

    if (data.remaining <= 5) {
        // 最后 5 秒闪烁提醒
        document.getElementById('countdown').classList.add('urgent');
    }
}

3. BET_SUCCESS - 投注成功

说明: 用户投注成功的确认

{
    "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"
    }
}

前端处理:

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 - 封盘

说明: 下注阶段结束,不再接受投注

{
    "event": "BETTING_CLOSED",
    "data": {
        "period_id": 12345,
        "period_number": "20260108-001",
        "state": "shaking",
        "message": "投注已封盘,等待开奖...",
        "total_bets_count": 150,
        "total_bets_amount": 25000
    }
}

前端处理:

function handleBettingClosed(data) {
    // 1. 禁用投注按钮
    disableBettingButtons();

    // 2. 停止倒计时
    stopCountdown();

    // 3. 显示"等待开奖"动画
    showShakingAnimation();

    // 4. 显示本期投注统计
    showBetStats(data.total_bets_count, data.total_bets_amount);
}

5. GAME_RESULT - 开奖结果

说明: 开奖结果公布及用户结算信息

{
    "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
                }
            ]
        }
    }
}

前端处理:

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 - 实时投注统计

说明: 实时推送当前期号的投注分布(可选功能)

{
    "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
    }
}

前端处理:

function handleBetStatsUpdate(data) {
    // 更新投注分布图表
    updateBetDistributionChart(data.stats);

    // 显示热门投注项
    highlightPopularBets(data.stats);
}

7. CONFIG_UPDATED - 配置更新

说明: 房间配置被管理员修改

{
    "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": "房间配置已更新,下期生效"
    }
}

前端处理:

function handleConfigUpdated(data) {
    // 显示配置更新通知
    showNotification(data.message, 'info');

    // 可选:提前显示新赔率
    previewNewOdds(data.new_config.odds);
}

8. ERROR - 错误通知

说明: 服务器推送的错误信息

{
    "event": "ERROR",
    "data": {
        "code": "BET_AMOUNT_INVALID",
        "message": "投注金额超出限制",
        "details": {
            "min_bet": 10,
            "max_bet": 50000,
            "your_bet": 100000
        }
    }
}

🔼 客户端 → 服务器事件

1. SUBSCRIBE_ROOM - 订阅房间

说明: 连接后立即订阅房间,接收该房间的实时消息

{
    "action": "SUBSCRIBE_ROOM",
    "data": {
        "room_id": 1
    }
}

2. PLACE_BET - 下注

说明: 用户下注

{
    "action": "PLACE_BET",
    "data": {
        "room_id": 1,
        "period_id": 12345,
        "bet_type": "odd",
        "bet_amount": 100,
        "client_timestamp": "2026-01-08T10:00:15Z"
    }
}

服务器响应: BET_SUCCESSERROR 事件


3. GET_GAME_STATE - 获取当前游戏状态

说明: 用户刷新页面或重连后,获取当前游戏状态

{
    "action": "GET_GAME_STATE",
    "data": {
        "room_id": 1
    }
}

服务器响应:

{
    "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 - 获取投注历史

说明: 查询用户历史投注记录

{
    "action": "GET_BET_HISTORY",
    "data": {
        "room_id": 1,
        "limit": 20,
        "offset": 0
    }
}

服务器响应:

{
    "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. 获取房间列表

请求:

GET /api/xocdia/rooms

响应:

{
    "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
            }
        }
    ]
}

前端调用示例:

async function fetchRooms() {
    const response = await fetch('/api/xocdia/rooms', {
        credentials: 'include'
    });
    const data = await response.json();

    if (data.success) {
        renderRoomList(data.data);
    }
}

2. 获取房间详情

请求:

GET /api/xocdia/rooms/{room_id}

响应:

{
    "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. 获取当前期号信息

请求:

GET /api/xocdia/periods/current?room_id=1

响应:

{
    "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": { /* ... */ }
        }
    }
}

前端调用示例:

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 方式,备用)

请求:

POST /api/xocdia/bet
Content-Type: application/json

{
    "room_id": 1,
    "period_id": 12345,
    "bet_type": "odd",
    "bet_amount": 100
}

响应:

{
    "success": true,
    "message": "投注成功",
    "data": {
        "bet_id": 67890,
        "remaining_balance": 9900,
        "potential_win": 100
    }
}

错误响应:

{
    "success": false,
    "code": "INSUFFICIENT_BALANCE",
    "message": "余额不足",
    "details": {
        "current_balance": 50,
        "required": 100
    }
}

前端调用示例:

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. 获取开奖历史

请求:

GET /api/xocdia/periods/history?room_id=1&limit=20&offset=0

响应:

{
    "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. 获取用户投注记录

请求:

GET /api/xocdia/my-bets?room_id=1&limit=20&offset=0

响应:

{
    "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. 获取用户余额

请求:

GET /api/user/balance

响应:

{
    "success": true,
    "data": {
        "balance": 10000.50,
        "currency": "VND"
    }
}

数据结构定义

GameState(游戏状态)

type GameState = 'waiting' | 'betting' | 'shaking' | 'settling' | 'showing';

BetType(投注类型)

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(投注状态)

type BetStatus = 'pending' | 'win' | 'lose' | 'refund';

GameResult(开奖结果)

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(房间配置)

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 连接错误 尝试重连

前端对接流程

第一步:初始化

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 }
        }));
    }
}

第二步:处理服务器事件

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();
        }
    }
}

第三步:投注操作

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 渲染

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;
    }
}

第五步:使用示例

// 初始化游戏
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. 错误处理

// 全局错误处理
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. 性能优化

// 防抖投注(防止重复点击)
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. 测试建议

// 模拟 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