Files
App.K12.Student.Miniapp_Flu…/lib/features/conquerPackage/captureList/index.dart
T

960 lines
30 KiB
Dart
Raw 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.
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:new_learn/core/utils/img_url.dart';
/// ============================================================
/// 【CaptureListPage】攻克列表页 —— 对标 Vue pages/conquerPackage/captureList/index.vue
///
/// 一、Vue 源结构:
/// HeaderBac + NavBar(title: "攻克列表", bg-color: #ADD1FF00)
/// ├── rule-view 攻克规则说明卡片(remark 富文本)
/// └── capture-list-refresh-wrap
/// └── up-pull-refresh
/// ├── captureList 攻克项列表
/// │ └── capture-item
/// │ ├── capture-info (左:标题+学科标签;右:攻克标准百分比)
/// │ └── capture-progress
/// │ ├── progress-top (进度条 + 右侧按钮)
/// │ └── capture-progress-info (展开的历次攻克详情)
/// └── DataEmpty 空状态
///
/// 二、换算规则(本工程统一):Vue 的 `N rpx` → Flutter 的 `(N/2).w/.h/.sp/.r`
/// 例:间距 28rpx → 14.w;圆角 32rpx → 16.r;字号 28rpx → 14.sp
///
/// 三、状态(StatefulWidget):
/// - remark: 规则说明文字
/// - data: 攻克包列表
/// - refreshing: 下拉刷新状态
/// - isDataEmpty: 空数据标记
///
/// 四、接口暂未对接,使用 Mock 数据展示页面效果。
/// ============================================================
/// 攻克记录状态枚举
enum CaptureRecordStatus {
none(0), // 未开始
success(1), // 成功
fail(2), // 失败
processing(3); // 判题中
final int value;
const CaptureRecordStatus(this.value);
factory CaptureRecordStatus.fromInt(int? v) {
return CaptureRecordStatus.values.firstWhere(
(e) => e.value == v,
orElse: () => CaptureRecordStatus.none,
);
}
}
/// 攻克包状态
enum CapturePackStatus {
notStarted(0), // 未开始
inProgress(1), // 进行中
conquered(2), // 已攻克
failed(3), // 攻克失败
laterStudy(4), // 后续学习
judging(5); // 判题中
final int value;
const CapturePackStatus(this.value);
factory CapturePackStatus.fromInt(int? v) {
return CapturePackStatus.values.firstWhere(
(e) => e.value == v,
orElse: () => CapturePackStatus.notStarted,
);
}
}
/// 攻克记录模型
class CaptureRecord {
final String id;
final CaptureRecordStatus status;
final int? accuracyRate; // 正确率百分比
final int? overcomeTimeSecond; // 攻克用时(秒)
const CaptureRecord({
required this.id,
required this.status,
this.accuracyRate,
this.overcomeTimeSecond,
});
}
/// 攻克包模型
class CapturePack {
final String id;
final String name;
final String baseSubjectName;
final int overcomeStandard; // 攻克标准百分比
final int maxOvercomeCount; // 最大攻克次数
final int recordCount; // 已进行次数
final CapturePackStatus status;
final List<CaptureRecord> records;
bool isShowCaptureInfo; // 是否展开详情
CapturePack({
required this.id,
required this.name,
required this.baseSubjectName,
required this.overcomeStandard,
required this.maxOvercomeCount,
required this.recordCount,
required this.status,
required this.records,
this.isShowCaptureInfo = false,
});
}
class CaptureListPage extends StatefulWidget {
const CaptureListPage({super.key});
@override
State<CaptureListPage> createState() => _CaptureListPageState();
}
class _CaptureListPageState extends State<CaptureListPage> {
// ========== 颜色常量 ==========
static const Color _bgColor = Color(0xFFF5F8FA);
static const Color _titleColor = Color(0xFF1D2B47);
static const Color _subtitleColor = Color(0xFF495A7A);
static const Color _primary = Color(0xFF3D7DF5);
static const Color _success = Color(0xFF00CC96);
static const Color _error = Color(0xFFFF4D4D);
static const Color _processing = Color(0xFFF5963D);
static const Color _barBg = Color(0xFFE3ECFF);
static const Color _mutedText = Color(0xFF687FAD);
// ========== 状态 ==========
String _remark = '';
List<CapturePack> _data = [];
bool _refreshing = false;
bool _isDataEmpty = false;
@override
void initState() {
super.initState();
_loadMockData();
}
/// Mock 数据 —— 替代真实接口,仅用于展示页面效果
void _loadMockData() {
_remark = '1. 每次攻克需达到指定正确率才算成功;\n'
'2. 攻克失败可重新挑战,最多可挑战 N 次;\n'
'3. 判题中状态下不能进行操作;\n'
'4. 详情可查看每次攻克的用时与正确率。';
_data = [
CapturePack(
id: '1',
name: '一元一次方程入门',
baseSubjectName: '数学',
overcomeStandard: 80,
maxOvercomeCount: 3,
recordCount: 2,
status: CapturePackStatus.inProgress,
records: [
CaptureRecord(
id: 'r1-1',
status: CaptureRecordStatus.fail,
accuracyRate: 60,
overcomeTimeSecond: 480,
),
CaptureRecord(
id: 'r1-2',
status: CaptureRecordStatus.success,
accuracyRate: 90,
overcomeTimeSecond: 600,
),
],
isShowCaptureInfo: true,
),
CapturePack(
id: '2',
name: '英语单词 Unit 3',
baseSubjectName: '英语',
overcomeStandard: 70,
maxOvercomeCount: 5,
recordCount: 0,
status: CapturePackStatus.notStarted,
records: [],
),
CapturePack(
id: '3',
name: '文言文翻译技巧',
baseSubjectName: '语文',
overcomeStandard: 75,
maxOvercomeCount: 3,
recordCount: 3,
status: CapturePackStatus.conquered,
records: [
CaptureRecord(
id: 'r3-1',
status: CaptureRecordStatus.fail,
accuracyRate: 50,
overcomeTimeSecond: 300,
),
CaptureRecord(
id: 'r3-2',
status: CaptureRecordStatus.fail,
accuracyRate: 65,
overcomeTimeSecond: 420,
),
CaptureRecord(
id: 'r3-3',
status: CaptureRecordStatus.success,
accuracyRate: 88,
overcomeTimeSecond: 540,
),
],
),
CapturePack(
id: '4',
name: '物理力学综合',
baseSubjectName: '物理',
overcomeStandard: 85,
maxOvercomeCount: 3,
recordCount: 1,
status: CapturePackStatus.judging,
records: [
CaptureRecord(
id: 'r4-1',
status: CaptureRecordStatus.processing,
accuracyRate: null,
overcomeTimeSecond: 700,
),
],
isShowCaptureInfo: true,
),
CapturePack(
id: '5',
name: '化学方程式配平',
baseSubjectName: '化学',
overcomeStandard: 80,
maxOvercomeCount: 3,
recordCount: 3,
status: CapturePackStatus.failed,
records: [
CaptureRecord(
id: 'r5-1',
status: CaptureRecordStatus.fail,
accuracyRate: 40,
overcomeTimeSecond: 200,
),
CaptureRecord(
id: 'r5-2',
status: CaptureRecordStatus.fail,
accuracyRate: 55,
overcomeTimeSecond: 300,
),
CaptureRecord(
id: 'r5-3',
status: CaptureRecordStatus.fail,
accuracyRate: 60,
overcomeTimeSecond: 400,
),
],
),
CapturePack(
id: '6',
name: '历史时间轴梳理',
baseSubjectName: '历史',
overcomeStandard: 70,
maxOvercomeCount: 3,
recordCount: 2,
status: CapturePackStatus.laterStudy,
records: [
CaptureRecord(
id: 'r6-1',
status: CaptureRecordStatus.success,
accuracyRate: 80,
overcomeTimeSecond: 360,
),
CaptureRecord(
id: 'r6-2',
status: CaptureRecordStatus.success,
accuracyRate: 95,
overcomeTimeSecond: 300,
),
],
),
];
_isDataEmpty = _data.isEmpty;
}
/// 下拉刷新
Future<void> _onRefresh() async {
if (_refreshing) return;
setState(() => _refreshing = true);
// 模拟网络请求延迟
await Future.delayed(const Duration(milliseconds: 800));
if (mounted) {
setState(() {
_loadMockData();
_refreshing = false;
});
}
}
/// 切换攻克详情展开/收起
void _toggleCaptureInfo(CapturePack item) {
final validRecords = item.records.where((r) => r.status != CaptureRecordStatus.none).toList();
if (validRecords.isEmpty) return;
setState(() {
item.isShowCaptureInfo = !item.isShowCaptureInfo;
});
}
/// 点击按钮跳转(占位,不接接口)
void _navigateToCaptureInfo(CapturePack item) {
if (item.status == CapturePackStatus.judging) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('该攻克正在判题中,暂不能操作'), behavior: SnackBarBehavior.floating),
);
return;
}
// TODO(业务逻辑): 对接 GetStartOvercome 接口后跳转
}
/// 点击报告(占位,不接接口)
void _getReport(CaptureRecord record, CapturePack pack) {
if (record.status == CaptureRecordStatus.processing) return;
// TODO(业务逻辑): 跳转报告页
}
/// 数字转中文(1-9)
String _numberToChinese(int num) {
const chineseNum = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
return chineseNum[num.clamp(0, 9)];
}
/// 秒数格式化:mm分ss秒 / h时mm分
String _formatDuration(int seconds) {
if (seconds <= 0) return '0秒';
final h = seconds ~/ 3600;
final m = (seconds % 3600) ~/ 60;
final s = seconds % 60;
if (h > 0) return '${h}时${m}分';
if (m > 0) return '${m}分${s}秒';
return '${s}秒';
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _bgColor,
body: Stack(
children: [
// 页面底色
const Positioned.fill(child: ColoredBox(color: _bgColor)),
// 顶部背景图(对标 HeaderBac,bg-size: 100% auto → width 100%,高度自适应)
Positioned(
top: 0,
left: 0,
right: 0,
child: CachedNetworkImage(
imageUrl: ImgUrl.build(
'/StaticResource/2026-05-26/my-header-bg-default1779775984375.png',
),
fit: BoxFit.fitWidth,
width: double.infinity,
),
),
// 主内容(安全区内,含透明 AppBar + 规则卡片 + 列表)
SafeArea(
child: Column(
children: [
// 顶部导航栏(对标 NavBar,bg-color 透明,白色文字)
SizedBox(
height: 44.h,
child: Stack(
children: [
const Center(
child: Text(
'攻克列表',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
),
Positioned(
left: 16.w,
top: 0,
bottom: 0,
child: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.white, size: 20),
onPressed: () => Navigator.of(context).maybePop(),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
),
],
),
),
// 规则卡片(透明底 + 白边,置于渐变背景之上)
_buildRuleView(),
SizedBox(height: 19.h),
// 列表区域
Expanded(
child: RefreshIndicator(
onRefresh: _onRefresh,
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
if (_isDataEmpty)
SliverFillRemaining(child: _buildEmpty())
else
SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 14.w),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => _buildCaptureItem(_data[index]),
childCount: _data.length,
),
),
),
SliverToBoxAdapter(child: SizedBox(height: 20.h)),
],
),
),
),
],
),
),
],
),
);
}
// ============================================================
// 子组件构建方法
// ============================================================
/// 攻克规则说明卡片(透明底 + 白色边框 + 阴影,对标 Vue rule-view)
Widget _buildRuleView() {
return Container(
margin: EdgeInsets.fromLTRB(14.w, 16.h, 14.w, 0),
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 16.h),
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(16.r),
border: Border.all(color: Colors.white, width: 1.w),
boxShadow: [
BoxShadow(
color: const Color.fromARGB(255, 255, 255, 255).withOpacity(0.05),
blurRadius: 20.r,
offset: Offset(0, 10.h),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildRuleTitle(),
SizedBox(height: 16.h),
_buildRuleContent(),
],
),
);
}
Widget _buildRuleTitle() {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CachedNetworkImage(
imageUrl: ImgUrl.build('/StaticResource/2026-04-10/capture-list-rule.png'),
width: 18.w,
height: 18.w,
fit: BoxFit.fitWidth,
),
SizedBox(width: 6.w),
Stack(
clipBehavior: Clip.none,
children: [
Padding(
padding: EdgeInsets.only(bottom: 2.h),
child: Text(
'攻克规则说明',
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.bold,
color: _titleColor,
),
),
),
Positioned(
bottom: -1.h,
right: 0,
child: Container(
width: 59.5.w,
height: 9.h,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [
Color(0x003D7DF5),
Color(0x331D9CFF),
Color(0x808AECFD),
],
),
borderRadius: BorderRadius.circular(50.r),
),
),
),
],
),
],
);
}
Widget _buildRuleContent() {
final lines = _remark.split('\n');
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: lines.asMap().entries.map((entry) {
final isLast = entry.key == lines.length - 1;
return Padding(
padding: EdgeInsets.only(bottom: isLast ? 0 : 10.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 4.w,
height: 4.w,
margin: EdgeInsets.fromLTRB(6.5.w, 7.5.h, 13.w, 0),
decoration: const BoxDecoration(
color: _primary,
shape: BoxShape.circle,
),
),
Expanded(
child: Text(
entry.value,
style: TextStyle(fontSize: 14.sp, color: _subtitleColor, height: 1.5),
),
),
],
),
);
}).toList(),
);
}
/// 空状态
Widget _buildEmpty() {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CachedNetworkImage(
imageUrl: ImgUrl.build('/StaticResource/2026-04-11/noCoursesss.png'),
width: 200.w,
height: 200.w,
),
SizedBox(height: 16.h),
Text('暂无攻克', style: TextStyle(fontSize: 14.sp, color: _mutedText)),
],
),
);
}
/// 攻克项卡片
Widget _buildCaptureItem(CapturePack item) {
return Container(
margin: EdgeInsets.only(bottom: 14.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.r),
border: Border.all(color: Colors.white, width: 1.w),
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFFFAFEFF), Color(0xFFEDF7FF)],
),
),
child: Column(
children: [
_buildCaptureInfo(item),
_buildCaptureProgress(item),
],
),
);
}
/// 攻克项头部信息
Widget _buildCaptureInfo(CapturePack item) {
return Padding(
padding: EdgeInsets.fromLTRB(14.w, 18.5.h, 14.w, 0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 4,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildCaptureTitle(item.name),
SizedBox(height: 10.h),
_buildSubjectTag(item.baseSubjectName),
],
),
),
SizedBox(width: 10.w),
_buildStandardBox(item.overcomeStandard),
],
),
);
}
Widget _buildCaptureTitle(String name) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 3.w,
height: 14.h,
margin: EdgeInsets.only(right: 7.5.w),
decoration: BoxDecoration(
color: _primary,
borderRadius: BorderRadius.circular(10.r),
),
),
Expanded(
child: Text(
name,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: _titleColor,
),
),
),
],
);
}
Widget _buildSubjectTag(String subject) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 1.w, vertical: 1.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.r),
gradient: LinearGradient(
colors: const [Color(0xFF5A9EFF), Color(0xFFF2F8FF), Color(0xFF5A9EFF)],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 2.h),
decoration: BoxDecoration(
color: const Color(0xFFE5F0FF),
borderRadius: BorderRadius.circular(4.r),
),
child: Text(
subject,
style: TextStyle(
fontSize: 12.sp,
color: _primary,
fontWeight: FontWeight.w500,
),
),
),
);
}
/// 右侧攻克标准百分比盒子
Widget _buildStandardBox(int standard) {
return Container(
width: 60.w,
height: 51.h,
padding: EdgeInsets.all(2.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.r),
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF60D0C0), Color(0xFF6EE9DB)],
),
),
child: Column(
children: [
Expanded(
flex: 1,
child: Center(
child: Text(
'攻克标准',
style: TextStyle(fontSize: 12.sp, color: Colors.white),
),
),
),
Container(
padding: EdgeInsets.symmetric(vertical: 1.5.h, horizontal: 4.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(6.r),
bottomRight: Radius.circular(6.r),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
CachedNetworkImage(
imageUrl: ImgUrl.build('/StaticResource/2026-04-10/capture-list-success.png'),
width: 14.w,
height: 14.w,
fit: BoxFit.fitWidth,
),
SizedBox(width: 5.5.w),
Text(
'$standard%',
style: TextStyle(fontSize: 15.sp, color: _success, fontWeight: FontWeight.bold),
),
],
),
),
],
),
);
}
/// 进度 + 按钮 + 详情展开区
Widget _buildCaptureProgress(CapturePack item) {
return Container(
margin: EdgeInsets.only(top: 10.5.h),
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 16.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.r),
),
child: Column(
children: [
_buildProgressTop(item),
if (item.isShowCaptureInfo) _buildProgressInfo(item),
],
),
);
}
Widget _buildProgressTop(CapturePack item) {
return Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => _toggleCaptureInfo(item),
behavior: HitTestBehavior.opaque,
child: Row(
children: [
Text('攻克次数', style: TextStyle(fontSize: 12.sp, color: _mutedText)),
SizedBox(width: 10.w),
_buildProgressBars(item),
SizedBox(width: 10.w),
_buildProgressCount(item),
],
),
),
),
_buildButtonGroup(item),
],
);
}
Widget _buildProgressBars(CapturePack item) {
return Row(
children: List.generate(item.maxOvercomeCount, (idx) {
final record = idx < item.records.length ? item.records[idx] : null;
Color color;
switch (record?.status ?? CaptureRecordStatus.none) {
case CaptureRecordStatus.success:
color = _success;
break;
case CaptureRecordStatus.fail:
color = _error;
break;
case CaptureRecordStatus.processing:
color = _processing;
break;
default:
color = _barBg;
}
return Container(
width: 20.w,
height: 4.h,
margin: EdgeInsets.only(right: idx == item.maxOvercomeCount - 1 ? 0 : 4.w),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(4.r),
),
);
}),
);
}
Widget _buildProgressCount(CapturePack item) {
final hasRecords = item.recordCount > 0 && item.records.isNotEmpty;
return Row(
children: [
Text(
'${item.recordCount}/${item.maxOvercomeCount}',
style: TextStyle(fontSize: 12.sp, color: _mutedText),
),
if (hasRecords)
Padding(
padding: EdgeInsets.only(left: 4.5.w),
child: Icon(
item.isShowCaptureInfo ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
size: 12.sp,
color: _mutedText,
),
),
],
);
}
/// 右侧按钮组(根据状态不同显示)
Widget _buildButtonGroup(CapturePack item) {
switch (item.status) {
case CapturePackStatus.notStarted:
return _buildOutlineButton('开始攻克', _primary, () => _navigateToCaptureInfo(item));
case CapturePackStatus.inProgress:
final text = item.records.isNotEmpty ? '继续攻克' : '开始攻克';
return _buildOutlineButton(text, _primary, () => _navigateToCaptureInfo(item));
case CapturePackStatus.conquered:
return _buildOutlineButton('已攻克', _success, null, opacity: 0.5);
case CapturePackStatus.failed:
return _buildOutlineButton('攻克失败', _error, () => _navigateToCaptureInfo(item), opacity: 0.5);
case CapturePackStatus.laterStudy:
return Row(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: () => _navigateToCaptureInfo(item),
child: Text(
'后续学习',
style: TextStyle(fontSize: 11.sp, color: _primary),
),
),
SizedBox(width: 8.w),
_buildOutlineButton('重新攻克', _primary, () => _navigateToCaptureInfo(item)),
],
);
case CapturePackStatus.judging:
return _buildOutlineButton('判题中', _success, null, opacity: 0.5);
}
}
Widget _buildOutlineButton(String text, Color color, VoidCallback? onTap, {double opacity = 1.0}) {
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: Opacity(
opacity: opacity,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h),
height: 30.h,
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border.all(color: color, width: 1.w),
borderRadius: BorderRadius.circular(30.r),
color: onTap == null ? Colors.transparent : color.withOpacity(0.08),
),
child: Text(
text,
style: TextStyle(fontSize: 13.sp, color: color, fontWeight: FontWeight.w500),
),
),
),
);
}
/// 展开的历次攻克详情
Widget _buildProgressInfo(CapturePack item) {
final validRecords = item.records.where((r) => r.status != CaptureRecordStatus.none).toList();
if (validRecords.isEmpty) return const SizedBox.shrink();
return Container(
margin: EdgeInsets.only(top: 16.h),
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.5.h),
decoration: BoxDecoration(
color: const Color(0xFFFAFBFD),
borderRadius: BorderRadius.circular(6.r),
border: Border.all(color: _barBg, width: 0.5.w),
),
child: Column(
children: validRecords.asMap().entries.map((entry) {
final infoIndex = entry.key;
final record = entry.value;
return GestureDetector(
onTap: () => _getReport(record, item),
behavior: HitTestBehavior.opaque,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 5.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
'第${_numberToChinese(infoIndex + 1)}次攻克',
style: TextStyle(fontSize: 12.sp, color: _mutedText),
),
SizedBox(width: 13.w),
Text(
_formatDuration(record.overcomeTimeSecond ?? 0),
style: TextStyle(fontSize: 12.sp, color: _subtitleColor),
),
],
),
if (record.status == CaptureRecordStatus.processing)
Text('判题中', style: TextStyle(fontSize: 12.sp, color: _processing))
else
_buildInfoRight(record, item),
],
),
),
);
}).toList(),
),
);
}
Widget _buildInfoRight(CaptureRecord record, CapturePack item) {
final isSuccess = (record.accuracyRate ?? 0) >= item.overcomeStandard;
final bgColor = isSuccess ? const Color(0xFFE8FAF6) : const Color(0xFFFFF0F0);
final textColor = isSuccess ? _success : _error;
final iconPath = isSuccess
? '/StaticResource/2026-04-22/capture-list-success.png'
: '/StaticResource/2026-04-22/capture-list-fail.png';
return Container(
padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 1.5.h),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(4.r),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
CachedNetworkImage(
imageUrl: ImgUrl.build(iconPath),
width: 14.w,
height: 14.w,
fit: BoxFit.fitWidth,
),
SizedBox(width: 5.5.w),
Text(
'${record.accuracyRate ?? 0}%',
style: TextStyle(fontSize: 12.sp, color: textColor, fontWeight: FontWeight.w500),
),
],
),
);
}
}