Enhance VocabService and VocabLessonView for improved vocabulary review: Updated VocabService to load previous lesson vocabulary for both review and mixed training modes. Added new computed properties and methods in VocabLessonView to manage vocabulary from previous lessons, including a mixed pool for enhanced training. Updated UI to reflect current lesson and mixed review status in the vocabulary trainer.

This commit is contained in:
Torsten Schulz (local)
2026-02-09 11:17:04 +01:00
parent c7a05c3213
commit 022cd47e7e
4 changed files with 91 additions and 9 deletions

View File

@@ -885,10 +885,14 @@ export default class VocabService {
const plainLesson = lesson.get({ plain: true });
// Bei Wiederholungslektionen: Lade Vokabeln aus vorherigen Lektionen
// Lade Vokabeln aus vorherigen Lektionen (für Wiederholung UND für gemischten Vokabeltrainer)
if (plainLesson.lessonNumber > 1) {
plainLesson.previousLessonExercises = await this._getReviewVocabExercises(plainLesson.courseId, plainLesson.lessonNumber);
}
// Bei Wiederholungslektionen: Auch Lektions-Liste für Anzeige
if (plainLesson.lessonType === 'review' || plainLesson.lessonType === 'vocab_review') {
plainLesson.reviewLessons = await this._getReviewLessons(plainLesson.courseId, plainLesson.lessonNumber);
plainLesson.reviewVocabExercises = await this._getReviewVocabExercises(plainLesson.courseId, plainLesson.lessonNumber);
plainLesson.reviewVocabExercises = plainLesson.previousLessonExercises || [];
}
console.log(`[getLesson] Lektion ${lessonId} geladen:`, {
@@ -897,7 +901,8 @@ export default class VocabService {
lessonType: plainLesson.lessonType,
exerciseCount: plainLesson.grammarExercises ? plainLesson.grammarExercises.length : 0,
reviewLessonsCount: plainLesson.reviewLessons ? plainLesson.reviewLessons.length : 0,
reviewVocabExercisesCount: plainLesson.reviewVocabExercises ? plainLesson.reviewVocabExercises.length : 0
reviewVocabExercisesCount: plainLesson.reviewVocabExercises ? plainLesson.reviewVocabExercises.length : 0,
previousLessonExercisesCount: plainLesson.previousLessonExercises ? plainLesson.previousLessonExercises.length : 0
});
return plainLesson;
}

View File

@@ -387,6 +387,8 @@
"successRate": "Erfolgsrate",
"modeMultipleChoice": "Multiple Choice",
"modeTyping": "Texteingabe",
"currentLesson": "Aktuelle Lektion",
"mixedReview": "Wiederholung",
"lessonCompleted": "Lektion abgeschlossen!",
"goToNextLesson": "Zur nächsten Lektion wechseln?",
"allLessonsCompleted": "Alle Lektionen abgeschlossen!",

View File

@@ -387,6 +387,8 @@
"successRate": "Success Rate",
"modeMultipleChoice": "Multiple Choice",
"modeTyping": "Text Input",
"currentLesson": "Current Lesson",
"mixedReview": "Review",
"lessonCompleted": "Lesson completed!",
"goToNextLesson": "Go to next lesson?",
"allLessonsCompleted": "All lessons completed!",

View File

@@ -73,6 +73,12 @@
</span>
</div>
<div class="stats-row">
<span class="mode-badge" :class="{ 'mode-active': vocabTrainerPhase === 'current' }">
{{ $t('socialnetwork.vocab.courses.currentLesson') || 'Aktuelle Lektion' }}
</span>
<span v-if="previousVocab && previousVocab.length > 0" class="mode-badge" :class="{ 'mode-active': vocabTrainerPhase === 'mixed' }">
{{ $t('socialnetwork.vocab.courses.mixedReview') || 'Wiederholung' }}
</span>
<span class="mode-badge" :class="{ 'mode-active': vocabTrainerMode === 'multiple_choice', 'mode-completed': vocabTrainerMode === 'typing' }">
{{ $t('socialnetwork.vocab.courses.modeMultipleChoice') }}
</span>
@@ -441,6 +447,9 @@ export default {
vocabTrainerTotalAttempts: 0,
vocabTrainerStats: {}, // { [vocabKey]: { attempts: 0, correct: 0, wrong: 0 } }
vocabTrainerChoiceOptions: [],
vocabTrainerPhase: 'current', // 'current' = aktuelle Lektion, 'mixed' = gemischt mit alten
vocabTrainerMixedPool: [], // Pool aus alten Lektionsvokabeln
vocabTrainerMixedAttempts: 0, // Zähler für Mixed-Phase
currentVocabQuestion: null,
vocabTrainerAnswer: '',
vocabTrainerSelectedChoice: null,
@@ -507,6 +516,16 @@ export default {
return [];
}
},
/** Vokabeln aus vorherigen Lektionen (für gemischte Wiederholung im Vokabeltrainer) */
previousVocab() {
try {
if (!this.lesson || !this.lesson.previousLessonExercises) return [];
return this._extractVocabFromExercises(this.lesson.previousLessonExercises);
} catch (e) {
console.error('Fehler in previousVocab:', e);
return [];
}
},
importantVocab() {
// Extrahiere wichtige Begriffe aus den Übungen
try {
@@ -998,6 +1017,7 @@ export default {
return;
}
console.log('[VocabLessonView] Vokabeln gefunden:', this.importantVocab.length);
console.log('[VocabLessonView] Alte Vokabeln:', this.previousVocab?.length || 0);
this.vocabTrainerActive = true;
this.vocabTrainerPool = [...this.importantVocab];
this.vocabTrainerMode = 'multiple_choice';
@@ -1006,6 +1026,11 @@ export default {
this.vocabTrainerWrong = 0;
this.vocabTrainerTotalAttempts = 0;
this.vocabTrainerStats = {};
this.vocabTrainerPhase = 'current';
this.vocabTrainerMixedAttempts = 0;
// Bereite Mixed-Pool aus alten Vokabeln vor (ohne Duplikate aus aktueller Lektion)
this.vocabTrainerMixedPool = this._buildMixedPool();
console.log('[VocabLessonView] Mixed-Pool:', this.vocabTrainerMixedPool.length, 'Vokabeln');
console.log('[VocabLessonView] Rufe nextVocabQuestion auf');
this.$nextTick(() => {
this.nextVocabQuestion();
@@ -1015,11 +1040,23 @@ export default {
this.vocabTrainerActive = false;
this.vocabTrainerMode = 'multiple_choice';
this.vocabTrainerAutoSwitchedToTyping = false;
this.vocabTrainerPhase = 'current';
this.vocabTrainerMixedAttempts = 0;
this.vocabTrainerMixedPool = [];
this.currentVocabQuestion = null;
this.vocabTrainerAnswer = '';
this.vocabTrainerSelectedChoice = null;
this.vocabTrainerAnswered = false;
},
/** Erstellt den Mixed-Pool aus vorherigen Lektions-Vokabeln (ohne Duplikate der aktuellen Lektion) */
_buildMixedPool() {
if (!this.previousVocab || this.previousVocab.length === 0) return [];
const currentKeys = new Set(this.importantVocab.map(v => this.getVocabKey(v)));
const filtered = this.previousVocab.filter(v => !currentKeys.has(this.getVocabKey(v)));
// Zufällig mischen und auf 40 begrenzen
const shuffled = [...filtered].sort(() => Math.random() - 0.5);
return shuffled.slice(0, 40);
},
getVocabKey(vocab) {
return `${vocab.learning}|${vocab.reference}`;
},
@@ -1031,13 +1068,43 @@ export default {
return this.vocabTrainerStats[key];
},
checkVocabModeSwitch() {
// Wechsle zu Texteingabe wenn 80% erreicht und mindestens 20 Versuche
if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= 20) {
const successRate = (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100;
if (successRate >= 80) {
const MC_THRESHOLD = 60; // Multiple Choice Versuche pro Phase
const MIXED_LIMIT = 40; // Anzahl gemischter Vokabeln aus alten Lektionen
if (this.vocabTrainerPhase === 'current') {
// Phase 1: Aktuelle Lektion - nach MC_THRESHOLD Versuchen mit 80% → Wechsel zu Mixed oder Typing
if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= MC_THRESHOLD) {
const successRate = (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100;
if (successRate >= 80) {
// Wechsel zur Mixed-Phase (falls alte Vokabeln vorhanden)
if (this.vocabTrainerMixedPool.length > 0) {
console.log('[VocabLessonView] Wechsel zu Mixed-Phase mit', this.vocabTrainerMixedPool.length, 'alten Vokabeln');
this.vocabTrainerPhase = 'mixed';
this.vocabTrainerPool = [...this.vocabTrainerMixedPool];
this.vocabTrainerMixedAttempts = 0;
// Stats zurücksetzen für neue Phase
this.vocabTrainerCorrect = 0;
this.vocabTrainerWrong = 0;
this.vocabTrainerTotalAttempts = 0;
} else {
// Kein Mixed-Pool → direkt zu Typing
this.vocabTrainerMode = 'typing';
this.vocabTrainerAutoSwitchedToTyping = true;
this.vocabTrainerPool = [...this.importantVocab];
this.vocabTrainerCorrect = 0;
this.vocabTrainerWrong = 0;
this.vocabTrainerTotalAttempts = 0;
}
}
}
} else if (this.vocabTrainerPhase === 'mixed') {
// Phase 2: Gemischte Wiederholung - nach MIXED_LIMIT Versuchen → Wechsel zu Typing mit allen Vokabeln
if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= MIXED_LIMIT) {
console.log('[VocabLessonView] Mixed-Phase abgeschlossen, wechsle zu Typing');
this.vocabTrainerMode = 'typing';
this.vocabTrainerAutoSwitchedToTyping = true; // Markiere als automatisch gewechselt
// Reset Stats für Texteingabe-Modus
this.vocabTrainerAutoSwitchedToTyping = true;
// Im Typing: Pool aus aktuellen + alten Vokabeln kombinieren
this.vocabTrainerPool = [...this.importantVocab, ...this.vocabTrainerMixedPool];
this.vocabTrainerCorrect = 0;
this.vocabTrainerWrong = 0;
this.vocabTrainerTotalAttempts = 0;
@@ -1048,6 +1115,12 @@ export default {
// Wechsle zurück zu Multiple Choice
this.vocabTrainerMode = 'multiple_choice';
this.vocabTrainerAutoSwitchedToTyping = false;
// Zurück zur aktuellen Phase mit passendem Pool
if (this.vocabTrainerPhase === 'mixed') {
this.vocabTrainerPool = [...this.vocabTrainerMixedPool];
} else {
this.vocabTrainerPool = [...this.importantVocab];
}
// Reset Stats für Multiple Choice Modus
this.vocabTrainerCorrect = 0;
this.vocabTrainerWrong = 0;