482 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
		
		
			
		
	
	
			482 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| 
								 | 
							
								<!DOCTYPE html>
							 | 
						|||
| 
								 | 
							
								<html lang="zh-CN">
							 | 
						|||
| 
								 | 
							
								<head>
							 | 
						|||
| 
								 | 
							
								    <meta charset="UTF-8">
							 | 
						|||
| 
								 | 
							
								    <meta name="viewport" content="width=device-width, initial-scale=1.0">
							 | 
						|||
| 
								 | 
							
								    <title>WebSocket 测试工具</title>
							 | 
						|||
| 
								 | 
							
								    <style>
							 | 
						|||
| 
								 | 
							
								        * {
							 | 
						|||
| 
								 | 
							
								            box-sizing: border-box;
							 | 
						|||
| 
								 | 
							
								            margin: 0;
							 | 
						|||
| 
								 | 
							
								            padding: 0;
							 | 
						|||
| 
								 | 
							
								            font-family: 'Arial', 'Microsoft YaHei', sans-serif;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        body {
							 | 
						|||
| 
								 | 
							
								            max-width: 1200px;
							 | 
						|||
| 
								 | 
							
								            margin: 20px auto;
							 | 
						|||
| 
								 | 
							
								            padding: 0 20px;
							 | 
						|||
| 
								 | 
							
								            background-color: #f5f7fa;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .container {
							 | 
						|||
| 
								 | 
							
								            background: white;
							 | 
						|||
| 
								 | 
							
								            border-radius: 8px;
							 | 
						|||
| 
								 | 
							
								            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
							 | 
						|||
| 
								 | 
							
								            padding: 25px;
							 | 
						|||
| 
								 | 
							
								            margin-bottom: 20px;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        h1 {
							 | 
						|||
| 
								 | 
							
								            color: #2c3e50;
							 | 
						|||
| 
								 | 
							
								            margin-bottom: 20px;
							 | 
						|||
| 
								 | 
							
								            font-size: 24px;
							 | 
						|||
| 
								 | 
							
								            border-bottom: 2px solid #3498db;
							 | 
						|||
| 
								 | 
							
								            padding-bottom: 10px;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .status-bar {
							 | 
						|||
| 
								 | 
							
								            display: flex;
							 | 
						|||
| 
								 | 
							
								            align-items: center;
							 | 
						|||
| 
								 | 
							
								            gap: 15px;
							 | 
						|||
| 
								 | 
							
								            margin-bottom: 20px;
							 | 
						|||
| 
								 | 
							
								            padding: 12px 15px;
							 | 
						|||
| 
								 | 
							
								            background-color: #f8f9fa;
							 | 
						|||
| 
								 | 
							
								            border-radius: 6px;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .status-label {
							 | 
						|||
| 
								 | 
							
								            font-weight: bold;
							 | 
						|||
| 
								 | 
							
								            color: #495057;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .status-value {
							 | 
						|||
| 
								 | 
							
								            padding: 4px 10px;
							 | 
						|||
| 
								 | 
							
								            border-radius: 4px;
							 | 
						|||
| 
								 | 
							
								            font-weight: bold;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .status-connected {
							 | 
						|||
| 
								 | 
							
								            background-color: #d4edda;
							 | 
						|||
| 
								 | 
							
								            color: #155724;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .status-disconnected {
							 | 
						|||
| 
								 | 
							
								            background-color: #f8d7da;
							 | 
						|||
| 
								 | 
							
								            color: #721c24;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .status-connecting {
							 | 
						|||
| 
								 | 
							
								            background-color: #fff3cd;
							 | 
						|||
| 
								 | 
							
								            color: #856404;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .btn {
							 | 
						|||
| 
								 | 
							
								            padding: 8px 16px;
							 | 
						|||
| 
								 | 
							
								            border: none;
							 | 
						|||
| 
								 | 
							
								            border-radius: 4px;
							 | 
						|||
| 
								 | 
							
								            cursor: pointer;
							 | 
						|||
| 
								 | 
							
								            font-size: 14px;
							 | 
						|||
| 
								 | 
							
								            font-weight: 500;
							 | 
						|||
| 
								 | 
							
								            transition: background-color 0.2s;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .btn-primary {
							 | 
						|||
| 
								 | 
							
								            background-color: #3498db;
							 | 
						|||
| 
								 | 
							
								            color: white;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .btn-primary:hover {
							 | 
						|||
| 
								 | 
							
								            background-color: #2980b9;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .btn-danger {
							 | 
						|||
| 
								 | 
							
								            background-color: #e74c3c;
							 | 
						|||
| 
								 | 
							
								            color: white;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .btn-danger:hover {
							 | 
						|||
| 
								 | 
							
								            background-color: #c0392b;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .btn-success {
							 | 
						|||
| 
								 | 
							
								            background-color: #2ecc71;
							 | 
						|||
| 
								 | 
							
								            color: white;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .btn-success:hover {
							 | 
						|||
| 
								 | 
							
								            background-color: #27ae60;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .control-group {
							 | 
						|||
| 
								 | 
							
								            display: flex;
							 | 
						|||
| 
								 | 
							
								            gap: 15px;
							 | 
						|||
| 
								 | 
							
								            margin-bottom: 20px;
							 | 
						|||
| 
								 | 
							
								            align-items: center;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .input-group {
							 | 
						|||
| 
								 | 
							
								            display: flex;
							 | 
						|||
| 
								 | 
							
								            gap: 10px;
							 | 
						|||
| 
								 | 
							
								            align-items: center;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .input-group label {
							 | 
						|||
| 
								 | 
							
								            color: #495057;
							 | 
						|||
| 
								 | 
							
								            font-weight: 500;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .input-group input, .input-group select {
							 | 
						|||
| 
								 | 
							
								            padding: 8px 12px;
							 | 
						|||
| 
								 | 
							
								            border: 1px solid #ced4da;
							 | 
						|||
| 
								 | 
							
								            border-radius: 4px;
							 | 
						|||
| 
								 | 
							
								            font-size: 14px;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .message-area {
							 | 
						|||
| 
								 | 
							
								            margin-top: 20px;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .message-input {
							 | 
						|||
| 
								 | 
							
								            width: 100%;
							 | 
						|||
| 
								 | 
							
								            height: 100px;
							 | 
						|||
| 
								 | 
							
								            padding: 12px;
							 | 
						|||
| 
								 | 
							
								            border: 1px solid #ced4da;
							 | 
						|||
| 
								 | 
							
								            border-radius: 6px;
							 | 
						|||
| 
								 | 
							
								            resize: none;
							 | 
						|||
| 
								 | 
							
								            font-size: 14px;
							 | 
						|||
| 
								 | 
							
								            margin-bottom: 10px;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .log-area {
							 | 
						|||
| 
								 | 
							
								            width: 100%;
							 | 
						|||
| 
								 | 
							
								            height: 300px;
							 | 
						|||
| 
								 | 
							
								            padding: 15px;
							 | 
						|||
| 
								 | 
							
								            border: 1px solid #ced4da;
							 | 
						|||
| 
								 | 
							
								            border-radius: 6px;
							 | 
						|||
| 
								 | 
							
								            background-color: #f8f9fa;
							 | 
						|||
| 
								 | 
							
								            overflow-y: auto;
							 | 
						|||
| 
								 | 
							
								            font-size: 14px;
							 | 
						|||
| 
								 | 
							
								            line-height: 1.6;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .log-item {
							 | 
						|||
| 
								 | 
							
								            margin-bottom: 8px;
							 | 
						|||
| 
								 | 
							
								            padding-bottom: 8px;
							 | 
						|||
| 
								 | 
							
								            border-bottom: 1px dashed #e9ecef;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .log-time {
							 | 
						|||
| 
								 | 
							
								            color: #6c757d;
							 | 
						|||
| 
								 | 
							
								            font-size: 12px;
							 | 
						|||
| 
								 | 
							
								            margin-right: 10px;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .log-send {
							 | 
						|||
| 
								 | 
							
								            color: #2980b9;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .log-receive {
							 | 
						|||
| 
								 | 
							
								            color: #27ae60;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .log-status {
							 | 
						|||
| 
								 | 
							
								            color: #856404;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        .log-error {
							 | 
						|||
| 
								 | 
							
								            color: #e74c3c;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								    </style>
							 | 
						|||
| 
								 | 
							
								</head>
							 | 
						|||
| 
								 | 
							
								<body>
							 | 
						|||
| 
								 | 
							
								<div class="container">
							 | 
						|||
| 
								 | 
							
								    <h1>WebSocket 测试工具</h1>
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    <!-- 连接状态区 -->
							 | 
						|||
| 
								 | 
							
								    <div class="status-bar">
							 | 
						|||
| 
								 | 
							
								        <div class="status-label">连接状态:</div>
							 | 
						|||
| 
								 | 
							
								        <div id="connectionStatus" class="status-value status-disconnected">未连接</div>
							 | 
						|||
| 
								 | 
							
								        <div class="status-label">服务地址:</div>
							 | 
						|||
| 
								 | 
							
								        <div id="wsUrl" class="status-value">ws://192.168.110.25:8000/ws</div>
							 | 
						|||
| 
								 | 
							
								        <div class="status-label">连接时间:</div>
							 | 
						|||
| 
								 | 
							
								        <div id="connectTime" class="status-value">-</div>
							 | 
						|||
| 
								 | 
							
								    </div>
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    <!-- 控制按钮区 -->
							 | 
						|||
| 
								 | 
							
								    <div class="control-group">
							 | 
						|||
| 
								 | 
							
								        <button id="connectBtn" class="btn btn-primary">建立连接</button>
							 | 
						|||
| 
								 | 
							
								        <button id="disconnectBtn" class="btn btn-danger" disabled>断开连接</button>
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        <!-- 心跳控制 -->
							 | 
						|||
| 
								 | 
							
								        <div class="input-group">
							 | 
						|||
| 
								 | 
							
								            <label>自动心跳:</label>
							 | 
						|||
| 
								 | 
							
								            <select id="autoHeartbeat">
							 | 
						|||
| 
								 | 
							
								                <option value="on">开启</option>
							 | 
						|||
| 
								 | 
							
								                <option value="off">关闭</option>
							 | 
						|||
| 
								 | 
							
								            </select>
							 | 
						|||
| 
								 | 
							
								            <label>间隔(秒):</label>
							 | 
						|||
| 
								 | 
							
								            <input type="number" id="heartbeatInterval" value="30" min="10" max="120" style="width: 80px;">
							 | 
						|||
| 
								 | 
							
								            <button id="sendHeartbeatBtn" class="btn btn-success">手动发送心跳</button>
							 | 
						|||
| 
								 | 
							
								        </div>
							 | 
						|||
| 
								 | 
							
								    </div>
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    <!-- 自定义消息发送区 -->
							 | 
						|||
| 
								 | 
							
								    <div class="message-area">
							 | 
						|||
| 
								 | 
							
								        <h3>发送自定义消息</h3>
							 | 
						|||
| 
								 | 
							
								        <textarea id="messageInput" class="message-input"
							 | 
						|||
| 
								 | 
							
								                  placeholder='示例:{"type":"test","content":"Hello WebSocket"}'>{"type":"test","content":"Hello WebSocket"}</textarea>
							 | 
						|||
| 
								 | 
							
								        <button id="sendMessageBtn" class="btn btn-primary" disabled>发送消息</button>
							 | 
						|||
| 
								 | 
							
								    </div>
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    <!-- 日志显示区 -->
							 | 
						|||
| 
								 | 
							
								    <div class="message-area">
							 | 
						|||
| 
								 | 
							
								        <h3>消息日志</h3>
							 | 
						|||
| 
								 | 
							
								        <div id="logContainer" class="log-area">
							 | 
						|||
| 
								 | 
							
								            <div class="log-item"><span class="log-time">[加载完成]</span> 请点击「建立连接」开始测试</div>
							 | 
						|||
| 
								 | 
							
								        </div>
							 | 
						|||
| 
								 | 
							
								        <button id="clearLogBtn" class="btn btn-primary" style="margin-top: 10px;">清空日志</button>
							 | 
						|||
| 
								 | 
							
								    </div>
							 | 
						|||
| 
								 | 
							
								</div>
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								<script>
							 | 
						|||
| 
								 | 
							
								    // 全局变量
							 | 
						|||
| 
								 | 
							
								    let ws = null;
							 | 
						|||
| 
								 | 
							
								    let heartbeatTimer = null;
							 | 
						|||
| 
								 | 
							
								    const wsUrl = "ws://192.168.110.25:8000/ws";
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    // DOM 元素
							 | 
						|||
| 
								 | 
							
								    const connectionStatus = document.getElementById('connectionStatus');
							 | 
						|||
| 
								 | 
							
								    const connectTime = document.getElementById('connectTime');
							 | 
						|||
| 
								 | 
							
								    const connectBtn = document.getElementById('connectBtn');
							 | 
						|||
| 
								 | 
							
								    const disconnectBtn = document.getElementById('disconnectBtn');
							 | 
						|||
| 
								 | 
							
								    const sendMessageBtn = document.getElementById('sendMessageBtn');
							 | 
						|||
| 
								 | 
							
								    const sendHeartbeatBtn = document.getElementById('sendHeartbeatBtn');
							 | 
						|||
| 
								 | 
							
								    const autoHeartbeat = document.getElementById('autoHeartbeat');
							 | 
						|||
| 
								 | 
							
								    const heartbeatInterval = document.getElementById('heartbeatInterval');
							 | 
						|||
| 
								 | 
							
								    const messageInput = document.getElementById('messageInput');
							 | 
						|||
| 
								 | 
							
								    const logContainer = document.getElementById('logContainer');
							 | 
						|||
| 
								 | 
							
								    const clearLogBtn = document.getElementById('clearLogBtn');
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    // 工具函数:添加日志
							 | 
						|||
| 
								 | 
							
								    function addLog(content, type = 'status') {
							 | 
						|||
| 
								 | 
							
								        const now = new Date().toLocaleString('zh-CN', {
							 | 
						|||
| 
								 | 
							
								            year: 'numeric', month: '2-digit', day: '2-digit',
							 | 
						|||
| 
								 | 
							
								            hour: '2-digit', minute: '2-digit', second: '2-digit'
							 | 
						|||
| 
								 | 
							
								        });
							 | 
						|||
| 
								 | 
							
								        const logItem = document.createElement('div');
							 | 
						|||
| 
								 | 
							
								        logItem.className = 'log-item';
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        let logClass = '';
							 | 
						|||
| 
								 | 
							
								        switch (type) {
							 | 
						|||
| 
								 | 
							
								            case 'send':
							 | 
						|||
| 
								 | 
							
								                logClass = 'log-send';
							 | 
						|||
| 
								 | 
							
								                break;
							 | 
						|||
| 
								 | 
							
								            case 'receive':
							 | 
						|||
| 
								 | 
							
								                logClass = 'log-receive';
							 | 
						|||
| 
								 | 
							
								                break;
							 | 
						|||
| 
								 | 
							
								            case 'error':
							 | 
						|||
| 
								 | 
							
								                logClass = 'log-error';
							 | 
						|||
| 
								 | 
							
								                break;
							 | 
						|||
| 
								 | 
							
								            default:
							 | 
						|||
| 
								 | 
							
								                logClass = 'log-status';
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        logItem.innerHTML = `<span class="log-time">[${now}]</span> <span class="${logClass}">${content}</span>`;
							 | 
						|||
| 
								 | 
							
								        logContainer.appendChild(logItem);
							 | 
						|||
| 
								 | 
							
								        // 滚动到最新日志
							 | 
						|||
| 
								 | 
							
								        logContainer.scrollTop = logContainer.scrollHeight;
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    // 工具函数:格式化JSON(便于日志显示)
							 | 
						|||
| 
								 | 
							
								    function formatJson(jsonStr) {
							 | 
						|||
| 
								 | 
							
								        try {
							 | 
						|||
| 
								 | 
							
								            const obj = JSON.parse(jsonStr);
							 | 
						|||
| 
								 | 
							
								            return JSON.stringify(obj, null, 2);
							 | 
						|||
| 
								 | 
							
								        } catch (e) {
							 | 
						|||
| 
								 | 
							
								            return jsonStr; // 非JSON格式直接返回
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    // 建立WebSocket连接
							 | 
						|||
| 
								 | 
							
								    function connectWebSocket() {
							 | 
						|||
| 
								 | 
							
								        if (ws) {
							 | 
						|||
| 
								 | 
							
								            addLog('已存在连接,无需重复建立', 'error');
							 | 
						|||
| 
								 | 
							
								            return;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        try {
							 | 
						|||
| 
								 | 
							
								            ws = new WebSocket(wsUrl);
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								            // 连接成功
							 | 
						|||
| 
								 | 
							
								            ws.onopen = function () {
							 | 
						|||
| 
								 | 
							
								                connectionStatus.className = 'status-value status-connected';
							 | 
						|||
| 
								 | 
							
								                connectionStatus.textContent = '已连接';
							 | 
						|||
| 
								 | 
							
								                const now = new Date().toLocaleString('zh-CN');
							 | 
						|||
| 
								 | 
							
								                connectTime.textContent = now;
							 | 
						|||
| 
								 | 
							
								                addLog(`连接成功!服务地址:${wsUrl}`, 'status');
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								                // 更新按钮状态
							 | 
						|||
| 
								 | 
							
								                connectBtn.disabled = true;
							 | 
						|||
| 
								 | 
							
								                disconnectBtn.disabled = false;
							 | 
						|||
| 
								 | 
							
								                sendMessageBtn.disabled = false;
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								                // 开启自动心跳(默认开启)
							 | 
						|||
| 
								 | 
							
								                if (autoHeartbeat.value === 'on') {
							 | 
						|||
| 
								 | 
							
								                    startAutoHeartbeat();
							 | 
						|||
| 
								 | 
							
								                }
							 | 
						|||
| 
								 | 
							
								            };
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								            // 接收消息
							 | 
						|||
| 
								 | 
							
								            ws.onmessage = function (event) {
							 | 
						|||
| 
								 | 
							
								                const message = event.data;
							 | 
						|||
| 
								 | 
							
								                addLog(`收到消息:\n${formatJson(message)}`, 'receive');
							 | 
						|||
| 
								 | 
							
								            };
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								            // 连接关闭
							 | 
						|||
| 
								 | 
							
								            ws.onclose = function (event) {
							 | 
						|||
| 
								 | 
							
								                connectionStatus.className = 'status-value status-disconnected';
							 | 
						|||
| 
								 | 
							
								                connectionStatus.textContent = '已断开';
							 | 
						|||
| 
								 | 
							
								                addLog(`连接断开!代码:${event.code},原因:${event.reason || '未知'}`, 'status');
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								                // 清除自动心跳
							 | 
						|||
| 
								 | 
							
								                stopAutoHeartbeat();
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								                // 更新按钮状态
							 | 
						|||
| 
								 | 
							
								                connectBtn.disabled = false;
							 | 
						|||
| 
								 | 
							
								                disconnectBtn.disabled = true;
							 | 
						|||
| 
								 | 
							
								                sendMessageBtn.disabled = true;
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								                // 重置WebSocket对象
							 | 
						|||
| 
								 | 
							
								                ws = null;
							 | 
						|||
| 
								 | 
							
								            };
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								            // 连接错误
							 | 
						|||
| 
								 | 
							
								            ws.onerror = function (error) {
							 | 
						|||
| 
								 | 
							
								                addLog(`连接错误:${error.message || '未知错误'}`, 'error');
							 | 
						|||
| 
								 | 
							
								            };
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        } catch (e) {
							 | 
						|||
| 
								 | 
							
								            addLog(`建立连接失败:${e.message}`, 'error');
							 | 
						|||
| 
								 | 
							
								            ws = null;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    // 断开WebSocket连接
							 | 
						|||
| 
								 | 
							
								    function disconnectWebSocket() {
							 | 
						|||
| 
								 | 
							
								        if (!ws) {
							 | 
						|||
| 
								 | 
							
								            addLog('当前无连接,无需断开', 'error');
							 | 
						|||
| 
								 | 
							
								            return;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        ws.close(1000, '手动断开连接');
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    // 发送心跳消息(符合约定格式:{"timestamp":xxxxx, "type":"heartbeat"})
							 | 
						|||
| 
								 | 
							
								    function sendHeartbeat() {
							 | 
						|||
| 
								 | 
							
								        if (!ws || ws.readyState !== WebSocket.OPEN) {
							 | 
						|||
| 
								 | 
							
								            addLog('发送心跳失败:当前无有效连接', 'error');
							 | 
						|||
| 
								 | 
							
								            return;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        const heartbeatMsg = {
							 | 
						|||
| 
								 | 
							
								            timestamp: Date.now(), // 当前毫秒时间戳
							 | 
						|||
| 
								 | 
							
								            type: "heartbeat"
							 | 
						|||
| 
								 | 
							
								        };
							 | 
						|||
| 
								 | 
							
								        const msgStr = JSON.stringify(heartbeatMsg);
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        ws.send(msgStr);
							 | 
						|||
| 
								 | 
							
								        addLog(`发送心跳:\n${formatJson(msgStr)}`, 'send');
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    // 开启自动心跳
							 | 
						|||
| 
								 | 
							
								    function startAutoHeartbeat() {
							 | 
						|||
| 
								 | 
							
								        // 先停止已有定时器
							 | 
						|||
| 
								 | 
							
								        stopAutoHeartbeat();
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        const interval = parseInt(heartbeatInterval.value) * 1000;
							 | 
						|||
| 
								 | 
							
								        if (isNaN(interval) || interval < 10000) {
							 | 
						|||
| 
								 | 
							
								            addLog('自动心跳间隔无效,已重置为30秒', 'error');
							 | 
						|||
| 
								 | 
							
								            heartbeatInterval.value = 30;
							 | 
						|||
| 
								 | 
							
								            return startAutoHeartbeat();
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        addLog(`开启自动心跳,间隔:${heartbeatInterval.value}秒`, 'status');
							 | 
						|||
| 
								 | 
							
								        heartbeatTimer = setInterval(sendHeartbeat, interval);
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    // 停止自动心跳
							 | 
						|||
| 
								 | 
							
								    function stopAutoHeartbeat() {
							 | 
						|||
| 
								 | 
							
								        if (heartbeatTimer) {
							 | 
						|||
| 
								 | 
							
								            clearInterval(heartbeatTimer);
							 | 
						|||
| 
								 | 
							
								            heartbeatTimer = null;
							 | 
						|||
| 
								 | 
							
								            addLog('已停止自动心跳', 'status');
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    // 发送自定义消息
							 | 
						|||
| 
								 | 
							
								    function sendCustomMessage() {
							 | 
						|||
| 
								 | 
							
								        if (!ws || ws.readyState !== WebSocket.OPEN) {
							 | 
						|||
| 
								 | 
							
								            addLog('发送消息失败:当前无有效连接', 'error');
							 | 
						|||
| 
								 | 
							
								            return;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        const msgStr = messageInput.value.trim();
							 | 
						|||
| 
								 | 
							
								        if (!msgStr) {
							 | 
						|||
| 
								 | 
							
								            addLog('发送消息失败:消息内容不能为空', 'error');
							 | 
						|||
| 
								 | 
							
								            return;
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								        try {
							 | 
						|||
| 
								 | 
							
								            // 验证JSON格式(可选,仅提示不强制)
							 | 
						|||
| 
								 | 
							
								            JSON.parse(msgStr);
							 | 
						|||
| 
								 | 
							
								            ws.send(msgStr);
							 | 
						|||
| 
								 | 
							
								            addLog(`发送自定义消息:\n${formatJson(msgStr)}`, 'send');
							 | 
						|||
| 
								 | 
							
								        } catch (e) {
							 | 
						|||
| 
								 | 
							
								            addLog(`JSON格式错误:${e.message},仍尝试发送原始内容`, 'error');
							 | 
						|||
| 
								 | 
							
								            ws.send(msgStr);
							 | 
						|||
| 
								 | 
							
								            addLog(`发送自定义消息(非JSON):\n${msgStr}`, 'send');
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								    }
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    // 绑定按钮事件
							 | 
						|||
| 
								 | 
							
								    connectBtn.addEventListener('click', connectWebSocket);
							 | 
						|||
| 
								 | 
							
								    disconnectBtn.addEventListener('click', disconnectWebSocket);
							 | 
						|||
| 
								 | 
							
								    sendMessageBtn.addEventListener('click', sendCustomMessage);
							 | 
						|||
| 
								 | 
							
								    sendHeartbeatBtn.addEventListener('click', sendHeartbeat);
							 | 
						|||
| 
								 | 
							
								    clearLogBtn.addEventListener('click', () => {
							 | 
						|||
| 
								 | 
							
								        logContainer.innerHTML = '<div class="log-item"><span class="log-time">[日志已清空]</span> 请继续操作...</div>';
							 | 
						|||
| 
								 | 
							
								    });
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    // 自动心跳开关变更事件
							 | 
						|||
| 
								 | 
							
								    autoHeartbeat.addEventListener('change', function () {
							 | 
						|||
| 
								 | 
							
								        if (ws && ws.readyState === WebSocket.OPEN) {
							 | 
						|||
| 
								 | 
							
								            if (this.value === 'on') {
							 | 
						|||
| 
								 | 
							
								                startAutoHeartbeat();
							 | 
						|||
| 
								 | 
							
								            } else {
							 | 
						|||
| 
								 | 
							
								                stopAutoHeartbeat();
							 | 
						|||
| 
								 | 
							
								            }
							 | 
						|||
| 
								 | 
							
								        } else {
							 | 
						|||
| 
								 | 
							
								            addLog('需先建立有效连接才能控制自动心跳', 'error');
							 | 
						|||
| 
								 | 
							
								            // 重置选择
							 | 
						|||
| 
								 | 
							
								            this.value = 'off';
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								    });
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    // 心跳间隔变更事件(实时生效)
							 | 
						|||
| 
								 | 
							
								    heartbeatInterval.addEventListener('change', function () {
							 | 
						|||
| 
								 | 
							
								        if (autoHeartbeat.value === 'on' && ws && ws.readyState === WebSocket.OPEN) {
							 | 
						|||
| 
								 | 
							
								            startAutoHeartbeat();
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								    });
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								    // 快捷键支持(Ctrl+Enter发送消息)
							 | 
						|||
| 
								 | 
							
								    messageInput.addEventListener('keydown', function (e) {
							 | 
						|||
| 
								 | 
							
								        if (e.ctrlKey && e.key === 'Enter') {
							 | 
						|||
| 
								 | 
							
								            sendCustomMessage();
							 | 
						|||
| 
								 | 
							
								            e.preventDefault();
							 | 
						|||
| 
								 | 
							
								        }
							 | 
						|||
| 
								 | 
							
								    });
							 | 
						|||
| 
								 | 
							
								</script>
							 | 
						|||
| 
								 | 
							
								</body>
							 | 
						|||
| 
								 | 
							
								</html>
							 |