添加测试按钮,修改上课页面逻辑
This commit is contained in:
parent
f1e7589344
commit
7faa9f1b35
103
README.md
103
README.md
@ -1,81 +1,64 @@
|
||||
# 智慧教室管理系统
|
||||
# ClassMG Communication Server
|
||||
|
||||
## 项目概述
|
||||
This server enables real-time communication between multiple instances of the ClassMG application running on different devices. It provides a simple HTTP API for message passing, classroom management, and question handling.
|
||||
|
||||
智慧教室管理系统是一款基于HarmonyOS/ArkTS开发的应用,旨在提供智能化的教室管理解决方案。系统包含多个功能模块,用于管理教室状态、课程信息和用户权限等。
|
||||
## Prerequisites
|
||||
|
||||
## 功能特点
|
||||
- Node.js (v12 or higher)
|
||||
- NPM (Node Package Manager)
|
||||
|
||||
- **用户管理**: 支持教师和学生两种用户类型,提供差异化的功能体验
|
||||
- **教室监控**: 实时监控教室温度、湿度、人数等数据
|
||||
- **课程数据**: 展示课程信息、教师信息和学生出勤情况
|
||||
- **个人设置**: 用户可以修改个人信息和系统偏好设置
|
||||
## Installation
|
||||
|
||||
## 技术架构
|
||||
1. Install Node.js and NPM if you haven't already:
|
||||
- Download from [nodejs.org](https://nodejs.org/)
|
||||
|
||||
- **前端**: HarmonyOS/ArkTS
|
||||
- **后端**: MySQL数据库
|
||||
- **通信**: HTTP API
|
||||
2. Clone or download this repository to your computer
|
||||
|
||||
## 系统要求
|
||||
3. Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
- HarmonyOS设备
|
||||
- 网络连接
|
||||
- 支持HarmonyOS 3.0及以上版本
|
||||
## Running the server
|
||||
|
||||
## 数据库配置
|
||||
1. Start the server:
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
or
|
||||
```bash
|
||||
node server.js
|
||||
```
|
||||
|
||||
系统使用MySQL数据库存储用户信息和系统数据。数据库配置如下:
|
||||
2. The server will start on port 5243. Make note of your computer's IP address, which will be displayed in the console output.
|
||||
|
||||
- **数据库地址**: 139.155.155.67:25342
|
||||
- **数据库名**: hongm
|
||||
- **用户名**: hongm
|
||||
- **密码**: JsKJeG7CX2WnyArt
|
||||
## Connecting ClassMG app to the server
|
||||
|
||||
### 数据库初始化
|
||||
1. Make sure all devices (server and app clients) are on the same network
|
||||
|
||||
导入`database_setup.sql`文件以创建必要的表和初始数据。主要的数据表包括:
|
||||
2. Open the ClassMG project in DevEco Studio
|
||||
|
||||
- **UserText**: 存储用户个人信息
|
||||
- **UserPassword**: 存储用户登录信息
|
||||
3. Update the `CHANNEL_API_CONFIG.baseUrl` in `entry/src/main/ets/common/ClassRoomService.ets` to:
|
||||
```
|
||||
http://<Your computer's IP address>:5243
|
||||
```
|
||||
|
||||
## 使用指南
|
||||
4. Compile and run the ClassMG app on your devices
|
||||
|
||||
### 登录系统
|
||||
## Testing
|
||||
|
||||
1. 在登录页面输入您的账号和密码(默认密码: 1)
|
||||
2. 系统会根据账号自动识别用户类型:
|
||||
- 账号包含"2"的识别为学生
|
||||
- 账号包含"0"的识别为教师
|
||||
3. 登录成功后将显示过渡页面,然后进入系统主页
|
||||
1. Run the server on one computer
|
||||
2. Run the ClassMG app on two different devices (or emulators)
|
||||
3. Log in as a teacher on one device and create a class
|
||||
4. Log in as a student on the other device and join the class using the class code
|
||||
5. Test messaging, question publishing, and answering functionality
|
||||
|
||||
### 系统导航
|
||||
## Troubleshooting
|
||||
|
||||
系统包含三个主要页面:
|
||||
- **Connection Issues**: Make sure all devices are on the same network and can reach the server's IP address
|
||||
- **Port Conflicts**: If port 5243 is already in use, change the PORT variable in server.js and update the app accordingly
|
||||
- **CORS Issues**: If experiencing cross-origin problems, verify the CORS middleware is correctly configured in server.js
|
||||
|
||||
- **首页**: 显示教室监控和综合上课数据
|
||||
- **上课**: 提供课程相关功能
|
||||
- **设置**: 管理个人信息和系统设置
|
||||
## Note
|
||||
|
||||
### 个人信息设置
|
||||
|
||||
在设置页面,用户可以查看和修改个人信息:
|
||||
|
||||
- **头像**: 显示用户头像
|
||||
- **账号**: 显示当前登录账号(不可修改)
|
||||
- **昵称**: 显示用户昵称(不可修改)
|
||||
- **邮箱**: 可修改,需符合邮箱格式
|
||||
- **电话**: 显示联系电话(不可修改)
|
||||
|
||||
## 开发信息
|
||||
|
||||
- **开发团队**: 922213102班鸿蒙第一组
|
||||
- **版本**: 1.0.0
|
||||
|
||||
## 备注
|
||||
|
||||
- 用户密码默认为"1"
|
||||
- 系统现有示例用户:
|
||||
- 学生: 账号"2",昵称"张三"
|
||||
- 学生: 账号"9222",昵称"李华"
|
||||
- 教师: 账号"0",昵称"教师demo"
|
||||
This is a development server intended for local testing only. For production use, additional security measures would be required.
|
930
entry/src/main/ets/common/ClassRoomService.ets
Normal file
930
entry/src/main/ets/common/ClassRoomService.ets
Normal file
@ -0,0 +1,930 @@
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import logManager, { LogCategory, LogEventType } from './logtext';
|
||||
import settingsService from './SettingsService';
|
||||
import { DatabaseService } from './DatabaseService';
|
||||
import http from '@ohos.net.http';
|
||||
|
||||
// 课堂会话状态
|
||||
export enum ClassSessionStatus {
|
||||
ACTIVE = 'active',
|
||||
ENDED = 'ended'
|
||||
}
|
||||
|
||||
// 学生状态
|
||||
export enum StudentStatus {
|
||||
ONLINE = 'online',
|
||||
OFFLINE = 'offline'
|
||||
}
|
||||
|
||||
// 题目状态
|
||||
export enum QuestionStatus {
|
||||
ACTIVE = 'active',
|
||||
ENDED = 'ended'
|
||||
}
|
||||
|
||||
// 发送者角色
|
||||
export enum SenderRole {
|
||||
TEACHER = 'teacher',
|
||||
STUDENT = 'student'
|
||||
}
|
||||
|
||||
// 课堂模型
|
||||
export class ClassSessionModel {
|
||||
sessionId: string = ''; // 课堂ID(由系统生成)
|
||||
className: string = ''; // 课堂名称
|
||||
classCode: string = ''; // 课堂暗号(由教师设置,学生使用此暗号加入)
|
||||
teacherAccount: string = ''; // 教师账号ID
|
||||
teacherName: string = ''; // 教师姓名
|
||||
startTime: Date = new Date(); // 课堂开始时间
|
||||
endTime?: Date; // 课堂结束时间(未结束为空)
|
||||
status: ClassSessionStatus = ClassSessionStatus.ACTIVE; // 课堂状态
|
||||
students: StudentSession[] = []; // 参与学生列表
|
||||
questions: QuestionModel[] = []; // 课堂题目列表
|
||||
messages: MessageModel[] = []; // 课堂消息列表
|
||||
|
||||
constructor(sessionId: string = '',
|
||||
className: string = '',
|
||||
classCode: string = '',
|
||||
teacherAccount: string = '',
|
||||
teacherName: string = '') {
|
||||
this.sessionId = sessionId || this.generateSessionId();
|
||||
this.className = className;
|
||||
this.classCode = classCode;
|
||||
this.teacherAccount = teacherAccount;
|
||||
this.teacherName = teacherName;
|
||||
this.startTime = new Date();
|
||||
this.status = ClassSessionStatus.ACTIVE;
|
||||
}
|
||||
|
||||
// 生成唯一会话ID
|
||||
private generateSessionId(): string {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substring(2);
|
||||
}
|
||||
}
|
||||
|
||||
// 学生会话信息
|
||||
export class StudentSession {
|
||||
studentAccount: string = ''; // 学生账号
|
||||
studentName: string = ''; // 学生姓名
|
||||
joinTime: Date = new Date(); // 加入时间
|
||||
status: StudentStatus = StudentStatus.ONLINE; // 学生状态
|
||||
|
||||
constructor(studentAccount: string = '', studentName: string = '') {
|
||||
this.studentAccount = studentAccount;
|
||||
this.studentName = studentName;
|
||||
this.joinTime = new Date();
|
||||
this.status = StudentStatus.ONLINE;
|
||||
}
|
||||
}
|
||||
|
||||
// 课堂消息模型
|
||||
export class MessageModel {
|
||||
id: string = ''; // 消息ID
|
||||
senderId: string = ''; // 发送者账号
|
||||
senderName: string = ''; // 发送者姓名
|
||||
senderRole: SenderRole; // 发送者角色
|
||||
content: string = ''; // 消息内容
|
||||
timestamp: Date = new Date(); // 发送时间
|
||||
|
||||
constructor(senderId: string = '',
|
||||
senderName: string = '',
|
||||
senderRole: SenderRole,
|
||||
content: string = '') {
|
||||
this.id = Date.now().toString(36) + Math.random().toString(36).substring(2);
|
||||
this.senderId = senderId;
|
||||
this.senderName = senderName;
|
||||
this.senderRole = senderRole;
|
||||
this.content = content;
|
||||
this.timestamp = new Date();
|
||||
}
|
||||
}
|
||||
|
||||
// 题目模型
|
||||
export class QuestionModel {
|
||||
questionId: string = ''; // 题目ID
|
||||
title: string = ''; // 题目标题
|
||||
options: QuestionOption[] = []; // 题目选项
|
||||
correctOption: number = 0; // 正确选项的索引
|
||||
startTime: Date = new Date(); // 题目开始时间
|
||||
endTime?: Date; // 题目结束时间(未结束为空)
|
||||
duration: number = 30; // 答题时间(秒)
|
||||
status: QuestionStatus = QuestionStatus.ACTIVE; // 题目状态
|
||||
answers: QuestionAnswer[] = []; // 学生答案列表
|
||||
classCode: string = ''; // 关联的课堂暗号
|
||||
|
||||
constructor(title: string = '',
|
||||
options: QuestionOption[] = [],
|
||||
correctOption: number = 0,
|
||||
duration: number = 30,
|
||||
classCode: string = '') {
|
||||
this.questionId = Date.now().toString(36) + Math.random().toString(36).substring(2);
|
||||
this.title = title;
|
||||
this.options = options;
|
||||
this.correctOption = correctOption;
|
||||
this.duration = duration;
|
||||
this.startTime = new Date();
|
||||
this.status = QuestionStatus.ACTIVE;
|
||||
this.classCode = classCode;
|
||||
}
|
||||
}
|
||||
|
||||
// 题目选项
|
||||
export class QuestionOption {
|
||||
index: number = 0; // 选项索引
|
||||
content: string = ''; // 选项内容
|
||||
|
||||
constructor(index: number = 0, content: string = '') {
|
||||
this.index = index;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
// 学生答案
|
||||
export class QuestionAnswer {
|
||||
studentAccount: string = ''; // 学生账号
|
||||
studentName: string = ''; // 学生姓名
|
||||
selectedOption: number = 0; // 所选选项索引
|
||||
timestamp: Date = new Date(); // 提交时间
|
||||
isCorrect: boolean = false; // 是否正确
|
||||
|
||||
constructor(studentAccount: string = '',
|
||||
studentName: string = '',
|
||||
selectedOption: number = 0,
|
||||
correctOption: number = 0) {
|
||||
this.studentAccount = studentAccount;
|
||||
this.studentName = studentName;
|
||||
this.selectedOption = selectedOption;
|
||||
this.timestamp = new Date();
|
||||
this.isCorrect = selectedOption === correctOption;
|
||||
}
|
||||
}
|
||||
|
||||
// 事件类型
|
||||
export enum WebSocketEventType {
|
||||
JOIN_CLASS = 'joinClass',
|
||||
LEAVE_CLASS = 'leaveClass',
|
||||
SEND_MESSAGE = 'sendMessage',
|
||||
PUBLISH_QUESTION = 'publishQuestion',
|
||||
SUBMIT_ANSWER = 'submitAnswer',
|
||||
END_QUESTION = 'endQuestion',
|
||||
END_CLASS = 'endClass'
|
||||
}
|
||||
|
||||
// API响应接口定义
|
||||
export interface ChannelApiResponse {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
data?: ClassSessionModel | QuestionModel | MessageModel | QuestionAnswer | Array<MessageModel> | Array<QuestionModel> | boolean | null;
|
||||
}
|
||||
|
||||
// HTTP请求选项接口
|
||||
export interface HttpRequestOptions {
|
||||
method: number; // http.RequestMethod enum value
|
||||
header?: object;
|
||||
extraData?: string;
|
||||
connectTimeout?: number;
|
||||
readTimeout?: number;
|
||||
}
|
||||
|
||||
// HTTP响应接口
|
||||
export interface HttpResponseResult {
|
||||
responseCode: number;
|
||||
result?: string | object | ArrayBuffer;
|
||||
}
|
||||
|
||||
// 事件数据接口
|
||||
export interface EventConnection {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface QuestionAnswerEvent {
|
||||
questionId: string;
|
||||
answer: QuestionAnswer;
|
||||
}
|
||||
|
||||
// 请求数据类型接口
|
||||
export interface CreateSessionRequest {
|
||||
className: string;
|
||||
classCode: string;
|
||||
teacherAccount: string;
|
||||
teacherName: string;
|
||||
}
|
||||
|
||||
export interface JoinSessionRequest {
|
||||
classCode: string;
|
||||
studentAccount: string;
|
||||
studentName: string;
|
||||
}
|
||||
|
||||
export interface SendMessageRequest {
|
||||
classCode: string;
|
||||
message: MessageModel;
|
||||
}
|
||||
|
||||
export interface SubmitAnswerRequest {
|
||||
questionId: string;
|
||||
classCode: string;
|
||||
answer: QuestionAnswer;
|
||||
}
|
||||
|
||||
export interface EndQuestionRequest {
|
||||
questionId: string;
|
||||
classCode: string;
|
||||
}
|
||||
|
||||
export interface EndSessionRequest {
|
||||
classCode: string;
|
||||
}
|
||||
|
||||
// API配置接口
|
||||
export interface ApiConfig {
|
||||
baseUrl: string;
|
||||
pollInterval: number;
|
||||
timeout: number;
|
||||
}
|
||||
|
||||
// 事件数据联合类型
|
||||
export type EventData = ClassSessionModel | QuestionModel | MessageModel | QuestionAnswer | EventConnection | QuestionAnswerEvent | null;
|
||||
|
||||
// 事件回调类型
|
||||
export type EventCallback = (data: EventData) => void;
|
||||
|
||||
// API配置
|
||||
const CHANNEL_API_CONFIG: ApiConfig = {
|
||||
baseUrl: 'http://139.155.155.67:5243', // Change this to your computer's IP address
|
||||
pollInterval: 2000, // 轮询间隔(毫秒)
|
||||
timeout: 10000 // 请求超时(毫秒)
|
||||
};
|
||||
|
||||
// 课堂通信服务
|
||||
export class ClassRoomService {
|
||||
private static instance: ClassRoomService;
|
||||
private dbService: DatabaseService = DatabaseService.getInstance();
|
||||
|
||||
// 当前课堂会话
|
||||
private currentSession: ClassSessionModel | null = null;
|
||||
private currentClassCode: string = '';
|
||||
|
||||
// 回调函数集合
|
||||
private eventCallbacks: Map<string, Array<EventCallback>> = new Map();
|
||||
|
||||
// 是否已连接
|
||||
private connected: boolean = false;
|
||||
|
||||
// 是否为教师
|
||||
private isTeacher: boolean = false;
|
||||
|
||||
// 消息轮询定时器ID
|
||||
private pollTimerId: number = -1;
|
||||
|
||||
// 最后接收的消息ID
|
||||
private lastMessageId: string = '';
|
||||
|
||||
// 最后接收的题目ID
|
||||
private lastQuestionId: string = '';
|
||||
|
||||
private constructor() {
|
||||
// 初始化
|
||||
hilog.info(0, 'ClassMG', 'ClassRoom Service Initialized');
|
||||
}
|
||||
|
||||
public static getInstance(): ClassRoomService {
|
||||
if (!ClassRoomService.instance) {
|
||||
ClassRoomService.instance = new ClassRoomService();
|
||||
}
|
||||
return ClassRoomService.instance;
|
||||
}
|
||||
|
||||
// 创建HTTP请求客户端
|
||||
private createHttpClient(): http.HttpRequest {
|
||||
let httpRequest = http.createHttp();
|
||||
return httpRequest;
|
||||
}
|
||||
|
||||
// 发起HTTP POST请求
|
||||
private async postRequest(endpoint: string, data: object): Promise<ChannelApiResponse> {
|
||||
try {
|
||||
const httpRequest = this.createHttpClient();
|
||||
|
||||
const response = await httpRequest.request(
|
||||
`${CHANNEL_API_CONFIG.baseUrl}/${endpoint}`,
|
||||
{
|
||||
method: http.RequestMethod.POST,
|
||||
header: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
extraData: JSON.stringify(data),
|
||||
connectTimeout: CHANNEL_API_CONFIG.timeout,
|
||||
readTimeout: CHANNEL_API_CONFIG.timeout
|
||||
}
|
||||
);
|
||||
|
||||
httpRequest.destroy();
|
||||
|
||||
if (response.responseCode === 200) {
|
||||
const jsonString: string = response.result ? response.result.toString() : '{}';
|
||||
try {
|
||||
const responseData = JSON.parse(jsonString) as ChannelApiResponse;
|
||||
return responseData;
|
||||
} catch (parseError) {
|
||||
logManager.error(LogCategory.NETWORK, `Error parsing response: ${parseError}`);
|
||||
const errorResponse: ChannelApiResponse = {
|
||||
success: false,
|
||||
message: `Parse error: ${parseError}`
|
||||
};
|
||||
return errorResponse;
|
||||
}
|
||||
}
|
||||
|
||||
const errorResponse: ChannelApiResponse = {
|
||||
success: false,
|
||||
message: `HTTP error: ${response.responseCode}`
|
||||
};
|
||||
return errorResponse;
|
||||
} catch (error) {
|
||||
logManager.error(LogCategory.NETWORK, `Network request error: ${error}`);
|
||||
const errorResponse: ChannelApiResponse = {
|
||||
success: false,
|
||||
message: `Request error: ${error}`
|
||||
};
|
||||
return errorResponse;
|
||||
}
|
||||
}
|
||||
|
||||
// 发起HTTP GET请求
|
||||
private async getRequest(endpoint: string): Promise<ChannelApiResponse> {
|
||||
try {
|
||||
const httpRequest = this.createHttpClient();
|
||||
|
||||
const response = await httpRequest.request(
|
||||
`${CHANNEL_API_CONFIG.baseUrl}/${endpoint}`,
|
||||
{
|
||||
method: http.RequestMethod.GET,
|
||||
connectTimeout: CHANNEL_API_CONFIG.timeout,
|
||||
readTimeout: CHANNEL_API_CONFIG.timeout
|
||||
}
|
||||
);
|
||||
|
||||
httpRequest.destroy();
|
||||
|
||||
if (response.responseCode === 200) {
|
||||
const jsonString: string = response.result ? response.result.toString() : '{}';
|
||||
try {
|
||||
const responseData = JSON.parse(jsonString) as ChannelApiResponse;
|
||||
return responseData;
|
||||
} catch (parseError) {
|
||||
logManager.error(LogCategory.NETWORK, `Error parsing response: ${parseError}`);
|
||||
const errorResponse: ChannelApiResponse = {
|
||||
success: false,
|
||||
message: `Parse error: ${parseError}`
|
||||
};
|
||||
return errorResponse;
|
||||
}
|
||||
}
|
||||
|
||||
const errorResponse: ChannelApiResponse = {
|
||||
success: false,
|
||||
message: `HTTP error: ${response.responseCode}`
|
||||
};
|
||||
return errorResponse;
|
||||
} catch (error) {
|
||||
logManager.error(LogCategory.NETWORK, `Network request error: ${error}`);
|
||||
const errorResponse: ChannelApiResponse = {
|
||||
success: false,
|
||||
message: `Request error: ${error}`
|
||||
};
|
||||
return errorResponse;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查当前用户是否为教师
|
||||
public checkIsTeacher(): boolean {
|
||||
const currentAccount = settingsService.getCurrentAccount();
|
||||
const category = this.dbService.getUserCategory(currentAccount);
|
||||
this.isTeacher = category === 'teacher';
|
||||
return this.isTeacher;
|
||||
}
|
||||
|
||||
// 启动消息轮询
|
||||
private startPolling(): void {
|
||||
if (this.pollTimerId !== -1) {
|
||||
clearInterval(this.pollTimerId);
|
||||
}
|
||||
|
||||
this.pollTimerId = setInterval(() => {
|
||||
this.pollForUpdates();
|
||||
}, CHANNEL_API_CONFIG.pollInterval);
|
||||
}
|
||||
|
||||
// 停止消息轮询
|
||||
private stopPolling(): void {
|
||||
if (this.pollTimerId !== -1) {
|
||||
clearInterval(this.pollTimerId);
|
||||
this.pollTimerId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 轮询更新
|
||||
private async pollForUpdates(): Promise<void> {
|
||||
if (!this.connected || !this.currentClassCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 查询新消息
|
||||
const messagesResponse = await this.getRequest(`messages/${this.currentClassCode}?since=${this.lastMessageId}`);
|
||||
|
||||
if (messagesResponse.success && messagesResponse.data && Array.isArray(messagesResponse.data)) {
|
||||
const messages = messagesResponse.data as MessageModel[];
|
||||
|
||||
if (messages.length > 0) {
|
||||
// 更新最后接收的消息ID
|
||||
this.lastMessageId = messages[messages.length - 1].id;
|
||||
|
||||
// 将消息添加到当前会话
|
||||
if (this.currentSession) {
|
||||
messages.forEach(message => {
|
||||
// 避免添加自己发送的消息(已在本地添加)
|
||||
const isDuplicate = this.currentSession?.messages.some(m => m.id === message.id);
|
||||
if (!isDuplicate) {
|
||||
this.currentSession?.messages.push(message);
|
||||
this.notifyEvent(WebSocketEventType.SEND_MESSAGE, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 查询新题目
|
||||
const questionsResponse = await this.getRequest(`questions/${this.currentClassCode}?since=${this.lastQuestionId}`);
|
||||
|
||||
if (questionsResponse.success && questionsResponse.data && Array.isArray(questionsResponse.data)) {
|
||||
const questions = questionsResponse.data as QuestionModel[];
|
||||
|
||||
if (questions.length > 0) {
|
||||
// 更新最后接收的题目ID
|
||||
this.lastQuestionId = questions[questions.length - 1].questionId;
|
||||
|
||||
// 处理题目
|
||||
questions.forEach(question => {
|
||||
if (this.currentSession) {
|
||||
// 检查题目是否已存在
|
||||
const existingQuestionIndex = this.currentSession.questions.findIndex(q => q.questionId === question.questionId);
|
||||
|
||||
if (existingQuestionIndex === -1) {
|
||||
// 新题目
|
||||
this.currentSession.questions.push(question);
|
||||
this.notifyEvent(WebSocketEventType.PUBLISH_QUESTION, question);
|
||||
|
||||
// 如果题目处于活动状态且有结束时间,设置倒计时
|
||||
if (question.status === QuestionStatus.ACTIVE && question.duration > 0) {
|
||||
setTimeout(() => {
|
||||
this.checkQuestionStatus(question.questionId);
|
||||
}, question.duration * 1000);
|
||||
}
|
||||
} else {
|
||||
// 更新已有题目
|
||||
const oldQuestion = this.currentSession.questions[existingQuestionIndex];
|
||||
const oldStatus = oldQuestion.status;
|
||||
|
||||
// 更新题目
|
||||
this.currentSession.questions[existingQuestionIndex] = question;
|
||||
|
||||
// 如果状态从活动变为结束,通知题目结束
|
||||
if (oldStatus === QuestionStatus.ACTIVE && question.status === QuestionStatus.ENDED) {
|
||||
this.notifyEvent(WebSocketEventType.END_QUESTION, question);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 查询课堂状态
|
||||
const sessionResponse = await this.getRequest(`session/${this.currentClassCode}`);
|
||||
|
||||
if (sessionResponse.success && sessionResponse.data) {
|
||||
const session = sessionResponse.data as ClassSessionModel;
|
||||
|
||||
// 检查课堂是否已结束
|
||||
if (session.status === ClassSessionStatus.ENDED && this.currentSession?.status === ClassSessionStatus.ACTIVE) {
|
||||
// 更新当前会话状态
|
||||
if (this.currentSession) {
|
||||
this.currentSession.status = ClassSessionStatus.ENDED;
|
||||
this.currentSession.endTime = session.endTime ? new Date(session.endTime) : new Date();
|
||||
}
|
||||
|
||||
// 通知课堂结束
|
||||
this.notifyEvent(WebSocketEventType.END_CLASS, session);
|
||||
|
||||
// 如果不是教师,停止轮询
|
||||
if (!this.isTeacher) {
|
||||
this.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logManager.error(LogCategory.NETWORK, `Polling error: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查题目状态
|
||||
private async checkQuestionStatus(questionId: string): Promise<void> {
|
||||
try {
|
||||
const response = await this.getRequest(`question/${questionId}`);
|
||||
|
||||
if (response.success && response.data) {
|
||||
const question = response.data as QuestionModel;
|
||||
|
||||
// 如果题目仍处于活动状态,结束题目
|
||||
if (question.status === QuestionStatus.ACTIVE && this.currentSession) {
|
||||
// 查找并更新本地题目
|
||||
const questionIndex = this.currentSession.questions.findIndex(q => q.questionId === questionId);
|
||||
|
||||
if (questionIndex !== -1) {
|
||||
// 更新题目状态
|
||||
this.currentSession.questions[questionIndex].status = QuestionStatus.ENDED;
|
||||
this.currentSession.questions[questionIndex].endTime = new Date();
|
||||
|
||||
// 提交题目状态更新
|
||||
const request: EndQuestionRequest = {
|
||||
questionId: questionId,
|
||||
classCode: this.currentClassCode
|
||||
};
|
||||
|
||||
await this.postRequest('endQuestion', request);
|
||||
|
||||
// 通知题目结束
|
||||
this.notifyEvent(WebSocketEventType.END_QUESTION, this.currentSession.questions[questionIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logManager.error(LogCategory.CLASS, `Error checking question status: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 连接服务
|
||||
public async connect(): Promise<boolean> {
|
||||
if (this.connected) return true;
|
||||
|
||||
try {
|
||||
// 验证服务器连接
|
||||
const response = await this.getRequest('ping');
|
||||
|
||||
if (response.success) {
|
||||
this.connected = true;
|
||||
const connectionEvent: EventConnection = { status: 'connected' };
|
||||
this.notifyEvent('connection', connectionEvent);
|
||||
logManager.info(LogCategory.NETWORK, LogEventType.SYSTEM_INFO, 'Channel service connected');
|
||||
return true;
|
||||
} else {
|
||||
logManager.error(LogCategory.NETWORK, `Connection failed: ${response.message}`);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
logManager.error(LogCategory.NETWORK, `Connection error: ${error}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 断开连接
|
||||
public disconnect(): void {
|
||||
if (!this.connected) return;
|
||||
|
||||
// 停止轮询
|
||||
this.stopPolling();
|
||||
|
||||
this.connected = false;
|
||||
this.notifyEvent('connection', { status: 'disconnected' });
|
||||
logManager.info(LogCategory.NETWORK, LogEventType.SYSTEM_INFO, 'Channel service disconnected');
|
||||
|
||||
// 清空当前会话
|
||||
this.currentSession = null;
|
||||
this.currentClassCode = '';
|
||||
}
|
||||
|
||||
// 创建新课堂(教师)
|
||||
public async createClassSession(className: string, classCode: string): Promise<ClassSessionModel | null> {
|
||||
try {
|
||||
// 发送创建请求
|
||||
const reqData: CreateSessionRequest = {
|
||||
className: className,
|
||||
classCode: classCode,
|
||||
teacherAccount: settingsService.getCurrentAccount(),
|
||||
teacherName: settingsService.getUserNickname()
|
||||
};
|
||||
|
||||
const response = await this.postRequest('createSession', reqData);
|
||||
|
||||
if (response.success && response.data) {
|
||||
// 设置当前会话
|
||||
this.currentSession = response.data as ClassSessionModel;
|
||||
this.currentClassCode = classCode;
|
||||
this.isTeacher = true;
|
||||
this.connected = true;
|
||||
|
||||
// 开始消息轮询
|
||||
this.startPolling();
|
||||
|
||||
// 通知连接状态
|
||||
const connectionEvent: EventConnection = { status: 'connected' };
|
||||
this.notifyEvent(WebSocketEventType.JOIN_CLASS, connectionEvent);
|
||||
|
||||
return this.currentSession;
|
||||
} else {
|
||||
logManager.error(LogCategory.CLASS, `Create session failed: ${response.message}`);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
logManager.error(LogCategory.CLASS, `Create session error: ${error}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 加入课堂(学生)
|
||||
public async joinClassSession(classCode: string): Promise<boolean> {
|
||||
try {
|
||||
// 发送加入请求
|
||||
const reqData: JoinSessionRequest = {
|
||||
classCode: classCode,
|
||||
studentAccount: settingsService.getCurrentAccount(),
|
||||
studentName: settingsService.getUserNickname()
|
||||
};
|
||||
|
||||
const response = await this.postRequest('joinSession', reqData);
|
||||
|
||||
if (response.success && response.data) {
|
||||
// 设置当前会话
|
||||
this.currentSession = response.data as ClassSessionModel;
|
||||
this.currentClassCode = classCode;
|
||||
this.isTeacher = false;
|
||||
this.connected = true;
|
||||
|
||||
// 开始消息轮询
|
||||
this.startPolling();
|
||||
|
||||
// 通知连接状态
|
||||
const connectionEvent: EventConnection = { status: 'connected' };
|
||||
this.notifyEvent(WebSocketEventType.JOIN_CLASS, connectionEvent);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
logManager.error(LogCategory.CLASS, `Join session failed: ${response.message}`);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
logManager.error(LogCategory.CLASS, `Join session error: ${error}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
public async sendMessage(content: string): Promise<boolean> {
|
||||
if (!this.currentSession || !this.connected || !this.currentClassCode) {
|
||||
logManager.error(LogCategory.CLASS, 'Cannot send message: No active session or not connected');
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentAccount = settingsService.getCurrentAccount();
|
||||
const senderName = this.dbService.getUserNickname(currentAccount) || currentAccount;
|
||||
const role = this.isTeacher ? SenderRole.TEACHER : SenderRole.STUDENT;
|
||||
|
||||
// 创建消息
|
||||
const message = new MessageModel(currentAccount, senderName, role, content);
|
||||
|
||||
try {
|
||||
// 发送消息请求
|
||||
const request: SendMessageRequest = {
|
||||
classCode: this.currentClassCode,
|
||||
message: message
|
||||
};
|
||||
|
||||
const response = await this.postRequest('sendMessage', request);
|
||||
|
||||
if (response.success) {
|
||||
// 添加到当前会话
|
||||
this.currentSession.messages.push(message);
|
||||
|
||||
// 通知消息发送
|
||||
this.notifyEvent(WebSocketEventType.SEND_MESSAGE, message);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
logManager.error(LogCategory.CLASS, `Send message failed: ${response.message}`);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
logManager.error(LogCategory.CLASS, `Send message error: ${error}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 发布题目(教师)
|
||||
public async publishQuestion(title: string, options: QuestionOption[], correctOption: number, duration: number): Promise<QuestionModel | null> {
|
||||
if (!this.isTeacher || !this.currentSession || !this.connected || !this.currentClassCode) {
|
||||
logManager.error(LogCategory.CLASS, 'Cannot publish question: Not a teacher, no active session or not connected');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 创建题目
|
||||
const question = new QuestionModel(title, options, correctOption, duration, this.currentClassCode);
|
||||
|
||||
try {
|
||||
// 发送发布题目请求
|
||||
const response = await this.postRequest('publishQuestion', question);
|
||||
|
||||
if (response.success && response.data) {
|
||||
// 使用服务器返回的题目
|
||||
const publishedQuestion = response.data as QuestionModel;
|
||||
|
||||
// 添加到当前会话
|
||||
this.currentSession.questions.push(publishedQuestion);
|
||||
|
||||
// 设置题目结束倒计时
|
||||
setTimeout(() => {
|
||||
this.checkQuestionStatus(publishedQuestion.questionId);
|
||||
}, duration * 1000);
|
||||
|
||||
// 通知题目发布
|
||||
this.notifyEvent(WebSocketEventType.PUBLISH_QUESTION, publishedQuestion);
|
||||
|
||||
return publishedQuestion;
|
||||
} else {
|
||||
logManager.error(LogCategory.CLASS, `Publish question failed: ${response.message}`);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
logManager.error(LogCategory.CLASS, `Publish question error: ${error}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 提交答案(学生)
|
||||
public async submitAnswer(questionId: string, selectedOption: number): Promise<boolean> {
|
||||
if (this.isTeacher || !this.currentSession || !this.connected || !this.currentClassCode) {
|
||||
logManager.error(LogCategory.CLASS, 'Cannot submit answer: Is a teacher, no active session or not connected');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找题目
|
||||
const question = this.currentSession.questions.find(q => q.questionId === questionId);
|
||||
if (!question || question.status === QuestionStatus.ENDED) {
|
||||
logManager.error(LogCategory.CLASS, 'Cannot submit answer: Question not found or already ended');
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentAccount = settingsService.getCurrentAccount();
|
||||
const studentName = this.dbService.getUserNickname(currentAccount) || currentAccount;
|
||||
|
||||
// 创建答案
|
||||
const answer = new QuestionAnswer(
|
||||
currentAccount,
|
||||
studentName,
|
||||
selectedOption,
|
||||
question.correctOption
|
||||
);
|
||||
|
||||
try {
|
||||
// 发送提交答案请求
|
||||
const request: SubmitAnswerRequest = {
|
||||
questionId: questionId,
|
||||
classCode: this.currentClassCode,
|
||||
answer: answer
|
||||
};
|
||||
|
||||
const response = await this.postRequest('submitAnswer', request);
|
||||
|
||||
if (response.success) {
|
||||
// 检查是否已答题
|
||||
const existingAnswerIndex = question.answers.findIndex(a => a.studentAccount === currentAccount);
|
||||
if (existingAnswerIndex !== -1) {
|
||||
// 更新已有答案
|
||||
question.answers[existingAnswerIndex] = answer;
|
||||
} else {
|
||||
// 添加新答案
|
||||
question.answers.push(answer);
|
||||
}
|
||||
|
||||
// 通知答案提交
|
||||
const answerEvent: QuestionAnswerEvent = {
|
||||
questionId: questionId,
|
||||
answer: answer
|
||||
};
|
||||
|
||||
this.notifyEvent(WebSocketEventType.SUBMIT_ANSWER, answerEvent);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
logManager.error(LogCategory.CLASS, `Submit answer failed: ${response.message}`);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
logManager.error(LogCategory.CLASS, `Submit answer error: ${error}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 结束课堂(教师)
|
||||
public async endClassSession(): Promise<boolean> {
|
||||
if (!this.isTeacher || !this.currentSession || !this.connected || !this.currentClassCode) {
|
||||
logManager.error(LogCategory.CLASS, 'Cannot end class: Not a teacher, no active session or not connected');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 发送结束课堂请求
|
||||
const request: EndSessionRequest = {
|
||||
classCode: this.currentClassCode
|
||||
};
|
||||
|
||||
const response = await this.postRequest('endSession', request);
|
||||
|
||||
if (response.success) {
|
||||
// 更新课堂状态
|
||||
this.currentSession.status = ClassSessionStatus.ENDED;
|
||||
this.currentSession.endTime = new Date();
|
||||
|
||||
// 通知课堂结束
|
||||
this.notifyEvent(WebSocketEventType.END_CLASS, this.currentSession);
|
||||
|
||||
// 停止轮询
|
||||
this.stopPolling();
|
||||
|
||||
// 清空当前会话
|
||||
const endedSession = this.currentSession;
|
||||
this.currentSession = null;
|
||||
this.currentClassCode = '';
|
||||
|
||||
return true;
|
||||
} else {
|
||||
logManager.error(LogCategory.CLASS, `End class failed: ${response.message}`);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
logManager.error(LogCategory.CLASS, `End class error: ${error}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前课堂
|
||||
public getCurrentSession(): ClassSessionModel | null {
|
||||
return this.currentSession;
|
||||
}
|
||||
|
||||
// 获取当前题目(如果有)
|
||||
public getCurrentQuestion(): QuestionModel | null {
|
||||
if (!this.currentSession) return null;
|
||||
|
||||
// 查找最新的活动题目
|
||||
const activeQuestions = this.currentSession.questions.filter(q => q.status === QuestionStatus.ACTIVE);
|
||||
if (activeQuestions.length === 0) return null;
|
||||
|
||||
// 返回最新发布的题目
|
||||
return activeQuestions[activeQuestions.length - 1];
|
||||
}
|
||||
|
||||
// 注册事件回调
|
||||
public addEventListener(event: string, callback: EventCallback): void {
|
||||
if (!this.eventCallbacks.has(event)) {
|
||||
this.eventCallbacks.set(event, []);
|
||||
}
|
||||
|
||||
const callbacks = this.eventCallbacks.get(event);
|
||||
if (callbacks) {
|
||||
callbacks.push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
// 移除事件回调
|
||||
public removeEventListener(event: string, callback: EventCallback): void {
|
||||
if (!this.eventCallbacks.has(event)) return;
|
||||
|
||||
const callbacks = this.eventCallbacks.get(event);
|
||||
if (callbacks) {
|
||||
const index = callbacks.indexOf(callback);
|
||||
if (index !== -1) {
|
||||
callbacks.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 通知事件
|
||||
private notifyEvent(event: string, data: EventData): void {
|
||||
if (!this.eventCallbacks.has(event)) return;
|
||||
|
||||
const callbacks = this.eventCallbacks.get(event);
|
||||
if (callbacks) {
|
||||
callbacks.forEach(callback => {
|
||||
try {
|
||||
callback(data);
|
||||
} catch (error) {
|
||||
logManager.error(LogCategory.ERROR, `Error in event callback: ${error}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
export default ClassRoomService.getInstance();
|
@ -256,6 +256,11 @@ export class SettingsService {
|
||||
this.refreshUserInfo();
|
||||
}
|
||||
|
||||
// 获取当前用户账号
|
||||
public getCurrentAccount(): string {
|
||||
return this.currentAccount;
|
||||
}
|
||||
|
||||
// 刷新用户信息
|
||||
private refreshUserInfo(): void {
|
||||
if (this.currentAccount) {
|
||||
|
@ -155,7 +155,8 @@ export class LogManager {
|
||||
new VersionLogItem("1.0.0", "2025-2-10", ["初始版本发布", "实现基本功能界面", "添加用户信息管理", "支持主题色切换", "支持中英文切换"]),
|
||||
new VersionLogItem("1.1.0", "2025-3-31", ["添加用户信息编辑功能", "增加版本日志功能", "改进主题颜色切换效果","添加公告栏","修复已知问题"]),
|
||||
new VersionLogItem("1.1.5","2025-4-1",["增加远程测试数据库","优化登录页面","优化用户信息设置","增加退出按钮"]),
|
||||
new VersionLogItem("1.1.7","2025-4-1",["添加测试按钮","修复已知问题"])
|
||||
new VersionLogItem("1.1.7","2025-4-1",["添加测试按钮","修复已知问题"]),
|
||||
new VersionLogItem("1.2.0","2025-4-2",["添加课堂功能","修改上课页面逻辑"])
|
||||
];
|
||||
}
|
||||
|
||||
|
885
entry/src/main/ets/pages/ClassLivePage.ets
Normal file
885
entry/src/main/ets/pages/ClassLivePage.ets
Normal file
@ -0,0 +1,885 @@
|
||||
import { router } from '@kit.ArkUI';
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import settingsService, { SettingsModel, TextResources } from '../common/SettingsService';
|
||||
import logManager, { LogCategory, LogEventType } from '../common/logtext';
|
||||
import classRoomService, {
|
||||
ClassSessionModel,
|
||||
MessageModel,
|
||||
QuestionModel,
|
||||
QuestionOption,
|
||||
QuestionAnswer,
|
||||
SenderRole,
|
||||
WebSocketEventType,
|
||||
QuestionStatus,
|
||||
EventData
|
||||
} from '../common/ClassRoomService';
|
||||
|
||||
// 路由参数接口
|
||||
interface RouteParams {
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
// 课堂界面模式
|
||||
enum ClassLiveMode {
|
||||
TEACHER = 'teacher',
|
||||
STUDENT = 'student'
|
||||
}
|
||||
|
||||
// 对话框按钮定义
|
||||
interface DialogButton {
|
||||
value: string;
|
||||
action: () => void;
|
||||
}
|
||||
|
||||
// 对话框配置
|
||||
interface AlertDialogConfig {
|
||||
title: string;
|
||||
message: string;
|
||||
primaryButton: DialogButton;
|
||||
secondaryButton?: DialogButton;
|
||||
}
|
||||
|
||||
// 选项统计结果接口
|
||||
interface OptionStats {
|
||||
count: number;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
// 正确率统计结果接口
|
||||
interface CorrectRateStats {
|
||||
correctCount: number;
|
||||
totalCount: number;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct ClassLivePage {
|
||||
// 从路由参数获取模式
|
||||
@State mode: ClassLiveMode = ClassLiveMode.STUDENT;
|
||||
|
||||
// 课堂会话
|
||||
@State classSession: ClassSessionModel | null = null;
|
||||
|
||||
// 消息列表
|
||||
@State messages: MessageModel[] = [];
|
||||
|
||||
// 当前消息
|
||||
@State messageText: string = '';
|
||||
|
||||
// 当前题目
|
||||
@State currentQuestion: QuestionModel | null = null;
|
||||
|
||||
// 学生已选答案
|
||||
@State selectedOption: number = -1;
|
||||
|
||||
// 是否显示题目统计
|
||||
@State showQuestionStats: boolean = false;
|
||||
|
||||
// 是否显示题目编辑器
|
||||
@State showQuestionEditor: boolean = false;
|
||||
|
||||
// 是否显示答题窗口
|
||||
@State showAnswerDialog: boolean = false;
|
||||
|
||||
// 题目编辑相关状态
|
||||
@State questionTitle: string = '';
|
||||
@State questionOptions: QuestionOption[] = [];
|
||||
@State correctOption: number = 0;
|
||||
@State questionDuration: number = 30;
|
||||
|
||||
// 错误信息
|
||||
@State errorMessage: string = '';
|
||||
|
||||
// 是否加载中
|
||||
@State isLoading: boolean = false;
|
||||
|
||||
// 倒计时
|
||||
@State countdownSeconds: number = 0;
|
||||
private countdownTimer: number = -1;
|
||||
|
||||
// 从设置服务获取设置
|
||||
@State settings: SettingsModel = settingsService.getSettings();
|
||||
|
||||
// 获取文本资源
|
||||
@State texts: TextResources = settingsService.getTextResources();
|
||||
|
||||
// 事件回调函数引用
|
||||
private messageCallback: (data: EventData) => void = () => {};
|
||||
private questionCallback: (data: EventData) => void = () => {};
|
||||
private endQuestionCallback: (data: EventData) => void = () => {};
|
||||
private endClassCallback: (data: EventData) => void = () => {};
|
||||
|
||||
aboutToAppear() {
|
||||
// 注册语言变化的回调
|
||||
settingsService.registerLanguageChangeCallback(() => {
|
||||
this.texts = settingsService.getTextResources();
|
||||
});
|
||||
|
||||
// 注册主题颜色变化的回调
|
||||
settingsService.registerColorChangeCallback(() => {
|
||||
this.settings = settingsService.getSettings();
|
||||
});
|
||||
|
||||
// 获取路由参数中的模式
|
||||
const params = router.getParams() as RouteParams;
|
||||
if (params && params.mode) {
|
||||
this.mode = params.mode as ClassLiveMode;
|
||||
}
|
||||
|
||||
// 获取当前课堂会话
|
||||
this.classSession = classRoomService.getCurrentSession();
|
||||
|
||||
if (!this.classSession) {
|
||||
// 如果没有课堂会话,返回上课页面
|
||||
this.showErrorAndReturn('未找到课堂会话,请重新加入或创建课堂');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前消息列表
|
||||
this.messages = this.classSession.messages || [];
|
||||
|
||||
// 获取当前活动题目
|
||||
this.currentQuestion = classRoomService.getCurrentQuestion();
|
||||
|
||||
// 注册事件监听
|
||||
this.registerEventListeners();
|
||||
|
||||
logManager.info(LogCategory.CLASS, LogEventType.PAGE_APPEAR, 'ClassLivePage');
|
||||
}
|
||||
|
||||
// 注册事件监听
|
||||
private registerEventListeners() {
|
||||
// 接收新消息
|
||||
this.messageCallback = (data: EventData) => {
|
||||
const message = data as MessageModel;
|
||||
if (message && message.id) {
|
||||
this.messages.push(message);
|
||||
}
|
||||
};
|
||||
classRoomService.addEventListener(WebSocketEventType.SEND_MESSAGE, this.messageCallback);
|
||||
|
||||
// 题目发布
|
||||
this.questionCallback = (data: EventData) => {
|
||||
const question = data as QuestionModel;
|
||||
if (question && question.questionId) {
|
||||
this.currentQuestion = question;
|
||||
|
||||
if (this.mode === ClassLiveMode.STUDENT) {
|
||||
// 学生模式,显示答题窗口
|
||||
this.selectedOption = -1;
|
||||
this.showAnswerDialog = true;
|
||||
}
|
||||
|
||||
// 设置倒计时
|
||||
this.startCountdown(question.duration);
|
||||
}
|
||||
};
|
||||
classRoomService.addEventListener(WebSocketEventType.PUBLISH_QUESTION, this.questionCallback);
|
||||
|
||||
// 题目结束
|
||||
this.endQuestionCallback = (data: EventData) => {
|
||||
const question = data as QuestionModel;
|
||||
if (question && question.questionId) {
|
||||
// 更新题目状态
|
||||
this.currentQuestion = question;
|
||||
|
||||
// 停止倒计时
|
||||
this.stopCountdown();
|
||||
|
||||
// 显示题目统计
|
||||
this.showAnswerDialog = false;
|
||||
this.showQuestionStats = true;
|
||||
}
|
||||
};
|
||||
classRoomService.addEventListener(WebSocketEventType.END_QUESTION, this.endQuestionCallback);
|
||||
|
||||
// 课堂结束
|
||||
this.endClassCallback = (data: EventData) => {
|
||||
const session = data as ClassSessionModel;
|
||||
if (session && session.sessionId) {
|
||||
// 显示课堂结束提示
|
||||
const primaryButton: DialogButton = {
|
||||
value: '确定',
|
||||
action: () => {
|
||||
// 返回上课页面 - 使用明确的导航而不是back()
|
||||
router.pushUrl({
|
||||
url: 'pages/ClassPage'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const alertConfig: AlertDialogConfig = {
|
||||
title: '课堂已结束',
|
||||
message: '教师已结束当前课堂',
|
||||
primaryButton: primaryButton
|
||||
};
|
||||
AlertDialog.show(alertConfig);
|
||||
}
|
||||
};
|
||||
classRoomService.addEventListener(WebSocketEventType.END_CLASS, this.endClassCallback);
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
private async sendMessage() {
|
||||
if (!this.messageText || this.messageText.trim() === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await classRoomService.sendMessage(this.messageText);
|
||||
|
||||
if (result) {
|
||||
// 清空消息
|
||||
this.messageText = '';
|
||||
}
|
||||
} catch (error) {
|
||||
// 处理错误
|
||||
logManager.error(LogCategory.CLASS, `Send message error: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 开始倒计时
|
||||
private startCountdown(seconds: number) {
|
||||
// 停止之前的计时器
|
||||
this.stopCountdown();
|
||||
|
||||
// 设置初始倒计时
|
||||
this.countdownSeconds = seconds;
|
||||
|
||||
// 创建新计时器
|
||||
this.countdownTimer = setInterval(() => {
|
||||
if (this.countdownSeconds > 0) {
|
||||
this.countdownSeconds--;
|
||||
} else {
|
||||
// 倒计时结束
|
||||
this.stopCountdown();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// 停止倒计时
|
||||
private stopCountdown() {
|
||||
if (this.countdownTimer !== -1) {
|
||||
clearInterval(this.countdownTimer);
|
||||
this.countdownTimer = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 发布题目
|
||||
private async publishQuestion() {
|
||||
if (!this.questionTitle || this.questionTitle.trim() === '') {
|
||||
this.errorMessage = '请输入题目内容';
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.questionOptions.length < 2) {
|
||||
this.errorMessage = '请至少添加两个选项';
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
try {
|
||||
const question = await classRoomService.publishQuestion(
|
||||
this.questionTitle,
|
||||
this.questionOptions,
|
||||
this.correctOption,
|
||||
this.questionDuration
|
||||
);
|
||||
|
||||
if (question) {
|
||||
// 清空题目编辑器
|
||||
this.resetQuestionEditor();
|
||||
// 关闭题目编辑器
|
||||
this.showQuestionEditor = false;
|
||||
} else {
|
||||
this.errorMessage = '发布题目失败,请稍后再试';
|
||||
}
|
||||
} catch (error) {
|
||||
this.errorMessage = '发布题目过程中发生错误';
|
||||
logManager.error(LogCategory.CLASS, `Publish question error: ${error}`);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 提交答案
|
||||
private async submitAnswer() {
|
||||
if (this.selectedOption < 0 || !this.currentQuestion) {
|
||||
this.errorMessage = '请选择一个选项';
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
try {
|
||||
const result = await classRoomService.submitAnswer(
|
||||
this.currentQuestion.questionId,
|
||||
this.selectedOption
|
||||
);
|
||||
|
||||
if (result) {
|
||||
// 关闭答题窗口
|
||||
this.showAnswerDialog = false;
|
||||
// 清空错误消息
|
||||
this.errorMessage = '';
|
||||
} else {
|
||||
this.errorMessage = '提交答案失败,请稍后再试';
|
||||
}
|
||||
} catch (error) {
|
||||
this.errorMessage = '提交答案过程中发生错误';
|
||||
logManager.error(LogCategory.CLASS, `Submit answer error: ${error}`);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 结束课堂
|
||||
private async endClass() {
|
||||
this.isLoading = true;
|
||||
|
||||
try {
|
||||
const result = await classRoomService.endClassSession();
|
||||
|
||||
if (result) {
|
||||
// 返回上课页面 - 使用明确的导航而不是back()
|
||||
router.pushUrl({
|
||||
url: 'pages/ClassPage'
|
||||
});
|
||||
} else {
|
||||
// 显示错误提示
|
||||
const primaryButton: DialogButton = {
|
||||
value: '确定',
|
||||
action: () => {}
|
||||
};
|
||||
|
||||
const alertConfig: AlertDialogConfig = {
|
||||
title: '错误',
|
||||
message: '结束课堂失败,请稍后再试',
|
||||
primaryButton: primaryButton
|
||||
};
|
||||
AlertDialog.show(alertConfig);
|
||||
}
|
||||
} catch (error) {
|
||||
// 显示错误提示
|
||||
const primaryButton: DialogButton = {
|
||||
value: '确定',
|
||||
action: () => {}
|
||||
};
|
||||
|
||||
const alertConfig: AlertDialogConfig = {
|
||||
title: '错误',
|
||||
message: '结束课堂失败,请稍后再试',
|
||||
primaryButton: primaryButton
|
||||
};
|
||||
AlertDialog.show(alertConfig);
|
||||
logManager.error(LogCategory.CLASS, `End class error: ${error}`);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示错误并返回
|
||||
private showErrorAndReturn(message: string) {
|
||||
const primaryButton: DialogButton = {
|
||||
value: '确定',
|
||||
action: () => {
|
||||
// 使用明确的导航而不是back()
|
||||
router.pushUrl({
|
||||
url: 'pages/ClassPage'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const alertConfig: AlertDialogConfig = {
|
||||
title: '错误',
|
||||
message: message,
|
||||
primaryButton: primaryButton
|
||||
};
|
||||
AlertDialog.show(alertConfig);
|
||||
}
|
||||
|
||||
// 创建问题编辑器内的选项
|
||||
private addOption() {
|
||||
const newIndex = this.questionOptions.length;
|
||||
const newOption = new QuestionOption(newIndex, '');
|
||||
this.questionOptions.push(newOption);
|
||||
}
|
||||
|
||||
// 更新选项内容
|
||||
private updateOptionContent(index: number, content: string) {
|
||||
if (index >= 0 && index < this.questionOptions.length) {
|
||||
const options = [...this.questionOptions];
|
||||
options[index].content = content;
|
||||
this.questionOptions = options;
|
||||
}
|
||||
}
|
||||
|
||||
// 移除选项
|
||||
private removeOption(index: number) {
|
||||
if (this.questionOptions.length <= 2) {
|
||||
this.errorMessage = '至少需要两个选项';
|
||||
return;
|
||||
}
|
||||
|
||||
// 移除选项
|
||||
this.questionOptions = this.questionOptions.filter((_, i) => i !== index);
|
||||
|
||||
// 重新编号
|
||||
this.questionOptions.forEach((option, i) => {
|
||||
option.index = i;
|
||||
});
|
||||
|
||||
// 如果正确选项是被删除的选项或之后的选项,需要调整
|
||||
if (this.correctOption >= index) {
|
||||
// 如果是最后一个选项,则正确选项为前一个
|
||||
if (this.correctOption >= this.questionOptions.length) {
|
||||
this.correctOption = this.questionOptions.length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 计算选项计数和百分比
|
||||
private getOptionStats(index: number): OptionStats {
|
||||
if (!this.currentQuestion || this.currentQuestion.answers.length === 0) {
|
||||
return { count: 0, percentage: 0 };
|
||||
}
|
||||
|
||||
// 使用普通for循环来避免索引访问的问题
|
||||
let optionCount = 0;
|
||||
for (let i = 0; i < this.currentQuestion.answers.length; i++) {
|
||||
const answer = this.currentQuestion.answers[i];
|
||||
if (answer.selectedOption === index) {
|
||||
optionCount++;
|
||||
}
|
||||
}
|
||||
|
||||
const totalCount = this.currentQuestion.answers.length;
|
||||
const percentage = totalCount > 0 ? Math.round((optionCount / totalCount) * 100) : 0;
|
||||
|
||||
return { count: optionCount, percentage: percentage };
|
||||
}
|
||||
|
||||
// 计算正确率
|
||||
private getCorrectRate(): CorrectRateStats {
|
||||
if (!this.currentQuestion || this.currentQuestion.answers.length === 0) {
|
||||
return { correctCount: 0, totalCount: 0, percentage: 0 };
|
||||
}
|
||||
|
||||
// 使用普通for循环来避免索引访问的问题
|
||||
let correctCount = 0;
|
||||
for (let i = 0; i < this.currentQuestion.answers.length; i++) {
|
||||
const answer = this.currentQuestion.answers[i];
|
||||
if (answer.isCorrect) {
|
||||
correctCount++;
|
||||
}
|
||||
}
|
||||
|
||||
const totalCount = this.currentQuestion.answers.length;
|
||||
const percentage = totalCount > 0 ? Math.round((correctCount / totalCount) * 100) : 0;
|
||||
|
||||
return { correctCount, totalCount, percentage };
|
||||
}
|
||||
|
||||
// 获取学生自己的答案
|
||||
private getMyAnswer(): QuestionAnswer | null {
|
||||
if (!this.currentQuestion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const currentAccount = settingsService.getCurrentAccount();
|
||||
|
||||
// 使用for循环查找
|
||||
for (let i = 0; i < this.currentQuestion.answers.length; i++) {
|
||||
const answer = this.currentQuestion.answers[i];
|
||||
if (answer.studentAccount === currentAccount) {
|
||||
return answer;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 重置题目编辑器
|
||||
private resetQuestionEditor() {
|
||||
this.questionTitle = '';
|
||||
this.questionOptions = [];
|
||||
this.correctOption = 0;
|
||||
this.questionDuration = 30;
|
||||
this.errorMessage = '';
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// 顶部导航栏
|
||||
Row() {
|
||||
Row() {
|
||||
Image($r('app.media.back'))
|
||||
.width(24)
|
||||
.height(24)
|
||||
.fillColor(Color.White)
|
||||
.margin({ right: 16 })
|
||||
.onClick(() => {
|
||||
// 返回确认
|
||||
if (this.mode === ClassLiveMode.TEACHER) {
|
||||
const primaryButton: DialogButton = {
|
||||
value: '确定',
|
||||
action: () => {
|
||||
this.endClass();
|
||||
}
|
||||
};
|
||||
|
||||
const secondaryButton: DialogButton = {
|
||||
value: '取消',
|
||||
action: () => {}
|
||||
};
|
||||
|
||||
const alertConfig: AlertDialogConfig = {
|
||||
title: '离开课堂',
|
||||
message: '确定要离开当前课堂吗?这将结束整个课堂。',
|
||||
primaryButton: primaryButton,
|
||||
secondaryButton: secondaryButton
|
||||
};
|
||||
AlertDialog.show(alertConfig);
|
||||
} else {
|
||||
const primaryButton: DialogButton = {
|
||||
value: '确定',
|
||||
action: () => {
|
||||
// 使用明确的导航而不是back()
|
||||
router.pushUrl({
|
||||
url: 'pages/ClassPage'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const secondaryButton: DialogButton = {
|
||||
value: '取消',
|
||||
action: () => {}
|
||||
};
|
||||
|
||||
const alertConfig: AlertDialogConfig = {
|
||||
title: '离开课堂',
|
||||
message: '确定要离开当前课堂吗?',
|
||||
primaryButton: primaryButton,
|
||||
secondaryButton: secondaryButton
|
||||
};
|
||||
AlertDialog.show(alertConfig);
|
||||
}
|
||||
})
|
||||
|
||||
Text(this.classSession?.className || '在线课堂')
|
||||
.fontSize(20)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.fontColor(Color.White)
|
||||
}
|
||||
|
||||
// 教师端显示结束课堂按钮
|
||||
if (this.mode === ClassLiveMode.TEACHER) {
|
||||
Button("结束课堂")
|
||||
.fontSize(14)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.backgroundColor('rgba(255,255,255,0.2)')
|
||||
.borderRadius(16)
|
||||
.fontColor(Color.White)
|
||||
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
|
||||
.onClick(() => {
|
||||
this.endClass();
|
||||
})
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(this.settings.themeColor)
|
||||
.height(60)
|
||||
.justifyContent(FlexAlign.SpaceBetween)
|
||||
.padding({ left: 16, right: 16 })
|
||||
|
||||
// 课堂内容区域,包含聊天功能
|
||||
Column() {
|
||||
// 课堂信息区域
|
||||
Column() {
|
||||
Text(`课堂名称: ${this.classSession?.className || '-'}`)
|
||||
.fontSize(16)
|
||||
.margin({ bottom: 5 })
|
||||
|
||||
Text(`授课教师: ${this.classSession?.teacherName || '-'}`)
|
||||
.fontSize(16)
|
||||
.margin({ bottom: 5 })
|
||||
|
||||
Text(`角色: ${this.mode === ClassLiveMode.TEACHER ? '教师' : '学生'}`)
|
||||
.fontSize(16)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(10)
|
||||
.backgroundColor('#f5f5f5')
|
||||
.borderRadius(8)
|
||||
.margin({ top: 10, bottom: 10 })
|
||||
|
||||
// 通知消息
|
||||
if (this.currentQuestion) {
|
||||
Column() {
|
||||
Text("当前有一个正在进行的题目")
|
||||
.fontSize(16)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.margin({ bottom: 10 })
|
||||
|
||||
Text(`题目: ${this.currentQuestion.title}`)
|
||||
.fontSize(16)
|
||||
.margin({ bottom: 10 })
|
||||
|
||||
if (this.countdownSeconds > 0) {
|
||||
Text(`剩余时间: ${this.countdownSeconds}秒`)
|
||||
.fontSize(16)
|
||||
.fontColor(this.countdownSeconds > 10 ? '#333' : '#F56C6C')
|
||||
.margin({ bottom: 10 })
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding(16)
|
||||
.backgroundColor('#f0f0f0')
|
||||
.borderRadius(8)
|
||||
.margin({ bottom: 10 })
|
||||
}
|
||||
|
||||
// 聊天历史记录区域
|
||||
Column() {
|
||||
Text("聊天记录")
|
||||
.fontSize(18)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.margin({ bottom: 10 })
|
||||
|
||||
List({ space: 8 }) {
|
||||
ForEach(this.messages, (message: MessageModel) => {
|
||||
ListItem() {
|
||||
Row() {
|
||||
// 消息气泡
|
||||
Column() {
|
||||
// 发送者信息
|
||||
Row() {
|
||||
Text(message.senderName)
|
||||
.fontSize(14)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
|
||||
Text(message.senderRole === SenderRole.TEACHER ? ' (教师)' : ' (学生)')
|
||||
.fontSize(12)
|
||||
.fontColor('#666')
|
||||
}
|
||||
.width('100%')
|
||||
.margin({ bottom: 4 })
|
||||
|
||||
// 消息内容
|
||||
Text(message.content)
|
||||
.fontSize(16)
|
||||
.margin({ top: 4 })
|
||||
|
||||
// 时间
|
||||
Text(this.formatTime(message.timestamp))
|
||||
.fontSize(12)
|
||||
.fontColor('#999')
|
||||
.margin({ top: 4 })
|
||||
.alignSelf(ItemAlign.End)
|
||||
}
|
||||
.padding(12)
|
||||
.backgroundColor(message.senderRole === SenderRole.TEACHER ? '#e1f3d8' : '#edf2fc')
|
||||
.borderRadius(8)
|
||||
.alignSelf(ItemAlign.Start)
|
||||
.width('85%')
|
||||
}
|
||||
.width('100%')
|
||||
.justifyContent(message.senderId === settingsService.getCurrentAccount() ? FlexAlign.End : FlexAlign.Start)
|
||||
.margin({ top: 4, bottom: 4 })
|
||||
}
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
.padding(10)
|
||||
.backgroundColor('#f9f9f9')
|
||||
.borderRadius(8)
|
||||
.scrollBar(BarState.Auto)
|
||||
}
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
.margin({ bottom: 10 })
|
||||
|
||||
// 消息输入区域
|
||||
Row() {
|
||||
TextInput({ placeholder: '输入消息...', text: this.messageText })
|
||||
.layoutWeight(1)
|
||||
.height(44)
|
||||
.borderRadius(22)
|
||||
.backgroundColor('#f5f5f5')
|
||||
.padding({ left: 20, right: 20 })
|
||||
.onChange((value: string) => {
|
||||
this.messageText = value;
|
||||
})
|
||||
|
||||
Button("发送")
|
||||
.height(44)
|
||||
.width(80)
|
||||
.margin({ left: 10 })
|
||||
.borderRadius(22)
|
||||
.backgroundColor(this.settings.themeColor)
|
||||
.onClick(() => {
|
||||
this.sendMessage();
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.margin({ top: 10 })
|
||||
|
||||
// 功能按钮区域
|
||||
Row({ space: 20 }) {
|
||||
if (this.mode === ClassLiveMode.TEACHER && !this.showQuestionEditor) {
|
||||
Button("发布题目")
|
||||
.width(150)
|
||||
.height(44)
|
||||
.fontSize(16)
|
||||
.backgroundColor(this.settings.themeColor)
|
||||
.borderRadius(22)
|
||||
.fontColor(Color.White)
|
||||
.onClick(() => {
|
||||
this.showQuestionEditor = true;
|
||||
|
||||
// 初始化题目选项
|
||||
if (this.questionOptions.length === 0) {
|
||||
this.addOption();
|
||||
this.addOption();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (this.mode === ClassLiveMode.STUDENT && this.currentQuestion && !this.showAnswerDialog) {
|
||||
Button("查看题目")
|
||||
.width(150)
|
||||
.height(44)
|
||||
.fontSize(16)
|
||||
.backgroundColor(this.settings.themeColor)
|
||||
.borderRadius(22)
|
||||
.fontColor(Color.White)
|
||||
.onClick(() => {
|
||||
this.showAnswerDialog = true;
|
||||
})
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.margin({ top: 10 })
|
||||
|
||||
// 显示错误信息
|
||||
if (this.errorMessage) {
|
||||
Text(this.errorMessage)
|
||||
.fontSize(14)
|
||||
.fontColor('#F56C6C')
|
||||
.width('100%')
|
||||
.textAlign(TextAlign.Center)
|
||||
.margin({ top: 10 })
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
if (this.isLoading) {
|
||||
Row() {
|
||||
LoadingProgress()
|
||||
.width(24)
|
||||
.height(24)
|
||||
.margin({ right: 10 })
|
||||
|
||||
Text("处理中...")
|
||||
.fontSize(14)
|
||||
.fontColor('#666')
|
||||
}
|
||||
.margin({ top: 10 })
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.padding(16)
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
private formatTime(timestamp: Date): string {
|
||||
const date = new Date(timestamp);
|
||||
const hours = date.getHours().toString().padStart(2, '0');
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
aboutToDisappear() {
|
||||
// 移除事件监听
|
||||
if (this.messageCallback) {
|
||||
classRoomService.removeEventListener(WebSocketEventType.SEND_MESSAGE, this.messageCallback);
|
||||
}
|
||||
|
||||
if (this.questionCallback) {
|
||||
classRoomService.removeEventListener(WebSocketEventType.PUBLISH_QUESTION, this.questionCallback);
|
||||
}
|
||||
|
||||
if (this.endQuestionCallback) {
|
||||
classRoomService.removeEventListener(WebSocketEventType.END_QUESTION, this.endQuestionCallback);
|
||||
}
|
||||
|
||||
if (this.endClassCallback) {
|
||||
classRoomService.removeEventListener(WebSocketEventType.END_CLASS, this.endClassCallback);
|
||||
}
|
||||
|
||||
// 停止倒计时
|
||||
this.stopCountdown();
|
||||
|
||||
logManager.info(LogCategory.CLASS, LogEventType.PAGE_DISAPPEAR, 'ClassLivePage');
|
||||
}
|
||||
|
||||
onBackPress() {
|
||||
logManager.info(LogCategory.CLASS, LogEventType.PAGE_BACK, 'ClassLivePage');
|
||||
|
||||
// 拦截返回键,显示确认对话框
|
||||
if (this.mode === ClassLiveMode.TEACHER) {
|
||||
const primaryButton: DialogButton = {
|
||||
value: '确定',
|
||||
action: () => {
|
||||
this.endClass();
|
||||
}
|
||||
};
|
||||
|
||||
const secondaryButton: DialogButton = {
|
||||
value: '取消',
|
||||
action: () => {}
|
||||
};
|
||||
|
||||
const alertConfig: AlertDialogConfig = {
|
||||
title: '离开课堂',
|
||||
message: '确定要离开当前课堂吗?这将结束整个课堂。',
|
||||
primaryButton: primaryButton,
|
||||
secondaryButton: secondaryButton
|
||||
};
|
||||
AlertDialog.show(alertConfig);
|
||||
} else {
|
||||
const primaryButton: DialogButton = {
|
||||
value: '确定',
|
||||
action: () => {
|
||||
// 使用明确的导航而不是back()
|
||||
router.pushUrl({
|
||||
url: 'pages/ClassPage'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const secondaryButton: DialogButton = {
|
||||
value: '取消',
|
||||
action: () => {}
|
||||
};
|
||||
|
||||
const alertConfig: AlertDialogConfig = {
|
||||
title: '离开课堂',
|
||||
message: '确定要离开当前课堂吗?',
|
||||
primaryButton: primaryButton,
|
||||
secondaryButton: secondaryButton
|
||||
};
|
||||
AlertDialog.show(alertConfig);
|
||||
}
|
||||
|
||||
// 返回true表示已处理返回事件
|
||||
return true;
|
||||
}
|
||||
}
|
@ -3,6 +3,15 @@ import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { MonitorDataType } from './HomePage';
|
||||
import settingsService, { SettingsModel, TextResources } from '../common/SettingsService';
|
||||
import logManager, { LogCategory, LogEventType } from '../common/logtext';
|
||||
import { DatabaseService } from '../common/DatabaseService';
|
||||
import classRoomService, { ClassSessionModel, QuestionOption } from '../common/ClassRoomService';
|
||||
|
||||
// 用户角色类型
|
||||
enum UserRole {
|
||||
STUDENT = 'student',
|
||||
TEACHER = 'teacher',
|
||||
UNKNOWN = 'unknown'
|
||||
}
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
@ -13,7 +22,19 @@ struct ClassPage {
|
||||
time: '',
|
||||
content: '',
|
||||
status: false
|
||||
}; // Initialize with empty values
|
||||
};
|
||||
|
||||
// 新增状态
|
||||
@State userRole: UserRole = UserRole.UNKNOWN;
|
||||
@State classCode: string = '';
|
||||
@State className: string = '';
|
||||
@State showCreateClassDialog: boolean = false;
|
||||
@State showJoinClassDialog: boolean = false;
|
||||
@State errorMessage: string = '';
|
||||
@State isLoading: boolean = false;
|
||||
|
||||
// 数据库服务
|
||||
private dbService: DatabaseService = DatabaseService.getInstance();
|
||||
|
||||
// 从设置服务获取设置
|
||||
@State settings: SettingsModel = settingsService.getSettings();
|
||||
@ -31,60 +52,658 @@ struct ClassPage {
|
||||
this.settings = settingsService.getSettings();
|
||||
});
|
||||
|
||||
// 检查用户角色
|
||||
const currentAccount = settingsService.getCurrentAccount();
|
||||
const category = this.dbService.getUserCategory(currentAccount);
|
||||
|
||||
if (category === 'student') {
|
||||
this.userRole = UserRole.STUDENT;
|
||||
} else if (category === 'teacher') {
|
||||
this.userRole = UserRole.TEACHER;
|
||||
} else {
|
||||
this.userRole = UserRole.UNKNOWN;
|
||||
}
|
||||
|
||||
// 同步设置到课堂服务
|
||||
classRoomService.checkIsTeacher();
|
||||
|
||||
this.Log_Event('aboutToAppear');
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
// 顶部导航栏
|
||||
Row() {
|
||||
Text(this.texts.classPageTitle).fontSize(22).fontColor(Color.White).fontWeight(FontWeight.Bold)
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(this.settings.themeColor)
|
||||
.height(60)
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.padding({ left: 20 })
|
||||
|
||||
// 页面内容区
|
||||
Scroll() {
|
||||
Column() {
|
||||
// 当前课程信息
|
||||
Column() {
|
||||
Text(this.currentClassInfo.name).fontSize(24).fontWeight(FontWeight.Bold).margin({ bottom: 10 })
|
||||
Text(`${this.texts.teacher}: ${this.currentClassInfo.teacher}`).fontSize(16).fontColor('#666').margin({ bottom: 8 })
|
||||
Text(`${this.texts.classroom}: ${this.currentClassInfo.room}`).fontSize(16).fontColor('#666')
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(Color.White)
|
||||
.borderRadius(12)
|
||||
.padding(20)
|
||||
.shadow({ radius: 6, color: '#eeeeee' })
|
||||
.margin({ top: 15, bottom: 15 })
|
||||
|
||||
// 上课数据卡片
|
||||
Card({
|
||||
title: this.texts.classData,
|
||||
data: this.classData,
|
||||
themeColor: this.settings.themeColor
|
||||
})
|
||||
Stack({ alignContent: Alignment.TopStart }) {
|
||||
Column() {
|
||||
// 顶部导航栏
|
||||
Row() {
|
||||
Text(this.texts.classPageTitle).fontSize(22).fontColor(Color.White).fontWeight(FontWeight.Bold)
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 16, right: 16 })
|
||||
.backgroundColor(this.settings.themeColor)
|
||||
.height(60)
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.padding({ left: 20 })
|
||||
|
||||
// 页面内容区
|
||||
Scroll() {
|
||||
Column() {
|
||||
// 根据用户角色显示不同界面
|
||||
if (this.userRole === UserRole.STUDENT) {
|
||||
this.StudentView()
|
||||
} else if (this.userRole === UserRole.TEACHER) {
|
||||
this.TeacherView()
|
||||
} else {
|
||||
this.UnknownRoleView()
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 16, right: 16 })
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.scrollBar(BarState.Off)
|
||||
|
||||
// 底部导航栏
|
||||
BottomNavigation({
|
||||
activePage: 'class',
|
||||
themeColor: this.settings.themeColor,
|
||||
texts: this.texts
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
|
||||
// 创建课堂对话框
|
||||
if (this.showCreateClassDialog) {
|
||||
this.CreateClassDialog()
|
||||
}
|
||||
|
||||
// 加入课堂对话框
|
||||
if (this.showJoinClassDialog) {
|
||||
this.JoinClassDialog()
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.scrollBar(BarState.Off)
|
||||
|
||||
// 底部导航栏
|
||||
BottomNavigation({
|
||||
activePage: 'class',
|
||||
themeColor: this.settings.themeColor,
|
||||
texts: this.texts
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
}
|
||||
|
||||
// 学生视图
|
||||
@Builder
|
||||
StudentView() {
|
||||
Column() {
|
||||
// 学生上课指引卡片
|
||||
Column() {
|
||||
Text("学生上课")
|
||||
.fontSize(24)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.margin({ bottom: 16 })
|
||||
|
||||
Text("请输入老师提供的课堂暗号进入课堂")
|
||||
.fontSize(16)
|
||||
.fontColor('#666')
|
||||
.margin({ bottom: 20 })
|
||||
|
||||
// 暗号输入框
|
||||
Column({ space: 8 }) {
|
||||
Text("课堂暗号")
|
||||
.fontSize(16)
|
||||
.fontColor('#666666')
|
||||
.alignSelf(ItemAlign.Start)
|
||||
|
||||
TextInput({ placeholder: '请输入4-6位数字暗号' })
|
||||
.type(InputType.Number)
|
||||
.maxLength(6)
|
||||
.placeholderColor('#999999')
|
||||
.placeholderFont({ size: 16 })
|
||||
.width('100%')
|
||||
.height(50)
|
||||
.fontSize(16)
|
||||
.fontColor('#333333')
|
||||
.backgroundColor('#F5F5F5')
|
||||
.borderRadius(8)
|
||||
.padding({ left: 16, right: 16 })
|
||||
.onChange((value: string) => {
|
||||
this.classCode = value;
|
||||
this.errorMessage = '';
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.margin({ bottom: 16 })
|
||||
|
||||
// 错误信息
|
||||
if (this.errorMessage !== '') {
|
||||
Text(this.errorMessage)
|
||||
.fontSize(14)
|
||||
.fontColor('#FF0000')
|
||||
.width('100%')
|
||||
.margin({ bottom: 16 })
|
||||
}
|
||||
|
||||
// 加入课堂按钮
|
||||
Button("加入课堂")
|
||||
.width('100%')
|
||||
.height(50)
|
||||
.fontSize(18)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.backgroundColor(this.settings.themeColor)
|
||||
.borderRadius(8)
|
||||
.fontColor(Color.White)
|
||||
.enabled(!this.isLoading)
|
||||
.opacity(this.isLoading ? 0.6 : 1)
|
||||
.onClick(() => {
|
||||
this.joinClass();
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(Color.White)
|
||||
.borderRadius(12)
|
||||
.padding(20)
|
||||
.shadow({ radius: 6, color: '#eeeeee' })
|
||||
.margin({ top: 20, bottom: 20 })
|
||||
|
||||
// 当前课程信息卡片(只在没有正在进行的课程时显示)
|
||||
Column() {
|
||||
Text("近期课程").fontSize(20).fontWeight(FontWeight.Bold).margin({ bottom: 16 })
|
||||
|
||||
Row() {
|
||||
Column() {
|
||||
Text(this.currentClassInfo.name)
|
||||
.fontSize(18)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
Text(`${this.texts.teacher}: ${this.currentClassInfo.teacher}`)
|
||||
.fontSize(14)
|
||||
.fontColor('#666')
|
||||
.margin({ top: 8 })
|
||||
Text(`${this.texts.classroom}: ${this.currentClassInfo.room}`)
|
||||
.fontSize(14)
|
||||
.fontColor('#666')
|
||||
.margin({ top: 4 })
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
|
||||
Column() {
|
||||
Text("未开始")
|
||||
.fontSize(14)
|
||||
.fontColor('#999')
|
||||
.backgroundColor('#f5f5f5')
|
||||
.borderRadius(12)
|
||||
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
|
||||
}
|
||||
.justifyContent(FlexAlign.Center)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(16)
|
||||
.borderRadius(8)
|
||||
.backgroundColor('#f9f9f9')
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(Color.White)
|
||||
.borderRadius(12)
|
||||
.padding(20)
|
||||
.shadow({ radius: 6, color: '#eeeeee' })
|
||||
}
|
||||
}
|
||||
|
||||
// 教师视图
|
||||
@Builder
|
||||
TeacherView() {
|
||||
Column() {
|
||||
// 教师上课指引卡片
|
||||
Column() {
|
||||
Text("教师上课")
|
||||
.fontSize(24)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.margin({ bottom: 16 })
|
||||
|
||||
Text("点击下方按钮创建新课堂")
|
||||
.fontSize(16)
|
||||
.fontColor('#666')
|
||||
.margin({ bottom: 20 })
|
||||
|
||||
// 创建课堂按钮
|
||||
Button("创建新课堂")
|
||||
.width('100%')
|
||||
.height(50)
|
||||
.fontSize(18)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.backgroundColor(this.settings.themeColor)
|
||||
.borderRadius(8)
|
||||
.fontColor(Color.White)
|
||||
.enabled(!this.isLoading)
|
||||
.opacity(this.isLoading ? 0.6 : 1)
|
||||
.onClick(() => {
|
||||
this.showCreateClassDialog = true;
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(Color.White)
|
||||
.borderRadius(12)
|
||||
.padding(20)
|
||||
.shadow({ radius: 6, color: '#eeeeee' })
|
||||
.margin({ top: 20, bottom: 20 })
|
||||
|
||||
// 历史课堂记录
|
||||
Column() {
|
||||
Text("历史课堂").fontSize(20).fontWeight(FontWeight.Bold).margin({ bottom: 16 })
|
||||
|
||||
// 示例历史课堂卡片
|
||||
Row() {
|
||||
Column() {
|
||||
Text("高等数学")
|
||||
.fontSize(18)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
Text("课堂暗号: 123456")
|
||||
.fontSize(14)
|
||||
.fontColor('#666')
|
||||
.margin({ top: 8 })
|
||||
Text("2025-04-01 10:30")
|
||||
.fontSize(14)
|
||||
.fontColor('#666')
|
||||
.margin({ top: 4 })
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
|
||||
Column() {
|
||||
Text("已结束")
|
||||
.fontSize(14)
|
||||
.fontColor('#999')
|
||||
.backgroundColor('#f5f5f5')
|
||||
.borderRadius(12)
|
||||
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
|
||||
}
|
||||
.justifyContent(FlexAlign.Center)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(16)
|
||||
.borderRadius(8)
|
||||
.backgroundColor('#f9f9f9')
|
||||
.margin({ bottom: 12 })
|
||||
|
||||
// 示例历史课堂卡片
|
||||
Row() {
|
||||
Column() {
|
||||
Text("线性代数")
|
||||
.fontSize(18)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
Text("课堂暗号: 654321")
|
||||
.fontSize(14)
|
||||
.fontColor('#666')
|
||||
.margin({ top: 8 })
|
||||
Text("2025-03-30 14:30")
|
||||
.fontSize(14)
|
||||
.fontColor('#666')
|
||||
.margin({ top: 4 })
|
||||
}
|
||||
.layoutWeight(1)
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
|
||||
Column() {
|
||||
Text("已结束")
|
||||
.fontSize(14)
|
||||
.fontColor('#999')
|
||||
.backgroundColor('#f5f5f5')
|
||||
.borderRadius(12)
|
||||
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
|
||||
}
|
||||
.justifyContent(FlexAlign.Center)
|
||||
}
|
||||
.width('100%')
|
||||
.padding(16)
|
||||
.borderRadius(8)
|
||||
.backgroundColor('#f9f9f9')
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(Color.White)
|
||||
.borderRadius(12)
|
||||
.padding(20)
|
||||
.shadow({ radius: 6, color: '#eeeeee' })
|
||||
}
|
||||
}
|
||||
|
||||
// 未知角色视图
|
||||
@Builder
|
||||
UnknownRoleView() {
|
||||
Column() {
|
||||
Text("用户角色未知")
|
||||
.fontSize(24)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.margin({ bottom: 16 })
|
||||
|
||||
Text("系统无法识别您的用户角色,请联系管理员或重新登录")
|
||||
.fontSize(16)
|
||||
.fontColor('#666')
|
||||
.margin({ bottom: 20 })
|
||||
|
||||
Button("返回登录")
|
||||
.width('60%')
|
||||
.height(50)
|
||||
.fontSize(18)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.backgroundColor(this.settings.themeColor)
|
||||
.borderRadius(8)
|
||||
.fontColor(Color.White)
|
||||
.onClick(() => {
|
||||
// 返回登录页
|
||||
router.replaceUrl({
|
||||
url: 'pages/login'
|
||||
});
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.backgroundColor(Color.White)
|
||||
.borderRadius(12)
|
||||
.padding(20)
|
||||
.shadow({ radius: 6, color: '#eeeeee' })
|
||||
.margin({ top: 20 })
|
||||
}
|
||||
|
||||
// 创建课堂对话框
|
||||
@Builder
|
||||
CreateClassDialog() {
|
||||
// 遮罩层
|
||||
Column() {
|
||||
// 对话框内容
|
||||
Column() {
|
||||
// 标题
|
||||
Text("创建新课堂")
|
||||
.fontSize(22)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.margin({ bottom: 24 })
|
||||
|
||||
// 课堂名称
|
||||
Column({ space: 8 }) {
|
||||
Text("课堂名称")
|
||||
.fontSize(16)
|
||||
.fontColor('#666666')
|
||||
.alignSelf(ItemAlign.Start)
|
||||
|
||||
TextInput({ placeholder: '请输入课堂名称' })
|
||||
.maxLength(20)
|
||||
.placeholderColor('#999999')
|
||||
.placeholderFont({ size: 16 })
|
||||
.width('100%')
|
||||
.height(50)
|
||||
.fontSize(16)
|
||||
.fontColor('#333333')
|
||||
.backgroundColor('#F5F5F5')
|
||||
.borderRadius(8)
|
||||
.padding({ left: 16, right: 16 })
|
||||
.onChange((value: string) => {
|
||||
this.className = value;
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.margin({ bottom: 16 })
|
||||
|
||||
// 课堂暗号
|
||||
Column({ space: 8 }) {
|
||||
Text("课堂暗号")
|
||||
.fontSize(16)
|
||||
.fontColor('#666666')
|
||||
.alignSelf(ItemAlign.Start)
|
||||
|
||||
TextInput({ placeholder: '请输入4-6位数字暗号' })
|
||||
.type(InputType.Number)
|
||||
.maxLength(6)
|
||||
.placeholderColor('#999999')
|
||||
.placeholderFont({ size: 16 })
|
||||
.width('100%')
|
||||
.height(50)
|
||||
.fontSize(16)
|
||||
.fontColor('#333333')
|
||||
.backgroundColor('#F5F5F5')
|
||||
.borderRadius(8)
|
||||
.padding({ left: 16, right: 16 })
|
||||
.onChange((value: string) => {
|
||||
this.classCode = value;
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.margin({ bottom: 16 })
|
||||
|
||||
// 错误信息
|
||||
if (this.errorMessage !== '') {
|
||||
Text(this.errorMessage)
|
||||
.fontSize(14)
|
||||
.fontColor('#FF0000')
|
||||
.width('100%')
|
||||
.margin({ bottom: 16 })
|
||||
}
|
||||
|
||||
// 按钮组
|
||||
Row({ space: 20 }) {
|
||||
Button("取消")
|
||||
.width('45%')
|
||||
.height(45)
|
||||
.fontSize(16)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.backgroundColor('#f5f5f5')
|
||||
.borderRadius(8)
|
||||
.fontColor('#333')
|
||||
.onClick(() => {
|
||||
this.showCreateClassDialog = false;
|
||||
this.errorMessage = '';
|
||||
})
|
||||
|
||||
Button("创建")
|
||||
.width('45%')
|
||||
.height(45)
|
||||
.fontSize(16)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.backgroundColor(this.settings.themeColor)
|
||||
.borderRadius(8)
|
||||
.fontColor(Color.White)
|
||||
.enabled(!this.isLoading)
|
||||
.opacity(this.isLoading ? 0.6 : 1)
|
||||
.onClick(() => {
|
||||
this.createClass();
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.width('85%')
|
||||
.backgroundColor(Color.White)
|
||||
.borderRadius(16)
|
||||
.padding(24)
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor('rgba(0,0,0,0.5)')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.onClick(() => {
|
||||
// 点击遮罩不关闭对话框
|
||||
// 只能通过按钮关闭
|
||||
})
|
||||
}
|
||||
|
||||
// 加入课堂对话框
|
||||
@Builder
|
||||
JoinClassDialog() {
|
||||
// 遮罩层
|
||||
Column() {
|
||||
// 对话框内容
|
||||
Column() {
|
||||
// 标题
|
||||
Text("加入课堂")
|
||||
.fontSize(22)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
.margin({ bottom: 24 })
|
||||
|
||||
// 课堂暗号
|
||||
Column({ space: 8 }) {
|
||||
Text("课堂暗号")
|
||||
.fontSize(16)
|
||||
.fontColor('#666666')
|
||||
.alignSelf(ItemAlign.Start)
|
||||
|
||||
TextInput({ placeholder: '请输入4-6位数字暗号' })
|
||||
.type(InputType.Number)
|
||||
.maxLength(6)
|
||||
.placeholderColor('#999999')
|
||||
.placeholderFont({ size: 16 })
|
||||
.width('100%')
|
||||
.height(50)
|
||||
.fontSize(16)
|
||||
.fontColor('#333333')
|
||||
.backgroundColor('#F5F5F5')
|
||||
.borderRadius(8)
|
||||
.padding({ left: 16, right: 16 })
|
||||
.onChange((value: string) => {
|
||||
this.classCode = value;
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.margin({ bottom: 16 })
|
||||
|
||||
// 错误信息
|
||||
if (this.errorMessage !== '') {
|
||||
Text(this.errorMessage)
|
||||
.fontSize(14)
|
||||
.fontColor('#FF0000')
|
||||
.width('100%')
|
||||
.margin({ bottom: 16 })
|
||||
}
|
||||
|
||||
// 按钮组
|
||||
Row({ space: 20 }) {
|
||||
Button("取消")
|
||||
.width('45%')
|
||||
.height(45)
|
||||
.fontSize(16)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.backgroundColor('#f5f5f5')
|
||||
.borderRadius(8)
|
||||
.fontColor('#333')
|
||||
.onClick(() => {
|
||||
this.showJoinClassDialog = false;
|
||||
this.errorMessage = '';
|
||||
})
|
||||
|
||||
Button("加入")
|
||||
.width('45%')
|
||||
.height(45)
|
||||
.fontSize(16)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.backgroundColor(this.settings.themeColor)
|
||||
.borderRadius(8)
|
||||
.fontColor(Color.White)
|
||||
.enabled(!this.isLoading)
|
||||
.opacity(this.isLoading ? 0.6 : 1)
|
||||
.onClick(() => {
|
||||
this.joinClass();
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.width('85%')
|
||||
.backgroundColor(Color.White)
|
||||
.borderRadius(16)
|
||||
.padding(24)
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.backgroundColor('rgba(0,0,0,0.5)')
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.onClick(() => {
|
||||
// 点击遮罩不关闭对话框
|
||||
// 只能通过按钮关闭
|
||||
})
|
||||
}
|
||||
|
||||
// 创建课堂方法
|
||||
private async createClass() {
|
||||
// 参数验证
|
||||
if (!this.className || this.className.trim() === '') {
|
||||
this.errorMessage = '请输入课堂名称';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.classCode || this.classCode.trim() === '') {
|
||||
this.errorMessage = '请输入课堂暗号';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^\d{4,6}$/.test(this.classCode)) {
|
||||
this.errorMessage = '课堂暗号必须是4-6位数字';
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
// 连接服务
|
||||
await classRoomService.connect();
|
||||
|
||||
// 创建课堂
|
||||
try {
|
||||
const classSession = await classRoomService.createClassSession(this.className, this.classCode);
|
||||
|
||||
if (classSession) {
|
||||
// 创建成功
|
||||
this.showCreateClassDialog = false;
|
||||
this.errorMessage = '';
|
||||
|
||||
// 跳转到教师上课页面
|
||||
router.pushUrl({
|
||||
url: 'pages/ClassLivePage',
|
||||
params: {
|
||||
mode: 'teacher'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.errorMessage = '创建课堂失败,请稍后再试';
|
||||
}
|
||||
} catch (error) {
|
||||
this.errorMessage = '创建过程中发生错误,请稍后再试';
|
||||
logManager.error(LogCategory.CLASS, `Create class error: ${error}`);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 加入课堂方法
|
||||
private async joinClass() {
|
||||
// 参数验证
|
||||
if (!this.classCode || this.classCode.trim() === '') {
|
||||
this.errorMessage = '请输入课堂暗号';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^\d{4,6}$/.test(this.classCode)) {
|
||||
this.errorMessage = '课堂暗号必须是4-6位数字';
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
// 连接服务
|
||||
await classRoomService.connect();
|
||||
|
||||
// 加入课堂
|
||||
try {
|
||||
const result = await classRoomService.joinClassSession(this.classCode);
|
||||
|
||||
if (result) {
|
||||
// 加入成功
|
||||
this.showJoinClassDialog = false;
|
||||
this.errorMessage = '';
|
||||
|
||||
// 跳转到学生上课页面
|
||||
router.pushUrl({
|
||||
url: 'pages/ClassLivePage',
|
||||
params: {
|
||||
mode: 'student'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.errorMessage = '加入课堂失败,请检查暗号是否正确';
|
||||
}
|
||||
} catch (error) {
|
||||
this.errorMessage = '加入过程中发生错误,请稍后再试';
|
||||
logManager.error(LogCategory.CLASS, `Join class error: ${error}`);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 页面生命周期方法
|
||||
onPageShow(): void {
|
||||
@ -256,10 +875,7 @@ struct BottomNavigation {
|
||||
.onClick(() => {
|
||||
if (this.activePage !== 'settings') {
|
||||
router.replaceUrl({
|
||||
url: 'pages/SettingsPage',
|
||||
params: {
|
||||
direction: 'right' // 从上课页向右切换到设置页
|
||||
}
|
||||
url: 'pages/SettingsPage'
|
||||
});
|
||||
}
|
||||
})
|
||||
@ -267,7 +883,9 @@ struct BottomNavigation {
|
||||
.width('100%')
|
||||
.height(60)
|
||||
.backgroundColor(Color.White)
|
||||
.border({ color: '#eeeeee', width: 1, style: BorderStyle.Solid })
|
||||
.borderWidth({ top: 0.5 })
|
||||
.borderColor('#eeeeee')
|
||||
.padding({ top: 8, bottom: 8 })
|
||||
}
|
||||
}
|
||||
|
||||
|
BIN
entry/src/main/resources/base/media/add.png
Normal file
BIN
entry/src/main/resources/base/media/add.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
entry/src/main/resources/base/media/back.png
Normal file
BIN
entry/src/main/resources/base/media/back.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
entry/src/main/resources/base/media/close.png
Normal file
BIN
entry/src/main/resources/base/media/close.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
entry/src/main/resources/base/media/delete.png
Normal file
BIN
entry/src/main/resources/base/media/delete.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
BIN
entry/src/main/resources/base/media/question.png
Normal file
BIN
entry/src/main/resources/base/media/question.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
@ -5,6 +5,7 @@
|
||||
"pages/HomePage",
|
||||
"pages/SettingsPage",
|
||||
"pages/ClassPage",
|
||||
"pages/TransitionPage"
|
||||
"pages/TransitionPage",
|
||||
"pages/ClassLivePage"
|
||||
]
|
||||
}
|
14
package.json
Normal file
14
package.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "classmg-server",
|
||||
"version": "1.0.0",
|
||||
"description": "Communication server for ClassMG application",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5"
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user