diff --git a/lib/core/models/deduction_rule_item.dart b/lib/core/models/deduction_rule_item.dart index e463d3c..c680d55 100644 --- a/lib/core/models/deduction_rule_item.dart +++ b/lib/core/models/deduction_rule_item.dart @@ -21,6 +21,22 @@ class DeductionRuleItem { } abstract final class DeductionRuleCatalog { + /// 服务端规定赛扣分项编码到平板控件键的映射。 + /// 名称文案存在“运动员的”“每人”等差异,提交与草稿恢复必须按编码匹配。 + static const prescribedServerItemAliases = { + 'T_DED_ROPE_SALUTE': '赛前或赛后未行绳礼(每队)', + 'T_DED_APPEARANCE': '头发、着装、仪容不符合规定,佩戴饰物或饰物掉落(每人)', + 'T_DED_CHANGE_1X8': '更改1×8拍规定元素内容', + 'T_DED_INTERRUPT': '套路中断4秒以上', + 'T_DED_AREA_FOUL': '场地犯规(踩线/出界,仅个人花样规定套路)', + // 兼容旧版内置规则编码。 + 'T_NO_ROPE_SALUTE': '赛前或赛后未行绳礼(每队)', + 'T_APPEARANCE': '头发、着装、仪容不符合规定,佩戴饰物或饰物掉落(每人)', + 'T_CHANGE_1X8': '更改1×8拍规定元素内容', + 'T_INTERRUPT_4S': '套路中断4秒以上', + 'T_VENUE_FOUL': '场地犯规(踩线/出界,仅个人花样规定套路)', + }; + static const _mistake = DeductionRuleItem( key: '失误', label: '失误', diff --git a/lib/core/models/prescribed_execution_rule.dart b/lib/core/models/prescribed_execution_rule.dart index a94297e..b2198d1 100644 --- a/lib/core/models/prescribed_execution_rule.dart +++ b/lib/core/models/prescribed_execution_rule.dart @@ -20,6 +20,14 @@ class ExecutionScoreCriterion { } abstract final class PrescribedExecutionRuleCatalog { + static const displayLabels = { + '动作准确性': '动作的准确性(个人30分/集体20分)', + '动作熟练性': '动作的熟练性(个人20分/集体15分)', + '动作合拍性': '动作的合拍性(个人30分/集体15分)', + '运动能力一致性': '运动能力一致性(集体15分)', + '表演技巧一致性': '表演技巧一致性(集体15分)', + }; + /// 服务端规定赛评分项编码到平板控件键的映射。 /// 按编码匹配可避免“动作的准确性”与“动作准确性”等文案差异丢分。 static const serverItemAliases = { @@ -83,4 +91,6 @@ abstract final class PrescribedExecutionRuleCatalog { PrescribedEntryType.individual => individual, PrescribedEntryType.collective => collective, }; + + static String displayLabelFor(String item) => displayLabels[item] ?? item; } diff --git a/lib/modules/scoring/controllers/scoring_controller.dart b/lib/modules/scoring/controllers/scoring_controller.dart index 3bcb29b..3721f30 100644 --- a/lib/modules/scoring/controllers/scoring_controller.dart +++ b/lib/modules/scoring/controllers/scoring_controller.dart @@ -437,6 +437,9 @@ class ScoringController extends GetxController { final key = _matchingKey( deductionCounts.keys, '${item.name}${item.code}', + aliases: profile.competition == CompetitionKind.prescribed + ? DeductionRuleCatalog.prescribedServerItemAliases + : const {}, ); if (key != null) { deductionCounts[key] = count; @@ -879,6 +882,13 @@ class ScoringController extends GetxController { .description; } + String performanceItemDisplayLabel(String item) { + if (profile.role == ScoreRole.execution) { + return PrescribedExecutionRuleCatalog.displayLabelFor(item); + } + return '$item(${performanceItemMaximum(item).round()})'; + } + void setPerformance(String item, double value) { performanceItems[item] = value.clamp(0, performanceItemMaximum(item)); recalculate(); diff --git a/lib/modules/scoring/views/scoring_page.dart b/lib/modules/scoring/views/scoring_page.dart index 2f5b703..c2117ce 100644 --- a/lib/modules/scoring/views/scoring_page.dart +++ b/lib/modules/scoring/views/scoring_page.dart @@ -1296,7 +1296,7 @@ class _PerformancePanel extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - '${entry.key}($itemMaximum)', + controller.performanceItemDisplayLabel(entry.key), style: const TextStyle(fontWeight: FontWeight.w700), ), if (description != null) ...[ diff --git a/test/prescribed_execution_rule_test.dart b/test/prescribed_execution_rule_test.dart index 634670c..8cbf36f 100644 --- a/test/prescribed_execution_rule_test.dart +++ b/test/prescribed_execution_rule_test.dart @@ -30,6 +30,29 @@ void main() { expect(rules.fold(0, (sum, item) => sum + item.maximum), 80); }); + test('完成质量评分项同时标明个人与集体分值', () { + expect( + PrescribedExecutionRuleCatalog.displayLabelFor('动作准确性'), + '动作的准确性(个人30分/集体20分)', + ); + expect( + PrescribedExecutionRuleCatalog.displayLabelFor('动作熟练性'), + '动作的熟练性(个人20分/集体15分)', + ); + expect( + PrescribedExecutionRuleCatalog.displayLabelFor('动作合拍性'), + '动作的合拍性(个人30分/集体15分)', + ); + expect( + PrescribedExecutionRuleCatalog.displayLabelFor('运动能力一致性'), + '运动能力一致性(集体15分)', + ); + expect( + PrescribedExecutionRuleCatalog.displayLabelFor('表演技巧一致性'), + '表演技巧一致性(集体15分)', + ); + }); + test('个人与集体规定赛使用不同任务状态键', () { final profile = JudgeAccounts.find('GS-E1')!; diff --git a/test/score_item_key_matcher_test.dart b/test/score_item_key_matcher_test.dart index ce22db0..835ca70 100644 --- a/test/score_item_key_matcher_test.dart +++ b/test/score_item_key_matcher_test.dart @@ -35,7 +35,13 @@ void main() { }); test('规定赛完成质量项按稳定编码匹配平板控件', () { - const keys = ['动作准确性', '动作熟练性', '动作合拍性']; + const keys = [ + '动作准确性', + '动作熟练性', + '动作合拍性', + '运动能力一致性', + '表演技巧一致性', + ]; const aliases = PrescribedExecutionRuleCatalog.serverItemAliases; expect( @@ -54,6 +60,48 @@ void main() { ScoreItemKeyMatcher.match(keys, 'CQ_SYNCHRONY 动作的合拍性', aliases: aliases), '动作合拍性', ); + expect( + ScoreItemKeyMatcher.match( + keys, + 'CQ_ABILITY_CONSISTENCY 运动能力一致性', + aliases: aliases, + ), + '运动能力一致性', + ); + expect( + ScoreItemKeyMatcher.match( + keys, + 'CQ_SKILL_CONSISTENCY 表演技巧一致性', + aliases: aliases, + ), + '表演技巧一致性', + ); + }); + + test('规定赛主裁扣分项按稳定编码匹配平板控件', () { + final keys = DeductionRuleCatalog.forCompetition( + CompetitionKind.prescribed, + ).map((item) => item.key).toList(); + const aliases = DeductionRuleCatalog.prescribedServerItemAliases; + const serverItems = { + 'T_DED_ROPE_SALUTE': '赛前或赛后未行绳礼(每队)', + 'T_DED_APPEARANCE': '头发、着装、仪容不符合规定,佩戴饰物或饰物掉落(每人)', + 'T_DED_CHANGE_1X8': '更改1×8拍规定元素内容', + 'T_DED_INTERRUPT': '套路中断4秒以上', + 'T_DED_AREA_FOUL': '场地犯规(踩线/出界,仅个人花样规定套路)', + }; + + expect( + { + for (final item in serverItems.entries) + item.key: ScoreItemKeyMatcher.match( + keys, + item.key, + aliases: aliases, + ), + }, + serverItems, + ); }); final keys = DeductionRuleCatalog.forCompetition(