Selaa lähdekoodia

fix(admin): add payroll search filter reset

彭峰 1 kuukausi sitten
vanhempi
commit
e68454888c

+ 1 - 1
projects/xiaoshu-admin/src/app/pages/operations-payroll.component.html

@@ -1,7 +1,7 @@
 <header class="page-header payroll-header"><div><p>PAYROLL CENTER</p><h1>工资结算</h1><span>按有效上课记录与复习奖励核算工资,完成月度审核与发放登记</span></div><div class="header-actions"><button class="ui-button refresh-action" type="button" (click)="refreshLive()" [disabled]="refreshing()"><lucide-icon [img]="icons.RefreshCw" [size]="17" [class.spin]="refreshing()" />{{refreshing()?'正在刷新':'刷新实时数据'}}</button>@if(tab()!=='rewards'){<button class="ui-button export-action" type="button" (click)="exportExcel()" [disabled]="saving()"><lucide-icon [img]="icons.Download" [size]="17" />导出 Excel</button>}</div></header>
 @if(message()){<p class="notice" role="status">{{message()}}</p>} @if(error()){<p class="notice error" role="alert">{{error()}}</p>}
 <nav class="tabs payroll-tabs" aria-label="工资结算功能"><button type="button" [class.active]="tab()==='overview'" (click)="tab.set('overview')"><lucide-icon [img]="icons.TrendingUp" [size]="17" />收益统计</button><button type="button" [class.active]="tab()==='rewards'" (click)="openRewards()">复习奖励</button><button type="button" [class.active]="tab()==='batches'" (click)="tab.set('batches')"><lucide-icon [img]="icons.Banknote" [size]="17" />工资批次</button><button type="button" [class.active]="tab()==='rates'" (click)="tab.set('rates')"><lucide-icon [img]="icons.Settings2" [size]="17" />排课与工资标准</button></nav>
