mcy_new #1

Merged
wangyang merged 179 commits from mcy_new into master 2025-08-28 10:10:45 +08:00
8 changed files with 334 additions and 143 deletions
Showing only changes of commit 9517df9b0e - Show all commits

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:dio/dio.dart' hide Headers;
import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_param.dart';
import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_result.dart';
@ -123,4 +125,13 @@ abstract class RetrofitClient {
//
@POST("/api/hms/Annotate/AnnotateSubmit")
Future reviewSubmission(@Body() ReviewSubmissionParams param);
// OSS key
@GET("/api/infra/Oss/GetPresignedUri")
Future getOssPresignedUri(@Query('key') String key);
// OSS
@PUT('{theUrl}')
@Headers(<String, dynamic>{'Content-Type': ''})
Future<void> uploadImag(@Path() String theUrl, @Part() File file);
}

View File

@ -1,6 +1,7 @@
import 'package:get/get.dart';
import 'package:get/get_connect/http/src/request/request.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:making_school_asignment_app/common/config/request_config.dart';
part 'do_paper_details_result.g.dart';
@ -39,12 +40,19 @@ class DoPaperDetailsResult extends Object {
@JsonKey(name: 'zgtAnswer') //
String zgtAnswer;
@JsonKey(name: 'zgtAnnotate') //
@JsonKey(name: 'showZgtAnnotate') //
String? showZgtAnnotate;
@JsonKey(name: 'zgtAnnotate') //
String? zgtAnnotate;
@JsonKey(name: 'lastAnswerTime')
@JsonKey(name: 'lastAnswerTime') //
String lastAnswerTime;
//
@JsonKey(name: 'annotateTime')
String? annotateTime;
@JsonKey(name: 'isFav')
bool isFav;
@ -78,6 +86,8 @@ class DoPaperDetailsResult extends Object {
this.templateIdKeys,
this.templateIdKeyMap,
this.priority,
this.annotateTime,
this.showZgtAnnotate,
) {
if (templateIds.keys.isNotEmpty) {
templateIdKeys = templateIds.keys.map((e) => int.parse(e)).toList();
@ -94,6 +104,15 @@ class DoPaperDetailsResult extends Object {
var currentStudent = students.firstWhereOrNull((e) => e.id == studentId);
if (currentStudent != null) priority = currentStudent.isPriority;
}
zgtAnswer = '${RequestConfig.imgUrl}$zgtAnswer?$lastAnswerTime';
if (zgtAnnotate?.isNotEmpty ?? false) {
showZgtAnnotate = RequestConfig.imgUrl + zgtAnnotate!; //
if (annotateTime != null) showZgtAnnotate = '${showZgtAnnotate!}?$annotateTime';
}
print('学生作答图片:${zgtAnswer}');
print('老师批注图片:${showZgtAnnotate}');
}
factory DoPaperDetailsResult.fromJson(Map<String, dynamic> srcJson) => _$DoPaperDetailsResultFromJson(srcJson);

View File

@ -16,7 +16,7 @@ class ReviewSubmissionParams extends Object {
@JsonKey(name: 'studentScores')
List<StudentScores> studentScores;
@JsonKey(name: 'pictureBytes')
@JsonKey(name: 'zgtAnnotate')
String? zgtAnnotate;
ReviewSubmissionParams({

View File

@ -0,0 +1,19 @@
import '../mixins/request_tool_mixin.dart';
// OSS
class UploadOssImgUtils with RequestToolMixin {
static UploadOssImgUtils? _singleton;
static UploadOssImgUtils getInstance() => _singleton ??= UploadOssImgUtils._internal();
UploadOssImgUtils._internal();
Future<void> getPresignedUri(String key) async {
var res = await getClient().getOssPresignedUri(key);
print(res);
}
String setImgKey(String homeworkId, String studentId, String templateId) {
// $"hms-annotate/{input.HomeworkId}/{input.StudentId}/{input.TemplateId}.png"612 13:54
return 'hms-annotate/$homeworkId/$studentId/$templateId.png';
}
}

View File

@ -47,7 +47,7 @@ class _QuestionPaperViewState extends State<QuestionPaperView> {
return Stack(
children: [
//
QuestionImageView(maxWidth, maxHeight, sateData, annotationState),
QuestionImageView(maxWidth, maxHeight, sateData, annotationState, logic),
//
Positioned(right: 3.w, bottom: 4.h, child: const $ContinueToReview(isFloatingAction: true)),
//
@ -188,9 +188,7 @@ Widget $scoringQuestionsView(BuildContext context, StudentQuestions item, double
child: Stack(
alignment: const FractionalOffset(0, 0),
children: [
Container(
color: Colors.yellow,
child: Row(
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
@ -261,8 +259,9 @@ Widget $scoringQuestionsView(BuildContext context, StudentQuestions item, double
),
],
),
),
Row(
IgnorePointer(
// 穿
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(width: 1.1.w),
@ -281,18 +280,21 @@ Widget $scoringQuestionsView(BuildContext context, StudentQuestions item, double
),
)
],
)
),
),
],
));
),
);
}
//
class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar> {
final double maxWidth;
final double maxHeight;
final HomeworkReviewLogic logic;
final HomeworkReviewState sateData;
final HomeworkReviewAnnotationsControlState annotationState;
QuestionImageView(this.maxWidth, this.maxHeight, this.sateData, this.annotationState, {super.key});
QuestionImageView(this.maxWidth, this.maxHeight, this.sateData, this.annotationState, this.logic, {super.key});
///
int _findTargetIndex<T>(List<T> list, T target, [int reciprocal = 2]) {
@ -311,6 +313,15 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
final scrollControllerQuestion = useScrollController(); //
var vnHandWritings = useValueNotifier<List<dynamic>>([]);
useEffect(() {
var listenStream = sateData.data.listen((e) {
//
sateData.handwritings = [];
vnHandWritings.value = sateData.handwritings;
});
return () => listenStream.cancel();
}, []);
ImageStream? imageStream;
var imageStreamListener = useState<ImageStreamListener>(ImageStreamListener((ImageInfo info, bool _) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@ -335,18 +346,59 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
bool? res = await showDialog<bool>(
context: context,
builder: (context1) {
return AlertDialog(content: quickText("是否撤销上次批阅批注痕迹"), actions: <Widget>[
return AlertDialog(content: quickText("是否撤销全部批注痕迹?"), actions: <Widget>[
TextButton(child: quickText("取消"), onPressed: () => Navigator.pop(context1, false)),
TextButton(child: quickText("确定", color: Theme.of(context).primaryColor), onPressed: () => Navigator.pop(context1, true))
]);
},
);
if (res == true) vnHandWritings.value = [];
} else {
//
if (sateData.data.value?.showZgtAnnotate != null) {
await showDialog<bool>(
context: context,
builder: (context1) {
return AlertDialog(content: quickText("是否撤销上次批阅批注痕迹?"), actions: <Widget>[
TextButton(child: quickText("取消"), onPressed: () => Navigator.pop(context1, false)),
TextButton(
child: quickText("确定", color: Theme.of(context).primaryColor),
onPressed: () {
Navigator.pop(context1, true);
sateData.data.value?.zgtAnnotate = null;
sateData.data.value?.showZgtAnnotate = null;
},
)
]);
},
);
}
}
return;
}
//
if (annotationsData.isEmpty) {
//
if (sateData.data.value?.showZgtAnnotate != null) {
await showDialog<bool>(
context: context,
builder: (context1) {
return AlertDialog(content: quickText("是否撤销上次批阅批注痕迹?"), actions: <Widget>[
TextButton(child: quickText("取消"), onPressed: () => Navigator.pop(context1, false)),
TextButton(
child: quickText("确定", color: Theme.of(context).primaryColor),
onPressed: () {
Navigator.pop(context1, true);
sateData.data.value?.zgtAnnotate = null;
sateData.data.value?.showZgtAnnotate = null;
},
)
]);
},
);
}
} else {
var index = _findTargetIndex(annotationsData, null);
if (index != -1) {
annotationsData = annotationsData.sublist(0, index + 1);
@ -354,6 +406,7 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
} else {
annotationsData = [];
}
}
vnHandWritings.value = annotationsData;
});
@ -378,6 +431,7 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
alignment: Alignment.center,
child: Obx(() {
var imageUrl = sateData.data.value?.zgtAnswer;
var showZgtAnnotate = sateData.data.value?.showZgtAnnotate;
if (imageUrl == null) return const SizedBox();
return GestureDetector(
@ -399,6 +453,7 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
var imageScale = sateData.imageScale.value;
if (imageScale == null || !annotationState.pen.value) return;
vnHandWritings.value.add(null); // 线
sateData.handwritings = vnHandWritings.value; //
},
onPointerMove: (PointerMoveEvent event) {
var imageScale = sateData.imageScale.value;
@ -408,28 +463,43 @@ class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar
if (dy > imageScale.actualImgHeight || dy < 0) return; //
vnHandWritings.value = List.from(vnHandWritings.value)..add(localPosition);
sateData.handwritings = vnHandWritings.value;
},
child: RepaintBoundary(
child: CustomPaint(
foregroundPainter: DrawingPainter(ctrl: vnHandWritings),
child: RepaintBoundary(
child: $TheCachedNetworkImage(
child: Stack(
children: [
$TheCachedNetworkImage(
imageUrl: imageUrl,
(context, imageProvider) {
Image imageWidget = Image(image: imageProvider, fit: BoxFit.fitWidth);
imageStream?.removeListener(imageStreamListener.value);
imageStream = imageWidget.image.resolve(const ImageConfiguration())..addListener(imageStreamListener.value);
return imageWidget;
},
imageUrl: RequestConfig.imgUrl + imageUrl,
),
),
RepaintBoundary(
key: logic.pictureOverviewKey,
child: CustomPaint(
foregroundPainter: DrawingPainter(ctrl: vnHandWritings),
size: Size(
sateData.imageScale.value?.actualImgWidth ?? 0,
sateData.imageScale.value?.actualImgHeight ?? 0,
),
child: showZgtAnnotate != null
? $TheCachedNetworkImage(
imageUrl: showZgtAnnotate,
(_, imageProvider) => Image(image: imageProvider, fit: BoxFit.fitWidth),
)
: null,
),
),
],
),
),
),
),
);
}));
}),
);
}
}

