|
@@ -1,11 +1,12 @@
|
|
|
import { DropdownDirective } from '../../shared/dropdown.directive';
|
|
import { DropdownDirective } from '../../shared/dropdown.directive';
|
|
|
|
|
+import { allFeeRows, reviewFeeTotal } from '../../core/fee-summary';
|
|
|
import { ReviewPage } from '../../core/review-page';
|
|
import { ReviewPage } from '../../core/review-page';
|
|
|
import { CommonModule } from '@angular/common';
|
|
import { CommonModule } from '@angular/common';
|
|
|
import { ChangeDetectionStrategy, Component, computed, inject, OnInit, OnDestroy, signal } from '@angular/core';
|
|
import { ChangeDetectionStrategy, Component, computed, inject, OnInit, OnDestroy, signal } from '@angular/core';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
import { FormsModule } from '@angular/forms';
|
|
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
|
|
import { AlertCircle, ArrowRight, BookOpen, CalendarDays, CheckCircle2, ChevronLeft, ChevronRight, Clock3, LoaderCircle, LucideAngularModule, RotateCcw, Sparkles } from 'lucide-angular';
|
|
import { AlertCircle, ArrowRight, BookOpen, CalendarDays, CheckCircle2, ChevronLeft, ChevronRight, Clock3, LoaderCircle, LucideAngularModule, RotateCcw, Sparkles } from 'lucide-angular';
|
|
|
-import { catchError, finalize, forkJoin, of, Subscription } from 'rxjs';
|
|
|
|
|
|
|
+import { catchError, finalize, forkJoin, map, of, Subscription, switchMap } from 'rxjs';
|
|
|
import { ApiError, ApiService } from '../../core/api.service';
|
|
import { ApiError, ApiService } from '../../core/api.service';
|
|
|
import { extractReadingWords } from '../../core/external-reading';
|
|
import { extractReadingWords } from '../../core/external-reading';
|
|
|
import { canStartTeacherLedLearning } from '../../core/learning-access';
|
|
import { canStartTeacherLedLearning } from '../../core/learning-access';
|
|
@@ -64,6 +65,15 @@ export class StudyRecordsPageComponent implements OnInit, OnDestroy {
|
|
|
readonly liveSyncStatus = signal<LiveSyncStatus>({ state: 'current' });
|
|
readonly liveSyncStatus = signal<LiveSyncStatus>({ state: 'current' });
|
|
|
readonly error = signal('');
|
|
readonly error = signal('');
|
|
|
incomeMonth = '';
|
|
incomeMonth = '';
|
|
|
|
|
+ readonly incomeDates = signal<ReviewPage['dates']>([]);
|
|
|
|
|
+ readonly incomeSummaryError = signal('');
|
|
|
|
|
+ readonly incomeMonthOptions = computed(() => {
|
|
|
|
|
+ const months = new Set(this.incomeDates().map(row => row.date.slice(0, 7)).filter(month => /^\d{4}-\d{2}$/.test(month)));
|
|
|
|
|
+ months.add(new Intl.DateTimeFormat('sv-SE', {timeZone:'Asia/Shanghai'}).format(new Date()).slice(0,7));
|
|
|
|
|
+ return [...months].sort().reverse();
|
|
|
|
|
+ });
|
|
|
|
|
+ setIncomeMonth(value: string): void { this.incomeMonth = value; this.recordPage.set(1); this.load(); }
|
|
|
|
|
+ incomeMonthLabel(): string { return this.incomeMonth ? this.incomeMonth.replace('-', '年') + '月' : '全部月份'; }
|
|
|
readonly totalIncome = signal<number|null>(null);
|
|
readonly totalIncome = signal<number|null>(null);
|
|
|
readonly activeTab = signal(0);
|
|
readonly activeTab = signal(0);
|
|
|
readonly datePickerOpen = signal(false);
|
|
readonly datePickerOpen = signal(false);
|
|
@@ -616,8 +626,30 @@ export class StudyRecordsPageComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private loadIncome(uid: number): void {
|
|
private loadIncome(uid: number): void {
|
|
|
- this.historyRequest?.unsubscribe();const requestId=++this.historyRequestId;
|
|
|
|
|
- this.historyRequest=this.api.post<ReviewPage>('app_review_page',{uid,month:this.incomeMonth || undefined,page:this.recordPage(),pageSize:20,status:this.meta.path==='pages/member/ke_fuxi'?'completed':undefined}).pipe(finalize(()=>{if(requestId===this.historyRequestId)this.loading.set(false);})).subscribe({next:({result})=>{if(requestId!==this.historyRequestId)return;this.items.set(result.items);this.serverTotal.set(result.page.itemCount);this.totalIncome.set(result.summary.income);},error:(e:ApiError)=>this.error.set(e.message)});
|
|
|
|
|
|
|
+ this.historyRequest?.unsubscribe();
|
|
|
|
|
+ const requestId = ++this.historyRequestId;
|
|
|
|
|
+ const params = {uid, month: this.incomeMonth || undefined, page: this.recordPage(), pageSize: 20, status: this.meta.path === 'pages/member/ke_fuxi' ? 'completed' : undefined};
|
|
|
|
|
+ const isIncome = this.meta.path === 'pages/member/ke_fuxi';
|
|
|
|
|
+ this.incomeSummaryError.set('');
|
|
|
|
|
+ this.totalIncome.set(null);
|
|
|
|
|
+ const fetchPage = (page: number, pageSize = 20, includeMetadata = true) => this.api.post<ReviewPage>('app_review_page', {...params, page, pageSize, includeMetadata, includeIncome: includeMetadata}).pipe(map(({result}) => result));
|
|
|
|
|
+ this.historyRequest = fetchPage(params.page).pipe(switchMap(result => {
|
|
|
|
|
+ if (!isIncome || result.summary.incomeScope === (params.month || 'all')) return of({result, income: result.summary.income});
|
|
|
|
|
+ const toPage = (data: ReviewPage, index: number) => ({items: data.items, total: data.page.itemCount, pages: data.page.pageCount || 1, index});
|
|
|
|
|
+ const total = result.page.itemCount;
|
|
|
|
|
+ const first = total > result.items.length ? {items: [], total, pages: Math.ceil(total / 100), index: 0} : toPage(result, params.page);
|
|
|
|
|
+ return allFeeRows(first, page => fetchPage(page, 100, false).pipe(map(data => toPage(data, page)))).pipe(
|
|
|
|
|
+ map(rows => ({result, income: reviewFeeTotal(rows)})),
|
|
|
|
|
+ catchError(() => { this.incomeSummaryError.set('费用合计暂不可用,请刷新重试'); return of({result, income: null}); }),
|
|
|
|
|
+ );
|
|
|
|
|
+ }), finalize(() => { if (requestId === this.historyRequestId) this.loading.set(false); })).subscribe({
|
|
|
|
|
+ next: ({result, income}) => {
|
|
|
|
|
+ if (requestId !== this.historyRequestId) return;
|
|
|
|
|
+ this.items.set(result.items); this.serverTotal.set(result.page.itemCount); this.totalIncome.set(income);
|
|
|
|
|
+ if (result.dates?.length) this.incomeDates.set(result.dates);
|
|
|
|
|
+ },
|
|
|
|
|
+ error: (e: ApiError) => this.error.set(e.message),
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private asList(value: unknown): Item[] {
|
|
private asList(value: unknown): Item[] {
|