统一裁判提交确认弹窗的评分项描述

按项目名:数量或录入分值展示难度、表现、规定赛和主裁评分项,替换仅提示已完成录入的占位描述。

精简确认卡片中的重复标题,保留取消资格原因;补充各角色明细与确认弹窗测试。
This commit is contained in:
ltx 2026-10-12 16:37:49 +08:00
parent f8d2a87fba
commit f0571b1f03
5 changed files with 249 additions and 111 deletions

View File

@ -20,6 +20,7 @@ import '../widgets/competition_header.dart';
import '../widgets/drawing_board_dialog.dart';
import '../widgets/record_button.dart';
import '../widgets/scoring_dialogs.dart';
import '../widgets/submission_score_details.dart';
class ScoringPage extends StatefulWidget {
const ScoringPage({super.key});
@ -101,7 +102,7 @@ class _ScoringPageState extends State<ScoringPage> with WidgetsBindingObserver {
}
final correctionSubmission = controller.isCorrectionMode.value;
final confirmed = await showSubmitConfirmation(
details: _submissionScoreDetails(controller),
details: buildSubmissionScoreDetails(controller),
correctionMode: controller.isCorrectionMode.value,
);
if (confirmed != true) return;
@ -2419,81 +2420,6 @@ String _formatScore(num value) {
return fixed.replaceFirst(RegExp(r'\.?0+$'), '');
}
List<SubmissionScoreDetail> _submissionScoreDetails(
ScoringController controller,
) {
switch (controller.profile.role) {
case ScoreRole.difficulty:
if (controller.usesShowDifficultyMatrix) {
return [
for (final category in controller.showDifficultyCategories)
SubmissionScoreDetail(
label: category,
description: _showDifficultyCountSummary(controller, category),
),
];
}
return [
for (final entry in controller.requiredElementCounts.entries)
SubmissionScoreDetail(
label: entry.key,
description: '规定元素完成数量:${entry.value} 个',
),
for (final entry in controller.difficultyCounts.entries)
SubmissionScoreDetail(
label: '${entry.key}级难度',
description: '记录数量:${entry.value}',
),
];
case ScoreRole.presentation:
return [
for (final item in controller.performanceItems.keys)
SubmissionScoreDetail(
label: item,
description:
'人工减 ${controller.performanceMinusCount(item)}、失误自动减 ${controller.performanceErrorCount.value} / 加 ${controller.performancePlusCount(item)}',
),
SubmissionScoreDetail(
label: '失误',
description:
'已记录 ${controller.performanceErrorCount.value} 次,计分时从五个表现类别各减去该次数,并上传参与综合扣分',
),
];
case ScoreRole.creative:
case ScoreRole.execution:
return [
for (final entry in controller.performanceItems.entries)
SubmissionScoreDetail(label: entry.key, description: '评分项已完成录入'),
];
case ScoreRole.technical:
return [
for (final entry in controller.requiredElementCounts.entries)
SubmissionScoreDetail(
label: entry.key,
description: '规定元素完成数量:${entry.value} 个',
),
for (final rule in controller.deductionRules)
SubmissionScoreDetail(
label: rule.key,
description:
'已记录 ${controller.deductionCounts[rule.key] ?? 0} 次 · ${rule.standard}',
),
];
}
}
String _showDifficultyCountSummary(
ScoringController controller,
String category,
) {
final recorded = <String>[];
for (final level in controller.showDifficultyLevels) {
final count = controller.showDifficultyCount(category, level);
if (count > 0) recorded.add('$level级×$count');
}
return recorded.isEmpty ? '尚未记录动作' : recorded.join('、');
}
class _TableRow extends StatelessWidget {
const _TableRow({required this.cells, this.statusColor});
final List<Widget> cells;

View File

@ -328,38 +328,19 @@ class _SubmissionDetailCard extends StatelessWidget {
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
detail.label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: 13,
fontWeight: FontWeight.w800,
),
),
),
],
child: Align(
alignment: Alignment.centerLeft,
child: Text(
detail.description,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: 14,
height: 1.3,
fontWeight: FontWeight.w700,
),
const SizedBox(height: 7),
Text(
detail.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: 11,
height: 1.25,
fontWeight: FontWeight.w600,
),
),
],
),
),
);
}

View File

