This commit is contained in:
chenshuainan
2026-09-28 20:12:34 +08:00
parent 847986f861
commit 5f53bd8f94
60 changed files with 14624 additions and 398 deletions
@@ -0,0 +1,244 @@
/// ============================================================
/// 【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);
}
}