-@if(tab()!=='rates'){<section class="toolbar payroll-toolbar"><label><span>统计月份</span><input type="month" [ngModel]="month()" (ngModelChange)="month.set($event)" /></label><label><span>门店</span><select [ngModel]="storeId()" (ngModelChange)="storeId.set(+$event)"><option [ngValue]="0">全部门店</option>@for(store of overview()?.stores||[];track store.storeId){<option [ngValue]="store.storeId">{{store.storeName==='未绑定门店'?'未绑定门店(ID '+store.storeId+')':store.storeName}}</option>}</select></label><label class="search-field"><span>陪练搜索</span><input [ngModel]="search()" (ngModelChange)="search.set($event)" [placeholder]="tab()==='rewards'?'老师姓名或 ID':'姓名或手机号'" (keyup.enter)="applyFilters()" /></label><button class="ui-button primary query-action" type="button" (click)="applyFilters()"><lucide-icon [img]="icons.Search" [size]="17" />查询</button><span class="source-chip" [class.cache]="overview()?.sourceStatus?.mode==='cache'" role="status"><i></i>{{overview()?.sourceStatus?.mode==='live'?'实时数据':'缓存数据'}} · {{overview()?.sourceStatus?.refreshedAt|date:'MM-dd HH:mm:ss'}}</span></section>}
+@if(tab()!=='rates'){<section class="toolbar payroll-toolbar"><label><span>统计月份</span><input type="month" [ngModel]="month()" (ngModelChange)="month.set($event)" /></label><label><span>门店</span><select [ngModel]="storeId()" (ngModelChange)="storeId.set(+$event)"><option [ngValue]="0">全部门店</option>@for(store of overview()?.stores||[];track store.storeId){<option [ngValue]="store.storeId">{{store.storeName==='未绑定门店'?'未绑定门店(ID '+store.storeId+')':store.storeName}}</option>}</select></label><label class="search-field"><span>陪练搜索</span><input [ngModel]="search()" (ngModelChange)="search.set($event)" [placeholder]="tab()==='rewards'?'老师姓名或 ID':'姓名或手机号'" (keyup.enter)="applyFilters()" /></label><div class="filter-actions"><button class="ui-button primary query-action" type="button" (click)="applyFilters()"><lucide-icon [img]="icons.Search" [size]="17" />查询</button><button class="ui-button secondary reset-action" type="button" (click)="resetFilters()">重置</button></div><span class="source-chip" [class.cache]="overview()?.sourceStatus?.mode==='cache'" role="status"><i></i>{{overview()?.sourceStatus?.mode==='live'?'实时数据':'缓存数据'}} · {{overview()?.sourceStatus?.refreshedAt|date:'MM-dd HH:mm:ss'}}</span></section>}
 @if(tab()==='overview'){
  @if(overview();as data){
   @if(data.sourceStatus.mode==='cache'){<p class="notice error">{{data.sourceStatus.message}}。当前可以查看缓存,但不能生成或审核工资单。</p>}

+ 15 - 1
projects/xiaoshu-admin/src/app/pages/operations-payroll.component.scss

@@ -102,6 +102,16 @@
   border-radius: 12px;
 }
 
+.filter-actions {
+  display: flex;
+  gap: 10px;
+}
+
+.reset-action {
+  min-width: 80px;
+  height: 46px;
+}
+
 .query-action {
   min-width: 96px;
   height: 46px;
@@ -358,7 +368,7 @@
   }
 
   .payroll-toolbar {
-    grid-template-columns: 190px 220px minmax(240px, 1fr) auto;
+    grid-template-columns: repeat(2, minmax(0, 1fr));
   }
 
   .source-chip {
@@ -413,6 +423,10 @@
     grid-column: auto;
   }
 
+  .filter-actions > button {
+    flex: 1;
+  }
+
   .query-action,
   .source-chip {
     width: 100%;

+ 1 - 0
projects/xiaoshu-admin/src/app/pages/operations-payroll.component.ts

@@ -26,6 +26,7 @@ export class OperationsPayrollComponent implements OnInit{
  clearRewardCoach():void{this.rewardCoachId.set(0);this.openRewards();}isManager():boolean{return this.sessions.session()?.operationsRole==='ops-manager'||this.sessions.session()?.isSuperAdmin===true;}
  async load(refresh=false):Promise<void>{this.loading.set(true);this.error.set('');try{const [overview,page,rates,standards]=await Promise.all([this.operations.payrollOverview(this.month(),this.storeId(),this.search(),refresh),this.operations.payrolls(),this.operations.payRates(),this.operations.teachingStandards()]);this.overview.set(overview);this.page.set(page);this.rates.set(rates.items);this.standards.set(standards);this.mappingSelections.set(Object.fromEntries(standards.mappings.map((row)=>[row.courseId,row.categoryKey||row.suggestedCategoryKey])));}catch(error){this.error.set(error instanceof Error?error.message:'工资数据加载失败');}finally{this.loading.set(false);}}
  async refreshLive():Promise<void>{if(this.tab()==='rewards'){this.openRewards();return;}if(this.refreshing())return;this.refreshing.set(true);this.error.set('');try{this.overview.set(await this.operations.payrollOverview(this.month(),this.storeId(),this.search(),true));this.message.set('实时上课记录已刷新');}catch(error){this.error.set(error instanceof Error?error.message:'实时工资数据刷新失败,当前继续显示缓存');}finally{this.refreshing.set(false);}}
+ async resetFilters():Promise<void>{this.search.set('');this.storeId.set(0);this.rewardCoachId.set(0);await this.applyFilters();}
  async applyFilters():Promise<void>{if(this.tab()==='rewards'){this.openRewards();return;}await this.load(true);}
  async open(row:PayrollSummary):Promise<void>{this.loading.set(true);this.error.set('');try{this.selectedCoachId.set(0);this.detail.set(await this.operations.payrollDetail(row.objectId));}catch(error){this.error.set(error instanceof Error?error.message:'工资单加载失败');}finally{this.loading.set(false);}}
  coachLabel(name:string,coachId:number):string{const value=String(name||'').trim();return !value||value===String(coachId)?`陪练 ${coachId}`:value;}