|
|
@@ -1,10 +1,12 @@
|
|
|
import {CommonModule} from '@angular/common';
|
|
|
-import {Component,ChangeDetectionStrategy,OnChanges,Input,Output,EventEmitter,computed,inject,signal} from '@angular/core';
|
|
|
+import {Component,ChangeDetectionStrategy,OnChanges,OnDestroy,Input,Output,EventEmitter,computed,inject,signal,ViewChild,ElementRef,Injector,afterNextRender,AfterRenderRef} from '@angular/core';
|
|
|
import {FormsModule} from '@angular/forms';
|
|
|
import {OperationsService} from '../operations.service';
|
|
|
import {LessonHistoryDetail,TransferPreview} from './lesson-record.models';
|
|
|
@Component({selector:'app-lesson-record-detail',standalone:true,imports:[CommonModule,FormsModule],templateUrl:'./lesson-record-detail.component.html',styleUrls:['./operations-professional.scss','./lesson-records.component.scss'],changeDetection:ChangeDetectionStrategy.OnPush})
|
|
|
-export class LessonRecordDetailComponent implements OnChanges {
|
|
|
+export class LessonRecordDetailComponent implements OnChanges,OnDestroy {
|
|
|
+ @ViewChild('detailDrawer') private drawer?:ElementRef<HTMLElement>;
|
|
|
+ private readonly injector=inject(Injector);private pendingReveal?:AfterRenderRef;
|
|
|
@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}[]>([]);
|
|
|
@@ -12,12 +14,23 @@ export class LessonRecordDetailComponent implements OnChanges {
|
|
|
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();}
|
|
|
+ ngOnDestroy():void{this.pendingReveal?.destroy();}
|
|
|
+ private revealAfterRender(selector:string):void{
|
|
|
+ this.pendingReveal?.destroy();
|
|
|
+ this.pendingReveal=afterNextRender(()=>{
|
|
|
+ const drawer=this.drawer?.nativeElement,target=drawer?.querySelector<HTMLElement>(selector);if(!drawer||!target)return;
|
|
|
+ const bounds=drawer.getBoundingClientRect(),rect=target.getBoundingClientRect(),viewport=window.visualViewport;
|
|
|
+ const top=Math.max(bounds.top,viewport?.offsetTop||0)+16,bottom=Math.min(bounds.bottom,viewport?viewport.offsetTop+viewport.height:window.innerHeight)-16;
|
|
|
+ const delta=rect.bottom>bottom?rect.bottom-bottom:rect.top<top?rect.top-top:0;
|
|
|
+ if(delta)drawer.scrollTo({top:drawer.scrollTop+delta,behavior:window.matchMedia('(prefers-reduced-motion: reduce)').matches?'instant':'smooth'});
|
|
|
+ },{injector:this.injector});
|
|
|
+ }
|
|
|
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);}}
|
|
|
+ async beginTransfer():Promise<void>{this.transferring.set(true);this.coachPickerOpen.set(false);this.revealAfterRender('.transfer-panel');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);}
|
|
|
+ searchCoach(value:string):void{this.coachId=0;this.coachQuery.set(value);this.coachPickerOpen.set(true);this.activeCoach.set(-1);this.resetPreview();this.revealAfterRender('.coach-picker');}
|
|
|
+ openCoaches():void{this.coachPickerOpen.set(true);this.activeCoach.set(-1);this.revealAfterRender('.coach-picker');}
|
|
|
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{
|