feat(my): 新增错题本和我的收藏页面,重构学科标签组件
1. 为我的页面添加错题本和我的收藏跳转路由,指向共用的QuestionListPage 2. 抽离全局学科标签组件,移除class模块内的重复实现 3. 新增用户收藏/错题本接口层和数据模型 4. 完成QuestionListPage分页列表页实现,支持学科筛选和游标分页 5. 调整考试页面卡片布局和图标偏移参数
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
import 'package:new_learn/core/api/user_collect/model.dart';
|
||||||
|
import 'package:new_learn/core/network/dio_client.dart';
|
||||||
|
|
||||||
|
/// ============================================================
|
||||||
|
/// 【user_collect 模块接口】我的收藏 + 错题本 —— 对标 Vue3 端 api/userCollect/index.ts
|
||||||
|
///
|
||||||
|
/// getSubjects —— 学科筛选项(顶部 Tab)
|
||||||
|
/// GET /api/v1/UserCollect/GetUserCollectSubject/{type}
|
||||||
|
///
|
||||||
|
/// getCollectList —— 收藏/错题分页列表(游标分页)
|
||||||
|
/// GET /api/v1/UserCollect/GetUserCollect
|
||||||
|
/// 参数:type(1收藏/2错题) / subjectId(可选) / pageIndex / pageSize / prevPageId
|
||||||
|
///
|
||||||
|
/// 游标分页说明(对齐 Vue useList):
|
||||||
|
/// 第一页 prevPageId=0,之后每页传上一页最后一条的 id;
|
||||||
|
/// 切学科/下拉刷新时 prevPageId 重置为 0。
|
||||||
|
/// ============================================================
|
||||||
|
class UserCollectApi {
|
||||||
|
UserCollectApi._();
|
||||||
|
|
||||||
|
static final UserCollectApi instance = UserCollectApi._();
|
||||||
|
|
||||||
|
/// 学科筛选项([type] 1=我的收藏 2=错题本)
|
||||||
|
Future<List<CollectTab>> getSubjects(int type) async {
|
||||||
|
final data = await DioClient.instance.get(
|
||||||
|
'/api/v1/UserCollect/GetUserCollectSubject/$type',
|
||||||
|
);
|
||||||
|
// 兼容裸数组 / {data: [...]} 响应壳
|
||||||
|
final List<dynamic> list = data is List
|
||||||
|
? data
|
||||||
|
: (data is Map<String, dynamic>
|
||||||
|
? (data['data'] as List<dynamic>? ?? const [])
|
||||||
|
: const []);
|
||||||
|
return list
|
||||||
|
.whereType<Map<String, dynamic>>()
|
||||||
|
.map(CollectTab.fromJson)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 收藏/错题分页列表
|
||||||
|
///
|
||||||
|
/// [type] 1=我的收藏 2=错题本;[subjectId] 不传 = 全部学科;
|
||||||
|
/// [prevPageId] 游标:首页传 0,翻页传上一页最后一条 id。
|
||||||
|
Future<UserCollectPage> getCollectList({
|
||||||
|
required int type,
|
||||||
|
int? subjectId,
|
||||||
|
required int pageIndex,
|
||||||
|
int pageSize = 10,
|
||||||
|
int prevPageId = 0,
|
||||||
|
}) async {
|
||||||
|
final query = <String, dynamic>{
|
||||||
|
'type': type,
|
||||||
|
'pageIndex': pageIndex,
|
||||||
|
'pageSize': pageSize,
|
||||||
|
'prevPageId': prevPageId,
|
||||||
|
};
|
||||||
|
// 对齐 Vue useList:subjectId 为 null 时不带该参数
|
||||||
|
if (subjectId != null) query['subjectId'] = subjectId;
|
||||||
|
|
||||||
|
final data = await DioClient.instance.get(
|
||||||
|
'/api/v1/UserCollect/GetUserCollect',
|
||||||
|
query: query,
|
||||||
|
);
|
||||||
|
return UserCollectPage.fromJson(data as Map<String, dynamic>);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/// ============================================================
|
||||||
|
/// 【user_collect 模块模型】我的收藏 + 错题本 —— 对标 Vue3 端 api/userCollect/type.ts
|
||||||
|
///
|
||||||
|
/// CollectTab 学科筛选 Tab(GetUserCollectSubject 返回项)
|
||||||
|
/// UserCollectItem 列表项(收藏/错题通用,靠 type 区分)
|
||||||
|
/// UserCollectPage 分页响应(游标分页:pageIndex + prevPageId)
|
||||||
|
///
|
||||||
|
/// 说明:错题本页(myWrong)与收藏页(myCollect)共用同一套接口,
|
||||||
|
/// type=1 我的收藏 / type=2 错题本。
|
||||||
|
/// 序列化:手动 fromJson(兼容 {code, message, data: {...}} 响应壳),
|
||||||
|
/// 不依赖 build_runner。
|
||||||
|
/// ============================================================
|
||||||
|
library;
|
||||||
|
|
||||||
|
/// 兼容后端 id 字段:雪花 id 后端可能以 String 返回(防 JS 精度丢失),
|
||||||
|
/// 也可能直接是 num;统一转 int,无法解析返回 null。
|
||||||
|
int? _toInt(dynamic v) {
|
||||||
|
if (v == null) return null;
|
||||||
|
if (v is num) return v.toInt();
|
||||||
|
if (v is String && v.isNotEmpty) return int.tryParse(v);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 学科筛选 Tab
|
||||||
|
class CollectTab {
|
||||||
|
const CollectTab({
|
||||||
|
this.id,
|
||||||
|
required this.name,
|
||||||
|
this.remark = '',
|
||||||
|
this.status = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 学科 id;null = 客户端插入的"全部"
|
||||||
|
final int? id;
|
||||||
|
final String name;
|
||||||
|
final String remark;
|
||||||
|
final int status;
|
||||||
|
|
||||||
|
factory CollectTab.fromJson(Map<String, dynamic> json) {
|
||||||
|
return CollectTab(
|
||||||
|
id: _toInt(json['id']),
|
||||||
|
name: json['name'] as String? ?? '',
|
||||||
|
remark: json['remark'] as String? ?? '',
|
||||||
|
status: _toInt(json['status']) ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 收藏/错题列表项
|
||||||
|
class UserCollectItem {
|
||||||
|
const UserCollectItem({
|
||||||
|
this.id = 0,
|
||||||
|
this.questionId = 0,
|
||||||
|
this.recordQuestionId = 0,
|
||||||
|
this.questionStem = '',
|
||||||
|
this.subjectName = '',
|
||||||
|
this.packName = '',
|
||||||
|
this.createTime = '',
|
||||||
|
});
|
||||||
|
|
||||||
|
/// 列表明细 id(游标分页 prevPageId 取本页最后一条的 id)
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
/// 题目 id(详情页参数 questionId)
|
||||||
|
final int questionId;
|
||||||
|
|
||||||
|
/// 答题记录 id(详情页参数 recordQuestionId)
|
||||||
|
final int recordQuestionId;
|
||||||
|
|
||||||
|
/// 题干 HTML(RichTextContent 渲染)
|
||||||
|
final String questionStem;
|
||||||
|
|
||||||
|
/// 学科名(SubjectTag 配色用)
|
||||||
|
final String subjectName;
|
||||||
|
|
||||||
|
/// 来源试卷/礼包名
|
||||||
|
final String packName;
|
||||||
|
|
||||||
|
/// 收藏/错题时间字符串
|
||||||
|
final String createTime;
|
||||||
|
|
||||||
|
factory UserCollectItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
return UserCollectItem(
|
||||||
|
id: _toInt(json['id']) ?? 0,
|
||||||
|
questionId: _toInt(json['questionId']) ?? 0,
|
||||||
|
recordQuestionId: _toInt(json['recordQuestionId']) ?? 0,
|
||||||
|
questionStem: json['questionStem'] as String? ?? '',
|
||||||
|
subjectName: json['subjectName'] as String? ?? '',
|
||||||
|
packName: json['packName'] as String? ?? '',
|
||||||
|
createTime: json['createTime'] as String? ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 收藏/错题分页响应
|
||||||
|
class UserCollectPage {
|
||||||
|
const UserCollectPage({
|
||||||
|
this.data = const [],
|
||||||
|
this.total = 0,
|
||||||
|
this.pageIndex = 1,
|
||||||
|
this.pageSize = 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<UserCollectItem> data; // 当前页数据
|
||||||
|
final int total; // 总条数(顶部"X 道错题/收藏题目")
|
||||||
|
final int pageIndex;
|
||||||
|
final int pageSize;
|
||||||
|
|
||||||
|
/// 兼容业务响应壳 {code, message, data: {...}}:data 存在就取 data
|
||||||
|
factory UserCollectPage.fromJson(Map<String, dynamic> json) {
|
||||||
|
final payload = (json['data'] is Map<String, dynamic>)
|
||||||
|
? json['data'] as Map<String, dynamic>
|
||||||
|
: json;
|
||||||
|
return UserCollectPage(
|
||||||
|
data: (payload['data'] as List<dynamic>? ?? const [])
|
||||||
|
.whereType<Map<String, dynamic>>()
|
||||||
|
.map(UserCollectItem.fromJson)
|
||||||
|
.toList(),
|
||||||
|
total: _toInt(payload['total']) ?? 0,
|
||||||
|
pageIndex: _toInt(payload['pageIndex']) ?? 1,
|
||||||
|
pageSize: _toInt(payload['pageSize']) ?? 10,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
||||||
|
/// ============================================================
|
||||||
|
/// 【SubjectTag】学科标签(对标 Vue components/SubjectTag/SubjectTag.vue)
|
||||||
|
///
|
||||||
|
/// 四学科配色(与 Vue subjectMap 一致),未知学科兜底数学蓝:
|
||||||
|
/// 数学 #3D7DF5 / #E5F0FF 物理 #A45CFF / #F5ECFF
|
||||||
|
/// 英语 #E5A301 / #FFF6E3 化学 #2DB985 / #E5FAF3
|
||||||
|
///
|
||||||
|
/// 描边:Vue 用 ::before + mask-composite 画 2rpx 的学科色渐变描边
|
||||||
|
/// (两端 60% 学科色、中间 60% 近白色);Flutter 用"外层渐变容器 +
|
||||||
|
/// 内层底色容器留 1px 边"的嵌套方案等价实现。
|
||||||
|
///
|
||||||
|
/// 原 class 模块私有组件,因错题本/我的收藏列表也需要,按项目规范
|
||||||
|
/// 提升为 core/components 全局公共组件。
|
||||||
|
/// ============================================================
|
||||||
|
class SubjectTag extends StatelessWidget {
|
||||||
|
const SubjectTag({super.key, required this.subject});
|
||||||
|
|
||||||
|
final String subject;
|
||||||
|
|
||||||
|
static const Map<String, _SubjectColor> _map = {
|
||||||
|
// 数学:描边 rgba(90,158,255,.6) → rgba(242,248,255,.6)
|
||||||
|
'数学': _SubjectColor(0xFF3D7DF5, 0xFFE5F0FF, 0x995A9EFF, 0x99F2F8FF),
|
||||||
|
// 物理:描边 rgba(182,127,255,.6) → rgba(252,246,255,.6)
|
||||||
|
'物理': _SubjectColor(0xFFA45CFF, 0xFFF5ECFF, 0x99B67FFF, 0x99FCF6FF),
|
||||||
|
// 英语:描边 rgba(247,194,82,.6) → rgba(255,251,243,.6)
|
||||||
|
'英语': _SubjectColor(0xFFE5A301, 0xFFFFF6E3, 0x99F7C252, 0x99FFFBF3),
|
||||||
|
// 化学:描边 rgba(91,209,165,.6) → rgba(240,255,248,.6)
|
||||||
|
'化学': _SubjectColor(0xFF2DB985, 0xFFE5FAF3, 0x995BD1A5, 0x99F0FFF8),
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final t = subject.trim();
|
||||||
|
if (t.isEmpty) return const SizedBox.shrink();
|
||||||
|
final c =
|
||||||
|
_map[t] ??
|
||||||
|
const _SubjectColor(0xFF3D7DF5, 0xFFE5F0FF, 0x995A9EFF, 0x99F2F8FF);
|
||||||
|
|
||||||
|
// Vue border-radius: 12rpx
|
||||||
|
final outerRadius = BorderRadius.circular(6.r);
|
||||||
|
// Vue ::before padding: 2rpx(描边厚度)
|
||||||
|
final borderWidth = 1.w;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
// 外层:学科色横向渐变,露出的 1px 边即"渐变描边"
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.centerLeft,
|
||||||
|
end: Alignment.centerRight,
|
||||||
|
colors: [Color(c.borderA), Color(c.borderB), Color(c.borderA)],
|
||||||
|
),
|
||||||
|
borderRadius: outerRadius,
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.all(borderWidth),
|
||||||
|
// Vue padding: 4rpx 14rpx,扣除 2rpx 描边后内层 2rpx 12rpx
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 1.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Color(c.bg),
|
||||||
|
borderRadius: BorderRadius.circular(6.r - borderWidth),
|
||||||
|
),
|
||||||
|
// Vue white-space: nowrap:学科名不换行
|
||||||
|
child: Text(
|
||||||
|
t,
|
||||||
|
softWrap: false,
|
||||||
|
style: TextStyle(fontSize: 11, color: Color(c.fg)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SubjectColor {
|
||||||
|
const _SubjectColor(this.fg, this.bg, this.borderA, this.borderB);
|
||||||
|
|
||||||
|
final int fg; // 文字色
|
||||||
|
final int bg; // 底色
|
||||||
|
final int borderA; // 渐变描边两端色(60% 学科色)
|
||||||
|
final int borderB; // 渐变描边中间色(60% 近白)
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import 'package:new_learn/core/api/class/model.dart';
|
|||||||
import 'package:new_learn/features/my/my_page.dart';
|
import 'package:new_learn/features/my/my_page.dart';
|
||||||
import 'package:new_learn/features/conquerPackage/captureList/index.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/my/my_point/my_point_page.dart';
|
||||||
|
import 'package:new_learn/features/my/question_list/question_list_page.dart';
|
||||||
import 'package:new_learn/features/my/my_report/my_report_page.dart';
|
import 'package:new_learn/features/my/my_report/my_report_page.dart';
|
||||||
import 'package:new_learn/features/my/my_report/report_detail_page.dart';
|
import 'package:new_learn/features/my/my_report/report_detail_page.dart';
|
||||||
import 'package:new_learn/features/my/exam/exam_page.dart';
|
import 'package:new_learn/features/my/exam/exam_page.dart';
|
||||||
@@ -138,6 +139,15 @@ final GoRouter appRouter = GoRouter(
|
|||||||
MyUpdateDetailPage(initialPost: state.extra as UserPostDto),
|
MyUpdateDetailPage(initialPost: state.extra as UserPostDto),
|
||||||
),
|
),
|
||||||
GoRoute(path: '/myPoint', builder: (_, _) => const MyPointPage()),
|
GoRoute(path: '/myPoint', builder: (_, _) => const MyPointPage()),
|
||||||
|
// 错题本(type=2)/ 我的收藏(type=1)共用 QuestionListPage
|
||||||
|
GoRoute(
|
||||||
|
path: '/myWrong',
|
||||||
|
builder: (_, _) => const QuestionListPage(type: 2),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/myCollect',
|
||||||
|
builder: (_, _) => const QuestionListPage(type: 1),
|
||||||
|
),
|
||||||
GoRoute(path: '/myReport', builder: (_, _) => const MyReportPage()),
|
GoRoute(path: '/myReport', builder: (_, _) => const MyReportPage()),
|
||||||
GoRoute(path: '/myExam', builder: (_, _) => const ExamPage()),
|
GoRoute(path: '/myExam', builder: (_, _) => const ExamPage()),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|||||||
import 'package:new_learn/core/api/class/model.dart';
|
import 'package:new_learn/core/api/class/model.dart';
|
||||||
import 'package:new_learn/core/components/image_preview_viewer.dart';
|
import 'package:new_learn/core/components/image_preview_viewer.dart';
|
||||||
import 'package:new_learn/core/components/resolve_problem_box.dart';
|
import 'package:new_learn/core/components/resolve_problem_box.dart';
|
||||||
|
import 'package:new_learn/core/components/subject_tag.dart';
|
||||||
import 'package:new_learn/core/components/user_circle_avatar.dart';
|
import 'package:new_learn/core/components/user_circle_avatar.dart';
|
||||||
import 'package:new_learn/core/utils/img_url.dart';
|
import 'package:new_learn/core/utils/img_url.dart';
|
||||||
|
|
||||||
@@ -236,35 +237,6 @@ class CardHeader extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 学科标签(对标 SubjectTag)
|
|
||||||
class SubjectTag extends StatelessWidget {
|
|
||||||
const SubjectTag({super.key, required this.subject});
|
|
||||||
|
|
||||||
final String subject;
|
|
||||||
|
|
||||||
static const Map<String, _SubjectColor> _map = {
|
|
||||||
'数学': _SubjectColor(0xFF3D7DF5, 0xFFE5F0FF),
|
|
||||||
'物理': _SubjectColor(0xFFA45CFF, 0xFFF5ECFF),
|
|
||||||
'英语': _SubjectColor(0xFFE5A301, 0xFFFFF6E3),
|
|
||||||
'化学': _SubjectColor(0xFF2DB985, 0xFFE5FAF3),
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final t = subject.trim();
|
|
||||||
if (t.isEmpty) return const SizedBox.shrink();
|
|
||||||
final c = _map[t] ?? const _SubjectColor(0xFF3D7DF5, 0xFFE5F0FF);
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 7.w, vertical: 2.h),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Color(c.bg),
|
|
||||||
borderRadius: BorderRadius.circular(6.r),
|
|
||||||
),
|
|
||||||
child: Text(t, style: TextStyle(fontSize: 11, color: Color(c.fg))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 纯文字笔记(album-desc,最多 2 行)
|
/// 纯文字笔记(album-desc,最多 2 行)
|
||||||
class AlbumDesc extends StatelessWidget {
|
class AlbumDesc extends StatelessWidget {
|
||||||
const AlbumDesc({super.key, required this.text});
|
const AlbumDesc({super.key, required this.text});
|
||||||
@@ -613,9 +585,3 @@ class _CommentInputState extends State<CommentInput> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SubjectColor {
|
|
||||||
const _SubjectColor(this.fg, this.bg);
|
|
||||||
final int fg;
|
|
||||||
final int bg;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -111,8 +111,8 @@ class _ExamPageState extends State<ExamPage> {
|
|||||||
child: CommonPagedList<ExamDto>(
|
child: CommonPagedList<ExamDto>(
|
||||||
key: ValueKey('exam-list-${_currentTab.id}'),
|
key: ValueKey('exam-list-${_currentTab.id}'),
|
||||||
load: _loadPage,
|
load: _loadPage,
|
||||||
padding: EdgeInsets.fromLTRB(12.w, 16.h, 14.w, 20.h),
|
padding: EdgeInsets.fromLTRB(12.w, 24.h, 12.w, 0),
|
||||||
separatorBuilder: (_, __) => SizedBox(height: 14.h),
|
// separatorBuilder: (_, __) => SizedBox(height: 14.h),
|
||||||
noMoreText: '- AI 爱学蝶变提醒你 没有更多了 -',
|
noMoreText: '- AI 爱学蝶变提醒你 没有更多了 -',
|
||||||
emptyWidget: _buildEmpty(),
|
emptyWidget: _buildEmpty(),
|
||||||
buildItem: (ctx, item, index) {
|
buildItem: (ctx, item, index) {
|
||||||
@@ -212,14 +212,14 @@ class _ExamCard extends StatelessWidget {
|
|||||||
|
|
||||||
// 书图标设计尺寸(目标渲染尺寸)
|
// 书图标设计尺寸(目标渲染尺寸)
|
||||||
static const double _bookWidth = 124;
|
static const double _bookWidth = 124;
|
||||||
static const double _bookHeight = 134;
|
static const double _bookHeight = 124;
|
||||||
// 卡片尺寸
|
// 卡片尺寸
|
||||||
static const double _cardHeight = 92;
|
static const double _cardHeight = 92;
|
||||||
static const double _cardRadius = 24;
|
static const double _cardRadius = 24;
|
||||||
// ⚠ book_icon.png 自带透明 padding,下面这些偏移用于补偿图片留白
|
// ⚠ book_icon.png 自带透明 padding,下面这些偏移用于补偿图片留白
|
||||||
// 让书图案的左下角对齐卡片左下角。换图时需重新调整。
|
// 让书图案的左下角对齐卡片左下角。换图时需重新调整。
|
||||||
static const double _bookOffsetLeft = -14; // 左补留白(宽度加大了,偏移也加大)
|
static const double _bookOffsetLeft = -14; // 左补留白(宽度加大了,偏移也加大)
|
||||||
static const double _bookOffsetBottom = 10; // 底补留白
|
static const double _bookOffsetBottom = 6; // 底补留白
|
||||||
static const double _bookExtraHeight = 0; // 放大补偿,让图案达到目标高度
|
static const double _bookExtraHeight = 0; // 放大补偿,让图案达到目标高度
|
||||||
|
|
||||||
String get _timeRange {
|
String get _timeRange {
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class MyPage extends ConsumerWidget {
|
|||||||
'${AppEnv.imgBaseUrl}/StaticResource/2026-04-01/error.png',
|
'${AppEnv.imgBaseUrl}/StaticResource/2026-04-01/error.png',
|
||||||
title: '错题本',
|
title: '错题本',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// TODO: 跳转到错题本页
|
context.push('/myWrong');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_MenuItem(
|
_MenuItem(
|
||||||
@@ -133,7 +133,7 @@ class MyPage extends ConsumerWidget {
|
|||||||
'${AppEnv.imgBaseUrl}/StaticResource/2026-04-01/collect.png',
|
'${AppEnv.imgBaseUrl}/StaticResource/2026-04-01/collect.png',
|
||||||
title: '我的收藏',
|
title: '我的收藏',
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// TODO: 跳转到我的收藏页
|
context.push('/myCollect');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_MenuItem(
|
_MenuItem(
|
||||||
|
|||||||
@@ -0,0 +1,505 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:new_learn/core/api/user_collect/api.dart';
|
||||||
|
import 'package:new_learn/core/api/user_collect/model.dart';
|
||||||
|
import 'package:new_learn/core/components/common_paged_list.dart';
|
||||||
|
import 'package:new_learn/core/components/rich_text_content.dart';
|
||||||
|
import 'package:new_learn/core/components/subject_tag.dart';
|
||||||
|
import 'package:new_learn/core/logger/app_talker.dart';
|
||||||
|
import 'package:new_learn/core/utils/img_url.dart';
|
||||||
|
import 'package:new_learn/core/utils/screen_adapt_template.dart';
|
||||||
|
|
||||||
|
/// ============================================================
|
||||||
|
/// 【QuestionListPage】我的收藏 / 错题本 列表页
|
||||||
|
/// —— 对标 Vue pages/myPackage/myCollect/myCollect.vue
|
||||||
|
/// 和 pages/myPackage/myWrong/myWrong.vue
|
||||||
|
///
|
||||||
|
/// 两个页面结构完全一致(同一套接口 UserCollect),仅 [type] 不同:
|
||||||
|
/// type=1 我的收藏(/myCollect) type=2 错题本(/myWrong)
|
||||||
|
/// 标题 / 总数文案 / 顶部背景图 / 空状态 / 装饰色带 由 [_config] 按 type 切换。
|
||||||
|
///
|
||||||
|
/// 页面结构(对齐 Vue,从上到下):
|
||||||
|
/// 1. 学科筛选 Tab("全部" + 接口返回学科,胶囊样式,点击切学科重载)
|
||||||
|
/// 2. 总数头:主题背景图 + 大号总数 + 装饰色带 + 文案(道错题/收藏题目)
|
||||||
|
/// 3. #F5F8FA 列表区:CommonPagedList 游标分页
|
||||||
|
/// 卡片 = SubjectTag + 时间 + packName + 题干富文本(限高渐隐)
|
||||||
|
///
|
||||||
|
/// 分页要点(游标分页,对齐 Vue useList):
|
||||||
|
/// pageKey 当 pageIndex;prevPageId 在闭包里维护——首页/刷新重置为 0,
|
||||||
|
/// 每页请求后更新为本页最后一条 id;切学科用 ValueKey 重建列表(天然回首页)。
|
||||||
|
///
|
||||||
|
/// 题干限高:Vue 用 DOM 测量后加渐隐遮罩;Flutter 用 OverflowBox 限高 70.h
|
||||||
|
/// 裁剪 + 底部固定渐隐遮罩(遮罩终点色与题干底色色值一致,内容不足时不可见)。
|
||||||
|
/// ============================================================
|
||||||
|
class QuestionListPage extends StatefulWidget {
|
||||||
|
const QuestionListPage({super.key, required this.type});
|
||||||
|
|
||||||
|
/// 1=我的收藏 2=错题本
|
||||||
|
final int type;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<QuestionListPage> createState() => _QuestionListPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _QuestionListPageState extends State<QuestionListPage> {
|
||||||
|
static const int _pageSize = 10;
|
||||||
|
|
||||||
|
/// 学科 Tab 列表("全部"为客户端插入,id=null)
|
||||||
|
List<CollectTab> _tabs = const [CollectTab(name: '全部')];
|
||||||
|
|
||||||
|
/// 当前选中的学科 id(null = 全部)
|
||||||
|
int? _subjectId;
|
||||||
|
|
||||||
|
/// 顶部总数(来自分页响应 total)
|
||||||
|
int _total = 0;
|
||||||
|
|
||||||
|
/// 游标:本页最后一条 id,首页/刷新/切学科时重置为 0
|
||||||
|
int _prevPageId = 0;
|
||||||
|
|
||||||
|
_PageConfig get _config =>
|
||||||
|
widget.type == 1 ? _PageConfig.collect : _PageConfig.wrong;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
talker.debug('打开页面:${widget.type == 1 ? '我的收藏' : '错题本'}');
|
||||||
|
_loadTabs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 拉取学科筛选项;失败兜底只有"全部"(对齐 Vue catch 逻辑)
|
||||||
|
Future<void> _loadTabs() async {
|
||||||
|
try {
|
||||||
|
final tabs = await UserCollectApi.instance.getSubjects(widget.type);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_tabs = [const CollectTab(name: '全部'), ...tabs];
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
talker.error('获取学科Tab失败', e);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_tabs = const [CollectTab(name: '全部')];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 分页加载(CommonPagedList 回调):只写请求 + 返回数据
|
||||||
|
Future<PagingResult<UserCollectItem>> _load(int pageKey) async {
|
||||||
|
// 首页/下拉刷新:游标重置(对齐 Vue useList pageNo===1 时 prevPageId=0)
|
||||||
|
if (pageKey == 1) _prevPageId = 0;
|
||||||
|
|
||||||
|
final res = await UserCollectApi.instance.getCollectList(
|
||||||
|
type: widget.type,
|
||||||
|
subjectId: _subjectId,
|
||||||
|
pageIndex: pageKey,
|
||||||
|
pageSize: _pageSize,
|
||||||
|
prevPageId: _prevPageId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 更新游标为本页最后一条 id
|
||||||
|
_prevPageId = res.data.isNotEmpty ? res.data.last.id : 0;
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _total = res.total);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PagingResult<UserCollectItem>(
|
||||||
|
data: res.data,
|
||||||
|
hasNext: res.total > pageKey * _pageSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 切学科:ValueKey 变化后 CommonPagedList 整体重建,自动回到第一页
|
||||||
|
void _onTabTap(CollectTab tab) {
|
||||||
|
if (_subjectId == tab.id) return;
|
||||||
|
setState(() {
|
||||||
|
_subjectId = tab.id;
|
||||||
|
_prevPageId = 0;
|
||||||
|
_total = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 点击卡片 → 题目解析详情页
|
||||||
|
/// TODO: 详情页待接入——错题本跳 questionAnalysis、收藏跳 collectAnalysis,
|
||||||
|
/// 参数:questionId=${item.questionId}
|
||||||
|
/// recordQuestionId=${item.recordQuestionId}
|
||||||
|
/// listDetailId=${item.id}
|
||||||
|
void _goDetail(UserCollectItem item) {
|
||||||
|
talker.debug(
|
||||||
|
'题目详情待接入:type=${widget.type} questionId=${item.questionId} '
|
||||||
|
'recordQuestionId=${item.recordQuestionId} listDetailId=${item.id}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cfg = _config;
|
||||||
|
final isPad =
|
||||||
|
MediaQuery.sizeOf(context).width >= AppAdapt.tabletBreakpoint;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
surfaceTintColor: Colors.white,
|
||||||
|
elevation: 0,
|
||||||
|
title: Text(
|
||||||
|
cfg.title,
|
||||||
|
style: const TextStyle(color: Color(0xFF1D2B47)),
|
||||||
|
),
|
||||||
|
iconTheme: const IconThemeData(color: Color(0xFF1D2B47)),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () => context.canPop() ? context.pop() : context.go('/my'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
// 1. 学科筛选 Tab(白底)
|
||||||
|
_buildTabBar(),
|
||||||
|
// 2. 总数头(背景图 + 大号数字)
|
||||||
|
_buildTotalHeader(cfg, isPad),
|
||||||
|
// 3. 列表区(#F5F8FA 灰底)
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
color: const Color(0xFFF5F8FA),
|
||||||
|
child: CommonPagedList<UserCollectItem>(
|
||||||
|
// 切学科时重建列表,状态自动回第一页
|
||||||
|
key: ValueKey('question_list_${widget.type}_$_subjectId'),
|
||||||
|
padding: EdgeInsets.fromLTRB(14.w, 16.h, 14.w, 14.h),
|
||||||
|
emptyWidget: _buildEmpty(cfg),
|
||||||
|
load: _load,
|
||||||
|
buildItem: (context, item, index) => _QuestionCard(
|
||||||
|
item: item,
|
||||||
|
onTap: () => _goDetail(item),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 学科筛选胶囊 Tab 栏(横向滚动)
|
||||||
|
Widget _buildTabBar() {
|
||||||
|
return Container(
|
||||||
|
color: Colors.white,
|
||||||
|
height: 56.h, // 上下 padding 12.h + 胶囊 32.h(对齐 Vue 24rpx+64rpx)
|
||||||
|
child: ListView.separated(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
||||||
|
itemCount: _tabs.length,
|
||||||
|
separatorBuilder: (_, __) => SizedBox(width: 12.w),
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
final tab = _tabs[i];
|
||||||
|
final active = _subjectId == tab.id;
|
||||||
|
return Center(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => _onTabTap(tab),
|
||||||
|
child: Container(
|
||||||
|
height: 32.h,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: active
|
||||||
|
? const Color(0xFF3D7DF5)
|
||||||
|
: const Color(0xFFF5F8FA),
|
||||||
|
borderRadius: BorderRadius.circular(999),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
tab.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 15.sp,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: active
|
||||||
|
? Colors.white
|
||||||
|
: const Color(0xFF687FAD),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 总数头:背景图(widthFix)+ 大号总数 + 装饰色带 + 文案
|
||||||
|
Widget _buildTotalHeader(_PageConfig cfg, bool isPad) {
|
||||||
|
final numFontSize = isPad ? 52.5.sp : 35.sp;
|
||||||
|
final labelFontSize = isPad ? 21.sp : 14.sp;
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
// 背景图:宽度撑满,高度按图比例(对齐 Vue mode="widthFix")
|
||||||
|
CachedNetworkImage(
|
||||||
|
imageUrl: ImgUrl.build(isPad ? cfg.topBgPad : cfg.topBgPhone),
|
||||||
|
width: double.infinity,
|
||||||
|
fit: BoxFit.fitWidth,
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
placeholder: (_, __) => const SizedBox.shrink(),
|
||||||
|
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
// 数字 + 色带 + 文案(绝对定位叠在图上)
|
||||||
|
Positioned(
|
||||||
|
top: isPad ? 45.h : 16.5.h, // Vue padding-top: 90rpx / 33rpx
|
||||||
|
left: 30.w, // Vue padding-left: 60rpx
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// 大号数字 + 底部装饰色带
|
||||||
|
Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: isPad ? -7.h : -4.h,
|
||||||
|
child: Container(
|
||||||
|
height: 9.h, // Vue 18rpx
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(50.r),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.centerLeft,
|
||||||
|
end: Alignment.centerRight,
|
||||||
|
colors: cfg.underlineColors,
|
||||||
|
stops: const [0, 0.36, 1],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'$_total',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: numFontSize,
|
||||||
|
fontWeight: isPad ? FontWeight.w500 : FontWeight.w600,
|
||||||
|
color: const Color(0xFF1D2B47),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(width: 10.w), // Vue margin-l-20
|
||||||
|
// 文案(Vue margin-t-30:相对数字略微下沉)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 15.h),
|
||||||
|
child: Text(
|
||||||
|
cfg.totalLabel,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: labelFontSize,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: const Color(0xFF687FAD),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 空状态:空态图 + 文案(对齐 Vue DataEmpty)
|
||||||
|
Widget _buildEmpty(_PageConfig cfg) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
CachedNetworkImage(
|
||||||
|
imageUrl: ImgUrl.build(cfg.emptyImg),
|
||||||
|
width: 160.w,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
placeholder: (_, __) => const SizedBox.shrink(),
|
||||||
|
errorWidget: (_, __, ___) => const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
Text(
|
||||||
|
cfg.emptyText,
|
||||||
|
style: TextStyle(fontSize: 14.sp, color: const Color(0xFF999999)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 单条收藏/错题卡片:SubjectTag + 时间 + packName + 题干富文本
|
||||||
|
class _QuestionCard extends StatelessWidget {
|
||||||
|
const _QuestionCard({required this.item, required this.onTap});
|
||||||
|
|
||||||
|
final UserCollectItem item;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: 14.h), // Vue listItem margin-bottom: 28rpx
|
||||||
|
child: Material(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16.r),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(16.r),
|
||||||
|
child: Padding(
|
||||||
|
// Vue padding: t-32 l/r-28 b-28
|
||||||
|
padding: EdgeInsets.fromLTRB(14.w, 16.h, 14.w, 14.h),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// 第一行:学科标签 + 时间
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SubjectTag(subject: item.subjectName),
|
||||||
|
const Spacer(),
|
||||||
|
Text(
|
||||||
|
item.createTime,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.sp, // Vue font-24
|
||||||
|
color: const Color(0xFFB4C2E0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 14.h), // Vue pack-name margin-top: 28rpx
|
||||||
|
// 试卷/礼包名
|
||||||
|
Text(
|
||||||
|
item.packName,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.sp, // Vue font-32
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: const Color(0xFF1D2B47),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16.h), // Vue question-stem-wrap margin-top: 32rpx
|
||||||
|
// 题干富文本(限高渐隐)
|
||||||
|
_StemBox(html: item.questionStem),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 题干容器:#FAFBFD 底 + #E3ECFF 边框,内容限高 70.h(Vue 140rpx),
|
||||||
|
/// 超出部分底部渐隐遮罩
|
||||||
|
class _StemBox extends StatelessWidget {
|
||||||
|
const _StemBox({required this.html});
|
||||||
|
|
||||||
|
final String html;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: EdgeInsets.all(12.w), // Vue padding: 24rpx
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFFAFBFD),
|
||||||
|
borderRadius: BorderRadius.circular(8.r), // Vue 16rpx
|
||||||
|
border: Border.all(color: const Color(0xFFE3ECFF)),
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// 限高裁剪:OverflowBox 让富文本按自然高度布局,超出 70.h 的部分裁掉
|
||||||
|
ClipRect(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxHeight: 70.h),
|
||||||
|
child: OverflowBox(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
minHeight: 0,
|
||||||
|
maxHeight: double.infinity,
|
||||||
|
child: RichTextContent(
|
||||||
|
html: html,
|
||||||
|
textStyle: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: const Color(0xFF495A7A),
|
||||||
|
height: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 底部渐隐遮罩(Vue mask-layer 60rpx):
|
||||||
|
// 终点色与底色 #FAFBFD 一致,内容不足时遮罩盖在底色上视觉不可见
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
height: 30.h,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [Color(0x00FAFBFD), Color(0xFFFAFBFD)],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(8.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 页面差异化配置(按 type 切换文案/图片/配色)
|
||||||
|
class _PageConfig {
|
||||||
|
const _PageConfig({
|
||||||
|
required this.title,
|
||||||
|
required this.totalLabel,
|
||||||
|
required this.topBgPhone,
|
||||||
|
required this.topBgPad,
|
||||||
|
required this.emptyImg,
|
||||||
|
required this.emptyText,
|
||||||
|
required this.underlineColors,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final String totalLabel;
|
||||||
|
final String topBgPhone;
|
||||||
|
final String topBgPad;
|
||||||
|
final String emptyImg;
|
||||||
|
final String emptyText;
|
||||||
|
final List<Color> underlineColors;
|
||||||
|
|
||||||
|
/// 我的收藏(type=1):橙色系装饰
|
||||||
|
static final _PageConfig collect = _PageConfig(
|
||||||
|
title: '我的收藏',
|
||||||
|
totalLabel: '收藏题目',
|
||||||
|
topBgPhone: '/StaticResource/2026-04-08/my-collect-top-bg.png',
|
||||||
|
topBgPad: '/StaticResource/2026-06-02/my-collect-top-bg-pad1780378706432.png',
|
||||||
|
emptyImg: '/StaticResource/2026-04-11/contentEmptys.png',
|
||||||
|
emptyText: '暂无收藏',
|
||||||
|
// Vue: linear-gradient(90deg, #F59C3D00 0%, #FFCA1D33 36%, #FDAD8A80 100%)
|
||||||
|
underlineColors: const [
|
||||||
|
Color(0x00F59C3D),
|
||||||
|
Color(0x33FFCA1D),
|
||||||
|
Color(0x80FDAD8A),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
/// 错题本(type=2):蓝色系装饰
|
||||||
|
static final _PageConfig wrong = _PageConfig(
|
||||||
|
title: '错题本',
|
||||||
|
totalLabel: '道错题',
|
||||||
|
topBgPhone: '/StaticResource/2026-04-08/my-wrong-top-bg.png',
|
||||||
|
topBgPad: '/StaticResource/2026-06-02/my-wrong-top-bg-pad1780372865693.png',
|
||||||
|
emptyImg: '/StaticResource/2026-04-11/noDatas.png',
|
||||||
|
emptyText: '暂无错题',
|
||||||
|
// Vue: linear-gradient(90deg, #3D7DF500 0%, #1D9CFF33 36%, #8AECFD80 100%)
|
||||||
|
underlineColors: const [
|
||||||
|
Color(0x003D7DF5),
|
||||||
|
Color(0x331D9CFF),
|
||||||
|
Color(0x808AECFD),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user