Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
何 广一
chatroom
Commits
353e4327
Commit
353e4327
authored
Aug 21, 2025
by
何 广一
Browse files
get & update & submit message
parent
e42e383f
Changes
9
Show whitespace changes
Inline
Side-by-side
chat-room/src/app/chat/current-room.tsx
0 → 100644
View file @
353e4327
'
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
chat-room/src/app/interface.tsx
→
chat-room/src/app/
chat/
interface.tsx
View file @
353e4327
...
...
@@ -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
chat-room/src/app/chat/page.tsx
0 → 100644
View file @
353e4327
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
>
);
}
chat-room/src/app/chat/room-context.tsx
0 → 100644
View file @
353e4327
'
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
chat-room/src/app/room-list.tsx
→
chat-room/src/app/
chat/
room-list.tsx
View file @
353e4327
'
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
()
/*
...
...
@@ -45,30 +47,38 @@ export default function RoomListProvider() {
}
}
const
handleRoomClick
=
(
room
:
Ty
.
RoomPreviewInfo
)
=>
{
setCurrentRoomInfo
(
room
);
}
useEffect
(()
=>
{
getRooms
();
const
interval
=
setInterval
(()
=>
{
getRooms
();
},
1
000
);
//
1
秒
},
2
000
);
//
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
}
>
<
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
>
Last message from
{
room
.
lastMessage
.
sender
}
:
{
room
.
lastMessage
.
content
}
</
p
>
<
p
>
{
room
.
lastMessage
.
sender
}
:
{
room
.
lastMessage
.
content
}
</
p
>
)
:
(
<
p
>
No messages yet.
</
p
>
<
p
>
暂无消息。
</
p
>
)
}
</
div
>
</
li
>
))
}
...
...
chat-room/src/app/chat/room-message.tsx
0 → 100644
View file @
353e4327
'
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
chat-room/src/app/chat/send-message.tsx
0 → 100644
View file @
353e4327
'
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
chat-room/src/app/chat/username-check.tsx
0 → 100644
View file @
353e4327
'
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
chat-room/src/app/page.tsx
View file @
353e4327
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"
>
发送
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
>
</
div
>
</
div
>
</
div
>
);
}
\ No newline at end of file
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment