This commit is contained in:
ditiqi 2025-05-27 15:46:34 +08:00
parent 1c3b7bdad0
commit dcd38d453d
3 changed files with 189 additions and 152 deletions

View File

@ -1,13 +1,22 @@
import { createBunWebSocket } from 'hono/bun';
import type { ServerWebSocket } from 'bun';
import { WSContext } from 'hono/ws';
import { MessageType } from '../../../packages/client/src/websocket/type';
enum MessageType {
TEXT = 'text',
IMAGE = 'image',
FILE = 'file',
RETRACT = 'retract', // 用于实现撤回
}
interface WSMessageParams {
text?: string;
type?: MessageType;
fileUri?: string;
}
// 定义消息类型接口
interface WSMessage {
type: 'join' | 'leave' | 'message';
roomId: string;
data: any;
data: WSMessageParams;
}
// 创建 WebSocket 实例并指定泛型类型
@ -27,7 +36,7 @@ const wsHandler = upgradeWebSocket((c) => {
},
onMessage(message, ws) {
try {
const parsedMessage: WSMessage = JSON.parse(message.data);
const parsedMessage: WSMessage = JSON.parse(message.data as any);
console.log('收到消息:', parsedMessage);
switch (parsedMessage.type) {

View File

@ -1,161 +1,28 @@
'use client';
import { useHello, useTRPC, useWebSocket, MessageType } from '@repo/client';
import { useHello, useTRPC } from '@repo/client';
import { useQuery } from '@tanstack/react-query';
import { useRef, useState } from 'react';
import Link from 'next/link';
export default function Home() {
const trpc = useTRPC();
const { data, isLoading } = useQuery(trpc.user.getUser.queryOptions());
const [message, setMessage] = useState('');
const [roomId, setRoomId] = useState('');
const messagesEndRef = useRef<HTMLDivElement>(null);
// 使用 WebSocket hook
const { messages, currentRoom, connecting, joinRoom, leaveRoom, sendMessage } = useWebSocket();
// 滚动到底部
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
setTimeout(scrollToBottom, 100);
};
const handleJoinRoom = async () => {
const success = await joinRoom(roomId.trim());
if (success) {
setRoomId('');
}
};
const handleLeaveRoom = async () => {
await leaveRoom();
};
const handleSendMessage = async () => {
const success = await sendMessage({
text: message,
type: MessageType.TEXT,
});
if (success) {
setMessage('');
}
};
// 处理按回车发送消息
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
};
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">WebSocket </h1>
{/* 房间管理 */}
<div className="mb-6 p-4 border rounded">
<h2 className="text-xl mb-2"></h2>
{!currentRoom ? (
<div className="flex gap-2">
<input
type="text"
value={roomId}
onChange={(e) => setRoomId(e.target.value)}
onKeyPress={handleKeyPress}
disabled={connecting}
className="border border-gray-300 rounded px-3 py-2 flex-1"
placeholder="输入房间ID..."
/>
<button
onClick={handleJoinRoom}
disabled={connecting}
className={`px-4 py-2 rounded text-white ${
connecting ? 'bg-gray-400 cursor-not-allowed' : 'bg-green-500 hover:bg-green-600'
}`}
>
{connecting ? '连接中...' : '加入房间'}
</button>
</div>
) : (
<div className="flex gap-2 items-center">
<span>: {currentRoom}</span>
<button
onClick={handleLeaveRoom}
disabled={connecting}
className={`px-4 py-2 rounded text-white ${
connecting ? 'bg-gray-400 cursor-not-allowed' : 'bg-red-500 hover:bg-red-600'
}`}
>
</button>
</div>
)}
<h1 className="text-2xl font-bold mb-4"></h1>
<div className="space-y-4">
<div className="p-4 border rounded">
<h2 className="text-xl mb-2"></h2>
<ul className="space-y-2">
<li>
<Link href="/websocket" className="text-blue-500 hover:text-blue-600 hover:underline">
WebSocket
</Link>
</li>
{/* 可以在这里添加更多功能链接 */}
</ul>
</div>
</div>
{/* 消息显示区域 */}
{currentRoom && (
<div className="mb-4">
<div className="border rounded p-4 h-[400px] overflow-y-auto mb-4 bg-gray-50">
{messages.map((msg, index) => (
<div
key={index}
className={`mb-2 p-2 rounded ${
msg.action === 'system'
? 'bg-gray-200 text-gray-700'
: msg.action === 'error'
? 'bg-red-100 text-red-700'
: 'bg-blue-100 text-blue-700'
}`}
>
<div className="flex items-center gap-2">
{msg.data.type === MessageType.IMAGE && msg.data.fileUri && (
<img src={msg.data.fileUri} alt="图片消息" className="max-w-xs" />
)}
{msg.data.type === MessageType.FILE && msg.data.fileUri && (
<a
href={msg.data.fileUri}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline"
>
</a>
)}
{msg.data.text && <div>{msg.data.text}</div>}
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
<div className="flex gap-2">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyPress={handleKeyPress}
disabled={connecting}
className="border border-gray-300 rounded px-3 py-2 flex-1"
placeholder="输入消息..."
/>
<button
onClick={handleSendMessage}
disabled={connecting}
className={`px-4 py-2 rounded text-white ${
connecting ? 'bg-gray-400 cursor-not-allowed' : 'bg-blue-500 hover:bg-blue-600'
}`}
>
</button>
</div>
</div>
)}
{!currentRoom && (
<div>
<p className="text-gray-600">提示: 请先加入一个房间开始聊天</p>
<p className="text-gray-600">ID来测试房间内通信</p>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,161 @@
'use client';
import { useHello, useTRPC, useWebSocket, MessageType } from '@repo/client';
import { useQuery } from '@tanstack/react-query';
import { useRef, useState } from 'react';
export default function WebSocketPage() {
const trpc = useTRPC();
const { data, isLoading } = useQuery(trpc.user.getUser.queryOptions());
const [message, setMessage] = useState('');
const [roomId, setRoomId] = useState('');
const messagesEndRef = useRef<HTMLDivElement>(null);
// 使用 WebSocket hook
const { messages, currentRoom, connecting, joinRoom, leaveRoom, sendMessage } = useWebSocket();
// 滚动到底部
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
setTimeout(scrollToBottom, 100);
};
const handleJoinRoom = async () => {
const success = await joinRoom(roomId.trim());
if (success) {
setRoomId('');
}
};
const handleLeaveRoom = async () => {
await leaveRoom();
};
const handleSendMessage = async () => {
const success = await sendMessage({
text: message,
type: MessageType.TEXT,
});
if (success) {
setMessage('');
}
};
// 处理按回车发送消息
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
};
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">WebSocket </h1>
{/* 房间管理 */}
<div className="mb-6 p-4 border rounded">
<h2 className="text-xl mb-2"></h2>
{!currentRoom ? (
<div className="flex gap-2">
<input
type="text"
value={roomId}
onChange={(e) => setRoomId(e.target.value)}
onKeyPress={handleKeyPress}
disabled={connecting}
className="border border-gray-300 rounded px-3 py-2 flex-1"
placeholder="输入房间ID..."
/>
<button
onClick={handleJoinRoom}
disabled={connecting}
className={`px-4 py-2 rounded text-white ${
connecting ? 'bg-gray-400 cursor-not-allowed' : 'bg-green-500 hover:bg-green-600'
}`}
>
{connecting ? '连接中...' : '加入房间'}
</button>
</div>
) : (
<div className="flex gap-2 items-center">
<span>: {currentRoom}</span>
<button
onClick={handleLeaveRoom}
disabled={connecting}
className={`px-4 py-2 rounded text-white ${
connecting ? 'bg-gray-400 cursor-not-allowed' : 'bg-red-500 hover:bg-red-600'
}`}
>
</button>
</div>
)}
</div>
{/* 消息显示区域 */}
{currentRoom && (
<div className="mb-4">
<div className="border rounded p-4 h-[400px] overflow-y-auto mb-4 bg-gray-50">
{messages.map((msg, index) => (
<div
key={index}
className={`mb-2 p-2 rounded ${
msg.action === 'system'
? 'bg-gray-200 text-gray-700'
: msg.action === 'error'
? 'bg-red-100 text-red-700'
: 'bg-blue-100 text-blue-700'
}`}
>
<div className="flex items-center gap-2">
{msg.data.type === MessageType.IMAGE && msg.data.fileUri && (
<img src={msg.data.fileUri} alt="图片消息" className="max-w-xs" />
)}
{msg.data.type === MessageType.FILE && msg.data.fileUri && (
<a
href={msg.data.fileUri}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline"
>
</a>
)}
{msg.data.text && <div>{msg.data.text}</div>}
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
<div className="flex gap-2">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyPress={handleKeyPress}
disabled={connecting}
className="border border-gray-300 rounded px-3 py-2 flex-1"
placeholder="输入消息..."
/>
<button
onClick={handleSendMessage}
disabled={connecting}
className={`px-4 py-2 rounded text-white ${
connecting ? 'bg-gray-400 cursor-not-allowed' : 'bg-blue-500 hover:bg-blue-600'
}`}
>
</button>
</div>
</div>
)}
{!currentRoom && (
<div>
<p className="text-gray-600">提示: 请先加入一个房间开始聊天</p>
<p className="text-gray-600">ID来测试房间内通信</p>
</div>
)}
</div>
);
}