This commit is contained in:
chenshuainan
2026-09-28 20:12:34 +08:00
parent 847986f861
commit 5f53bd8f94
60 changed files with 14624 additions and 398 deletions
@@ -0,0 +1,136 @@
/// ============================================================
/// 【课堂习题 · 会话状态与 UI 桥接】
///
/// Vue 端各 createXxxHandlers 通过一堆 ref 共享状态;Flutter 端按
/// 用户拍板用“普通 Dart 控制器(无 Riverpod)”,因此把跨控制器共享
/// 的可变状态收敛到 [ExerciseSession],把与 Widget/导航/弹窗相关的
/// 副作用收敛到 [ExerciseUiBridge],由页面 State 实现并注入。
/// ============================================================
library;
import 'package:flutter/widgets.dart';
import 'package:new_learn/features/conquerPackage/classExercises/exercise_question_view.dart';
import 'package:new_learn/features/conquerPackage/classExercises/models/question_model.dart';
import 'package:new_learn/features/conquerPackage/classExercises/widgets/compound_question_panel.dart';
import 'package:new_learn/features/conquerPackage/classExercises/widgets/question_plan_countdown.dart';
/// 当前本地 ISO 时间(对标 getCurrentISO)
String getCurrentISO() => DateTime.now().toIso8601String();
/// 攻克流程弹窗配置(对标 Vue conquerPopupCon)
class ConquerPopupConfig {
ConquerPopupConfig({
this.show = false,
this.type = 'note',
this.title = '',
this.subtitle = '',
this.confirmText = '确定',
this.cancelText = '取消',
this.isConfirm = false,
this.confirmAction = '',
});
bool show;
String type; // note / time / submit / grading
String title;
String subtitle;
String confirmText;
String cancelText;
bool isConfirm;
String confirmAction;
}
/// 跨控制器共享的会话状态(字段名与 Vue 保持一致)
class ExerciseSession {
/// 题目列表(解析后的 QuestionModel,与 Map 同引用语义)
List<QuestionModel> questions = [];
/// 当前大题索引(swiper index)
int currentIndex = 0;
/// edit / view
String mode = 'edit';
String courseId = '';
String packId = '';
String totalPackId = '';
String recordId = '';
bool isWrong = false;
/// view 全量题号(originalMaxSort)
int originalMaxSort = 0;
/// 导航栏标题
String navBarTitle = '';
/// 大题索引 -> 激活子题索引
final Map<int, int> activeSubIndexMap = {};
/// 大题索引 -> 等待页面挂载后定位的子题索引
final Map<int, int> pendingSubIndexMap = {};
/// 已处理过的题目 id(避免重复提交,对应 processedQuestionIds)
final Set<String> processedQuestionIds = <String>{};
/// 单题页 ref(itemRefs)
final Map<int, GlobalKey<ExerciseQuestionViewState>> itemRefs = {};
/// 复合题面板 ref(compoundQuestionFn)
GlobalKey<CompoundQuestionPanelState>? panelKey;
/// 倒计时组件 ref(countDownRef)
GlobalKey<QuestionPlanCountDownState>? countDownKey;
bool get isView => mode == 'view';
/// 当前题(questionsInfo)。Flutter 与列表元素保持同一引用,
/// 不再做 Vue 的浅拷贝。
QuestionModel? get currentQuestion {
if (currentIndex < 0 || currentIndex >= questions.length) return null;
return questions[currentIndex];
}
}
/// 控制器 -> 页面的副作用桥接(页面 State implements 本接口)
abstract class ExerciseUiBridge {
/// 全局 loading(对标 uni.showLoading({mask:true}))
void showLoading([String message]);
void hideLoading();
/// 轻提示(对标 uni.showToast icon:none)
void toast(String message);
/// 通知页面刷新(对标 Vue ref 赋值触发渲染)
void notifyListeners();
/// PageController 切页(对标 swiper.slideTo)
void jumpToPage(int index);
/// 打开“退出攻克”确认框(openExitPopup)
void openExitPopup();
/// 展示攻克流程弹窗(getConquerPopupCon)
void showConquerPopup(ConquerPopupConfig config);
/// 关闭攻克流程弹窗
void hideConquerPopup();
/// 答题卡显隐(showAnswerSheet)
void setAnswerSheetVisible(bool visible);
/// 暂停弹窗显隐(showPausePopup)
void setPausePopupVisible(bool visible);
/// 交卷成功跳报告页(redirectTo report)
void redirectToReport(String recordId, String packId);
/// 返回上 N 页(navigateBack delta)
void navigateBack({int delta});
/// 重启到攻克列表(reLaunch captureList)
void relaunchCaptureList(String courseId);
/// 是否平板(对标 isPadDevice(),time 弹窗字号用)
bool get isPad;
}