Browse Source

feat: 添加消息记录标签页系统,发送消息时自动切换到对应标签页

ws
hyh 3 months ago
parent
commit
65e729867e
  1. 510
      src/CellularManagement.WebAPI/wwwroot/websocket.html

510
src/CellularManagement.WebAPI/wwwroot/websocket.html

@ -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>
Loading…
Cancel
Save