@ -0,0 +1,105 @@
import '../../../core/models/judge_account.dart';
import '../controllers/scoring_controller.dart';
import 'scoring_dialogs.dart';
/// 提交前复核各评分项的原始录入,不展示裁判合计分或平台最终成绩。
List<SubmissionScoreDetail> buildSubmissionScoreDetails(
ScoringController controller,
) {
switch (controller.profile.role) {
case ScoreRole.difficulty:
if (controller.usesShowDifficultyMatrix) {
return [
for (final category in controller.showDifficultyCategories)
SubmissionScoreDetail(
label: category,
description: _showDifficultyCountSummary(controller, category),
),
];
}
return [
for (final entry in controller.requiredElementCounts.entries)
SubmissionScoreDetail(
label: entry.key,
description: '${entry.key}:${entry.value}个',
),
for (final entry in controller.difficultyCounts.entries)
SubmissionScoreDetail(
label: '${entry.key}级难度',
description: '${entry.key}级难度:${entry.value}个',
),
];
case ScoreRole.presentation:
return [
for (final item in controller.performanceItems.keys)
SubmissionScoreDetail(
label: item,
description:
'$item:加${controller.performancePlusCount(item)}次、人工减${controller.performanceMinusCount(item)}次、失误联动减${controller.performanceErrorCount.value}次',
),
SubmissionScoreDetail(
label: '失误',
description: '失误:${controller.performanceErrorCount.value}次',
),
];
case ScoreRole.creative:
case ScoreRole.execution:
return [
for (final entry in controller.performanceItems.entries)
SubmissionScoreDetail(
label: entry.key,
description: '${entry.key}:${_formatInputValue(entry.value)}分',
),
];
case ScoreRole.technical:
return [
for (final entry in controller.requiredElementCounts.entries)
SubmissionScoreDetail(
label: entry.key,
description: '${entry.key}:${entry.value}个',
),
for (final rule in controller.deductionRules)
SubmissionScoreDetail(
label: rule.key,
description:
'${rule.key}:${controller.deductionCounts[rule.key] ?? 0}次',
),
if (controller.disqualified.value)
SubmissionScoreDetail(
label: '取消比赛资格',
description:
'取消比赛资格:1项(${_disqualificationDescription(controller)})',
),
];
}
}
String _formatInputValue(double value) {
final fixed = value.toStringAsFixed(2);
return fixed.replaceFirst(RegExp(r'\.?0+$'), '');
}
String _disqualificationDescription(ScoringController controller) {
final code = controller.selectedDisqualifyReasonCode;
var label = code ?? '已选择原因';
for (final item in controller.disqualifyReasonItems) {
if (item.code == code) {
label = item.name;
break;
}
}
final remark = controller.disqualifyRemark;
return remark == null || remark.isEmpty ? label : '$label · $remark';
}
String _showDifficultyCountSummary(
ScoringController controller,
String category,
) {
var total = 0;
for (final level in controller.showDifficultyLevels) {
final count = controller.showDifficultyCount(category, level);
total += count;
}
return '$category:$total个';
}

View File

@ -16,8 +16,11 @@ void main() {
onPressed: () => showSubmitConfirmation(
correctionMode: false,
details: const [
SubmissionScoreDetail(label: '音乐', description: '减 1 / 加 2'),
SubmissionScoreDetail(label: '完成质量', description: '评分项已完成录入'),
SubmissionScoreDetail(label: '音乐', description: '音乐:加2次、减1次'),
SubmissionScoreDetail(
label: '完成质量',
description: '完成质量:12.5分',
),
],
),
child: const Text('打开'),
@ -32,8 +35,8 @@ void main() {
expect(find.text('确认提交成绩'), findsOneWidget);
expect(find.text('评分内容(2项)'), findsOneWidget);
expect(find.text('音乐'), findsOneWidget);
expect(find.text('完成质量'), findsOneWidget);
expect(find.text('音乐:加2次、减1次'), findsOneWidget);
expect(find.text('完成质量:12.5分'), findsOneWidget);
expect(find.textContaining('裁判总分'), findsNothing);
expect(find.textContaining('本次裁判得分'), findsNothing);
});

View File

