|
@@ -3,7 +3,8 @@ import { CommonModule } from '@angular/common';
|
|
|
import { ChangeDetectionStrategy, Component, computed, inject, NgZone, OnDestroy, OnInit, signal } from '@angular/core';
|
|
import { ChangeDetectionStrategy, Component, computed, inject, NgZone, OnDestroy, OnInit, signal } from '@angular/core';
|
|
|
import { Router, RouterLink } from '@angular/router';
|
|
import { Router, RouterLink } from '@angular/router';
|
|
|
import { AlertCircle, BarChart3, BookMarked, BookOpen, CalendarDays, Check, ChevronRight, Clock3, Eye, EyeOff, GraduationCap, Info, LucideAngularModule, RefreshCw, Search, UserRound, Zap } from 'lucide-angular';
|
|
import { AlertCircle, BarChart3, BookMarked, BookOpen, CalendarDays, Check, ChevronRight, Clock3, Eye, EyeOff, GraduationCap, Info, LucideAngularModule, RefreshCw, Search, UserRound, Zap } from 'lucide-angular';
|
|
|
-import { finalize } from 'rxjs';
|
|
|
|
|
|
|
+import { finalize, map, Subscription } from 'rxjs';
|
|
|
|
|
+import { beijingToday, TeacherTodayReviewService } from '../../core/teacher-today-review.service';
|
|
|
import { antiForgetCompleted, antiForgetDate, buildAntiForgetView, findAntiForgetList } from '../../core/anti-forget';
|
|
import { antiForgetCompleted, antiForgetDate, buildAntiForgetView, findAntiForgetList } from '../../core/anti-forget';
|
|
|
import { ApiService } from '../../core/api.service';
|
|
import { ApiService } from '../../core/api.service';
|
|
|
import { PlatformService } from '../../core/platform.service';
|
|
import { PlatformService } from '../../core/platform.service';
|
|
@@ -37,7 +38,7 @@ export class HomePageComponent implements OnInit, OnDestroy {
|
|
|
readonly reviewData=signal<ReviewPage|null>(null);
|
|
readonly reviewData=signal<ReviewPage|null>(null);
|
|
|
readonly antiForgetView = computed(() => reviewOverview(this.reviewData()));
|
|
readonly antiForgetView = computed(() => reviewOverview(this.reviewData()));
|
|
|
readonly isCoach = computed(() => Number(this.session.groupId()) === 3);
|
|
readonly isCoach = computed(() => Number(this.session.groupId()) === 3);
|
|
|
- readonly coachReviewItems = computed(() => [...this.antiForgetItems()].sort((left, right) => {
|
|
|
|
|
|
|
+ readonly coachReviewItems = computed(() => this.antiForgetItems().filter(item => antiForgetDate(item) === this.today()).sort((left, right) => {
|
|
|
const stateOrder = (item: Item) => antiForgetCompleted(item) ? 2 : this.coachReviewReady(item) ? 0 : 1;
|
|
const stateOrder = (item: Item) => antiForgetCompleted(item) ? 2 : this.coachReviewReady(item) ? 0 : 1;
|
|
|
const stateDifference = stateOrder(left) - stateOrder(right);
|
|
const stateDifference = stateOrder(left) - stateOrder(right);
|
|
|
if (stateDifference) return stateDifference;
|
|
if (stateDifference) return stateDifference;
|
|
@@ -52,12 +53,20 @@ export class HomePageComponent implements OnInit, OnDestroy {
|
|
|
private readonly platform = inject(PlatformService);
|
|
private readonly platform = inject(PlatformService);
|
|
|
private readonly liveData = inject(LiveDataRefreshService);
|
|
private readonly liveData = inject(LiveDataRefreshService);
|
|
|
private readonly zone=inject(NgZone);
|
|
private readonly zone=inject(NgZone);
|
|
|
|
|
+ private readonly todayReviews = inject(TeacherTodayReviewService);
|
|
|
|
|
+ private reviewRequest?: Subscription;
|
|
|
private midnightTimer?: ReturnType<typeof setTimeout>;
|
|
private midnightTimer?: ReturnType<typeof setTimeout>;
|
|
|
- private readonly resumeToday=()=>{if(document.visibilityState==='visible')this.retryAntiForget();};
|
|
|
|
|
|
|
+ private readonly resumeToday=()=>{
|
|
|
|
|
+ if(document.visibilityState==='visible') {
|
|
|
|
|
+ const uid=this.session.user()?.userId;
|
|
|
|
|
+ if(uid && (!this.isCoach() || this.reviewLoadedDate !== beijingToday())) this.loadAntiForget(uid);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ private reviewLoadedDate = '';
|
|
|
private scheduleTodayRefresh():void{
|
|
private scheduleTodayRefresh():void{
|
|
|
if(this.midnightTimer)clearTimeout(this.midnightTimer);
|
|
if(this.midnightTimer)clearTimeout(this.midnightTimer);
|
|
|
const now=Date.now(),next=(Math.floor((now+8*3600000)/86400000)+1)*86400000-8*3600000;
|
|
const now=Date.now(),next=(Math.floor((now+8*3600000)/86400000)+1)*86400000-8*3600000;
|
|
|
- this.midnightTimer=this.zone.runOutsideAngular(()=>setTimeout(()=>this.zone.run(()=>{this.retryAntiForget();this.retryCoachStats();this.scheduleTodayRefresh();}),next-now+100));
|
|
|
|
|
|
|
+ this.midnightTimer=this.zone.runOutsideAngular(()=>setTimeout(()=>this.zone.run(()=>{const uid=this.session.user()?.userId;if(uid)this.loadAntiForget(uid);this.retryCoachStats();this.scheduleTodayRefresh();}),next-now+100));
|
|
|
}
|
|
}
|
|
|
private stopLiveRefresh?: () => void;
|
|
private stopLiveRefresh?: () => void;
|
|
|
|
|
|
|
@@ -77,7 +86,7 @@ export class HomePageComponent implements OnInit, OnDestroy {
|
|
|
if (!currentId) return;
|
|
if (!currentId) return;
|
|
|
this.loadStats(currentId, true);
|
|
this.loadStats(currentId, true);
|
|
|
if(!this.isCoach())this.loadBalances(currentId);
|
|
if(!this.isCoach())this.loadBalances(currentId);
|
|
|
- this.loadAntiForget(currentId, true);
|
|
|
|
|
|
|
+ if (!this.isCoach()) this.loadAntiForget(currentId, true);
|
|
|
},
|
|
},
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
@@ -85,6 +94,7 @@ export class HomePageComponent implements OnInit, OnDestroy {
|
|
|
|
|
|
|
|
ngOnDestroy(): void {
|
|
ngOnDestroy(): void {
|
|
|
this.stopLiveRefresh?.();
|
|
this.stopLiveRefresh?.();
|
|
|
|
|
+ this.reviewRequest?.unsubscribe();
|
|
|
if(this.midnightTimer)clearTimeout(this.midnightTimer);
|
|
if(this.midnightTimer)clearTimeout(this.midnightTimer);
|
|
|
document.removeEventListener('visibilitychange',this.resumeToday);
|
|
document.removeEventListener('visibilitychange',this.resumeToday);
|
|
|
}
|
|
}
|
|
@@ -96,7 +106,7 @@ export class HomePageComponent implements OnInit, OnDestroy {
|
|
|
|
|
|
|
|
retryAntiForget(): void {
|
|
retryAntiForget(): void {
|
|
|
const uid = this.session.user()?.userId;
|
|
const uid = this.session.user()?.userId;
|
|
|
- if (uid) this.loadAntiForget(uid);
|
|
|
|
|
|
|
+ if (uid) this.loadAntiForget(uid, false, true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
coachReviewStudent(item: Item): string {
|
|
coachReviewStudent(item: Item): string {
|
|
@@ -128,16 +138,7 @@ export class HomePageComponent implements OnInit, OnDestroy {
|
|
|
return this.coachReviewReady(item) ? '待复习' : '未到期';
|
|
return this.coachReviewReady(item) ? '待复习' : '未到期';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- coachReviewAction(item: Item): string {
|
|
|
|
|
- if (antiForgetCompleted(item)) return '查看';
|
|
|
|
|
- return this.coachReviewReady(item) ? '带学生复习' : '查看计划';
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
openCoachReview(item: Item): void {
|
|
openCoachReview(item: Item): void {
|
|
|
- if (!this.coachReviewReady(item) && !antiForgetCompleted(item)) {
|
|
|
|
|
- void this.router.navigateByUrl('/pages/stu/stu_calendar');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
const studentId = Number(item['yhid'] ?? 0);
|
|
const studentId = Number(item['yhid'] ?? 0);
|
|
|
if (!studentId) {
|
|
if (!studentId) {
|
|
|
this.platform.toast('复习任务缺少关联学员', 'error');
|
|
this.platform.toast('复习任务缺少关联学员', 'error');
|
|
@@ -168,15 +169,23 @@ export class HomePageComponent implements OnInit, OnDestroy {
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private loadAntiForget(uid: string | number, background = false): void {
|
|
|
|
|
|
|
+ private loadAntiForget(uid: string | number, background = false, force = false): void {
|
|
|
|
|
+ this.reviewRequest?.unsubscribe();
|
|
|
if (!background) {
|
|
if (!background) {
|
|
|
this.antiForgetLoading.set(true);
|
|
this.antiForgetLoading.set(true);
|
|
|
this.antiForgetError.set('');
|
|
this.antiForgetError.set('');
|
|
|
}
|
|
}
|
|
|
- this.api.post<ReviewPage>('app_review_page', { uid, page:1,pageSize:this.isCoach()?4:20,...(this.isCoach()?{scope:'today',includeMetadata:false,includeIncome:false}:{status:'pending'}) }).pipe(
|
|
|
|
|
|
|
+ const request = this.isCoach() ? this.todayReviews.load(force) :
|
|
|
|
|
+ this.api.post<ReviewPage>('app_review_page', { uid, page: 1, pageSize: 20, status: 'pending' }).pipe(map(({ result }) => result));
|
|
|
|
|
+ this.reviewRequest = request.pipe(
|
|
|
finalize(() => { if (!background) this.antiForgetLoading.set(false); }),
|
|
finalize(() => { if (!background) this.antiForgetLoading.set(false); }),
|
|
|
).subscribe({
|
|
).subscribe({
|
|
|
- next: ({ result }) => {this.reviewData.set(result);this.antiForgetItems.set(result.items);this.antiForgetError.set('');},
|
|
|
|
|
|
|
+ next: (result) => {
|
|
|
|
|
+ this.reviewData.set(result);
|
|
|
|
|
+ this.antiForgetItems.set(result.items);
|
|
|
|
|
+ this.reviewLoadedDate = beijingToday();
|
|
|
|
|
+ this.antiForgetError.set('');
|
|
|
|
|
+ },
|
|
|
error: () => { this.antiForgetError.set('抗遗忘计划暂时无法加载'); },
|
|
error: () => { this.antiForgetError.set('抗遗忘计划暂时无法加载'); },
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|