|
|
@@ -23,7 +23,7 @@ import {
|
|
|
Volume2,
|
|
|
X,
|
|
|
} from 'lucide-angular';
|
|
|
-import { catchError, finalize, forkJoin, of } from 'rxjs';
|
|
|
+import { catchError, finalize, forkJoin, Observable, of, switchMap } from 'rxjs';
|
|
|
import { GAME_AUDIO } from '../../core/app.constants';
|
|
|
import { ApiError, ApiService } from '../../core/api.service';
|
|
|
import {
|
|
|
@@ -109,6 +109,7 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
readonly selected = signal<Set<string>>(new Set());
|
|
|
readonly selectionRevealed = signal<Set<string>>(new Set());
|
|
|
readonly selectionAssessment = signal<Record<string, 'known' | 'unknown'>>({});
|
|
|
+ readonly activeSelectionBucket = signal<'pending' | 'completed'>('pending');
|
|
|
readonly currentIndex = signal(0);
|
|
|
readonly revealed = signal(false);
|
|
|
readonly completed = signal(false);
|
|
|
@@ -185,6 +186,16 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
};
|
|
|
|
|
|
readonly current = computed(() => this.words()[this.currentIndex()] ?? null);
|
|
|
+ readonly completedSelectionWords = computed(() => this.words().filter((word) => this.selectionWordCompleted(word)));
|
|
|
+ readonly pendingSelectionWords = computed(() => this.words().filter((word) => !this.selectionWordCompleted(word)));
|
|
|
+ readonly newlyKnownWords = computed(() => this.words().filter((word) => !this.wordHistoricallyLearned(word) && this.selectionAssessment()[word.id] === 'known'));
|
|
|
+ readonly selectionActionLabel = computed(() => {
|
|
|
+ const hasLearningWords = this.selected().size > 0;
|
|
|
+ const hasKnownWords = this.newlyKnownWords().length > 0;
|
|
|
+ if (hasLearningWords && hasKnownWords) return '保存并开始学习';
|
|
|
+ if (hasKnownWords) return '保存识词结果';
|
|
|
+ return '开始学习';
|
|
|
+ });
|
|
|
readonly currentQuestion = computed(() => this.questions()[this.currentIndex()] ?? null);
|
|
|
readonly wordPreviewPageCount = computed(() => Math.max(1, Math.ceil(this.wordPreviewTotal() / this.wordPreviewPageSize)));
|
|
|
readonly wordPreviewPages = computed(() => {
|
|
|
@@ -452,13 +463,26 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
toggleWord(word: Word): void {
|
|
|
const shouldSelect = !this.selected().has(word.id);
|
|
|
if (!this.setWordSelected(word, shouldSelect) || !this.isRefineSelectionPage) return;
|
|
|
- this.setSelectionAssessment(word, shouldSelect ? 'unknown' : 'known');
|
|
|
+ if (shouldSelect) this.setSelectionAssessment(word, 'unknown');
|
|
|
+ else this.clearSelectionAssessment(word);
|
|
|
}
|
|
|
|
|
|
selectionWordRevealed(word: Word): boolean {
|
|
|
return this.selectionRevealed().has(word.id);
|
|
|
}
|
|
|
|
|
|
+ wordHistoricallyLearned(word: Word): boolean {
|
|
|
+ return Number(word['w_learned'] ?? 0) > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ selectionWordCompleted(word: Word): boolean {
|
|
|
+ return this.wordHistoricallyLearned(word) || this.selectionAssessment()[word.id] === 'known';
|
|
|
+ }
|
|
|
+
|
|
|
+ selectionCompletionLabel(word: Word): string {
|
|
|
+ return this.wordHistoricallyLearned(word) ? '历史已学完' : '本次已认识';
|
|
|
+ }
|
|
|
+
|
|
|
revealSelectionWord(word: Word): void {
|
|
|
this.selectionRevealed.update((current) => {
|
|
|
const next = new Set(current);
|
|
|
@@ -487,9 +511,10 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
|
|
|
startSelected(): void {
|
|
|
- const chosen = this.words().filter((word) => this.selected().has(word.id));
|
|
|
- if (!chosen.length) {
|
|
|
- this.platform.toast('请至少选择一个单词', 'error');
|
|
|
+ const chosen = this.pendingSelectionWords().filter((word) => this.selected().has(word.id));
|
|
|
+ const newlyKnown = this.newlyKnownWords();
|
|
|
+ if (!chosen.length && !newlyKnown.length) {
|
|
|
+ this.platform.toast('请先判断单词是否认识,或选择要学习的单词', 'error');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -500,13 +525,15 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
return;
|
|
|
}
|
|
|
this.submitting.set(true);
|
|
|
- this.api.post('e_add_words', {
|
|
|
- uid: student.userId,
|
|
|
- wordsId: chosen.map((word) => word.wordId).join(','),
|
|
|
- kcid: this.query('pnid'),
|
|
|
- save: 1,
|
|
|
- }).pipe(finalize(() => this.submitting.set(false))).subscribe({
|
|
|
- next: () => this.beginSelectedFlow(chosen, ''),
|
|
|
+ this.persistWordsAsLearned(newlyKnown, student).pipe(finalize(() => this.submitting.set(false))).subscribe({
|
|
|
+ next: () => {
|
|
|
+ if (newlyKnown.length) this.commitKnownWords(newlyKnown);
|
|
|
+ if (chosen.length) {
|
|
|
+ this.beginSelectedFlow(chosen, '');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.platform.toast('识词结果已保存', 'success');
|
|
|
+ },
|
|
|
error: (error: ApiError) => this.platform.toast(error.message, 'error'),
|
|
|
});
|
|
|
return;
|
|
|
@@ -1252,7 +1279,10 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
};
|
|
|
if (!existing['xxqs']) addon['fxrl'] = '';
|
|
|
this.submitting.set(true);
|
|
|
- this.api.post(action, { content: JSON.stringify(content), addon: JSON.stringify(addon) }).pipe(finalize(() => this.submitting.set(false))).subscribe({
|
|
|
+ this.persistWordsAsLearned(this.words(), owner).pipe(
|
|
|
+ switchMap(() => this.api.post(action, { content: JSON.stringify(content), addon: JSON.stringify(addon) })),
|
|
|
+ finalize(() => this.submitting.set(false)),
|
|
|
+ ).subscribe({
|
|
|
next: () => {
|
|
|
this.platform.toast('提交成功', 'success');
|
|
|
void this.router.navigate(['/pages/member/myorder'], { queryParams: { pnid: this.query('pnid') } });
|
|
|
@@ -1386,12 +1416,15 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
xxqs: JSON.stringify(xxqs),
|
|
|
};
|
|
|
this.submitting.set(true);
|
|
|
- this.api.post('stu_record_update_v2', {
|
|
|
- orderId: this.query('orderId'),
|
|
|
- uid: owner?.userId,
|
|
|
- inputer: user?.honeyName,
|
|
|
- addon: JSON.stringify(addon),
|
|
|
- }).pipe(finalize(() => this.submitting.set(false))).subscribe({
|
|
|
+ this.persistWordsAsLearned(allWords, owner).pipe(
|
|
|
+ switchMap(() => this.api.post('stu_record_update_v2', {
|
|
|
+ orderId: this.query('orderId'),
|
|
|
+ uid: owner?.userId,
|
|
|
+ inputer: user?.honeyName,
|
|
|
+ addon: JSON.stringify(addon),
|
|
|
+ })),
|
|
|
+ finalize(() => this.submitting.set(false)),
|
|
|
+ ).subscribe({
|
|
|
next: () => this.navigateStage('/pages/stu/pl_end'),
|
|
|
error: (error: ApiError) => this.platform.toast(error.message, 'error'),
|
|
|
});
|
|
|
@@ -1676,6 +1709,7 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
this.selected.set(new Set());
|
|
|
this.selectionRevealed.set(new Set());
|
|
|
this.selectionAssessment.set({});
|
|
|
+ this.activeSelectionBucket.set('pending');
|
|
|
this.currentIndex.set(0);
|
|
|
this.revealed.set(false);
|
|
|
this.completed.set(false);
|
|
|
@@ -1764,6 +1798,26 @@ export class LearningPageComponent implements OnInit, OnDestroy {
|
|
|
this.selectionAssessment.update((current) => ({ ...current, [word.id]: assessment }));
|
|
|
}
|
|
|
|
|
|
+ private clearSelectionAssessment(word: Word): void {
|
|
|
+ this.selectionAssessment.update((current) => Object.fromEntries(Object.entries(current).filter(([id]) => id !== word.id)) as Record<string, 'known' | 'unknown'>);
|
|
|
+ }
|
|
|
+
|
|
|
+ private persistWordsAsLearned(words: Word[], owner: AppUser | null | undefined): Observable<unknown> {
|
|
|
+ if (!words.length) return of(null);
|
|
|
+ return this.api.post('e_add_words', {
|
|
|
+ uid: owner?.userId,
|
|
|
+ wordsId: words.map((word) => word.wordId).join(','),
|
|
|
+ kcid: this.query('pnid') || this.query('kcid') || this.orderInfo()['kcid'],
|
|
|
+ save: 1,
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private commitKnownWords(words: Word[]): void {
|
|
|
+ const ids = new Set(words.map((word) => word.id));
|
|
|
+ this.words.update((current) => current.map((word) => ids.has(word.id) ? { ...word, w_learned: Math.max(1, Number(word['w_learned'] ?? 0)) } : word));
|
|
|
+ this.selectionAssessment.update((current) => Object.fromEntries(Object.entries(current).filter(([id]) => !ids.has(id))) as Record<string, 'known' | 'unknown'>);
|
|
|
+ }
|
|
|
+
|
|
|
private findList(value: unknown): SourceItem[] {
|
|
|
if (Array.isArray(value)) return value as SourceItem[];
|
|
|
if (value && typeof value === 'object') {
|