feat: 新增过渡页面路由、上传组件及完善跳转逻辑
- 为conquerPackage模块添加过渡页面路由并实现页面跳转逻辑 - 新增通用多图上传组件Upload,支持多种模式与图片操作功能 - 新增任务相关的API及Model基础文件 - 注释课前课中页面的测试试题内容
This commit is contained in:
@@ -22,6 +22,10 @@ import 'package:new_learn/core/network/dio_client.dart';
|
||||
/// —— 获取攻克报告(view 模式数据源)
|
||||
/// GET /api/v1/StudyPack/GetReport/{recordId}
|
||||
///
|
||||
/// getRecord
|
||||
/// —— 获取攻克记录信息(过渡页使用)
|
||||
/// GET /api/v1/StudyPack/GetRecord/{recordId}
|
||||
///
|
||||
/// 注意:
|
||||
/// - 页面只调这里,不直接碰 Dio;
|
||||
/// - 异常不在 catch,交页面统一处理;
|
||||
@@ -77,4 +81,17 @@ class BeforeDuringClassApi {
|
||||
}
|
||||
return const HandoutListResponse();
|
||||
}
|
||||
|
||||
/// 获取攻克记录信息 → GET /api/v1/StudyPack/GetRecord/{recordId}
|
||||
///
|
||||
/// 过渡页使用:获取攻克包名称、攻克标准、是否有题目等。
|
||||
Future<OvercomeRecordInfo> getRecord(String recordId) async {
|
||||
final data = await DioClient.instance.get(
|
||||
'/api/v1/StudyPack/GetRecord/$recordId',
|
||||
);
|
||||
if (data is Map<String, dynamic>) {
|
||||
return OvercomeRecordInfo.fromJson(data);
|
||||
}
|
||||
return const OvercomeRecordInfo();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +154,34 @@ class HandoutItem {
|
||||
}
|
||||
}
|
||||
|
||||
/// 攻克记录信息(过渡页使用,对标 Vue GetRecordData 返回值)
|
||||
///
|
||||
/// 接口:GET /api/v1/StudyPack/GetRecord/{recordId}
|
||||
/// 用于过渡页(课前/课中/课后)展示攻克包名称、攻克标准、是否有题目等。
|
||||
class OvercomeRecordInfo {
|
||||
const OvercomeRecordInfo({
|
||||
this.id = '',
|
||||
this.name = '',
|
||||
this.overcomeStandard = 0,
|
||||
this.hasQuestion = false,
|
||||
});
|
||||
|
||||
final String id; // 记录 id
|
||||
final String name; // 攻克包名称
|
||||
final int overcomeStandard; // 攻克标准(百分比 0-100)
|
||||
final bool hasQuestion; // 是否有题目
|
||||
|
||||
factory OvercomeRecordInfo.fromJson(Map<String, dynamic> json) {
|
||||
final payload = (json['data'] as Map<String, dynamic>?) ?? json;
|
||||
return OvercomeRecordInfo(
|
||||
id: payload['id']?.toString() ?? '',
|
||||
name: payload['name'] as String? ?? '',
|
||||
overcomeStandard: _parseInt(payload['overcomeStandard']),
|
||||
hasQuestion: payload['hasQuestion'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 讲义列表响应(Handout 接口返回的列表)
|
||||
class HandoutListResponse {
|
||||
const HandoutListResponse({this.items = const []});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
|
||||
import 'package:new_learn/core/components/audio_player_card.dart';
|
||||
import 'package:new_learn/core/components/video_player_view.dart';
|
||||
@@ -79,13 +81,47 @@ class RichTextContent extends StatelessWidget {
|
||||
textStyle: textStyle,
|
||||
onTapUrl: onTapUrl ?? _defaultOnTapUrl,
|
||||
customStylesBuilder: customStylesBuilder,
|
||||
customWidgetBuilder: _combinedCustomWidget,
|
||||
customWidgetBuilder: _combinedCustomWidget(context),
|
||||
renderMode: renderMode,
|
||||
);
|
||||
}
|
||||
|
||||
/// 合并默认替换 + 用户自定义替换
|
||||
CustomWidgetBuilder get _combinedCustomWidget => (element) {
|
||||
CustomWidgetBuilder _combinedCustomWidget(BuildContext context) => (element) {
|
||||
// 内置:<img src="*.svg"> → flutter_svg 行内渲染
|
||||
// core 版 flutter_widget_from_html 不支持 SVG,手动处理
|
||||
if (element.localName == 'img') {
|
||||
final src = element.attributes['src'];
|
||||
if (src != null && src.toLowerCase().endsWith('.svg')) {
|
||||
final width = double.tryParse(element.attributes['width'] ?? '');
|
||||
final height = double.tryParse(element.attributes['height'] ?? '');
|
||||
final w = width != null ? width.w : null;
|
||||
final h = height != null ? height.h : null;
|
||||
// 超采样(SSAA):SVG 以 3 倍尺寸光栅化再缩回来,消除真机边缘锯齿
|
||||
const scale = 3.0;
|
||||
return InlineCustomWidget(
|
||||
child: SizedBox(
|
||||
width: w,
|
||||
height: h,
|
||||
child: OverflowBox(
|
||||
maxWidth: w != null ? w * scale : double.infinity,
|
||||
maxHeight: h != null ? h * scale : double.infinity,
|
||||
child: Transform.scale(
|
||||
scale: 1 / scale,
|
||||
alignment: Alignment.center,
|
||||
child: SvgPicture.network(
|
||||
src,
|
||||
width: w != null ? w * scale : null,
|
||||
height: h != null ? h * scale : null,
|
||||
fit: BoxFit.contain,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// 内置:<audio> → 本项目 AudioPlayerCard
|
||||
if (element.localName == 'audio') {
|
||||
final src = element.attributes['src'];
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'package:new_learn/features/my/my_page.dart';
|
||||
import 'package:new_learn/features/my/note/note_page.dart';
|
||||
import 'package:new_learn/features/conquerPackage/captureList/index.dart';
|
||||
import 'package:new_learn/features/conquerPackage/beforeDuringClass/index.dart';
|
||||
import 'package:new_learn/features/conquerPackage/transitionPage/transition_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';
|
||||
@@ -210,6 +211,12 @@ final GoRouter appRouter = GoRouter(
|
||||
extra: state.extra as Map<String, dynamic>?,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/conquerPackage/transitionPage',
|
||||
builder: (_, state) => TransitionPage(
|
||||
extra: state.extra as Map<String, dynamic>?,
|
||||
),
|
||||
),
|
||||
GoRoute(path: '/register', builder: (_, _) => const RegisterPage()),
|
||||
|
||||
// app_router.dart 的 routes 里加,不用管跳转逻辑
|
||||
|
||||
Reference in New Issue
Block a user