Compare commits

..

3 Commits

Author SHA1 Message Date
1147192855@qq.com 9517df9b0e no message 2024-06-18 10:17:04 +08:00
1147192855@qq.com db43b4a88f no message 2024-06-14 18:28:58 +08:00
1147192855@qq.com 4b92774cc8 no message 2024-06-14 17:17:24 +08:00
13 changed files with 563 additions and 189 deletions

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:dio/dio.dart' hide Headers; 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_param.dart';
import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_result.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") @POST("/api/hms/Annotate/AnnotateSubmit")
Future reviewSubmission(@Body() ReviewSubmissionParams param); 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

@ -0,0 +1,22 @@
import 'package:json_annotation/json_annotation.dart';
part 'do_paper_bus.g.dart';
// BUG
@JsonSerializable()
class BottomOperationBar extends Object {
@JsonKey(name: 'revoke_pre_step') //
bool revokePreStep;
@JsonKey(name: 'revoke_all') //
bool revokeAll;
BottomOperationBar({
required this.revokePreStep,
required this.revokeAll,
});
factory BottomOperationBar.fromJson(Map<String, dynamic> srcJson) => _$BottomOperationBarFromJson(srcJson);
Map<String, dynamic> toJson() => _$BottomOperationBarToJson(this);
}

View File

