瀏覽代碼

style: enlarge schedule metrics and fill planner width

彭峰 4 天之前
父節點
當前提交
9d5d6de45f

+ 42 - 20
projects/xiaoshu-admin/src/app/pages/operations-schedule-layout.scss

@@ -5,16 +5,20 @@ app-operations-schedule.schedule-ui {
 .page-header p { margin: 0; color: #347a64; font-size: 12px; font-weight: 800; letter-spacing: .03em; }
 .page-header p { margin: 0; color: #347a64; font-size: 12px; font-weight: 800; letter-spacing: .03em; }
 .summary-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-bottom: 14px; }
 .summary-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-bottom: 14px; }
 .summary-card {
 .summary-card {
+  display: grid;
+  grid-template-columns: minmax(0, 1fr) auto;
+  grid-template-rows: auto 1fr;
+  align-items: center;
+  column-gap: 12px;
   min-width: 0;
   min-width: 0;
-  min-height: 112px;
-  align-content: space-between;
+  min-height: 124px;
   padding: 16px 18px;
   padding: 16px 18px;
   border-color: #dbe8e2;
   border-color: #dbe8e2;
   box-shadow: 0 6px 18px rgba(31, 67, 55, .035);
   box-shadow: 0 6px 18px rgba(31, 67, 55, .035);
 }
 }
-.summary-card span { color: #657b70; font-weight: 750; }
-.summary-card strong { color: #21493c; font-size: clamp(26px, 2.2vw, 34px); line-height: 1; font-variant-numeric: tabular-nums; }
-.summary-card small { min-height: 16px; line-height: 1.35; }
+.summary-card span { grid-column: 1; grid-row: 1; color: #657b70; font-weight: 750; }
+.summary-grid .summary-card strong { grid-column: 2; grid-row: 1 / span 2; justify-self: end; align-self: center; color: #21493c; font-size: clamp(40px, 3vw, 56px) !important; line-height: 1; font-variant-numeric: tabular-nums; white-space: nowrap; }
+.summary-card small { grid-column: 1; grid-row: 2; align-self: end; min-height: 16px; line-height: 1.35; }
 .summary-card-primary {
 .summary-card-primary {
   position: relative;
   position: relative;
   isolation: isolate;
   isolation: isolate;
@@ -56,22 +60,26 @@ app-operations-schedule.schedule-ui {
 .planner-heading strong { color: #224b3e; font-size: 16px; }
 .planner-heading strong { color: #224b3e; font-size: 16px; }
 .planner-heading span, .planner-heading small { color: #74867e; font-size: 12px; }
 .planner-heading span, .planner-heading small { color: #74867e; font-size: 12px; }
 .planner-heading small { white-space: nowrap; font-variant-numeric: tabular-nums; }
 .planner-heading small { white-space: nowrap; font-variant-numeric: tabular-nums; }
+.planner-meta { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 8px 14px; }
+.planner-meta .latest-data { min-height: 40px; padding: 8px 12px; border: 1px solid #dce6e1; border-radius: 11px; color: #315b4c; background: #f8fbf9; font: inherit; font-size: 12px; font-weight: 750; white-space: nowrap; cursor: pointer; }
+.planner-meta .latest-data:hover { border-color: #9ec3b2; background: #edf7f1; }
 .schedule-planner .toolbar {
 .schedule-planner .toolbar {
-  flex-wrap: wrap;
-  gap: 10px;
+  display: grid;
+  grid-template-columns: minmax(170px, .8fr) 44px minmax(210px, 1fr) 44px minmax(230px, 1fr);
+  align-items: end;
+  gap: 12px;
   margin: 0;
   margin: 0;
   padding: 14px 18px 16px;
   padding: 14px 18px 16px;
   border: 0;
   border: 0;
   border-radius: 0;
   border-radius: 0;
   box-shadow: none;
   box-shadow: none;
 }
 }
-.schedule-planner .toolbar > .segmented { width: 190px; }
-.schedule-planner .toolbar .date-filter { flex: 0 0 200px; width: 200px; }
-.schedule-planner .toolbar .coach-filter { flex: 0 1 300px; width: 300px; min-width: 230px; max-width: 300px; margin-left: auto; }
+.schedule-planner .toolbar > .segmented { width: 100%; }
+.schedule-planner .toolbar > button { width: 44px; padding-inline: 0; }
+.schedule-planner .toolbar .date-filter { width: 100%; min-width: 0; }
+.schedule-planner .toolbar .coach-filter { width: 100%; min-width: 0; max-width: none; margin-left: 0; }
 .schedule-planner .schedule-filterbar {
 .schedule-planner .schedule-filterbar {
-  flex-wrap: wrap;
-  justify-content: space-between;
-  gap: 10px 12px;
+  display: block;
   margin: 0;
   margin: 0;
   padding: 14px 18px 16px;
   padding: 14px 18px 16px;
   border: 0;
   border: 0;
@@ -79,8 +87,11 @@ app-operations-schedule.schedule-ui {
   border-radius: 0 0 18px 18px;
   border-radius: 0 0 18px 18px;
   background: #fbfdfc;
   background: #fbfdfc;
 }
 }
-.schedule-planner .schedule-search { flex: 0 1 430px; width: min(100%, 430px); max-width: 430px; }
-.schedule-planner .status-filter { flex: 0 0 220px; }
+.planner-filter-fields { display: grid; grid-template-columns: minmax(240px, 1.2fr) minmax(190px, 1fr) minmax(200px, .85fr) minmax(140px, .55fr); align-items: end; gap: 14px; width: 100%; }
+.planner-filter-fields .filter-actions { width: 100%; }
+.planner-filter-fields .filter-actions button { flex: 1; }
+.planner-filter-fields .filter-result { justify-self: end; }
+.schedule-planner .schedule-search, .schedule-planner .status-filter { width: 100%; min-width: 0; max-width: none; }
 .schedule-planner .filter-result { margin-left: 0; }
 .schedule-planner .filter-result { margin-left: 0; }
 .schedule-planner .schedule-loading { margin: 0 18px 14px; }
 .schedule-planner .schedule-loading { margin: 0 18px 14px; }
 
 
@@ -110,20 +121,31 @@ app-operations-schedule.schedule-ui {
   .summary-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
   .summary-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
 }
 }
 
 
+@media (max-width: 1200px) and (min-width: 701px) {
+  .schedule-planner .toolbar { grid-template-columns: 190px 44px minmax(190px, 1fr) 44px; }
+  .schedule-planner .toolbar .coach-filter { grid-column: 1 / -1; }
+  .planner-filter-fields { grid-template-columns: minmax(220px, 1fr) minmax(180px, .7fr); }
+  .planner-filter-fields .filter-actions { grid-column: 1; }
+  .planner-filter-fields .filter-result { grid-column: 2; justify-self: end; }
+}
+
 @media (max-width: 700px) {
 @media (max-width: 700px) {
   .summary-grid { gap: 10px; margin-bottom: 12px; }
   .summary-grid { gap: 10px; margin-bottom: 12px; }
   .summary-card { min-height: 96px; padding: 14px; }
   .summary-card { min-height: 96px; padding: 14px; }
   .summary-card:first-child { grid-column: 1 / -1; }
   .summary-card:first-child { grid-column: 1 / -1; }
+  .summary-grid .summary-card strong { font-size: clamp(34px, 8vw, 44px) !important; }
   .summary-card:last-child { min-height: 82px; }
   .summary-card:last-child { min-height: 82px; }
-  .summary-card:last-child strong { grid-column: auto; grid-row: auto; font-size: 26px; }
-  .summary-card:last-child span, .summary-card:last-child small { grid-column: auto; }
+  .summary-grid .summary-card:last-child strong { font-size: clamp(36px, 8vw, 44px) !important; }
   .planner-heading { align-items: flex-start; flex-wrap: wrap; padding: 13px 14px 0; }
   .planner-heading { align-items: flex-start; flex-wrap: wrap; padding: 13px 14px 0; }
   .planner-heading > div:first-child { display: grid; gap: 3px; }
   .planner-heading > div:first-child { display: grid; gap: 3px; }
-  .schedule-planner .toolbar { padding: 12px 14px; }
+  .planner-meta { justify-content: flex-start; }
+  .schedule-planner .toolbar { grid-template-columns: 44px minmax(0, 1fr) 44px; justify-content: stretch; padding: 12px 14px; }
   .schedule-planner .toolbar > .segmented { width: 100%; }
   .schedule-planner .toolbar > .segmented { width: 100%; }
   .schedule-planner .toolbar .date-filter, .schedule-planner .toolbar .coach-filter { width: 100%; min-width: 0; max-width: none; margin-left: 0; }
   .schedule-planner .toolbar .date-filter, .schedule-planner .toolbar .coach-filter { width: 100%; min-width: 0; max-width: none; margin-left: 0; }
-  .schedule-planner .schedule-filterbar { grid-template-columns: minmax(0, 1fr); padding: 12px 14px 14px; }
-  .schedule-planner .schedule-search, .schedule-planner .status-filter { width: 100%; max-width: none; min-width: 0; }
+  .schedule-planner .schedule-filterbar { padding: 12px 14px 14px; }
+  .planner-filter-fields { grid-template-columns: minmax(0, 1fr); gap: 10px; }
+  .planner-filter-fields .filter-actions { grid-column: 1; }
+  .planner-filter-fields .filter-result { justify-self: start; }
   .schedule-planner .filter-result { margin-left: 0; }
   .schedule-planner .filter-result { margin-left: 0; }
   .schedule-workflow > summary { align-items: flex-start; padding: 12px 14px; }
   .schedule-workflow > summary { align-items: flex-start; padding: 12px 14px; }
   .schedule-workflow > summary > span:first-child { display: grid; gap: 3px; }
   .schedule-workflow > summary > span:first-child { display: grid; gap: 3px; }

+ 3 - 2
projects/xiaoshu-admin/src/app/pages/operations-schedule.component.html

@@ -19,7 +19,7 @@
 </section>
 </section>
 
 
 <section class="schedule-planner" aria-label="课表日期与筛选">
 <section class="schedule-planner" aria-label="课表日期与筛选">
-  <div class="planner-heading"><div><strong>查看课表</strong><span>先选日期与陪练,再按学员或状态定位预约</span></div><small>数据库更新 {{(data()?.refreshedAt|date:'MM-dd HH:mm:ss')||'—'}}</small></div>
+  <div class="planner-heading"><div><strong>查看课表</strong><span>先选日期与陪练,再按学员或状态定位预约</span></div><div class="planner-meta"><small>数据库更新 {{(data()?.refreshedAt|date:'MM-dd HH:mm:ss')||'—'}}</small>@if(data()?.dataDateTo){<button class="latest-data" type="button" (click)="showLatest()">最近有数据:{{data()?.dataDateTo}}</button>}</div></div>
 <section class="toolbar">
 <section class="toolbar">
   <div class="segmented">
   <div class="segmented">
     <button type="button" [class.active]="view()==='day'" (click)="view.set('day');load()">日</button>
     <button type="button" [class.active]="view()==='day'" (click)="view.set('day');load()">日</button>
@@ -30,7 +30,6 @@
   <app-operations-date-filter class="date-filter" label="日期" [value]="date()" (valueChange)="updateDraftDate($event)" />
   <app-operations-date-filter class="date-filter" label="日期" [value]="date()" (valueChange)="updateDraftDate($event)" />
   <button type="button" aria-label="下一时间段" (click)="move(view()==='week'?7:1)"><lucide-icon [img]="icons.ChevronRight" [size]="17" /></button>
   <button type="button" aria-label="下一时间段" (click)="move(view()==='week'?7:1)"><lucide-icon [img]="icons.ChevronRight" [size]="17" /></button>
   <label class="coach-filter"><span>陪练筛选</span><select [ngModel]="coachFilter()" (ngModelChange)="coachFilter.set(+$event);load()"><option [ngValue]="0">全部陪练</option>@for(coach of data()?.coaches||[];track coach.userId){<option [ngValue]="coach.userId">{{coach.displayName}}</option>}</select></label>
   <label class="coach-filter"><span>陪练筛选</span><select [ngModel]="coachFilter()" (ngModelChange)="coachFilter.set(+$event);load()"><option [ngValue]="0">全部陪练</option>@for(coach of data()?.coaches||[];track coach.userId){<option [ngValue]="coach.userId">{{coach.displayName}}</option>}</select></label>
-  @if(data()?.dataDateTo){<button class="latest-data" type="button" (click)="showLatest()">最近有数据:{{data()?.dataDateTo}}</button>}
 </section>
 </section>
 
 
 @if(loading()){
 @if(loading()){
@@ -45,6 +44,7 @@
 }
 }
 
 
 <section class="schedule-filterbar" aria-label="排课记录筛选">
 <section class="schedule-filterbar" aria-label="排课记录筛选">
+  <div class="planner-filter-fields">
   <label class="schedule-search"><span>搜索排课</span><span class="filter-control"><lucide-icon [img]="icons.Search" [size]="16"/><input type="search" [ngModel]="scheduleSearchInput()" (ngModelChange)="scheduleSearchInput.set($event)" (keydown.enter)="applyScheduleFilters()" placeholder="学员、陪练、课程或业务ID"/></span></label>
   <label class="schedule-search"><span>搜索排课</span><span class="filter-control"><lucide-icon [img]="icons.Search" [size]="16"/><input type="search" [ngModel]="scheduleSearchInput()" (ngModelChange)="scheduleSearchInput.set($event)" (keydown.enter)="applyScheduleFilters()" placeholder="学员、陪练、课程或业务ID"/></span></label>
   <label class="status-filter"><span>业务状态</span><select [ngModel]="statusFilterInput()" (ngModelChange)="statusFilterInput.set($event)"><option value="all">全部状态</option><option value="upcoming">待开始</option><option value="live">上课中</option><option value="coach-ended">陪练已结束</option><option value="confirmed">学员已确认</option><option value="completed">已完课</option><option value="archived">已归档</option><option value="overdue">逾期未完成</option><option value="unverified">状态待核对</option><option value="cancelled">已取消</option><option value="unassigned">待分配 / 账号异常</option></select></label>
   <label class="status-filter"><span>业务状态</span><select [ngModel]="statusFilterInput()" (ngModelChange)="statusFilterInput.set($event)"><option value="all">全部状态</option><option value="upcoming">待开始</option><option value="live">上课中</option><option value="coach-ended">陪练已结束</option><option value="confirmed">学员已确认</option><option value="completed">已完课</option><option value="archived">已归档</option><option value="overdue">逾期未完成</option><option value="unverified">状态待核对</option><option value="cancelled">已取消</option><option value="unassigned">待分配 / 账号异常</option></select></label>
   <div class="filter-actions">
   <div class="filter-actions">
@@ -52,6 +52,7 @@
     <button class="reset-button" type="button" (click)="clearScheduleFilters()" [disabled]="searching()"><lucide-icon [img]="icons.RotateCcw" [size]="15" />重置</button>
     <button class="reset-button" type="button" (click)="clearScheduleFilters()" [disabled]="searching()"><lucide-icon [img]="icons.RotateCcw" [size]="15" />重置</button>
   </div>
   </div>
   <div class="filter-result" role="status"><strong>{{visibleAppointments().length}}</strong><span>条筛选结果</span><small>当前范围共 {{data()?.appointments?.length||0}} 条</small></div>
   <div class="filter-result" role="status"><strong>{{visibleAppointments().length}}</strong><span>条筛选结果</span><small>当前范围共 {{data()?.appointments?.length||0}} 条</small></div>
+  </div>
 </section>
 </section>
 </section>
 </section>