Implement dialog prompts for lesson navigation and error handling in VocabLessonView
- Removed the manual check answer button, transitioning to automatic answer verification upon option selection. - Added dialog overlays for navigating to the next lesson, course completion notifications, and error messages, enhancing user interaction. - Introduced methods to handle dialog confirmations and cancellations, improving the flow of lesson transitions and error management. - Updated styles for dialog components to ensure a consistent and user-friendly interface.
This commit is contained in:
@@ -106,9 +106,7 @@
|
|||||||
{{ option }}
|
{{ option }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button @click="checkVocabAnswer" :disabled="!vocabTrainerSelectedChoice" class="btn-check">
|
<!-- Button entfernt: Prüfung erfolgt automatisch beim Klick auf Option -->
|
||||||
{{ $t('socialnetwork.vocab.courses.checkAnswer') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Texteingabe Modus -->
|
<!-- Texteingabe Modus -->
|
||||||
<div v-else class="vocab-answer-area typing">
|
<div v-else class="vocab-answer-area typing">
|
||||||
@@ -257,6 +255,55 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Dialog für Navigation zur nächsten Lektion -->
|
||||||
|
<div v-if="showNextLessonDialog" class="dialog-overlay" @click.self="cancelNavigateToNextLesson">
|
||||||
|
<div class="dialog" style="width: 400px; height: auto;">
|
||||||
|
<div class="dialog-header">
|
||||||
|
<span class="dialog-title">{{ $t('socialnetwork.vocab.courses.lessonCompleted') }}</span>
|
||||||
|
<span class="dialog-close" @click="cancelNavigateToNextLesson">✖</span>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-body">
|
||||||
|
<p>{{ $t('socialnetwork.vocab.courses.goToNextLesson') }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<button @click="confirmNavigateToNextLesson" class="dialog-button">{{ $t('general.yes') }}</button>
|
||||||
|
<button @click="cancelNavigateToNextLesson" class="dialog-button">{{ $t('general.no') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dialog für Kurs-Abschluss -->
|
||||||
|
<div v-if="showCompletionDialog" class="dialog-overlay" @click.self="closeCompletionDialog">
|
||||||
|
<div class="dialog" style="width: 400px; height: auto;">
|
||||||
|
<div class="dialog-header">
|
||||||
|
<span class="dialog-title">{{ $t('socialnetwork.vocab.courses.allLessonsCompleted') }}</span>
|
||||||
|
<span class="dialog-close" @click="closeCompletionDialog">✖</span>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-body">
|
||||||
|
<p>{{ $t('socialnetwork.vocab.courses.allLessonsCompleted') }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<button @click="closeCompletionDialog" class="dialog-button">{{ $t('general.ok') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dialog für Fehler -->
|
||||||
|
<div v-if="showErrorDialog" class="dialog-overlay" @click.self="closeErrorDialog">
|
||||||
|
<div class="dialog" style="width: 400px; height: auto;">
|
||||||
|
<div class="dialog-header">
|
||||||
|
<span class="dialog-title">{{ $t('error-title') }}</span>
|
||||||
|
<span class="dialog-close" @click="closeErrorDialog">✖</span>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-body">
|
||||||
|
<p>{{ errorMessage }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<button @click="closeErrorDialog" class="dialog-button">{{ $t('general.ok') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -298,7 +345,12 @@ export default {
|
|||||||
vocabTrainerLastCorrect: false,
|
vocabTrainerLastCorrect: false,
|
||||||
vocabTrainerDirection: 'L2R', // L2R: learning->reference, R2L: reference->learning
|
vocabTrainerDirection: 'L2R', // L2R: learning->reference, R2L: reference->learning
|
||||||
isCheckingLessonCompletion: false, // Flag um Endlosschleife zu verhindern
|
isCheckingLessonCompletion: false, // Flag um Endlosschleife zu verhindern
|
||||||
isNavigatingToNext: false // Flag um mehrfache Navigation zu verhindern
|
isNavigatingToNext: false, // Flag um mehrfache Navigation zu verhindern
|
||||||
|
showNextLessonDialog: false,
|
||||||
|
nextLessonId: null,
|
||||||
|
showCompletionDialog: false,
|
||||||
|
showErrorDialog: false,
|
||||||
|
errorMessage: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -463,6 +515,12 @@ export default {
|
|||||||
this.isCheckingLessonCompletion = false;
|
this.isCheckingLessonCompletion = false;
|
||||||
this.isNavigatingToNext = false;
|
this.isNavigatingToNext = false;
|
||||||
|
|
||||||
|
// Prüfe ob 'tab' Query-Parameter vorhanden ist (für Navigation zur nächsten Lektion)
|
||||||
|
const tabParam = this.$route.query.tab;
|
||||||
|
if (tabParam === 'learn') {
|
||||||
|
this.activeTab = 'learn';
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await apiClient.get(`/api/vocab/lessons/${this.lessonId}`);
|
const res = await apiClient.get(`/api/vocab/lessons/${this.lessonId}`);
|
||||||
this.lesson = res.data;
|
this.lesson = res.data;
|
||||||
@@ -614,7 +672,8 @@ export default {
|
|||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Fehler beim Prüfen der Antwort:', e);
|
console.error('Fehler beim Prüfen der Antwort:', e);
|
||||||
alert(e.response?.data?.error || 'Fehler beim Prüfen der Antwort');
|
this.showErrorDialog = true;
|
||||||
|
this.errorMessage = e.response?.data?.error || 'Fehler beim Prüfen der Antwort';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async checkLessonCompletion() {
|
async checkLessonCompletion() {
|
||||||
@@ -702,25 +761,13 @@ export default {
|
|||||||
console.log('[VocabLessonView] Nächste Lektion gefunden:', nextLesson.id);
|
console.log('[VocabLessonView] Nächste Lektion gefunden:', nextLesson.id);
|
||||||
|
|
||||||
// Zeige Erfolgs-Meldung und leite weiter
|
// Zeige Erfolgs-Meldung und leite weiter
|
||||||
const shouldNavigate = confirm(this.$t('socialnetwork.vocab.courses.lessonCompleted') + '\n' + this.$t('socialnetwork.vocab.courses.goToNextLesson'));
|
// Verwende Dialog statt confirm
|
||||||
|
this.showNextLessonDialog = true;
|
||||||
if (shouldNavigate) {
|
this.nextLessonId = nextLesson.id;
|
||||||
console.log('[VocabLessonView] Navigiere zur nächsten Lektion:', nextLesson.id);
|
|
||||||
// Setze Flags zurück BEVOR die Navigation stattfindet
|
|
||||||
this.isNavigatingToNext = false;
|
|
||||||
this.isCheckingLessonCompletion = false;
|
|
||||||
// Verwende replace statt push, um die History nicht zu belasten
|
|
||||||
this.$router.replace(`/socialnetwork/vocab/courses/${this.courseId}/lessons/${nextLesson.id}`);
|
|
||||||
} else {
|
|
||||||
// Benutzer hat abgebrochen - Flag zurücksetzen
|
|
||||||
console.log('[VocabLessonView] Navigation abgebrochen');
|
|
||||||
this.isNavigatingToNext = false;
|
|
||||||
this.isCheckingLessonCompletion = false;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Letzte Lektion - zeige Abschluss-Meldung
|
// Letzte Lektion - zeige Abschluss-Meldung
|
||||||
console.log('[VocabLessonView] Letzte Lektion erreicht');
|
console.log('[VocabLessonView] Letzte Lektion erreicht');
|
||||||
alert(this.$t('socialnetwork.vocab.courses.allLessonsCompleted'));
|
this.showCompletionDialog = true;
|
||||||
this.isNavigatingToNext = false;
|
this.isNavigatingToNext = false;
|
||||||
this.isCheckingLessonCompletion = false;
|
this.isCheckingLessonCompletion = false;
|
||||||
}
|
}
|
||||||
@@ -733,6 +780,32 @@ export default {
|
|||||||
back() {
|
back() {
|
||||||
this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}`);
|
this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}`);
|
||||||
},
|
},
|
||||||
|
confirmNavigateToNextLesson() {
|
||||||
|
if (this.nextLessonId) {
|
||||||
|
console.log('[VocabLessonView] Navigiere zur nächsten Lektion:', this.nextLessonId);
|
||||||
|
// Setze Flags zurück BEVOR die Navigation stattfindet
|
||||||
|
this.isNavigatingToNext = false;
|
||||||
|
this.isCheckingLessonCompletion = false;
|
||||||
|
this.showNextLessonDialog = false;
|
||||||
|
// Verwende replace statt push, um die History nicht zu belasten
|
||||||
|
// Setze activeTab auf 'learn' für die nächste Lektion via Query-Parameter
|
||||||
|
this.$router.replace(`/socialnetwork/vocab/courses/${this.courseId}/lessons/${this.nextLessonId}?tab=learn`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancelNavigateToNextLesson() {
|
||||||
|
console.log('[VocabLessonView] Navigation abgebrochen');
|
||||||
|
this.isNavigatingToNext = false;
|
||||||
|
this.isCheckingLessonCompletion = false;
|
||||||
|
this.showNextLessonDialog = false;
|
||||||
|
this.nextLessonId = null;
|
||||||
|
},
|
||||||
|
closeCompletionDialog() {
|
||||||
|
this.showCompletionDialog = false;
|
||||||
|
},
|
||||||
|
closeErrorDialog() {
|
||||||
|
this.showErrorDialog = false;
|
||||||
|
this.errorMessage = '';
|
||||||
|
},
|
||||||
// Vokabeltrainer-Methoden
|
// Vokabeltrainer-Methoden
|
||||||
startVocabTrainer() {
|
startVocabTrainer() {
|
||||||
console.log('[VocabLessonView] startVocabTrainer aufgerufen');
|
console.log('[VocabLessonView] startVocabTrainer aufgerufen');
|
||||||
@@ -884,6 +957,10 @@ export default {
|
|||||||
},
|
},
|
||||||
selectVocabChoice(option) {
|
selectVocabChoice(option) {
|
||||||
this.vocabTrainerSelectedChoice = option;
|
this.vocabTrainerSelectedChoice = option;
|
||||||
|
// Bei Multiple Choice: Sofort prüfen
|
||||||
|
if (this.vocabTrainerMode === 'multiple_choice') {
|
||||||
|
this.checkVocabAnswer();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
normalizeVocab(s) {
|
normalizeVocab(s) {
|
||||||
return String(s || '').trim().toLowerCase().replace(/\s+/g, ' ');
|
return String(s || '').trim().toLowerCase().replace(/\s+/g, ' ');
|
||||||
@@ -919,16 +996,17 @@ export default {
|
|||||||
|
|
||||||
this.vocabTrainerAnswered = true;
|
this.vocabTrainerAnswered = true;
|
||||||
|
|
||||||
// Im Typing-Modus: Automatisch zur nächsten Frage nach kurzer Pause (nur bei richtiger Antwort)
|
// Automatisch zur nächsten Frage nach kurzer Pause (nur bei richtiger Antwort)
|
||||||
if (this.vocabTrainerMode === 'typing' && this.vocabTrainerLastCorrect) {
|
if (this.vocabTrainerLastCorrect) {
|
||||||
// Prüfe ob noch Fragen vorhanden sind
|
// Prüfe ob noch Fragen vorhanden sind
|
||||||
if (this.vocabTrainerPool && this.vocabTrainerPool.length > 0) {
|
if (this.vocabTrainerPool && this.vocabTrainerPool.length > 0) {
|
||||||
|
const delay = this.vocabTrainerMode === 'multiple_choice' ? 2000 : 500; // 2 Sekunden für Multiple Choice, 500ms für Typing
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Prüfe erneut, ob noch Fragen vorhanden sind (könnte sich geändert haben)
|
// Prüfe erneut, ob noch Fragen vorhanden sind (könnte sich geändert haben)
|
||||||
if (this.vocabTrainerPool && this.vocabTrainerPool.length > 0 && this.vocabTrainerActive) {
|
if (this.vocabTrainerPool && this.vocabTrainerPool.length > 0 && this.vocabTrainerActive) {
|
||||||
this.nextVocabQuestion();
|
this.nextVocabQuestion();
|
||||||
}
|
}
|
||||||
}, 500);
|
}, delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1498,4 +1576,77 @@ export default {
|
|||||||
.btn-continue:hover {
|
.btn-continue:hover {
|
||||||
background: #0056b3;
|
background: #0056b3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dialog Styles */
|
||||||
|
.dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
background: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
max-width: 90%;
|
||||||
|
max-height: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
background-color: #F9A22C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-close {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-left: 10px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-body {
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-button:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user