|
|
@@ -103,6 +103,9 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
readonly catalogCategoryIndex = signal(0);
|
|
|
readonly selectedNodes = signal<Set<string>>(new Set());
|
|
|
readonly words = signal<Word[]>([]);
|
|
|
+ readonly wordPreviewPage = signal(1);
|
|
|
+ readonly wordPreviewTotal = signal(0);
|
|
|
+ readonly wordPreviewLoading = signal(false);
|
|
|
readonly selected = signal<Set<string>>(new Set());
|
|
|
readonly selectionRevealed = signal<Set<string>>(new Set());
|
|
|
readonly selectionAssessment = signal<Record<string, 'known' | 'unknown'>>({});
|
|
|
@@ -183,6 +186,17 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
|
|
|
readonly current = computed(() => this.words()[this.currentIndex()] ?? null);
|
|
|
readonly currentQuestion = computed(() => this.questions()[this.currentIndex()] ?? null);
|
|
|
+ readonly wordPreviewPageCount = computed(() => Math.max(1, Math.ceil(this.wordPreviewTotal() / this.wordPreviewPageSize)));
|
|
|
+ readonly wordPreviewPages = computed(() => {
|
|
|
+ const pageCount = this.wordPreviewPageCount();
|
|
|
+ if (pageCount <= 3) return Array.from({ length: pageCount }, (_, index) => index + 1);
|
|
|
+ const start = Math.min(Math.max(1, this.wordPreviewPage() - 1), pageCount - 2);
|
|
|
+ return Array.from({ length: 3 }, (_, index) => start + index);
|
|
|
+ });
|
|
|
+ readonly wordPreviewRangeStart = computed(() => this.wordPreviewTotal()
|
|
|
+ ? ((this.wordPreviewPage() - 1) * this.wordPreviewPageSize) + 1
|
|
|
+ : 0);
|
|
|
+ readonly wordPreviewRangeEnd = computed(() => Math.min(this.wordPreviewPage() * this.wordPreviewPageSize, this.wordPreviewTotal()));
|
|
|
readonly memoryView = computed(() => buildAntiForgetView(this.calendarItems()));
|
|
|
readonly memorySummary = computed(() => this.memoryView().summary);
|
|
|
readonly memoryCalendar = computed(() => buildAntiForgetCalendar(
|
|
|
@@ -214,6 +228,7 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
private readonly api = inject(ApiService);
|
|
|
private readonly session = inject(SessionService);
|
|
|
private readonly platform = inject(PlatformService);
|
|
|
+ private readonly wordPreviewPageSize = 30;
|
|
|
private examTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
private antiStartedAt = Date.now();
|
|
|
|
|
|
@@ -368,6 +383,15 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
this.toggleNode(node);
|
|
|
}
|
|
|
|
|
|
+ openWordPreviewPage(page: number): void {
|
|
|
+ const target = Math.min(Math.max(1, page), this.wordPreviewPageCount());
|
|
|
+ if (target === this.wordPreviewPage() || this.wordPreviewLoading()) return;
|
|
|
+ const previous = this.wordPreviewPage();
|
|
|
+ this.wordPreviewPage.set(target);
|
|
|
+ this.wordPreviewLoading.set(true);
|
|
|
+ this.loadReadOnlyWords(true, previous);
|
|
|
+ }
|
|
|
+
|
|
|
selectCatalogCategory(index: number): void {
|
|
|
if (index === this.catalogCategoryIndex()) return;
|
|
|
this.catalogCategoryIndex.set(index);
|
|
|
@@ -1012,18 +1036,7 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
private loadStageWords(): void {
|
|
|
const nid = this.query('nid');
|
|
|
if (nid && this.isReadOnlyWordView) {
|
|
|
- const learner = this.session.student() ?? this.session.user();
|
|
|
- this.api.get<unknown>('e_ck_list', {
|
|
|
- checkwords: 2,
|
|
|
- nids: nid,
|
|
|
- modelId: 52,
|
|
|
- cpage: 1,
|
|
|
- psize: 500,
|
|
|
- uid: learner?.userId,
|
|
|
- }).pipe(finalize(() => this.loading.set(false))).subscribe({
|
|
|
- next: ({ result }) => this.initializeStage(this.findList(result).map((item) => this.toWord(item)).filter((word) => word.word)),
|
|
|
- error: (error: ApiError) => this.error.set(error.message),
|
|
|
- });
|
|
|
+ this.loadReadOnlyWords();
|
|
|
return;
|
|
|
}
|
|
|
if (nid) {
|
|
|
@@ -1042,6 +1055,38 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
this.initializeStage(stored);
|
|
|
}
|
|
|
|
|
|
+ private loadReadOnlyWords(pageChange = false, previousPage = 1): void {
|
|
|
+ const learner = this.session.student() ?? this.session.user();
|
|
|
+ this.api.get<unknown>('e_ck_list', {
|
|
|
+ checkwords: 2,
|
|
|
+ nids: this.query('nid'),
|
|
|
+ modelId: 52,
|
|
|
+ cpage: this.wordPreviewPage(),
|
|
|
+ psize: this.wordPreviewPageSize,
|
|
|
+ uid: learner?.userId,
|
|
|
+ }).pipe(finalize(() => {
|
|
|
+ if (pageChange) this.wordPreviewLoading.set(false);
|
|
|
+ else this.loading.set(false);
|
|
|
+ })).subscribe({
|
|
|
+ next: ({ result, page }) => {
|
|
|
+ const words = this.findList(result).map((item) => this.toWord(item)).filter((word) => word.word);
|
|
|
+ this.words.set(words);
|
|
|
+ this.wordPreviewTotal.set(Math.max(words.length, Number(page?.itemCount ?? 0)));
|
|
|
+ if (pageChange) {
|
|
|
+ setTimeout(() => document.getElementById('readonly-word-title')?.scrollIntoView({ behavior: 'smooth', block: 'start' }));
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error: (error: ApiError) => {
|
|
|
+ if (pageChange) {
|
|
|
+ this.wordPreviewPage.set(previousPage);
|
|
|
+ this.platform.toast('该页单词加载失败,请重试', 'error');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.error.set(error.message);
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
private initializeStage(source: Word[]): void {
|
|
|
const words = this.isStudyListPage ? source : this.shuffle(source);
|
|
|
this.words.set(words);
|
|
|
@@ -1613,6 +1658,9 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
this.nodes.set([]);
|
|
|
this.selectedNodes.set(new Set());
|
|
|
this.words.set([]);
|
|
|
+ this.wordPreviewPage.set(1);
|
|
|
+ this.wordPreviewTotal.set(0);
|
|
|
+ this.wordPreviewLoading.set(false);
|
|
|
this.selected.set(new Set());
|
|
|
this.selectionRevealed.set(new Set());
|
|
|
this.selectionAssessment.set({});
|