341 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
			
		
		
	
	
			341 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Dart
		
	
	
| import 'package:flutter/material.dart';
 | |
| import 'package:qhd_prevention/customWidget/custom_button.dart';
 | |
| import 'package:qhd_prevention/customWidget/toast_util.dart';
 | |
| import 'package:qhd_prevention/pages/my_appbar.dart';
 | |
| import '../../../http/ApiService.dart';
 | |
| import '../../../tools/tools.dart'; // 替换为实际路径
 | |
| 
 | |
| class StudyPractisePage extends StatefulWidget {
 | |
|   final String videoCoursewareId;
 | |
| 
 | |
|   const StudyPractisePage({Key? key, required this.videoCoursewareId})
 | |
|     : super(key: key);
 | |
| 
 | |
|   @override
 | |
|   _PracticePageState createState() => _PracticePageState();
 | |
| }
 | |
| 
 | |
| class Question {
 | |
|   final String questionDry;
 | |
|   final String questionType; // '1','2','3','4'
 | |
|   final Map<String, String> options;
 | |
|   final String answer;
 | |
|   final String descr;
 | |
|   bool correctAnswerShow;
 | |
|   String checked;
 | |
| 
 | |
|   Question({
 | |
|     required this.questionDry,
 | |
|     required this.questionType,
 | |
|     required this.options,
 | |
|     required this.answer,
 | |
|     required this.descr,
 | |
|     this.correctAnswerShow = false,
 | |
|     this.checked = '',
 | |
|   });
 | |
| 
 | |
|   factory Question.fromJson(Map<String, dynamic> json) {
 | |
|     final type = json['QUESTIONTYPE'] as String;
 | |
|     Map<String, String> 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? ?? '';
 | |
|       }
 | |
|     }
 | |
|     return Question(
 | |
|       questionDry: json['QUESTIONDRY'] as String? ?? '',
 | |
|       questionType: type,
 | |
|       options: opts,
 | |
|       answer: json['ANSWER'] as String? ?? '',
 | |
|       descr: json['DESCR'] as String? ?? '',
 | |
|     );
 | |
|   }
 | |
| }
 | |
| 
 | |
