Files
2026-09-04 15:22:20 +08:00

125 lines
5.3 KiB
Dart
Raw Permalink 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 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:new_learn/features/exam/exam_question.dart';
import 'package:new_learn/features/exam/question_type.dart';
/// ============================================================
/// 【paperProvider】试卷题目数据源 —— Riverpod 内存主状态
///
/// 一、架构定位(对应方案分析结论):
/// 题目是"会话数据"不是"资产":进卷拉一次、退出即弃,
/// 不落 Isar(Isar 只负责跨会话的答案进度,见 answer_provider)。
/// 100 题内存占用 ≈ 1MB,PageView.builder 懒加载按需构建,
/// 大数组传入无性能压力。
///
/// 二、mock 模式(对标 Vue3 内联 mock 体验):
/// const bool mock = true 时直接本地生成题目(Future.delayed
/// 模拟接口延迟),改 false 后接真实接口(DioClient),
/// 一行开关切换,热重启即生效。
///
/// 三、paperSizeProvider:demo 专用,控制生成题数(50/100/200),
/// paperProvider watch 它 → 改变自动重新加载(Riverpod 响应式)。
/// ============================================================
/// Demo 专用:试卷题目数量(50/100/200,入口页选择)
class PaperSizeNotifier extends Notifier<int> {
@override
int build() => 100;
void setSize(int size) => state = size;
}
final paperSizeProvider =
NotifierProvider<PaperSizeNotifier, int>(PaperSizeNotifier.new);
/// 试卷题目加载器
final paperProvider = AsyncNotifierProvider<PaperNotifier, List<ExamQuestion>>(
PaperNotifier.new,
);
class PaperNotifier extends AsyncNotifier<List<ExamQuestion>> {
/// 接口 mock 开关:true = 本地生成;false = 接真实接口(DioClient)
static const bool mock = true;
@override
Future<List<ExamQuestion>> build() async {
final size = ref.watch(paperSizeProvider);
if (mock) {
// 模拟接口延迟:首屏展示 loading,验证加载态
await Future.delayed(const Duration(milliseconds: 600));
return MockExamData.generate(size);
}
// 真实接口:GET /api/v1/Paper/{paperId}/Questions
// final resp = await DioClient.instance.get('/api/v1/Paper/1/Questions');
// return (resp.data['list'] as List).map(Question.fromJson).toList();
return const [];
}
}
/// ============================================================
/// 【MockExamData】大数据模拟生成器(100 题 × 4 题型)
///
/// 模拟真实试卷的题目构成:单选 / 多选 / 判断 / 解答 循环分布,
/// 题干带少量 HTML 标签(RichTextContent 富文本渲染路径),
/// 用于验证 PageView.builder 大数据滑动性能与题型分发。
/// ============================================================
class MockExamData {
/// 学科题材池(按题号轮换,避免题目千篇一律)
static const List<(String, String)> _stems = [
('数学', '计算 12 × 8 = ?'),
('语文', '下列词语书写<u>完全正确</u>的一项是?'),
('英语', '— What does he do?<br>— He is a(n) <b>____</b>.'),
('科学', '植物进行光合作用的主要器官是?'),
('常识', '地球是太阳系中最大的行星。'),
('语文', '“落霞与孤鹜齐飞”出自哪篇名作?'),
('数学', '一个三角形最多有几个直角?'),
('英语', '下列单词中发音与其他三项不同的一项是?'),
];
static List<ExamQuestion> generate(int count) {
return List.generate(count, (i) {
final (subject, stem) = _stems[i % _stems.length];
return switch (i % 4) {
// 0 → 单选(4 个选项)
0 => ExamQuestion(
questionId: i + 1,
type: QuestionType.single,
title: '<p><b>【$subject】</b> 第 ${i + 1} 题</p><p>$stem</p>',
options: const ['选项 A 的内容', '选项 B 的内容', '选项 C 的内容', '选项 D 的内容'],
answer: 'A',
analysis: '本题考察基础知识点,正确答案为 A。',
),
// 1 → 多选(答案两个字母)
1 => ExamQuestion(
questionId: i + 1,
type: QuestionType.multi,
title:
'<p><b>【$subject】</b> 第 ${i + 1} 题(多选)</p><p>$stem</p>',
options: const ['选项 A 的内容', '选项 B 的内容', '选项 C 的内容', '选项 D 的内容'],
answer: 'A,C',
analysis: '本题有两个正确答案:A 与 C。',
),
// 2 → 判断(对 / 错)
2 => ExamQuestion(
questionId: i + 1,
type: QuestionType.judge,
title:
'<p><b>【$subject】</b> 第 ${i + 1} 题(判断)</p><p>$stem</p>',
options: const ['对', '错'],
answer: i % 6 == 0 ? '错' : '对',
analysis: '依据教材定义判断,答案为 ${i % 6 == 0 ? "错" : "对"}。',
),
// 3 → 解答题(文本输入)
_ => ExamQuestion(
questionId: i + 1,
type: QuestionType.essay,
title:
'<p><b>【$subject】</b> 第 ${i + 1} 题(解答)</p><p>$stem</p>',
options: const [],
answer: '水有三种状态:液态、固态、气态。',
analysis: '解答题需人工批改,参考答案:水有三种状态。',
),
};
});
}
}