Просмотр исходного кода

fix: 简化学习记录日期筛选

彭峰 2 месяцев назад
Родитель
Сommit
43b0bba845

+ 3 - 23
src/app/features/study-records/study-records-page.component.html

@@ -1,6 +1,6 @@
 <section class="records-page">
 <section class="records-page">
-  @if (meta.path === 'pages/his/his' && session.groupId() === 3) {
-    <form class="filter-row" (change)="onFilterChange()"><label><span>学员</span><select name="student" [(ngModel)]="studentId"><option [ngValue]="0">选择学员</option>@for (student of students(); track $index) { <option [ngValue]="student['szyh']">{{ student['honeyname'] || student['HoneyName'] }}</option> }</select></label><label><span>日期</span><input name="date" type="date" [(ngModel)]="selectedDate" /></label></form>
+  @if (meta.path === 'pages/his/his') {
+    <form class="filter-row" (change)="onFilterChange()">@if (session.groupId() === 3) { <label><span>学员</span><select name="student" [(ngModel)]="studentId"><option [ngValue]="0">选择学员</option>@for (student of students(); track $index) { <option [ngValue]="student['szyh']">{{ student['honeyname'] || student['HoneyName'] }}</option> }</select></label> }<label><span>日期</span><input name="date" type="date" [(ngModel)]="selectedDate" /></label></form>
   }
   }
   @if (meta.path === 'pages/his/his') {
   @if (meta.path === 'pages/his/his') {
     <section class="ai-reading-feature" aria-labelledby="ai-reading-title">
     <section class="ai-reading-feature" aria-labelledby="ai-reading-title">
@@ -24,26 +24,6 @@
         <li><b>4</b><span>AI 批改</span></li>
         <li><b>4</b><span>AI 批改</span></li>
       </ol>
       </ol>
     </section>
     </section>
-    <section class="learning-calendar" aria-labelledby="learning-calendar-title">
-      <header>
-        <div><span id="learning-calendar-title">学习日历</span><strong>{{ calendarLabel() }}</strong></div>
-        <nav aria-label="切换月份">
-          <button type="button" aria-label="上个月" title="上个月" (click)="shiftCalendarMonth(-1)"><lucide-icon [img]="icons.ChevronLeft" [size]="18" /></button>
-          <button type="button" class="current-month" (click)="showCurrentMonth()">本月</button>
-          <button type="button" aria-label="下个月" title="下个月" (click)="shiftCalendarMonth(1)"><lucide-icon [img]="icons.ChevronRight" [size]="18" /></button>
-        </nav>
-      </header>
-      <div class="calendar-weekdays" aria-hidden="true"><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span><span>日</span></div>
-      <div class="calendar-grid" role="grid" [attr.aria-label]="calendarLabel() + '学习情况'">
-        @for (day of calendarDays(); track day.date) {
-          <button type="button" class="calendar-day" role="gridcell" [class.outside]="!day.inMonth" [class.learned]="day.count > 0" [class.today]="day.isToday" [class.selected]="calendarSelectedDate() === day.date" [attr.aria-selected]="calendarSelectedDate() === day.date" [attr.aria-label]="day.date + (day.count ? ',学习' + day.count + '次' : ',未学习')" (click)="selectCalendarDay(day)">
-            <span>{{ day.day }}</span>
-            @if (day.count) { <small>{{ day.count > 1 ? day.count + '次' : '已学' }}</small><i aria-hidden="true"></i> }
-          </button>
-        }
-      </div>
-      <footer><span><i class="learned"></i>已学习</span><span><i></i>未学习</span><strong>本月学习 {{ calendarSummary().learnedDays }} 天,共 {{ calendarSummary().records }} 次</strong></footer>
-    </section>
     @if (courses().length) {
     @if (courses().length) {
       <section class="current-learning-card" aria-labelledby="current-learning-title">
       <section class="current-learning-card" aria-labelledby="current-learning-title">
         <header><div><span id="current-learning-title">正在学习</span><strong>继续选词学习</strong></div><span>{{ courses().length }} 项内容</span></header>
         <header><div><span id="current-learning-title">正在学习</span><strong>继续选词学习</strong></div><span>{{ courses().length }} 项内容</span></header>
@@ -65,7 +45,7 @@
     <section class="learning-record-card" aria-labelledby="learning-record-title">
     <section class="learning-record-card" aria-labelledby="learning-record-title">
       <header>
       <header>
         <div><span id="learning-record-title">学习记录</span><strong>{{ selectedCalendarLabel() }}</strong></div>
         <div><span id="learning-record-title">学习记录</span><strong>{{ selectedCalendarLabel() }}</strong></div>
-        <div class="record-actions"><span>{{ visibleItems().length }} 条</span>@if (calendarSelectedDate()) { <button type="button" (click)="showAllRecords()">显示全部</button> }</div>
+        <div class="record-actions"><span>{{ visibleItems().length }} 条</span>@if (selectedDate) { <button type="button" (click)="showAllRecords()">显示全部</button> }</div>
       </header>
       </header>
       @if (visibleItems().length) {
       @if (visibleItems().length) {
         <div class="record-list">
         <div class="record-list">

+ 2 - 26
src/app/features/study-records/study-records-page.component.scss

@@ -1,31 +1,8 @@
 :host { display: block; min-height: 100%; background: var(--surface-muted); }
 :host { display: block; min-height: 100%; background: var(--surface-muted); }
 .records-page { min-height: 100%; }
 .records-page { min-height: 100%; }
-.learning-calendar { margin: 12px 14px 0; padding: 16px 14px 13px; background: white; border: 1px solid var(--line); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); }
-.learning-calendar > header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; gap: 12px; }
-.learning-calendar > header > div { display: flex; flex-direction: column; gap: 3px; }
-.learning-calendar > header span { color: var(--ink-soft); font-size: 11px; }
-.learning-calendar > header strong { font-size: 18px; }
-.learning-calendar nav { display: grid; grid-template-columns: 38px auto 38px; align-items: center; gap: 3px; }
-.learning-calendar nav button { display: grid; min-width: 38px; height: 38px; padding: 0; color: var(--ink-soft); background: transparent; border: 0; border-radius: var(--radius-sm); place-items: center; }
-.learning-calendar nav .current-month { padding-inline: 8px; color: var(--brand-strong); font-size: 12px; font-weight: 650; }
-.calendar-weekdays, .calendar-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; }
-.calendar-weekdays { margin-bottom: 5px; }
-.calendar-weekdays span { color: var(--ink-faint); font-size: 10px; text-align: center; }
-.calendar-day { display: flex; width: 100%; min-width: 0; height: 44px; padding: 0; align-items: center; justify-content: center; position: relative; color: var(--ink-soft); background: var(--surface-muted); border: 1px solid transparent; border-radius: var(--radius-sm); font: inherit; cursor: pointer; flex-direction: column; gap: 1px; }
-.calendar-day > span { color: inherit; font-size: 12px; font-weight: 600; }
-.calendar-day small { color: var(--brand-deep); font-size: 8px; line-height: 1; }
-.calendar-day > i { position: absolute; right: 4px; bottom: 4px; width: 5px; height: 5px; background: var(--brand); border-radius: 50%; }
-.calendar-day.learned { color: var(--brand-deep); background: var(--brand-pale); border-color: color-mix(in srgb, var(--brand) 35%, var(--line)); }
-.calendar-day.today { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
-.calendar-day.selected { border-color: var(--brand-strong); box-shadow: inset 0 0 0 1px var(--brand-strong); }
-.calendar-day.outside { opacity: .32; }
-.learning-calendar > footer { display: flex; flex-wrap: wrap; align-items: center; margin-top: 12px; color: var(--ink-soft); font-size: 10px; gap: 10px; }
-.learning-calendar > footer span { display: flex; align-items: center; gap: 4px; }
-.learning-calendar > footer i { width: 8px; height: 8px; background: var(--surface-muted); border: 1px solid var(--line); border-radius: 2px; }
-.learning-calendar > footer i.learned { background: var(--brand-pale); border-color: var(--brand); }
-.learning-calendar > footer strong { margin-left: auto; color: var(--brand-strong); font-size: 10px; }
 .filter-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); padding: 12px 16px; background: white; border-bottom: 1px solid var(--line); gap: 10px; }
 .filter-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); padding: 12px 16px; background: white; border-bottom: 1px solid var(--line); gap: 10px; }
 .filter-row label { display: grid; gap: 5px; }
 .filter-row label { display: grid; gap: 5px; }
