feat(VocabLessonView): enhance exercise access and vocab trainer experience
- Added conditional access to exercises based on user progress and previous vocab completion. - Introduced visual indicators for exercise availability and review priorities to guide users. - Updated vocab trainer descriptions and button labels to reflect the user's current state and encourage engagement. - Implemented methods to manage exercise unlock conditions and improve user feedback on progress.
This commit is contained in:
@@ -20,7 +20,8 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:class="{ active: activeTab === 'exercises' }"
|
:class="{ active: activeTab === 'exercises' }"
|
||||||
@click="activeTab = 'exercises'"
|
:disabled="!canAccessExercises"
|
||||||
|
@click="openExercisesTab"
|
||||||
class="tab-button"
|
class="tab-button"
|
||||||
>
|
>
|
||||||
{{ $t('socialnetwork.vocab.courses.exercises') }}
|
{{ $t('socialnetwork.vocab.courses.exercises') }}
|
||||||
@@ -190,10 +191,18 @@
|
|||||||
<!-- Vokabeltrainer -->
|
<!-- Vokabeltrainer -->
|
||||||
<div v-if="importantVocab && importantVocab.length > 0" class="vocab-trainer-section">
|
<div v-if="importantVocab && importantVocab.length > 0" class="vocab-trainer-section">
|
||||||
<h4>{{ $t('socialnetwork.vocab.courses.vocabTrainer') }}</h4>
|
<h4>{{ $t('socialnetwork.vocab.courses.vocabTrainer') }}</h4>
|
||||||
|
<div v-if="hasPreviousVocab" class="review-priority-note">
|
||||||
|
<strong>Tageswiederholung zuerst</strong>
|
||||||
|
<p>Ältere Vokabeln stehen hier bewusst vor der Kapitel-Prüfung, damit Wiederholung täglich mitläuft.</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="hasExercises && !canAccessExercises" class="exercise-lock-note">
|
||||||
|
<strong>Kapitel-Prüfung noch gesperrt</strong>
|
||||||
|
<p>{{ exerciseUnlockHint }}</p>
|
||||||
|
</div>
|
||||||
<div v-if="!vocabTrainerActive" class="vocab-trainer-start">
|
<div v-if="!vocabTrainerActive" class="vocab-trainer-start">
|
||||||
<p>{{ $t('socialnetwork.vocab.courses.vocabTrainerDescription') }}</p>
|
<p>{{ hasPreviousVocab ? 'Starte mit der Wiederholung älterer Vokabeln und arbeite dich dann zur aktuellen Lektion vor.' : $t('socialnetwork.vocab.courses.vocabTrainerDescription') }}</p>
|
||||||
<button @click="startVocabTrainer" class="btn-start-trainer">
|
<button @click="startVocabTrainer" class="btn-start-trainer">
|
||||||
{{ $t('socialnetwork.vocab.courses.startVocabTrainer') }}
|
{{ hasPreviousVocab ? 'Tageswiederholung starten' : $t('socialnetwork.vocab.courses.startVocabTrainer') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="vocab-trainer-active">
|
<div v-else class="vocab-trainer-active">
|
||||||
@@ -293,8 +302,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Button um zu Übungen zu wechseln -->
|
<!-- Button um zu Übungen zu wechseln -->
|
||||||
<div v-if="hasExercises" class="continue-to-exercises">
|
<div v-if="hasExercises && canAccessExercises" class="continue-to-exercises">
|
||||||
<button @click="activeTab = 'exercises'" class="btn-continue">
|
<button @click="openExercisesTab" class="btn-continue">
|
||||||
{{ $t('socialnetwork.vocab.courses.startExercises') }}
|
{{ $t('socialnetwork.vocab.courses.startExercises') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -692,6 +701,7 @@ export default {
|
|||||||
vocabTrainerPhase: 'current', // 'current' = aktuelle Lektion, 'mixed' = gemischt mit alten
|
vocabTrainerPhase: 'current', // 'current' = aktuelle Lektion, 'mixed' = gemischt mit alten
|
||||||
vocabTrainerMixedPool: [], // Pool aus alten Lektionsvokabeln
|
vocabTrainerMixedPool: [], // Pool aus alten Lektionsvokabeln
|
||||||
vocabTrainerMixedAttempts: 0, // Zähler für Mixed-Phase
|
vocabTrainerMixedAttempts: 0, // Zähler für Mixed-Phase
|
||||||
|
exercisePreparationCompleted: false,
|
||||||
currentVocabQuestion: null,
|
currentVocabQuestion: null,
|
||||||
vocabTrainerAnswer: '',
|
vocabTrainerAnswer: '',
|
||||||
vocabTrainerSelectedChoice: null,
|
vocabTrainerSelectedChoice: null,
|
||||||
@@ -727,6 +737,20 @@ export default {
|
|||||||
const exercises = this.effectiveExercises;
|
const exercises = this.effectiveExercises;
|
||||||
return exercises && Array.isArray(exercises) && exercises.length > 0;
|
return exercises && Array.isArray(exercises) && exercises.length > 0;
|
||||||
},
|
},
|
||||||
|
hasPreviousVocab() {
|
||||||
|
return Array.isArray(this.previousVocab) && this.previousVocab.length > 0;
|
||||||
|
},
|
||||||
|
canAccessExercises() {
|
||||||
|
if (!this.hasExercises) return false;
|
||||||
|
const isReview = this.lesson?.lessonType === 'review' || this.lesson?.lessonType === 'vocab_review';
|
||||||
|
return isReview || this.exercisePreparationCompleted;
|
||||||
|
},
|
||||||
|
exerciseUnlockHint() {
|
||||||
|
if (this.hasPreviousVocab) {
|
||||||
|
return 'Beantworte zuerst einige Wiederholungsfragen aus älteren Lektionen richtig. Danach wird die Kapitel-Prüfung freigeschaltet.';
|
||||||
|
}
|
||||||
|
return 'Arbeite zuerst kurz mit dem Vokabeltrainer der aktuellen Lektion. Danach wird die Kapitel-Prüfung freigeschaltet.';
|
||||||
|
},
|
||||||
/** Für Wiederholungslektionen: Übungen aus vorherigen Lektionen (Kapitelprüfung). Sonst: eigene Grammatik-Übungen. */
|
/** Für Wiederholungslektionen: Übungen aus vorherigen Lektionen (Kapitelprüfung). Sonst: eigene Grammatik-Übungen. */
|
||||||
effectiveExercises() {
|
effectiveExercises() {
|
||||||
if (!this.lesson) return [];
|
if (!this.lesson) return [];
|
||||||
@@ -836,6 +860,40 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
openExercisesTab() {
|
||||||
|
if (!this.canAccessExercises) {
|
||||||
|
this.activeTab = 'learn';
|
||||||
|
this.showErrorDialog = true;
|
||||||
|
this.errorMessage = this.exerciseUnlockHint;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.activeTab = 'exercises';
|
||||||
|
},
|
||||||
|
updateExerciseUnlockState() {
|
||||||
|
if (this.exercisePreparationCompleted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.hasExercises) {
|
||||||
|
this.exercisePreparationCompleted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minimumAttempts = this.hasPreviousVocab ? 8 : 6;
|
||||||
|
const successRate = this.vocabTrainerTotalAttempts > 0
|
||||||
|
? (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
if (this.hasPreviousVocab) {
|
||||||
|
if (this.vocabTrainerPhase === 'mixed' && this.vocabTrainerTotalAttempts >= minimumAttempts && successRate >= 70) {
|
||||||
|
this.exercisePreparationCompleted = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.vocabTrainerTotalAttempts >= minimumAttempts && successRate >= 70) {
|
||||||
|
this.exercisePreparationCompleted = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
_extractVocabFromExercises(exercises) {
|
_extractVocabFromExercises(exercises) {
|
||||||
// Sicherstellen, dass exercises ein Array ist
|
// Sicherstellen, dass exercises ein Array ist
|
||||||
if (!exercises) {
|
if (!exercises) {
|
||||||
@@ -989,6 +1047,11 @@ export default {
|
|||||||
this.assistantMessages = [];
|
this.assistantMessages = [];
|
||||||
this.assistantInput = '';
|
this.assistantInput = '';
|
||||||
this.assistantError = '';
|
this.assistantError = '';
|
||||||
|
this.exercisePreparationCompleted = false;
|
||||||
|
this.vocabTrainerActive = false;
|
||||||
|
this.vocabTrainerPool = [];
|
||||||
|
this.vocabTrainerMixedPool = [];
|
||||||
|
this.vocabTrainerPhase = 'current';
|
||||||
// Reset Flags
|
// Reset Flags
|
||||||
this.isCheckingLessonCompletion = false;
|
this.isCheckingLessonCompletion = false;
|
||||||
this.isNavigatingToNext = false;
|
this.isNavigatingToNext = false;
|
||||||
@@ -998,6 +1061,9 @@ export default {
|
|||||||
if (tabParam === 'learn') {
|
if (tabParam === 'learn') {
|
||||||
this.activeTab = 'learn';
|
this.activeTab = 'learn';
|
||||||
}
|
}
|
||||||
|
if (tabParam === 'exercises') {
|
||||||
|
this.activeTab = 'learn';
|
||||||
|
}
|
||||||
if (this.$route.query.assistant) {
|
if (this.$route.query.assistant) {
|
||||||
this.activeTab = 'learn';
|
this.activeTab = 'learn';
|
||||||
}
|
}
|
||||||
@@ -1406,10 +1472,13 @@ 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)
|
// Bereite Mixed-Pool aus alten Vokabeln vor (ohne Duplikate aus aktueller Lektion)
|
||||||
this.vocabTrainerMixedPool = this._buildMixedPool();
|
this.vocabTrainerMixedPool = this._buildMixedPool();
|
||||||
|
this.vocabTrainerPhase = this.vocabTrainerMixedPool.length > 0 ? 'mixed' : 'current';
|
||||||
|
this.vocabTrainerMixedAttempts = 0;
|
||||||
|
this.vocabTrainerPool = this.vocabTrainerPhase === 'mixed'
|
||||||
|
? [...this.vocabTrainerMixedPool]
|
||||||
|
: [...this.importantVocab];
|
||||||
debugLog('[VocabLessonView] Mixed-Pool:', this.vocabTrainerMixedPool.length, 'Vokabeln');
|
debugLog('[VocabLessonView] Mixed-Pool:', this.vocabTrainerMixedPool.length, 'Vokabeln');
|
||||||
debugLog('[VocabLessonView] Rufe nextVocabQuestion auf');
|
debugLog('[VocabLessonView] Rufe nextVocabQuestion auf');
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -1451,6 +1520,8 @@ export default {
|
|||||||
const MC_THRESHOLD = 60; // Multiple Choice Versuche pro Phase
|
const MC_THRESHOLD = 60; // Multiple Choice Versuche pro Phase
|
||||||
const MIXED_LIMIT = 40; // Anzahl gemischter Vokabeln aus alten Lektionen
|
const MIXED_LIMIT = 40; // Anzahl gemischter Vokabeln aus alten Lektionen
|
||||||
|
|
||||||
|
this.updateExerciseUnlockState();
|
||||||
|
|
||||||
if (this.vocabTrainerPhase === 'current') {
|
if (this.vocabTrainerPhase === 'current') {
|
||||||
// Phase 1: Aktuelle Lektion - nach MC_THRESHOLD Versuchen mit 80% → Wechsel zu Mixed oder Typing
|
// Phase 1: Aktuelle Lektion - nach MC_THRESHOLD Versuchen mit 80% → Wechsel zu Mixed oder Typing
|
||||||
if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= MC_THRESHOLD) {
|
if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= MC_THRESHOLD) {
|
||||||
@@ -2349,6 +2420,31 @@ export default {
|
|||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.review-priority-note,
|
||||||
|
.exercise-lock-note {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-priority-note {
|
||||||
|
background: #eef7ff;
|
||||||
|
border: 1px solid #b9d8ff;
|
||||||
|
color: #234a72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exercise-lock-note {
|
||||||
|
background: #fff3cd;
|
||||||
|
border: 1px solid #ffc107;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.review-priority-note strong,
|
||||||
|
.exercise-lock-note strong {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.vocab-trainer-start {
|
.vocab-trainer-start {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user