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:
@@ -1310,8 +1310,20 @@ export default class VocabService {
|
|||||||
await progress.save();
|
await progress.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extrahiere richtige Antwort und Alternativen
|
||||||
|
const answerData = typeof exercise.answerData === 'string'
|
||||||
|
? JSON.parse(exercise.answerData)
|
||||||
|
: exercise.answerData;
|
||||||
|
|
||||||
|
const correctAnswer = Array.isArray(answerData.correct)
|
||||||
|
? answerData.correct[0]
|
||||||
|
: answerData.correct;
|
||||||
|
const alternatives = answerData.alternatives || [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
correct: isCorrect,
|
correct: isCorrect,
|
||||||
|
correctAnswer: correctAnswer,
|
||||||
|
alternatives: alternatives,
|
||||||
explanation: exercise.explanation,
|
explanation: exercise.explanation,
|
||||||
progress: progress.get({ plain: true })
|
progress: progress.get({ plain: true })
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -366,7 +366,16 @@
|
|||||||
"checkAnswer": "Antwort prüfen",
|
"checkAnswer": "Antwort prüfen",
|
||||||
"correct": "Richtig!",
|
"correct": "Richtig!",
|
||||||
"wrong": "Falsch",
|
"wrong": "Falsch",
|
||||||
"explanation": "Erklärung"
|
"explanation": "Erklärung",
|
||||||
|
"learn": "Lernen",
|
||||||
|
"exercises": "Übungen",
|
||||||
|
"learnVocabulary": "Vokabeln lernen",
|
||||||
|
"culturalNotes": "Kulturelle Notizen",
|
||||||
|
"importantVocab": "Wichtige Begriffe",
|
||||||
|
"noVocabInfo": "Lies die Beschreibung oben und die Erklärungen in den Übungen, um die wichtigsten Begriffe zu lernen.",
|
||||||
|
"startExercises": "Zu den Übungen",
|
||||||
|
"correctAnswer": "Richtige Antwort",
|
||||||
|
"alternatives": "Alternative Antworten"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -366,7 +366,16 @@
|
|||||||
"checkAnswer": "Check Answer",
|
"checkAnswer": "Check Answer",
|
||||||
"correct": "Correct!",
|
"correct": "Correct!",
|
||||||
"wrong": "Wrong",
|
"wrong": "Wrong",
|
||||||
"explanation": "Explanation"
|
"explanation": "Explanation",
|
||||||
|
"learn": "Learn",
|
||||||
|
"exercises": "Exercises",
|
||||||
|
"learnVocabulary": "Learn Vocabulary",
|
||||||
|
"culturalNotes": "Cultural Notes",
|
||||||
|
"importantVocab": "Important Vocabulary",
|
||||||
|
"noVocabInfo": "Read the description above and the explanations in the exercises to learn the most important terms.",
|
||||||
|
"startExercises": "Start Exercises",
|
||||||
|
"correctAnswer": "Correct Answer",
|
||||||
|
"alternatives": "Alternative Answers"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,62 @@
|
|||||||
|
|
||||||
<p v-if="lesson.description" class="lesson-description">{{ lesson.description }}</p>
|
<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>
|
<h3>{{ $t('socialnetwork.vocab.courses.grammarExercises') }}</h3>
|
||||||
<div v-for="exercise in lesson.grammarExercises" :key="exercise.id" class="exercise-item">
|
<div v-for="exercise in lesson.grammarExercises" :key="exercise.id" class="exercise-item">
|
||||||
<h4>{{ exercise.title }}</h4>
|
<h4>{{ exercise.title }}</h4>
|
||||||
@@ -34,7 +89,13 @@
|
|||||||
</button>
|
</button>
|
||||||
<div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
|
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -55,7 +116,13 @@
|
|||||||
</button>
|
</button>
|
||||||
<div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
|
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -72,7 +139,13 @@
|
|||||||
</button>
|
</button>
|
||||||
<div v-if="exerciseResults[exercise.id]" class="exercise-result" :class="exerciseResults[exercise.id].correct ? 'correct' : 'wrong'">
|
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -112,9 +185,54 @@ export default {
|
|||||||
loading: false,
|
loading: false,
|
||||||
lesson: null,
|
lesson: null,
|
||||||
exerciseAnswers: {},
|
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: {
|
computed: {
|
||||||
...mapGetters(['user'])
|
...mapGetters(['user'])
|
||||||
},
|
},
|
||||||
@@ -441,4 +559,127 @@ export default {
|
|||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
overflow-x: auto;
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user