+.filter-row label:only-child { grid-column: 1 / -1; }
 .filter-row span { color: var(--ink-soft); font-size: 11px; }
 .filter-row span { color: var(--ink-soft); font-size: 11px; }
 .filter-row :is(select, input) { width: 100%; min-height: 40px; padding: 7px 9px; background: white; border: 1px solid var(--line); border-radius: var(--radius-sm); }
 .filter-row :is(select, input) { width: 100%; min-height: 40px; padding: 7px 9px; background: white; border: 1px solid var(--line); border-radius: var(--radius-sm); }
 .record-tabs { display: grid; grid-template-columns: 1fr 1fr; background: white; border-bottom: 1px solid var(--line); }
 .record-tabs { display: grid; grid-template-columns: 1fr 1fr; background: white; border-bottom: 1px solid var(--line); }
@@ -78,6 +55,5 @@
 .chevron { color: var(--ink-soft); }
 .chevron { color: var(--ink-soft); }
 .spin { animation: spin .8s linear infinite; }
 .spin { animation: spin .8s linear infinite; }
 @keyframes spin { to { transform: rotate(360deg); } }
 @keyframes spin { to { transform: rotate(360deg); } }
-@media (hover: hover) { .learning-calendar nav button:hover, .calendar-day:hover { background: var(--surface-hover); } .calendar-day.learned:hover { background: color-mix(in srgb, var(--brand-pale) 78%, var(--brand)); } }
-@media (max-width: 360px) { .learning-calendar, .learning-record-card { margin-inline: 10px; } .learning-calendar { padding-inline: 10px; } .calendar-day { height: 40px; } .learning-calendar > footer strong { width: 100%; margin-left: 0; } }
+@media (max-width: 360px) { .learning-record-card { margin-inline: 10px; } }
 @media (min-width: 760px) { .records-page { max-width: 760px; margin: 0 auto; border-inline: 1px solid var(--line); } }
 @media (min-width: 760px) { .records-page { max-width: 760px; margin: 0 auto; border-inline: 1px solid var(--line); } }

