|
|
@@ -1,5 +1,5 @@
|
|
|
import {CommonModule} from '@angular/common';
|
|
|
-import {Component,ChangeDetectionStrategy,OnChanges,Input,Output,EventEmitter,inject,signal} from '@angular/core';
|
|
|
+import {Component,ChangeDetectionStrategy,OnChanges,Input,Output,EventEmitter,computed,inject,signal} from '@angular/core';
|
|
|
import {FormsModule} from '@angular/forms';
|
|
|
import {OperationsService} from '../operations.service';
|
|
|
import {LessonHistoryDetail,TransferPreview} from './lesson-record.models';
|
|
|
@@ -8,11 +8,23 @@ export class LessonRecordDetailComponent implements OnChanges {
|
|
|
@Input() lessonId='';@Input() appointmentId='';@Output() closed=new EventEmitter<void>();@Output() changed=new EventEmitter<void>();
|
|
|
readonly data=signal<LessonHistoryDetail|null>(null);readonly busy=signal(false);readonly error=signal('');readonly message=signal('');
|
|
|
readonly transferring=signal(false);readonly preview=signal<TransferPreview|null>(null);readonly coaches=signal<{userId:number;displayName:string}[]>([]);
|
|
|
+ readonly coachQuery=signal('');readonly coachPickerOpen=signal(false);readonly activeCoach=signal(-1);
|
|
|
+ readonly filteredCoaches=computed(()=>{const q=this.coachQuery().trim().toLocaleLowerCase();const selected=this.coaches().find(c=>c.userId===this.coachId);return this.coaches().filter(c=>!q||selected&&q===this.coachLabel(selected).toLocaleLowerCase()||q.split(/\s+/).every(part=>(c.displayName+' '+c.userId).toLocaleLowerCase().includes(part)));});
|
|
|
scope='lesson';coachId=0;reason='';private key='';private serial=0;private readonly ops=inject(OperationsService);
|
|
|
ngOnChanges():void{void this.load();}
|
|
|
async load():Promise<void>{const serial=++this.serial;this.busy.set(true);this.error.set('');try{const d=await this.ops.request<LessonHistoryDetail>('ops/lessons/detail',{lessonId:this.lessonId,appointmentId:this.appointmentId});if(serial===this.serial)this.data.set(d);}catch(e){if(serial===this.serial)this.error.set(e instanceof Error?e.message:'详情加载失败');}finally{if(serial===this.serial)this.busy.set(false);}}
|
|
|
async beginTransfer():Promise<void>{this.transferring.set(true);if(!this.data()?.appointmentId)this.scope='student';this.resetPreview();this.busy.set(true);this.error.set('');try{const rows=await this.ops.request<{items:{userId:number;displayName:string}[]}>('ops/lessons/transfer-coaches');this.coaches.set(rows.items);}catch(e){this.error.set(e instanceof Error?e.message:'陪练列表加载失败');}finally{this.busy.set(false);}}
|
|
|
resetPreview():void{this.preview.set(null);this.key='';}
|
|
|
+ coachLabel(coach:{userId:number;displayName:string}):string{return `${coach.displayName} · ${coach.userId}`;}
|
|
|
+ searchCoach(value:string):void{this.coachId=0;this.coachQuery.set(value);this.coachPickerOpen.set(true);this.activeCoach.set(-1);this.resetPreview();}
|
|
|
+ openCoaches():void{this.coachPickerOpen.set(true);this.activeCoach.set(-1);}
|
|
|
+ selectCoach(coach:{userId:number;displayName:string}):void{this.coachId=coach.userId;this.coachQuery.set(this.coachLabel(coach));this.coachPickerOpen.set(false);this.activeCoach.set(-1);this.resetPreview();}
|
|
|
+ closeCoachesOnBlur(event:FocusEvent):void{if(!(event.currentTarget as HTMLElement).contains(event.relatedTarget as Node|null))this.coachPickerOpen.set(false);}
|
|
|
+ coachKeydown(event:KeyboardEvent):void{
|
|
|
+ if(event.key==='Escape'){event.preventDefault();event.stopPropagation();this.coachPickerOpen.set(false);return;}
|
|
|
+ if(event.key==='ArrowDown'||event.key==='ArrowUp'){event.preventDefault();this.coachPickerOpen.set(true);const n=this.filteredCoaches().length;this.activeCoach.set(n?(this.activeCoach()<0?(event.key==='ArrowDown'?0:n-1):(this.activeCoach()+ (event.key==='ArrowDown'?1:-1)+n)%n):-1);setTimeout(()=>document.getElementById('transfer-coach-'+this.activeCoach())?.scrollIntoView({block:'nearest'}));}
|
|
|
+ if(event.key==='Enter'&&this.coachPickerOpen()){event.preventDefault();const row=this.filteredCoaches()[this.activeCoach()];if(row)this.selectCoach(row);}
|
|
|
+ }
|
|
|
params():Record<string,unknown>{return{studentId:this.data()?.studentId,appointmentId:this.data()?.appointmentId,scope:this.scope,coachId:this.coachId};}
|
|
|
async checkTransfer():Promise<void>{if(!this.coachId||this.reason.trim().length<2){this.error.set('请选择目标陪练,并填写至少两个字的转交原因');return;}this.busy.set(true);this.error.set('');this.preview.set(null);try{this.preview.set(await this.ops.request<TransferPreview>('ops/lessons/transfer-preview',this.params()));this.key=crypto.randomUUID();}catch(e){this.error.set(e instanceof Error?e.message:'预览失败');}finally{this.busy.set(false);}}
|
|
|
async saveTransfer():Promise<void>{const p=this.preview();if(!p?.count||this.busy())return;this.busy.set(true);this.error.set('');try{const r=await this.ops.request<{count:number}>('ops/lessons/transfer',{...this.params(),version:p.version,reason:this.reason.trim(),idempotencyKey:this.key});this.message.set(`已转交 ${r.count} 次未完成复习`);this.transferring.set(false);this.resetPreview();await this.load();this.changed.emit();}catch(e){this.error.set(e instanceof Error?e.message:'保存失败,可用同一请求重试');}finally{this.busy.set(false);}}
|