Compare commits
4 Commits
c5ec91b295
...
63658cf6e1
| Author | SHA1 | Date |
|---|---|---|
|
|
63658cf6e1 | |
|
|
9517df9b0e | |
|
|
db43b4a88f | |
|
|
4b92774cc8 |
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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({
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"6月12日 13:54
|
||||||
|
return 'hms-annotate/$homeworkId/$studentId/$templateId.png';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue