更新
This commit is contained in:
@@ -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(' ', ' ');
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user