+ 11 - 112
src/app/features/study-records/study-records-page.component.ts

@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common';
 import { ChangeDetectionStrategy, Component, computed, inject, OnInit, signal } from '@angular/core';
 import { ChangeDetectionStrategy, Component, computed, inject, OnInit, 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, ChevronRight, Clock3, LoaderCircle, LucideAngularModule, RotateCcw, Sparkles } from 'lucide-angular';
 import { catchError, finalize, forkJoin, of } from 'rxjs';
 import { catchError, finalize, forkJoin, of } from 'rxjs';
 import { ApiError, ApiService } from '../../core/api.service';
 import { ApiError, ApiService } from '../../core/api.service';
 import { extractReadingWords, readNcloudSessionToken } from '../../core/external-reading';
 import { extractReadingWords, readNcloudSessionToken } from '../../core/external-reading';
@@ -11,14 +11,6 @@ import { SessionService } from '../../core/session.service';
 
 
 type Item = Record<string, unknown>;
 type Item = Record<string, unknown>;
 
 
-interface CalendarDay {
-  date: string;
-  day: number;
-  inMonth: boolean;
-  isToday: boolean;
-  count: number;
-}
-
 @Component({
 @Component({
   selector: 'app-study-records-page',
   selector: 'app-study-records-page',
   standalone: true,
   standalone: true,
@@ -36,50 +28,20 @@ export class StudyRecordsPageComponent implements OnInit {
   readonly error = signal('');
   readonly error = signal('');
   readonly totalIncome = signal(0);
   readonly totalIncome = signal(0);
   readonly activeTab = signal(0);
   readonly activeTab = signal(0);
-  readonly calendarMonth = signal(this.firstOfMonth(new Date()));
-  readonly calendarSelectedDate = signal('');
-  readonly visibleItems = computed(() => {
-    const selectedDate = this.calendarSelectedDate();
-    if (this.meta.path !== 'pages/his/his' || !selectedDate) return this.items();
-    return this.items().filter((item) => this.itemDate(item) === selectedDate);
-  });
+  readonly visibleItems = computed(() => this.items());
   readonly latestReadingRecord = computed(() => this.items().find((item) => extractReadingWords(item['xxqs']).length > 0));
   readonly latestReadingRecord = computed(() => this.items().find((item) => extractReadingWords(item['xxqs']).length > 0));
   readonly latestReadingWordCount = computed(() => {
   readonly latestReadingWordCount = computed(() => {
     const item = this.latestReadingRecord();
     const item = this.latestReadingRecord();
     return item ? extractReadingWords(item['xxqs']).length : 0;
     return item ? extractReadingWords(item['xxqs']).length : 0;
   });
   });
-  readonly selectedCalendarLabel = computed(() => {
-    const selectedDate = this.calendarSelectedDate();
+  readonly selectedCalendarLabel = (): string => {
+    const selectedDate = this.selectedDate;
     if (!selectedDate) return '全部记录';
     if (!selectedDate) return '全部记录';
     const [year, month, day] = selectedDate.split('-').map(Number);
     const [year, month, day] = selectedDate.split('-').map(Number);
     return new Intl.DateTimeFormat('zh-CN', { month: 'long', day: 'numeric', weekday: 'short' })
     return new Intl.DateTimeFormat('zh-CN', { month: 'long', day: 'numeric', weekday: 'short' })
       .format(new Date(year, month - 1, day, 12));
       .format(new Date(year, month - 1, day, 12));
-  });
-  readonly calendarLabel = computed(() => new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: 'long' }).format(this.calendarMonth()));
-  readonly calendarDays = computed<CalendarDay[]>(() => {
-    const month = this.calendarMonth();
-    const year = month.getFullYear();
-    const monthIndex = month.getMonth();
-    const counts = new Map<string, number>();
-    for (const item of this.items()) {
-      const date = this.itemDate(item);
-      if (date) counts.set(date, (counts.get(date) ?? 0) + 1);
-    }
-    const offset = (new Date(year, monthIndex, 1, 12).getDay() + 6) % 7;
-    const start = new Date(year, monthIndex, 1 - offset, 12);
-    const today = this.dateKey(new Date());
-    return Array.from({ length: 42 }, (_, index) => {
-      const date = new Date(start);
-      date.setDate(start.getDate() + index);
-      const key = this.dateKey(date);
-      return { date: key, day: date.getDate(), inMonth: date.getMonth() === monthIndex, isToday: key === today, count: counts.get(key) ?? 0 };
-    });
-  });
-  readonly calendarSummary = computed(() => {
-    const days = this.calendarDays().filter((day) => day.inMonth && day.count > 0);
-    return { learnedDays: days.length, records: days.reduce((sum, day) => sum + day.count, 0) };
-  });
-  readonly icons = { AlertCircle, ArrowRight, BookOpen, CalendarDays, CheckCircle2, ChevronLeft, ChevronRight, Clock3, LoaderCircle, RotateCcw, Sparkles };
+  };
+  readonly icons = { AlertCircle, ArrowRight, BookOpen, CalendarDays, CheckCircle2, ChevronRight, Clock3, LoaderCircle, RotateCcw, Sparkles };
   studentId = 0;
   studentId = 0;
   selectedDate = '';
   selectedDate = '';
 
 
@@ -87,7 +49,6 @@ export class StudyRecordsPageComponent implements OnInit {
   private readonly router = inject(Router);
   private readonly router = inject(Router);
   private readonly api = inject(ApiService);
   private readonly api = inject(ApiService);
   readonly session = inject(SessionService);
   readonly session = inject(SessionService);
-  private calendarInitialized = false;
   private syncingHistoryQuery = false;
   private syncingHistoryQuery = false;
 
 
   ngOnInit(): void {
   ngOnInit(): void {
@@ -118,7 +79,6 @@ export class StudyRecordsPageComponent implements OnInit {
       next: ({ result }) => {
       next: ({ result }) => {
         const list = this.asList(result);
         const list = this.asList(result);
         this.items.set(list);
         this.items.set(list);
-        if (this.meta.path === 'pages/his/his') this.initializeCalendar(list);
       },
       },
       error: (error: ApiError) => this.error.set(error.message),
       error: (error: ApiError) => this.error.set(error.message),
     });
     });
@@ -129,41 +89,14 @@ export class StudyRecordsPageComponent implements OnInit {
   onFilterChange(): void {
   onFilterChange(): void {
     this.studentId = Number(this.studentId || 0);
     this.studentId = Number(this.studentId || 0);
     this.selectedDate = this.dateKey(this.selectedDate);
     this.selectedDate = this.dateKey(this.selectedDate);
-    this.calendarInitialized = false;
-    if (this.selectedDate) {
-      this.calendarSelectedDate.set(this.selectedDate);
-      this.calendarMonth.set(this.firstOfMonth(this.parseDate(this.selectedDate)));
-      this.calendarInitialized = true;
-    }
     this.syncHistoryState();
     this.syncHistoryState();
     this.load();
     this.load();
   }
   }
 
 
-  shiftCalendarMonth(amount: number): void {
-    const month = this.calendarMonth();
-    this.calendarMonth.set(new Date(month.getFullYear(), month.getMonth() + amount, 1, 12));
-    this.calendarSelectedDate.set('');
-    this.syncHistoryState();
-  }
-
-  showCurrentMonth(): void {
-    this.calendarMonth.set(this.firstOfMonth(new Date()));
-    this.calendarSelectedDate.set('');
-    this.syncHistoryState();
-  }
-
-  selectCalendarDay(day: CalendarDay): void {
-    this.calendarSelectedDate.set(day.date);
-    if (!day.inMonth) {
-      const [year, month] = day.date.split('-').map(Number);
-      this.calendarMonth.set(new Date(year, month - 1, 1, 12));
-    }
-    this.syncHistoryState();
-  }
-
   showAllRecords(): void {
   showAllRecords(): void {
-    this.calendarSelectedDate.set('');
+    this.selectedDate = '';
     this.syncHistoryState();
     this.syncHistoryState();
+    this.load();
   }
   }
 
 
   courseId(item: Item): string {
   courseId(item: Item): string {
@@ -264,7 +197,6 @@ export class StudyRecordsPageComponent implements OnInit {
         const list = this.asList(records.result);
         const list = this.asList(records.result);
         this.items.set(list);
         this.items.set(list);
         this.courses.set(this.asList(courses.result));
         this.courses.set(this.asList(courses.result));
-        this.initializeCalendar(list);
       },
       },
       error: (error: ApiError) => this.error.set(error.message),
       error: (error: ApiError) => this.error.set(error.message),
     });
     });
@@ -303,14 +235,7 @@ export class StudyRecordsPageComponent implements OnInit {
     if (this.meta.path !== 'pages/his/his') return;
     if (this.meta.path !== 'pages/his/his') return;
     const query = this.route.snapshot.queryParamMap;
     const query = this.route.snapshot.queryParamMap;
     this.studentId = Number(query.get('uid') ?? 0);
     this.studentId = Number(query.get('uid') ?? 0);
-    this.selectedDate = this.dateKey(query.get('date') ?? '');
-    const selectedCalendarDate = this.dateKey(query.get('hisDay') ?? '') || this.selectedDate;
-    const restoredMonth = this.parseMonth(query.get('hisMonth') ?? '');
-
-    if (selectedCalendarDate) this.calendarSelectedDate.set(selectedCalendarDate);
-    if (restoredMonth) this.calendarMonth.set(restoredMonth);
-    else if (selectedCalendarDate) this.calendarMonth.set(this.firstOfMonth(this.parseDate(selectedCalendarDate)));
-    if (restoredMonth || selectedCalendarDate) this.calendarInitialized = true;
+    this.selectedDate = this.dateKey(query.get('date') ?? query.get('hisDay') ?? '');
   }
   }
 
 
   private syncHistoryState(): void {
   private syncHistoryState(): void {
@@ -323,29 +248,14 @@ export class StudyRecordsPageComponent implements OnInit {
         uid: teacherStudentId,
         uid: teacherStudentId,
         name: null,
         name: null,
         date: this.selectedDate || null,
         date: this.selectedDate || null,
-        hisDay: this.calendarSelectedDate() || null,
-        hisMonth: this.monthKey(this.calendarMonth()),
+        hisDay: null,
+        hisMonth: null,
       },
       },
       queryParamsHandling: 'merge',
       queryParamsHandling: 'merge',
       replaceUrl: true,
       replaceUrl: true,
     }).finally(() => { this.syncingHistoryQuery = false; });
     }).finally(() => { this.syncingHistoryQuery = false; });
   }
   }
 
 
