Enhance VocabLessonView and VocabService with new learning features
- Added a tabbed interface in VocabLessonView for 'Learn' and 'Exercises' sections, improving user navigation. - Implemented logic to display important vocabulary and cultural notes in the learning section. - Updated exercise result display to include correct answers and alternatives for better user feedback. - Enhanced VocabService to extract correct answers and alternatives from exercise data, supporting the new UI features. - Added new translations for vocabulary-related terms in both English and German, ensuring consistency across the application.
This commit is contained in:
@@ -9,7 +9,62 @@
|
||||
|
||||
<p v-if="lesson.description" class="lesson-description">{{ lesson.description }}</p>
|
||||
|
||||
<div v-if="lesson.grammarExercises && lesson.grammarExercises.length > 0" class="grammar-exercises">
|
||||
<!-- Tabs für Lernen und Übungen -->
|
||||
<div class="lesson-tabs">
|
||||
<button
|
||||
:class="{ active: activeTab === 'learn' }"
|
||||
@click="activeTab = 'learn'"
|
||||
class="tab-button"
|
||||
>
|
||||
{{ $t('socialnetwork.vocab.courses.learn') }}
|
||||
</button>
|
||||
<button
|
||||
:class="{ active: activeTab === 'exercises' }"
|
||||
@click="activeTab = 'exercises'"
|
||||
class="tab-button"
|
||||
:disabled="!hasExercises"
|
||||
>
|
||||
{{ $t('socialnetwork.vocab.courses.exercises') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Lernen-Tab -->
|
||||
<div v-if="activeTab === 'learn'" class="learn-section">
|
||||
<h3>{{ $t('socialnetwork.vocab.courses.learnVocabulary') }}</h3>
|
||||
|
||||
<!-- Kulturelle Notizen -->
|
||||
<div v-if="lesson.culturalNotes" class="cultural-notes">
|
||||
<h4>{{ $t('socialnetwork.vocab.courses.culturalNotes') }}</h4>
|
||||
<p>{{ lesson.culturalNotes }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Wichtige Begriffe aus den Übungen -->
|
||||
<div v-if="importantVocab.length > 0" class="vocab-list">
|
||||
<h4>{{ $t('socialnetwork.vocab.courses.importantVocab') }}</h4>
|
||||
<div class="vocab-items">
|
||||
<div v-for="(vocab, index) in importantVocab" :key="index" class="vocab-item">
|
||||
<strong>{{ vocab.learning }}</strong>
|
||||
<span class="separator">→</span>
|
||||
<span>{{ vocab.reference }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hinweis wenn keine Vokabeln vorhanden -->
|
||||
<div v-else class="no-vocab-info">
|
||||
<p>{{ $t('socialnetwork.vocab.courses.noVocabInfo') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Button um zu Übungen zu wechseln -->
|
||||
<div v-if="hasExercises" class="continue-to-exercises">
|
||||
<button @click="activeTab = 'exercises'" class="btn-continue">
|
||||
{{ $t('socialnetwork.vocab.courses.startExercises') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Übungen-Tab -->
|
||||
<div v-if="activeTab === 'exercises' && lesson.grammarExercises && lesson.grammarExercises.length > 0" class="grammar-exercises">
|
||||
<h3>{{ $t('socialnetwork.vocab.courses.grammarExercises') }}</h3>
|
||||
<div v-for="exercise in lesson.grammarExercises" :key="exercise.id" class="exercise-item">
|
||||
<h4>{{ exercise.title }}</h4>
|
||||
@@ -34,7 +89,13 @@
|
||||
</button>
|
||||
<div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
|
||||
<strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
|
||||
<p v-if="exercise.explanation" class="exercise-explanation">{{ exercise.explanation }}</p>
|
||||
<p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].correctAnswer" class="correct-answer">
|
||||
{{ $t('socialnetwork.vocab.courses.correctAnswer') }}: {{ exerciseResults[exercise.id].correctAnswer }}
|
||||
</p>
|
||||
<p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].alternatives && exerciseResults[exercise.id].alternatives.length > 0" class="alternatives">
|
||||
{{ $t('socialnetwork.vocab.courses.alternatives') }}: {{ exerciseResults[exercise.id].alternatives.join(', ') }}
|
||||
</p>
|
||||
<p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -55,7 +116,13 @@
|
||||
</button>
|
||||
<div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
|
||||
<strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
|
||||
<p v-if="exercise.explanation" class="exercise-explanation">{{ exercise.explanation }}</p>
|
||||
<p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].correctAnswer" class="correct-answer">
|
||||
{{ $t('socialnetwork.vocab.courses.correctAnswer') }}: {{ exerciseResults[exercise.id].correctAnswer }}
|
||||
</p>
|
||||
<p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].alternatives && exerciseResults[exercise.id].alternatives.length > 0" class="alternatives">
|
||||
{{ $t('socialnetwork.vocab.courses.alternatives') }}: {{ exerciseResults[exercise.id].alternatives.join(', ') }}
|
||||
</p>
|
||||
<p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +139,13 @@
|
||||
</button>
|
||||
<div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
|
||||
<strong>{{ exerciseResults[exercise.id].correct ? $t('socialnetwork.vocab.courses.correct') : $t('socialnetwork.vocab.courses.wrong') }}</strong>
|
||||
<p v-if="exercise.explanation" class="exercise-explanation">{{ exercise.explanation }}</p>
|
||||
<p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].correctAnswer" class="correct-answer">
|
||||
{{ $t('socialnetwork.vocab.courses.correctAnswer') }}: {{ exerciseResults[exercise.id].correctAnswer }}
|
||||
</p>
|
||||
<p v-if="!exerciseResults[exercise.id].correct && exerciseResults[exercise.id].alternatives && exerciseResults[exercise.id].alternatives.length > 0" class="alternatives">
|
||||
{{ $t('socialnetwork.vocab.courses.alternatives') }}: {{ exerciseResults[exercise.id].alternatives.join(', ') }}
|
||||
</p>
|
||||
<p v-if="exerciseResults[exercise.id].explanation" class="exercise-explanation">{{ exerciseResults[exercise.id].explanation }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -112,9 +185,54 @@ export default {
|
||||
loading: false,
|
||||
lesson: null,
|
||||
exerciseAnswers: {},
|
||||
exerciseResults: {}
|
||||
exerciseResults: {},
|
||||
activeTab: 'learn' // Standardmäßig "Lernen"-Tab
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user']),
|
||||
hasExercises() {
|
||||
return this.lesson && this.lesson.grammarExercises && this.lesson.grammarExercises.length > 0;
|
||||
},
|
||||
importantVocab() {
|
||||
// Extrahiere wichtige Begriffe aus den Übungen
|
||||
if (!this.lesson || !this.lesson.grammarExercises) return [];
|
||||
|
||||
const vocabMap = new Map();
|
||||
|
||||
this.lesson.grammarExercises.forEach(exercise => {
|
||||
// Extrahiere aus questionData
|
||||
const qData = this.getQuestionData(exercise);
|
||||
const aData = this.getAnswerData(exercise);
|
||||
|
||||
if (qData && aData) {
|
||||
// Für Multiple Choice: Extrahiere Optionen und richtige Antwort
|
||||
if (this.getExerciseType(exercise) === 'multiple_choice') {
|
||||
const correct = Array.isArray(aData.correct) ? aData.correct[0] : aData.correct;
|
||||
const question = qData.text || '';
|
||||
|
||||
// Versuche die Frage zu analysieren (z.B. "Wie sagt man X auf Bisaya?")
|
||||
const match = question.match(/['"]([^'"]+)['"]/);
|
||||
if (match) {
|
||||
const germanWord = match[1];
|
||||
vocabMap.set(correct, { learning: correct, reference: germanWord });
|
||||
} else if (correct) {
|
||||
// Fallback: Verwende die richtige Antwort als Lernwort
|
||||
vocabMap.set(correct, { learning: correct, reference: correct });
|
||||
}
|
||||
}
|
||||
|
||||
// Für Gap Fill: Extrahiere richtige Antworten
|
||||
if (this.getExerciseType(exercise) === 'gap_fill' && aData.correct) {
|
||||
const correct = Array.isArray(aData.correct) ? aData.correct[0] : aData.correct;
|
||||
vocabMap.set(correct, { learning: correct, reference: correct });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(vocabMap.values());
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user'])
|
||||
},
|
||||
@@ -441,4 +559,127 @@ export default {
|
||||
font-size: 0.85em;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.lesson-tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin: 20px 0;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
color: #666;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab-button:hover:not(:disabled) {
|
||||
color: #333;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
color: #007bff;
|
||||
border-bottom-color: #007bff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Lernen-Sektion */
|
||||
.learn-section {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.learn-section h3 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.cultural-notes {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
background: #e7f3ff;
|
||||
border-left: 4px solid #007bff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.cultural-notes h4 {
|
||||
margin-top: 0;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.vocab-list {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.vocab-list h4 {
|
||||
margin-bottom: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.vocab-items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.vocab-item {
|
||||
padding: 10px;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.vocab-item strong {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.no-vocab-info {
|
||||
padding: 15px;
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
border-radius: 4px;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.continue-to-exercises {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-continue {
|
||||
padding: 12px 24px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1.1em;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.btn-continue:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user