feat(VocabCourseView): add hard vocabulary practice button and refresh logic
All checks were successful
Deploy to production / deploy (push) Successful in 1m57s
All checks were successful
Deploy to production / deploy (push) Successful in 1m57s
- Introduced a button to start hard vocabulary practice when applicable, enhancing user engagement with challenging vocabulary. - Implemented methods to manage and refresh the hard vocabulary list from local storage, ensuring users have access to their difficult words. - Added event listeners to refresh the hard vocabulary list on window focus, improving the overall user experience.
This commit is contained in:
@@ -16,6 +16,14 @@
|
|||||||
<span v-if="course.shareCode && isOwner" class="share-code">
|
<span v-if="course.shareCode && isOwner" class="share-code">
|
||||||
{{ $t('socialnetwork.vocab.courses.shareCode') }}: <code>{{ course.shareCode }}</code>
|
{{ $t('socialnetwork.vocab.courses.shareCode') }}: <code>{{ course.shareCode }}</code>
|
||||||
</span>
|
</span>
|
||||||
|
<button
|
||||||
|
v-if="hardVocabCount > 0"
|
||||||
|
type="button"
|
||||||
|
class="course-hard-practice-link"
|
||||||
|
@click="openHardPractice"
|
||||||
|
>
|
||||||
|
{{ $t('socialnetwork.vocab.courses.startHardVocabTrainer', { count: hardVocabCount }) }}
|
||||||
|
</button>
|
||||||
<button type="button" class="course-dictionary-link" @click="goCourseDictionary">
|
<button type="button" class="course-dictionary-link" @click="goCourseDictionary">
|
||||||
{{ $t('socialnetwork.vocab.dictionary.open') }}
|
{{ $t('socialnetwork.vocab.dictionary.open') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -345,6 +353,7 @@ export default {
|
|||||||
srsLoading: false,
|
srsLoading: false,
|
||||||
showAddLessonDialog: false,
|
showAddLessonDialog: false,
|
||||||
assistantSettings: null,
|
assistantSettings: null,
|
||||||
|
hardVocabList: [],
|
||||||
lessonFormTouched: false,
|
lessonFormTouched: false,
|
||||||
newLesson: {
|
newLesson: {
|
||||||
lessonNumber: 1,
|
lessonNumber: 1,
|
||||||
@@ -390,6 +399,9 @@ export default {
|
|||||||
}
|
}
|
||||||
return Array.isArray(this.srsDueItems) ? this.srsDueItems.length : 0;
|
return Array.isArray(this.srsDueItems) ? this.srsDueItems.length : 0;
|
||||||
},
|
},
|
||||||
|
hardVocabCount() {
|
||||||
|
return Array.isArray(this.hardVocabList) ? this.hardVocabList.length : 0;
|
||||||
|
},
|
||||||
dueReviewLessons() {
|
dueReviewLessons() {
|
||||||
return this.sortedLessons
|
return this.sortedLessons
|
||||||
.filter((lesson) => {
|
.filter((lesson) => {
|
||||||
@@ -519,12 +531,45 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
courseId() {
|
courseId() {
|
||||||
this.loadCourse();
|
this.loadCourse();
|
||||||
|
this.refreshHardVocabList();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
displayCourseTitle(course) {
|
displayCourseTitle(course) {
|
||||||
return localizeVocabCourseTitle(course?.title, this.$i18n?.locale) || '';
|
return localizeVocabCourseTitle(course?.title, this.$i18n?.locale) || '';
|
||||||
},
|
},
|
||||||
|
hardStorageKey() {
|
||||||
|
return this.courseId ? `yourpart:vocab:hardList:${this.courseId}` : null;
|
||||||
|
},
|
||||||
|
refreshHardVocabList() {
|
||||||
|
const key = this.hardStorageKey();
|
||||||
|
if (!key) {
|
||||||
|
this.hardVocabList = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(key);
|
||||||
|
const parsed = raw ? JSON.parse(raw) : {};
|
||||||
|
const values = parsed && typeof parsed === 'object' ? Object.values(parsed) : [];
|
||||||
|
this.hardVocabList = values
|
||||||
|
.map((entry, idx) => {
|
||||||
|
const learning = String(entry?.learning || '').trim();
|
||||||
|
const reference = String(entry?.reference || '').trim();
|
||||||
|
if (!learning || !reference) return null;
|
||||||
|
return {
|
||||||
|
id: `hard-${idx}-${learning}-${reference}`,
|
||||||
|
learning,
|
||||||
|
reference
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
} catch (_) {
|
||||||
|
this.hardVocabList = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleWindowFocus() {
|
||||||
|
this.refreshHardVocabList();
|
||||||
|
},
|
||||||
async loadCourse() {
|
async loadCourse() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
@@ -858,6 +903,14 @@ export default {
|
|||||||
lessonId: lesson.id
|
lessonId: lesson.id
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
openHardPractice() {
|
||||||
|
if (!this.hardVocabCount) return;
|
||||||
|
this.$refs.practiceDialog?.open?.({
|
||||||
|
courseId: this.courseId,
|
||||||
|
initialPool: this.hardVocabList,
|
||||||
|
onClose: () => this.refreshHardVocabList()
|
||||||
|
});
|
||||||
|
},
|
||||||
openSrsPractice() {
|
openSrsPractice() {
|
||||||
if (!this.srsDueItems.length) {
|
if (!this.srsDueItems.length) {
|
||||||
return;
|
return;
|
||||||
@@ -922,6 +975,11 @@ export default {
|
|||||||
this.loadCourse(),
|
this.loadCourse(),
|
||||||
this.loadAssistantSettings()
|
this.loadAssistantSettings()
|
||||||
]);
|
]);
|
||||||
|
this.refreshHardVocabList();
|
||||||
|
window.addEventListener('focus', this.handleWindowFocus);
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
window.removeEventListener('focus', this.handleWindowFocus);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -978,6 +1036,10 @@ export default {
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.course-hard-practice-link {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.course-assistant {
|
.course-assistant {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|||||||
Reference in New Issue
Block a user