@ -1,6 +1,7 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get/get_connect/http/src/request/request.dart'; import 'package:get/get_connect/http/src/request/request.dart';
import 'package:json_annotation/json_annotation.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'; part 'do_paper_details_result.g.dart';
@ -39,12 +40,19 @@ class DoPaperDetailsResult extends Object {
@JsonKey(name: 'zgtAnswer') // @JsonKey(name: 'zgtAnswer') //
String zgtAnswer; String zgtAnswer;
@JsonKey(name: 'zgtAnnotate') // @JsonKey(name: 'showZgtAnnotate') //
String? showZgtAnnotate;
@JsonKey(name: 'zgtAnnotate') //
String? zgtAnnotate; String? zgtAnnotate;
@JsonKey(name: 'lastAnswerTime') @JsonKey(name: 'lastAnswerTime') //
String lastAnswerTime; String lastAnswerTime;
//
@JsonKey(name: 'annotateTime')
String? annotateTime;
@JsonKey(name: 'isFav') @JsonKey(name: 'isFav')
bool isFav; bool isFav;
@ -78,6 +86,8 @@ class DoPaperDetailsResult extends Object {
this.templateIdKeys, this.templateIdKeys,
this.templateIdKeyMap, this.templateIdKeyMap,
this.priority, this.priority,
this.annotateTime,
this.showZgtAnnotate,
) { ) {
if (templateIds.keys.isNotEmpty) { if (templateIds.keys.isNotEmpty) {
templateIdKeys = templateIds.keys.map((e) => int.parse(e)).toList(); 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); var currentStudent = students.firstWhereOrNull((e) => e.id == studentId);
if (currentStudent != null) priority = currentStudent.isPriority; 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); factory DoPaperDetailsResult.fromJson(Map<String, dynamic> srcJson) => _$DoPaperDetailsResultFromJson(srcJson);

View File

@ -4,6 +4,9 @@ part 'do_test_questions_image_info.g.dart';
@JsonSerializable() @JsonSerializable()
class TestQuestionsImageInfo extends Object { class TestQuestionsImageInfo extends Object {
@JsonKey(name: 'templateId')
int? templateId;
@JsonKey(name: 'boxHeight') @JsonKey(name: 'boxHeight')
double boxHeight; double boxHeight;
@ -28,6 +31,12 @@ class TestQuestionsImageInfo extends Object {
@JsonKey(name: 'remainingHeight') // .() @JsonKey(name: 'remainingHeight') // .()
double remainingHeight; double remainingHeight;
@JsonKey(name: 'imageHeightOffsetStart') // Y
double imageHeightOffsetStart;
@JsonKey(name: 'imageHeightOffsetend') // Y
double imageHeightOffsetend;
// //
@JsonKey(name: 'scaleRatio') @JsonKey(name: 'scaleRatio')
double scaleRatio; double scaleRatio;
@ -42,17 +51,22 @@ class TestQuestionsImageInfo extends Object {
required this.boxHeight, required this.boxHeight,
required this.imageWidth, required this.imageWidth,
required this.imageHeight, required this.imageHeight,
this.templateId,
this.scaleRatio = 1, this.scaleRatio = 1,
this.zoom = 1, this.zoom = 1,
this.actualImgWidth = 0, this.actualImgWidth = 0,
this.actualImgHeight = 0, this.actualImgHeight = 0,
this.remainingHeight = 0, this.remainingHeight = 0,
this.imageHeightOffsetStart = 0,
this.imageHeightOffsetend = 0,
}) { }) {
// //
scaleRatio = boxWidth / imageWidth; scaleRatio = boxWidth / imageWidth;
actualImgWidth = imageWidth * scaleRatio; actualImgWidth = imageWidth * scaleRatio;
actualImgHeight = imageHeight * scaleRatio; actualImgHeight = imageHeight * scaleRatio;
remainingHeight = boxHeight - actualImgHeight; remainingHeight = boxHeight - actualImgHeight;
imageHeightOffsetStart = remainingHeight / 2;
imageHeightOffsetend = imageHeightOffsetStart + actualImgHeight;
} }
factory TestQuestionsImageInfo.fromJson(Map<String, dynamic> srcJson) => _$TestQuestionsImageInfoFromJson(srcJson); factory TestQuestionsImageInfo.fromJson(Map<String, dynamic> srcJson) => _$TestQuestionsImageInfoFromJson(srcJson);

View File

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

View File

@ -0,0 +1,31 @@
// event_bus
import 'dart:async';
import 'package:event_bus/event_bus.dart';
import 'package:making_school_asignment_app/common/utils/event_bus_utils.dart';
mixin EventBusMixin<T> {
StreamSubscription? _subscription;
static final EventBusUtils _exampleUtil = EventBusUtils();
static final EventBus _eBus = _exampleUtil.getEventBus();
/*
* 线
* @param {Function} callback
*/
/// 线 @param {Function} callback
StreamSubscription eventOn<T>({required void Function(T) callback}) {
_subscription = _eBus.on<T>().listen(callback);
return _subscription!;
}
// 线
void eventFire({required T model}) {
_exampleUtil.toFire<T>(model);
}
// 线
void eventCancel() {
_subscription?.cancel();
}
}

View File

@ -0,0 +1,26 @@
import 'package:event_bus/event_bus.dart';
//
typedef EventCallback = void Function(dynamic arg);
///线
class EventBusUtils {
late final EventBus _eventBus;
//
static final EventBusUtils _instance = EventBusUtils._internal();
factory EventBusUtils() => _instance;
EventBusUtils._internal() {
_eventBus = EventBus();
}
//
EventBus getEventBus() {
return _eventBus;
}
//
void toFire<T>(T model) {
_eventBus.fire(model);
}
}

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

@ -2,6 +2,8 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:making_school_asignment_app/common/job/marking_models/do_paper_bus.dart';
import 'package:making_school_asignment_app/common/mixins/event_bus_mixin.dart';
import 'package:making_school_asignment_app/common/utils/anti_shake_throttling.dart'; import 'package:making_school_asignment_app/common/utils/anti_shake_throttling.dart';
import 'package:making_school_asignment_app/common/utils/utils.dart'; import 'package:making_school_asignment_app/common/utils/utils.dart';
import 'package:making_school_asignment_app/page/global_widget/my_text.dart'; import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
@ -16,7 +18,7 @@ class BottomAnnotationSwitch extends StatefulWidget {
State<BottomAnnotationSwitch> createState() => _BottomAnnotationSwitchJobState(); State<BottomAnnotationSwitch> createState() => _BottomAnnotationSwitchJobState();
} }
class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch> with SingleTickerProviderStateMixin { class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch> with SingleTickerProviderStateMixin, EventBusMixin<BottomOperationBar> {
late AnimationController _animationController; // late AnimationController _animationController; //
final _homeworkLogic = Get.find<HomeworkReviewLogic>(); final _homeworkLogic = Get.find<HomeworkReviewLogic>();
final _logicControl = Get.find<HomeworkReviewLogic>().annotationState; final _logicControl = Get.find<HomeworkReviewLogic>().annotationState;
@ -39,6 +41,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch> with
_animationController.reverse(); _animationController.reverse();
} }
}); });
super.initState(); super.initState();
} }
@ -48,6 +51,8 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch> with
..removeListener(toUp) ..removeListener(toUp)
..dispose(); ..dispose();
_opControllisten?.cancel(); _opControllisten?.cancel();
eventCancel();
super.dispose(); super.dispose();
} }
@ -132,7 +137,7 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch> with
], ],
), ),
), ),
Container(width: double.infinity, color: Colors.white, height: 0.5.h), Container(width: double.infinity, color: Colors.white, height: 0.35.h),
Expanded( Expanded(
child: Row( child: Row(
children: [ children: [
@ -142,19 +147,26 @@ class _BottomAnnotationSwitchJobState extends State<BottomAnnotationSwitch> with
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
child: IconButton( child: IconButton(
onPressed: () => easyThrottle('homework_bottom_action_bar_annotations', () {}), onPressed: () => easyThrottle(
'homework_bottom_action_bar_annotations',
() => eventFire(model: BottomOperationBar(revokeAll: false, revokePreStep: true)),
),
icon: Icon(const IconData(0xe638, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor), icon: Icon(const IconData(0xe638, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor),
), ),
), ),
), ),
Container(width: 0.3.w, height: double.infinity, color: Colors.white), Container(width: 0.3.w, height: double.infinity, color: Colors.white),
//
Expanded( Expanded(
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
child: IconButton( child: IconButton(
icon: Icon(const IconData(0xe637, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor), icon: Icon(const IconData(0xe637, fontFamily: "AlibabaIcon"), size: iconSize, color: defaultColor),
onPressed: () => easyThrottle('homework_bottom_action_bar_annotations', () {}), onPressed: () => easyThrottle(
'homework_bottom_action_bar_annotations',
() => eventFire(model: BottomOperationBar(revokeAll: true, revokePreStep: false)),
),
), ),
), ),
), ),

View File

@ -6,9 +6,11 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:functional_widget_annotation/functional_widget_annotation.dart'; import 'package:functional_widget_annotation/functional_widget_annotation.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:making_school_asignment_app/common/config/request_config.dart'; import 'package:making_school_asignment_app/common/config/request_config.dart';
import 'package:making_school_asignment_app/common/job/marking_models/do_paper_bus.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_param.dart';
import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_result.dart'; import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_result.dart';
import 'package:making_school_asignment_app/common/job/marking_models/do_test_questions_image_info.dart'; import 'package:making_school_asignment_app/common/job/marking_models/do_test_questions_image_info.dart';
import 'package:making_school_asignment_app/common/mixins/event_bus_mixin.dart';
import 'package:making_school_asignment_app/common/utils/anti_shake_throttling.dart'; import 'package:making_school_asignment_app/common/utils/anti_shake_throttling.dart';
import 'package:making_school_asignment_app/common/utils/cached_network_img.dart'; import 'package:making_school_asignment_app/common/utils/cached_network_img.dart';
import 'package:making_school_asignment_app/page/global_widget/my_text.dart'; import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
@ -30,6 +32,7 @@ class QuestionPaperView extends StatefulWidget {
class _QuestionPaperViewState extends State<QuestionPaperView> { class _QuestionPaperViewState extends State<QuestionPaperView> {
final logic = Get.find<HomeworkReviewLogic>(); final logic = Get.find<HomeworkReviewLogic>();
final sateData = Get.find<HomeworkReviewLogic>().state; final sateData = Get.find<HomeworkReviewLogic>().state;
final annotationState = Get.find<HomeworkReviewLogic>().annotationState;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -44,7 +47,7 @@ class _QuestionPaperViewState extends State<QuestionPaperView> {
return Stack( return Stack(
children: [ children: [
// //
$QuestionImageView(maxWidth, maxHeight, sateData), QuestionImageView(maxWidth, maxHeight, sateData, annotationState, logic),
// //
Positioned(right: 3.w, bottom: 4.h, child: const $ContinueToReview(isFloatingAction: true)), Positioned(right: 3.w, bottom: 4.h, child: const $ContinueToReview(isFloatingAction: true)),
// //
@ -54,7 +57,6 @@ class _QuestionPaperViewState extends State<QuestionPaperView> {
child: Obx(() { child: Obx(() {
LastPage? lastPageVal = sateData.data.value?.lastPage; LastPage? lastPageVal = sateData.data.value?.lastPage;
if (lastPageVal == null) return const SizedBox(); if (lastPageVal == null) return const SizedBox();
return FloatingActionButton( return FloatingActionButton(
heroTag: '点击前往上一题', heroTag: '点击前往上一题',
tooltip: '点击前往上一题', tooltip: '点击前往上一题',
@ -78,7 +80,6 @@ class _QuestionPaperViewState extends State<QuestionPaperView> {
child: Obx(() { child: Obx(() {
NextPage? nextPageVal = sateData.data.value?.nextPage; NextPage? nextPageVal = sateData.data.value?.nextPage;
if (nextPageVal == null) return const SizedBox(); if (nextPageVal == null) return const SizedBox();
return FloatingActionButton( return FloatingActionButton(
heroTag: '点击前往下一题', heroTag: '点击前往下一题',
tooltip: '点击前往下一题', tooltip: '点击前往下一题',
@ -111,9 +112,13 @@ Widget $questionNumberView(BuildContext context, HomeworkReviewState sateData) {
final scrollControllerNum = useScrollController(); // final scrollControllerNum = useScrollController(); //
useEffect(() { useEffect(() {
StreamSubscription listenVal = sateData.slide.listen((e) => scrollControllerNum.jumpTo(e)); scrollControllerNum.addListener(() {
if (sateData.panQuestView == false) sateData.slide.value = scrollControllerNum.offset;
});
var listenVal = sateData.slide.listen((e) {
if (e != scrollControllerNum.offset && sateData.panQuestView == true) scrollControllerNum.jumpTo(e);
});
//
return () { return () {
listenVal.cancel(); listenVal.cancel();
}; };
@ -132,25 +137,31 @@ Widget $questionNumberView(BuildContext context, HomeworkReviewState sateData) {
) )
], ],
), ),
child: SingleChildScrollView( child: GestureDetector(
controller: scrollControllerNum, onPanDown: (_) => sateData.panQuestView = false,
physics: const BouncingScrollPhysics(), child: SingleChildScrollView(
padding: EdgeInsets.zero, controller: scrollControllerNum,
scrollDirection: Axis.vertical, // physics: const BouncingScrollPhysics(),
child: Obx(() { padding: EdgeInsets.zero,
var imageVal = sateData.imageScale.value; scrollDirection: Axis.vertical, //
if (imageVal == null) return const SizedBox(); child: Obx(() {
var studentQuestions = sateData.studentQuestions.value; var imageVal = sateData.imageScale.value;
print('书哈哈哈...'); if (imageVal == null) return const SizedBox();
return Padding( var studentQuestions = sateData.studentQuestions.value;
padding: EdgeInsets.only(top: imageVal.remainingHeight > 0 ? imageVal.remainingHeight / 2 : 0),
child: Column( var boxHeight = imageVal.boxHeight;
mainAxisAlignment: MainAxisAlignment.start, var actualImgHeight = imageVal.actualImgHeight; //
children: studentQuestions?.asMap().keys.map((e) => $ScoringQuestionsView(studentQuestions[e], imageVal.scaleRatio)).toList() ?? [],
), return Container(
); height: boxHeight > actualImgHeight ? boxHeight : actualImgHeight,
}), padding: EdgeInsets.only(top: imageVal.remainingHeight > 0 ? imageVal.remainingHeight / 2 : 0),
), child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: studentQuestions?.asMap().keys.map((e) => $ScoringQuestionsView(studentQuestions[e], imageVal.scaleRatio)).toList() ?? [],
),
);
}),
)),
); );
} }
@ -172,86 +183,85 @@ Widget $scoringQuestionsView(BuildContext context, StudentQuestions item, double
}, []); }, []);
var padinVal = item.correctRate > 0 ? EdgeInsets.only(top: 6.4.h) : EdgeInsets.symmetric(vertical: 2.h); var padinVal = item.correctRate > 0 ? EdgeInsets.only(top: 6.4.h) : EdgeInsets.symmetric(vertical: 2.h);
return Container( return Container(
height: item.height * scaleRatio, height: item.height * scaleRatio,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
child: Stack( child: Stack(
alignment: const FractionalOffset(0, 0), alignment: const FractionalOffset(0, 0),
children: [ children: [
Container( Row(
color: Colors.yellow, crossAxisAlignment: CrossAxisAlignment.start,
child: Row( children: [
crossAxisAlignment: CrossAxisAlignment.start, //
children: [ Expanded(
// child: ElevatedButton(
Expanded( style: ElevatedButton.styleFrom(
child: ElevatedButton( padding: EdgeInsets.zero,
style: ElevatedButton.styleFrom( tapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: EdgeInsets.zero, backgroundColor: const Color.fromRGBO(237, 237, 237, 1), //
tapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), //
backgroundColor: const Color.fromRGBO(237, 237, 237, 1), // ),
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), // child: Padding(
), padding: padinVal,
child: Padding( child: Icon(
padding: padinVal, size: 22.sp,
child: Icon( color: studentScore.value == 2 ? const Color.fromRGBO(255, 152, 0, 1) : const Color.fromRGBO(114, 114, 114, 1),
size: 22.sp, const IconData(0xe62b, fontFamily: "AlibabaIcon"),
color: studentScore.value == 2 ? const Color.fromRGBO(255, 152, 0, 1) : const Color.fromRGBO(114, 114, 114, 1),
const IconData(0xe62b, fontFamily: "AlibabaIcon"),
),
),
onPressed: () => easyThrottle('scoring_homework_questions', () {
studentScore.value = studentScore.value == 2 ? null : 2;
}),
), ),
), ),
// onPressed: () => easyThrottle('scoring_homework_questions', () {
Expanded( studentScore.value = studentScore.value == 2 ? null : 2;
child: ElevatedButton( }),
style: ElevatedButton.styleFrom( ),
padding: EdgeInsets.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: const Color.fromRGBO(237, 237, 237, 1), //
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), //
),
child: Padding(
padding: padinVal,
child: Icon(
size: 22.sp,
color: studentScore.value == 1 ? const Color.fromRGBO(255, 152, 0, 1) : const Color.fromRGBO(114, 114, 114, 1),
const IconData(0xe62c, fontFamily: "AlibabaIcon"),
),
),
onPressed: () => easyThrottle('scoring_homework_questions', () {
studentScore.value = studentScore.value == 1 ? null : 1;
}),
),
),
//
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: const Color.fromRGBO(237, 237, 237, 1), //
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), //
),
child: Padding(
padding: padinVal,
child: Icon(
size: 22.sp,
color: studentScore.value == 0 ? const Color.fromRGBO(255, 152, 0, 1) : const Color.fromRGBO(114, 114, 114, 1),
const IconData(0xe62a, fontFamily: "AlibabaIcon"),
),
),
onPressed: () => easyThrottle('scoring_homework_questions', () {
studentScore.value = studentScore.value == 0 ? null : 0;
}),
),
),
],
), ),
), //
Row( Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: const Color.fromRGBO(237, 237, 237, 1), //
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), //
),
child: Padding(
padding: padinVal,
child: Icon(
size: 22.sp,
color: studentScore.value == 1 ? const Color.fromRGBO(255, 152, 0, 1) : const Color.fromRGBO(114, 114, 114, 1),
const IconData(0xe62c, fontFamily: "AlibabaIcon"),
),
),
onPressed: () => easyThrottle('scoring_homework_questions', () {
studentScore.value = studentScore.value == 1 ? null : 1;
}),
),
),
//
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
backgroundColor: const Color.fromRGBO(237, 237, 237, 1), //
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero), //
),
child: Padding(
padding: padinVal,
child: Icon(
size: 22.sp,
color: studentScore.value == 0 ? const Color.fromRGBO(255, 152, 0, 1) : const Color.fromRGBO(114, 114, 114, 1),
const IconData(0xe62a, fontFamily: "AlibabaIcon"),
),
),
onPressed: () => easyThrottle('scoring_homework_questions', () {
studentScore.value = studentScore.value == 0 ? null : 0;
}),
),
),
],
),
IgnorePointer(
// 穿
child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
SizedBox(width: 1.1.w), SizedBox(width: 1.1.w),
@ -270,91 +280,227 @@ Widget $scoringQuestionsView(BuildContext context, StudentQuestions item, double
), ),
) )
], ],
) ),
], ),
)); ],
),
);
} }
// //
@hwidget class QuestionImageView extends HookWidget with EventBusMixin<BottomOperationBar> {
Widget $questionImageView(double maxWidth, double maxHeight, HomeworkReviewState sateData) { final double maxWidth;
final scrollControllerQuestion = useScrollController(); // final double maxHeight;
var vnHandWritings = useValueNotifier<List<dynamic>>([]); final HomeworkReviewLogic logic;
ImageStream? imageStream; final HomeworkReviewState sateData;
var imageStreamListener = useState<ImageStreamListener>(ImageStreamListener((ImageInfo info, bool _) { final HomeworkReviewAnnotationsControlState annotationState;
WidgetsBinding.instance.addPostFrameCallback((_) { QuestionImageView(this.maxWidth, this.maxHeight, this.sateData, this.annotationState, this.logic, {super.key});
sateData.imageScale.value = TestQuestionsImageInfo(
boxWidth: maxWidth,
boxHeight: maxHeight,
imageWidth: info.image.width.toDouble(),
imageHeight: info.image.height.toDouble(),
url: sateData.data.value!.zgtAnswer,
);
});
}));
// ///
useEffect(() { int _findTargetIndex<T>(List<T> list, T target, [int reciprocal = 2]) {
scrollControllerQuestion.addListener(() { List<int> indexs = [];
// for (int i = list.length - 1; i >= 0; i--) {
sateData.slide.value = scrollControllerQuestion.offset; if (list[i] == target) {
}); indexs.add(i);
// if (indexs.length == reciprocal) return i;
return () { }
imageStream?.removeListener(imageStreamListener.value); }
}; return -1;
}, []); }
print('这里是加载了..............'); @override
return Container( Widget build(BuildContext context) {
height: maxHeight, final scrollControllerQuestion = useScrollController(); //
// padding: EdgeInsets.only(bottom: 2.h, top: 2.h), var vnHandWritings = useValueNotifier<List<dynamic>>([]);
alignment: Alignment.center,
child: SingleChildScrollView( useEffect(() {
controller: scrollControllerQuestion, var listenStream = sateData.data.listen((e) {
physics: const BouncingScrollPhysics(), //
padding: EdgeInsets.zero, sateData.handwritings = [];
scrollDirection: Axis.vertical, // vnHandWritings.value = sateData.handwritings;
});
return () => listenStream.cancel();
}, []);
ImageStream? imageStream;
var imageStreamListener = useState<ImageStreamListener>(ImageStreamListener((ImageInfo info, bool _) {
WidgetsBinding.instance.addPostFrameCallback((_) {
sateData.imageScale.value = TestQuestionsImageInfo(
templateId: sateData.data.value?.templateId,
boxWidth: maxWidth,
boxHeight: maxHeight,
imageWidth: info.image.width.toDouble(),
imageHeight: info.image.height.toDouble(),
url: sateData.data.value!.zgtAnswer,
);
});
}));
//
useEffect(() {
eventOn(callback: (BottomOperationBar e) async {
var annotationsData = vnHandWritings.value; //
if (e.revokeAll) {
//
if (annotationsData.isNotEmpty) {
bool? res = 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))
]);
},
);
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);
annotationsData = List.from(annotationsData);
} else {
annotationsData = [];
}
}
vnHandWritings.value = annotationsData;
});
scrollControllerQuestion.addListener(() {
sateData.slide.value = scrollControllerQuestion.offset;
}); //
var listenVal = sateData.slide.listen((e) {
if (e != scrollControllerQuestion.offset && sateData.panQuestView == false) scrollControllerQuestion.jumpTo(e);
});
//
return () {
listenVal.cancel();
imageStream?.removeListener(imageStreamListener.value);
};
}, []);
return Container(
height: maxHeight,
// padding: EdgeInsets.only(bottom: 2.h, top: 2.h),
alignment: Alignment.center,
child: Obx(() { child: Obx(() {
var imageUrl = sateData.data.value?.zgtAnswer; var imageUrl = sateData.data.value?.zgtAnswer;
var showZgtAnnotate = sateData.data.value?.showZgtAnnotate;
if (imageUrl == null) return const SizedBox(); if (imageUrl == null) return const SizedBox();
return Container( return GestureDetector(
decoration: BoxDecoration(boxShadow: [ onPanDown: (_) => sateData.panQuestView = true,
BoxShadow( child: SingleChildScrollView(
color: Colors.grey.withOpacity(0.2), controller: scrollControllerQuestion,
offset: Offset(-6.r, 1.r), //x轴偏移量 physics: !annotationState.pen.value ? const BouncingScrollPhysics() : const NeverScrollableScrollPhysics(),
blurRadius: 10.r, // padding: EdgeInsets.zero,
spreadRadius: 8.r // scrollDirection: Axis.vertical, //
) child: Container(
]), decoration: BoxDecoration(
child: Listener( boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.2), offset: Offset(-6.r, 1.r), blurRadius: 10.r, spreadRadius: 8.r)]),
behavior: HitTestBehavior.opaque, child: Listener(
onPointerUp: (PointerUpEvent details) {}, behavior: HitTestBehavior.opaque,
onPointerMove: (PointerMoveEvent details) {}, onPointerUp: (PointerUpEvent details) {
child: RepaintBoundary( //
child: CustomPaint( // activePointers--;
foregroundPainter: DrawingPainter(ctrl: vnHandWritings), // globalPosition = null;
child: RepaintBoundary( var imageScale = sateData.imageScale.value;
child: $TheCachedNetworkImage( if (imageScale == null || !annotationState.pen.value) return;
(context, imageProvider) { vnHandWritings.value.add(null); // 线
print('图片加载了..............'); sateData.handwritings = vnHandWritings.value; //
Image imageWidget = Image(image: imageProvider, fit: BoxFit.fitWidth); },
imageStream?.removeListener(imageStreamListener.value); onPointerMove: (PointerMoveEvent event) {
imageStream = imageWidget.image.resolve(const ImageConfiguration())..addListener(imageStreamListener.value); var imageScale = sateData.imageScale.value;
if (imageScale == null || !annotationState.pen.value) return;
Offset localPosition = event.localPosition;
var dy = localPosition.dy;
if (dy > imageScale.actualImgHeight || dy < 0) return; //
return imageWidget; vnHandWritings.value = List.from(vnHandWritings.value)..add(localPosition);
}, sateData.handwritings = vnHandWritings.value;
imageUrl: RequestConfig.imgUrl + imageUrl, },
), 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;
},
),
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,
),
),
],
), ),
), ),
), ),
), ),
); );
}), }),
), );
); }
} }
// //
@ -363,23 +509,23 @@ class DrawingPainter extends CustomPainter {
final Paint paintBrush = Paint() final Paint paintBrush = Paint()
..color = Colors.red ..color = Colors.red
..strokeCap = StrokeCap.round ..strokeCap = StrokeCap.round
..strokeWidth = 1.5.r; ..strokeWidth = 0.6.r;
DrawingPainter({required this.ctrl}) : super(repaint: ctrl); DrawingPainter({required this.ctrl}) : super(repaint: ctrl);
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
var points = ctrl.value; var points = ctrl.value;
var pointsLength = points.length; var pointsLength = points.length;
// for (int i = 0; i < pointsLength; i++) { for (int i = 0; i < pointsLength; i++) {
// GestureRecording item = points[i]; Offset? offsetData = points[i];
// Offset? offsetData = item.data; Offset? nextOffsetData = pointsLength - 1 == i ? null : points[i + 1];
// Offset? nextOffsetData = pointsLength - 1 == i ? null : points[i + 1].data; if (offsetData != null && nextOffsetData != null) canvas.drawLine(offsetData, nextOffsetData, paintBrush);
// if (offsetData != null && nextOffsetData != null) { }
// canvas.drawLine(offsetData, nextOffsetData, paintBrush);
// }
// }
} }
@override @override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false; bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
@override
bool shouldRebuildSemantics(CustomPainter oldDelegate) => false;
} }