| class _PracticePageState extends State<StudyPractisePage> {
 | |
|   int current = 0;
 | |
|   List<Question> options = [];
 | |
|   bool loading = true;
 | |
|   final Map<String, String> questionTypeMap = {
 | |
|     '1': '单选题',
 | |
|     '2': '多选题',
 | |
|     '3': '判断题',
 | |
|     '4': '填空题',
 | |
|   };
 | |
| 
 | |
|   @override
 | |
|   void initState() {
 | |
|     super.initState();
 | |
|     _getData();
 | |
|   }
 | |
| 
 | |
|   Future<void> _getData() async {
 | |
|     setState(() => loading = true);
 | |
|     final res = await ApiService.questionListByVideo(widget.videoCoursewareId);
 | |
|     if (res['result'] == 'success') {
 | |
|       List list = res['varList'] as List;
 | |
|       options = list.map((e) => Question.fromJson(e)).toList();
 | |
|     } else {
 | |
|       options = [];
 | |
|     }
 | |
|     setState(() => loading = false);
 | |
|   }
 | |
| 
 | |
|   void _chooseTopic(String type, String item) {
 | |
|     final q = options[current];
 | |
|     if (q.correctAnswerShow) return;
 | |
|     setState(() {
 | |
|       if (type == 'radio' || type == 'judge') {
 | |
|         q.checked = (q.checked == item) ? '' : item;
 | |
|         _correctAnswerShow();
 | |
|       } else if (type == 'multiple') {
 | |
|         List<String> arr = q.checked.isNotEmpty ? q.checked.split(',') : [];
 | |
|         if (arr.contains(item))
 | |
|           arr.remove(item);
 | |
|         else
 | |
|           arr.add(item);
 | |
|         arr.sort();
 | |
|         q.checked = arr.join(',');
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   void _correctAnswerShow() {
 | |
|     final q = options[current];
 | |
|     if (q.questionType == '2' && q.checked.split(',').length < 2) {
 | |
|       ToastUtil.showError(context, '多选题最少需要选择两个答案');
 | |
|       return;
 | |
|     }
 | |
|     if (q.checked.isNotEmpty) {
 | |
|       setState(() => q.correctAnswerShow = true);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Widget _buildOptions(Question q) {
 | |
|     switch (q.questionType) {
 | |
|       case '1':
 | |
|       case '3':
 | |
|         return Column(
 | |
|           children:
 | |
|               q.options.entries.map((e) {
 | |
|                 bool isChecked = q.checked == e.key;
 | |
|                 return _optionItem(
 | |
|                   label: e.key,
 | |
|                   text: e.value,
 | |
|                   active: !q.correctAnswerShow && isChecked,
 | |
|                   right: q.correctAnswerShow && q.answer == e.key && isChecked,
 | |
|                   err: q.correctAnswerShow && q.answer != e.key && isChecked,
 | |
|                   warning:
 | |
|                       q.correctAnswerShow && q.answer == e.key && !isChecked,
 | |
|                   onTap:
 | |
|                       () => _chooseTopic(
 | |
|                         q.questionType == '3' ? 'judge' : 'radio',
 | |
|                         e.key,
 | |
|                       ),
 | |
|                 );
 | |
|               }).toList(),
 | |
|         );
 | |
|       case '2':
 | |
|         return Column(
 | |
|           children: [
 | |
|             ...q.options.entries.map((e) {
 | |
|               bool isChecked = q.checked.split(',').contains(e.key);
 | |
|               bool isCorrect = q.answer.contains(e.key);
 | |
|               return _optionItem(
 | |
|                 label: e.key,
 | |
|                 text: e.value,
 | |
|                 multiple: true,
 | |
|                 active: !q.correctAnswerShow && isChecked,
 | |
|                 right: q.correctAnswerShow && isCorrect && isChecked,
 | |
|                 err: q.correctAnswerShow && !isCorrect && isChecked,
 | |
|                 warning: q.correctAnswerShow && isCorrect && !isChecked,
 | |
|                 onTap: () => _chooseTopic('multiple', e.key),
 | |
|               );
 | |
|             }),
 | |
|             if (!q.correctAnswerShow) ...[
 | |
|               const SizedBox(height: 10,),
 | |
|               Row(
 | |
|                 mainAxisAlignment: MainAxisAlignment.center,
 | |
|                 children: [
 | |
|                 CustomButton(text: '确认答案', backgroundColor: Colors.blue, onPressed: _correctAnswerShow,padding: EdgeInsets.symmetric(horizontal: 30, vertical: 5),)
 | |
|               ],)
 | |
|             ]
 | |
|           ],
 | |
|         );
 | |
|       case '4':
 | |
|         return TextField(
 | |
|           maxLength: 255,
 | |
|           onChanged: (v) => q.checked = v,
 | |
|           decoration: InputDecoration(
 | |
|             hintText: '请输入内容',
 | |
|             border: OutlineInputBorder(),
 | |
|           ),
 | |
|         );
 | |
|       default:
 | |
|         return SizedBox.shrink();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   Widget _optionItem({
 | |
|     required String label,
 | |
|     required String text,
 | |
|     bool active = false,
 | |
|     bool right = false,
 | |
|     bool err = false,
 | |
|     bool warning = false,
 | |
|     bool multiple = false,
 | |
|     required VoidCallback onTap,
 | |
|   }) {
 | |
|     Color fg = Colors.black87;
 | |
|     Color bg = Colors.grey.shade200;
 | |
|     Color hasTextColor = Colors.black54;
 | |
|     if (right) {
 | |
|       fg = Colors.green;
 | |
|       bg = Colors.green;
 | |
|       hasTextColor = Colors.white;
 | |
|     }
 | |
|     if (err) {
 | |
|       fg = Colors.red;
 | |
|       bg = Colors.red;
 | |
|       hasTextColor = Colors.white;
 | |
| 
 | |
|     }
 | |
|     if (warning) {
 | |
|       fg = Colors.green;
 | |
|       bg = Colors.green;
 | |
|       hasTextColor = Colors.white;
 | |
| 
 | |
|     }
 | |
|     if (active) fg = Colors.blue;
 | |
| 
 | |
|     return GestureDetector(
 | |
|       onTap: onTap,
 | |
|       child: Container(
 | |
|         margin: EdgeInsets.symmetric(vertical: 8),
 | |
|         child: Row(
 | |
|           children: [
 | |
|             Container(
 | |
|               width: 40,
 | |
|               height: 40,
 | |
|               alignment: Alignment.center,
 | |
|               decoration: BoxDecoration(
 | |
|                 color: active ? Colors.blue : bg,
 | |
|                 shape: BoxShape.circle,
 | |
|               ),
 | |
|               child:
 | |
|                   multiple
 | |
|                       ? (right
 | |
|                           ? Text(label, style: TextStyle(color: hasTextColor))
 | |
|                           : err
 | |
|                           ? Text(label, style: TextStyle(color: hasTextColor))
 | |
|                           : Text(label, style: TextStyle(color: active ? Colors.white:hasTextColor)))
 | |
|                       : Text(
 | |
|                         label,
 | |
|                         style: TextStyle(color: multiple ? fg : hasTextColor),
 | |
|                       ),
 | |
|             ),
 | |
|             SizedBox(width: 16),
 | |
|             Expanded(
 | |
|               child: Text(text, style: TextStyle(color: fg, fontSize: 16)),
 | |
|             ),
 | |
|           ],
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   String _renderAnswerText(Question q) {
 | |
|     if (q.questionType == '3') return q.checked == 'A' ? '对' : '错';
 | |
|     return q.checked;
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   @override
 | |
|   Widget build(BuildContext context) {
 | |
|     final q = options.isNotEmpty ? options[current] : null;
 | |
|     return Scaffold(
 | |
|       backgroundColor: Colors.white,
 | |
|       appBar: MyAppbar(title: '课后练习'),
 | |
|       body:
 | |
|           loading
 | |
|               ? Center(child: CircularProgressIndicator())
 | |
|               : options.isEmpty
 | |
|               ? NoDataWidget.show()
 | |
|               : Padding(
 | |
|                 padding: EdgeInsets.all(16),
 | |
|                 child: Column(
 | |
|                   crossAxisAlignment: CrossAxisAlignment.start,
 | |
|                   children: [
 | |
|                     Stack(
 | |
|                       children: [
 | |
|                         SizedBox(
 | |
|                           width: 1000,
 | |
|                           height: 60,
 | |
|                           child: Image.asset(
 | |
|                             'assets/study/bgimg1.png',
 | |
|                             fit: BoxFit.fill,
 | |
|                           ),
 | |
|                         ),
 | |
|                         SizedBox(
 | |
|                           height: 60,
 | |
|                           child: Center(
 | |
|                             child: Text(
 | |
|                               '当前试题 ${current + 1}/${options.length}',
 | |
|                               style: TextStyle(color: Colors.white, fontSize: 17, fontWeight: FontWeight.bold),
 | |
|                             ),
 | |
|                           ),
 | |
|                         )
 | |
|                       ],
 | |
|                     ),
 | |
|                     SizedBox(height: 16),
 | |
|                     if (q != null) ...[
 | |
|                       Text(
 | |
|                         '${current + 1}. ${q.questionDry} (${questionTypeMap[q.questionType]})',
 | |
|                         style: TextStyle(
 | |
|                           fontWeight: FontWeight.bold,
 | |
|                           fontSize: 18,
 | |
|                         ),
 | |
|                       ),
 | |
|                       SizedBox(height: 16),
 | |
|                       _buildOptions(q),
 | |
|                       if (q.correctAnswerShow) ...[
 | |
|                         Divider(),
 | |
|                         Text('我的答案: ${q.checked.split(',').join('')}'),
 | |
|                         Text('正确答案: ${q.answer}'),
 | |
|                         Text('权威解读: ${q.descr}'),
 | |
|                       ],
 | |
|                     ],
 | |
|                     Spacer(),
 | |
|                     Row(
 | |
|                       children: [
 | |
|                         if (current > 0)
 | |
|                           Expanded(
 | |
|                             child: CustomButton(
 | |
|                               text: '上一题',
 | |
|                               textStyle: TextStyle(color: Colors.black54),
 | |
|                               backgroundColor: Colors.grey.shade200,
 | |
|                               onPressed: () => setState(() => current--),
 | |
|                             ),
 | |
|                           ),
 | |
|                         if (current > 0 && current < options.length - 1)
 | |
|                           SizedBox(width: 16),
 | |
|                         if (current < options.length - 1)
 | |
|                           Expanded(
 | |
|                             child: CustomButton(
 | |
|                               text: '下一题',
 | |
|                               backgroundColor: Colors.blue,
 | |
|                               onPressed: () => setState(() => current++),
 | |
|                             ),
 | |
|                           ),
 | |
|                       ],
 | |
|                     ),
 | |
|                   ],
 | |
|                 ),
 | |
|               ),
 | |
|     );
 | |
|   }
 | |
| }
 |