修改iOS 权限弹框内容

This commit is contained in:
chenshuainan
2026-09-08 15:17:35 +08:00
parent d5f73cfbb5
commit 44fb5f86e2
4 changed files with 1312 additions and 417 deletions
+6
View File
@@ -12,6 +12,7 @@ import 'package:new_learn/features/class/my_interact_page.dart';
import 'package:new_learn/features/class/my_update_detail_page.dart';
import 'package:new_learn/core/api/class/model.dart';
import 'package:new_learn/features/my/my_page.dart';
import 'package:new_learn/features/conquerPackage/captureList/index.dart';
import 'package:new_learn/features/my/my_point/my_point_page.dart';
import 'package:new_learn/features/recite/recite_page.dart';
import 'package:new_learn/features/task/task_page.dart';
@@ -144,6 +145,11 @@ final GoRouter appRouter = GoRouter(
),
// ---------- 登录页 ----------
GoRoute(path: '/login', builder: (_, _) => const LoginPage()),
// ---------- 攻克包 - 截图名单 ----------
GoRoute(
path: '/conquerPackage/captureList',
builder: (_, _) => const CaptureListPage(),
),
// app_router.dart 的 routes 里加,不用管跳转逻辑
// GoRoute(path: '/task/:id', builder: (_, _) => const TaskDetailPage()),
// ---------- 以后新业务页在这里加,一行一个 ----------
@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class CaptureListPage extends StatelessWidget {
const CaptureListPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Align 对齐布局 Demo11111')),
// ---------- 下方即为核心代码,可直接复制到 Scaffold body ----------
body: Container(
width: 300.w,
height: 300.h,
color: Colors.grey[300],
),
);
}
}
@@ -0,0 +1,959 @@
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),
),
],
),
);
}
}
+329 -417
View File
File diff suppressed because it is too large Load Diff