+
+
+
+
+
+
+
+
+
-
-
+
+
{{ $t('socialnetwork.vocab.courses.next') }}
@@ -252,10 +285,15 @@ export default {
// Vokabeltrainer
vocabTrainerActive: false,
vocabTrainerPool: [],
+ vocabTrainerMode: 'multiple_choice', // 'multiple_choice' oder 'typing'
vocabTrainerCorrect: 0,
vocabTrainerWrong: 0,
+ vocabTrainerTotalAttempts: 0,
+ vocabTrainerStats: {}, // { [vocabKey]: { attempts: 0, correct: 0, wrong: 0 } }
+ vocabTrainerChoiceOptions: [],
currentVocabQuestion: null,
vocabTrainerAnswer: '',
+ vocabTrainerSelectedChoice: null,
vocabTrainerAnswered: false,
vocabTrainerLastCorrect: false,
vocabTrainerDirection: 'L2R' // L2R: learning->reference, R2L: reference->learning
@@ -299,17 +337,22 @@ export default {
// Extrahiere wichtige Begriffe aus den Übungen
try {
if (!this.lesson || !this.lesson.grammarExercises || !Array.isArray(this.lesson.grammarExercises)) {
+ console.log('[importantVocab] Keine Übungen vorhanden');
return [];
}
const vocabMap = new Map();
- this.lesson.grammarExercises.forEach(exercise => {
+ this.lesson.grammarExercises.forEach((exercise, idx) => {
try {
+ console.log(`[importantVocab] Verarbeite Übung ${idx + 1}:`, exercise.title);
// Extrahiere aus questionData
const qData = this.getQuestionData(exercise);
const aData = this.getAnswerData(exercise);
+ console.log(`[importantVocab] qData:`, qData);
+ console.log(`[importantVocab] aData:`, aData);
+
if (qData && aData) {
// Für Multiple Choice: Extrahiere Optionen und richtige Antwort
if (this.getExerciseType(exercise) === 'multiple_choice') {
@@ -317,22 +360,28 @@ export default {
const correctIndex = aData.correctAnswer !== undefined ? aData.correctAnswer : (aData.correct || 0);
const correctAnswer = options[correctIndex] || '';
+ console.log(`[importantVocab] Multiple Choice - options:`, options, `correctIndex:`, correctIndex, `correctAnswer:`, correctAnswer);
+
if (correctAnswer) {
// Versuche die Frage zu analysieren (z.B. "Wie sagt man 'X' auf Bisaya?" oder "Was bedeutet 'X'?")
const question = qData.question || qData.text || '';
+ console.log(`[importantVocab] Frage:`, question);
// Pattern 1: "Wie sagt man 'X' auf Bisaya?" -> X ist Deutsch, correctAnswer ist Bisaya
let match = question.match(/['"]([^'"]+)['"]/);
if (match) {
const germanWord = match[1];
+ console.log(`[importantVocab] Pattern 1 gefunden - Bisaya:`, correctAnswer, `Deutsch:`, germanWord);
vocabMap.set(correctAnswer, { learning: correctAnswer, reference: germanWord });
} else {
// Pattern 2: "Was bedeutet 'X'?" -> X ist Bisaya, correctAnswer ist Deutsch
match = question.match(/Was bedeutet ['"]([^'"]+)['"]/);
if (match) {
const bisayaWord = match[1];
+ console.log(`[importantVocab] Pattern 2 gefunden - Bisaya:`, bisayaWord, `Deutsch:`, correctAnswer);
vocabMap.set(bisayaWord, { learning: bisayaWord, reference: correctAnswer });
} else {
+ console.log(`[importantVocab] Kein Pattern gefunden, Fallback`);
// Fallback: Verwende die richtige Antwort als Lernwort
vocabMap.set(correctAnswer, { learning: correctAnswer, reference: correctAnswer });
}
@@ -343,6 +392,7 @@ export default {
// Für Gap Fill: Extrahiere richtige Antworten
if (this.getExerciseType(exercise) === 'gap_fill') {
const answers = aData.answers || (aData.correct ? (Array.isArray(aData.correct) ? aData.correct : [aData.correct]) : []);
+ console.log(`[importantVocab] Gap Fill - answers:`, answers);
if (answers.length > 0) {
// Versuche aus dem Text Kontext zu extrahieren
const text = qData.text || '';
@@ -361,7 +411,9 @@ export default {
}
});
- return Array.from(vocabMap.values());
+ const result = Array.from(vocabMap.values());
+ console.log(`[importantVocab] Ergebnis:`, result);
+ return result;
} catch (e) {
console.error('Fehler in importantVocab computed property:', e);
return [];
@@ -522,11 +574,72 @@ export default {
const res = await apiClient.post(`/api/vocab/grammar-exercises/${exerciseId}/check`, { answer });
this.exerciseResults[exerciseId] = res.data;
+
+ // Prüfe ob alle Übungen bestanden sind
+ await this.checkLessonCompletion();
} catch (e) {
console.error('Fehler beim Prüfen der Antwort:', e);
alert(e.response?.data?.error || 'Fehler beim Prüfen der Antwort');
}
},
+ async checkLessonCompletion() {
+ // Prüfe ob alle Übungen korrekt beantwortet wurden
+ if (!this.lesson || !this.lesson.grammarExercises) return;
+
+ const allExercises = this.lesson.grammarExercises;
+ const allCompleted = allExercises.every(exercise => {
+ const result = this.exerciseResults[exercise.id];
+ return result && result.correct;
+ });
+
+ if (allCompleted) {
+ // Berechne Gesamt-Score
+ const totalExercises = allExercises.length;
+ const correctExercises = allExercises.filter(ex => this.exerciseResults[ex.id]?.correct).length;
+ const score = Math.round((correctExercises / totalExercises) * 100);
+
+ // Aktualisiere Fortschritt
+ try {
+ await apiClient.put(`/api/vocab/lessons/${this.lessonId}/progress`, {
+ completed: true,
+ score: score,
+ timeSpentMinutes: 0 // TODO: Zeit tracken
+ });
+
+ // Weiterleitung zur nächsten Lektion
+ await this.navigateToNextLesson();
+ } catch (e) {
+ console.error('Fehler beim Aktualisieren des Fortschritts:', e);
+ }
+ }
+ },
+ async navigateToNextLesson() {
+ try {
+ // Lade Kurs mit allen Lektionen
+ const courseRes = await apiClient.get(`/api/vocab/courses/${this.courseId}`);
+ const course = courseRes.data;
+
+ if (!course.lessons || course.lessons.length === 0) return;
+
+ // Finde aktuelle Lektion
+ const currentLessonIndex = course.lessons.findIndex(l => l.id === parseInt(this.lessonId));
+
+ if (currentLessonIndex >= 0 && currentLessonIndex < course.lessons.length - 1) {
+ // Nächste Lektion gefunden
+ const nextLesson = course.lessons[currentLessonIndex + 1];
+
+ // Zeige Erfolgs-Meldung und leite weiter
+ if (confirm(this.$t('socialnetwork.vocab.courses.lessonCompleted') + '\n' + this.$t('socialnetwork.vocab.courses.goToNextLesson'))) {
+ this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}/lessons/${nextLesson.id}`);
+ }
+ } else {
+ // Letzte Lektion - zeige Abschluss-Meldung
+ alert(this.$t('socialnetwork.vocab.courses.allLessonsCompleted'));
+ }
+ } catch (e) {
+ console.error('Fehler beim Laden der nächsten Lektion:', e);
+ }
+ },
back() {
this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}`);
},
@@ -535,52 +648,149 @@ export default {
if (!this.importantVocab || this.importantVocab.length === 0) return;
this.vocabTrainerActive = true;
this.vocabTrainerPool = [...this.importantVocab];
+ this.vocabTrainerMode = 'multiple_choice';
this.vocabTrainerCorrect = 0;
this.vocabTrainerWrong = 0;
+ this.vocabTrainerTotalAttempts = 0;
+ this.vocabTrainerStats = {};
this.nextVocabQuestion();
- this.$nextTick(() => {
- this.$refs.vocabInput?.focus?.();
- });
},
stopVocabTrainer() {
this.vocabTrainerActive = false;
this.currentVocabQuestion = null;
this.vocabTrainerAnswer = '';
+ this.vocabTrainerSelectedChoice = null;
this.vocabTrainerAnswered = false;
},
+ getVocabKey(vocab) {
+ return `${vocab.learning}|${vocab.reference}`;
+ },
+ getVocabStats(vocab) {
+ const key = this.getVocabKey(vocab);
+ if (!this.vocabTrainerStats[key]) {
+ this.vocabTrainerStats[key] = { attempts: 0, correct: 0, wrong: 0 };
+ }
+ return this.vocabTrainerStats[key];
+ },
+ checkVocabModeSwitch() {
+ // Wechsle zu Texteingabe wenn 80% erreicht und mindestens 20 Versuche
+ if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= 20) {
+ const successRate = (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100;
+ if (successRate >= 80) {
+ this.vocabTrainerMode = 'typing';
+ // Reset Stats für Texteingabe-Modus
+ this.vocabTrainerCorrect = 0;
+ this.vocabTrainerWrong = 0;
+ this.vocabTrainerTotalAttempts = 0;
+ }
+ }
+ },
+ buildChoiceOptions(correctAnswer, allVocabs) {
+ const options = new Set([correctAnswer]);
+ // Füge 3 Distraktoren hinzu
+ while (options.size < 4 && allVocabs.length > 1) {
+ const randomVocab = allVocabs[Math.floor(Math.random() * allVocabs.length)];
+ const distractor = this.vocabTrainerDirection === 'L2R' ? randomVocab.reference : randomVocab.learning;
+ if (distractor !== correctAnswer) {
+ options.add(distractor);
+ }
+ }
+ // Shuffle
+ const arr = Array.from(options);
+ for (let i = arr.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [arr[i], arr[j]] = [arr[j], arr[i]];
+ }
+ return arr;
+ },
nextVocabQuestion() {
if (!this.vocabTrainerPool || this.vocabTrainerPool.length === 0) {
this.currentVocabQuestion = null;
return;
}
+
+ // Prüfe ob Modus-Wechsel nötig ist
+ this.checkVocabModeSwitch();
+
+ // Wähle zufällige Vokabel
const randomIndex = Math.floor(Math.random() * this.vocabTrainerPool.length);
const vocab = this.vocabTrainerPool[randomIndex];
this.vocabTrainerDirection = Math.random() < 0.5 ? 'L2R' : 'R2L';
this.currentVocabQuestion = {
vocab: vocab,
prompt: this.vocabTrainerDirection === 'L2R' ? vocab.learning : vocab.reference,
- answer: this.vocabTrainerDirection === 'L2R' ? vocab.reference : vocab.learning
+ answer: this.vocabTrainerDirection === 'L2R' ? vocab.reference : vocab.learning,
+ key: this.getVocabKey(vocab)
};
+
+ // Reset UI
this.vocabTrainerAnswer = '';
+ this.vocabTrainerSelectedChoice = null;
this.vocabTrainerAnswered = false;
- this.$nextTick(() => {
- this.$refs.vocabInput?.focus?.();
- });
+
+ // Erstelle Choice-Optionen für Multiple Choice
+ if (this.vocabTrainerMode === 'multiple_choice') {
+ this.vocabTrainerChoiceOptions = this.buildChoiceOptions(this.currentVocabQuestion.answer, this.vocabTrainerPool);
+ }
+
+ // Fokussiere Eingabefeld im Typing-Modus
+ if (this.vocabTrainerMode === 'typing') {
+ this.$nextTick(() => {
+ this.$refs.vocabInput?.focus?.();
+ });
+ }
+ },
+ selectVocabChoice(option) {
+ this.vocabTrainerSelectedChoice = option;
},
normalizeVocab(s) {
return String(s || '').trim().toLowerCase().replace(/\s+/g, ' ');
},
checkVocabAnswer() {
- if (!this.currentVocabQuestion || !this.vocabTrainerAnswer.trim()) return;
- const userAnswer = this.normalizeVocab(this.vocabTrainerAnswer);
- const correctAnswer = this.normalizeVocab(this.currentVocabQuestion.answer);
- this.vocabTrainerLastCorrect = userAnswer === correctAnswer;
+ if (!this.currentVocabQuestion) return;
+
+ let userAnswer = '';
+ if (this.vocabTrainerMode === 'multiple_choice') {
+ if (!this.vocabTrainerSelectedChoice) return;
+ userAnswer = this.vocabTrainerSelectedChoice;
+ } else {
+ if (!this.vocabTrainerAnswer.trim()) return;
+ userAnswer = this.vocabTrainerAnswer;
+ }
+
+ const normalizedUser = this.normalizeVocab(userAnswer);
+ const normalizedCorrect = this.normalizeVocab(this.currentVocabQuestion.answer);
+ this.vocabTrainerLastCorrect = normalizedUser === normalizedCorrect;
+
+ // Update Stats
+ const stats = this.getVocabStats(this.currentVocabQuestion.vocab);
+ stats.attempts++;
+ this.vocabTrainerTotalAttempts++;
+
if (this.vocabTrainerLastCorrect) {
this.vocabTrainerCorrect++;
+ stats.correct++;
} else {
this.vocabTrainerWrong++;
+ stats.wrong++;
}
+
this.vocabTrainerAnswered = true;
+
+ // Im Typing-Modus: Automatisch zur nächsten Frage nach kurzer Pause (nur bei richtiger Antwort)
+ if (this.vocabTrainerMode === 'typing' && this.vocabTrainerLastCorrect) {
+ setTimeout(() => {
+ this.nextVocabQuestion();
+ }, 500);
+ }
+
+ // Im Typing-Modus bei falscher Antwort: Eingabefeld fokussieren für erneuten Versuch
+ if (this.vocabTrainerMode === 'typing' && !this.vocabTrainerLastCorrect) {
+ this.$nextTick(() => {
+ this.$refs.vocabInput?.focus?.();
+ this.vocabTrainerAnswer = '';
+ });
+ }
}
},
async mounted() {
@@ -921,15 +1131,48 @@ export default {
}
.vocab-trainer-stats {
- display: flex;
- justify-content: space-between;
- align-items: center;
margin-bottom: 15px;
padding: 10px;
background: #f5f5f5;
border-radius: 4px;
}
+.stats-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 15px;
+ margin-bottom: 10px;
+}
+
+.stats-row:last-child {
+ margin-bottom: 0;
+}
+
+.success-rate {
+ font-weight: bold;
+ color: #28a745;
+}
+
+.mode-badge {
+ padding: 5px 10px;
+ border-radius: 4px;
+ font-size: 0.9em;
+ background: #e9ecef;
+ color: #6c757d;
+}
+
+.mode-badge.mode-active {
+ background: #007bff;
+ color: white;
+ font-weight: bold;
+}
+
+.mode-badge.mode-completed {
+ background: #28a745;
+ color: white;
+}
+
.btn-stop-trainer {
padding: 5px 15px;
background: #dc3545;
@@ -969,9 +1212,46 @@ export default {
}
.vocab-answer-area {
+ margin-bottom: 15px;
+}
+
+.vocab-answer-area.typing {
display: flex;
gap: 10px;
- margin-bottom: 15px;
+}
+
+.vocab-answer-area.multiple-choice {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.choice-buttons {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 10px;
+ margin-bottom: 10px;
+}
+
+.choice-button {
+ padding: 12px;
+ border: 2px solid #ddd;
+ border-radius: 4px;
+ background: white;
+ cursor: pointer;
+ font-size: 1em;
+ transition: all 0.2s;
+}
+
+.choice-button:hover {
+ border-color: #007bff;
+ background: #f0f8ff;
+}
+
+.choice-button.selected {
+ border-color: #007bff;
+ background: #007bff;
+ color: white;
}
.vocab-input {
@@ -982,7 +1262,7 @@ export default {
font-size: 1em;
}
-.vocab-answer-area button {
+.btn-check {
padding: 10px 20px;
background: #4CAF50;
color: white;
@@ -992,11 +1272,11 @@ export default {
font-size: 1em;
}
-.vocab-answer-area button:hover:not(:disabled) {
+.btn-check:hover:not(:disabled) {
background: #45a049;
}
-.vocab-answer-area button:disabled {
+.btn-check:disabled {
background: #ccc;
cursor: not-allowed;
}