瀏覽代碼

fix(admin): show booked duration in cross-date appointments

彭峰 3 小時之前
父節點
當前提交
b00588a33d

+ 1 - 1
projects/xiaoshu-admin/src/app/pages/schedule-bulk-manager.component.html

@@ -12,7 +12,7 @@
  @if(message()){<p class="notice" role="status">{{message()}}</p>}@if(error()){<p class="notice error" role="alert">{{error()}}</p>}
  <div class="controls"><div><strong>共 {{total()}} 条</strong><span>已选 {{selected().size}} 条</span></div><div><button type="button" (click)="selectAllMatching()" [disabled]="loading()||selecting()||saving()||hasPendingFilters()||!resultsReady()">{{selecting()?'正在选择…':'全选全部查询结果'}}</button><button type="button" (click)="clearSelection()" [disabled]="!selected().size||selecting()||saving()">清空选择</button></div></div>
  <div class="table-wrap" [attr.aria-busy]="loading()"><table><thead><tr><th><input type="checkbox" aria-label="选择本页全部未开始预约" [checked]="pageSelected()" (change)="togglePage()" [disabled]="!selectablePage().length||loading()||selecting()||saving()||hasPendingFilters()||!resultsReady()" /></th><th>预约时间</th><th>学员</th><th>老师</th><th>课程</th><th>状态</th></tr></thead><tbody>
- @for(row of rows();track row.targetId){<tr><td><input type="checkbox" [attr.aria-label]="'选择 '+row.studentName+' 的预约'" [checked]="selected().has(row.targetId)" [disabled]="!canSelect(row)||loading()||selecting()||saving()||hasPendingFilters()||!resultsReady()" (change)="toggleRow(row)" /></td><td>{{row.startsAt|date:'yyyy-MM-dd HH:mm':'+0800'}}</td><td>{{row.studentName}}</td><td>{{row.coachName}}</td><td>{{row.courseName}}</td><td>{{row.statusLabel || (row.cancelled?'已取消':row.statusCode===0?'未开始':'不可批量取消')}}</td></tr>}
+ @for(row of rows();track row.targetId){<tr><td><input type="checkbox" [attr.aria-label]="'选择 '+row.studentName+' 的预约'" [checked]="selected().has(row.targetId)" [disabled]="!canSelect(row)||loading()||selecting()||saving()||hasPendingFilters()||!resultsReady()" (change)="toggleRow(row)" /></td><td>{{row.startsAt|date:'yyyy-MM-dd HH:mm':'+0800'}}</td><td>{{row.studentName}}</td><td>{{row.coachName}}</td><td>{{row.courseName}}<span class="course-duration"> · @if(row.durationMinutes && row.durationMinutes > 0){{{row.durationMinutes}}分钟}@else{时长待核对}</span></td><td>{{row.statusLabel || (row.cancelled?'已取消':row.statusCode===0?'未开始':'不可批量取消')}}</td></tr>}
  @empty{<tr><td colspan="6" class="empty">{{loading()?'正在加载…':'没有找到预约'}}</td></tr>}
  </tbody></table></div>
  <div class="pagination"><label>每页 <select [ngModel]="pageSize()" (ngModelChange)="changePageSize(+$event)" [disabled]="loading()||selecting()||saving()"><option [ngValue]="10">10</option><option [ngValue]="25">25</option><option [ngValue]="50">50</option><option [ngValue]="100">100</option></select> 条</label><div><button type="button" (click)="changePage(page()-1)" [disabled]="page()<=1||loading()||selecting()||saving()">上一页</button><span>第 {{page()}} / {{pageCount()}} 页</span><button type="button" (click)="changePage(page()+1)" [disabled]="page()>=pageCount()||loading()||selecting()||saving()">下一页</button></div></div>

+ 2 - 0
projects/xiaoshu-admin/src/app/pages/schedule-bulk-manager.component.scss

@@ -11,3 +11,5 @@ button,input,select{font:inherit}button{background:#fff;border:1px solid #cdded7
 .search .reset{background:#fff;color:#176c57}.search input:not([type=checkbox]){box-sizing:border-box;width:100%;min-height:40px}
 @media(max-width:900px){.search fieldset{grid-template-columns:1fr 1fr}.search label{grid-column:1/-1}.search button{width:100%}}
 @media(max-width:420px){.search fieldset{grid-template-columns:minmax(0,1fr)}.search label{grid-column:auto}}
+
+.course-duration{white-space:nowrap}

+ 12 - 0
projects/xiaoshu-admin/src/app/pages/schedule-bulk-manager.component.spec.ts

@@ -6,6 +6,18 @@ describe('跨日期预约批量管理',()=>{
  let ops:jasmine.SpyObj<OperationsService>;
  const rows=Array.from({length:201},(_,i)=>({targetId:'a'+i,generalId:i+1,studentName:'学员'+i,coachName:'杨珍',courseName:'课程',startsAt:'2026-11-05T10:00:00+08:00',updatedAt:'2026-11-01T00:00:00.000Z',statusCode:i===199?10:0,cancelled:i===200,readOnly:false}));
  beforeEach(()=>{ops=jasmine.createSpyObj<OperationsService>('OperationsService',['list','mutate','batchMutations']);ops.batchMutations.and.callFake(async work=>work());ops.list.and.callFake(async(_kind,page,size,search)=>({page,pageSize:size,total:rows.length,items:rows.slice((page-1)*size,page*size),refreshedAt:''}));TestBed.configureTestingModule({imports:[ScheduleBulkManagerComponent],providers:[{provide:OperationsService,useValue:ops}]});});
+ it('课程栏显示预约实际时长,历史缺失时明确待核对且不修改预约',async()=>{
+  ops.list.and.resolveTo({page:1,pageSize:25,total:3,items:[
+   {...rows[0],courseName:'阅读写作',classType:4,durationMinutes:60},
+   {...rows[1],courseName:'单词课',classType:2,durationMinutes:30},
+   {...rows[2],courseName:'历史课程'}
+  ],refreshedAt:''});
+  const fixture=TestBed.createComponent(ScheduleBulkManagerComponent);
+  fixture.detectChanges();await fixture.whenStable();fixture.detectChanges();
+  const courses=Array.from(fixture.nativeElement.querySelectorAll('tbody tr')).map((row:any)=>row.querySelectorAll('td')[4].textContent.replace(/\s+/g,' ').trim());
+  expect(courses).toEqual(['阅读写作 · 60分钟','单词课 · 30分钟','历史课程 · 时长待核对']);
+  expect(ops.mutate).not.toHaveBeenCalled();
+ });
  it('按姓名跨日期查询,全选所有匹配页,排除已取消和训练中课程,并可取消单项',async()=>{
   const c=TestBed.createComponent(ScheduleBulkManagerComponent).componentInstance;c.searchInput='杨珍';await c.searchAppointments();
   expect(ops.list).toHaveBeenCalledWith('appointments',1,25,'杨珍',{dateFrom:'',dateTo:''});await c.selectAllMatching();expect(c.selected().size).toBe(199);expect(c.selected().has('a199')).toBeFalse();expect(c.selected().has('a200')).toBeFalse();