@ -0,0 +1,123 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'package:yd_pattern_scoring_system/core/models/deduction_rule_item.dart';
import 'package:yd_pattern_scoring_system/core/models/judge_account.dart';
import 'package:yd_pattern_scoring_system/core/storage/local_storage_service.dart';
import 'package:yd_pattern_scoring_system/modules/scoring/controllers/scoring_controller.dart';
import 'package:yd_pattern_scoring_system/modules/scoring/repositories/score_repository.dart';
import 'package:yd_pattern_scoring_system/modules/scoring/widgets/submission_score_details.dart';
class _ScoreRepository extends Fake implements ScoreRepository {}
class _Storage extends Fake implements LocalStorageService {}
ScoringController _controller(ScoreRole role, CompetitionKind competition) {
final controller = ScoringController(_ScoreRepository(), _Storage());
controller.profile = JudgeAccount(
account: 'test',
name: '测试裁判',
competition: competition,
role: role,
roleCode: role == ScoreRole.technical ? 'T' : 'P',
seat: 1,
);
return controller;
}
void main() {
test('规定赛创意编排与完成质量逐项展示实际录入分值,包括零分', () {
final creative = _controller(
ScoreRole.creative,
CompetitionKind.prescribed,
);
creative.performanceItems.assignAll({'过渡连接、成套创编、场地运用': 3.5, '现场表现效果': 0});
final creativeDetails = buildSubmissionScoreDetails(creative);
expect(creativeDetails.map((item) => item.description), [
'过渡连接、成套创编、场地运用:3.5分',
'现场表现效果:0分',
]);
final execution = _controller(
ScoreRole.execution,
CompetitionKind.prescribed,
);
execution.performanceItems.assignAll({'动作准确性': 24, '动作熟练性': 12.5});
final executionDetails = buildSubmissionScoreDetails(execution);
expect(executionDetails.map((item) => item.label), ['动作准确性', '动作熟练性']);
expect(executionDetails.map((item) => item.description), [
'动作准确性:24分',
'动作熟练性:12.5分',
]);
expect(
executionDetails.any((item) => item.description == '评分项已完成录入'),
isFalse,
);
});
test('难度裁判列出规定元素与各等级的原始数量', () {
final controller = _controller(
ScoreRole.difficulty,
CompetitionKind.individual,
);
controller.requiredElementCounts.assignAll({'n3+(3级及以上)': 2});
controller.difficultyCounts.assignAll({'3': 4, '4': 0});
final details = buildSubmissionScoreDetails(controller);
expect(details.map((item) => item.description), [
'n3+(3级及以上):2个',
'3级难度:4个',
'4级难度:0个',
]);
});
test('表现裁判展示加减和失误次数,主裁展示规定元素、扣分及取消资格原因', () {
final performance = _controller(
ScoreRole.presentation,
CompetitionKind.individual,
);
performance.performanceItems['音乐'] = 12;
performance.performancePlusCounts['音乐'] = 3;
performance.performanceMinusCounts['音乐'] = 1;
performance.performanceErrorCount.value = 2;
final performanceDetails = buildSubmissionScoreDetails(performance);
expect(performanceDetails.first.description, '音乐:加3次、人工减1次、失误联动减2次');
expect(performanceDetails.last.description, '失误:2次');
final chief = _controller(ScoreRole.technical, CompetitionKind.individual);
chief.requiredElementCounts['互动配合'] = 1;
chief.deductionRules = DeductionRuleCatalog.forCompetition(
CompetitionKind.individual,
);
chief.deductionCounts['失误'] = 2;
chief.disqualified.value = true;
chief.selectedDisqualifyReasonCode = 'GENERIC_NO_SHOW';
chief.disqualifyRemark = '未到场';
final chiefDetails = buildSubmissionScoreDetails(chief);
expect(chiefDetails.first.description, '互动配合:1个');
expect(
chiefDetails.firstWhere((item) => item.label == '失误').description,
'失误:2次',
);
expect(chiefDetails.last.description, '取消比赛资格:1项(GENERIC_NO_SHOW · 未到场)');
});
test('表演赛难度 A 组按类别显示动作总数', () {
final controller = _controller(ScoreRole.difficulty, CompetitionKind.show);
controller.profile = const JudgeAccount(
account: 'show-a',
name: 'A组裁判',
competition: CompetitionKind.show,
role: ScoreRole.difficulty,
roleCode: 'DGA',
seat: 1,
);
final category = controller.showDifficultyCategories.first;
controller.difficultyCategoryCounts['$category::3'] = 2;
controller.difficultyCategoryCounts['$category::4'] = 1;
final details = buildSubmissionScoreDetails(controller);
expect(details.first.description, '$category:3个');
expect(
details[1].description,
'${controller.showDifficultyCategories[1]}:0个',
);
});
}