/// ============================================================ /// 【lexicon 模块模型】背单词:章节 / 关卡 / 题目 / 小结 /// —— 对标 Vue3 端 api/lexicon/type.ts /// ============================================================ library; /// 兼容 number | String 的 id 安全转换(对标 TS 的 number | string) int safeLexiconId(dynamic v) { if (v is num) return v.toInt(); if (v is String) return int.tryParse(v) ?? 0; return 0; } double _safeDouble(dynamic v) { if (v is num) return v.toDouble(); if (v is String) return double.tryParse(v) ?? 0; return 0; } /// 章节(词汇章节列表项) class Chapter { const Chapter({ this.id = 0, this.title = '', this.icon = '', this.finishLexiconCount = 0, this.levelCount = 0, this.lexiconCount = 0, this.finishProgress = 0, this.cover = '', this.index = '', }); final int id; final String title; final String icon; final int finishLexiconCount; final int levelCount; final int lexiconCount; final double finishProgress; // 0~1 final String cover; final String index; // 封面叠字序号(接口可能下发,Vue 端直接渲染 item.index) factory Chapter.fromJson(Map json) { return Chapter( id: safeLexiconId(json['id']), title: json['title'] as String? ?? '', icon: json['icon'] as String? ?? '', finishLexiconCount: (json['finishLexiconCount'] as num?)?.toInt() ?? 0, levelCount: (json['levelCount'] as num?)?.toInt() ?? 0, lexiconCount: (json['lexiconCount'] as num?)?.toInt() ?? 0, finishProgress: _safeDouble(json['finishProgress']), cover: json['cover'] as String? ?? '', index: json['index']?.toString() ?? '', ); } } /// 章节列表响应 class ChapterResponse { const ChapterResponse({this.chapters = const [], this.totalFinishProgress = 0}); final List chapters; final double totalFinishProgress; // 0~1 factory ChapterResponse.fromJson(Map json) { final payload = (json['data'] as Map?) ?? json; return ChapterResponse( chapters: (payload['chapters'] as List? ?? const []) .whereType>() .map(Chapter.fromJson) .toList(), totalFinishProgress: _safeDouble(payload['totalFinishProgress']), ); } } /// 关卡 class Level { const Level({ this.id = 0, this.title = '', this.lexiconCount = 0, this.isOpen = false, this.finishLexiconCount = 0, this.finishProgress = 0, }); final int id; final String title; final int lexiconCount; final bool isOpen; // 是否解锁 final int finishLexiconCount; final double finishProgress; // 0~1 factory Level.fromJson(Map json) { return Level( id: safeLexiconId(json['id']), title: json['title'] as String? ?? '', lexiconCount: (json['lexiconCount'] as num?)?.toInt() ?? 0, isOpen: json['isOpen'] as bool? ?? false, finishLexiconCount: (json['finishLexiconCount'] as num?)?.toInt() ?? 0, finishProgress: _safeDouble(json['finishProgress']), ); } } /// 关卡列表响应(含章节头部汇总) class LevelResponse { const LevelResponse({ this.id = 0, this.title = '', this.lexiconCount = 0, this.finishLexiconCount = 0, this.finishProgress = 0, this.levels = const [], }); final int id; final String title; final int lexiconCount; final int finishLexiconCount; final double finishProgress; final List levels; factory LevelResponse.fromJson(Map json) { final payload = (json['data'] as Map?) ?? json; return LevelResponse( id: safeLexiconId(payload['id']), title: payload['title'] as String? ?? '', lexiconCount: (payload['lexiconCount'] as num?)?.toInt() ?? 0, finishLexiconCount: (payload['finishLexiconCount'] as num?)?.toInt() ?? 0, finishProgress: _safeDouble(payload['finishProgress']), levels: (payload['levels'] as List? ?? const []) .whereType>() .map(Level.fromJson) .toList(), ); } } /// 答题页单词题目 class Question { const Question({ this.id = 0, this.americanEnglish = '', this.americanPhonetic = '', this.americanVoiceUrl = '', this.imageDesc = '', this.contextMatch = '', this.english = '', this.phonetic = '', this.voiceUrl = '', this.chineseDesc = '', this.options = const [], }); final int id; final String americanEnglish; final String americanPhonetic; final String americanVoiceUrl; final String imageDesc; // 配图 final String contextMatch; // 语境搭配(空则不可翻转) final String english; final String phonetic; final String voiceUrl; final String chineseDesc; // 正确答案 final List options; factory Question.fromJson(Map json) { return Question( id: safeLexiconId(json['id']), americanEnglish: json['americanEnglish'] as String? ?? '', americanPhonetic: json['americanPhonetic'] as String? ?? '', americanVoiceUrl: json['americanVoiceUrl'] as String? ?? '', imageDesc: json['imageDesc'] as String? ?? '', contextMatch: json['contextMatch'] as String? ?? '', english: json['english'] as String? ?? '', phonetic: json['phonetic'] as String? ?? '', voiceUrl: json['voiceUrl'] as String? ?? '', chineseDesc: json['chineseDesc'] as String? ?? '', options: (json['options'] as List? ?? const []) .map((e) => e.toString()) .toList(), ); } } /// 已作答单词(小结回顾用) class Lexicon { const Lexicon({ this.id = 0, this.english = '', this.phonetic = '', this.voiceUrl = '', this.chineseDesc = '', this.isCorrect = false, }); final int id; final String english; final String phonetic; final String voiceUrl; final String chineseDesc; final bool isCorrect; factory Lexicon.fromJson(Map json) { return Lexicon( id: safeLexiconId(json['id']), english: json['english'] as String? ?? '', phonetic: json['phonetic'] as String? ?? '', voiceUrl: json['voiceUrl'] as String? ?? '', chineseDesc: json['chineseDesc'] as String? ?? '', isCorrect: json['isCorrect'] as bool? ?? false, ); } } /// LevelStart 开局信息 class LevelStartInfo { const LevelStartInfo({ this.id = 0, this.userLevelId = 0, this.finishLexiconCount = 0, this.lexiconCount = 0, this.questions = const [], this.lexicons = const [], }); final int id; final int userLevelId; final int finishLexiconCount; final int lexiconCount; final List questions; final List lexicons; factory LevelStartInfo.fromJson(Map json) { final payload = (json['data'] as Map?) ?? json; return LevelStartInfo( id: safeLexiconId(payload['id']), userLevelId: (payload['userLevelId'] as num?)?.toInt() ?? 0, finishLexiconCount: (payload['finishLexiconCount'] as num?)?.toInt() ?? 0, lexiconCount: (payload['lexiconCount'] as num?)?.toInt() ?? 0, questions: (payload['questions'] as List? ?? const []) .whereType>() .map(Question.fromJson) .toList(), lexicons: (payload['lexicons'] as List? ?? const []) .whereType>() .map(Lexicon.fromJson) .toList(), ); } } /// 闯关小结信息 class LevelOverInfo { const LevelOverInfo({ this.id = 0, this.userLevelId = 0, this.lexiconCount = 0, this.correctCount = 0, this.wrongCount = 0, this.nextLevelId = 0, this.nextChapterId = 0, this.lexicons = const [], }); final int id; final int userLevelId; final int lexiconCount; final int correctCount; final int wrongCount; final int nextLevelId; // 0 表示没有下一关 final int nextChapterId; // 0 表示没有下一章 final List lexicons; /// 正确率(0~100,四舍五入;无单词兜底 0) int get rate => lexiconCount <= 0 ? 0 : (correctCount * 100 / lexiconCount).round(); factory LevelOverInfo.fromJson(Map json) { final payload = (json['data'] as Map?) ?? json; return LevelOverInfo( id: safeLexiconId(payload['id']), userLevelId: (payload['userLevelId'] as num?)?.toInt() ?? 0, lexiconCount: (payload['lexiconCount'] as num?)?.toInt() ?? 0, correctCount: (payload['correctCount'] as num?)?.toInt() ?? 0, wrongCount: (payload['wrongCount'] as num?)?.toInt() ?? 0, nextLevelId: safeLexiconId(payload['nextLevelId']), nextChapterId: safeLexiconId(payload['nextChapterId']), lexicons: (payload['lexicons'] as List? ?? const []) .whereType>() .map(Lexicon.fromJson) .toList(), ); } }