import 'dart:async'; import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart'; import 'package:qhd_prevention/customWidget/custom_button.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/pages/home/study/study_detail_page.dart'; import 'package:qhd_prevention/pages/my_appbar.dart'; import 'package:qhd_prevention/http/ApiService.dart'; import 'dart:convert'; class Question { final Map rawData; final String questionDry; final String questionType; final Map options; String checked; Question({ required this.rawData, required this.questionDry, required this.questionType, required this.options, this.checked = '', }); factory Question.fromJson(Map json) { final type = json['QUESTIONTYPE'] as String? ?? '1'; final opts = {}; if (type == '1' || type == '2' || type == '3') { opts['A'] = json['OPTIONA'] as String? ?? ''; opts['B'] = json['OPTIONB'] as String? ?? ''; if (type != '3') { opts['C'] = json['OPTIONC'] as String? ?? ''; opts['D'] = json['OPTIOND'] as String? ?? ''; } } final raw = Map.from(json); return Question( rawData: raw, questionDry: json['QUESTIONDRY'] as String? ?? '', questionType: type, options: opts, ); } } class TakeExamPage extends StatefulWidget { const TakeExamPage({ required this.examInfo, required this.examType, super.key, }); final Map examInfo; final TakeExamType examType; @override State createState() => _TakeExamPageState(); } class _TakeExamPageState extends State { late final List questions; late final Map info; int current = 0; late int remainingSeconds; Timer? _timer; final questionTypeMap = { '1': '单选题', '2': '多选题', '3': '判断题', '4': '填空题', }; @override void initState() { super.initState(); info = widget.examInfo['pd'] as Map? ?? {}; final rawList = widget.examInfo['inputQue'] as List? ?? []; questions = rawList .map((e) => Question.fromJson(e as Map)) .toList(); final numberOfExams = widget.examInfo['NUMBEROFEXAMS'] as String? ?? '0'; WidgetsBinding.instance.addPostFrameCallback((_) { if (numberOfExams == '-9999') { _showTip('强化学习考试开始,限时${info['ANSWERSHEETTIME']}分钟,请注意答题时间!'); } else { _showTip('您无考试次数!'); } }); final minutes = info['ANSWERSHEETTIME'] as int? ?? 0; remainingSeconds = minutes * 60; _startTimer(); } @override void dispose() { _timer?.cancel(); super.dispose(); } Future _showTip(String content) { return showDialog( context: context, builder: (_) => CustomAlertDialog( title: '温馨提示', content: content, cancelText: '', confirmText: '确定', onConfirm: () {}, ), ); } bool _validateCurrentAnswer() { final q = questions[current]; if (q.checked.isEmpty) { _showTip('请对本题进行作答。'); return false; } if (q.questionType == '2' && q.checked.split(',').length < 2) { _showTip('多选题最少需要选择两个答案。'); return false; } return true; } void _startTimer() { _timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (remainingSeconds <= 0) { timer.cancel(); _onTimeUp(); } else { setState(() => remainingSeconds--); } }); } void _chooseTopic(String type, String key) { final q = questions[current]; if (type == 'radio' || type == 'judge') { q.checked = (q.checked == key) ? '' : key; } else { final arr = q.checked.isNotEmpty ? q.checked.split(',') : []; if (arr.contains(key)) arr.remove(key); else arr.add(key); arr.sort(); q.checked = arr.join(','); } setState(() {}); } void _nextQuestion() { if (!_validateCurrentAnswer()) return; if (current < questions.length - 1) setState(() => current++); } void _previousQuestion() { if (current > 0) setState(() => current--); } void _confirmSubmit() { if (!_validateCurrentAnswer()) return; showDialog( context: context, builder: (_) => CustomAlertDialog( title: '温馨提示', content: '请确认是否交卷!', cancelText: '取消', onCancel: () {}, onConfirm: _submit, ), ); } Future _submit() async { for (var q in questions) { if (q.questionType == '2') q.checked = q.checked.replaceAll(',', ''); q.rawData['checked'] = q.checked; } final data = { 'STAGEEXAMPAPERINPUT_ID': widget.examInfo['STRENGTHEN_PAPER_QUESTION_ID'], 'STUDENT_ID': widget.examInfo['STUDENT_ID'], 'CLASS_ID': widget.examInfo['CLASS_ID'], 'NUMBEROFEXAMS': widget.examInfo['NUMBEROFEXAMS'], 'entrySite': widget.examType.name, 'PASSSCORE': info['PASSSCORE'], 'EXAMSCORE': info['EXAMSCORE'], 'EXAMTIMEBEGIN': info['EXAMTIMEBEGIN'], 'options': jsonEncode(questions.map((q) => q.rawData).toList()), }; final res = await ApiService.submitExam(data); if (res['result'] == 'success') { final score = res['examScore'] ?? '0'; final passed = res['examResult'] != '0'; showDialog( context: context, builder: (_) => CustomAlertDialog( title: '温馨提示', content: passed ? '您的成绩为 $score 分,恭喜您通过本次考试,请继续保持!' : '您的成绩为 $score 分,很遗憾您没有通过本次考试,请再接再厉!', cancelText: '', confirmText: '确定', onConfirm: () { Navigator.pop(context); }, ), ); } } void _onTimeUp() { ToastUtil.showError(context, '考试时间已结束'); _submit(); } Widget _buildOptions(Question q) { if (q.questionType == '4') { return TextField( controller: TextEditingController(text: q.checked), onChanged: (val) => q.checked = val, maxLength: 255, decoration: const InputDecoration( hintText: '请输入内容', border: OutlineInputBorder(), ), ); } final keys = q.questionType == '3' ? ['A', 'B'] : ['A', 'B', 'C', 'D']; return Column( children: keys.map((key) { final active = q.checked.split(',').contains(key); return GestureDetector( onTap: () => _chooseTopic( q.questionType == '3' ? 'judge' : (q.questionType == '2' ? 'multiple' : 'radio'), key, ), child: Container( margin: const EdgeInsets.symmetric(vertical: 8), child: Row( children: [ Container( width: 40, height: 40, alignment: Alignment.center, decoration: BoxDecoration( color: active ? Colors.blue : Colors.grey.shade200, shape: BoxShape.circle, ), child: Text( key, style: TextStyle( color: active ? Colors.white : Colors.black87, ), ), ), const SizedBox(width: 16), Expanded( child: Text( q.options[key] ?? '', style: const TextStyle(fontSize: 16), ), ), ], ), ), ); }).toList(), ); } String get _formattedTime { final m = remainingSeconds ~/ 60; final s = remainingSeconds % 60; return '${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}'; } @override Widget build(BuildContext context) { final q = questions.isNotEmpty ? questions[current] : null; return PopScope( canPop: false, // 禁用返回 child: Scaffold( appBar: const MyAppbar(title: '课程考试', isBack: false,), body: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack( children: [ Container( width: double.infinity, height: 120, decoration: BoxDecoration( image: const DecorationImage( image: AssetImage('assets/study/bgimg1.png'), fit: BoxFit.cover, ), borderRadius: BorderRadius.circular(8), ), ), Positioned.fill( child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( '考试科目:${info['EXAMNAME'] ?? ''}', style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Text( '当前试题 ${current + 1}/${questions.length}', style: const TextStyle( color: Colors.white, fontSize: 16, ), ), const SizedBox(height: 8), Text( '考试剩余时间:$_formattedTime', style: const TextStyle( color: Colors.white, fontSize: 16, ), ), ], ), ), ), ], ), const SizedBox(height: 16), if (q != null) ...[ Text( '${current + 1}. ${q.questionDry} (${questionTypeMap[q.questionType] ?? ''})', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18, ), ), const SizedBox(height: 16), _buildOptions(q), ], const Spacer(), Row( children: [ if (current > 0) Expanded( child: CustomButton( text: '上一题', backgroundColor: Colors.grey.shade200, textStyle: const TextStyle(color: Colors.black54), onPressed: _previousQuestion, ), ), if (current > 0 && current < questions.length - 1) const SizedBox(width: 16), if (current < questions.length - 1) Expanded( child: CustomButton( text: '下一题', backgroundColor: Colors.blue, onPressed: _nextQuestion, ), ), if (current == questions.length - 1) Expanded( child: CustomButton( text: '交卷', backgroundColor: Colors.blue, onPressed: _confirmSubmit, ), ), ], ), ], ), ), ), ); } }