|
|
@ -29,6 +29,7 @@ |
|
|
|
height: 100%; |
|
|
|
z-index: -1; |
|
|
|
opacity: 0.3; |
|
|
|
pointer-events: none; |
|
|
|
} |
|
|
|
.content-container { |
|
|
|
position: relative; |
|
|
@ -49,90 +50,216 @@ |
|
|
|
|
|
|
|
<!-- 内容容器 --> |
|
|
|
<div class="content-container flex flex-col h-full"> |
|
|
|
<!-- 顶部状态栏 --> |
|
|
|
<div class="bg-white dark:bg-gray-800 shadow-sm"> |
|
|
|
<div class="max-w-4xl mx-auto px-4 py-3"> |
|
|
|
<!-- WebSocket地址输入区域 --> |
|
|
|
<div class="flex items-center space-x-2 mb-3"> |
|
|
|
<input type="text" |
|
|
|
id="wsAddress" |
|
|
|
placeholder="输入WebSocket地址" |
|
|
|
class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 |
|
|
|
rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 |
|
|
|
dark:bg-gray-700 dark:text-white transition-colors duration-200"> |
|
|
|
</div> |
|
|
|
<div class="flex justify-between items-center"> |
|
|
|
<div class="flex items-center space-x-2"> |
|
|
|
<div id="status" class="flex items-center space-x-2"> |
|
|
|
<div class="w-2 h-2 rounded-full bg-red-500 status-dot"></div> |
|
|
|
<span class="text-sm text-gray-600 dark:text-gray-300">未连接</span> |
|
|
|
<!-- 消息区域 --> |
|
|
|
<div class="flex-1 overflow-hidden"> |
|
|
|
<div class="max-w-7xl mx-auto h-full px-4 py-4"> |
|
|
|
<div class="flex h-full gap-4"> |
|
|
|
<!-- 左侧:连接控制和消息格式 --> |
|
|
|
<div class="w-1/2 flex flex-col gap-4 h-full"> |
|
|
|
<!-- 第一行:连接控制 --> |
|
|
|
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm"> |
|
|
|
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">连接控制</h3> |
|
|
|
<div class="space-y-4"> |
|
|
|
<!-- WebSocket地址输入区域 --> |
|
|
|
<div class="space-y-2"> |
|
|
|
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">WebSocket地址</label> |
|
|
|
<input type="text" |
|
|
|
id="wsAddress" |
|
|
|
placeholder="输入WebSocket地址" |
|
|
|
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 |
|
|
|
rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 |
|
|
|
dark:bg-gray-700 dark:text-white transition-colors duration-200"> |
|
|
|
</div> |
|
|
|
<!-- 连接状态和按钮 --> |
|
|
|
<div class="flex items-center justify-between"> |
|
|
|
<div id="status" class="flex items-center space-x-2"> |
|
|
|
<div class="w-2 h-2 rounded-full bg-red-500 status-dot"></div> |
|
|
|
<span class="text-sm text-gray-600 dark:text-gray-300">未连接</span> |
|
|
|
</div> |
|
|
|
<div class="flex space-x-2"> |
|
|
|
<button id="connectBtn" |
|
|
|
class="px-4 py-2 bg-indigo-500 hover:bg-indigo-600 text-white rounded-lg |
|
|
|
shadow-sm transition-colors duration-200"> |
|
|
|
连接 |
|
|
|
</button> |
|
|
|
<button id="disconnectBtn" |
|
|
|
class="px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg |
|
|
|
shadow-sm transition-colors duration-200"> |
|
|
|
断开 |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<!-- 连接统计 --> |
|
|
|
<div class="flex items-center justify-between text-sm text-gray-600 dark:text-gray-300"> |
|
|
|
<span>消息: <span id="messageCount">0</span></span> |
|
|
|
<span>连接: <span id="connectionCount">0</span></span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div id="stats" class="flex items-center space-x-4 ml-4"> |
|
|
|
<span class="text-sm text-gray-600 dark:text-gray-300"> |
|
|
|
消息: <span id="messageCount">0</span> |
|
|
|
</span> |
|
|
|
<span class="text-sm text-gray-600 dark:text-gray-300"> |
|
|
|
连接: <span id="connectionCount">0</span> |
|
|
|
</span> |
|
|
|
|
|
|
|
<!-- 第二行:聊天消息和通知消息 --> |
|
|
|
<div class="grid grid-cols-2 gap-4 flex-1"> |
|
|
|
<!-- 聊天消息 --> |
|
|
|
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm h-full"> |
|
|
|
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">聊天消息</h3> |
|
|
|
<div class="space-y-4 h-[calc(100%-3rem)] flex flex-col"> |
|
|
|
<div class="p-3 bg-indigo-50 dark:bg-indigo-900/20 rounded-lg flex-1"> |
|
|
|
<pre class="text-sm text-indigo-700 dark:text-indigo-300 whitespace-pre-wrap">{ |
|
|
|
"type": "chat", |
|
|
|
"payload": { |
|
|
|
"message": "消息内容" |
|
|
|
} |
|
|
|
}</pre> |
|
|
|
</div> |
|
|
|
<div class="flex space-x-2"> |
|
|
|
<input type="text" |
|
|
|
id="chatMessage" |
|
|
|
value="这是一条聊天消息" |
|
|
|
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 |
|
|
|
rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 |
|
|
|
dark:bg-gray-700 dark:text-white"> |
|
|
|
<button onclick="sendExampleMessage('chat')" |
|
|
|
class="px-3 py-2 bg-indigo-500 hover:bg-indigo-600 text-white rounded-lg |
|
|
|
shadow-sm transition-colors duration-200"> |
|
|
|
发送 |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 通知消息 --> |
|
|
|
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm h-full"> |
|
|
|
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">通知消息</h3> |
|
|
|
<div class="space-y-4 h-[calc(100%-3rem)] flex flex-col"> |
|
|
|
<div class="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg flex-1"> |
|
|
|
<pre class="text-sm text-blue-700 dark:text-blue-300 whitespace-pre-wrap">{ |
|
|
|
"type": "notification", |
|
|
|
"payload": { |
|
|
|
"message": "消息内容" |
|
|
|
} |
|
|
|
}</pre> |
|
|
|
</div> |
|
|
|
<div class="flex space-x-2"> |
|
|
|
<input type="text" |
|
|
|
id="notificationMessage" |
|
|
|
value="这是一条通知消息" |
|
|
|
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 |
|
|
|
rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 |
|
|
|
dark:bg-gray-700 dark:text-white"> |
|
|
|
<button onclick="sendExampleMessage('notification')" |
|
|
|
class="px-3 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg |
|
|
|
shadow-sm transition-colors duration-200"> |
|
|
|
发送 |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="flex space-x-2"> |
|
|
|
<button id="connectBtn" |
|
|
|
class="px-4 py-2 bg-indigo-500 hover:bg-indigo-600 text-white rounded-lg shadow-sm |
|
|
|
transition-colors duration-200"> |
|
|
|
连接 |
|
|
|
</button> |
|
|
|
<button id="disconnectBtn" |
|
|
|
class="px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg shadow-sm |
|
|
|
transition-colors duration-200"> |
|
|
|
断开 |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 消息区域 --> |
|
|
|
<div class="flex-1 overflow-hidden"> |
|
|
|
<div class="max-w-4xl mx-auto h-full px-4 py-4"> |
|
|
|
<div class="flex justify-between items-center mb-2"> |
|
|
|
<h2 class="text-lg font-semibold text-gray-700 dark:text-gray-300">消息记录</h2> |
|
|
|
<div class="flex space-x-2"> |
|
|
|
<select id="messageType" |
|
|
|
class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-lg |
|
|
|
dark:bg-gray-700 dark:text-white"> |
|
|
|
<option value="message">普通消息</option> |
|
|
|
<option value="system">系统消息</option> |
|
|
|
<option value="error">错误消息</option> |
|
|
|
</select> |
|
|
|
<button id="clearBtn" |
|
|
|
class="px-3 py-1 bg-gray-500 hover:bg-gray-600 text-white rounded-lg |
|
|
|
shadow-sm transition-colors duration-200"> |
|
|
|
清空消息 |
|
|
|
</button> |
|
|
|
<!-- 第三行:系统消息和广播消息 --> |
|
|
|
<div class="grid grid-cols-2 gap-4 flex-1"> |
|
|
|
<!-- 系统消息 --> |
|
|
|
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm h-full"> |
|
|
|
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">系统消息</h3> |
|
|
|
<div class="space-y-4 h-[calc(100%-3rem)] flex flex-col"> |
|
|
|
<div class="p-3 bg-green-50 dark:bg-green-900/20 rounded-lg flex-1"> |
|
|
|
<pre class="text-sm text-green-700 dark:text-green-300 whitespace-pre-wrap">{ |
|
|
|
"type": "system", |
|
|
|
"payload": { |
|
|
|
"message": "消息内容" |
|
|
|
} |
|
|
|
}</pre> |
|
|
|
</div> |
|
|
|
<div class="flex space-x-2"> |
|
|
|
<input type="text" |
|
|
|
id="systemMessage" |
|
|
|
value="这是一条系统消息" |
|
|
|
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 |
|
|
|
rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 |
|
|
|
dark:bg-gray-700 dark:text-white"> |
|
|
|
<button onclick="sendExampleMessage('system')" |
|
|
|
class="px-3 py-2 bg-green-500 hover:bg-green-600 text-white rounded-lg |
|
|
|
shadow-sm transition-colors duration-200"> |
|
|
|
发送 |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 广播消息 --> |
|
|
|
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm h-full"> |
|
|
|
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-300 mb-4">广播消息</h3> |
|
|
|
<div class="space-y-4 h-[calc(100%-3rem)] flex flex-col"> |
|
|
|
<div class="p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg flex-1"> |
|
|
|
<pre class="text-sm text-purple-700 dark:text-purple-300 whitespace-pre-wrap">{ |
|
|
|
"type": "broadcast", |
|
|
|
"payload": { |
|
|
|
"message": "消息内容" |
|
|
|
} |
|
|
|
}</pre> |
|
|
|
</div> |
|
|
|
<div class="flex space-x-2"> |
|
|
|
<input type="text" |
|
|
|
id="broadcastMessage" |
|
|
|
value="这是一条广播消息" |
|
|
|
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 |
|
|
|
rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 |
|
|
|
dark:bg-gray-700 dark:text-white"> |
|
|
|
<button onclick="sendExampleMessage('broadcast')" |
|
|
|
class="px-3 py-2 bg-purple-500 hover:bg-purple-600 text-white rounded-lg |
|
|
|
shadow-sm transition-colors duration-200"> |
|
|
|
发送 |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div id="messages" class="h-full overflow-y-auto space-y-4"> |
|
|
|
<!-- 消息将在这里动态添加 --> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 输入区域 --> |
|
|
|
<div class="bg-white dark:bg-gray-800 shadow-sm"> |
|
|
|
<div class="max-w-4xl mx-auto px-4 py-4"> |
|
|
|
<div class="flex space-x-2"> |
|
|
|
<input type="text" |
|
|
|
id="messageInput" |
|
|
|
placeholder="输入消息..." |
|
|
|
class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 |
|
|
|
rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 |
|
|
|
dark:bg-gray-700 dark:text-white transition-colors duration-200"> |
|
|
|
<button id="sendBtn" |
|
|
|
class="px-4 py-2 bg-indigo-500 hover:bg-indigo-600 text-white rounded-lg |
|
|
|
shadow-sm transition-colors duration-200"> |
|
|
|
发送 |
|
|
|
</button> |
|
|
|
<!-- 右侧:消息记录区域 --> |
|
|
|
<div class="w-1/2 flex flex-col h-full"> |
|
|
|
<div class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm h-full"> |
|
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
|
<h2 class="text-lg font-semibold text-gray-700 dark:text-gray-300">消息记录</h2> |
|
|
|
<div class="flex space-x-2"> |
|
|
|
<button id="clearBtn" |
|
|
|
class="px-3 py-1 bg-gray-500 hover:bg-gray-600 text-white rounded-lg |
|
|
|
shadow-sm transition-colors duration-200"> |
|
|
|
清空消息 |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 标签页导航 --> |
|
|
|
<div class="flex border-b border-gray-200 dark:border-gray-700 mb-4"> |
|
|
|
<button class="tab-button active px-4 py-2 text-sm font-medium text-indigo-600 dark:text-indigo-400 border-b-2 border-indigo-500" |
|
|
|
data-tab="all"> |
|
|
|
全部消息 |
|
|
|
</button> |
|
|
|
<button class="tab-button px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300" |
|
|
|
data-tab="chat"> |
|
|
|
聊天消息 |
|
|
|
</button> |
|
|
|
<button class="tab-button px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300" |
|
|
|
data-tab="notification"> |
|
|
|
通知消息 |
|
|
|
</button> |
|
|
|
<button class="tab-button px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300" |
|
|
|
data-tab="system"> |
|
|
|
系统消息 |
|
|
|
</button> |
|
|
|
<button class="tab-button px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300" |
|
|
|
data-tab="broadcast"> |
|
|
|
广播消息 |
|
|
|
</button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 消息容器 --> |
|
|
|
<div id="messages" class="h-[calc(100%-8rem)] overflow-y-auto space-y-4"> |
|
|
|
<!-- 消息将在这里动态添加 --> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@ -148,55 +275,63 @@ |
|
|
|
const particleVelocities = new Float32Array(particleCount * 3); |
|
|
|
|
|
|
|
function initThreeJS() { |
|
|
|
scene = new THREE.Scene(); |
|
|
|
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
|
|
|
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); |
|
|
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
|
|
document.getElementById('three-container').appendChild(renderer.domElement); |
|
|
|
|
|
|
|
// 初始化粒子 |
|
|
|
for (let i = 0; i < particleCount; i++) { |
|
|
|
particlePositions[i * 3] = (Math.random() - 0.5) * 20; |
|
|
|
particlePositions[i * 3 + 1] = (Math.random() - 0.5) * 20; |
|
|
|
particlePositions[i * 3 + 2] = (Math.random() - 0.5) * 20; |
|
|
|
|
|
|
|
particleColors[i * 3] = Math.random(); |
|
|
|
particleColors[i * 3 + 1] = Math.random(); |
|
|
|
particleColors[i * 3 + 2] = Math.random(); |
|
|
|
|
|
|
|
particleVelocities[i * 3] = (Math.random() - 0.5) * 0.02; |
|
|
|
particleVelocities[i * 3 + 1] = (Math.random() - 0.5) * 0.02; |
|
|
|
particleVelocities[i * 3 + 2] = (Math.random() - 0.5) * 0.02; |
|
|
|
try { |
|
|
|
scene = new THREE.Scene(); |
|
|
|
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
|
|
|
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); |
|
|
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
|
|
renderer.setClearColor(0x000000, 0); // 设置透明背景 |
|
|
|
document.getElementById('three-container').appendChild(renderer.domElement); |
|
|
|
|
|
|
|
// 初始化粒子 |
|
|
|
for (let i = 0; i < particleCount; i++) { |
|
|
|
particlePositions[i * 3] = (Math.random() - 0.5) * 20; |
|
|
|
particlePositions[i * 3 + 1] = (Math.random() - 0.5) * 20; |
|
|
|
particlePositions[i * 3 + 2] = (Math.random() - 0.5) * 20; |
|
|
|
|
|
|
|
particleColors[i * 3] = Math.random(); |
|
|
|
particleColors[i * 3 + 1] = Math.random(); |
|
|
|
particleColors[i * 3 + 2] = Math.random(); |
|
|
|
|
|
|
|
particleVelocities[i * 3] = (Math.random() - 0.5) * 0.02; |
|
|
|
particleVelocities[i * 3 + 1] = (Math.random() - 0.5) * 0.02; |
|
|
|
particleVelocities[i * 3 + 2] = (Math.random() - 0.5) * 0.02; |
|
|
|
} |
|
|
|
|
|
|
|
particleGeometry.setAttribute('position', new THREE.BufferAttribute(particlePositions, 3)); |
|
|
|
particleGeometry.setAttribute('color', new THREE.BufferAttribute(particleColors, 3)); |
|
|
|
|
|
|
|
const particleMaterial = new THREE.PointsMaterial({ |
|
|
|
size: 0.1, |
|
|
|
vertexColors: true, |
|
|
|
transparent: true, |
|
|
|
opacity: 0.8, |
|
|
|
blending: THREE.AdditiveBlending |
|
|
|
}); |
|
|
|
|
|
|
|
particles = new THREE.Points(particleGeometry, particleMaterial); |
|
|
|
scene.add(particles); |
|
|
|
|
|
|
|
camera.position.z = 15; |
|
|
|
|
|
|
|
// 添加环境光 |
|
|
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); |
|
|
|
scene.add(ambientLight); |
|
|
|
|
|
|
|
// 添加点光源 |
|
|
|
const pointLight = new THREE.PointLight(0xffffff, 1); |
|
|
|
pointLight.position.set(10, 10, 10); |
|
|
|
scene.add(pointLight); |
|
|
|
|
|
|
|
animate(); |
|
|
|
} catch (error) { |
|
|
|
console.error('Three.js初始化错误:', error); |
|
|
|
} |
|
|
|
|
|
|
|
particleGeometry.setAttribute('position', new THREE.BufferAttribute(particlePositions, 3)); |
|
|
|
particleGeometry.setAttribute('color', new THREE.BufferAttribute(particleColors, 3)); |
|
|
|
|
|
|
|
const particleMaterial = new THREE.PointsMaterial({ |
|
|
|
size: 0.1, |
|
|
|
vertexColors: true, |
|
|
|
transparent: true, |
|
|
|
opacity: 0.8 |
|
|
|
}); |
|
|
|
|
|
|
|
particles = new THREE.Points(particleGeometry, particleMaterial); |
|
|
|
scene.add(particles); |
|
|
|
|
|
|
|
camera.position.z = 15; |
|
|
|
|
|
|
|
// 添加环境光 |
|
|
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); |
|
|
|
scene.add(ambientLight); |
|
|
|
|
|
|
|
// 添加点光源 |
|
|
|
const pointLight = new THREE.PointLight(0xffffff, 1); |
|
|
|
pointLight.position.set(10, 10, 10); |
|
|
|
scene.add(pointLight); |
|
|
|
|
|
|
|
animate(); |
|
|
|
} |
|
|
|
|
|
|
|
function animate() { |
|
|
|
if (!scene || !camera || !renderer) return; |
|
|
|
|
|
|
|
requestAnimationFrame(animate); |
|
|
|
|
|
|
|
// 更新粒子位置 |
|
|
@ -221,11 +356,23 @@ |
|
|
|
|
|
|
|
// 处理窗口大小变化 |
|
|
|
window.addEventListener('resize', function() { |
|
|
|
if (!camera || !renderer) return; |
|
|
|
camera.aspect = window.innerWidth / window.innerHeight; |
|
|
|
camera.updateProjectionMatrix(); |
|
|
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
|
|
}); |
|
|
|
|
|
|
|
// 确保在页面完全加载后初始化 |
|
|
|
window.addEventListener('load', function() { |
|
|
|
console.log('页面加载完成,初始化Three.js'); |
|
|
|
initThreeJS(); |
|
|
|
}); |
|
|
|
|
|
|
|
// 在页面加载时初始化 |
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
initWebSocketAddress(); |
|
|
|
}); |
|
|
|
|
|
|
|
// WebSocket 相关代码 |
|
|
|
let socket; |
|
|
|
let reconnectAttempts = 0; |
|
|
@ -267,10 +414,45 @@ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 添加消息到显示区域 |
|
|
|
// 标签页切换功能 |
|
|
|
document.querySelectorAll('.tab-button').forEach(button => { |
|
|
|
button.addEventListener('click', function() { |
|
|
|
const tabType = this.getAttribute('data-tab'); |
|
|
|
switchTab(tabType); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
// 切换标签页的函数 |
|
|
|
function switchTab(tabType) { |
|
|
|
const tabButton = document.querySelector(`.tab-button[data-tab="${tabType}"]`); |
|
|
|
if (tabButton) { |
|
|
|
// 移除所有按钮的active类 |
|
|
|
document.querySelectorAll('.tab-button').forEach(btn => { |
|
|
|
btn.classList.remove('active', 'text-indigo-600', 'dark:text-indigo-400', 'border-indigo-500'); |
|
|
|
btn.classList.add('text-gray-500', 'dark:text-gray-400'); |
|
|
|
}); |
|
|
|
|
|
|
|
// 添加当前按钮的active类 |
|
|
|
tabButton.classList.add('active', 'text-indigo-600', 'dark:text-indigo-400', 'border-indigo-500'); |
|
|
|
tabButton.classList.remove('text-gray-500', 'dark:text-gray-400'); |
|
|
|
|
|
|
|
// 显示/隐藏消息 |
|
|
|
document.querySelectorAll('.message').forEach(message => { |
|
|
|
const messageType = message.getAttribute('data-type'); |
|
|
|
if (tabType === 'all' || messageType === tabType) { |
|
|
|
message.style.display = 'flex'; |
|
|
|
} else { |
|
|
|
message.style.display = 'none'; |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 修改添加消息的函数 |
|
|
|
function addMessage(message, isReceived = false, type = 'message') { |
|
|
|
const messageElement = document.createElement('div'); |
|
|
|
messageElement.className = `message flex ${isReceived ? 'justify-start' : 'justify-end'}`; |
|
|
|
messageElement.setAttribute('data-type', type); |
|
|
|
|
|
|
|
const messageContent = document.createElement('div'); |
|
|
|
messageContent.className = `flex items-start space-x-2 max-w-[80%]`; |
|
|
@ -287,9 +469,18 @@ |
|
|
|
avatarClass += ' bg-red-500'; |
|
|
|
avatar.textContent = '错'; |
|
|
|
break; |
|
|
|
default: |
|
|
|
case 'chat': |
|
|
|
avatarClass += isReceived ? ' bg-gray-500' : ' bg-indigo-500'; |
|
|
|
avatar.textContent = isReceived ? 'S' : '我'; |
|
|
|
break; |
|
|
|
case 'notification': |
|
|
|
avatarClass += ' bg-blue-500'; |
|
|
|
avatar.textContent = '通'; |
|
|
|
break; |
|
|
|
case 'broadcast': |
|
|
|
avatarClass += ' bg-purple-500'; |
|
|
|
avatar.textContent = '广'; |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
avatar.className = avatarClass; |
|
|
@ -304,8 +495,15 @@ |
|
|
|
case 'error': |
|
|
|
bubbleClass += ' bg-red-100 dark:bg-red-900'; |
|
|
|
break; |
|
|
|
default: |
|
|
|
case 'chat': |
|
|
|
bubbleClass += isReceived ? ' bg-gray-100 dark:bg-gray-700' : ' bg-indigo-100 dark:bg-indigo-900'; |
|
|
|
break; |
|
|
|
case 'notification': |
|
|
|
bubbleClass += ' bg-blue-100 dark:bg-blue-900'; |
|
|
|
break; |
|
|
|
case 'broadcast': |
|
|
|
bubbleClass += ' bg-purple-100 dark:bg-purple-900'; |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
bubble.className = bubbleClass; |
|
|
@ -395,6 +593,58 @@ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 发送示例消息 |
|
|
|
function sendExampleMessage(type) { |
|
|
|
if (!socket || socket.readyState !== WebSocket.OPEN) { |
|
|
|
addMessage('未连接到服务器', false, 'error'); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const messageInput = document.getElementById(`${type}Message`); |
|
|
|
const messageContent = messageInput.value.trim(); |
|
|
|
|
|
|
|
if (!messageContent) { |
|
|
|
addMessage('消息内容不能为空', false, 'error'); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const messages = { |
|
|
|
chat: { |
|
|
|
type: "chat", |
|
|
|
payload: { |
|
|
|
message: messageContent |
|
|
|
} |
|
|
|
}, |
|
|
|
notification: { |
|
|
|
type: "notification", |
|
|
|
payload: { |
|
|
|
message: messageContent |
|
|
|
} |
|
|
|
}, |
|
|
|
system: { |
|
|
|
type: "system", |
|
|
|
payload: { |
|
|
|
message: messageContent |
|
|
|
} |
|
|
|
}, |
|
|
|
broadcast: { |
|
|
|
type: "broadcast", |
|
|
|
payload: { |
|
|
|
message: messageContent |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
try { |
|
|
|
socket.send(JSON.stringify(messages[type])); |
|
|
|
addMessage(messageContent, false, type); |
|
|
|
// 切换到对应的标签页 |
|
|
|
switchTab(type); |
|
|
|
} catch (error) { |
|
|
|
addMessage(`发送失败: ${error.message}`, false, 'error'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 事件监听 |
|
|
|
connectBtn.addEventListener('click', connect); |
|
|
|
disconnectBtn.addEventListener('click', function() { |
|
|
@ -431,12 +681,6 @@ |
|
|
|
function initWebSocketAddress() { |
|
|
|
wsAddressInput.value = getDefaultWebSocketAddress(); |
|
|
|
} |
|
|
|
|
|
|
|
// 在页面加载时初始化 |
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
initWebSocketAddress(); |
|
|
|
initThreeJS(); |
|
|
|
}); |
|
|
|
</script> |
|
|
|
</body> |
|
|
|
</html> |