Commit 353e4327 authored by 何 广一's avatar 何 广一
Browse files

get & update & submit message

parent e42e383f
'use client'
import { createContext, useContext, useState, ReactNode, useEffect } from 'react'
import * as Ty from './interface'
import { useRoom } from './room-context'
export default function RoomHeaderProvider() {
const { currentRoomInfo } = useRoom()
return (
<h2 className={`text-lg font-bold ${currentRoomInfo ? '' : 'text-gray-500'}`}>
{currentRoomInfo?.roomName ?? '房间'}
</h2>
);
};
\ No newline at end of file
......@@ -44,3 +44,29 @@ export interface MessageAddArgs {
content: string;
sender: string;
}
export interface RoomMessageListArgs {
roomId: number;
}
export interface RoomMessageListRes {
messages: Message[];
}
export interface RoomMessageGetUpdateArgs {
roomId: number;
sinceMessageId: number;
}
export interface RoomMessageGetUpdateRes {
messages: Message[];
}
export interface RoomMessageGetUpdateArgs {
roomId: number;
sinceMessageId: number;
}
export interface RoomMessageGetUpdateRes {
messages: Message[];
}
\ No newline at end of file
import * as Ty from './interface'
import RoomListProvider from './room-list';
import RoomHeaderProvider from './current-room';
import RoomMessageProvider from './room-message';
import SendMessageProvider from './send-message';
import UsernameCheck from './username-check';
import { RoomProvider } from './room-context'
export default function Home() {
return (
<RoomProvider>
<UsernameCheck />
<div className="bg-gray-100">
<div className="flex h-screen">
<div className="w-1/4 bg-white p-4 overflow-y-auto">
<h2 className="text-lg font-bold mb-4">房间列表</h2>
<RoomListProvider />
</div>
<div className="w-3/4 bg-gray-100 p-4">
<div className="bg-white p-4 mb-4">
<RoomHeaderProvider />
</div>
<RoomMessageProvider />
<div className="bg-white p-4">
<SendMessageProvider />
</div>
</div>
</div>
</div>
</RoomProvider>
);
}
'use client'
import { createContext, useContext, useState, ReactNode, useCallback } from 'react'
import * as Ty from './interface'
import { useSearchParams} from 'next/navigation';
interface RoomCtxValue {
currentRoomInfo: Ty.RoomPreviewInfo | null
setCurrentRoomInfo: (r: Ty.RoomPreviewInfo | null) => void
username: string;
setUsername: (u: string) => void;
msgVersion: number;
triggerRefresh: () => void;
}
const RoomContext = createContext<RoomCtxValue>({} as RoomCtxValue)
export function RoomProvider({ children }: { children: ReactNode }) {
const searchParams = useSearchParams();
const userName = searchParams.get('username')?.trim() || '';
const [currentRoomInfo, setCurrentRoomInfo] = useState<Ty.RoomPreviewInfo | null>(null)
const [username, setUsername] = useState<string>(userName);
const [msgVersion, setMsgVersion] = useState(0);
const triggerRefresh = useCallback(() => setMsgVersion(v => v + 1), []);
return (
<RoomContext.Provider value={{
currentRoomInfo,
setCurrentRoomInfo,
username,
setUsername,
msgVersion,
triggerRefresh
}}>
{children}
</RoomContext.Provider>
)
}
export const useRoom = () => useContext(RoomContext)
\ No newline at end of file
'use client'
import { useState, useEffect } from 'react'
import * as Ty from './interface'
import { useRoom } from './room-context'
export default function RoomListProvider() {
const [roomsLoading, setRoomsLoading] = useState<boolean>(false)
const [roomsError, setRoomsError] = useState<string>('')
const [rooms, setRooms] = useState<Ty.RoomPreviewInfo[]>([])
const { setCurrentRoomInfo, msgVersion } = useRoom()
/*
- url: /api/room/list
......@@ -45,31 +47,39 @@ export default function RoomListProvider() {
}
}
const handleRoomClick = (room: Ty.RoomPreviewInfo) => {
setCurrentRoomInfo(room);
}
useEffect(() => {
getRooms();
const interval = setInterval(() => {
getRooms();
}, 1000); // 1
}, 2000); // 2
// 组件卸载时清除定时器
return () => {
clearInterval(interval)
}
}, []);
}, [msgVersion]);
return (
<div className="room-container">
{roomsError && <div className="text-red-500">{roomsError}</div>}
{roomsLoading && !rooms.length && <div className="text-gray-500">加载中...</div>}
<ul className="mb-2">
{rooms && rooms.map((room) => (
<li key={room.roomId}>
<h3>{room.roomName} (ID: {room.roomId})</h3>
{room.lastMessage ? (
<p>Last message from {room.lastMessage.sender}: {room.lastMessage.content}</p>
) : (
<p>No messages yet.</p>
)}
</li>
<li key={room.roomId} >
<div className="bg-gray-200 p-4 mb-2 border-b border-gray-300 rounded w-full" onClick={() => handleRoomClick(room)}>
<h3>{room.roomName} (ID: {room.roomId})</h3>
{room.lastMessage ? (
<p>{room.lastMessage.sender}: {room.lastMessage.content}</p>
) : (
<p>暂无消息。</p>
)}
</div>
</li>
))
}
</ul>
......
'use client'
import { useState, useEffect, useRef, use } from 'react'
import * as Ty from './interface'
import { useRoom } from './room-context'
export default function RoomMessageProvider() {
const { currentRoomInfo, msgVersion } = useRoom()
const [messagesLoading, setMessagesLoading] = useState<boolean>(false)
const [messagesError, setMessagesError] = useState<string>('')
const [messages, setMessages] = useState<Ty.Message[]>([])
const messagesRef = useRef(messages);
useEffect(() => { messagesRef.current = messages; }, [messages]);
/*
- url: /api/room/message/list
- method: GET
- argument:
interface RoomMessageListArgs {
roomId: number;
}
- response:
interface RoomMessageListRes {
messages: Message[];
}
*/
const getMessages = async (currentRoomInfo : Ty.RoomPreviewInfo) => {
setMessagesLoading(true)
setMessagesError('')
const Args : Ty.RoomMessageListArgs = {
roomId: currentRoomInfo.roomId
}
const params = new URLSearchParams();
Object.entries(Args).forEach(([k, v]) => params.set(k, String(v)));
try {
const res = await fetch(`${Ty.urlPrefix}/api/room/message/list?${params}`, {
method: 'GET',
});
if (!res.ok) {
throw new Error('无法获取房间列表,错误码:' + res.status);
}
const data: Ty.BackendResponse<Ty.RoomMessageListRes> = await res.json();
if (data.code !== 0) {
throw new Error('无法获取房间列表,错误信息:' + data.message);
}
setMessages(data.data.messages);
} catch (error) {
console.error('房间列表获取失败:', error);
setMessagesError(error.message);
} finally {
setMessagesLoading(false);
}
}
/*
- url: /api/room/message/getUpdate
- method: GET
- argument:
interface RoomMessageGetUpdateArgs {
roomId: number;
sinceMessageId: number;
}
- response:
interface RoomMessageGetUpdateRes {
messages: Message[];
}
*/
const updateMessages = async (currentRoomInfo : Ty.RoomPreviewInfo, sinceMessageId: number) => {
setMessagesLoading(true)
setMessagesError('')
const Args : Ty.RoomMessageGetUpdateArgs = {
roomId: currentRoomInfo.roomId,
sinceMessageId
}
const params = new URLSearchParams();
Object.entries(Args).forEach(([k, v]) => params.set(k, String(v)));
try{
const res = await fetch(`${Ty.urlPrefix}/api/room/message/getUpdate?${params}`, {
method: 'GET',
});
if (!res.ok) {
throw new Error('无法获取房间列表,错误码:' + res.status);
}
const data: Ty.BackendResponse<Ty.RoomMessageGetUpdateRes> = await res.json();
if (data.code !== 0) {
throw new Error('无法获取房间列表,错误信息:' + data.message);
}
setMessages((prevMessages) => [...prevMessages, ...data.data.messages]);
} catch (error) {
console.error('房间列表获取失败:', error);
setMessagesError(error.message);
} finally {
setMessagesLoading(false);
}
}
const scrollRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setMessagesLoading(false)
setMessagesError('')
setMessages([]);
if(currentRoomInfo)
{
getMessages(currentRoomInfo);
const interval = setInterval(() => {
const msg = messagesRef.current
const lastId = msg.length ? msg[msg.length - 1].messageId : 0;
updateMessages(currentRoomInfo, lastId);
}, 2000); // 2秒
return () => {
clearInterval(interval)
}
}
}, [currentRoomInfo, msgVersion]);
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [messages]);
if(currentRoomInfo)
{
return (
<div
ref={scrollRef}
className="bg-white p-4 overflow-y-auto h-3/5"
>
<div className="bg-gray-100 min-h-full">
{messagesError && <div className="text-red-500">{messagesError}</div>}
<ul className="mb-2">
{messages?.map((msg) => (
<li key={msg.messageId} className="mb-3">
<div className="text-xs text-gray-500 mb-1">{msg.sender}</div>
<div className="inline-block max-w-xs lg:max-w-md px-3 py-2 rounded-xl bg-blue-500 text-white">
{msg.content}
</div>
</li>
))}
</ul>
</div>
</div>
);
}
else
{
return (
<div
ref={scrollRef}
className="bg-white p-4 overflow-y-auto h-3/5"
>
<div className="bg-gray-100 min-h-full">
<h2 className='text-lg font-bold text-gray-500'>
查看消息
</h2>
</div>
</div>
);
}
};
\ No newline at end of file
'use client'
import { useRoom } from './room-context'
import { useState } from 'react'
import * as Ty from './interface'
export default function SendMessageProvider() {
const { username, currentRoomInfo, triggerRefresh } = useRoom()
const [text, setText] = useState('');
const [sendError, setSendError] = useState<string | null>(null);
/*
- url: /api/message/add
- method: POST
- argument:
interface MessageAddArgs {
roomId: number;
content: string;
sender: string;
}
- response: null (只要code为0即为成功)
*/
const sendMessage = async () => {
setSendError(null);
if (!currentRoomInfo) return;
const Args : Ty.MessageAddArgs = {
roomId: currentRoomInfo.roomId,
content: text,
sender: username
};
try {
const response = await fetch(`${Ty.urlPrefix}/api/message/add`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(Args)
});
const data: Ty.BackendResponse<null> = await response.json();
if (data.code === 0) {
setText('');
triggerRefresh();
} else {
throw new Error(`发送失败: ${data.message}`);
}
} catch (error) {
setSendError(error.message);
}
}
return (
<>
{sendError && <div className="text-red-500">{sendError}</div>}
<textarea
className="w-full resize-none"
rows={4}
placeholder={currentRoomInfo ? '输入消息...' : '请选择房间后再发送消息'}
value={text}
onChange={(e) => setText(e.target.value)}
disabled={!currentRoomInfo}
/>
<button
className={
currentRoomInfo && text.length ?
"bg-blue-500 text-white py-2 px-4 mt-2 w-full hover:bg-blue-600 active:bg-blue-700" :
"bg-gray-300 text-gray-500 py-2 px-4 mt-2 w-full cursor-not-allowed"
}
onClick={sendMessage}
>
发送
</button>
</>
)
}
\ No newline at end of file
'use client'
import { useEffect } from 'react';
import { redirect } from 'next/navigation';
import { useRoom } from './room-context'
export default function UsernameCheck() {
const { username } = useRoom()
useEffect(() => {
if (!username) {
redirect('/');
}
}, [username]);
if (!username) return null;
return null;
}
\ No newline at end of file
import * as Ty from './interface'
import RoomListProvider from './room-list';
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default function Home() {
return (
<div className="bg-gray-100">
<div className="flex h-screen">
<div className="w-1/4 bg-white p-4 overflow-y-auto">
<h2 className="text-lg font-bold mb-4">房间列表</h2>
<RoomListProvider />
</div>
<div className="w-3/4 bg-gray-100 p-4">
{/* 聊天房间名称 */}
<div className="bg-white p-4 mb-4">
<h2 className="text-lg font-bold">聊天房间名称</h2>
</div>
const [username, setUsername] = useState('');
const router = useRouter();
{/* 历史消息列表 */}
<div className="bg-white p-4 overflow-y-auto h-3/5">
<div className="bg-gray-100 h-full">
{/* 消息项 */}
<div className="mb-2">消息1</div>
<div className="mb-2">消息2</div>
<div className="mb-2">消息3</div>
</div>
</div>
const handleSubmit = () => {
if (!username.trim()) return; // 简单校验
router.push(`/chat?username=${encodeURIComponent(username)}`);
};
<div className="bg-white p-4">
<textarea className="w-full resize-none" rows={4} placeholder="输入消息..."></textarea>
<button className="bg-blue-500 text-white py-2 px-4 mt-2 w-full hover:bg-blue-600 active:bg-blue-700">
发送
</button>
</div>
</div>
</div>
return (
<div className="bg-gray-100 h-screen flex flex-col items-center justify-center">
<h1 className="text-2xl mb-4">请输入用户名</h1>
<input
className="border border-gray-300 rounded px-3 py-2 mb-4"
placeholder="用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
<button
className=
{username.length ?
"bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 active:bg-blue-700" :
"bg-gray-300 text-gray-500 px-4 py-2 rounded cursor-not-allowed"
}
onClick={handleSubmit}
>
进入聊天室
</button>
</div>
);
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment