| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- <section class="center-page">
- @if (loading() || syncNoticeVisible()) {
- <div class="center-sync" [class.delayed]="liveSyncStatus().state !== 'current'" role="status"><lucide-icon [class.spin]="loading() || liveChecking()" [img]="icons.LoaderCircle" [size]="18" /><span>{{ liveStatusText() }}</span></div>
- }
- @if (!isCoach) {
- <section class="course-band">
- <header><lucide-icon [img]="icons.BookOpen" [size]="21" /><h2>我的学习内容</h2><span>{{ (loading()||error())&&!courses().length ? '—' : courses().length }} 项</span></header>
- @if (courses().length) {
- <ul class="course-list" id="learning-course-list">@for (item of visibleCourses(); track courseBindingId(item) || courseId(item) || $index) { <li><button type="button" [class.readonly]="!canStartPointReading" [disabled]="!canOpenCourse(item)" (click)="openCourse(item)"><span><strong>{{ courseCategory(item) ? courseCategory(item)+' · ' : '' }}{{ courseTitle(item) }}</strong><small>{{ courseHint(item) }}</small></span><lucide-icon [img]="icons.ChevronRight" [size]="18" /></button></li> }</ul>
- @if (courses().length > 3) {
- <button class="course-toggle" type="button" (click)="toggleCourses()" [attr.aria-expanded]="coursesExpanded()" aria-controls="learning-course-list"><span>{{ coursesExpanded() ? '收起' : '展开其余 ' + (courses().length - 3) + ' 项' }}</span><lucide-icon [img]="icons.ChevronDown" [size]="18" [class.expanded]="coursesExpanded()" /></button>
- }
- } @else { <p class="band-empty">{{ error() ? '学习内容暂时无法加载,请重试' : loading() ? '正在读取学习内容…' : '暂未分配学习内容,请联系老师' }}</p> }
- </section>
- }
- <section class="schedule-band">
- <header><lucide-icon [img]="isCoach ? icons.GraduationCap : icons.CalendarDays" [size]="21" /><h2>{{ isHistory ? '陪练记录' : isCoach ? '陪练排课' : '伴学列表' }}</h2><span>{{ (loading()||error())&&!schedules().length ? '—' : isHistory || hasScheduleCalendar ? visibleSchedules().length + ' / ' + schedules().length : schedules().length }} 节</span></header>
- <nav class="range-tabs schedule-actions" aria-label="课表操作"><button type="button" (click)="load(true)" [disabled]="loading()">刷新课表</button><button type="button" (click)="clearScheduleFilters()">清除筛选</button>@if (nextScheduleDate() && !isHistory) { <button type="button" (click)="showNextSchedule()">最近一节 · {{nextScheduleDate()}}</button> }</nav>
- @if (isHistory) {
- <div class="history-filter-panel" aria-live="polite"><strong>{{historyMonth()==='all'?'全部月份':historyMonth()}} · 陪练费用合计:{{loading() || error() || !earningsSummary() ? '—' : '¥' + (earningsSummary()!.amount | number:'1.2-2')}}</strong><p>按当前筛选全部 {{listTotal()}} 条记录汇总,包含所有分页;已取消记录不计费用。</p>@if(earningsSummary()?.pendingCount){<small>另有 {{earningsSummary()!.pendingCount}} 条费用待核对,未计入合计。</small>}</div>
- <section class="history-filter-panel" aria-label="陪练记录筛选">
- <div class="history-filter-grid">
- <label>
- <span>月份</span>
- <div class="history-filter-control month-control">
- <lucide-icon [img]="icons.CalendarDays" [size]="18" />
- <select [ngModel]="historyMonth()" (ngModelChange)="setHistoryMonth($event)" aria-label="按月份筛选陪练记录">
- <option value="all">全部月份</option>
- @for (month of historyMonthOptions(); track month.value) { <option [value]="month.value">{{ month.label }}({{ month.count }})</option> }
- </select>
- </div>
- </label>
- <label>
- <span>学生姓名</span>
- <div class="history-filter-control name-control">
- <lucide-icon [img]="icons.Search" [size]="18" />
- <input type="search" [ngModel]="historyStudentQuery()" (ngModelChange)="setHistoryStudentQuery($event)" placeholder="输入姓名查找" autocomplete="off" aria-label="按学生姓名筛选陪练记录" />
- </div>
- </label>
- </div>
- <footer>
- <span>当前显示 <strong>{{ visibleSchedules().length }}</strong> 节,共 {{ listTotal() }} 节</span>
- @if (historyFilterActive()) { <button type="button" (click)="clearHistoryFilters()"><lucide-icon [img]="icons.X" [size]="15" />清除筛选</button> }
- </footer>
- </section>
- }
- @if (hasScheduleCalendar) {
- <div class="schedule-tools">
- <div class="schedule-toolbar">
- <nav class="range-tabs" aria-label="课表日期范围">
- <button type="button" [class.active]="scheduleRange() === 'day' && selectedDate() === today()" (click)="showToday()">今天</button>
- <button type="button" [class.active]="scheduleRange() === 'day' && selectedDate() === tomorrow()" (click)="showTomorrow()">明天</button>
- <button type="button" [class.active]="scheduleRange() === 'week'" (click)="showWeek()">本周</button>
- <button type="button" [class.active]="scheduleRange() === 'all'" (click)="showAllSchedules()">全部</button>
- </nav>
- @if (isCoach && studentOptions().length) {
- <label class="student-filter"><span>学员</span>
- <select aria-label="筛选排课学员" [ngModel]="selectedStudent()" (ngModelChange)="setStudentFilter($event)">
- <option value="all">全部学员({{ allStudentCount() }})</option>
- @for (student of studentOptions(); track student.id) { <option [value]="student.id">{{ student.name }}({{ student.count }})</option> }
- </select>
- </label>
- }
- </div>
- <nav class="schedule-filters" [attr.aria-label]="isCoach ? '陪练排课状态筛选' : '伴学记录筛选'">
- <button type="button" [class.active]="scheduleFilter() === 'all'" (click)="setScheduleFilter('all')">全部 <span>{{ (loading()||error())&&!schedules().length ? '—' : scopedSchedules().length }}</span></button>
- <button type="button" [class.active]="scheduleFilter() === 'pending'" (click)="setScheduleFilter('pending')">计划未完成 <span>{{ (loading()||error())&&!schedules().length ? '—' : pendingCount() }}</span></button>
- <button type="button" [class.active]="scheduleFilter() === 'finished'" (click)="setScheduleFilter('finished')">已结束 <span>{{ (loading()||error())&&!schedules().length ? '—' : finishedCount() }}</span></button>
- </nav>
- <div class="calendar-panel">
- <div class="calendar-heading">
- <button type="button" (click)="shiftCalendarMonth(-1)" aria-label="上个月">‹</button>
- <strong>{{ calendarLabel() }}</strong>
- <button type="button" (click)="shiftCalendarMonth(1)" aria-label="下个月">›</button>
- </div>
- <div class="calendar-weekdays" aria-hidden="true">
- @for (week of weekLabels; track week) { <span>{{ week }}</span> }
- </div>
- <div class="calendar-grid">
- @for (day of calendarDays(); track day.date) {
- <button
- type="button"
- class="calendar-day"
- [class.outside]="!day.inMonth"
- [class.today]="day.isToday"
- [class.selected]="scheduleRange() === 'day' && selectedDate() === day.date"
- [class.has-course]="day.count > 0"
- [class.finished-course]="day.finishedCount > 0"
- [class.planned-course]="day.plannedCount > 0"
- (click)="selectCalendarDay(day)"
- [attr.aria-label]="day.date + ',已学完 ' + day.finishedCount + ' 节,计划学 ' + day.plannedCount + ' 节'"
- >
- <span>{{ day.day }}</span>
- @if (day.count > 0) { <em>{{ day.count }}</em> }
- </button>
- }
- </div>
- </div>
- <p class="schedule-summary">{{ scheduleRangeLabel() }} · {{(loading()||error())&&!schedules().length?'—':visibleSchedules().length}} 节课</p>
- </div>
- } @else if (!isCoach && schedules().length) {
- <nav class="schedule-filters" aria-label="伴学记录筛选">
- <button type="button" [class.active]="scheduleFilter() === 'all'" (click)="setScheduleFilter('all')">全部 <span>{{ listTotal() }}</span></button>
- <button type="button" [class.active]="scheduleFilter() === 'pending'" (click)="setScheduleFilter('pending')">计划未完成 <span>{{ (loading()||error())&&!schedules().length ? '—' : pendingCount() }}</span></button>
- <button type="button" [class.active]="scheduleFilter() === 'finished'" (click)="setScheduleFilter('finished')">已结束 <span>{{ (loading()||error())&&!schedules().length ? '—' : finishedCount() }}</span></button>
- </nav>
- }
- @if (visibleSchedules().length) {
- @for (group of groups(); track group.date) {
- <h3>{{ group.date }}</h3>
- <div class="schedule-list">
- @for (item of group.items; track $index) {
- <article [class.has-lesson-pay]="isHistory && isCoach && !item['cancelled']" [attr.data-schedule-id]="scheduleId(item)" (click)="open(item, $event)">
- <time>{{ scheduleTime(item) }}</time>
- <div class="schedule-copy"><strong>{{ scheduleTitle(item) }}</strong>@if (scheduleSubtitle(item)) { <span>{{ scheduleSubtitle(item) }}</span> }</div>
- @if (isHistory && isCoach && !item['cancelled']) { <div class="lesson-pay"><strong>{{ item['payStatus'] === 'earned' ? '陪练费用 ¥' + ($any(item['teacherPay']) | number:'1.2-2') : '费用待核对' }}</strong><small [class.feedback-pending]="item['feedbackStatus'] !== 'submitted'">{{item['feedbackStatus']==='submitted'?'反馈已提交':'反馈待补'}}</small></div> }
- <span class="status" [class]="'status ' + statusClass(item)">{{ statusText(item) }}</span>
- @if (isCoach) { <button type="button" class="report-button" (click)="report(item, $event)" aria-label="学习报表" title="学习报表"><lucide-icon [img]="icons.BarChart3" [size]="18" /></button> }
- <lucide-icon class="chevron" [img]="icons.ChevronRight" [size]="18" />
- </article>
- }
- </div>
- }
- } @else { <p class="band-empty">{{ error() ? '课表暂时无法加载,请重试' : loading() ? '正在读取排课数据…' : (isHistory && schedules().length && historyFilterActive() ? '没有符合筛选条件的陪练记录' : (hasScheduleCalendar ? scheduleEmptyText() : (schedules().length ? '该分类暂无伴学记录' : (isCoach ? '暂无陪练记录' : '暂无伴学记录')))) }}</p> }
- </section>
- @if (error()) {
- <div class="center-state error"><lucide-icon [img]="icons.AlertCircle" [size]="25" /><span>{{ error() }}{{schedules().length?',当前保留上次加载结果':''}}</span><button type="button" class="secondary-button" (click)="load(true)">重新加载</button></div>
- }
- <nav class="mobile-pagination"><button (click)="turnPage(-1)" [disabled]="loading()||listPage()<=1">上一页</button><span>共 {{(loading()||error())&&!schedules().length?'—':listTotal()}} 条 · {{listPage()}} / {{listPages()}}</span><button (click)="turnPage(1)" [disabled]="loading()||listPage()>=listPages()">下一页</button></nav></section>
|