/// ============================================================ /// 【课堂习题 · 会话状态与 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 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 activeSubIndexMap = {}; /// 大题索引 -> 等待页面挂载后定位的子题索引 final Map pendingSubIndexMap = {}; /// 已处理过的题目 id(避免重复提交,对应 processedQuestionIds) final Set processedQuestionIds = {}; /// 单题页 ref(itemRefs) final Map> itemRefs = {}; /// 复合题面板 ref(compoundQuestionFn) GlobalKey? panelKey; /// 倒计时组件 ref(countDownRef) GlobalKey? 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; }