feat(vocab): enhance feedback acknowledgment and localization in vocabulary lessons
All checks were successful
Deploy to production / deploy (push) Successful in 2m41s
All checks were successful
Deploy to production / deploy (push) Successful in 2m41s
- Added acknowledgment messages for exercise reinforcement in German, English, and Spanish localization files to improve user guidance. - Updated VocabLessonView to include a feedback acknowledgment button, enhancing user interaction after answering questions. - Implemented logic to track feedback acknowledgment state, improving the flow of lesson reviews and user experience.
This commit is contained in:
@@ -902,11 +902,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Nach falscher Kapitel-Antwort: zuerst Lösung, dann optional zum Üben -->
|
||||
<div v-if="showExerciseReinforcementDialog" class="dialog-overlay" @click.self="closeExerciseReinforcementDialog">
|
||||
<div v-if="showExerciseReinforcementDialog" class="dialog-overlay">
|
||||
<div class="dialog" style="width: 440px; height: auto;">
|
||||
<div class="dialog-header">
|
||||
<span class="dialog-title">{{ $t('socialnetwork.vocab.courses.exerciseWrongTitle') }}</span>
|
||||
<span class="dialog-close" @click="closeExerciseReinforcementDialog">✖</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<p v-if="exerciseReinforcementCorrectAnswer" class="exercise-reinforcement-correct">
|
||||
@@ -917,10 +916,10 @@
|
||||
</div>
|
||||
<div class="dialog-footer dialog-footer--stack">
|
||||
<button type="button" class="dialog-button dialog-button--primary" @click="confirmExerciseReinforcement">
|
||||
{{ $t('socialnetwork.vocab.courses.exerciseReinforcementGoPractice') }}
|
||||
{{ $t('socialnetwork.vocab.courses.exerciseReinforcementGoPracticeAck') }}
|
||||
</button>
|
||||
<button type="button" class="dialog-button" @click="closeExerciseReinforcementDialog">
|
||||
{{ $t('socialnetwork.vocab.courses.exerciseReinforcementStay') }}
|
||||
{{ $t('socialnetwork.vocab.courses.exerciseReinforcementStayAck') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1013,6 +1012,8 @@ export default {
|
||||
exerciseSequentialIndex: 0,
|
||||
/** Aus vorherigen Lektionen (MC-Optionen nach Fragentyp Ziel-/Muttersprache) */
|
||||
distractorPool: { target: [], native: [] },
|
||||
/** Fortschritt aller Kurslektionen inkl. lessonState für Spezial-Trainer-Boost */
|
||||
courseProgressList: [],
|
||||
/** { [exerciseId]: { options: string[], useTextAnswer: boolean } } */
|
||||
mcRandomizedOptions: {},
|
||||
lessonStatePersistenceReady: false,
|
||||
@@ -2038,6 +2039,7 @@ export default {
|
||||
this.vocabTrainerPhase = 'current';
|
||||
this.vocabTrainerCurrentAttempts = 0;
|
||||
this.vocabTrainerReviewAttempts = 0;
|
||||
this.courseProgressList = [];
|
||||
this.distractorPool = { target: [], native: [] };
|
||||
this.mcRandomizedOptions = {};
|
||||
// Reset Flags
|
||||
@@ -2059,6 +2061,7 @@ export default {
|
||||
try {
|
||||
const res = await apiClient.get(`/api/vocab/lessons/${this.lessonId}`);
|
||||
this.lesson = res.data;
|
||||
await this.loadCourseProgressForBoost();
|
||||
debugLog('[VocabLessonView] Geladene Lektion:', this.lesson?.id, this.lesson?.title);
|
||||
if (this.$route.query.assistant) {
|
||||
this.$nextTick(() => {
|
||||
@@ -2095,6 +2098,14 @@ export default {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async loadCourseProgressForBoost() {
|
||||
try {
|
||||
const { data } = await apiClient.get(`/api/vocab/courses/${this.courseId}/progress`);
|
||||
this.courseProgressList = Array.isArray(data) ? data : [];
|
||||
} catch (e) {
|
||||
this.courseProgressList = [];
|
||||
}
|
||||
},
|
||||
focusAssistantCard() {
|
||||
const target = this.$refs.assistantCard;
|
||||
if (!target || typeof target.scrollIntoView !== 'function') {
|
||||
@@ -2688,9 +2699,43 @@ export default {
|
||||
if (!this.previousVocab || this.previousVocab.length === 0) return [];
|
||||
const currentKeys = new Set(this.trainableLessonVocab.map(v => this.getVocabKey(v)));
|
||||
const filtered = this.previousVocab.filter(v => !currentKeys.has(this.getVocabKey(v)));
|
||||
// Zufällig mischen und auf 40 begrenzen
|
||||
const shuffled = [...filtered].sort(() => Math.random() - 0.5);
|
||||
return shuffled.slice(0, 40);
|
||||
const filteredByKey = new Map(filtered.map((v) => [this.getVocabKey(v), v]));
|
||||
|
||||
const weakMap = new Map();
|
||||
(this.courseProgressList || []).forEach((entry) => {
|
||||
const lessonId = Number(entry?.lessonId);
|
||||
if (!Number.isFinite(lessonId) || lessonId === Number(this.lessonId)) return;
|
||||
const weakList = Array.isArray(entry?.lessonState?.reviewWeakVocab)
|
||||
? entry.lessonState.reviewWeakVocab
|
||||
: [];
|
||||
weakList.forEach((w) => {
|
||||
const learning = String(w?.learning || '').trim();
|
||||
const reference = String(w?.reference || '').trim();
|
||||
if (!learning || !reference) return;
|
||||
const key = `${learning}|${reference}`;
|
||||
if (currentKeys.has(key) || !filteredByKey.has(key)) return;
|
||||
const prev = weakMap.get(key) || 0;
|
||||
weakMap.set(key, prev + Math.max(1, Number(w?.wrongCount) || 1));
|
||||
});
|
||||
});
|
||||
|
||||
const boosted = [];
|
||||
Array.from(weakMap.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 20)
|
||||
.forEach(([key, score]) => {
|
||||
const vocab = filteredByKey.get(key);
|
||||
if (!vocab) return;
|
||||
const weight = Math.min(4, Math.max(1, Math.round(score)));
|
||||
for (let i = 0; i < weight; i++) {
|
||||
boosted.push(vocab);
|
||||
}
|
||||
});
|
||||
|
||||
const boostedKeySet = new Set(Array.from(weakMap.keys()));
|
||||
const rest = filtered.filter((v) => !boostedKeySet.has(this.getVocabKey(v)));
|
||||
const shuffled = [...rest].sort(() => Math.random() - 0.5);
|
||||
return [...boosted, ...shuffled].slice(0, 40);
|
||||
},
|
||||
getVocabKey(vocab) {
|
||||
return `${vocab.learning}|${vocab.reference}`;
|
||||
|
||||
Reference in New Issue
Block a user