account-page.component.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import { CommonModule } from '@angular/common';
  2. import { ChangeDetectionStrategy, Component, computed, inject, OnInit, signal } from '@angular/core';
  3. import { Router, RouterLink } from '@angular/router';
  4. import { Award, BookMarked, ChevronRight, CircleHelp, History, LogOut, LucideAngularModule, MessageSquare, Settings, ShieldCheck, UserRound } from 'lucide-angular';
  5. import { finalize } from 'rxjs';
  6. import { APP_VERSION } from '../../core/app.constants';
  7. import { ApiService } from '../../core/api.service';
  8. import { AntiForgetHistorySummary } from '../../core/anti-forget';
  9. import { AppUser } from '../../core/models';
  10. import { SessionService } from '../../core/session.service';
  11. import { buildAccountMembership, CompanionLearningSummary } from './account-membership';
  12. interface CoachPanelSummary {
  13. todaySchedules: number;
  14. completedLessons: number;
  15. reviewIncome: number;
  16. reviewRecords: number;
  17. totalLessons: number;
  18. totalHours: number;
  19. course30: number;
  20. course60: number;
  21. trialLessons: number;
  22. }
  23. interface AccountOverview {
  24. profile: Partial<AppUser>;
  25. coachPanel?: CoachPanelSummary;
  26. companionSummary?: Pick<CompanionLearningSummary, 'completedSessions' | 'completedMinutes'>;
  27. antiForgetSummary?: AntiForgetHistorySummary;
  28. refreshedAt: string;
  29. }
  30. @Component({
  31. selector: 'app-account-page',
  32. standalone: true,
  33. imports: [CommonModule, RouterLink, LucideAngularModule],
  34. templateUrl: './account-page.component.html',
  35. styleUrl: './account-page.component.scss',
  36. changeDetection: ChangeDetectionStrategy.OnPush,
  37. })
  38. export class AccountPageComponent implements OnInit {
  39. readonly session = inject(SessionService);
  40. readonly version = APP_VERSION;
  41. readonly coachPanel = signal<CoachPanelSummary>({ todaySchedules: 0, completedLessons: 0, reviewIncome: 0, reviewRecords: 0, totalLessons: 0, totalHours: 0, course30: 0, course60: 0, trialLessons: 0 });
  42. readonly coachPanelLoading = signal(false);
  43. readonly antiForgetLoading = signal(false);
  44. readonly antiForgetError = signal('');
  45. readonly avatarFailed = signal(false);
  46. readonly companionLoading = signal(false);
  47. readonly antiForgetSummary = signal<AntiForgetHistorySummary>({ total: 0, completed: 0, progress: 0, todayTotal: 0, todayCompleted: 0, duePending: 0, scheduled: 0, scheduledDays: 0 });
  48. readonly membership = computed(() => buildAccountMembership(this.session.user()));
  49. readonly companionSummary = signal<CompanionLearningSummary>({ completedSessions: 0, completedMinutes: 0, durationLabel: '0 小时' });
  50. readonly icons = { Award, BookMarked, ChevronRight, CircleHelp, History, LogOut, MessageSquare, Settings, ShieldCheck, UserRound };
  51. readonly services = [
  52. { path: '/pages/his/well', label: '专项训练', icon: Award },
  53. { path: '/pages/member/starshop', label: '生词本', icon: BookMarked },
  54. { path: '/pages/his/his', label: '学习记录', icon: History },
  55. { path: '/pages/member/feedback', label: '意见反馈', icon: MessageSquare },
  56. ];
  57. private readonly api = inject(ApiService);
  58. private readonly router = inject(Router);
  59. ngOnInit(): void {
  60. const uid = this.session.user()?.userId;
  61. if (!uid) return;
  62. this.loadAccountOverview(false);
  63. }
  64. loadAntiForget(): void { this.loadAccountOverview(true); }
  65. private loadAccountOverview(refresh: boolean): void {
  66. const uid = this.session.user()?.userId;
  67. if (!uid) return;
  68. const coach = Number(this.session.groupId()) === 3;
  69. this.coachPanelLoading.set(coach);
  70. this.companionLoading.set(!coach);
  71. this.antiForgetLoading.set(!coach);
  72. this.antiForgetError.set('');
  73. this.api.post<AccountOverview>('app_account_overview', { uid, refresh }).pipe(
  74. finalize(() => {
  75. this.coachPanelLoading.set(false);
  76. this.companionLoading.set(false);
  77. this.antiForgetLoading.set(false);
  78. }),
  79. ).subscribe({
  80. next: ({ result }) => {
  81. if (!result) return;
  82. this.session.patchUser(result.profile ?? {});
  83. if (result.coachPanel) this.coachPanel.set(result.coachPanel);
  84. if (result.companionSummary) {
  85. const minutes = Number(result.companionSummary.completedMinutes ?? 0);
  86. this.companionSummary.set({ ...result.companionSummary, durationLabel: this.formatMinutes(minutes) });
  87. }
  88. if (result.antiForgetSummary) this.antiForgetSummary.set(result.antiForgetSummary);
  89. },
  90. error: () => this.antiForgetError.set('抗遗忘数据暂时无法加载'),
  91. });
  92. }
  93. logout(): void { this.session.logout(); void this.router.navigateByUrl('/pages/member/wxauth'); }
  94. avatarUrl(): string { return this.api.assetUrl(this.session.user()?.userFace); }
  95. private formatMinutes(minutes: number): string {
  96. if (!minutes) return '0 小时';
  97. const hours = Math.floor(minutes / 60);
  98. const remainder = minutes % 60;
  99. if (!hours) return `${remainder} 分钟`;
  100. return remainder ? `${hours} 小时 ${remainder} 分钟` : `${hours} 小时`;
  101. }
  102. }