|
@@ -6,7 +6,7 @@ import { useParams, useSearchParams } from 'react-router';
|
|
|
import AliRtcEngine, { AliRtcSubscribeState, AliRtcVideoTrack } from 'aliyun-rtc-sdk';
|
|
import AliRtcEngine, { AliRtcSubscribeState, AliRtcVideoTrack } from 'aliyun-rtc-sdk';
|
|
|
import { toast } from 'sonner';
|
|
import { toast } from 'sonner';
|
|
|
import { User } from '../../hooks/AuthProvider';
|
|
import { User } from '../../hooks/AuthProvider';
|
|
|
-import { aliyunClient, fileClient } from '@/client/api';
|
|
|
|
|
|
|
+import { aliyunClient, fileClient, chatMessageClient } from '@/client/api';
|
|
|
import { UserType } from '@/server/modules/users/user.enum';
|
|
import { UserType } from '@/server/modules/users/user.enum';
|
|
|
export enum Role {
|
|
export enum Role {
|
|
|
Teacher = UserType.TEACHER,
|
|
Teacher = UserType.TEACHER,
|
|
@@ -127,7 +127,11 @@ export const useClassroom = ({ user }:{ user : User }) => {
|
|
|
senderId,
|
|
senderId,
|
|
|
timestamp: Date.now()
|
|
timestamp: Date.now()
|
|
|
};
|
|
};
|
|
|
- setMessageList((prevMessageList) => [...prevMessageList, message])
|
|
|
|
|
|
|
+ setMessageList((prevMessageList) => [...prevMessageList, message]);
|
|
|
|
|
+ // 异步保存消息到数据库
|
|
|
|
|
+ saveMessageToDatabase(message).catch(() => {
|
|
|
|
|
+ // 静默失败,已经在saveMessageToDatabase中处理了错误
|
|
|
|
|
+ });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const showImageMessage = (imageUrl: string, senderName: string, senderId?: string): void => {
|
|
const showImageMessage = (imageUrl: string, senderName: string, senderId?: string): void => {
|
|
@@ -138,7 +142,11 @@ export const useClassroom = ({ user }:{ user : User }) => {
|
|
|
senderId,
|
|
senderId,
|
|
|
timestamp: Date.now()
|
|
timestamp: Date.now()
|
|
|
};
|
|
};
|
|
|
- setMessageList((prevMessageList) => [...prevMessageList, message])
|
|
|
|
|
|
|
+ setMessageList((prevMessageList) => [...prevMessageList, message]);
|
|
|
|
|
+ // 异步保存消息到数据库
|
|
|
|
|
+ saveMessageToDatabase(message).catch(() => {
|
|
|
|
|
+ // 静默失败,已经在saveMessageToDatabase中处理了错误
|
|
|
|
|
+ });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const showSystemMessage = (text: string): void => {
|
|
const showSystemMessage = (text: string): void => {
|
|
@@ -147,7 +155,72 @@ export const useClassroom = ({ user }:{ user : User }) => {
|
|
|
content: text,
|
|
content: text,
|
|
|
timestamp: Date.now()
|
|
timestamp: Date.now()
|
|
|
};
|
|
};
|
|
|
- setMessageList((prevMessageList) => [...prevMessageList, message])
|
|
|
|
|
|
|
+ setMessageList((prevMessageList) => [...prevMessageList, message]);
|
|
|
|
|
+ // 异步保存系统消息到数据库
|
|
|
|
|
+ saveMessageToDatabase(message).catch(() => {
|
|
|
|
|
+ // 静默失败,已经在saveMessageToDatabase中处理了错误
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 保存消息到数据库
|
|
|
|
|
+ const saveMessageToDatabase = async (message: Message): Promise<void> => {
|
|
|
|
|
+ if (!classId) return;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ await chatMessageClient.$post({
|
|
|
|
|
+ json: {
|
|
|
|
|
+ classId,
|
|
|
|
|
+ type: message.type,
|
|
|
|
|
+ content: message.content,
|
|
|
|
|
+ senderId: message.senderId || null,
|
|
|
|
|
+ senderName: message.sender || null,
|
|
|
|
|
+ timestamp: message.timestamp
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('保存消息到数据库失败:', error);
|
|
|
|
|
+ // 静默失败,不影响用户体验
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 保存IM消息到数据库(直接从IM消息对象创建)
|
|
|
|
|
+ const saveImMessageToDatabase = async (
|
|
|
|
|
+ msg: AliVCInteraction.ImMessage,
|
|
|
|
|
+ type: 'text' | 'image' | 'system',
|
|
|
|
|
+ content: string,
|
|
|
|
|
+ senderName?: string
|
|
|
|
|
+ ): Promise<void> => {
|
|
|
|
|
+ if (!classId) return;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ await chatMessageClient.$post({
|
|
|
|
|
+ json: {
|
|
|
|
|
+ classId,
|
|
|
|
|
+ type,
|
|
|
|
|
+ content,
|
|
|
|
|
+ senderId: msg.sender?.userId || null,
|
|
|
|
|
+ senderName: senderName || msg.sender?.userId || '系统',
|
|
|
|
|
+ timestamp: msg.timestamp || Date.now()
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('保存IM消息到数据库失败:', error);
|
|
|
|
|
+ // 静默失败,不影响用户体验
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 批量保存消息到数据库
|
|
|
|
|
+ const saveMessagesToDatabase = async (messages: Message[]): Promise<void> => {
|
|
|
|
|
+ if (!classId || messages.length === 0) return;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 由于API只支持单条创建,这里逐条保存
|
|
|
|
|
+ for (const message of messages) {
|
|
|
|
|
+ await saveMessageToDatabase(message);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('批量保存消息到数据库失败:', error);
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const showToast = (type: 'info' | 'success' | 'error', message: string): void => {
|
|
const showToast = (type: 'info' | 'success' | 'error', message: string): void => {
|
|
@@ -266,9 +339,13 @@ export const useClassroom = ({ user }:{ user : User }) => {
|
|
|
if (data.action === 'start_class') {
|
|
if (data.action === 'start_class') {
|
|
|
setClassStatus(ClassStatus.IN_PROGRESS);
|
|
setClassStatus(ClassStatus.IN_PROGRESS);
|
|
|
showSystemMessage('老师已开始上课');
|
|
showSystemMessage('老师已开始上课');
|
|
|
|
|
+ // 保存课堂开始消息到数据库
|
|
|
|
|
+ saveImMessageToDatabase(msg, 'system', '老师已开始上课', '系统').catch(() => {});
|
|
|
} else if (data.action === 'end_class') {
|
|
} else if (data.action === 'end_class') {
|
|
|
setClassStatus(ClassStatus.ENDED);
|
|
setClassStatus(ClassStatus.ENDED);
|
|
|
showSystemMessage('老师已结束上课');
|
|
showSystemMessage('老师已结束上课');
|
|
|
|
|
+ // 保存课堂结束消息到数据库
|
|
|
|
|
+ saveImMessageToDatabase(msg, 'system', '老师已结束上课', '系统').catch(() => {});
|
|
|
|
|
|
|
|
// 学生端收到结束课堂消息后,立即自动离开群组
|
|
// 学生端收到结束课堂消息后,立即自动离开群组
|
|
|
if (role === Role.Student) {
|
|
if (role === Role.Student) {
|
|
@@ -300,6 +377,8 @@ export const useClassroom = ({ user }:{ user : User }) => {
|
|
|
const data = JSON.parse(msg.data);
|
|
const data = JSON.parse(msg.data);
|
|
|
if (data.action === 'toggle_mute' && data.userId === userId) {
|
|
if (data.action === 'toggle_mute' && data.userId === userId) {
|
|
|
showSystemMessage(data.mute ? '你已被老师静音' : '老师已取消你的静音');
|
|
showSystemMessage(data.mute ? '你已被老师静音' : '老师已取消你的静音');
|
|
|
|
|
+ // 保存静音消息到数据库
|
|
|
|
|
+ saveImMessageToDatabase(msg, 'system', data.mute ? '你已被老师静音' : '老师已取消你的静音', '系统').catch(() => {});
|
|
|
}
|
|
}
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
console.error('解析静音指令失败', err);
|
|
console.error('解析静音指令失败', err);
|
|
@@ -314,6 +393,8 @@ export const useClassroom = ({ user }:{ user : User }) => {
|
|
|
};
|
|
};
|
|
|
setHandUpList([...handUpList, handUpData]);
|
|
setHandUpList([...handUpList, handUpData]);
|
|
|
showSystemMessage(`${data.studentName || data.studentId} 举手了`);
|
|
showSystemMessage(`${data.studentName || data.studentId} 举手了`);
|
|
|
|
|
+ // 保存举手消息到数据库
|
|
|
|
|
+ saveImMessageToDatabase(msg, 'system', `${data.studentName || data.studentId} 举手了`, '系统').catch(() => {});
|
|
|
} else if (data.action === InteractionAction.CancelHandUp) {
|
|
} else if (data.action === InteractionAction.CancelHandUp) {
|
|
|
setHandUpList(handUpList.filter(h => h.studentId !== data.studentId));
|
|
setHandUpList(handUpList.filter(h => h.studentId !== data.studentId));
|
|
|
}
|
|
}
|
|
@@ -339,6 +420,8 @@ export const useClassroom = ({ user }:{ user : User }) => {
|
|
|
setQuestions([...questions, question]);
|
|
setQuestions([...questions, question]);
|
|
|
}
|
|
}
|
|
|
showSystemMessage(`收到问题: ${data.question}`);
|
|
showSystemMessage(`收到问题: ${data.question}`);
|
|
|
|
|
+ // 保存问题消息到数据库
|
|
|
|
|
+ saveImMessageToDatabase(msg, 'system', `收到问题: ${data.question}`, '系统').catch(() => {});
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
console.error('解析问题消息失败', err);
|
|
console.error('解析问题消息失败', err);
|
|
|
}
|
|
}
|
|
@@ -348,6 +431,8 @@ export const useClassroom = ({ user }:{ user : User }) => {
|
|
|
if (data.action === InteractionAction.AnswerHandUp && data.studentId === userId) {
|
|
if (data.action === InteractionAction.AnswerHandUp && data.studentId === userId) {
|
|
|
showSystemMessage('老师已应答你的举手');
|
|
showSystemMessage('老师已应答你的举手');
|
|
|
setHandUpList(handUpList.filter(h => h.studentId !== data.studentId));
|
|
setHandUpList(handUpList.filter(h => h.studentId !== data.studentId));
|
|
|
|
|
+ // 保存应答消息到数据库
|
|
|
|
|
+ saveImMessageToDatabase(msg, 'system', '老师已应答你的举手', '系统').catch(() => {});
|
|
|
}
|
|
}
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
console.error('解析应答消息失败', err);
|
|
console.error('解析应答消息失败', err);
|
|
@@ -357,6 +442,8 @@ export const useClassroom = ({ user }:{ user : User }) => {
|
|
|
const data = JSON.parse(msg.data) as InteractionMessage;
|
|
const data = JSON.parse(msg.data) as InteractionMessage;
|
|
|
if (data.action === InteractionAction.KickStudent && data.studentId === userId) {
|
|
if (data.action === InteractionAction.KickStudent && data.studentId === userId) {
|
|
|
showSystemMessage('您已被老师移出课堂');
|
|
showSystemMessage('您已被老师移出课堂');
|
|
|
|
|
+ // 保存踢人消息到数据库
|
|
|
|
|
+ saveImMessageToDatabase(msg, 'system', '您已被老师移出课堂', '系统').catch(() => {});
|
|
|
// 学生被踢出后自动离开课堂
|
|
// 学生被踢出后自动离开课堂
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
leaveClass();
|
|
leaveClass();
|
|
@@ -664,6 +751,37 @@ export const useClassroom = ({ user }:{ user : User }) => {
|
|
|
listenGroupEvents();
|
|
listenGroupEvents();
|
|
|
listenMessageEvents();
|
|
listenMessageEvents();
|
|
|
|
|
|
|
|
|
|
+ // 加载历史聊天消息
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await chatMessageClient.history.$get({
|
|
|
|
|
+ query: {
|
|
|
|
|
+ classId,
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ pageSize: 50
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (response.status === 200) {
|
|
|
|
|
+ const historyData = await response.json();
|
|
|
|
|
+ if (historyData.data && historyData.data.length > 0) {
|
|
|
|
|
+ // 将历史消息添加到消息列表
|
|
|
|
|
+ const historyMessages: Message[] = historyData.data.map((msg: any) => ({
|
|
|
|
|
+ type: msg.type,
|
|
|
|
|
+ content: msg.content,
|
|
|
|
|
+ sender: msg.senderName || msg.senderId,
|
|
|
|
|
+ senderId: msg.senderId,
|
|
|
|
|
+ timestamp: msg.timestamp
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ setMessageList(prev => [...historyMessages, ...prev]);
|
|
|
|
|
+ showSystemMessage(`已加载 ${historyData.data.length} 条历史消息`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('加载历史消息失败:', error);
|
|
|
|
|
+ // 静默失败,不影响主要功能
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 只有老师角色需要获取现有的群组成员列表
|
|
// 只有老师角色需要获取现有的群组成员列表
|
|
|
if (role === Role.Teacher) {
|
|
if (role === Role.Teacher) {
|
|
|
try {
|
|
try {
|
|
@@ -1037,7 +1155,7 @@ export const useClassroom = ({ user }:{ user : User }) => {
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
if (isAudioOn) {
|
|
if (isAudioOn) {
|
|
|
- await aliRtcEngine.current?.stopAudioCapture()
|
|
|
|
|
|
|
+ await aliRtcEngine.current?.stopAudioCapture();
|
|
|
await aliRtcEngine.current?.publishLocalAudioStream(false);
|
|
await aliRtcEngine.current?.publishLocalAudioStream(false);
|
|
|
} else {
|
|
} else {
|
|
|
await aliRtcEngine.current?.publishLocalAudioStream(true);
|
|
await aliRtcEngine.current?.publishLocalAudioStream(true);
|