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 }); 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') { if (plainLesson.lessonType === 'review' || plainLesson.lessonType === 'vocab_review') {
plainLesson.reviewLessons = await this._getReviewLessons(plainLesson.courseId, plainLesson.lessonNumber); 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:`, { console.log(`[getLesson] Lektion ${lessonId} geladen:`, {
@@ -897,7 +901,8 @@ export default class VocabService {
lessonType: plainLesson.lessonType, lessonType: plainLesson.lessonType,
exerciseCount: plainLesson.grammarExercises ? plainLesson.grammarExercises.length : 0, exerciseCount: plainLesson.grammarExercises ? plainLesson.grammarExercises.length : 0,
reviewLessonsCount: plainLesson.reviewLessons ? plainLesson.reviewLessons.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; return plainLesson;
} }

View File

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

View File

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

View File

@@ -73,6 +73,12 @@
</span> </span>
</div> </div>
<div class="stats-row"> <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' }"> <span class="mode-badge" :class="{ 'mode-active': vocabTrainerMode === 'multiple_choice', 'mode-completed': vocabTrainerMode === 'typing' }">
{{ $t('socialnetwork.vocab.courses.modeMultipleChoice') }} {{ $t('socialnetwork.vocab.courses.modeMultipleChoice') }}
</span> </span>
@@ -441,6 +447,9 @@ export default {
vocabTrainerTotalAttempts: 0, vocabTrainerTotalAttempts: 0,
vocabTrainerStats: {}, // { [vocabKey]: { attempts: 0, correct: 0, wrong: 0 } } vocabTrainerStats: {}, // { [vocabKey]: { attempts: 0, correct: 0, wrong: 0 } }
vocabTrainerChoiceOptions: [], 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, currentVocabQuestion: null,
vocabTrainerAnswer: '', vocabTrainerAnswer: '',
vocabTrainerSelectedChoice: null, vocabTrainerSelectedChoice: null,
@@ -507,6 +516,16 @@ export default {
return []; 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() { importantVocab() {
// Extrahiere wichtige Begriffe aus den Übungen // Extrahiere wichtige Begriffe aus den Übungen
try { try {
@@ -998,6 +1017,7 @@ export default {
return; return;
} }
console.log('[VocabLessonView] Vokabeln gefunden:', this.importantVocab.length); console.log('[VocabLessonView] Vokabeln gefunden:', this.importantVocab.length);
console.log('[VocabLessonView] Alte Vokabeln:', this.previousVocab?.length || 0);
this.vocabTrainerActive = true; this.vocabTrainerActive = true;
this.vocabTrainerPool = [...this.importantVocab]; this.vocabTrainerPool = [...this.importantVocab];
this.vocabTrainerMode = 'multiple_choice'; this.vocabTrainerMode = 'multiple_choice';
@@ -1006,6 +1026,11 @@ export default {
this.vocabTrainerWrong = 0; this.vocabTrainerWrong = 0;
this.vocabTrainerTotalAttempts = 0; this.vocabTrainerTotalAttempts = 0;
this.vocabTrainerStats = {}; 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'); console.log('[VocabLessonView] Rufe nextVocabQuestion auf');
this.$nextTick(() => { this.$nextTick(() => {
this.nextVocabQuestion(); this.nextVocabQuestion();
@@ -1015,11 +1040,23 @@ export default {
this.vocabTrainerActive = false; this.vocabTrainerActive = false;
this.vocabTrainerMode = 'multiple_choice'; this.vocabTrainerMode = 'multiple_choice';
this.vocabTrainerAutoSwitchedToTyping = false; this.vocabTrainerAutoSwitchedToTyping = false;
this.vocabTrainerPhase = 'current';
this.vocabTrainerMixedAttempts = 0;
this.vocabTrainerMixedPool = [];
this.currentVocabQuestion = null; this.currentVocabQuestion = null;
this.vocabTrainerAnswer = ''; this.vocabTrainerAnswer = '';
this.vocabTrainerSelectedChoice = null; this.vocabTrainerSelectedChoice = null;
this.vocabTrainerAnswered = false; 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) { getVocabKey(vocab) {
return `${vocab.learning}|${vocab.reference}`; return `${vocab.learning}|${vocab.reference}`;
}, },
@@ -1031,13 +1068,43 @@ export default {
return this.vocabTrainerStats[key]; return this.vocabTrainerStats[key];
}, },
checkVocabModeSwitch() { checkVocabModeSwitch() {
// Wechsle zu Texteingabe wenn 80% erreicht und mindestens 20 Versuche const MC_THRESHOLD = 60; // Multiple Choice Versuche pro Phase
if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= 20) { 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; const successRate = (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100;
if (successRate >= 80) { 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.vocabTrainerMode = 'typing';
this.vocabTrainerAutoSwitchedToTyping = true; // Markiere als automatisch gewechselt this.vocabTrainerAutoSwitchedToTyping = true;
// Reset Stats für Texteingabe-Modus 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;
// Im Typing: Pool aus aktuellen + alten Vokabeln kombinieren
this.vocabTrainerPool = [...this.importantVocab, ...this.vocabTrainerMixedPool];
this.vocabTrainerCorrect = 0; this.vocabTrainerCorrect = 0;
this.vocabTrainerWrong = 0; this.vocabTrainerWrong = 0;
this.vocabTrainerTotalAttempts = 0; this.vocabTrainerTotalAttempts = 0;
@@ -1048,6 +1115,12 @@ export default {
// Wechsle zurück zu Multiple Choice // Wechsle zurück zu Multiple Choice
this.vocabTrainerMode = 'multiple_choice'; this.vocabTrainerMode = 'multiple_choice';
this.vocabTrainerAutoSwitchedToTyping = false; 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 // Reset Stats für Multiple Choice Modus
this.vocabTrainerCorrect = 0; this.vocabTrainerCorrect = 0;
this.vocabTrainerWrong = 0; this.vocabTrainerWrong = 0;