From cb947429fb9970e172c4ee2162d018c52b405a12 Mon Sep 17 00:00:00 2001 From: CrescentLeaf Date: Sun, 21 Sep 2025 16:11:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=B6=E7=99=BC=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/ui/chat/ChatFragment.tsx | 96 ++++++++++++++++++++++++++------- server/api/BaseApi.ts | 6 ++- server/api/ChatApi.ts | 44 +++++++++++++-- 3 files changed, 122 insertions(+), 24 deletions(-) diff --git a/client/ui/chat/ChatFragment.tsx b/client/ui/chat/ChatFragment.tsx index 98af06a..1845f97 100644 --- a/client/ui/chat/ChatFragment.tsx +++ b/client/ui/chat/ChatFragment.tsx @@ -1,14 +1,14 @@ -import { Tab } from "mdui" +import { Tab, TextField } from "mdui" import useEventListener from "../useEventListener.ts" -import Element_Message from "./Message.jsx" -import MessageContainer from "./MessageContainer.jsx" +import Element_Message from "./Message.tsx" +import MessageContainer from "./MessageContainer.tsx" import * as React from 'react' import Client from "../../api/Client.ts" import Message from "../../api/client_data/Message.ts" import Chat from "../../api/client_data/Chat.ts" import data from "../../Data.ts" -import { checkApiSuccessOrSncakbar } from "../snackbar.ts" +import { checkApiSuccessOrSncakbar, snackbar } from "../snackbar.ts" import useAsyncEffect from "../useAsyncEffect.ts" interface Args extends React.HTMLAttributes { @@ -23,6 +23,7 @@ export default function ChatFragment({ target, ...props }: Args) { const [tabItemSelected, setTabItemSelected] = React.useState('Chat') const tabRef = React.useRef(null) + const chatPanelRef = React.useRef(null) useEventListener(tabRef, 'change', () => { setTabItemSelected(tabRef.current?.value || "Chat") }) @@ -35,28 +36,71 @@ export default function ChatFragment({ target, ...props }: Args) { if (re.code != 200) return checkApiSuccessOrSncakbar(re, "對話錯誤") setChatInfo(re.data as Chat) + + loadMore() }, [target]) - let page = 0 + const page = React.useRef(0) async function loadMore() { const re = await Client.invoke("Chat.getMessageHistory", { token: data.access_token, target, - page, + page: page.current, }) if (checkApiSuccessOrSncakbar(re, "拉取歷史記錄失敗")) return - page++ - setMessagesList(messagesList.concat()) + const returnMsgs = (re.data!.messages as Message[]).reverse() + if (returnMsgs.length == 0) + return snackbar({ + message: "已經沒有消息了哦~", + placement: 'top', + }) + setMessagesList(returnMsgs.concat(messagesList)) + + if (page.current == 0 + 1) + setTimeout(() => chatPanelRef.current!.scrollTo({ + top: 10000000000, + behavior: "smooth", + }), 100) + + page.current++ } React.useEffect(() => { - + interface OnMessageData { + chat: string + msg: Message + } + Client.on('Client.onMessage', (data: unknown) => { + const { chat, msg } = (data as OnMessageData) + if (target == chat) { + setMessagesList(messagesList.concat([msg])) + } + }) return () => { - + Client.off('Client.onMessage') } }) + const inputRef = React.useRef(null) + + async function sendMessage() { + const text = inputRef.current!.value + + const re = await Client.invoke("Chat.sendMessage", { + token: data.access_token, + target, + text, + }, 5000) + if (checkApiSuccessOrSncakbar(re, "發送失敗")) return + inputRef.current!.value = '' + + chatPanelRef.current!.scrollTo({ + top: 10000000000, + behavior: "smooth", + }) + } + return (
設定 - - 加載更多 + loadMore()}>加載更多
- + + { + messagesList.map((msg) => + + {msg.text} + + ) + } { // 输入框 @@ -96,16 +153,19 @@ export default function ChatFragment({ target, ...props }: Args) {
- { + if (event.ctrlKey && event.key == 'Enter') + sendMessage() + }} style={{ marginRight: '10px', }}> + }} onClick={() => sendMessage()}>
{ - if (this.checkArgsMissing(args, ['token', 'target'])) return { + if (this.checkArgsMissing(args, ['token', 'target', 'text'])) return { msg: "參數缺失", code: 400, } @@ -71,9 +72,42 @@ export default class ChatApi extends BaseApi { msg: "令牌無效", } + const chat = Chat.findById(args.target as string) + if (chat == null) return { + code: 404, + msg: "對話不存在", + } + + const msg = { + text: args.text as string, + user_id: token.author, + } + const id = MessagesManager.getInstanceForChat(chat).addMessage(msg) + + const users: string[] = [] + if (chat.bean.type == 'private') { + users.push(token.author as string) + } + + for (const user of users) { + if (ApiManager.checkUserIsOnline(user)) { + const sockets = ApiManager.getUserClientSockets(user) + for (const socket of Object.keys(sockets)) + this.emitToClient(sockets[socket], 'Client.onMessage', { + chat: chat.bean.id, + msg: { + ...msg, + id + } + }) + } else { + // TODO: EventStore + } + } + return { - code: 501, - msg: "未實現", + code: 200, + msg: "成功", } }) /** @@ -104,7 +138,7 @@ export default class ChatApi extends BaseApi { code: 200, msg: "成功", data: { - messages: MessagesManager.getInstanceForChat(chat).getMessagesWithPage(), + messages: MessagesManager.getInstanceForChat(chat).getMessagesWithPage(15, args.page as number), }, } })