Files
2026-09-28 20:12:34 +08:00

245 lines
8.5 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.
/// ============================================================
/// 【ObjectiveAnswerView】客观题答案对比卡片
/// (1:1 迁移 Vue components/AnswerView.vue 的 type == 'objective' 分支;
/// 本页只使用客观题形态)
///
/// 视觉:
/// 218rpx 卡片:上行作答状态图标+文案(10 正确 / 11 错误 / 12 部分正确 /
/// 0 未作答),下行白色对比块:"正确答案:X / 你的答案:Y"
/// ============================================================
library;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:new_learn/core/components/rich_text_content.dart';
import 'package:new_learn/core/utils/img_url.dart';
import 'package:new_learn/core/utils/question/question_parser.dart';
import 'exercise_styles.dart';
class ObjectiveAnswerView extends StatelessWidget {
const ObjectiveAnswerView({
super.key,
required this.answerStatus,
this.userAnswer = '',
this.correctAnswer = '',
});
/// 0 未作答 / 10 正确 / 11 错误 / 12 部分正确
final dynamic answerStatus;
final String userAnswer;
final String correctAnswer;
int get _status => int.tryParse('$answerStatus') ?? 0;
bool get _isCorrect => _status == 10;
/// 客观题文字展示清洗(对标 formatOptionText),仅用于判空与纯文本展示
String _formatOptionText(dynamic ans) {
if (ans == null) return '';
String str;
if (ans is List) {
str = ans.join();
} else if (ans is Map) {
str = ans.toString();
if (str == '{}' || str == '[]') return '';
} else {
str = '$ans';
}
final trimmed = str.trim();
if (trimmed.isEmpty || trimmed == '[]' || trimmed == '{}' ||
trimmed == '""' || trimmed == "''") {
return '';
}
var result = trimmed.replaceAll(RegExp(r'<[^>]+>'), '');
result = result.replaceAll('&nbsp;', ' ');
result = result.replaceAll(RegExp(r'&[a-z]+;'), '');
result = result.replaceAll(RegExp(r'\s+'), ' ');
result = result.replaceAll(RegExp(r'^(\d+[\.\s、)\s]+|第\d+题)'), '');
result = result.replaceAll(',', '');
return result.trim();
}
/// 清洗后的答案 HTML(富文本渲染用)
String _rendered(String html) => cleanRichText(html);
bool _hasFormula(String html) =>
RegExp(r'math-formula|katex').hasMatch(html);
@override
Widget build(BuildContext context) {
final correctText = _formatOptionText(correctAnswer);
final userText = _formatOptionText(userAnswer);
final correctDisplay = correctText.isEmpty ? '无' : correctText;
final userDisplay = userText.isEmpty ? '未作答' : userText;
final statusText = _status == 10
? '回答正确'
: _status == 11
? '回答错误'
: _status == 12
? '部分正确'
: '未作答';
final accent = _isCorrect ? ExerciseColors.correct : ExerciseColors.error;
final correctHtml = _rendered(correctAnswer);
final userHtml = _rendered(userAnswer);
return Container(
height: 109.h, // 218rpx
decoration: BoxDecoration(
// CSS 外层:实色 padding-box + 180deg 渐变 border-box
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: _isCorrect
? const [Color(0x8000CC96), Color(0x1A00CC96), Color(0x8000CC96)]
: const [Color(0x80FF4C4C), Color(0x1AFF4C4C), Color(0x80FF4C4C)],
),
borderRadius: BorderRadius.circular(12.r),
),
child: Container(
margin: EdgeInsets.all(1.w),
decoration: BoxDecoration(
color: _isCorrect ? const Color(0xFFEBFFFA) : const Color(0xFFFFF0F0),
borderRadius: BorderRadius.circular(11.r),
),
child: Column(
children: [
// 第一行:状态提示
Padding(
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 14.h),
child: Row(
children: [
CachedNetworkImage(
imageUrl: ImgUrl.build(
_isCorrect
? '/StaticResource/2026-06-01/correct-icon1780284329572.png'
: '/StaticResource/2026-06-01/error-icon1780284292405.png',
),
width: 16.w,
height: 16.w,
),
SizedBox(width: 6.w),
Text(
statusText,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: accent,
),
),
],
),
),
// 第二行:答案对比
Expanded(
child: Container(
margin: EdgeInsets.fromLTRB(1.w, 0, 1.w, 1.w),
padding: EdgeInsets.fromLTRB(14.w, 0, 26.w, 0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10.r),
),
child: Row(
children: [
Expanded(
child: _CompareItem(
label: '正确答案:',
child: _hasFormula(correctHtml)
? RichTextContent(
html: correctHtml,
textStyle: TextStyle(
fontSize: 14.sp,
color: const Color(0xFF333333),
fontWeight: FontWeight.bold,
),
)
: Text(
correctDisplay,
style: TextStyle(
fontSize: 14.sp,
color: const Color(0xFF333333),
fontWeight: FontWeight.bold,
),
),
),
),
Expanded(
child: _CompareItem(
alignEnd: true,
label: '你的答案:',
child: _hasFormula(userHtml)
? RichTextContent(
html: userHtml,
textStyle: TextStyle(
fontSize: 14.sp,
color: _isCorrect
? ExerciseColors.correct
: (userText.isEmpty
? const Color(0xFF999999)
: ExerciseColors.error),
fontWeight: FontWeight.bold,
),
)
: Text(
userDisplay,
style: TextStyle(
fontSize: 14.sp,
color: _isCorrect
? ExerciseColors.correct
: (userText.isEmpty
? const Color(0xFF999999)
: ExerciseColors.error),
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
),
],
),
),
);
}
}
class _CompareItem extends StatelessWidget {
const _CompareItem({
required this.label,
required this.child,
this.alignEnd = false,
});
final String label;
final Widget child;
final bool alignEnd;
@override
Widget build(BuildContext context) {
final row = Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(
label,
style: TextStyle(fontSize: 14.sp, color: ExerciseColors.subtitle),
),
),
Flexible(child: child),
],
);
return alignEnd
? Align(alignment: Alignment.centerRight, child: row)
: Align(alignment: Alignment.centerLeft, child: row);
}
}