enum CourseSource { school, file, demo } class Course { Course({ required this.id, required this.name, required this.teacher, required this.room, required this.weekday, required this.startPeriod, required this.endPeriod, required Set weeks, required this.source, this.courseCode = '', this.notes = '', }) : weeks = Set.unmodifiable(weeks) { if ([id, name, teacher, room].any((value) => value.trim().isEmpty) || weekday < 1 || weekday > 7 || startPeriod < 1 || endPeriod > 12 || endPeriod < startPeriod || weeks.isEmpty || weeks.any((week) => week < 1 || week > 30)) { throw ArgumentError('课程字段、节次或周次无效'); } } final String id, name, teacher, room; final String courseCode, notes; final int weekday, startPeriod, endPeriod; final Set weeks; final CourseSource source; Map toJson() => { 'id': id, 'name': name, 'teacher': teacher, 'room': room, 'weekday': weekday, 'startPeriod': startPeriod, 'endPeriod': endPeriod, 'weeks': weeks.toList()..sort(), 'source': source.name, 'courseCode': courseCode, 'notes': notes, }; static Course fromJson(Map json, {CourseSource? source}) { try { String text(String key) { final v = json[key]; if (v is! String) throw const FormatException('课程字段缺失'); return v.trim(); } final rawWeeks = json['weeks']; if (rawWeeks is! List || rawWeeks.any((w) => w is! int)) { throw const FormatException('周次必须是整数数组'); } return Course( id: text('id'), name: text('name'), teacher: text('teacher'), room: text('room'), weekday: json['weekday'] as int, startPeriod: json['startPeriod'] as int, endPeriod: json['endPeriod'] as int, weeks: rawWeeks.cast().toSet(), source: source ?? CourseSource.values.byName(json['source'] as String), courseCode: (json['courseCode'] ?? '') as String, notes: (json['notes'] ?? '') as String, ); } catch (_) { throw const FormatException('课程字段、节次或周次无效'); } } }