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 }}
|
||||
</button>
|
||||
</div>
|
||||
<button @click="checkVocabAnswer" :disabled="!vocabTrainerSelectedChoice" class="btn-check">
|
||||
{{ $t('socialnetwork.vocab.courses.checkAnswer') }}
|
||||
</button>
|
||||
<!-- Button entfernt: Prüfung erfolgt automatisch beim Klick auf Option -->
|
||||
</div>
|
||||
<!-- Texteingabe Modus -->
|
||||
<div v-else class="vocab-answer-area typing">
|
||||
@@ -257,6 +255,55 @@
|
||||
</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>
|
||||
|
||||
<script>
|
||||
@@ -298,7 +345,12 @@ export default {
|
||||
vocabTrainerLastCorrect: false,
|
||||
vocabTrainerDirection: 'L2R', // L2R: learning->reference, R2L: reference->learning
|
||||
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: {
|
||||
@@ -463,6 +515,12 @@ export default {
|
||||
this.isCheckingLessonCompletion = 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 {
|
||||
const res = await apiClient.get(`/api/vocab/lessons/${this.lessonId}`);
|
||||
this.lesson = res.data;
|
||||
@@ -614,7 +672,8 @@ export default {
|
||||
});
|
||||
} catch (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() {
|
||||
@@ -702,25 +761,13 @@ export default {
|
||||
console.log('[VocabLessonView] Nächste Lektion gefunden:', nextLesson.id);
|
||||
|
||||
// Zeige Erfolgs-Meldung und leite weiter
|
||||
const shouldNavigate = confirm(this.$t('socialnetwork.vocab.courses.lessonCompleted') + '\n' + this.$t('socialnetwork.vocab.courses.goToNextLesson'));
|
||||
|
||||
if (shouldNavigate) {
|
||||
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;
|
||||
}
|
||||
// Verwende Dialog statt confirm
|
||||
this.showNextLessonDialog = true;
|
||||
this.nextLessonId = nextLesson.id;
|
||||
} else {
|
||||
// Letzte Lektion - zeige Abschluss-Meldung
|
||||
console.log('[VocabLessonView] Letzte Lektion erreicht');
|
||||
alert(this.$t('socialnetwork.vocab.courses.allLessonsCompleted'));
|
||||
this.showCompletionDialog = true;
|
||||
this.isNavigatingToNext = false;
|
||||
this.isCheckingLessonCompletion = false;
|
||||
}
|
||||
@@ -733,6 +780,32 @@ export default {
|
||||
back() {
|
||||
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
|
||||
startVocabTrainer() {
|
||||
console.log('[VocabLessonView] startVocabTrainer aufgerufen');
|
||||
@@ -884,6 +957,10 @@ export default {
|
||||
},
|
||||
selectVocabChoice(option) {
|
||||
this.vocabTrainerSelectedChoice = option;
|
||||
// Bei Multiple Choice: Sofort prüfen
|
||||
if (this.vocabTrainerMode === 'multiple_choice') {
|
||||
this.checkVocabAnswer();
|
||||
}
|
||||
},
|
||||
normalizeVocab(s) {
|
||||
return String(s || '').trim().toLowerCase().replace(/\s+/g, ' ');
|
||||
@@ -919,16 +996,17 @@ export default {
|
||||
|
||||
this.vocabTrainerAnswered = true;
|
||||
|
||||
// Im Typing-Modus: Automatisch zur nächsten Frage nach kurzer Pause (nur bei richtiger Antwort)
|
||||
if (this.vocabTrainerMode === 'typing' && this.vocabTrainerLastCorrect) {
|
||||
// Automatisch zur nächsten Frage nach kurzer Pause (nur bei richtiger Antwort)
|
||||
if (this.vocabTrainerLastCorrect) {
|
||||
// Prüfe ob noch Fragen vorhanden sind
|
||||
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(() => {
|
||||
// Prüfe erneut, ob noch Fragen vorhanden sind (könnte sich geändert haben)
|
||||
if (this.vocabTrainerPool && this.vocabTrainerPool.length > 0 && this.vocabTrainerActive) {
|
||||
this.nextVocabQuestion();
|
||||
}
|
||||
}, 500);
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1498,4 +1576,77 @@ export default {
|
||||
.btn-continue:hover {
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user