View File

@ -1,7 +1,12 @@
import 'dart:async'; 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/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:get/get.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_param.dart';
import 'package:making_school_asignment_app/common/job/marking_models/do_paper_details_result.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/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/mixins/request_tool_mixin.dart';
import 'package:making_school_asignment_app/common/utils/toast_utils.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'; import 'package:making_school_asignment_app/page/global_widget/my_text.dart';
// //
@ -21,7 +27,9 @@ class HomeworkReviewState {
late Rx<DoPaperDetailsResult?> data; late Rx<DoPaperDetailsResult?> data;
late Rx<List<StudentQuestions>?> studentQuestions; late Rx<List<StudentQuestions>?> studentQuestions;
late Rx<double> slide; // late Rx<double> slide; //
late bool? panQuestView;
late Rx<TestQuestionsImageInfo?> imageScale; late Rx<TestQuestionsImageInfo?> imageScale;
List<dynamic> handwritings = [];
// late String dateEnd = ''; // late String dateEnd = '';
// late int knowledgeId = 0; // late int knowledgeId = 0;
@ -47,6 +55,7 @@ class HomeworkReviewBinding extends Bindings {
} }
class HomeworkReviewLogic extends GetxController with RequestToolMixin { class HomeworkReviewLogic extends GetxController with RequestToolMixin {
final GlobalKey pictureOverviewKey = GlobalKey();
late StreamSubscription<DoPaperDetailsParam> _paramListen; late StreamSubscription<DoPaperDetailsParam> _paramListen;
late StreamSubscription<DoPaperDetailsResult?> _dataListen; late StreamSubscription<DoPaperDetailsResult?> _dataListen;
final HomeworkReviewState state = HomeworkReviewState(); final HomeworkReviewState state = HomeworkReviewState();
@ -91,6 +100,7 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin {
// item.topHeight = itemPre.height; // item.topHeight = itemPre.height;
// } // }
state.data.value = data; state.data.value = data;
state.handwritings = [];
state.studentQuestions.value = data.studentQuestions; state.studentQuestions.value = data.studentQuestions;
} finally { } finally {
if (timerControl.isActive) timerControl.cancel(); if (timerControl.isActive) timerControl.cancel();
@ -139,11 +149,63 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin {
await submit(context); 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 /// allPairs
Future<void> submit(BuildContext context) async { Future<void> submit(BuildContext context) async {
var data = state.data.value; var data = state.data.value;
if (data == null) return; if (data == null) return;
if (state.data.value?.studentQuestions.isEmpty ?? true) return; if (state.data.value?.studentQuestions.isEmpty ?? true) return;
var studentQuestions = state.data.value!.studentQuestions; var studentQuestions = state.data.value!.studentQuestions;
var noRatingElement = studentQuestions.firstWhereOrNull((e) => e.studentScore == null); var noRatingElement = studentQuestions.firstWhereOrNull((e) => e.studentScore == null);
@ -151,12 +213,19 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin {
ToastUtils.showInfo('${noRatingElement.questionNo}题请评分'); ToastUtils.showInfo('${noRatingElement.questionNo}题请评分');
return; return;
} }
//
String? zgtAnnotate = state.data.value?.zgtAnnotate;
String? newzgtAnnotate = await saveImage(context);
if (newzgtAnnotate != null) zgtAnnotate = newzgtAnnotate;
// TODO // TODO
await getClient() await getClient()
.reviewSubmission(ReviewSubmissionParams( .reviewSubmission(ReviewSubmissionParams(
homeworkId: state.param.value.homeworkId, homeworkId: state.param.value.homeworkId,
templateId: data.templateId, templateId: data.templateId,
studentId: data.studentId, studentId: data.studentId,
zgtAnnotate: zgtAnnotate,
studentScores: studentQuestions.map((e) { studentScores: studentQuestions.map((e) {
var studentScore = e.studentScore!; var studentScore = e.studentScore!;
return StudentScores( return StudentScores(
@ -190,7 +259,10 @@ class HomeworkReviewLogic extends GetxController with RequestToolMixin {
); );
return; 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 @override
void initState() { void initState() {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
// SystemChrome.setPreferredOrientations([DeviceOrientation.portraitDown]);
super.initState(); super.initState();
} }

View File

@ -82,6 +82,9 @@ dependencies:
easy_debounce: ^2.0.3 # 防抖节流 easy_debounce: ^2.0.3 # 防抖节流
flutter_hooks: ^0.20.5 flutter_hooks: ^0.20.5
flutter_spinkit: ^5.2.1 flutter_spinkit: ^5.2.1
event_bus: ^2.0.0
path_provider: ^2.1.3
uuid: ^3.0.7
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: