import { DropdownDirective } from '../shared/dropdown.directive'; import { CommonModule } from '@angular/common'; import { Component, Input, Output, EventEmitter, OnChanges, inject, signal, computed } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { OperationsService } from '../operations.service'; import { EnrollmentOptions } from '../member-enrollment.models'; @Component({selector:'app-member-enrollment',standalone:true,imports: [DropdownDirective, CommonModule,FormsModule],template:`

按分类开课

先选择课程分类,再选择内容。同一内容可分别用于不同分类;开课不扣课时。

@if(error()){}@if(message()){

{{message()}}

} @if(loading()){

正在加载分类内容…

}@if(error()){} @if(options();as value){
@if(value.reason){

{{value.reason}}

}
@for(r of value.rules;track r.objectId){{{r.durationMinutes}}分钟 · {{r.deliveryLabel}}:@for(c of r.creditCosts;track c.account){ {{c.label}} {{c.amount}} }({{r.affordable?'课时足够':'课时不足'}})}
最新课程在前 · 点击课程行可选择已选 {{selected().length}} 项
@if(selectedCourses().length){
@for(c of selectedCourses();track c.courseId){}
}
@for(c of filtered();track c.courseId){}@empty{}
选择课程内容学阶单词数量状态
{{c.courseName}}ID {{c.courseId}}{{c.stageName}}{{c.wordCount}}{{c.alreadyOpened?'该分类已开课':c.reason||'可开课'}}
该分类下暂无匹配内容,请检查课程目录和学阶。
}
`,styleUrls:['./member-enrollment.scss']}) export class MemberEnrollmentComponent implements OnChanges{ @Input({required:true}) studentId=0;@Input() refreshVersion=0;@Output() changed=new EventEmitter();private operations=inject(OperationsService); options=signal(null);selected=signal([]);search=signal('');stage=signal('');error=signal('');message=signal('');saving=signal(false);loading=signal(false);category='word';reason='为会员开课';private serial=0;private key='';private fingerprint=''; categoryStage(){return ({primary_writing:'primary',middle_writing:'middle',high_writing:'high'} as Record)[this.category]||'';} filtered=computed(()=>(this.options()?.items||[]).filter(c=>(!this.stage()||c.stageKey===this.stage())&&`${c.courseName} ${c.courseId}`.toLowerCase().includes(this.search().trim().toLowerCase()))); ngOnChanges(){void this.load();}changeCategory(value:string){this.category=value;this.options.set(null);this.selected.set([]);this.stage.set(this.categoryStage());this.message.set('');void this.load();} selectedCourses=computed(()=>(this.options()?.items||[]).filter(c=>this.selected().includes(c.courseId))); clearSelection(){if(!this.saving())this.selected.set([]);} toggle(id:number){const course=this.options()?.items.find(c=>c.courseId===id);if(this.saving()||this.loading()||!course?.eligible||course.alreadyOpened)return;this.selected.update(ids=>ids.includes(id)?ids.filter(x=>x!==id):[...ids,id]);} async load(){if(!this.studentId)return;const serial=++this.serial;this.loading.set(true);try{const result=await this.operations.request('ops/course-bindings/options',{studentId:this.studentId,categoryKey:this.category});if(serial===this.serial){this.options.set(result);this.selected.update(ids=>ids.filter(id=>result.items.some(c=>c.courseId===id&&c.eligible&&!c.alreadyOpened)));this.error.set('');}}catch(e){if(serial===this.serial){this.options.set(null);this.error.set(e instanceof Error?e.message:'开课内容加载失败');}}finally{if(serial===this.serial)this.loading.set(false);}} async submit(){if(this.saving()||this.loading()||!this.selected().length||!this.options()?.eligible||this.options()?.categoryKey!==this.category)return;if(this.reason.trim().length<2){this.error.set('请填写操作原因');return;}const payload={studentId:this.studentId,categoryKey:this.category,courseIds:[...this.selected()].sort((a,b)=>a-b)},f=JSON.stringify([payload,this.reason]);if(f!==this.fingerprint){this.fingerprint=f;this.key=crypto.randomUUID();}this.saving.set(true);this.error.set('');try{await this.operations.mutate('ops/course-bindings/save','',this.reason,payload,'',this.key);this.selected.set([]);this.fingerprint='';this.message.set('开课成功,可前往排课');await this.load();this.changed.emit();}catch(e){this.error.set(e instanceof Error?e.message:'开课失败');}finally{this.saving.set(false);}} }