新增 event , 优化 websocket
This commit is contained in:
7
examples/js_ws_event/.pages.yaml
Normal file
7
examples/js_ws_event/.pages.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
routes:
|
||||
- path: "sender"
|
||||
js:
|
||||
exec: "sender.js"
|
||||
- path: "event"
|
||||
js:
|
||||
exec: "event.js"
|
||||
23
examples/js_ws_event/event.js
Normal file
23
examples/js_ws_event/event.js
Normal file
@@ -0,0 +1,23 @@
|
||||
let name=request.getQuery("name")
|
||||
if (name===""){
|
||||
throw Error(`Missing name "${name}"`)
|
||||
}
|
||||
let ws = websocket();
|
||||
event.subscribe("messages").on(function (msg){
|
||||
ws.writeText(msg)
|
||||
})
|
||||
let shouldExit = false;
|
||||
while (!shouldExit) {
|
||||
let data = ws.readText();
|
||||
switch (data) {
|
||||
case "exit":
|
||||
shouldExit = true;
|
||||
break;
|
||||
default:
|
||||
event.put("messages",JSON.stringify({
|
||||
name:name,
|
||||
data:data
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
459
examples/js_ws_event/index.html
Normal file
459
examples/js_ws_event/index.html
Normal file
@@ -0,0 +1,459 @@
|
||||
<!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>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
height: 85vh;
|
||||
background-color: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(to right, #4a00e0, #8e2de2);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.status-connecting {
|
||||
background-color: #ffc107;
|
||||
}
|
||||
|
||||
.status-connected {
|
||||
background-color: #28a745;
|
||||
}
|
||||
|
||||
.status-disconnected {
|
||||
background-color: #dc3545;
|
||||
}
|
||||
|
||||
.connection-btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.connection-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.name-input-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.name-input-container label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.name-input-container input {
|
||||
padding: 4px 8px;
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #333;
|
||||
width: 80px;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.chat-area {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
background-color: #f8f9fa;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 70%;
|
||||
padding: 12px 18px;
|
||||
border-radius: 18px;
|
||||
position: relative;
|
||||
line-height: 1.4;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.left-message {
|
||||
align-self: flex-start;
|
||||
background-color: white;
|
||||
color: #333;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
.right-message {
|
||||
align-self: flex-end;
|
||||
background: linear-gradient(to right, #4a00e0, #8e2de2);
|
||||
color: white;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-size: 11px;
|
||||
margin-top: 5px;
|
||||
opacity: 0.7;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.message-name {
|
||||
font-size: 12px;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.input-area input {
|
||||
flex: 1;
|
||||
padding: 12px 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 25px;
|
||||
outline: none;
|
||||
font-size: 16px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.input-area input:focus {
|
||||
border-color: #8e2de2;
|
||||
box-shadow: 0 0 0 2px rgba(142, 45, 226, 0.2);
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
padding: 12px 25px;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(to right, #4a00e0, #8e2de2);
|
||||
color: white;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.send-btn:hover {
|
||||
background: linear-gradient(to right, #3a00b0, #7a1dc2);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.send-btn:disabled {
|
||||
background: #cccccc;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 48px;
|
||||
margin-bottom: 15px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.chat-container {
|
||||
height: 95vh;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 85%;
|
||||
}
|
||||
|
||||
.header {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-left, .header-right {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="chat-container">
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<h1>WebSocket聊天客户端</h1>
|
||||
<div class="status">
|
||||
<div class="status-indicator status-disconnected"></div>
|
||||
<span id="status-text">未连接</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="name-input-container">
|
||||
<label for="nameInput">姓名:</label>
|
||||
<input type="text" id="nameInput" placeholder="输入姓名">
|
||||
</div>
|
||||
<button class="connection-btn" id="connectionButton" onclick="toggleConnection()">连接</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-area" id="chatArea">
|
||||
<div class="empty-state">
|
||||
<i>💬</i>
|
||||
<p>连接服务器开始聊天</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<input type="text" id="messageInput" placeholder="输入消息..." disabled>
|
||||
<button class="send-btn" id="sendButton" onclick="sendMessage()" disabled>发送</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let ws = null;
|
||||
let isConnected = false;
|
||||
let currentUserName = "";
|
||||
const statusText = document.getElementById('status-text');
|
||||
const statusIndicator = document.querySelector('.status-indicator');
|
||||
const chatArea = document.getElementById('chatArea');
|
||||
const messageInput = document.getElementById('messageInput');
|
||||
const sendButton = document.getElementById('sendButton');
|
||||
const connectionButton = document.getElementById('connectionButton');
|
||||
const nameInput = document.getElementById('nameInput');
|
||||
|
||||
// 生成4位随机数字作为默认用户名
|
||||
function generateRandomUsername() {
|
||||
return Math.floor(1000 + Math.random() * 9000).toString();
|
||||
}
|
||||
|
||||
// 初始化
|
||||
function init() {
|
||||
const randomUsername = generateRandomUsername();
|
||||
nameInput.value = randomUsername;
|
||||
currentUserName = randomUsername;
|
||||
}
|
||||
|
||||
function toggleConnection() {
|
||||
if (isConnected) {
|
||||
disconnect();
|
||||
} else {
|
||||
connect();
|
||||
}
|
||||
}
|
||||
|
||||
function connect() {
|
||||
// 获取用户名,如果没有输入则使用默认值
|
||||
const username = nameInput.value.trim() || currentUserName;
|
||||
currentUserName = username;
|
||||
|
||||
updateStatus('正在连接...', 'status-connecting');
|
||||
connectionButton.textContent = '连接中...';
|
||||
|
||||
try {
|
||||
// 使用用户名作为查询参数
|
||||
ws = new WebSocket(`/event?name=${encodeURIComponent(username)}`);
|
||||
|
||||
ws.onopen = () => {
|
||||
isConnected = true;
|
||||
updateStatus('已连接', 'status-connected');
|
||||
connectionButton.textContent = '断开连接';
|
||||
enableControls(true);
|
||||
addSystemMessage('连接成功,可以开始聊天了');
|
||||
};
|
||||
|
||||
ws.onmessage = event => {
|
||||
try {
|
||||
// 解析JSON数据
|
||||
const data = JSON.parse(event.data);
|
||||
// 根据name字段判断消息显示位置
|
||||
if (data.name === currentUserName) {
|
||||
// 如果是当前用户,显示在右侧
|
||||
addMessage(data.data, 'right', data.name);
|
||||
} else {
|
||||
// 如果是其他用户,显示在左侧
|
||||
addMessage(data.data, 'left', data.name);
|
||||
}
|
||||
} catch (e) {
|
||||
// 如果不是JSON,按原方式处理
|
||||
addMessage(event.data, 'left', '系统');
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
isConnected = false;
|
||||
updateStatus('未连接', 'status-disconnected');
|
||||
connectionButton.textContent = '连接';
|
||||
enableControls(false);
|
||||
addSystemMessage('连接已断开');
|
||||
};
|
||||
|
||||
ws.onerror = error => {
|
||||
isConnected = false;
|
||||
updateStatus('连接错误', 'status-disconnected');
|
||||
connectionButton.textContent = '连接';
|
||||
enableControls(false);
|
||||
addSystemMessage('连接错误,请检查服务器');
|
||||
};
|
||||
} catch (error) {
|
||||
isConnected = false;
|
||||
updateStatus('连接失败', 'status-disconnected');
|
||||
connectionButton.textContent = '连接';
|
||||
enableControls(false);
|
||||
}
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
if (ws) {
|
||||
ws.close();
|
||||
ws = null;
|
||||
}
|
||||
isConnected = false;
|
||||
connectionButton.textContent = '连接';
|
||||
enableControls(false);
|
||||
addSystemMessage('已断开连接');
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
const message = messageInput.value.trim();
|
||||
if (message) {
|
||||
// 发送消息到服务器
|
||||
ws.send(message);
|
||||
// 清空输入框
|
||||
messageInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function addMessage(message, type, senderName) {
|
||||
const emptyState = document.querySelector('.empty-state');
|
||||
if (emptyState) {
|
||||
emptyState.remove();
|
||||
}
|
||||
|
||||
const messageElement = document.createElement('div');
|
||||
const time = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
|
||||
|
||||
if (type === 'right') {
|
||||
messageElement.className = 'message right-message';
|
||||
messageElement.innerHTML = `
|
||||
${message}
|
||||
<div class="message-time">${time}</div>
|
||||
`;
|
||||
} else if (type === 'left') {
|
||||
messageElement.className = 'message left-message';
|
||||
messageElement.innerHTML = `
|
||||
<div class="message-name">${senderName}</div>
|
||||
${message}
|
||||
<div class="message-time">${time}</div>
|
||||
`;
|
||||
} else {
|
||||
messageElement.className = 'message system-message';
|
||||
messageElement.style.alignSelf = 'center';
|
||||
messageElement.style.maxWidth = '100%';
|
||||
messageElement.style.backgroundColor = 'rgba(0,0,0,0.05)';
|
||||
messageElement.style.color = '#6c757d';
|
||||
messageElement.style.fontSize = '14px';
|
||||
messageElement.style.textAlign = 'center';
|
||||
messageElement.style.borderRadius = '10px';
|
||||
messageElement.textContent = message;
|
||||
}
|
||||
|
||||
chatArea.appendChild(messageElement);
|
||||
chatArea.scrollTop = chatArea.scrollHeight;
|
||||
}
|
||||
|
||||
function addSystemMessage(message) {
|
||||
addMessage(message, 'system');
|
||||
}
|
||||
|
||||
function updateStatus(message, statusClass) {
|
||||
statusText.textContent = message;
|
||||
statusIndicator.className = 'status-indicator ' + statusClass;
|
||||
}
|
||||
|
||||
function enableControls(connected) {
|
||||
messageInput.disabled = !connected;
|
||||
sendButton.disabled = !connected;
|
||||
nameInput.disabled = connected; // 连接时禁用姓名修改
|
||||
}
|
||||
|
||||
messageInput.addEventListener('keypress', function(event) {
|
||||
if (event.key === 'Enter') {
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化生成随机用户名
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
8
examples/js_ws_event/sender.js
Normal file
8
examples/js_ws_event/sender.js
Normal file
@@ -0,0 +1,8 @@
|
||||
let name=request.getQuery("name")
|
||||
let message=request.getQuery("data")
|
||||
event.put("messages", JSON.stringify({
|
||||
name:name,
|
||||
data:message
|
||||
}));
|
||||
|
||||
// response.write(event.subscribe("messages").get())
|
||||
Reference in New Issue
Block a user