Просмотр исходного кода

Tighten payroll summary layout

彭峰 3 дней назад
Родитель
Сommit
d332fec2b0

+ 37 - 4
projects/xiaoshu-admin/src/app/pages/operations-overview-metrics.scss

@@ -45,15 +45,48 @@
   white-space: nowrap;
 }
 
-/* Amounts need more horizontal space than counts, especially with a currency sign. */
+/* Eight payroll figures form a predictable two-row summary on desktop. */
 app-operations-payroll .payroll-metrics.overview-metrics {
-  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+
+app-operations-payroll .payroll-metrics.overview-metrics > article {
+  grid-template-columns: minmax(0, 1fr);
+  grid-template-rows: auto 1fr auto;
+  row-gap: 2px;
+  min-height: 124px;
+}
+
+app-operations-payroll .payroll-metrics.overview-metrics > article > span {
+  grid-column: 1;
+  grid-row: 1;
+}
+
+app-operations-payroll .payroll-metrics.overview-metrics > article > small {
+  grid-column: 1;
+  grid-row: 3;
+  overflow-wrap: normal;
 }
 
 app-operations-payroll .payroll-metrics.overview-metrics > article > strong {
-  font-size: clamp(30px, 2.35vw, 43px) !important;
+  grid-column: 1;
+  grid-row: 2;
+  align-self: center;
+  font-size: clamp(30px, 2.25vw, 43px) !important;
   white-space: normal;
-  overflow-wrap: anywhere;
+  overflow-wrap: break-word;
+}
+
+@media (max-width: 1320px) {
+  app-operations-payroll .payroll-metrics.overview-metrics {
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+  }
+}
+
+@media (max-width: 620px) {
+  app-operations-payroll .payroll-metrics.overview-metrics {
+    grid-template-columns: minmax(0, 1fr);
+  }
 }
 
 /* The selected day is context; the four live figures are the metrics. */

+ 7 - 11
projects/xiaoshu-admin/src/app/pages/operations-payroll.component.scss

@@ -103,9 +103,9 @@
 
 .payroll-toolbar {
   display: grid;
-  grid-template-columns: 190px 220px minmax(260px, 1fr) auto auto;
+  grid-template-columns: 170px 190px minmax(180px, 1fr) auto auto;
   align-items: end;
-  gap: 14px;
+  gap: 12px;
   padding: 18px;
   border-radius: 18px;
 }
@@ -304,9 +304,9 @@
 
 .payroll-metrics article {
   position: relative;
-  min-height: 138px;
+  min-height: 124px;
   overflow: hidden;
-  padding: 22px;
+  padding: 16px 18px;
   border: 1px solid #dce7e3;
   border-radius: 18px;
   background: #fff;
@@ -343,7 +343,7 @@
 
 .payroll-metrics strong {
   display: block;
-  margin: 12px 0 6px;
+  margin: 0;
   color: #16352d;
   font-size: clamp(24px, 2vw, 30px);
   line-height: 1.1;
@@ -508,17 +508,13 @@
 .reconciliation-summary strong { margin-top:7px;color:#176c55;font-size:24px; }
 
 @media (max-width: 1400px) {
-  .payroll-metrics {
-    grid-template-columns: repeat(3, 1fr);
-  }
-
   .payroll-toolbar {
-    grid-template-columns: repeat(2, minmax(0, 1fr));
+    grid-template-columns: minmax(150px, .7fr) minmax(170px, .8fr) minmax(220px, 1.3fr) auto;
   }
 
   .source-chip {
     grid-column: 1 / -1;
-    justify-self: start;
+    justify-self: end;
   }
 }
 

+ 2 - 2
projects/xiaoshu-admin/src/app/pages/payroll-cutoff.component.ts

@@ -5,7 +5,7 @@ import { OperationsService } from '../operations.service';
 interface Difference {id:string;dataset:string;legacyKey:string;student:string;course:string;date:string;fields:string[];current:unknown;fingerprint:string;cosmetic:boolean;resolved:boolean;}
 interface Cutoff {month:string;version:string;runId:string;cutoffAt:string;complete:boolean;pending:number;conflicts:Difference[];approved:boolean;monthClosed:boolean;datasets:{model:number;source:number;matched:number}[];}
 @Component({selector:'app-payroll-cutoff',standalone:true,imports:[CommonModule,FormsModule],template:`
-<section class="cutoff"><button type="button" (click)="load()" [disabled]="busy()">{{busy()?'核验中…':'核验 '+month()+' 结算来源'}}</button>
+<section class="cutoff" [class.is-collapsed]="!data()&&!error()"><button type="button" (click)="load()" [disabled]="busy()">{{busy()?'核验中…':'核验 '+month()+' 结算来源'}}</button>
 @if(error()){<p role="alert">{{error()}}</p>}
 @if(data();as d){<h3>{{d.month}} · 工资来源核验</h3><p>旧接口同步截止:{{d.cutoffAt|date:'yyyy-MM-dd HH:mm':'+0800'}}。{{d.monthClosed?'已覆盖整月':'当前仅可生成截止时间内的草稿,整月同步后才能审核'}}</p>
 <p>业务编号:{{d.complete?'对账一致':'凭据缺失或记录缺失,暂不能确认'}} · 待核对 {{d.pending}} 条</p>
@@ -14,7 +14,7 @@ interface Cutoff {month:string;version:string;runId:string;cutoffAt:string;compl
 <label>核验依据<textarea [(ngModel)]="reason" rows="2" placeholder="说明核对依据,至少 2 个字"></textarea></label>
 <div class="differences">@for(row of d.conflicts;track row.id){<details><summary>{{row.student||row.legacyKey}} · {{row.course}} · {{row.cosmetic?'不影响计薪':row.resolved?'已确认':'待核对'}}</summary><p>{{sourceName(row.dataset)}}编号 {{row.legacyKey}} · {{row.date||'日期待核对'}}</p><p>以下为新系统当前记录,请与旧系统同编号记录核对后填写依据。</p><dl>@for(field of row.fields;track field){<dt>{{fieldName(field)}}</dt><dd>{{fieldValue(row,field)}}</dd>}@empty{<dt>差异说明</dt><dd>来源没有提供具体字段,请完整核对该记录。</dd>}</dl>@if(!row.resolved&&canConfirm()){<button type="button" [disabled]="busy()||reason.trim().length<2" (click)="confirm(row)">确认此记录保留新系统</button>}</details>}</div>
 @if(canConfirm()){<button type="button" [disabled]="busy()||!d.complete||d.pending>0||reason.trim().length<2" (click)="confirm()">确认该月份截止来源</button>}@if(d.approved){<p role="status">来源已确认;数据变化后确认将自动失效。</p>}
-}</section>`,styles:[`.cutoff{padding:20px;border:1px solid #d8e5e1;border-radius:16px;background:#fff;margin:16px 0;color:#28443a}button{min-height:44px;padding:8px 16px;border:1px solid #197664;background:#eff8f5;border-radius:8px;color:#155c4e;cursor:pointer}button:disabled{opacity:.5;cursor:default}label{display:grid;gap:8px;margin:12px 0}textarea{padding:10px;border:1px solid #b8ccc4;border-radius:8px}dl{display:grid;grid-template-columns:minmax(100px,1fr) 2fr;gap:8px;margin:12px 0}dt{color:#567168}dd{margin:0;overflow-wrap:anywhere}details{padding:10px 0;border-bottom:1px solid #edf2f0}.differences{max-height:480px;overflow:auto;margin:12px 0}.source{display:inline-block;margin-right:20px}`]})
+}</section>`,styles:[`.cutoff{padding:20px;border:1px solid #d8e5e1;border-radius:16px;background:#fff;margin:16px 0;color:#28443a}.cutoff.is-collapsed{display:flex;justify-content:flex-end;margin:12px 0 0;padding:0;border:0;background:transparent}button{min-height:44px;padding:8px 16px;border:1px solid #197664;background:#eff8f5;border-radius:8px;color:#155c4e;cursor:pointer}button:disabled{opacity:.5;cursor:default}label{display:grid;gap:8px;margin:12px 0}textarea{padding:10px;border:1px solid #b8ccc4;border-radius:8px}dl{display:grid;grid-template-columns:minmax(100px,1fr) 2fr;gap:8px;margin:12px 0}dt{color:#567168}dd{margin:0;overflow-wrap:anywhere}details{padding:10px 0;border-bottom:1px solid #edf2f0}.differences{max-height:480px;overflow:auto;margin:12px 0}.source{display:inline-block;margin-right:20px}`]})
 export class PayrollCutoffComponent {
  readonly month=input.required<string>();readonly canConfirm=input(false);readonly changed=output<void>();readonly busy=signal(false);readonly error=signal('');readonly data=signal<Cutoff|null>(null);reason='';private readonly ops=inject(OperationsService);
  sourceName(value:string):string{return ({'54':'预约','59':'完课','60':'复习'} as Record<string,string>)[value]||'来源记录';}