-  private initializeCalendar(items: Item[]): void {
-    if (this.calendarInitialized) return;
-    this.calendarInitialized = true;
-    const currentMonth = this.monthKey(this.calendarMonth());
-    const dates = items.map((item) => this.itemDate(item)).filter(Boolean).sort();
-    if (!dates.some((date) => date.startsWith(currentMonth)) && dates.length) {
-      const [year, month] = dates.at(-1)!.split('-').map(Number);
-      this.calendarMonth.set(new Date(year, month - 1, 1, 12));
-    }
-  }
-
-  private itemDate(item: Item): string {
-    return this.dateKey(item['CreateTime'] ?? item['ctime'] ?? item['learned_date'] ?? item['PubAddTime']);
-  }
-
   private dateKey(value: unknown): string {
   private dateKey(value: unknown): string {
     const raw = String(value ?? '');
     const raw = String(value ?? '');
     const matched = raw.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/);
     const matched = raw.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/);
@@ -356,15 +266,4 @@ export class StudyRecordsPageComponent implements OnInit {
     return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
     return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
   }
   }
 
 
-  private firstOfMonth(date: Date): Date { return new Date(date.getFullYear(), date.getMonth(), 1, 12); }
-  private monthKey(date: Date): string { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; }
-  private parseDate(value: string): Date {
-    const [year, month, day] = value.split('-').map(Number);
-    return new Date(year, month - 1, day, 12);
-  }
-  private parseMonth(value: string): Date | null {
-    const matched = value.match(/^(\d{4})-(\d{1,2})$/);
-    if (!matched) return null;
-    return new Date(Number(matched[1]), Number(matched[2]) - 1, 1, 12);
-  }
 }
 }