整个课程和数据UI重新设计版本

This commit is contained in:
shenlei
2026-09-18 17:55:44 +09:00
parent febfd30f49
commit 0a4f97d105
154 changed files with 12366 additions and 4784 deletions
+35 -11
View File
@@ -3,20 +3,39 @@
// dart run tool/courses/validate.dart # 报告错误和覆盖率
// dart run tool/courses/validate.dart --uncovered A1 # 列出未覆盖的词
// dart run tool/courses/validate.dart --levels A1-U01 # 打印条目的计算等级
import 'dart:convert';
import 'dart:io';
import 'package:kouyu_english/core/a0_core.dart';
import 'course_validator.dart';
void main(List<String> args) {
// A0 核心条目与听写句来自 A0 单元包,脚本在 Flutter 之外运行,直接读文件。
final map = jsonDecode(
File('assets/courses/course-map.json').readAsStringSync(),
);
final a0Level = (map['levels'] as List).firstWhere(
(level) => level['id'] == 'A0',
);
final a0Items = [
for (final unit in a0Level['units'] as List)
for (final item
in jsonDecode(
File('assets/courses/${unit['file']}').readAsStringSync(),
)['coreItems']
as List)
item as Map<String, dynamic>,
];
final validator = CourseValidator(
projectRoot: Directory.current,
a0Texts: [
// 去掉 [name] 这类槽位占位符,它们不是教过的词。
for (final item in a0CoreItems.values)
item.replaceAll(RegExp(r'\[[^\]]*\]'), ''),
for (final sentence in a0DictationSentences.values) sentence.sentence,
for (final item in a0Items)
(item['en'] as String).replaceAll(RegExp(r'\[[^\]]*\]'), ''),
for (final item in a0Items)
if (item['review'] case {
'dictation': {'sentence': final String sentence},
})
sentence,
],
);
final report = validator.validate();
@@ -25,13 +44,17 @@ void main(List<String> args) {
stdout.writeln('错误 $issue');
}
if (report.missingUnits.isNotEmpty) {
stdout.writeln('待编写 ${report.missingUnits.length} 个单元:'
'${report.missingUnits.first} … ${report.missingUnits.last}');
stdout.writeln(
'待编写 ${report.missingUnits.length} 个单元:'
'${report.missingUnits.first} … ${report.missingUnits.last}',
);
}
for (final coverage in report.coverage.values) {
stdout.writeln('${coverage.level} 词池覆盖 ${coverage.covered}/${coverage.poolSize} '
'(${(coverage.ratio * 100).toStringAsFixed(1)}%)'
'${coverage.unitsReady ? '' : ',单元未写完'}');
stdout.writeln(
'${coverage.level} 词池覆盖 ${coverage.covered}/${coverage.poolSize} '
'(${(coverage.ratio * 100).toStringAsFixed(1)}%)'
'${coverage.unitsReady ? '' : ',单元未写完'}',
);
}
final uncoveredIndex = args.indexOf('--uncovered');
@@ -39,7 +62,8 @@ void main(List<String> args) {
final coverage = report.coverage[args[uncoveredIndex + 1]];
final byTopic = <String, List<String>>{};
for (final word in coverage?.uncovered ?? const <PoolWord>[]) {
byTopic.putIfAbsent(word.topic.isEmpty ? '(无主题)' : word.topic, () => [])
byTopic
.putIfAbsent(word.topic.isEmpty ? '(无主题)' : word.topic, () => [])
.add(word.headword);
}
final topics = byTopic.keys.toList()