View File

@ -1,7 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:ui' as ui;
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_param.dart';
import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_result.dart';
@ -9,6 +14,7 @@ import 'package:making_school_asignment_app/common/job/marking_models/do_test_qu
import 'package:making_school_asignment_app/common/job/marking_models/review_submission_params.dart';
import 'package:making_school_asignment_app/common/mixins/request_tool_mixin.dart';
import 'package:making_school_asignment_app/common/utils/toast_utils.dart';
import 'package:making_school_asignment_app/common/utils/upload_oss_img_utils.dart';
import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
//
@ -23,6 +29,7 @@ class HomeworkReviewState {
late Rx<double> slide; //
late bool? panQuestView;
late Rx<TestQuestionsImageInfo?> imageScale;
List<dynamic> handwritings = [];
// late String dateEnd = '';
// late int knowledgeId = 0;
@ -48,6 +55,7 @@ class HomeworkReviewBinding extends Bindings {
}
class HomeworkReviewLogic extends GetxController with RequestToolMixin {
final GlobalKey pictureOverviewKey = GlobalKey();
late StreamSubscription<DoPaperDetailsParam> _paramListen;
late StreamSubscription<DoPaperDetailsResult?> _dataListen;
final HomeworkReviewState state = HomeworkReviewState();
@ -92,6 +100,7 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin {
// item.topHeight = itemPre.height;
// }
state.data.value = data;
state.handwritings = [];
state.studentQuestions.value = data.studentQuestions;
} finally {
if (timerControl.isActive) timerControl.cancel();
@ -140,11 +149,63 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin {
await submit(context);
}
//
Future<String?> saveImage(context) async {
if (state.handwritings.isEmpty) return null;
try {
var data = state.data.value;
var param = state.param.value;
if (data == null) return null;
// OSS url
String imgKey = UploadOssImgUtils.getInstance().setImgKey(param.homeworkId, data.studentId.toString(), data.templateId.toString());
var resUrl = await getClient().getOssPresignedUri(imgKey);
if (resUrl == null) return null;
//
RenderRepaintBoundary? boundary = pictureOverviewKey.currentContext!.findRenderObject() as RenderRepaintBoundary?;
if (boundary == null) return null;
// double dpr = MediaQuery.of(context).devicePixelRatio;
double pixelRatio = MediaQuery.of(context).devicePixelRatio;
var imageScale = state.imageScale.value;
if (imageScale != null) {
//
pixelRatio = imageScale.imageWidth / imageScale.boxWidth;
}
ui.Image image = await boundary.toImage(pixelRatio: pixelRatio);
ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
if (byteData == null) return null;
Dio dio = Dio();
dio.options.contentType = null;
List<int> bytes = byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes);
await dio.put(
resUrl,
data: Stream.fromIterable(bytes.map((e) => [e])),
options: Options(contentType: null, headers: {Headers.contentLengthHeader: bytes.length}),
);
// var res = await getClient().uploadImag(resUrl, file);
return imgKey;
} catch (e) {
print('图片上传失败');
print(e.toString());
ToastUtils.getFluttertoast(msg: '图片上传失败', context: context);
} finally {
// ToastUtils.dismiss();
}
return null;
}
//
/// allPairs
Future<void> submit(BuildContext context) async {
var data = state.data.value;
if (data == null) return;
if (state.data.value?.studentQuestions.isEmpty ?? true) return;
var studentQuestions = state.data.value!.studentQuestions;
var noRatingElement = studentQuestions.firstWhereOrNull((e) => e.studentScore == null);
@ -152,12 +213,19 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin {
ToastUtils.showInfo('${noRatingElement.questionNo}题请评分');
return;
}
//
String? zgtAnnotate = state.data.value?.zgtAnnotate;
String? newzgtAnnotate = await saveImage(context);
if (newzgtAnnotate != null) zgtAnnotate = newzgtAnnotate;
// TODO
await getClient()
.reviewSubmission(ReviewSubmissionParams(
homeworkId: state.param.value.homeworkId,
templateId: data.templateId,
studentId: data.studentId,
zgtAnnotate: zgtAnnotate,
studentScores: studentQuestions.map((e) {
var studentScore = e.studentScore!;
return StudentScores(
@ -191,7 +259,10 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin {
);
return;
}
state.param.value = DoPaperDetailsParam.fromJson(state.param.value.toJson());
var newParams = DoPaperDetailsParam.fromJson(state.param.value.toJson());
newParams.templateId = null;
newParams.studentId = null;
state.param.value = newParams;
});
}
}

View File

@ -26,7 +26,6 @@ class _HomeworkReviewState extends State<HomeworkReview> {
@override
void initState() {
WidgetsFlutterBinding.ensureInitialized();
// SystemChrome.setPreferredOrientations([DeviceOrientation.portraitDown]);
super.initState();
}

View File

@ -83,6 +83,8 @@ dependencies:
flutter_hooks: ^0.20.5
flutter_spinkit: ^5.2.1
event_bus: ^2.0.0
path_provider: ^2.1.3
uuid: ^3.0.7
dev_dependencies:
flutter_test: