diff --git a/.gitignore b/.gitignore index 4f58acc..57cd9a1 100644 --- a/.gitignore +++ b/.gitignore @@ -185,3 +185,5 @@ marking_app/lib/pages/homework_correction/eventBus/job_do_papers_switch_operatio marking_app/lib/pages/homework_correction/eventBus/job_notes_view_bus.g.dart marking_app/lib/pages/homework_correction/eventBus/job_notes_view_bus.g.dart marking_app/lib/common/model/job/job_report_model.g.dart +marking_app/lib/common/model/job/job_report_join_class.g.dart +marking_app/lib/pages/homework_correction/job_report.g.dart diff --git a/marking_app/assets/images/2.0x/job_report_scale.png b/marking_app/assets/images/2.0x/job_report_scale.png new file mode 100644 index 0000000..189f292 Binary files /dev/null and b/marking_app/assets/images/2.0x/job_report_scale.png differ diff --git a/marking_app/assets/images/3.0x/job_report_scale.png b/marking_app/assets/images/3.0x/job_report_scale.png new file mode 100644 index 0000000..74d4d1e Binary files /dev/null and b/marking_app/assets/images/3.0x/job_report_scale.png differ diff --git a/marking_app/assets/images/4.0x/job_report_scale.png b/marking_app/assets/images/4.0x/job_report_scale.png new file mode 100644 index 0000000..45ac3c9 Binary files /dev/null and b/marking_app/assets/images/4.0x/job_report_scale.png differ diff --git a/marking_app/assets/images/job_report_scale.png b/marking_app/assets/images/job_report_scale.png new file mode 100644 index 0000000..458b542 Binary files /dev/null and b/marking_app/assets/images/job_report_scale.png differ diff --git a/marking_app/lib/common/model/job/job_report_join_class.dart b/marking_app/lib/common/model/job/job_report_join_class.dart new file mode 100644 index 0000000..9260cfb --- /dev/null +++ b/marking_app/lib/common/model/job/job_report_join_class.dart @@ -0,0 +1,45 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'job_report_join_class.g.dart'; + +@JsonSerializable() +class JobReportJoinClass extends Object { + @JsonKey(name: 'schoolId') + int schoolId; + + @JsonKey(name: 'schoolName') + String schoolName; + + @JsonKey(name: 'gradeId') + int gradeId; + + @JsonKey(name: 'gradeName') + String gradeName; + + @JsonKey(name: 'graduationYear') + String graduationYear; + + @JsonKey(name: 'className') + String className; + + @JsonKey(name: 'toBeSubmitCount') + int toBeSubmitCount; + + @JsonKey(name: 'submitCount') + int submitCount; + + JobReportJoinClass( + this.schoolId, + this.schoolName, + this.gradeId, + this.gradeName, + this.graduationYear, + this.className, + this.toBeSubmitCount, + this.submitCount, + ); + + factory JobReportJoinClass.fromJson(Map srcJson) => _$JobReportJoinClassFromJson(srcJson); + + Map toJson() => _$JobReportJoinClassToJson(this); +} diff --git a/marking_app/lib/common/model/job/job_report_model.dart b/marking_app/lib/common/model/job/job_report_model.dart index b633b13..fc33424 100644 --- a/marking_app/lib/common/model/job/job_report_model.dart +++ b/marking_app/lib/common/model/job/job_report_model.dart @@ -8,13 +8,13 @@ class JobReportModel extends Object { int studentCount; @JsonKey(name: 'finishRate') - int finishRate; + double finishRate; @JsonKey(name: 'correctRate') - int correctRate; + double correctRate; @JsonKey(name: 'errorRate') - int errorRate; + double errorRate; @JsonKey(name: 'validCount') int validCount; @@ -81,7 +81,7 @@ class KnowledgeInfos extends Object { String knowledgeName; @JsonKey(name: 'rate') - int rate; + double rate; KnowledgeInfos( this.knowledgeId, @@ -112,13 +112,13 @@ class QuestionAnswerInfos extends Object { List finishInfos; @JsonKey(name: 'correctRate') - int correctRate; + double correctRate; @JsonKey(name: 'errorRate') - int errorRate; + double errorRate; @JsonKey(name: 'noAnswerRate') - int noAnswerRate; + double noAnswerRate; QuestionAnswerInfos( this.questionId, @@ -145,7 +145,7 @@ class FinishInfos extends Object { int finishCount; @JsonKey(name: 'correctRate') - int correctRate; + double correctRate; FinishInfos( this.title, @@ -170,10 +170,10 @@ class StudentAnswerInfos extends Object { int useTime; @JsonKey(name: 'correctRate') - int correctRate; + double correctRate; @JsonKey(name: 'finishRate') - int finishRate; + double finishRate; @JsonKey(name: 'noAnswerCount') int noAnswerCount; diff --git a/marking_app/lib/pages/homework_correction/job_report.dart b/marking_app/lib/pages/homework_correction/job_report.dart index 3ee26f8..ea04596 100644 --- a/marking_app/lib/pages/homework_correction/job_report.dart +++ b/marking_app/lib/pages/homework_correction/job_report.dart @@ -1,11 +1,19 @@ +import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:functional_widget_annotation/functional_widget_annotation.dart'; import 'package:marking_app/common/mixin/common.dart'; import 'package:marking_app/common/model/common/base_structure_result.dart'; +import 'package:marking_app/common/model/job/job_report_join_class.dart'; import 'package:marking_app/common/model/job/job_report_model.dart'; -import 'package:marking_app/utils/my_future_builder.dart'; +import 'package:marking_app/utils/index.dart'; import 'package:marking_app/utils/my_text.dart'; import 'package:marking_app/utils/request/rest_client.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; + +import '../../utils/flutter_wave_loading.dart'; + +part 'job_report.g.dart'; /// 作业报告 class JobReport extends StatefulWidget { @@ -20,32 +28,1180 @@ class JobReport extends StatefulWidget { class _JobReportState extends State with CommonMixin { late Future _future; // 考试试卷 + List? involveClasses = []; + JobReportJoinClass? classData; + @override void initState() { - _future = getReport(widget.id); + getInvolveClasses(); + _future = getReport(); super.initState(); } - Future getReport(int jobid) async { + Future getInvolveClasses() async { try { RestClient _client = await getClient(); - BaseStructureResult data = await _client.getJobReport(jobid); - if (data.success) { + BaseStructureResult> result = await _client.getJobReportJoinClasses(widget.id); + if (result.success) { + toUpState(setState, () { + involveClasses = [JobReportJoinClass(-1, '全部', -1, '全部', '全部', '全部', -1, -1), ...(result.data ?? [])]; + }, mounted); + } + } catch (e) { + print(e); + } + } + + Future getReport() async { + try { + RestClient _client = await getClient(); + Map param = classData?.toJson() ?? {}; + param['jobid'] = widget.id; + BaseStructureResult data = await _client.getJobReport(param); + if (!data.success) { throw Exception(data.message ?? '获取报告失败'); } return data.data; - } catch (e) {} + } catch (e) { + print(e); + } + return null; } @override Widget build(BuildContext context) { return Scaffold( + backgroundColor: Color.fromRGBO(245, 245, 245, 1), appBar: AppBar( - title: quickText(widget.title, size: 18.sp, color: Colors.white), + backgroundColor: Colors.white, + title: quickText(widget.title, size: 16.sp, color: Color.fromRGBO(51, 51, 51, 1)), + actions: [ + // 下拉框 + $DropdownSelection(involveClasses, classData, call: (JobReportJoinClass _classData) { + classData = _classData; + toUpState(setState, () => _future = getReport(), mounted); + }), + ], ), body: MyFutureBuilder.buildFutureBuilderOfSingleInstance(context, _future, (data) { - return Container(); + if (data == null) + return Container( + alignment: Alignment.center, + child: ElevatedButton( + child: quickText('点击再次发起请求', color: Colors.white), + onPressed: () { + toUpState(setState, () => _future = getReport(), mounted); + }, + ), + ); + + return ListView( + padding: EdgeInsets.symmetric(vertical: 4.h, horizontal: 10.w), + children: [ + // 顶部图形数据 + $TopGraphic(data), + // 掌握知识点的情况 + $MasterKnowledgePoint(data.knowledgeInfos), + // 掌握知识点的情况 + $OverallPerformance(data.studentCount, data.overallTitles), + // 单位时间答题情况 + $UnitTimeAnsweringSituation(data.questionAnswerInfos), + // 人员数据概况 + $PersonnelDataOverview(data.studentAnswerInfos), + ], + ); }), ); } } + +/// 下拉选择框 +@swidget +Widget $dropdownSelection(List? involveClasses, JobReportJoinClass? classData, + {required Function(JobReportJoinClass) call}) { + print('有几条数据+${involveClasses?.length}'); + if (involveClasses == null) return Container(child: quickText('点击重试')); // 点击重试 + return DropdownButton( + value: classData?.gradeId ?? -1, + style: TextStyle(color: Color.fromRGBO(89, 89, 89, 1), fontSize: 12.sp), + items: involveClasses.map((e) { + return DropdownMenuItem( + value: e.gradeId, + child: quickText(e.graduationYear, size: 12.sp, color: Colors.black), + ); + }).toList(), + onChanged: (int? value) { + if (value == null) return; + call(involveClasses.firstWhere((element) => element.gradeId == value)); + }, + ); +} + +/// 顶部图形数据 +@swidget +Widget $topGraphic(JobReportModel data) { + return Container( + margin: EdgeInsets.only(top: 16.h, bottom: 10.h), + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 14.h), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16.r)), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 整体评价 + Stack( + alignment: const FractionalOffset(0.5, 0.05), + children: [ + Padding( + padding: EdgeInsetsDirectional.all(30.r), + child: Container( + width: 188.r, + height: 188.r, + alignment: Alignment.center, + padding: EdgeInsets.all(20.r), + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, // 设置为圆形 + boxShadow: [ + BoxShadow( + color: const Color.fromRGBO(104, 136, 253, 1), + spreadRadius: 1.r, // 阴影扩散半径 + blurRadius: 8.r, // 阴影模糊半径 + offset: Offset(0.0, 0), // 阴影偏移量 + ) + ], + ), + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.all(13.r), + decoration: BoxDecoration( + shape: BoxShape.circle, // 设置为圆形 + color: Color.fromRGBO(215, 223, 255, 1), + ), + child: Container( + decoration: BoxDecoration( + // color: Color.fromRGBO(104, 136, 253, 1), + shape: BoxShape.circle, // 设置为圆形 + border: Border.all(color: Color.fromRGBO(0, 179, 134, 0)), + gradient: RadialGradient( + center: Alignment.center, // 渐变中心点 + colors: [ + Color.fromRGBO(104, 136, 253, 0.7), + Color.fromRGBO(104, 136, 253, 0.8), + Color.fromRGBO(104, 136, 253, 0.9), + Color.fromRGBO(104, 136, 253, 1), + ], + ), + ), + alignment: Alignment.center, + child: quickText(data.scoreTitle, color: Colors.white, size: 32.sp, fontWeight: FontWeight.bold), + ), + ), + ), + ), + Image.asset("assets/images/job_report_scale.png", width: 230.r, fit: BoxFit.fill), + ], + ), + + // 完成率、正确率、作业数量 + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Container( + width: 188.r, + height: 188.r, + padding: EdgeInsets.all(9.r), + decoration: BoxDecoration( + shape: BoxShape.circle, // 设置为圆形 + color: Color.fromRGBO(104, 136, 253, 1), + ), + child: Row( + children: [ + Expanded( + flex: 2, + child: quickText('${data.validCount}份', + color: Colors.white, size: 10.sp, fontWeight: FontWeight.bold)), + SizedBox(width: 1.2.w), + Expanded( + flex: 7, + child: Container( + padding: EdgeInsets.all(12.r), + decoration: BoxDecoration( + shape: BoxShape.circle, // 设置为圆形 + color: Color.fromRGBO(76, 199, 147, 1), + ), + child: Row( + children: [ + Expanded( + flex: 2, + child: quickText('${doubleToStringAsFixed(data.correctRate)}%', + color: Colors.white, size: 10.sp, fontWeight: FontWeight.bold)), + Expanded( + flex: 5, + child: Container( + width: double.infinity, + height: double.infinity, + alignment: Alignment.center, + decoration: BoxDecoration( + shape: BoxShape.circle, // 设置为圆形 + color: Color.fromRGBO(254, 151, 40, 1), + ), + child: quickText('${doubleToStringAsFixed(data.finishRate)}%', + color: Colors.white, size: 14.sp, fontWeight: FontWeight.bold), + ), + ), + ], + ), + ), + ) + ], + ), + ), + SizedBox(width: 20.w), + Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 10.r, + height: 10.r, + decoration: BoxDecoration( + shape: BoxShape.circle, // 设置为圆形 + color: Color.fromRGBO(254, 151, 40, 1), + ), + ), + SizedBox(width: 5.w), + quickText('作业完成率', color: Color.fromRGBO(114, 114, 114, 1), size: 12.sp), + SizedBox(width: 4.w), + quickText('${doubleToStringAsFixed(data.finishRate)}%', + color: Color.fromRGBO(72, 72, 72, 1), size: 13.sp, fontWeight: FontWeight.bold), + ], + ), + SizedBox(height: 14.h), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 10.r, + height: 10.r, + decoration: BoxDecoration( + shape: BoxShape.circle, // 设置为圆形 + color: Color.fromRGBO(76, 199, 147, 1), + ), + ), + SizedBox(width: 5.w), + quickText('作业正确率', color: Color.fromRGBO(114, 114, 114, 1), size: 12.sp), + SizedBox(width: 4.w), + quickText('${doubleToStringAsFixed(data.correctRate)}%', + color: Color.fromRGBO(72, 72, 72, 1), size: 13.sp, fontWeight: FontWeight.bold), + ], + ), + SizedBox(height: 14.h), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 10.r, + height: 10.r, + decoration: BoxDecoration( + shape: BoxShape.circle, // 设置为圆形 + color: Color.fromRGBO(104, 136, 253, 1), + ), + ), + SizedBox(width: 5.w), + quickText('作业有效份数', color: Color.fromRGBO(114, 114, 114, 1), size: 12.sp), + SizedBox(width: 4.w), + quickText('${data.validCount}份', + color: Color.fromRGBO(72, 72, 72, 1), size: 13.sp, fontWeight: FontWeight.bold), + ], + ), + ], + ), + ) + ], + ), + SizedBox(height: 50.h), + // 全对、及格、不及格、未做 + Wrap( + spacing: 30.r, + runSpacing: 26.r, + children: [ + // 全对 + Container( + child: Column( + children: [ + Stack( + alignment: const FractionalOffset(0.5, 0.94), + children: [ + Container( + width: 140.w, //宽 + height: 170.h, //高 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.r), + color: Color.fromRGBO(241, 241, 241, 1), + ), + child: FlutterWaveLoading( + width: 140.w, //宽 + height: 170.h, //高 + isOval: false, // 是否椭圆裁切 + progress: data.allCorrect / data.studentCount, // 进度 + waveHeight: 8, //波浪高 + milliseconds: 5000, + color: Color.fromRGBO(0, 179, 134, 1), //颜色 + ), + ), + Positioned( + left: 16.h, + top: 10.h, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText( + data.allCorrect, + size: 32.sp, + fontWeight: FontWeight.bold, + color: Color.fromRGBO(122, 122, 122, 1), + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + quickText('人', size: 18.sp, color: Color.fromRGBO(122, 122, 122, 1)), + SizedBox(height: 4.h) + ], + ) + ], + ), + quickText('全对', color: Color.fromRGBO(164, 164, 164, 1), size: 16.sp), + ], + )), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText(doubleToStringAsFixed(data.allCorrect / data.studentCount * 100), + size: 34.sp, color: Colors.white, fontWeight: FontWeight.bold), + SizedBox(width: 1.5.w), + quickText('%', size: 22.sp, color: Colors.white) + ], + ), + ], + ), + SizedBox(height: 8.h), + InkWell( + onTap: () {}, + child: Container( + width: 77.w, + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 4.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(77.r), + border: Border.all(color: Color.fromRGBO(188, 188, 188, 1)), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + quickText('查看', size: 11.sp, color: Color.fromRGBO(118, 118, 118, 1)), + Icon(Icons.arrow_forward_ios, size: 11.sp, color: Color.fromRGBO(95, 95, 95, 1)) + ], + ), + ), + ) + ], + ), + ), + // 及格 + Container( + child: Column( + children: [ + Stack( + alignment: const FractionalOffset(0.5, 0.94), + children: [ + Container( + width: 140.w, //宽 + height: 170.h, //高 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.r), + color: Color.fromRGBO(241, 241, 241, 1), + ), + child: FlutterWaveLoading( + width: 140.w, //宽 + height: 170.h, //高 + isOval: false, // 是否椭圆裁切 + progress: data.passCount / data.studentCount, // 进度 + waveHeight: 8, //波浪高 + milliseconds: 8000, + color: Color.fromRGBO(255, 134, 0, 0.84), //颜色 + ), + ), + Positioned( + left: 16.h, + top: 10.h, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText( + data.passCount, + size: 32.sp, + fontWeight: FontWeight.bold, + color: Color.fromRGBO(122, 122, 122, 1), + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + quickText('人', size: 18.sp, color: Color.fromRGBO(122, 122, 122, 1)), + SizedBox(height: 4.h) + ], + ) + ], + ), + quickText('及格', color: Color.fromRGBO(164, 164, 164, 1), size: 16.sp), + ], + )), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText(doubleToStringAsFixed(data.passCount / data.studentCount * 100), + size: 34.sp, color: Colors.white, fontWeight: FontWeight.bold), + SizedBox(width: 1.5.w), + quickText('%', size: 22.sp, color: Colors.white) + ], + ), + ], + ), + SizedBox(height: 8.h), + InkWell( + onTap: () {}, + child: Container( + width: 77.w, + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 4.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(77.r), + border: Border.all(color: Color.fromRGBO(188, 188, 188, 1)), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + quickText('查看', size: 11.sp, color: Color.fromRGBO(118, 118, 118, 1)), + Icon(Icons.arrow_forward_ios, size: 11.sp, color: Color.fromRGBO(95, 95, 95, 1)) + ], + ), + ), + ) + ], + ), + ), + // 不及格 + Container( + child: Column( + children: [ + Stack( + alignment: const FractionalOffset(0.5, 0.94), + children: [ + Container( + width: 140.w, //宽 + height: 170.h, //高 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.r), + color: Color.fromRGBO(241, 241, 241, 1), + ), + child: FlutterWaveLoading( + width: 140.w, //宽 + height: 170.h, //高 + isOval: false, // 是否椭圆裁切 + progress: data.failCount / data.studentCount, // 进度 + waveHeight: 8, //波浪高 + milliseconds: 7000, + color: Color.fromRGBO(255, 106, 106, 1), //颜色 + ), + ), + Positioned( + left: 16.h, + top: 10.h, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText( + data.failCount, + size: 32.sp, + fontWeight: FontWeight.bold, + color: Color.fromRGBO(122, 122, 122, 1), + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + quickText('人', size: 18.sp, color: Color.fromRGBO(122, 122, 122, 1)), + SizedBox(height: 4.h) + ], + ) + ], + ), + quickText('不及格', color: Color.fromRGBO(164, 164, 164, 1), size: 16.sp), + ], + )), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText(doubleToStringAsFixed(data.failCount / data.studentCount * 100), + size: 34.sp, color: Colors.white, fontWeight: FontWeight.bold), + SizedBox(width: 1.5.w), + quickText('%', size: 22.sp, color: Colors.white) + ], + ), + ], + ), + SizedBox(height: 8.h), + InkWell( + onTap: () {}, + child: Container( + width: 77.w, + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 4.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(77.r), + border: Border.all(color: Color.fromRGBO(188, 188, 188, 1)), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + quickText('查看', size: 11.sp, color: Color.fromRGBO(118, 118, 118, 1)), + Icon(Icons.arrow_forward_ios, size: 11.sp, color: Color.fromRGBO(95, 95, 95, 1)) + ], + ), + ), + ) + ], + ), + ), + // 未做 + Container( + child: Column( + children: [ + Stack( + alignment: const FractionalOffset(0.5, 0.94), + children: [ + Container( + width: 140.w, //宽 + height: 170.h, //高 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.r), + color: Color.fromRGBO(241, 241, 241, 1), + ), + child: FlutterWaveLoading( + width: 140.w, //宽 + height: 170.h, //高 + isOval: false, // 是否椭圆裁切 + progress: data.noAnswerCount / data.studentCount, // 进度 + waveHeight: 8, //波浪高 + milliseconds: 6000, + color: Color.fromRGBO(96, 96, 96, 1), //颜色 + ), + ), + Positioned( + left: 16.h, + top: 10.h, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText( + data.noAnswerCount, + size: 32.sp, + fontWeight: FontWeight.bold, + color: Color.fromRGBO(122, 122, 122, 1), + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + quickText('人', size: 18.sp, color: Color.fromRGBO(122, 122, 122, 1)), + SizedBox(height: 4.h) + ], + ) + ], + ), + quickText('未做', color: Color.fromRGBO(164, 164, 164, 1), size: 16.sp), + ], + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + quickText(doubleToStringAsFixed(data.noAnswerCount / data.studentCount * 100), + size: 34.sp, color: Colors.white, fontWeight: FontWeight.bold), + SizedBox(width: 1.5.w), + quickText('%', size: 22.sp, color: Colors.white) + ], + ), + ], + ), + SizedBox(height: 8.h), + InkWell( + onTap: () {}, + child: Container( + width: 77.w, + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 4.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(77.r), + border: Border.all(color: Color.fromRGBO(188, 188, 188, 1)), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + quickText('查看', size: 11.sp, color: Color.fromRGBO(118, 118, 118, 1)), + Icon(Icons.arrow_forward_ios, size: 11.sp, color: Color.fromRGBO(95, 95, 95, 1)) + ], + ), + ), + ) + ], + ), + ), + ], + ), + SizedBox(height: 30.h) + ], + ), + ); +} + +/// 掌握知识点的情况 +@swidget +Widget $masterKnowledgePoint(BuildContext context, List knowledgeInfos) { + Widget childItem(int serialNumber, KnowledgeInfos knowItem) => Container( + margin: EdgeInsets.only(bottom: 20.h), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Expanded( + flex: 10, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + quickText('${(serialNumber + 1).toString() + '.' + knowItem.knowledgeName}', + size: 12.sp, color: Color.fromRGBO(152, 152, 152, 1)), + quickText('${doubleToStringAsFixed(knowItem.rate)}%', + size: 14.sp, color: Color.fromRGBO(64, 64, 64, 1), fontWeight: FontWeight.bold), + ], + ), + ), + SizedBox(width: 10.w), + Expanded(flex: 1, child: SizedBox()), + ], + ), + SizedBox(height: 6.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 10, + child: LinearPercentIndicator( + padding: EdgeInsets.zero, + animation: true, + lineHeight: 15.h, + animationDuration: 2500, + percent: 0.1, + center: Text( + '${doubleToStringAsFixed(knowItem.rate)}%', + style: TextStyle(color: Colors.white, fontSize: 10.sp), + ), + progressColor: Theme.of(context).primaryColor, + backgroundColor: const Color.fromRGBO(219, 224, 243, 1), + barRadius: Radius.circular(10.r), + )), + SizedBox(width: 10.w), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + quickText('查看', size: 14.sp, color: Color.fromRGBO(239, 135, 20, 1)), + Icon(Icons.arrow_forward_ios, size: 11.sp, color: Color.fromRGBO(239, 135, 20, 1)), + ], + ) + ], + ) + ], + ), + ); + return Container( + margin: EdgeInsets.only(top: 10.h), + padding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 12.w), + constraints: BoxConstraints(maxHeight: 320.h), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10.r)), + child: Column( + children: [ + Container( + child: quickText('知识点掌握情况', color: Color.fromRGBO(92, 92, 92, 1), size: 14.sp, fontWeight: FontWeight.bold), + margin: EdgeInsets.only(bottom: 24.h), + ), + // ...knowledgeInfos.asMap().keys.map((e) => childItem(e, knowledgeInfos[e])).toList() + Expanded( + child: ListView(children: knowledgeInfos.asMap().keys.map((e) => childItem(e, knowledgeInfos[e])).toList()), + ), + ], + )); +} + +/// 整体表现 +@swidget +Widget $overallPerformance(int totalNumber, List overallTitles) { + Map colorMap = { + '优秀': Color.fromRGBO(104, 136, 253, 1), + '良好': Color.fromRGBO(255, 186, 33, 1), + '一般': Color.fromRGBO(243, 163, 44, 1), + '较差': Color.fromRGBO(211, 211, 211, 1).withOpacity(0.5), + '很差': Color.fromRGBO(211, 211, 211, 1), + }; + return Container( + margin: EdgeInsets.only(top: 20.h), + padding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 16.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10.r), + ), + height: 310.h, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + alignment: Alignment.center, + child: quickText('整体表现', color: Color.fromRGBO(92, 92, 92, 1), size: 14.sp, fontWeight: FontWeight.bold), + margin: EdgeInsets.only(bottom: 20.h), + ), + Expanded( + child: PieChart( + PieChartData( + borderData: FlBorderData(show: false), + sectionsSpace: 2, + centerSpaceRadius: 0, + sections: overallTitles.map((e) { + return PieChartSectionData( + color: colorMap[e.title], + value: e.count / totalNumber * 100, + radius: 110, + title: e.title + (doubleToStringAsFixed(e.count / totalNumber * 100) + '%'), + titleStyle: TextStyle(fontSize: 14.sp, color: Colors.white, fontWeight: FontWeight.bold), + ); + }).toList(), + ), + ), + ) + ], + ), + ); +} + +/// 单位时间答题情况 +@swidget +Widget $unitTimeAnsweringSituation(BuildContext context, List questionAnswerInfos) { + List questionNos = []; // 题号 + List questionTypes = []; // 题型 + List completionStatusWithinThirtySeconds = []; // 30s内完成情况 + List completionStatusWithinThirtyOneSixtySeconds = []; // 31s-60s内完成情况 + List completionStatusWithinSixtyOneOneHundredAndTwenty = []; // 61s-120s内完成情况 + List accuracys = []; // 正确率 + List errorRate = []; // 错误率 + List notDone = []; // 未做 + List viewOriginalQuestion = []; // 查看原题 + List operate = []; + Map> mapData = { + '题号': questionNos, + '题型': questionTypes, + '30s内完成情况': completionStatusWithinThirtySeconds, + '31s-60s内完成情况': completionStatusWithinThirtyOneSixtySeconds, + '61s-120s内完成情况': completionStatusWithinSixtyOneOneHundredAndTwenty, + '正确率': accuracys, + '错误率': errorRate, + '未做': notDone, + '查看原题': viewOriginalQuestion, + '操作': operate + }; + + questionAnswerInfos.forEach((e) { + questionNos.add(e.questionNo); // 题号 + questionTypes.add(e.questionType == 2 ? '主观题' : '客观题'); // 题型 + + accuracys.add(doubleToStringAsFixed(e.correctRate) + '%'); + errorRate.add(doubleToStringAsFixed(e.errorRate) + '%'); + notDone.add(doubleToStringAsFixed(e.noAnswerRate) + '%'); + viewOriginalQuestion.add(e.questionId.toString()); + operate.add(e.questionId.toString()); + + e.finishInfos.forEach((element) { + if ('30s内完成情况' == element.title) { + completionStatusWithinThirtySeconds + .add('${element.finishCount}人完成,正确率${doubleToStringAsFixed(element.correctRate)}%'); + } else if ('31-60s内完成情况' == element.title) { + completionStatusWithinThirtyOneSixtySeconds + .add('${element.finishCount}人完成,正确率${doubleToStringAsFixed(element.correctRate)}%'); + } else { + // 61-120s内完成情况 + completionStatusWithinSixtyOneOneHundredAndTwenty + .add('${element.finishCount}人完成,正确率${doubleToStringAsFixed(element.correctRate)}%'); + } + }); + }); + bool containsChinese(String text) { + // 汉字、扩展A、扩展B、兼容汉字等Unicode范围 + const int startRange = 0x4E00; // 汉字范围的起始Unicode码 + const int endRange = 0x9FFF; // 汉字范围的结束Unicode码 + + for (int i = 0; i < text.length; i++) { + int charCode = text.codeUnitAt(i); + if (charCode >= startRange && charCode <= endRange) { + return true; // 找到了中文字符 + } + } + + return false; // 没有找到中文字符 + } + + return Container( + width: double.infinity, + height: 520.h, + margin: EdgeInsets.only(top: 20.h), + padding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 12.w), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10.r)), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + child: quickText('整体表现', color: Color.fromRGBO(92, 92, 92, 1), size: 14.sp, fontWeight: FontWeight.bold), + margin: EdgeInsets.only(bottom: 20.h), + ), + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...mapData.entries.map((entrie) { + bool isViewButton = ['查看原题', '操作'].contains(entrie.key); // 查看按钮 + bool isRatio = ['正确率', '错误率', '未做'].contains(entrie.key); + bool isQuestionNo = entrie.key == '题号'; + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 94.r, + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: 1.h, right: 1.w), + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + color: Color.fromRGBO(230, 230, 230, 1), + child: quickText(entrie.key, color: Color.fromRGBO(24, 35, 77, 1), size: 12.sp, maxLines: 2), + ), + ...entrie.value.map((e) { + bool noHasChineseCharacter = isQuestionNo && !containsChinese(e); + + return Container( + width: 96.r, + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: 1.h, right: 1.w), + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + color: Color.fromRGBO(245, 245, 245, 1), + child: isViewButton + ? InkWell( + onTap: () {}, + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + quickText('查看', color: Color.fromRGBO(239, 135, 20, 1), size: 12.sp), + Container( + padding: EdgeInsets.only(top: 1.h), + child: Icon( + Icons.arrow_forward_ios, + size: 10.sp, + color: Color.fromRGBO(239, 135, 20, 1), + ), + ), + ], + ), + ) + : RegExp(r'^\d+$').hasMatch(e) || (e.contains('%') && e.length < 4) + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + quickText(e, + color: isRatio + ? Theme.of(context).primaryColor + : Color.fromRGBO(82, 82, 82, 1), + size: isRatio || noHasChineseCharacter ? 12.sp : 12.sp, + maxLines: 2), + quickText('透', color: Colors.transparent, size: 12.sp), + ], + ) + : quickText(e, + color: isRatio ? Theme.of(context).primaryColor : Color.fromRGBO(82, 82, 82, 1), + size: isRatio || noHasChineseCharacter ? 12.sp : 12.sp, + maxLines: 2), + ); + }).toList(), + ], + ); + }).toList(), + ], + ), + ), + ), + + // ...mapData.entries.map((entrie) { + // return Row( + // children: [ + // Container( + // width: 56.w, + // alignment: Alignment.center, + // margin: EdgeInsets.only(bottom: 1.h, right: 1.w), + // padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + // color: Color.fromRGBO(230, 230, 230, 1), + // child: quickText(entrie.key, color: Color.fromRGBO(24, 35, 77, 1), size: 12.sp, maxLines: 2), + // ), + // ...entrie.value.asMap().keys.map((e) { + // String questionNo = questionNos[e]; + // return Container( + // width: 56.w, + // alignment: Alignment.center, + // margin: EdgeInsets.only(bottom: 1.h), + // padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + // color: Color.fromRGBO(245, 245, 245, 1), + // child: quickText(questionNo, color: Color.fromRGBO(82, 82, 82, 1), size: 12.sp), + // ); + // }).toList(), + // ], + // ); + // }).toList(), + + // Stack( + // children: [ + // if (false) + // Container( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisSize: MainAxisSize.min, + // children: [ + // Container( + // width: 56.w, + // alignment: Alignment.center, + // margin: EdgeInsets.only(bottom: 1.h), + // padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + // color: Color.fromRGBO(230, 230, 230, 1), + // child: quickText('题号', color: Color.fromRGBO(24, 35, 77, 1), size: 12.sp), + // ), + // Container( + // width: 56.w, + // alignment: Alignment.center, + // margin: EdgeInsets.only(bottom: 1.h), + // padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + // color: Color.fromRGBO(230, 230, 230, 1), + // child: quickText('题型', color: Color.fromRGBO(24, 35, 77, 1), size: 12.sp)), + // Container( + // width: 56.w, + // alignment: Alignment.center, + // margin: EdgeInsets.only(bottom: 1.h), + // padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + // color: Color.fromRGBO(230, 230, 230, 1), + // child: quickText('30s内完成情况', maxLines: 2, color: Color.fromRGBO(24, 35, 77, 1), size: 12.sp)), + // Container( + // width: 56.w, + // alignment: Alignment.center, + // margin: EdgeInsets.only(bottom: 1.h), + // padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + // color: Color.fromRGBO(230, 230, 230, 1), + // child: + // quickText('31s-60s内完成情况', maxLines: 2, color: Color.fromRGBO(24, 35, 77, 1), size: 12.sp)), + // Container( + // width: 56.w, + // alignment: Alignment.center, + // margin: EdgeInsets.only(bottom: 1.h), + // padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + // color: Color.fromRGBO(230, 230, 230, 1), + // child: + // quickText('61s-120s内完成情况', maxLines: 2, color: Color.fromRGBO(24, 35, 77, 1), size: 12.sp)), + // Container( + // width: 56.w, + // alignment: Alignment.center, + // margin: EdgeInsets.only(bottom: 1.h), + // padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + // color: Color.fromRGBO(230, 230, 230, 1), + // child: quickText('正确率', color: Color.fromRGBO(24, 35, 77, 1), size: 12.sp)), + // Container( + // width: 56.w, + // alignment: Alignment.center, + // margin: EdgeInsets.only(bottom: 1.h), + // padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + // color: Color.fromRGBO(230, 230, 230, 1), + // child: quickText('错误率', color: Color.fromRGBO(24, 35, 77, 1), size: 12.sp)), + // Container( + // width: 56.w, + // alignment: Alignment.center, + // margin: EdgeInsets.only(bottom: 1.h), + // padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + // color: Color.fromRGBO(230, 230, 230, 1), + // child: quickText('未做', color: Color.fromRGBO(24, 35, 77, 1), size: 12.sp)), + // Container( + // width: 56.w, + // alignment: Alignment.center, + // margin: EdgeInsets.only(bottom: 1.h), + // padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + // color: Color.fromRGBO(230, 230, 230, 1), + // child: quickText('查看原题', color: Color.fromRGBO(24, 35, 77, 1), size: 12.sp)), + // Container( + // width: 56.w, + // alignment: Alignment.center, + // margin: EdgeInsets.only(bottom: 1.h), + // padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + // color: Color.fromRGBO(230, 230, 230, 1), + // child: quickText('操作', color: Color.fromRGBO(24, 35, 77, 1), size: 12.sp)), + // ], + // ), + // ), + // ], + // ), + ], + ), + ); +} + +/// 人员数据概况 +@swidget +Widget $personnelDataOverview(BuildContext context, List studentAnswerInfos) { + List names = []; + List useTimes = []; + List correctRates = []; + List noAnswerCounts = []; + List rankings = []; + Map> mapData = { + '姓名': names, + '答题时长': useTimes, + '正确率': correctRates, + '未答题数': noAnswerCounts, + '班级排名': rankings, + }; + + studentAnswerInfos.forEach((student) { + TimeUnits timeUnits = convertMilliseconds(student.useTime); + String timeerStr = ''; + if (timeUnits.hours > 0) timeerStr = timeUnits.hours.toString() + ':'; + if (timeUnits.minutes > 0) timeerStr += timeUnits.minutes.toString() + ':'; + if (timeUnits.seconds > 0) timeerStr += timeUnits.seconds.toString(); + + names.add(student.studentName); + useTimes.add(timeerStr); + correctRates.add(doubleToStringAsFixed(student.correctRate) + '%'); + noAnswerCounts.add(student.noAnswerCount.toString()); + rankings.add(student.ranking.toString() + '名'); + }); + + return Container( + width: double.infinity, + height: 290.h, + margin: EdgeInsets.only(top: 20.h), + padding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 12.w), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10.r)), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + child: quickText('人员数据概况', color: Color.fromRGBO(92, 92, 92, 1), size: 14.sp, fontWeight: FontWeight.bold), + margin: EdgeInsets.only(bottom: 20.h), + ), + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...mapData.entries.map((entrie) { + bool isTransparentChinese = ['答题时长', '正确率', '未答题数'].contains(entrie.key); // 透明中文 + + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 94.r, + alignment: Alignment.center, + color: Color.fromRGBO(230, 230, 230, 1), + margin: EdgeInsets.only(bottom: 1.h, right: 1.w), + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + child: quickText(entrie.key, color: Color.fromRGBO(24, 35, 77, 1), size: 12.sp, maxLines: 2), + ), + ...entrie.value.map((e) { + bool isTransparentChineseNew = isTransparentChinese && (e?.length ?? 0) == 0; + return Container( + width: 100.r, + alignment: Alignment.center, + margin: EdgeInsets.only(bottom: 1.h, right: 1.w), + padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 8.w), + color: Color.fromRGBO(245, 245, 245, 1), + child: isTransparentChineseNew + ? quickText('透明', color: Colors.transparent, size: 12.sp) + : RegExp(r'^\d+$').hasMatch(e) || e.contains('%') + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + quickText(e, color: Color.fromRGBO(82, 82, 82, 1), size: 12.sp, maxLines: 2), + quickText('透', color: Colors.transparent, size: 12.sp), + ], + ) + : quickText(e, color: Color.fromRGBO(82, 82, 82, 1), size: 12.sp, maxLines: 2), + ); + }).toList(), + ], + ); + }).toList(), + ], + ), + ), + ), + ], + ), + ); +} + +// 定义一个类来保存转换后的时间单位 +class TimeUnits { + int hours; + int minutes; + int seconds; + + TimeUnits(this.hours, this.minutes, this.seconds); +} + +// 毫秒转小时、分钟、秒的函数 +TimeUnits convertMilliseconds(int totalSeconds) { + int hours = totalSeconds ~/ 3600; // 整除得到小时数 + int remainingSeconds = totalSeconds % 3600; // 求余得到剩余的秒数 + int minutes = remainingSeconds ~/ 60; // 整除得到分钟数 + int seconds = remainingSeconds % 60; // 求余得到秒数 + + return TimeUnits(hours, minutes, seconds); +} diff --git a/marking_app/lib/utils/flutter_wave_loading.dart b/marking_app/lib/utils/flutter_wave_loading.dart new file mode 100644 index 0000000..ae4051e --- /dev/null +++ b/marking_app/lib/utils/flutter_wave_loading.dart @@ -0,0 +1,181 @@ +import 'package:flutter/material.dart'; +import 'package:marking_app/utils/index.dart'; + +/// 说明: 贝塞尔曲线测试画布 +class FlutterWaveLoading extends StatefulWidget { + final double width; + final double height; + final double waveHeight; + final Color color; + final double strokeWidth; + final double progress; + final double factor; + final int secondAlpha; + final double borderRadius; + final bool isOval; + final int milliseconds; + + FlutterWaveLoading( + {this.width = 100, + this.height = 100 / 0.618, + this.factor = 1, + this.waveHeight = 5, + this.progress = 0.5, + this.color = Colors.green, + this.strokeWidth = 3, + this.secondAlpha = 88, + this.isOval = false, + this.milliseconds = 3000, + this.borderRadius = 20}); + + @override + _FlutterWaveLoadingState createState() => _FlutterWaveLoadingState(); +} + +class _FlutterWaveLoadingState extends State with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _anim; + + @override + void initState() { + _controller = AnimationController(vsync: this, duration: Duration(milliseconds: widget.milliseconds)) + ..addListener(toSetState) + ..repeat(); + _anim = CurveTween(curve: Curves.linear).animate(_controller); + super.initState(); + } + + void toSetState() => toUpState(setState, () {}, mounted); + + @override + void dispose() { + try { + _controller + ..removeListener(toSetState) + ..dispose(); + } catch (e) { + print('报错了.........:'); + print(e); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return UnconstrainedBox( + child: Container( + width: widget.width, + height: widget.height, + child: CustomPaint( + painter: BezierPainter( + factor: _anim.value, + waveHeight: widget.waveHeight, + progress: widget.progress, + color: widget.color, + strokeWidth: widget.strokeWidth, + secondAlpha: widget.secondAlpha, + isOval: widget.isOval, + borderRadius: widget.borderRadius), + ), + ), + ); + } +} + +class BezierPainter extends CustomPainter { + late Paint _mainPaint; + late Path _mainPath; + + double waveWidth = 80; + late double wrapHeight; + + final double waveHeight; + final Color color; + final double strokeWidth; + final double progress; + final double factor; + final int secondAlpha; + final double borderRadius; + final bool isOval; + + BezierPainter( + {this.factor = 1, + this.waveHeight = 8, + this.progress = 0.5, + this.color = Colors.green, + this.strokeWidth = 3, + this.secondAlpha = 88, + this.isOval = false, + this.borderRadius = 20}) { + _mainPaint = Paint() + ..color = Colors.yellow + ..style = PaintingStyle.stroke + ..strokeWidth = 2; + _mainPath = Path(); + } + + @override + void paint(Canvas canvas, Size size) { + // print(size); + waveWidth = size.width / 2; + wrapHeight = size.height; + + Path path = Path(); + if (!isOval) { + path.addRRect(RRect.fromRectXY(Offset(0, 0) & size, borderRadius, borderRadius)); + canvas.clipPath(path); + // 边框 + // canvas.drawPath( + // path, + // _mainPaint + // ..strokeWidth = strokeWidth + // ..color = color); + } + if (isOval) { + path.addOval(Offset(0, 0) & size); + canvas.clipPath(path); + canvas.drawPath( + path, + _mainPaint + ..strokeWidth = strokeWidth + ..color = color); + } + canvas.translate(0, wrapHeight); + canvas.save(); + canvas.translate(0, waveHeight); + canvas.save(); + canvas.translate(-4 * waveWidth + 2 * waveWidth * factor, 0); + drawWave(canvas); + canvas.drawPath( + _mainPath, + _mainPaint + ..style = PaintingStyle.fill + ..color = color.withAlpha(88)); + canvas.restore(); + + canvas.translate(-4 * waveWidth + 2 * waveWidth * factor * 2, 0); + drawWave(canvas); + canvas.drawPath( + _mainPath, + _mainPaint + ..style = PaintingStyle.fill + ..color = color); + canvas.restore(); + } + + void drawWave(Canvas canvas) { + _mainPath.moveTo(0, 0); + _mainPath.relativeLineTo(0, -wrapHeight * progress); + _mainPath.relativeQuadraticBezierTo(waveWidth / 2, -waveHeight * 2, waveWidth, 0); + _mainPath.relativeQuadraticBezierTo(waveWidth / 2, waveHeight * 2, waveWidth, 0); + _mainPath.relativeQuadraticBezierTo(waveWidth / 2, -waveHeight * 2, waveWidth, 0); + _mainPath.relativeQuadraticBezierTo(waveWidth / 2, waveHeight * 2, waveWidth, 0); + _mainPath.relativeQuadraticBezierTo(waveWidth / 2, -waveHeight * 2, waveWidth, 0); + _mainPath.relativeQuadraticBezierTo(waveWidth / 2, waveHeight * 2, waveWidth, 0); + _mainPath.relativeLineTo(0, wrapHeight); + _mainPath.relativeLineTo(-waveWidth * 3 * 2.0, 0); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} diff --git a/marking_app/lib/utils/request/rest_client.dart b/marking_app/lib/utils/request/rest_client.dart index 3b6d150..9064f08 100644 --- a/marking_app/lib/utils/request/rest_client.dart +++ b/marking_app/lib/utils/request/rest_client.dart @@ -17,6 +17,7 @@ import 'package:marking_app/common/model/job/job_concerned_with_student.dart'; import 'package:marking_app/common/model/job/job_concerned_with_student_params.dart'; import 'package:marking_app/common/model/job/job_note_taking_trajectory.dart'; import 'package:marking_app/common/model/job/job_page_tab.dart'; +import 'package:marking_app/common/model/job/job_report_join_class.dart'; import 'package:marking_app/common/model/job/job_report_model.dart'; import 'package:marking_app/common/model/job/job_review_submission.dart'; import 'package:marking_app/common/model/job/job_task_item.dart'; @@ -249,7 +250,11 @@ abstract class RestClient { @the_retrofit.POST("${RequestConfig.hwProxyKeywords}/api/Marking/auto") Future> toJobOneClickReview(@the_retrofit.Field() int taskId); - // 作业 => 一键批阅 - @the_retrofit.GET("${RequestConfig.hwProxyKeywords}/api/Marking/auto") - Future> getJobReport(@the_retrofit.Field() int jobId); + // 作业 => 获取作业报告 + @the_retrofit.GET("/api/jobs/job-report") + Future> getJobReport(@the_retrofit.Queries() Map params); + + // 作业 => 获取作业报告 + @the_retrofit.GET("/api/jobs/student-job-for-class") + Future>> getJobReportJoinClasses(@the_retrofit.Query("jobId") int jobId); }