ClassMG/entry/src/main/ets/pages/ClassLivePage.ets
2025-04-02 22:15:03 +08:00

1447 lines
42 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { router } from '@kit.ArkUI';
import promptAction from '@ohos.promptAction';
import settingsService, { SettingsModel, TextResources } from '../common/SettingsService';
import { ClassRoomService,
ClassSessionModel,
MessageModel,
QuestionModel,
QuestionOption,
QuestionAnswer,
WebSocketEventType,
SenderRole,
QuestionStatus,
EventData
} from '../common/ClassRoomService';
import logManager, { LogCategory, LogEventType } from '../common/logtext';
import http from '@ohos.net.http';
// 路由URL接口
interface RouterUrlOptions {
url: string;
params?: object;
}
// 路由参数接口
interface RouteParams {
mode?: string;
}
// 选项统计接口
interface OptionStats {
count: number;
percentage: number;
}
// 正确率统计接口
interface CorrectRateStats {
correctCount: number;
totalCount: number;
percentage: number;
}
// 统计数据接口
interface StatisticsData {
correctRate: CorrectRateStats;
getOptionStats: (index: number) => OptionStats;
}
// 对话框按钮定义
interface DialogButton {
value: string;
action: () => void;
}
// 对话框配置
interface AlertDialogConfig {
title: string;
message: string;
primaryButton: DialogButton;
secondaryButton?: DialogButton;
}
// 课堂界面模式
enum ClassLiveMode {
TEACHER = 'teacher',
STUDENT = 'student'
}
@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 = () => {};
// 用于服务实例
private classRoomService: ClassRoomService = ClassRoomService.getInstance();
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 = this.classRoomService.getCurrentSession();
if (!this.classSession) {
// 如果没有课堂会话,返回上课页面
this.showErrorAndReturn('未找到课堂会话,请重新加入或创建课堂');
return;
}
// 获取当前消息列表
this.messages = this.classSession.messages || [];
// 获取当前活动题目
this.currentQuestion = this.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) {
// 检查消息是否已经存在(防止重复)
const existingMessage = this.messages.find(m => m.id === message.id);
if (!existingMessage) {
this.messages.push(message);
}
}
};
this.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);
}
};
this.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;
}
};
this.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'
} as RouterUrlOptions);
}
};
const alertConfig: AlertDialogConfig = {
title: '课堂已结束',
message: '教师已结束当前课堂',
primaryButton: primaryButton
};
AlertDialog.show(alertConfig);
}
};
this.classRoomService.addEventListener(WebSocketEventType.END_CLASS, this.endClassCallback);
}
// 创建问题编辑器内的选项
private addOption() {
if (!this.questionOptions) {
this.questionOptions = [];
}
const newIndex = this.questionOptions.length;
const newOption: QuestionOption = {
index: newIndex,
content: ''
};
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);
// 重新编号
for (let i = 0; i < this.questionOptions.length; i++) {
this.questionOptions[i].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) {
const emptyStats: OptionStats = {
count: 0,
percentage: 0
};
return emptyStats;
}
// 使用普通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;
const stats: OptionStats = {
count: optionCount,
percentage: percentage
};
return stats;
}
// 计算正确率
private getCorrectRate(): CorrectRateStats {
if (!this.currentQuestion || this.currentQuestion.answers.length === 0) {
const emptyStats: CorrectRateStats = {
correctCount: 0,
totalCount: 0,
percentage: 0
};
return emptyStats;
}
// 使用普通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;
const stats: CorrectRateStats = {
correctCount: correctCount,
totalCount: totalCount,
percentage: percentage
};
return stats;
}
// 获取当前用户的答案
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 = '';
}
// 发送消息
private async sendMessage() {
if (!this.messageText || this.messageText.trim() === '') {
return;
}
try {
const result = await this.classRoomService.sendMessage(this.messageText);
if (result) {
// 清空消息
this.messageText = '';
}
} catch (error) {
// 处理错误
const errorMessage = error instanceof Error ? error.message : String(error);
logManager.error(LogCategory.CLASS, `Send message error: ${errorMessage}`);
}
}
// 开始倒计时
private startCountdown(seconds: number) {
// 停止之前的计时器
this.stopCountdown();
// 设置初始倒计时
this.countdownSeconds = seconds;
// 创建新计时器 - 使用明确的类型
this.countdownTimer = setInterval(() => {
if (this.countdownSeconds > 0) {
this.countdownSeconds--;
} else {
// 倒计时结束
this.stopCountdown();
}
}, 1000) as number; // 直接转换为number不用中间unknown
}
// 停止倒计时
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 this.classRoomService.publishQuestion(
this.questionTitle,
this.questionOptions,
this.correctOption,
this.questionDuration
);
if (question) {
// 清空题目编辑器
this.resetQuestionEditor();
// 关闭题目编辑器
this.showQuestionEditor = false;
} else {
this.errorMessage = '发布题目失败,请稍后再试';
}
} catch (error) {
this.errorMessage = '发布题目过程中发生错误';
const errorMessage = error instanceof Error ? error.message : String(error);
logManager.error(LogCategory.CLASS, `Publish question error: ${errorMessage}`);
} finally {
this.isLoading = false;
}
}
// 提交答案
private async submitAnswer() {
if (this.selectedOption < 0 || !this.currentQuestion) {
this.errorMessage = '请选择一个选项';
return;
}
this.isLoading = true;
try {
const result = await this.classRoomService.submitAnswer(
this.currentQuestion.questionId,
this.selectedOption
);
if (result) {
// 关闭答题窗口
this.showAnswerDialog = false;
// 清空错误消息
this.errorMessage = '';
} else {
this.errorMessage = '提交答案失败,请稍后再试';
}
} catch (error) {
this.errorMessage = '提交答案过程中发生错误';
const errorMessage = error instanceof Error ? error.message : String(error);
logManager.error(LogCategory.CLASS, `Submit answer error: ${errorMessage}`);
} finally {
this.isLoading = false;
}
}
// 结束课堂
private async endClass() {
this.isLoading = true;
try {
const result = await this.classRoomService.endClassSession();
if (result) {
// 返回上课页面 - 使用明确的导航而不是back()
router.pushUrl({
url: 'pages/ClassPage'
} as RouterUrlOptions);
} 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);
const errorMessage = error instanceof Error ? error.message : String(error);
logManager.error(LogCategory.CLASS, `End class error: ${errorMessage}`);
} finally {
this.isLoading = false;
}
}
// 显示错误并返回
private showErrorAndReturn(message: string) {
const primaryButton: DialogButton = {
value: '确定',
action: () => {
// 使用明确的导航而不是back()
router.pushUrl({
url: 'pages/ClassPage'
} as RouterUrlOptions);
}
};
const alertConfig: AlertDialogConfig = {
title: '错误',
message: message,
primaryButton: primaryButton
};
AlertDialog.show(alertConfig);
}
// 格式化时间
private formatTime(timestamp: number): 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) {
this.classRoomService.removeEventListener(WebSocketEventType.SEND_MESSAGE, this.messageCallback);
}
if (this.questionCallback) {
this.classRoomService.removeEventListener(WebSocketEventType.PUBLISH_QUESTION, this.questionCallback);
}
if (this.endQuestionCallback) {
this.classRoomService.removeEventListener(WebSocketEventType.END_QUESTION, this.endQuestionCallback);
}
if (this.endClassCallback) {
this.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'
} as RouterUrlOptions);
}
};
const secondaryButton: DialogButton = {
value: '取消',
action: () => {}
};
const alertConfig: AlertDialogConfig = {
title: '离开课堂',
message: '确定要离开当前课堂吗?',
primaryButton: primaryButton,
secondaryButton: secondaryButton
};
AlertDialog.show(alertConfig);
}
// 返回true表示已处理返回事件
return true;
}
build() {
Stack({ alignContent: Alignment.TopStart }) {
Column() {
// 顶部导航栏
Row() {
Row() {
Image($r('app.media.back'))
.width(24)
.height(24)
.fillColor(Color.White)
.margin({ right: 16 })
.onClick(() => {
this.onBackPress();
})
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.getTime()))
.fontSize(12)
.fontColor('#999')
.margin({ top: 4 })
.alignSelf(message.senderId === settingsService.getCurrentAccount() ? ItemAlign.End : ItemAlign.Start)
}
.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 })
// 错误消息和加载状态
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')
}
.justifyContent(FlexAlign.Center)
.width('100%')
.margin({ top: 10 })
}
}
.width('100%')
.height('100%')
.padding(16)
}
.width('100%')
.height('100%')
// 题目编辑窗口
if (this.showQuestionEditor) {
this.QuestionEditorDialog()
}
// 题目答题窗口
if (this.showAnswerDialog && this.currentQuestion) {
this.QuestionAnswerDialog()
}
// 题目统计窗口
if (this.showQuestionStats && this.currentQuestion) {
this.QuestionStatsDialog()
}
// 浮动操作按钮区域
Column() {
if (this.mode === ClassLiveMode.TEACHER && !this.showQuestionEditor) {
Button({ type: ButtonType.Circle }) {
Image($r('app.media.add'))
.width(24)
.height(24)
.fillColor(Color.White)
}
.width(56)
.height(56)
.backgroundColor(this.settings.themeColor)
.shadow({ radius: 8, color: 'rgba(0, 0, 0, 0.2)' })
.onClick(() => {
this.showQuestionEditor = true;
// 初始化题目选项
if (this.questionOptions.length === 0) {
this.addOption();
this.addOption();
}
})
}
if (this.mode === ClassLiveMode.STUDENT && this.currentQuestion && !this.showAnswerDialog && !this.showQuestionStats) {
Button({ type: ButtonType.Circle }) {
Image($r('app.media.question'))
.width(24)
.height(24)
.fillColor(Color.White)
}
.width(56)
.height(56)
.backgroundColor(this.settings.themeColor)
.shadow({ radius: 8, color: 'rgba(0, 0, 0, 0.2)' })
.onClick(() => {
this.showAnswerDialog = true;
})
}
}
.position({ x: 24, y: '85%' })
}
.width('100%')
.height('100%')
}
// 题目编辑窗口
@Builder
QuestionEditorDialog() {
Column() {
// 对话框内容
Column() {
// 标题
Row() {
Text("发布题目")
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
Button({ type: ButtonType.Circle }) {
Image($r('app.media.close'))
.width(16)
.height(16)
}
.width(32)
.height(32)
.backgroundColor('#f0f0f0')
.onClick(() => {
this.showQuestionEditor = false;
})
}
.width('100%')
.margin({ bottom: 20 })
// 题目内容
Column({ space: 10 }) {
Text("题目内容")
.fontSize(16)
.fontColor('#666666')
.alignSelf(ItemAlign.Start)
TextArea({ placeholder: '请输入题目内容', text: this.questionTitle })
.width('100%')
.height(100)
.fontSize(16)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.padding(10)
.onChange((value: string) => {
this.questionTitle = value;
})
}
.width('100%')
.margin({ bottom: 16 })
// 选项部分
Column({ space: 10 }) {
Row() {
Text("选项")
.fontSize(16)
.fontColor('#666666')
Blank()
Button({ type: ButtonType.Circle }) {
Image($r('app.media.add'))
.width(16)
.height(16)
}
.width(32)
.height(32)
.backgroundColor(this.settings.themeColor)
.onClick(() => {
this.addOption();
})
}
.width('100%')
// 选项列表
Column({ space: 10 }) {
ForEach(this.questionOptions, (option: QuestionOption, index: number) => {
Row() {
Radio({ value: index.toString(), group: 'correctOption' })
.checked(index === this.correctOption)
.onChange((isChecked: boolean) => {
if (isChecked) {
this.correctOption = index;
}
})
Text(`选项 ${String.fromCharCode(65 + index)}`)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ left: 8, right: 8 })
TextInput({ placeholder: '请输入选项内容', text: option.content })
.layoutWeight(1)
.height(40)
.fontSize(16)
.backgroundColor('#F5F5F5')
.borderRadius(8)
.padding({ left: 10, right: 10 })
.onChange((value: string) => {
this.updateOptionContent(index, value);
})
// 删除选项按钮
if (this.questionOptions.length > 2) {
Button({ type: ButtonType.Circle }) {
Image($r('app.media.delete'))
.width(16)
.height(16)
}
.width(32)
.height(32)
.backgroundColor('#f5f5f5')
.margin({ left: 8 })
.onClick(() => {
this.removeOption(index);
})
}
}
.width('100%')
.alignItems(VerticalAlign.Center)
})
}
.width('100%')
}
.width('100%')
.margin({ bottom: 16 })
// 答题时间
Column({ space: 10 }) {
Text("答题时间(秒)")
.fontSize(16)
.fontColor('#666666')
.alignSelf(ItemAlign.Start)
Row() {
Slider({
value: this.questionDuration,
min: 10,
max: 120,
step: 5,
style: SliderStyle.OutSet
})
.layoutWeight(1)
.blockColor(this.settings.themeColor)
.trackColor('#E1E1E1')
.selectedColor(this.settings.themeColor)
.showSteps(true)
.showTips(true)
.onChange((value: number) => {
this.questionDuration = value;
})
Text(this.questionDuration.toString())
.fontSize(16)
.fontColor('#333')
.margin({ left: 16 })
.width(40)
.textAlign(TextAlign.End)
}
.width('100%')
}
.width('100%')
.margin({ bottom: 20 })
// 错误信息
if (this.errorMessage) {
Text(this.errorMessage)
.fontSize(14)
.fontColor('#F56C6C')
.width('100%')
.textAlign(TextAlign.Center)
.margin({ bottom: 16 })
}
// 发布按钮
Button("发布题目")
.width('100%')
.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.publishQuestion();
})
}
.width('90%')
.backgroundColor(Color.White)
.borderRadius(16)
.padding(24)
}
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.5)')
.justifyContent(FlexAlign.Center)
.onClick(() => {
// 点击遮罩不关闭对话框
})
}
// 答题窗口
@Builder
QuestionAnswerDialog() {
Column() {
// 对话框内容
Column() {
// 标题
Row() {
Text("答题")
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
Button({ type: ButtonType.Circle }) {
Image($r('app.media.close'))
.width(16)
.height(16)
}
.width(32)
.height(32)
.backgroundColor('#f0f0f0')
.onClick(() => {
this.showAnswerDialog = false;
})
}
.width('100%')
.margin({ bottom: 20 })
// 题目内容
Column() {
Text(this.currentQuestion?.title || '')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 10 })
if (this.countdownSeconds > 0) {
Text(`剩余时间: ${this.countdownSeconds}秒`)
.fontSize(16)
.fontColor(this.countdownSeconds > 10 ? '#333' : '#F56C6C')
.margin({ bottom: 20 })
}
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.margin({ bottom: 16 })
// 选项列表
Column({ space: 10 }) {
if (this.currentQuestion) {
ForEach(this.currentQuestion.options, (option: QuestionOption, index: number) => {
Row() {
Radio({ value: index.toString(), group: 'answerOption' })
.checked(this.selectedOption === index)
.onChange((isChecked: boolean) => {
if (isChecked) {
this.selectedOption = index;
}
})
Text(`${String.fromCharCode(65 + index)}. ${option.content}`)
.fontSize(16)
.margin({ left: 10 })
.layoutWeight(1)
}
.width('100%')
.padding(10)
.borderRadius(8)
.backgroundColor(this.selectedOption === index ? '#f0f7ff' : '#f5f5f5')
.alignItems(VerticalAlign.Center)
})
}
}
.width('100%')
.margin({ bottom: 20 })
// 错误信息
if (this.errorMessage) {
Text(this.errorMessage)
.fontSize(14)
.fontColor('#F56C6C')
.width('100%')
.textAlign(TextAlign.Center)
.margin({ bottom: 16 })
}
// 提交按钮
Button("提交答案")
.width('100%')
.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.submitAnswer();
})
}
.width('90%')
.backgroundColor(Color.White)
.borderRadius(16)
.padding(24)
}
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.5)')
.justifyContent(FlexAlign.Center)
.onClick(() => {
// 点击遮罩不关闭对话框
})
}
// 获取题目统计数据 - 辅助方法
private getStatisticsData(): StatisticsData {
return {
correctRate: this.getCorrectRate(),
getOptionStats: (index: number) => this.getOptionStats(index)
};
}
// 题目统计内容
@Builder
QuestionStatsContent() {
Column() {
// 总体正确率
Row() {
Text("参与人数: ")
.fontSize(16)
.fontWeight(FontWeight.Medium)
Text(`${this.getCorrectRate().totalCount}人`)
.fontSize(16)
.fontWeight(FontWeight.Medium)
Blank()
Text("正确率: ")
.fontSize(16)
.fontWeight(FontWeight.Medium)
Text(`${this.getCorrectRate().percentage}%`)
.fontSize(16)
.fontColor(this.getCorrectRate().percentage > 50 ? '#67C23A' : '#F56C6C')
.fontWeight(FontWeight.Medium)
}
.width('100%')
.padding(10)
.borderRadius(8)
.backgroundColor('#f5f5f5')
.margin({ bottom: 16 })
// 选项统计
if (this.currentQuestion) {
ForEach(this.currentQuestion.options, (option: QuestionOption, index: number) => {
Column() {
Row() {
Text(`选项 ${String.fromCharCode(65 + index)}: ${option.content}`)
.fontSize(16)
.fontWeight(index === this.currentQuestion?.correctOption ? FontWeight.Bold : FontWeight.Normal)
.fontColor(index === this.currentQuestion?.correctOption ? '#67C23A' : '#333')
}
.width('100%')
.margin({ bottom: 4 })
Row() {
// 进度条
Column() {
Stack({ alignContent: Alignment.Start }) {
Row()
.width('100%')
.height(20)
.backgroundColor('#f0f0f0')
.borderRadius(10)
Row()
.width(`${this.getOptionStats(index).percentage}%`)
.height(20)
.backgroundColor(index === this.currentQuestion?.correctOption ? '#67C23A' : '#409EFF')
.borderRadius(10)
}
}
.layoutWeight(1)
// 百分比和人数
Text(`${this.getOptionStats(index).percentage}% (${this.getOptionStats(index).count}人)`)
.fontSize(14)
.fontColor('#666')
.margin({ left: 8 })
.width(100)
.textAlign(TextAlign.End)
}
.width('100%')
}
.width('100%')
.margin({ bottom: 12 })
.padding(10)
.borderRadius(8)
.backgroundColor(index === this.currentQuestion?.correctOption ? '#f0f9eb' : '#f9f9f9')
})
}
}
.width('100%')
}
// 题目统计窗口
@Builder
QuestionStatsDialog() {
Column() {
// 对话框内容
Column() {
// 标题
Row() {
Text("答题结果")
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
Button({ type: ButtonType.Circle }) {
Image($r('app.media.close'))
.width(16)
.height(16)
}
.width(32)
.height(32)
.backgroundColor('#f0f0f0')
.onClick(() => {
this.showQuestionStats = false;
})
}
.width('100%')
.margin({ bottom: 20 })
// 题目内容
Text(this.currentQuestion?.title || '')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.width('100%')
.margin({ bottom: 16 })
// 统计数据
Column() {
// 整体统计
if (this.mode === ClassLiveMode.TEACHER) {
this.QuestionStatsContent()
} else {
// 学生端显示自己的答案
if (this.getMyAnswer()) {
Column() {
// 学生答案
Row() {
Text("我的答案: ")
.fontSize(16)
.fontWeight(FontWeight.Medium)
Text(`${String.fromCharCode(65 + this.getMyAnswer()!.selectedOption)}`)
.fontSize(16)
.fontColor(this.getMyAnswer()!.isCorrect ? '#67C23A' : '#F56C6C')
.fontWeight(FontWeight.Medium)
Blank()
Text(this.getMyAnswer()!.isCorrect ? '✓ 正确' : '✗ 错误')
.fontSize(16)
.fontColor(this.getMyAnswer()!.isCorrect ? '#67C23A' : '#F56C6C')
.fontWeight(FontWeight.Medium)
}
.width('100%')
.padding(10)
.borderRadius(8)
.backgroundColor(this.getMyAnswer()!.isCorrect ? '#f0f9eb' : '#fef0f0')
.margin({ bottom: 20 })
// 正确答案
if (!this.getMyAnswer()!.isCorrect && this.currentQuestion) {
Row() {
Text("正确答案: ")
.fontSize(16)
.fontWeight(FontWeight.Medium)
Text(`${String.fromCharCode(65 + this.currentQuestion.correctOption)}`)
.fontSize(16)
.fontColor('#67C23A')
.fontWeight(FontWeight.Medium)
}
.width('100%')
.padding(10)
.borderRadius(8)
.backgroundColor('#f0f9eb')
.margin({ bottom: 20 })
}
}
.width('100%')
.margin({ bottom: 20 })
}
// 学生端也显示整体统计
this.QuestionStatsContent()
}
}
.width('100%')
// 关闭按钮
Button("确定")
.width('100%')
.height(45)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.backgroundColor(this.settings.themeColor)
.borderRadius(8)
.fontColor(Color.White)
.onClick(() => {
this.showQuestionStats = false;
})
}
.width('90%')
.backgroundColor(Color.White)
.borderRadius(16)
.padding(24)
}
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.5)')
.justifyContent(FlexAlign.Center)
.onClick(() => {
// 点击遮罩不关闭对话框
})
}
}