ソースを参照

Merge branch 'master' of http://git.fmode.cn:3000/nkkj/yss-project

徐福静0235668 1 日 前
コミット
f77bc0cb4c

+ 66 - 1
src/app/pages/customer-service/project-detail/project-detail.html

@@ -434,4 +434,69 @@
       </button>
     </div>
   </div>
-</div>
+</div>
+
+<!-- 文件标签内容 -->
+@if (activeTab() === 'files') {
+  <div class="tab-content">
+    <div class="files-header">
+      <h4>项目文件</h4>
+      <div class="files-actions">
+        <button class="primary-btn btn-hover-effect" (click)="openRenderPreview()" [disabled]="renderImages().length === 0">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
+            <circle cx="8.5" cy="8.5" r="1.5"></circle>
+            <polyline points="21 15 16 10 5 21"></polyline>
+          </svg>
+          <span>查看渲染图</span>
+        </button>
+      </div>
+    </div>
+    <div class="files-list">
+      <div class="file-item" *ngFor="let f of files()">
+        <div class="file-icon">{{ f.type === 'image' ? 'IMG' : 'DOC' }}</div>
+        <div class="file-info">
+          <div class="file-name">{{ f.name }}</div>
+          <div class="file-meta">{{ f.size }} · 由 {{ f.uploadedBy }} 于 {{ formatDate(f.uploadedAt) }} 上传</div>
+        </div>
+        <div class="file-actions">
+          <button class="link" (click)="previewFile(f)">预览</button>
+          <button class="link" (click)="downloadFile(f)">下载</button>
+        </div>
+      </div>
+    </div>
+  </div>
+}
+
+<!-- 只读渲染图预览弹窗 -->
+@if (showRenderPreviewModal) {
+  <div class="modal-backdrop" (click)="closeRenderPreview()"></div>
+  <div class="modal" role="dialog" aria-modal="true">
+    <div class="modal-header">
+      <h3>渲染图预览</h3>
+      <button class="close-button" (click)="closeRenderPreview()" aria-label="关闭">×</button>
+    </div>
+    <div class="modal-body">
+      @if (renderImages().length > 0) {
+        <div class="thumb-list">
+          @for (img of renderImages(); track img.id) {
+            <div class="thumb-item">
+              <img [src]="img.url" [alt]="img.name" />
+              <div class="thumb-meta">
+                <div class="name">{{ img.name }}</div>
+                <div class="sub">{{ img.size }} · {{ formatDate(img.uploadedAt) }}</div>
+              </div>
+            </div>
+          }
+        </div>
+      } @else {
+        <div class="empty">
+          <p>暂无可预览的渲染图</p>
+        </div>
+      }
+    </div>
+    <div class="modal-footer">
+      <button class="secondary-btn" (click)="closeRenderPreview()">关闭</button>
+    </div>
+  </div>
+}

+ 109 - 0
src/app/pages/customer-service/project-detail/project-detail.scss

@@ -2594,4 +2594,113 @@ $text-light: $text-tertiary;
       }
     }
   }
+}
+
+/* 只读渲染图预览弹窗 + 缩略图样式(customer-service/project-detail) */
+.project-detail-container {
+  .modal-backdrop {
+    position: fixed;
+    inset: 0;
+    background: rgba(0,0,0,0.45);
+    backdrop-filter: saturate(160%) blur(6px);
+    -webkit-backdrop-filter: saturate(160%) blur(6px);
+    z-index: 999;
+  }
+
+  .modal {
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    width: min(920px, 92vw);
+    max-height: 80vh;
+    background: $bg-white;
+    border: 1px solid $border-color;
+    border-radius: $border-radius;
+    box-shadow: 0 12px 28px rgba(0,0,0,0.15);
+    display: flex;
+    flex-direction: column;
+    z-index: 1000;
+  }
+
+  .modal-header {
+    padding: 14px 16px;
+    border-bottom: 1px solid $border-color;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+
+    h3 {
+      margin: 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: $text-primary;
+    }
+  }
+
+  .modal-body {
+    padding: 16px;
+    overflow: auto;
+    background: $bg-white;
+  }
+
+  .modal-footer {
+    padding: 12px 16px;
+    border-top: 1px solid $border-color;
+    display: flex;
+    justify-content: flex-end;
+    gap: 8px;
+    background: $bg-white;
+  }
+
+  .close-button {
+    background: transparent;
+    border: none;
+    font-size: 18px;
+    line-height: 1;
+    cursor: pointer;
+    color: $text-secondary;
+
+    &:hover { color: $text-primary; }
+  }
+
+  /* 缩略图网格 */
+  .thumb-list {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+    gap: 12px;
+  }
+
+  .thumb-item {
+    border: 1px solid $border-color;
+    border-radius: $border-radius;
+    overflow: hidden;
+    background: $bg-white;
+    box-shadow: 0 1px 2px rgba(0,0,0,0.04);
+  }
+
+  .thumb-item img {
+    width: 100%;
+    height: 120px;
+    object-fit: cover;
+    display: block;
+  }
+
+  .thumb-meta {
+    padding: 8px;
+
+    .name {
+      font-size: 13px;
+      color: $text-primary;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+
+    .sub {
+      font-size: 12px;
+      color: $text-secondary;
+      margin-top: 2px;
+    }
+  }
 }

+ 14 - 0
src/app/pages/customer-service/project-detail/project-detail.ts

@@ -207,6 +207,10 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
   showComplaintWarning = false
   showRefundRequest = false
   
+  // 渲染图只读预览弹窗状态与数据
+  showRenderPreviewModal = false;
+  renderImages = computed(() => this.files().filter(f => f.type === 'image'));
+  
   // 项目状态颜色映射
   statusColors = {
     '进行中': 'primary',
@@ -653,6 +657,16 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
     // 实际应用中,这里会打开文件预览
   }
   
+  // 打开渲染图只读预览
+  openRenderPreview(): void {
+    this.showRenderPreviewModal = true;
+  }
+
+  // 关闭渲染图只读预览
+  closeRenderPreview(): void {
+    this.showRenderPreviewModal = false;
+  }
+  
   // 获取项目状态的CSS类名
   getProjectStatusClass(status: string | null | undefined): string {
     if (!status) return 'status-default';

+ 161 - 117
src/app/pages/designer/dashboard/dashboard.html

@@ -12,148 +12,192 @@
 
   <!-- 主要内容区域 - 工作台 -->
   <div *ngIf="activeDashboard === 'main'" class="dashboard-main">
-    <!-- 核心信息卡片区域 - 每行列3张卡片 -->
-    <section class="core-cards-section">
-      <div class="cards-grid">
-        <!-- 紧急任务卡片 -->
-        <div *ngFor="let task of urgentTasks" class="core-card urgent-card">
-          <div class="card-header">
-            <span class="card-badge urgent">紧急</span>
-            <h3>{{ task.title }}</h3>
-          </div>
-          <div class="card-content">
-            <p class="project-name">项目: {{ task.projectName }}</p>
-            <p class="countdown">剩余: {{ getTaskCountdown(task.id) }}</p>
-          </div>
-          <div class="card-actions">
-            <button [routerLink]="['/designer/project-detail', task.projectId]" class="btn-primary">
-              立即处理
-            </button>
-          </div>
-        </div>
-        
-        <!-- 待办任务卡片 -->
-        <div *ngFor="let task of getTopTasks(6 - urgentTasks.length)" class="core-card task-card">
-          <div class="card-header">
-            <span class="card-badge" [class.overdue]="task.isOverdue">
-              {{ task.stage }}
-              <span *ngIf="task.isOverdue">/超期</span>
-            </span>
-            <h3>{{ task.title }}</h3>
+
+    <!-- 视图切换按钮 -->
+    <div class="view-toggle">
+      <button class="toggle-btn" (click)="toggleView()">
+        {{ viewMode === 'card' ? '切换为列表' : '切换为卡片' }}
+      </button>
+    </div>
+
+    <!-- 卡片视图 -->
+    @if (viewMode === 'card') {
+      <!-- 核心信息卡片区域 - 每行列3张卡片 -->
+      <section class="core-cards-section">
+        <div class="cards-grid">
+          <!-- 紧急任务卡片 -->
+          <div *ngFor="let task of urgentTasks" class="core-card urgent-card">
+            <div class="card-header">
+              <span class="card-badge urgent">紧急</span>
+              <h3>{{ task.title }}</h3>
+            </div>
+            <div class="card-content">
+              <p class="project-name">项目: {{ task.projectName }}</p>
+              <p class="countdown">剩余: {{ getTaskCountdown(task.id) }}</p>
+            </div>
+            <div class="card-actions">
+              <button [routerLink]="['/designer/project-detail', task.projectId]" class="btn-primary">
+                立即处理
+              </button>
+            </div>
           </div>
-          <div class="card-content">
-            <p class="project-name">项目: {{ task.projectName }}</p>
-            <p class="deadline" [class.overdue]="task.isOverdue">
-              截止: {{ task.deadline | date:'yyyy-MM-dd HH:mm' }}
-            </p>
-            
-            <!-- 进度条 -->
-            <div class="task-progress" *ngIf="task.stage !== '投诉处理' && !task.isCompleted">
-              <div class="progress-bar">
-                <div class="progress-fill" [style.width]="getTaskStageProgress(task.id) + '%'">
+          
+          <!-- 待办任务卡片 -->
+          <div *ngFor="let task of getTopTasks(6 - urgentTasks.length)" class="core-card task-card">
+            <div class="card-header">
+              <span class="card-badge" [class.overdue]="task.isOverdue">
+                {{ task.stage }}
+                <span *ngIf="task.isOverdue">/超期</span>
+              </span>
+              <h3>{{ task.title }}</h3>
+            </div>
+            <div class="card-content">
+              <p class="project-name">项目: {{ task.projectName }}</p>
+              <p class="deadline" [class.overdue]="task.isOverdue">
+                截止: {{ task.deadline | date:'yyyy-MM-dd HH:mm' }}
+              </p>
+              
+              <!-- 进度条 -->
+              <div class="task-progress" *ngIf="task.stage !== '投诉处理' && !task.isCompleted">
+                <div class="progress-bar">
+                  <div class="progress-fill" [style.width]="getTaskStageProgress(task.id) + '%'">
+                  </div>
                 </div>
+                <p class="progress-text">{{ getTaskStageProgress(task.id) }}%</p>
               </div>
-              <p class="progress-text">{{ getTaskStageProgress(task.id) }}%</p>
+            </div>
+            <div class="card-actions">
+              <button *ngIf="!task.isCompleted" (click)="markTaskAsCompleted(task.id)" class="btn-secondary">
+                标记完成
+              </button>
+              <button [routerLink]="['/designer/project-detail', task.projectId]" class="btn-primary">
+                查看详情
+              </button>
             </div>
           </div>
-          <div class="card-actions">
-            <button *ngIf="!task.isCompleted" (click)="markTaskAsCompleted(task.id)" class="btn-secondary">
-              标记完成
-            </button>
-            <button [routerLink]="['/designer/project-detail', task.projectId]" class="btn-primary">
-              查看详情
-            </button>
+          
+          <!-- 项目饱和度卡片 -->
+          <div class="core-card workload-card" *ngIf="(urgentTasks.length + tasks.length) < 6">
+            <div class="card-header">
+              <span class="card-badge workload">饱和度</span>
+              <h3>当前工作量</h3>
+            </div>
+            <div class="card-content">
+              <div class="workload-indicator">
+                <div class="workload-circle" [style.background]="getWorkloadColor()">
+                  <span class="workload-percentage">{{ workloadPercentage }}%</span>
+                </div>
+              </div>
+              <p class="workload-status">{{ getWorkloadStatus() }}</p>
+            </div>
+            <div class="card-actions">
+              <button class="btn-secondary" (click)="switchDashboard('personal')">
+                查看详情
+              </button>
+            </div>
           </div>
         </div>
-        
-        <!-- 项目饱和度卡片 -->
-        <div class="core-card workload-card" *ngIf="(urgentTasks.length + tasks.length) < 6">
-          <div class="card-header">
-            <span class="card-badge workload">饱和度</span>
-            <h3>当前工作量</h3>
+      </section>
+
+      <!-- 附加信息区域 -->
+      <section class="additional-info-section">
+        <!-- 待处理反馈区域 -->
+        <div class="info-column" *ngIf="pendingFeedbacks.length > 0">
+          <div class="section-header">
+            <h2>待处理反馈</h2>
           </div>
-          <div class="card-content">
-            <div class="workload-indicator">
-              <div class="workload-circle" [style.background]="getWorkloadColor()">
-                <span class="workload-percentage">{{ workloadPercentage }}%</span>
+          
+          <div class="feedback-list">
+            <div *ngFor="let item of pendingFeedbacks" class="feedback-item">
+              <div class="feedback-content">
+                <p class="feedback-title">⚠️ {{ item.task.title }} - 客户反馈</p>
+                <p class="feedback-project">项目: {{ item.task.projectName }}</p>
+                <p class="feedback-summary">反馈: {{ !item.feedback.isSatisfied ? '不满意' : '满意' }}</p>
+              </div>
+              <div class="feedback-actions">
+                <button (click)="handleFeedback(item.task.id)" class="btn-handle-feedback">
+                  处理反馈
+                </button>
               </div>
             </div>
-            <p class="workload-status">{{ getWorkloadStatus() }}</p>
-          </div>
-          <div class="card-actions">
-            <button class="btn-secondary" (click)="switchDashboard('personal')">
-              查看详情
-            </button>
           </div>
         </div>
-      </div>
-    </section>
 
-    <!-- 附加信息区域 -->
-    <section class="additional-info-section">
-      <!-- 待处理反馈区域 -->
-      <div class="info-column" *ngIf="pendingFeedbacks.length > 0">
-        <div class="section-header">
-          <h2>待处理反馈</h2>
-        </div>
-        
-        <div class="feedback-list">
-          <div *ngFor="let item of pendingFeedbacks" class="feedback-item">
-            <div class="feedback-content">
-              <p class="feedback-title">⚠️ {{ item.task.title }} - 客户反馈</p>
-              <p class="feedback-project">项目: {{ item.task.projectName }}</p>
-              <p class="feedback-summary">反馈: {{ !item.feedback.isSatisfied ? '不满意' : '满意' }}</p>
-            </div>
-            <div class="feedback-actions">
-              <button (click)="handleFeedback(item.task.id)" class="btn-handle-feedback">
-                处理反馈
-              </button>
+        <!-- 代班信息区域 -->
+        <div class="info-column" *ngIf="shiftTasks.length > 0">
+          <div class="section-header">
+            <h2>👥 代班信息</h2>
+            <button class="add-shift-btn" (click)="openShiftModal()">
+              添加代班任务
+            </button>
+          </div>
+          <div class="shift-list">
+            <div class="shift-item" *ngFor="let shift of shiftTasks">
+              <div class="shift-header">
+                <div class="shift-project">{{ shift.projectName }}</div>
+                <div class="shift-priority" [class.priority-high]="shift.priority === '高'" [class.priority-medium]="shift.priority === '中'" [class.priority-low]="shift.priority === '低'">
+                  {{ shift.priority }}级
+                </div>
+              </div>
+              <div class="shift-details">
+                <div class="shift-task">{{ shift.taskDescription }}</div>
+                <div class="shift-time">代班时间: {{ shift.shiftDate }}</div>
+              </div>
             </div>
           </div>
         </div>
-      </div>
 
-      <!-- 代班信息区域 -->
-      <div class="info-column" *ngIf="shiftTasks.length > 0">
-        <div class="section-header">
-          <h2>👥 代班信息</h2>
-          <button class="add-shift-btn" (click)="openShiftModal()">
-            添加代班任务
-          </button>
-        </div>
-        <div class="shift-list">
-          <div class="shift-item" *ngFor="let shift of shiftTasks">
-            <div class="shift-header">
-              <div class="shift-project">{{ shift.projectName }}</div>
-              <div class="shift-priority" [class.priority-high]="shift.priority === '高'" [class.priority-medium]="shift.priority === '中'" [class.priority-low]="shift.priority === '低'">
-                {{ shift.priority }}级
+        <!-- 时间预警区域 -->
+        <div class="info-column" *ngIf="overdueTasks.length > 0">
+          <div class="section-header">
+            <h2>⏰ 时间预警</h2>
+          </div>
+          
+          <div class="warning-list">
+            <div *ngFor="let task of overdueTasks" class="warning-item">
+              <div class="warning-content">
+                <p class="warning-title">{{ task.title }} - 已超期</p>
+                <p class="warning-detail">项目: {{ task.projectName }}</p>
               </div>
             </div>
-            <div class="shift-details">
-              <div class="shift-task">{{ shift.taskDescription }}</div>
-              <div class="shift-time">代班时间: {{ shift.shiftDate }}</div>
-            </div>
           </div>
         </div>
-      </div>
+      </section>
+    }
 
-      <!-- 时间预警区域 -->
-      <div class="info-column" *ngIf="overdueTasks.length > 0">
-        <div class="section-header">
-          <h2>⏰ 时间预警</h2>
+    <!-- 列表视图 -->
+    @if (viewMode === 'list') {
+      <section class="list-section">
+        <div class="list-header">
+          <div class="col urgency-col">紧急度</div>
+          <div class="col project-col">项目</div>
+          <div class="col title-col">任务</div>
+          <div class="col stage-col">阶段</div>
+          <div class="col deadline-col">截止时间</div>
+          <div class="col left-col">剩余</div>
+          <div class="col actions-col">操作</div>
         </div>
-        
-        <div class="warning-list">
-          <div *ngFor="let task of overdueTasks" class="warning-item">
-            <div class="warning-content">
-              <p class="warning-title">{{ task.title }} - 已超期</p>
-              <p class="warning-detail">项目: {{ task.projectName }}</p>
+        <div class="list-body">
+          @for (task of getTasksSortedByUrgency(); track task.id) {
+            <div class="list-row">
+              <div class="col urgency-col">
+                <span class="urgency-dot" [ngClass]="getUrgencyClass(task)"></span>
+                <span class="urgency-text">{{ getUrgencyLevel(task) }}</span>
+              </div>
+              <div class="col project-col">{{ task.projectName }}</div>
+              <div class="col title-col">{{ task.title }}</div>
+              <div class="col stage-col">{{ task.stage }}</div>
+              <div class="col deadline-col">{{ task.deadline | date:'yyyy-MM-dd HH:mm' }}</div>
+              <div class="col left-col">{{ getListTimeLeft(task) }}</div>
+              <div class="col actions-col">
+                <button class="btn-link" [routerLink]="['/designer/project-detail', task.projectId]">详情</button>
+                <button class="btn-link" *ngIf="!task.isCompleted" (click)="markTaskAsCompleted(task.id)">完成</button>
+              </div>
             </div>
-          </div>
+          }
         </div>
-      </div>
-    </section>
-
+      </section>
+    }
 
   </div>
 

+ 747 - 352
src/app/pages/designer/dashboard/dashboard.scss

@@ -101,6 +101,105 @@
   gap: 24px;
 }
 
+/* 视图切换按钮样式 */
+.view-toggle {
+  display: flex;
+  justify-content: flex-end;
+  margin-bottom: 8px;
+
+  .toggle-btn {
+    background: $ios-card-background;
+    color: $ios-text-primary;
+    border: 1px solid $ios-border;
+    border-radius: $ios-radius-md;
+    padding: 8px 14px;
+    cursor: pointer;
+    font-family: $ios-font-family;
+    transition: all .2s ease;
+
+    &:hover {
+      background-color: color-mix(in srgb, $ios-background-secondary 70%, white);
+    }
+  }
+}
+
+/* 列表视图样式 */
+.list-section {
+  background: $ios-card-background;
+  border-radius: $ios-radius-lg;
+  border: 1px solid $ios-border;
+  box-shadow: $ios-shadow-card;
+  overflow: hidden;
+
+  .list-header {
+    display: grid;
+    grid-template-columns: 120px 1.2fr 1.2fr 120px 180px 120px 160px;
+    padding: 12px 16px;
+    background: $ios-background-secondary;
+    color: $ios-text-secondary;
+    font-weight: $ios-font-weight-medium;
+    border-bottom: 1px solid $ios-border;
+  }
+
+  .list-body {
+    .list-row {
+      display: grid;
+      grid-template-columns: 120px 1.2fr 1.2fr 120px 180px 120px 160px;
+      padding: 12px 16px;
+      align-items: center;
+      border-bottom: 1px solid $ios-border;
+      transition: background .2s ease;
+
+      &:hover {
+        background: rgba(0,0,0,0.02);
+      }
+
+      .col {
+        font-size: 14px;
+        color: $ios-text-primary;
+      }
+
+      .actions-col {
+        display: flex;
+        gap: 10px;
+        .btn-link {
+          background: transparent;
+          border: none;
+          color: #007aff;
+          cursor: pointer;
+          padding: 6px 8px;
+          border-radius: $ios-radius-sm;
+
+          &:hover {
+            background: rgba(0,122,255,0.08);
+          }
+        }
+      }
+
+      .urgency-col {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        .urgency-dot {
+          width: 10px;
+          height: 10px;
+          border-radius: 50%;
+          display: inline-block;
+        }
+        .urgency-text {
+          font-weight: $ios-font-weight-medium;
+        }
+      }
+
+      /* 紧急度颜色 */
+      .urgency-overdue { background: $ios-danger; }
+      .urgency-high { background: #ff9500; }
+      .urgency-medium { background: #ffcc00; }
+      .urgency-low { background: #34c759; }
+    }
+  }
+}
+
 /* 核心卡片区域样式 */
 .core-cards-section {
   margin-bottom: 30px;
@@ -1387,439 +1486,735 @@
   color: $ios-text-tertiary;
 }
 
-.shift-actions {
-  display: flex;
-  gap: $ios-spacing-md;
-}
-
-.view-detail-btn,
-.mark-complete-btn {
-  flex: 1;
-  padding: $ios-spacing-sm;
-  border: 1px solid $ios-border;
-  border-radius: $ios-radius-md;
-  font-size: $ios-font-size-xs;
-  cursor: pointer;
-  transition: $ios-feedback-tap;
-  font-weight: $ios-font-weight-medium;
-  font-family: $ios-font-family;
-}
-
-.view-detail-btn {
-  background-color: $ios-card-background;
-  color: $ios-text-secondary;
-}
-
-.view-detail-btn:hover {
-  background-color: $ios-background-tertiary;
-  color: $ios-text-primary;
-  transform: translateY(-1px);
-}
-
-.mark-complete-btn {
-  background-color: $ios-primary;
-  color: white;
-  border-color: $ios-primary;
-}
-
-.mark-complete-btn:hover {
-  background-color: #003A8C;
-  transform: translateY(-1px);
-  box-shadow: $ios-shadow-sm;
-}
-
-.view-detail-btn:active,
-.mark-complete-btn:active {
-  transform: translateY(0);
-}
-
-.reminder-modal {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background-color: rgba(0, 0, 0, 0.5);
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  z-index: 1000;
-  backdrop-filter: blur(8px);
-}
-
-.modal-content {
-  background: $ios-card-background;
-  border-radius: $ios-radius-xl;
-  padding: 24px;
-  max-width: 400px;
-  width: 90%;
-  text-align: center;
-  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
-  border: 1px solid $ios-border;
-  
-  h3 {
-    font-size: 20px;
-    color: $ios-text-primary;
-    margin: 0 0 16px 0;
-    font-family: $ios-font-family;
-  }
-  
-  p {
-    font-size: 15px;
-    color: $ios-text-secondary;
-    margin: 0 0 24px 0;
-    line-height: 1.6;
-  }
-  
-  .btn-close {
-    padding: 12px 24px;
-    border: none;
-    border-radius: $ios-radius-md;
-    background-color: $ios-primary;
-    color: white;
-    font-size: 15px;
-    cursor: pointer;
-    transition: all 0.3s ease;
-    font-weight: $ios-font-weight-medium;
-    font-family: $ios-font-family;
-    
-    &:hover {
-      background-color: color.adjust($ios-primary, $lightness: -5%);
-      transform: translateY(-1px);
-      box-shadow: $ios-shadow-md;
-    }
-    
-    &:active {
-      transform: translateY(0);
-    }
-  }
-}
-
+/* 响应式设计补充 */
 @media (max-width: 768px) {
-  .dashboard-main {
-    grid-template-columns: 1fr;
+  .dashboard-header h1 {
+    font-size: 24px;
   }
   
-  .task-actions {
+  .dashboard-nav {
     flex-direction: column;
-    
-    button {
-      width: 100%;
-    }
   }
   
-  .urgent-item,
-  .warning-item,
-  .feedback-item {
-    flex-direction: column;
-    gap: 15px;
-    
-    .urgent-actions,
-    .warning-actions,
-    .feedback-actions {
-      width: 100%;
-      
-      button {
-        width: 100%;
-      }
-    }
+  .nav-btn {
+    padding: 10px 16px;
   }
   
-  .quick-access-grid {
+  .core-cards-section .cards-grid {
     grid-template-columns: 1fr;
+    gap: 16px;
   }
-}
-
-/* 设计师代班信息表样式 */
-.shift-info-section {
-  grid-column: 1 / -1;
-}
-
-.shift-table {
-  width: 100%;
-  background-color: $ios-card-background;
-  border-radius: $ios-radius-lg;
-  overflow: hidden;
-  box-shadow: $ios-shadow-card;
-  border: 1px solid $ios-border;
-}
-
-.shift-table-header {
-  background-color: color-mix(in srgb, $ios-primary 10%, transparent);
-  padding: 16px 20px;
-  display: grid;
-  grid-template-columns: 2fr 1fr 1fr 1fr;
-  gap: 10px;
-  font-weight: $ios-font-weight-medium;
-  color: $ios-primary;
-  font-size: 15px;
-  border-bottom: 1px solid $ios-border;
-}
-
-.shift-table-row {
-  padding: 16px 20px;
-  display: grid;
-  grid-template-columns: 2fr 1fr 1fr 1fr;
-  gap: 10px;
-  border-bottom: 1px solid $ios-border;
-  align-items: center;
-  transition: background-color 0.2s ease;
   
-  &:hover {
-    background-color: color-mix(in srgb, $ios-background-secondary 50%, $ios-card-background);
+  .additional-info-section {
+    grid-template-columns: 1fr;
+    gap: 16px;
   }
   
-  &:last-child {
-    border-bottom: none;
+  .core-card {
+    padding: 16px;
   }
   
-  .project-name {
+  .core-card h3 {
     font-size: 16px;
-    color: $ios-text-primary;
-    font-weight: $ios-font-weight-medium;
   }
   
-  .priority-tag {
-    padding: 6px 12px;
-    border-radius: $ios-radius-full;
-    font-size: 13px;
-    text-align: center;
-    font-weight: $ios-font-weight-medium;
-    border: 1px solid transparent;
+  .workload-circle {
+    width: 100px !important;
+    height: 100px !important;
   }
   
-  .priority-high {
-    background-color: rgba(255, 59, 48, 0.1);
-    color: $ios-danger;
-    border-color: $ios-danger;
+  .workload-percentage {
+    font-size: 24px !important;
   }
-  
-  .priority-medium {
-    background-color: rgba(255, 149, 0, 0.1);
-    color: $ios-warning;
-    border-color: $ios-warning;
+}
+
+/* iOS风格滚动条 */
+.dashboard-container {
+  &::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
   }
   
-  .priority-low {
-    background-color: rgba(0, 122, 255, 0.1);
-    color: $ios-primary;
-    border-color: $ios-primary;
+  &::-webkit-scrollbar-track {
+    background: $ios-background-secondary;
+    border-radius: $ios-radius-full;
   }
   
-  .shift-time {
-    font-size: 15px;
-    color: $ios-text-secondary;
+  &::-webkit-scrollbar-thumb {
+    background: $ios-scrollbar-thumb;
+    border: 2px solid $ios-background-secondary;
   }
   
-  .shift-actions {
-    display: flex;
-    gap: 8px;
-    justify-content: flex-end;
+  &::-webkit-scrollbar-thumb:hover {
+    background: $ios-scrollbar-thumb-hover;
   }
-  
-  .btn-shift-action {
-    padding: 8px 14px;
-    border: none;
-    border-radius: $ios-radius-md;
-    font-size: 13px;
-    cursor: pointer;
-    transition: all 0.3s ease;
+}
+
+.section-header {
+  margin-bottom: 16px;
+  h2 {
+    font-size: 22px;
+    color: $ios-text-primary;
     font-weight: $ios-font-weight-medium;
+    display: flex;
+    align-items: center;
     font-family: $ios-font-family;
-  }
-  
-  .btn-detail {
-    background-color: $ios-primary;
-    color: white;
-    
-    &:hover {
-      background-color: #003A8C;
-      transform: translateY(-1px);
-      box-shadow: $ios-shadow-md;
-    }
-  }
-  
-  .btn-complete {
-    background-color: $ios-success;
-    color: white;
-    
-    &:hover {
-      background-color: color.adjust($ios-success, $lightness: -5%);
-      transform: translateY(-1px);
-      box-shadow: $ios-shadow-md;
+    &::before {
+      content: '';
+      display: inline-block;
+      width: 3px;
+      height: 20px;
+      background-color: $ios-primary;
+      margin-right: 10px;
+      border-radius: $ios-radius-full;
     }
   }
 }
 
-/* 个人项目饱和度样式 */
-.workload-section {
-  grid-column: 1 / -1;
+.task-list {
   display: grid;
-  grid-template-columns: 1fr 1fr;
-  gap: 24px;
+  gap: 16px;
 }
 
-.workload-info {
-  background-color: $ios-card-background;
+.task-item {
+  background: $ios-card-background;
   border-radius: $ios-radius-lg;
   padding: 20px;
   box-shadow: $ios-shadow-card;
+  transition: all 0.3s ease;
   border: 1px solid $ios-border;
-}
-
-.workload-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16px;
-  
-  h3 {
-    font-size: 18px;
-    color: $ios-text-primary;
-    font-weight: $ios-font-weight-medium;
-    margin: 0;
-    font-family: $ios-font-family;
-  }
   
-  .workload-status {
-    padding: 6px 12px;
-    border-radius: $ios-radius-full;
-    font-size: 13px;
-    font-weight: $ios-font-weight-medium;
-    border: 1px solid transparent;
-  }
-  
-  .status-idle {
-    background-color: rgba(52, 199, 89, 0.1);
-    color: $ios-success;
-    border-color: $ios-success;
-  }
-  
-  .status-normal {
-    background-color: rgba(0, 122, 255, 0.1);
-    color: $ios-primary;
-    border-color: $ios-primary;
-  }
-  
-  .status-busy {
-    background-color: rgba(255, 149, 0, 0.1);
-    color: $ios-warning;
-    border-color: $ios-warning;
-  }
-  
-  .status-overloaded {
-    background-color: rgba(255, 59, 48, 0.1);
-    color: $ios-danger;
-    border-color: $ios-danger;
+  &:hover {
+    box-shadow: $ios-shadow-lg;
+    transform: translateY(-2px);
   }
-}
-
-.workload-progress {
-  margin-bottom: 8px;
   
-  .progress-bar {
-    height: 24px;
-    background-color: $ios-background-secondary;
-    border-radius: $ios-radius-full;
-    overflow: hidden;
-    border: 1px solid $ios-border;
-    position: relative;
+  .task-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 12px;
     
-    .progress-fill {
-      height: 100%;
-      background: linear-gradient(90deg, $ios-success, $ios-primary, $ios-warning, $ios-danger);
-      transition: width 0.6s ease;
+    h3 {
+      font-size: 17px;
+      color: $ios-text-primary;
+      margin: 0;
+      font-family: $ios-font-family;
+    }
+    
+    .task-stage {
+      font-size: 13px;
+      padding: 6px 14px;
       border-radius: $ios-radius-full;
+      background-color: $ios-primary-light;
+      color: $ios-primary;
+      border: 1px solid $ios-primary;
+    }
+  }
+  
+  .task-info {
+    margin-bottom: 16px;
+    
+    .project-name {
+      font-size: 15px;
+      color: $ios-text-secondary;
+      margin: 6px 0;
+    }
+    
+    .deadline {
+      font-size: 15px;
+      color: $ios-text-secondary;
+      margin: 6px 0;
+      
+      &.overdue {
+        color: $ios-danger;
+      }
+      
+      .overdue-badge {
+        background-color: rgba(255, 59, 48, 0.1);
+        color: $ios-danger;
+        padding: 4px 10px;
+        border-radius: $ios-radius-full;
+        font-size: 12px;
+        margin-left: 8px;
+        border: 1px solid $ios-danger;
+      }
+      
+      .countdown-badge {
+        background-color: color-mix(in srgb, $ios-warning 15%, transparent);
+        color: $ios-warning;
+        padding: 4px 10px;
+        border-radius: $ios-radius-full;
+        font-size: 12px;
+        margin-left: 8px;
+        border: 1px solid $ios-warning;
+      }
     }
     
-    .progress-percentage {
-      position: absolute;
-      top: 50%;
-      left: 50%;
-      transform: translate(-50%, -50%);
+    .task-progress {
+      margin-top: 12px;
+      
+      .progress-bar {
+        height: 8px;
+        background-color: $ios-background-secondary;
+        border-radius: $ios-radius-full;
+        overflow: hidden;
+        border: 1px solid $ios-border;
+        
+        .progress-fill {
+          height: 100%;
+          background-color: $ios-primary;
+          transition: width 0.3s ease;
+          border-radius: $ios-radius-full;
+        }
+      }
+      
+      .progress-text {
+        font-size: 13px;
+        color: $ios-text-secondary;
+        margin: 6px 0 0 0;
+        text-align: right;
+      }
+    }
+  }
+  
+  .task-actions {
+    display: flex;
+    gap: 12px;
+    
+    button {
+      padding: 10px 18px;
+      border: none;
+      border-radius: $ios-radius-md;
+      font-size: 15px;
+      cursor: pointer;
+      transition: $ios-animation-normal $ios-animation-easing;
+      font-weight: $ios-font-weight-medium;
+      font-family: $ios-font-family;
+    }
+    
+    .btn-complete {
+      background-color: $ios-success;
       color: white;
-      font-weight: $ios-font-weight-bold;
+      
+      &:hover {
+        background-color: color.adjust($ios-success, $lightness: -5%);
+        transform: translateY(-1px);
+        box-shadow: $ios-shadow-md;
+      }
+      
+      &:active {
+        transform: translateY(0);
+      }
+    }
+    
+    .btn-detail {
+      background-color: $ios-primary;
+      color: white;
+      
+      &:hover {
+        background-color: #003A8C;
+        transform: translateY(-1px);
+        box-shadow: $ios-shadow-md;
+      }
+      
+      &:active {
+        transform: translateY(0);
+      }
+    }
+  }
+}
+
+.urgent-section {
+  grid-column: 1 / -1;
+}
+
+.urgent-list {
+  display: grid;
+  gap: 16px;
+}
+
+.urgent-item {
+  background: color-mix(in srgb, $ios-danger 10%, transparent);
+  border: 1px solid $ios-danger;
+  border-radius: $ios-radius-lg;
+  padding: 16px 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  
+  .urgent-content {
+    .urgent-title {
+      font-size: 17px;
+      color: $ios-danger;
+      font-weight: $ios-font-weight-medium;
+      margin: 0 0 6px 0;
+      font-family: $ios-font-family;
+    }
+    
+    .urgent-detail {
       font-size: 15px;
-      text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
+      color: $ios-text-secondary;
+      margin: 0;
     }
   }
+  
+  .urgent-actions {
+    .btn-urgent-detail {
+      padding: 10px 18px;
+      border: none;
+      border-radius: $ios-radius-md;
+      background-color: $ios-danger;
+      color: white;
+      font-size: 15px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      font-weight: $ios-font-weight-medium;
+      font-family: $ios-font-family;
+      
+      &:hover {
+        background-color: color-mix(in srgb, $ios-danger 90%, black);
+        transform: translateY(-1px);
+        box-shadow: $ios-shadow-md;
+      }
+      
+      &:active {
+        transform: translateY(0);
+      }
+    }
+  }
+}
+
+.feedback-section {
+  grid-column: 1 / -1;
+}
+
+.feedback-list {
+  display: grid;
+  gap: 16px;
+}
+
+.feedback-item {
+  background: color-mix(in srgb, $ios-warning 10%, transparent);
+  border: 1px solid $ios-warning;
+  border-radius: $ios-radius-lg;
+  padding: 16px 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  
+  .feedback-content {
+    .feedback-title {
+      font-size: 17px;
+      color: $ios-warning;
+      font-weight: $ios-font-weight-medium;
+      margin: 0 0 6px 0;
+      font-family: $ios-font-family;
+    }
+    
+    .feedback-project,
+    .feedback-summary,
+    .feedback-time {
+      font-size: 14px;
+      color: $ios-text-secondary;
+      margin: 4px 0;
+    }
+  }
+  
+  .feedback-actions {
+    .btn-handle-feedback {
+      padding: 10px 18px;
+      border: 1px solid $ios-warning;
+      border-radius: $ios-radius-md;
+      background-color: $ios-card-background;
+      color: $ios-warning;
+      font-size: 15px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      font-weight: $ios-font-weight-medium;
+      font-family: $ios-font-family;
+      
+      &:hover {
+        background-color: $ios-warning;
+        color: white;
+        transform: translateY(-1px);
+        box-shadow: $ios-shadow-md;
+      }
+      
+      &:active {
+        transform: translateY(0);
+      }
+    }
+  }
+}
+
+.warning-section {
+  grid-column: 1 / -1;
+}
+
+.warning-list {
+  display: grid;
+  gap: 16px;
+}
+
+.warning-item {
+  background: rgba(255, 149, 0, 0.1);
+  border: 1px solid $ios-warning;
+  border-radius: $ios-radius-lg;
+  padding: 16px 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  
+  .warning-content {
+    
+    .warning-title {
+      font-size: 17px;
+      color: $ios-warning;
+      font-weight: $ios-font-weight-medium;
+      margin: 0 0 6px 0;
+      font-family: $ios-font-family;
+    }
+    
+    .warning-detail {
+      font-size: 15px;
+      color: $ios-warning;
+      margin: 0;
+    }
+  }
+  
+  .warning-actions {
+    
+    .btn-generate-reminder {
+      padding: 10px 18px;
+      border: 1px solid $ios-warning;
+      border-radius: $ios-radius-md;
+      background-color: $ios-card-background;
+      color: $ios-warning;
+      font-size: 15px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      font-weight: 500;
+      font-family: $ios-font-family;
+      
+      &:hover {
+        background-color: $ios-warning;
+        color: white;
+        transform: translateY(-1px);
+        box-shadow: $ios-shadow-md;
+      }
+      
+      &:active {
+        transform: translateY(0);
+      }
+    }
+  }
+}
+
+.quick-access-section {
+  grid-column: 1 / -1;
+}
+
+.quick-access-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+  gap: 16px;
+}
+
+.quick-access-item {
+  background: $ios-card-background;
+  border-radius: $ios-radius-lg;
+  padding: 20px 24px;
+  box-shadow: $ios-shadow-card;
+  text-decoration: none;
+  transition: all 0.3s ease;
+  border: 1px solid $ios-border;
+  min-height: 120px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  
+  &:hover {
+    box-shadow: $ios-shadow-md;
+    transform: translateY(-2px);
+  }
+  
+  &.has-items {
+    border-left: 4px solid $ios-primary;
+  }
+  
+  h3 {
+    font-size: 20px;
+    color: $ios-text-primary;
+    margin: 0 0 12px 0;
+    font-family: $ios-font-family;
+  }
+  
+  p {
+    font-size: 15px;
+    color: $ios-text-secondary;
+    margin: 0;
+  }
 }
 
-.timeline-info {
+.empty-state {
+  text-align: center;
+  padding: 48px 24px;
+  color: $ios-text-tertiary;
   background-color: $ios-card-background;
   border-radius: $ios-radius-lg;
-  padding: 20px;
+  border: 1px dashed $ios-border;
+}
+
+/* 代班信息样式 */
+.shift-info {
+  background: $ios-card-background;
+  border-radius: $ios-radius-lg;
+  padding: $ios-spacing-xl;
   box-shadow: $ios-shadow-card;
   border: 1px solid $ios-border;
+  height: fit-content;
 }
 
-.timeline-header {
-  margin-bottom: 16px;
-  
-  h3 {
-    font-size: 18px;
-    color: $ios-text-primary;
-    font-weight: $ios-font-weight-medium;
-    margin: 0;
-    font-family: $ios-font-family;
-  }
+.shift-info .section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: $ios-spacing-xl;
 }
 
-.timeline-list {
-  display: grid;
-  gap: 12px;
+.shift-info .section-header h2 {
+  font-size: $ios-font-size-lg;
+  color: $ios-text-primary;
+  font-weight: $ios-font-weight-medium;
+  display: flex;
+  align-items: center;
+  margin: 0;
+  font-family: $ios-font-family;
+}
+
+.add-shift-btn {
+  padding: $ios-spacing-sm $ios-spacing-lg;
+  border: 1px solid $ios-primary;
+  border-radius: $ios-radius-md;
+  background-color: $ios-card-background;
+  color: $ios-primary;
+  font-size: $ios-font-size-sm;
+  cursor: pointer;
+  transition: $ios-feedback-tap;
+  font-weight: $ios-font-weight-medium;
+  font-family: $ios-font-family;
+}
+
+.add-shift-btn:hover {
+  background-color: $ios-primary;
+  color: white;
+  transform: translateY(-1px);
+  box-shadow: $ios-shadow-sm;
+}
+
+.add-shift-btn:active {
+  transform: translateY(0);
+}
+
+.shift-list {
+  display: flex;
+  flex-direction: column;
+  gap: $ios-spacing-lg;
 }
 
-.timeline-item {
-  padding: 12px 16px;
+.shift-item {
   background-color: $ios-background-secondary;
   border-radius: $ios-radius-md;
-  border-left: 4px solid $ios-primary;
+  padding: $ios-spacing-lg;
+  border: 1px solid $ios-border;
+  transition: $ios-feedback-hover;
+}
+
+.shift-item:hover {
+  background-color: color-mix(in srgb, $ios-background-secondary 70%, white);
+  box-shadow: $ios-shadow-sm;
+}
+
+.shift-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: $ios-spacing-md;
+}
+
+.shift-project {
+  font-size: $ios-font-size-base;
+  color: $ios-text-primary;
+  font-weight: $ios-font-weight-medium;
+  font-family: $ios-font-family;
+}
+
+.shift-priority {
+  padding: $ios-spacing-xs $ios-spacing-sm;
+  border-radius: $ios-radius-full;
+  font-size: $ios-font-size-xs;
+  font-weight: $ios-font-weight-medium;
+  text-transform: uppercase;
+  font-family: $ios-font-family;
+}
+
+.shift-priority.priority-high {
+  background-color: color-mix(in srgb, $ios-danger 15%, transparent);
+  color: $ios-danger;
+  border: 1px solid $ios-danger;
+}
+
+.shift-priority.priority-medium {
+  background-color: color-mix(in srgb, $ios-warning 15%, transparent);
+  color: $ios-warning;
+  border: 1px solid $ios-warning;
+}
+
+.shift-priority.priority-low {
+  background-color: color-mix(in srgb, $ios-success 15%, transparent);
+  color: $ios-success;
+  border: 1px solid $ios-success;
+}
+
+.shift-details {
+  margin-bottom: $ios-spacing-lg;
+}
+
+.shift-task {
+  font-size: $ios-font-size-sm;
+  color: $ios-text-secondary;
+  margin-bottom: $ios-spacing-xs;
+  line-height: 1.4;
+}
+
+.shift-time {
+  font-size: $ios-font-size-xs;
+  color: $ios-text-tertiary;
+}
+
+/* 响应式设计补充 */
+@media (max-width: 768px) {
+  .dashboard-header h1 {
+    font-size: 24px;
+  }
   
-  .timeline-project {
-    font-size: 15px;
-    color: $ios-text-primary;
-    font-weight: $ios-font-weight-medium;
-    margin-bottom: 4px;
+  .dashboard-nav {
+    flex-direction: column;
   }
   
-  .timeline-deadline {
-    font-size: 13px;
-    color: $ios-text-secondary;
+  .nav-btn {
+    padding: 10px 16px;
+  }
+  
+  .core-cards-section .cards-grid {
+    grid-template-columns: 1fr;
+    gap: 16px;
+  }
+  
+  .additional-info-section {
+    grid-template-columns: 1fr;
+    gap: 16px;
+  }
+  
+  .core-card {
+    padding: 16px;
+  }
+  
+  .core-card h3 {
+    font-size: 16px;
   }
   
-  &.deadline-soon {
-    border-left-color: $ios-warning;
-    background-color: color-mix(in srgb, $ios-warning 5%, transparent);
+  .workload-circle {
+    width: 100px !important;
+    height: 100px !important;
   }
   
-  &.deadline-overdue {
-    border-left-color: $ios-danger;
-    background-color: color-mix(in srgb, $ios-danger 5%, transparent);
+  .workload-percentage {
+    font-size: 24px !important;
   }
 }
 
-/* 能力维度雷达图样式 */
-.skill-radar-section {
-  grid-column: 1 / -1;
-  margin-top: 24px;
+/* iOS风格滚动条 */
+.dashboard-container {
+  &::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
+  }
+  
+  &::-webkit-scrollbar-track {
+    background: $ios-background-secondary;
+    border-radius: $ios-radius-full;
+  }
+  
+  &::-webkit-scrollbar-thumb {
+    background: $ios-scrollbar-thumb;
+    border: 2px solid $ios-background-secondary;
+  }
+  
+  &::-webkit-scrollbar-thumb:hover {
+    background: $ios-scrollbar-thumb-hover;
+  }
 }
 
-/* 响应式布局调整 */
-@media (max-width: 1024px) {
-  .workload-section {
-    grid-template-columns: 1fr;
+.section-header {
+  margin-bottom: 16px;
+  h2 {
+    font-size: 22px;
+    color: $ios-text-primary;
+    font-weight: $ios-font-weight-medium;
+    display: flex;
+    align-items: center;
+    font-family: $ios-font-family;
+    &::before {
+      content: '';
+      display: inline-block;
+      width: 3px;
+      height: 20px;
+      background-color: $ios-primary;
+      margin-right: 10px;
+      border-radius: $ios-radius-full;
+    }
   }
 }
 
-@media (max-width: 768px) {
-  .shift-table-header,
-  .shift-table-row {
-    grid-template-columns: 1fr;
-    gap: 12px;
+.task-list {
+  display: grid;
+  gap: 16px;
+}
+
+.task-item {
+  background: $ios-card-background;
+  border-radius: $ios-radius-lg;
+  padding: 20px;
+  box-shadow: $ios-shadow-card;
+  transition: all 0.3s ease;
+  border: 1px solid $ios-border;
+  
+  &:hover {
+    box-shadow: $ios-shadow-lg;
+    transform: translateY(-2px);
   }
   
-  .shift-actions {
-    justify-content: flex-start;
+  .task-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 12px;
+    
+    h3 {
+      font-size: 17px;
+      color: $ios-text-primary;
+      margin: 0;
+      font-family: $ios-font-family;
+    }
+    
+    .task-stage {
+      padding: 4px 12px;
+      border-radius: $ios-radius-full;
+      font-size: 12px;
+      font-weight: $ios-font-weight-medium;
+      font-family: $ios-font-family;
+    }
   }
-}
+}

+ 73 - 7
src/app/pages/designer/dashboard/dashboard.ts

@@ -32,6 +32,8 @@ interface ProjectTimelineItem {
 export class Dashboard implements OnInit {
   // 视图管理
   activeDashboard: 'main' | 'skills' | 'personal' = 'main';
+  // 新增:工作台视图模式(卡片/列表)
+  viewMode: 'card' | 'list' = 'card';
   
   tasks: Task[] = [];
   overdueTasks: Task[] = [];
@@ -62,6 +64,11 @@ export class Dashboard implements OnInit {
     this.activeDashboard = view;
   }
   
+  // 新增:切换卡片/列表视图
+  toggleView(): void {
+    this.viewMode = this.viewMode === 'card' ? 'list' : 'card';
+  }
+  
   // 获取前N个任务的方法
   getTopTasks(count: number): Task[] {
     // 过滤掉紧急任务和超期任务
@@ -191,19 +198,15 @@ export class Dashboard implements OnInit {
     // 清除之前的定时器
     this.countdowns.clear();
     
-    // 为渲染任务启动倒计时
+    // 为所有任务启动倒计时,确保列表视图也有剩余时间显示
     this.tasks.forEach(task => {
-      if (task.stage === '渲染') {
-        this.updateCountdown(task.id, task.deadline);
-      }
+      this.updateCountdown(task.id, task.deadline);
     });
     
     // 定期更新倒计时
     setInterval(() => {
       this.tasks.forEach(task => {
-        if (task.stage === '渲染') {
-          this.updateCountdown(task.id, task.deadline);
-        }
+        this.updateCountdown(task.id, task.deadline);
       });
     }, 60000); // 每分钟更新一次
   }
@@ -231,6 +234,69 @@ export class Dashboard implements OnInit {
     return this.countdowns.get(taskId) || '';
   }
   
+  // 新增:列表视图专用剩余时间格式化(若未在countdowns中,直接计算)
+  getListTimeLeft(task: Task): string {
+    const cached = this.getTaskCountdown(task.id);
+    if (cached) return cached;
+    const now = new Date();
+    const diffMs = task.deadline.getTime() - now.getTime();
+    if (diffMs <= 0) return '已超期';
+    const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
+    const diffHours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
+    if (diffDays > 0) return `${diffDays}天${diffHours}小时`;
+    const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
+    if (diffHours > 0) return `${diffHours}小时${diffMinutes}分钟`;
+    return `${diffMinutes}分钟`;
+  }
+
+  // 新增:按紧急度排序
+  getTasksSortedByUrgency(): Task[] {
+    return this.tasks
+      .filter(t => !t.isCompleted)
+      .slice()
+      .sort((a, b) => this.getUrgencyScore(b) - this.getUrgencyScore(a));
+  }
+
+  // 新增:紧急度评分,数值越大越紧急
+  private getUrgencyScore(task: Task): number {
+    if (task.isOverdue) return 10000;
+    const now = new Date().getTime();
+    const hoursLeft = (task.deadline.getTime() - now) / (1000 * 60 * 60);
+    let base = 0;
+    if (hoursLeft <= 3) base = 9000;
+    else if (hoursLeft <= 24) base = 7000;
+    else if (hoursLeft <= 72) base = 4000;
+    else base = 1000;
+    // 渲染阶段适度提高权重
+    const stageBoost = task.stage === '渲染' ? 300 : 0;
+    return base + stageBoost;
+  }
+
+  // 新增:紧急度标签
+  getUrgencyLevel(task: Task): '超期' | '高' | '中' | '低' {
+    if (task.isOverdue) return '超期';
+    const now = new Date().getTime();
+    const hoursLeft = (task.deadline.getTime() - now) / (1000 * 60 * 60);
+    if (hoursLeft <= 24) return '高';
+    if (hoursLeft <= 72) return '中';
+    return '低';
+  }
+
+  // 新增:紧急度样式类
+  getUrgencyClass(task: Task): string {
+    const level = this.getUrgencyLevel(task);
+    switch (level) {
+      case '超期':
+        return 'urgency-overdue';
+      case '高':
+        return 'urgency-high';
+      case '中':
+        return 'urgency-medium';
+      default:
+        return 'urgency-low';
+    }
+  }
+  
   getTaskStageProgress(taskId: string): number {
     const task = this.tasks.find(t => t.id === taskId);
     if (!task) return 0;

+ 178 - 3
src/app/pages/designer/project-detail/debug-styles.scss

@@ -1,5 +1,7 @@
 /* 调试样式文件 - 增强版本,确保布局正确显示 */
 
+@use '../ios-theme.scss' as *;
+
 /* 重置所有可能冲突的样式 - 使用最高优先级 */
 * {
   box-sizing: border-box !important;
@@ -47,7 +49,7 @@
   gap: 20px !important;
   margin-top: 20px !important;
   width: 100% !important;
-  background-color: rgba(200, 200, 255, 0.3) !important; /* 明显的背景色 */
+  background-color: transparent !important; // 去除调试底色
   padding: 20px !important;
   border-radius: 8px !important;
 }
@@ -60,7 +62,7 @@
   display: flex !important;
   flex-direction: column !important;
   gap: 20px !important;
-  background-color: rgba(255, 200, 200, 0.3) !important; /* 左侧列背景色 */
+  background-color: transparent !important; // 去除左侧粉色底色
   padding: 10px !important;
   border-radius: 6px !important;
 }
@@ -73,7 +75,7 @@
   display: flex !important;
   flex-direction: column !important;
   gap: 20px !important;
-  background-color: rgba(200, 255, 200, 0.3) !important; /* 右侧列背景色 */
+  background-color: transparent !important; // 去除右侧调试底色
   padding: 10px !important;
   border-radius: 6px !important;
 }
@@ -100,4 +102,177 @@
     min-width: 100% !important;
     max-width: 100% !important;
   }
+}
+
+/* ===================== 四大板块:工具条与内容样式(调试版) ===================== */
+.sections-toolbar {
+  display: flex;
+  gap: $ios-spacing-md;
+  padding: $ios-spacing-sm;
+  background: $ios-background-secondary;
+  border-radius: $ios-radius-lg;
+  border: 1px solid $ios-border;
+  margin: $ios-spacing-md 0 $ios-spacing-lg;
+}
+
+.section-btn {
+  appearance: none;
+  border: 1px solid $ios-border;
+  background: $ios-background;
+  color: $ios-text-primary;
+  padding: $ios-spacing-sm $ios-spacing-lg;
+  border-radius: $ios-radius-md;
+  font-size: $ios-font-size-sm;
+  font-weight: $ios-font-weight-medium;
+  cursor: pointer;
+  transition: all 0.15s ease;
+}
+.section-btn:hover { transform: translateY(-1px); box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
+.section-btn.completed { background: #e6f7e6; color: $ios-success; border-color: rgba(0,0,0,0.06); }
+.section-btn.active { background: #e8f0fe; color: $ios-primary; border-color: $ios-primary; }
+.section-btn.pending { background: $ios-background; color: $ios-text-secondary; }
+
+.sections-content { margin-top: $ios-spacing-md; }
+
+.section-panel {
+  background: $ios-background;
+  border: 1px solid $ios-border;
+  border-radius: $ios-radius-lg;
+  padding: $ios-spacing-lg;
+}
+
+/* 交付执行:三列布局 */
+.delivery-grid {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: $ios-spacing-lg;
+}
+
+@media (max-width: 1024px) {
+  .delivery-grid { grid-template-columns: 1fr; }
+}
+
+.delivery-col {
+  background: $ios-background-secondary;
+  border: 1px solid $ios-border;
+  border-radius: $ios-radius-md;
+  padding: $ios-spacing-md;
+  display: flex;
+  flex-direction: column;
+  gap: $ios-spacing-md;
+}
+
+.delivery-stage-header {
+  display: flex;
+  align-items: center;
+  gap: $ios-spacing-sm;
+}
+.delivery-stage-header .dot {
+  width: 10px; height: 10px; border-radius: 50%; background: $ios-border;
+}
+.delivery-stage-header .dot.completed { background: $ios-success; }
+.delivery-stage-header .dot.active { background: $ios-primary; }
+
+.delivery-stage-body { display: flex; flex-direction: column; gap: $ios-spacing-md; }
+
+/* 统一纵向内容容器 */
+.section-vertical { display: flex; flex-direction: column; gap: $ios-spacing-lg; }
+
+.vertical-stage-block {
+  background: $ios-background-secondary;
+  border: 1px solid $ios-border;
+  border-radius: $ios-radius-md;
+  padding: $ios-spacing-md;
+}
+
+.vertical-stage-header { display: flex; align-items: center; gap: $ios-spacing-sm; margin-bottom: $ios-spacing-sm; }
+.vertical-stage-header .dot { width: 10px; height: 10px; border-radius: 50%; background: $ios-border; }
+.vertical-stage-header .dot.completed { background: $ios-success; }
+.vertical-stage-header .dot.active { background: $ios-primary; }
+
+.vertical-stage-body { display: flex; flex-direction: column; gap: $ios-spacing-md; }
+
+/* 缩略图列表适配到新容器 */
+.section-panel .thumb-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: $ios-spacing-md; }
+.section-panel .thumb-item { background: white; border: 1px solid $ios-border; border-radius: $ios-radius-sm; overflow: hidden; display: flex; flex-direction: column; }
+.section-panel .thumb-item img { width: 100%; height: 120px; object-fit: cover; }
+.section-panel .thumb-meta { display: flex; justify-content: space-between; padding: $ios-spacing-xs $ios-spacing-sm; font-size: $ios-font-size-xs; color: $ios-text-secondary; }
+
+/* 上传区小幅适配 */
+.section-panel .upload-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: $ios-spacing-sm;
+  padding: $ios-spacing-sm $ios-spacing-md;
+  background: $ios-background;
+  border: 1px dashed $ios-border;
+  border-radius: $ios-radius-sm;
+}
+
+/* 强调版:客户信息卡片更突出、边缘更清晰 */
+/* 顶部强调条、圆角与阴影调整,以及关键信息高亮 */
+.left-column .project-info-card.card {
+  border: 1px solid $ios-border !important;
+  border-radius: 12px !important; /* 圆角加大 */
+  box-shadow: 0 10px 24px rgba(0,0,0,0.06), 0 2px 6px rgba(0,0,0,0.05) !important; /* 阴影减弱些 */
+  position: relative;
+}
+.left-column .project-info-card.card::before {
+  content: "";
+  position: absolute;
+  top: 0; left: 0; right: 0;
+  height: 4px; /* 顶部强调条 */
+  background: $ios-primary;
+  border-top-left-radius: 12px;
+  border-top-right-radius: 12px;
+}
+/* 关键信息轻微高亮:客户姓名 */
+.left-column .project-info-card .info-item.key-info span {
+  font-weight: $ios-font-weight-semibold;
+  color: $ios-text-primary;
+}
+.left-column .project-info-card .info-item.key-info label {
+  color: $ios-primary;
+}
+/* 标签轻微高亮:提升标签显著性但不过度 */
+.left-column .project-info-card .tags .tag {
+  background-color: rgba(24, 144, 255, 0.08); /* 以主色系的淡背景形成层级 */
+  color: $ios-primary;
+}
+.left-column .project-info-card.card h2 {
+  color: $ios-primary !important; /* 标题与强调条呼应 */
+}
+
+/* 活动阶段卡片:浅红底色突显(更显眼版) */
+.delivery-col.active,
+.vertical-stage-block.active {
+  background: #ffeaea; /* 比 #fff2f0 更显眼 */
+  border-color: #ffccc7; /* 维持柔和的红系边框 */
+  box-shadow: 0 8px 22px rgba(255, 85, 62, 0.12), 0 2px 8px rgba(255, 85, 62, 0.10);
+  position: relative; /* 提供定位上下文给badge */
+  padding-right: $ios-spacing-lg; /* 让出badge空间,避免内容压贴 */
+}
+
+/* 活动阶段标题与圆点颜色呼应 */
+.delivery-col.active .delivery-stage-header h3,
+.vertical-stage-block.active .vertical-stage-header h3 { color: #ff4d4f; }
+.delivery-col.active .dot,
+.vertical-stage-block.active .dot { background: #ff4d4f; }
+
+/* 右上角状态badge:进行中 */
+.delivery-col.active::after,
+.vertical-stage-block.active::after {
+  content: '进行中';
+  position: absolute;
+  top: 8px;
+  right: 8px;
+  padding: 2px 8px;
+  background: #ff4d4f;
+  color: #fff;
+  border-radius: 999px;
+  font-size: 12px;
+  line-height: 1.4;
+  font-weight: 600;
+  box-shadow: 0 4px 10px rgba(255, 77, 79, 0.25);
 }

+ 297 - 309
src/app/pages/designer/project-detail/project-detail.html

@@ -61,97 +61,43 @@
     <!-- 项目进度标签页 -->
     @if (isActiveTab('progress')) {
       <div class="progress-tab-content">
-        <!-- 主要内容布局 - 左侧三分之一,右侧三分之二 -->
         <div class="main-content-layout">
-          <!-- 左侧三分之一 - 项目信息和客户画像 -->
+          <!-- 左侧保留 -->
           <div class="left-column">
-            <!-- 项目基本信息 -->
             <div class="project-info-card card">
-              <h2>项目基本信息</h2>
-              <div class="info-grid">
-                <div class="info-item">
-                  <label>项目名称</label>
-                  <span>{{ project?.name || '加载中...' }}</span>
-                </div>
-                <div class="info-item">
-                  <label>客户姓名</label>
-                  <span>{{ project?.customerName || '加载中...' }}</span>
-                </div>
-                <div class="info-item">
-                  <label>当前阶段</label>
-                  <span class="stage-tag">{{ project?.currentStage || '加载中...' }}</span>
-                </div>
-                @if (project) {
-                  <div class="info-item">
-                    <label>预计交付日期</label>
-                    <span>{{ project.deadline | date:'yyyy-MM-dd' }}</span>
-                  </div>
-                }
-              </div>
-            </div>
-
-            <!-- 客户画像 -->
-            <div class="customer-profile-card card">
-              <h2>客户画像</h2>
-              
-              <!-- 技能匹配度警告 -->
-              @if (getSkillMismatchWarning()) {
-                <div class="warning-banner">
-                  <div class="warning-content">
-                    <span class="warning-icon">⚠️</span>
-                    <span class="warning-text">{{ getSkillMismatchWarning() }}</span>
-                  </div>
-                  <button (click)="notifyTeamLeader('skill-mismatch')" class="contact-leader-btn">联系组长</button>
-                </div>
-              }
-            
+              <h2>客户信息</h2>
               @if (project) {
-                <div class="tags-container">
+                <div class="info-grid">
+                  <div class="info-item key-info"><label>客户姓名</label><span>{{ project.customerName }}</span></div>
+                  <div class="info-item key-info"><label>项目负责人</label><span>{{ project.assigneeName }}</span></div>
+                  <div class="info-item"><label>项目创建</label><span>{{ formatDate(project.createdAt) }}</span></div>
+                  <div class="info-item"><label>截止日期</label><span>{{ formatDate(project.deadline) }}</span></div>
+                </div>
+                <div class="tags-container" style="margin-top: 12px;">
                   <div class="tag-section">
-                    <h3>客户偏好</h3>
-                    <div class="tags-grid">
-                      @if (project.customerTags && project.customerTags.length > 0) {
-                        
-                        <!-- 已移除:需求类型 -->
-                        
-                        <div class="tag-item">
-                          <span class="tag-label">设计风格</span>
-                          @if (project.customerTags[0].preference) { 
-                            <span class="tag">{{ project.customerTags[0].preference }}</span>
-                          }
-                        </div>
-                        <div class="tag-item">
-                          <span class="tag-label">色彩氛围</span>
-                          @if (project.customerTags[0].colorAtmosphere) { 
-                            <span class="tag">{{ project.customerTags[0].colorAtmosphere }}</span>
-                          }
-                        </div>
+                    <h3>客户标签</h3>
+                    <div class="tags">
+                      @for (tag of project.customerTags; track $index) {
+                        <span class="tag">{{ tag.source }} · {{ tag.needType }} · {{ tag.preference }} · {{ tag.colorAtmosphere }}</span>
                       }
+                      @if (project.customerTags?.length === 0) { <span class="desc">暂无标签</span> }
                     </div>
                   </div>
-                  
                   <div class="tag-section">
-                    <h3>项目要求</h3>
-                    <div class="tags-flex">
-                      <div class="tag-group">
-                        <span class="group-label">高优先级需求</span>
-                        <div class="tags">
-                          @for (priority of project.highPriorityNeeds; track priority) {
-                            <span class="priority-tag">{{ priority }}</span>
-                          }
-                        </div>
-                      </div>
-                      <div class="tag-group">
-                        <span class="group-label">擅长技能</span>
-                        <div class="tags">
-                          @for (skill of project.skillsRequired; track skill) {
-                            <span class="skill-tag">{{ skill }}</span>
-                          }
-                        </div>
-                      </div>
-                    </div>
+                    <h3>高优先级需求</h3>
+                    <ul class="need-list">
+                      @for (need of project.highPriorityNeeds; track $index) {
+                        <li>{{ need }}</li>
+                      }
+                      @if (project.highPriorityNeeds?.length === 0) { <li class="desc">无</li> }
+                    </ul>
                   </div>
                 </div>
+              } @else {
+                <div class="loading-state">
+                  <div class="loading-spinner"></div>
+                  <div>正在加载客户信息...</div>
+                </div>
               }
             </div>
           </div>
@@ -161,269 +107,273 @@
             <div class="process-card card">
               <h2>制作流程进度</h2>
 
-              <!-- 串式流程:10个阶段横向排列,可展开专属卡片 -->
-              <!-- 已按需求移除:每个分阶段的展开按钮 -->
-              
-              <div class="stage-progress-container">
-                <div class="stage-progress-wrapper">
-                  <div class="stage-progress">
-                    @for (stage of getVisibleStages(); track stage) {
-                      <div class="stage" [class.completed]="getStageStatus(stage) === 'completed'" [class.active]="getStageStatus(stage) === 'active'" [class.pending]="getStageStatus(stage) === 'pending'">
-                        <div class="stage-icon">{{ getVisibleStages().indexOf(stage) + 1 }}</div>
-                        <div class="stage-name">{{ stage }}</div>
-                        <!-- 已移除原先位置的阶段展开按钮:
-                        <button class="stage-toggle" (click)="toggleStage(stage)">{{ expandedStages[stage] ? '收起' : '展开' }}</button>
-                        -->
-                      </div>
-                    }
-                  </div>
-                </div>
+              <!-- 新增:四大板块矩形按钮 -->
+              <div class="sections-toolbar">
+                @for (sec of sections; track sec.key) {
+                  <button class="section-btn"
+                          [class.completed]="getSectionStatus(sec.key) === 'completed'"
+                          [class.active]="getSectionStatus(sec.key) === 'active'"
+                          [class.pending]="getSectionStatus(sec.key) === 'pending'"
+                          (click)="toggleSection(sec.key)">
+                    <span class="section-label">{{ sec.label }}</span>
+                  </button>
+                }
               </div>
 
-              <!-- 阶段详情:位于每个阶段正下方(网格与上方阶段图标对齐) -->
-              <div class="stage-details-grid">
-                @for (stage of getVisibleStages(); track stage) {
-                  @if (getStageStatus(stage) === 'active') {
-                    <div class="stage-details-cell">
-                      <div class="stage-specific-card card" [class.success]="getStageStatus(stage)==='completed'" [class.warning]="getStageStatus(stage)==='active'" [class.danger]="getStageStatus(stage)==='pending'">
-                        <div class="stage-specific-header">
-                          <h3>{{ stage }} · 阶段详情</h3>
-                          <div class="ops">
-                            <!-- 已移除:查看阶段详情 按钮 -->
-                          </div>
-                        </div>
+              <!-- 串式流程:10个阶段横向排列(保持) -->
+              <div class="stage-progress-container">
+                <!-- ... existing code ... -->
+              </div>
 
-                        <!-- 针对不同阶段,展示对应卡片模块(示例:建模/软装/渲染/后期/尾款结算) -->
-                        @if (stage === '建模') {
-                          @if (shouldShowCard('modelCheck')) {
-                            <div class="model-check-section">
-                              <h4>模型误差检查清单</h4>
-                              <div class="checklist">
-                                @for (item of modelCheckItems; track item.id) {
-                                  <div class="checklist-item">
-                                    <label class="checklist-label">
-                                      <input type="checkbox" class="custom-checkbox" [checked]="item.isPassed" (change)="updateModelCheckItem(item.id, $any($event.target).checked)" [disabled]="!isDesignerView()">
-                                      <span class="checklist-text">{{ item.name }}</span>
-                                    </label>
-                                    <span class="check-status">{{ item.isPassed ? '已通过' : '待处理' }}</span>
-                                  </div>
-                                }
+              <!-- 新增:板块内容区(仅展开一个,其余收起) -->
+              <div class="sections-content">
+                @for (sec of sections; track sec.key) {
+                  @if (expandedSection === sec.key) {
+                    <div class="section-panel" [attr.data-key]="sec.key">
+                      <!-- 交付执行:三阶段横向排列、内容竖向展开、空间平分 -->
+                      @if (sec.key === 'delivery') {
+                        <div class="delivery-grid">
+                          @for (stage of sec.stages; track stage) {
+                            <div class="delivery-col" [class.active]="getStageStatus(stage) === 'active'">
+                              <div class="delivery-stage-header">
+                                <span class="dot" [class.completed]="getStageStatus(stage) === 'completed'" [class.active]="getStageStatus(stage) === 'active'"></span>
+                                <h3>{{ stage }}</h3>
                               </div>
-                            </div>
-                          }
-
-                          <div class="upload-section">
-                            <div class="upload-header">
-                              <h4>上传白模图片</h4>
-                              <span class="hint">支持:JPG/PNG;不强制4K</span>
-                            </div>
-                            <div class="upload-actions">
-                              @if (isDesignerView()) {
-                                <label class="secondary-btn">
-                                  选择图片
-                                  <input type="file" accept="{{allowedImageTypes}}" multiple (change)="onWhiteModelSelected($event)" style="display:none" />
-                                </label>
-                                <button class="primary-btn" [disabled]="whiteModelImages.length===0" (click)="confirmWhiteModelUpload()">确认上传</button>
-                              }
-                              @if (isTeamLeaderView()) {
-                                <button class="secondary-btn" (click)="syncUploadedImages('white')">同步图片信息</button>
-                              }
-                              @if (isCustomerServiceView()) {
-                                <span class="desc">只读</span>
-                              }
-                            </div>
-                            @if (whiteModelImages.length > 0) {
-                              <div class="thumb-list">
-                                @for (img of whiteModelImages; track img.id) {
-                                  <div class="thumb-item">
-                                    <img [src]="img.url" [alt]="img.name" />
-                                    <div class="thumb-meta">
-                                      <span class="name" [title]="img.name">{{ img.name }}</span>
-                                      <span class="size">{{ img.size }}</span>
+                              <!-- 直接复用原阶段内容卡片:按stage匹配显示 -->
+                              <div class="delivery-stage-body">
+                                @if (stage === '建模') { <!-- 引用原建模块 -->
+                                  <!-- BEGIN: reuse 建模阶段内容 -->
+                                  @if (shouldShowCard('modelCheck')) {
+                                    <div class="model-check-section">
+                                      <h4>模型误差检查清单</h4>
+                                      <div class="checklist">
+                                        @for (item of modelCheckItems; track item.id) {
+                                          <div class="checklist-item">
+                                            <label class="checklist-label">
+                                              <input type="checkbox" class="custom-checkbox" [checked]="item.isPassed" (change)="updateModelCheckItem(item.id, $any($event.target).checked)" [disabled]="!isDesignerView()">
+                                              <span class="checklist-text">{{ item.name }}</span>
+                                            </label>
+                                            <span class="check-status">{{ item.isPassed ? '已通过' : '待处理' }}</span>
+                                          </div>
+                                        }
+                                      </div>
                                     </div>
-                                    <div class="review-meta" style="display:flex;gap:8px;align-items:center;">
-                                      <span class="status-badge">{{ getImageReviewStatusText(img) }}</span>
-                                      @if (isTeamLeaderView()) {
-                                        <button class="link success" (click)="reviewImage(img.id, 'white', 'approved')">通过</button>
-                                        <button class="link warning" (click)="reviewImage(img.id, 'white', 'rejected')">驳回</button>
+                                  }
+                                  <div class="upload-section">
+                                    <div class="upload-header">
+                                      <h4>上传白模图片</h4>
+                                      <span class="hint">支持:JPG/PNG;不强制4K</span>
+                                    </div>
+                                    <div class="upload-actions">
+                                      @if (isDesignerView()) {
+                                        <label class="secondary-btn">
+                                          选择图片
+                                          <input type="file" accept="{{allowedImageTypes}}" multiple (change)="onWhiteModelSelected($event)" style="display:none" />
+                                        </label>
+                                        <button class="primary-btn" [disabled]="whiteModelImages.length===0" (click)="confirmWhiteModelUpload()">确认上传</button>
                                       }
+                                      @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('white')">同步图片信息</button> }
+                                      @if (isCustomerServiceView()) { <span class="desc">只读</span> }
                                     </div>
-                                    @if (isDesignerView()) { <button class="link danger" (click)="removeWhiteModelImage(img.id)">移除</button> }
+                                    @if (whiteModelImages.length > 0) {
+                                      <div class="thumb-list">
+                                        @for (img of whiteModelImages; track img.id) {
+                                          <div class="thumb-item">
+                                            <img [src]="img.url" [alt]="img.name" />
+                                            <div class="thumb-meta">
+                                              <span class="name" [title]="img.name">{{ img.name }}</span>
+                                              <span class="size">{{ img.size }}</span>
+                                            </div>
+                                            <div class="review-meta" style="display:flex;gap:8px;align-items:center;">
+                                              <span class="status-badge">{{ getImageReviewStatusText(img) }}</span>
+                                              @if (isTeamLeaderView()) {
+                                                <button class="link success" (click)="reviewImage(img.id, 'white', 'approved')">通过</button>
+                                                <button class="link warning" (click)="reviewImage(img.id, 'white', 'rejected')">驳回</button>
+                                              }
+                                            </div>
+                                            @if (isDesignerView()) { <button class="link danger" (click)="removeWhiteModelImage(img.id)">移除</button> }
+                                          </div>
+                                        }
+                                      </div>
+                                    }
                                   </div>
+                                  <!-- END: reuse 建模阶段内容 -->
                                 }
-                              </div>
-                            }
-                          </div>
-                        }
 
-                        @if (stage === '软装') {
-                          <div class="softdecor-section">
-                            <h4>软装补充资料</h4>
-                            <div class="upload-section">
-                              <div class="upload-header">
-                                <h4>上传软装小图</h4>
-                                <span class="hint">建议 ≤ 1MB 的 JPG/PNG 小图</span>
-                              </div>
-                              <div class="upload-actions">
-                                @if (isDesignerView()) {
-                                  <label class="secondary-btn">
-                                    选择图片
-                                    <input type="file" accept="{{allowedImageTypes}}" multiple (change)="onSoftDecorSmallPicsSelected($event)" style="display:none" />
-                                  </label>
-                                  <button class="primary-btn" [disabled]="softDecorImages.length===0" (click)="confirmSoftDecorUpload()">确认上传</button>
-                                }
-                                @if (isTeamLeaderView()) {
-                                  <button class="secondary-btn" (click)="syncUploadedImages('soft')">同步图片信息</button>
-                                }
-                                @if (isCustomerServiceView()) { <span class="desc">只读</span> }
-                              </div>
-                              @if (softDecorImages.length > 0) {
-                                <div class="thumb-list">
-                                  @for (img of softDecorImages; track img.id) {
-                                    <div class="thumb-item">
-                                      <img [src]="img.url" [alt]="img.name" />
-                                      <div class="thumb-meta">
-                                        <span class="name" [title]="img.name">{{ img.name }}</span>
-                                        <span class="size">{{ img.size }}</span>
+                                @if (stage === '软装') { <!-- 引用原软装块 -->
+                                  <!-- BEGIN: reuse 软装阶段内容 -->
+                                  <div class="softdecor-section">
+                                    <h4>软装补充资料</h4>
+                                    <div class="upload-section">
+                                      <div class="upload-header">
+                                        <h4>上传软装小图</h4>
+                                        <span class="hint">建议 ≤ 1MB 的 JPG/PNG 小图</span>
                                       </div>
-                                      <div class="review-meta" style="display:flex;gap:8px;align-items:center;">
-                                        <span class="status-badge">{{ getImageReviewStatusText(img) }}</span>
-                                        @if (isTeamLeaderView()) {
-                                          <button class="link success" (click)="reviewImage(img.id, 'soft', 'approved')">通过</button>
-                                          <button class="link warning" (click)="reviewImage(img.id, 'soft', 'rejected')">驳回</button>
+                                      <div class="upload-actions">
+                                        @if (isDesignerView()) {
+                                          <label class="secondary-btn">
+                                            选择图片
+                                            <input type="file" accept="{{allowedImageTypes}}" multiple (change)="onSoftDecorSmallPicsSelected($event)" style="display:none" />
+                                          </label>
+                                          <button class="primary-btn" [disabled]="softDecorImages.length===0" (click)="confirmSoftDecorUpload()">确认上传</button>
                                         }
+                                        @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('soft')">同步图片信息</button> }
+                                        @if (isCustomerServiceView()) { <span class="desc">只读</span> }
                                       </div>
-                                      @if (isDesignerView()) { <button class="link danger" (click)="removeSoftDecorImage(img.id)">移除</button> }
-                                    </div>
-                                  }
-                                </div>
-                              }
-                            </div>
-                          </div>
-                        }
-
-                        @if (stage === '渲染') {
-                          @if (shouldShowCard('renderProgress')) {
-                            <div class="render-progress-section">
-                              @if (isLoadingRenderProgress) {
-                                <div class="loading-state">
-                                  <div class="loading-spinner"></div>
-                                  <div>正在加载渲染进度...</div>
-                                </div>
-                              }
-                              @if (errorLoadingRenderProgress) {
-                                <div class="error-state">
-                                  <div>渲染进度加载失败</div>
-                                  <button class="secondary-btn" (click)="retryLoadRenderProgress()">重试</button>
-                                </div>
-                              }
-                              @if (!isLoadingRenderProgress && !errorLoadingRenderProgress && renderProgress) {
-                                <div class="progress-info" style="display:flex;gap:16px;align-items:center;margin:12px 0;">
-                                  <span>状态:{{ renderProgress.status }}</span>
-                                  <span>完成度:{{ renderProgress.completionRate }}%</span>
-                                  <span>预计剩余:{{ renderProgress.estimatedTimeRemaining }} 小时</span>
-                                </div>
-                              }
-
-                              <div class="upload-section">
-                                <div class="upload-header">
-                                  <h4>上传渲染大图</h4>
-                                  <span class="hint">需满足4K标准(最大边≥4000px)</span>
-                                </div>
-                                <div class="upload-actions" style="display:flex;gap:12px;align-items:center;">
-                                  @if (isDesignerView()) { <button class="primary-btn" (click)="openRenderUploadModal()">选择并上传</button> }
-                                  @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('render')">同步图片信息</button> }
-                                  @if (renderLargeImages.length>0) { <span class="desc">已上传 {{renderLargeImages.length}} 张</span> }
-                                </div>
-                                @if (renderLargeImages.length > 0) {
-                                  <div class="thumb-list">
-                                    @for (img of renderLargeImages; track img.id) {
-                                      <div class="thumb-item">
-                                        <img [src]="img.url" [alt]="img.name" />
-                                        <div class="thumb-meta">
-                                          <span class="name" [title]="img.name">{{ img.name }}</span>
-                                          <span class="size">{{ img.size }}</span>
-                                        </div>
-                                        <div class="review-meta" style="display:flex;gap:8px;align-items:center;">
-                                          <span class="status-badge">{{ getImageReviewStatusText(img) }}</span>
-                                          @if (isTeamLeaderView()) {
-                                            <button class="link success" (click)="reviewImage(img.id, 'render', 'approved')">通过</button>
-                                            <button class="link warning" (click)="reviewImage(img.id, 'render', 'rejected')">驳回</button>
+                                      @if (softDecorImages.length > 0) {
+                                        <div class="thumb-list">
+                                          @for (img of softDecorImages; track img.id) {
+                                            <div class="thumb-item">
+                                              <img [src]="img.url" [alt]="img.name" />
+                                              <div class="thumb-meta">
+                                                <span class="name" [title]="img.name">{{ img.name }}</span>
+                                                <span class="size">{{ img.size }}</span>
+                                              </div>
+                                              <div class="review-meta" style="display:flex;gap:8px;align-items:center;">
+                                                <span class="status-badge">{{ getImageReviewStatusText(img) }}</span>
+                                                @if (isTeamLeaderView()) {
+                                                  <button class="link success" (click)="reviewImage(img.id, 'soft', 'approved')">通过</button>
+                                                  <button class="link warning" (click)="reviewImage(img.id, 'soft', 'rejected')">驳回</button>
+                                                }
+                                              </div>
+                                              @if (isDesignerView()) { <button class="link danger" (click)="removeSoftDecorImage(img.id)">移除</button> }
+                                            </div>
                                           }
                                         </div>
-                                        @if (isDesignerView()) { <button class="link danger" (click)="removeRenderLargeImage(img.id)">移除</button> }
+                                      }
+                                    </div>
+                                  </div>
+                                  <!-- END: reuse 软装阶段内容 -->
+                                }
+
+                                @if (stage === '渲染') { <!-- 引用原渲染块 -->
+                                  <!-- BEGIN: reuse 渲染阶段内容(简化容器) -->
+                                  <div class="render-progress-section">
+                                    @if (isLoadingRenderProgress) {
+                                      <div class="loading-state">
+                                        <div class="loading-spinner"></div>
+                                        <div>正在加载渲染进度...</div>
+                                      </div>
+                                    }
+                                    @if (errorLoadingRenderProgress) {
+                                      <div class="error-state">
+                                        <div>渲染进度加载失败</div>
+                                        <button class="secondary-btn" (click)="retryLoadRenderProgress()">重试</button>
                                       </div>
                                     }
+                                    @if (!isLoadingRenderProgress && !errorLoadingRenderProgress && renderProgress) {
+                                      <div class="progress-info" style="display:flex;gap:16px;align-items:center;margin:12px 0;">
+                                        <span>状态:{{ renderProgress.status }}</span>
+                                        <span>完成度:{{ renderProgress.completionRate }}%</span>
+                                        <span>预计剩余:{{ renderProgress.estimatedTimeRemaining }} 小时</span>
+                                      </div>
+                                    }
+                                    <div class="upload-section">
+                                      <div class="upload-header">
+                                        <h4>上传渲染大图</h4>
+                                        <span class="hint">需满足4K标准(最大边≥4000px)</span>
+                                      </div>
+                                      <div class="upload-actions" style="display:flex;gap:12px;align-items:center;">
+                                        @if (isDesignerView()) { <button class="primary-btn" (click)="openRenderUploadModal()">选择并上传</button> }
+                                        @if (isTeamLeaderView()) { <button class="secondary-btn" (click)="syncUploadedImages('render')">同步图片信息</button> }
+                                        @if (renderLargeImages.length>0) { <span class="desc">已上传 {{renderLargeImages.length}} 张</span> }
+                                      </div>
+                                      @if (renderLargeImages.length > 0) {
+                                        <div class="thumb-list">
+                                          @for (img of renderLargeImages; track img.id) {
+                                            <div class="thumb-item">
+                                              <img [src]="img.url" [alt]="img.name" />
+                                              <div class="thumb-meta">
+                                                <span class="name" [title]="img.name">{{ img.name }}</span>
+                                                <span class="size">{{ img.size }}</span>
+                                              </div>
+                                              <div class="review-meta" style="display:flex;gap:8px;align-items:center;">
+                                                <span class="status-badge">{{ getImageReviewStatusText(img) }}</span>
+                                                @if (isTeamLeaderView()) {
+                                                  <button class="link success" (click)="reviewImage(img.id, 'render', 'approved')">通过</button>
+                                                  <button class="link warning" (click)="reviewImage(img.id, 'render', 'rejected')">驳回</button>
+                                                }
+                                              </div>
+                                              @if (isDesignerView()) { <button class="link danger" (click)="removeRenderLargeImage(img.id)">移除</button> }
+                                            </div>
+                                          }
+                                        </div>
+                                      }
+                                    </div>
                                   </div>
+                                  <!-- END: reuse 渲染阶段内容 -->
                                 }
                               </div>
                             </div>
                           }
-                        }
+                        </div>
+                      }
 
-                        @if (stage === '后期') {
-                          <div class="post-section">
-                            <h4>客户反馈</h4>
-                            <div class="card-content">
-                              @if (feedbacks.length === 0) { <div class="empty">暂无反馈</div> }
-                              @for (fb of feedbacks; track fb.id) {
-                                <div class="feedback-item">
-                                  <div class="feedback-header">
-                                    <div class="feedback-meta">
-                                      <span class="tag">{{ getFeedbackTag(fb) }}</span>
-                                      <span class="status">{{ fb.status }}</span>
-                                      <span class="time">{{ fb.createdAt | date:'MM-dd HH:mm' }}</span>
-                                    </div>
-                                    <div class="actions" style="display:flex;gap:8px;">
-                                      @if (fb.status === '待处理') { <button class="primary-btn" (click)="updateFeedbackStatus(fb.id, '处理中')" [disabled]="isReadOnly()">标记处理中</button> }
-                                      @if (fb.status !== '已解决') { <button class="secondary-btn" (click)="updateFeedbackStatus(fb.id, '已解决')" [disabled]="isReadOnly()">标记已解决</button> }
+                      <!-- 订单创建、确认需求、售后:统一纵向内容容器(保留原功能区) -->
+                      @if (sec.key !== 'delivery') {
+                        <div class="section-vertical">
+                          @for (stage of sec.stages; track stage) {
+                            <div class="vertical-stage-block" [class.active]="getStageStatus(stage) === 'active'">
+                              <div class="vertical-stage-header">
+                                <span class="dot" [class.completed]="getStageStatus(stage) === 'completed'" [class.active]="getStageStatus(stage) === 'active'"></span>
+                                <h3>{{ stage }}</h3>
+                              </div>
+                              <div class="vertical-stage-body">
+                                <!-- 复用原模板各阶段的具体内容(示例:尾款结算等) -->
+                                @if (stage === '尾款结算') {
+                                  @if (settlements.length > 0) {
+                                    <div class="settlement-table">
+                                      <table>
+                                        <thead>
+                                          <tr>
+                                            <th>阶段</th>
+                                            <th>比例</th>
+                                            <th>金额</th>
+                                            <th>状态</th>
+                                            <th>时间</th>
+                                          </tr>
+                                        </thead>
+                                        <tbody>
+                                          @for (st of settlements; track st.id) {
+                                            <tr>
+                                              <td>{{ st.stage }}</td>
+                                              <td>{{ st.percentage }}%</td>
+                                              <td>¥{{ st.amount | number:'1.0-0' }}</td>
+                                              <td>{{ st.status }}</td>
+                                              <td>{{ (st.settledAt || st.createdAt) | date:'MM-dd HH:mm' }}</td>
+                                            </tr>
+                                          }
+                                        </tbody>
+                                      </table>
                                     </div>
-                                  </div>
-                                  <div class="feedback-content">{{ fb.content }}</div>
-                                </div>
-                              }
-                            </div>
-                          </div>
-                        }
-
-                        @if (stage === '尾款结算') {
-                          @if (settlements.length > 0) {
-                            <div class="settlement-table">
-                              <table>
-                                <thead>
-                                  <tr>
-                                    <th>阶段</th>
-                                    <th>比例</th>
-                                    <th>金额</th>
-                                    <th>状态</th>
-                                    <th>时间</th>
-                                  </tr>
-                                </thead>
-                                <tbody>
-                                  @for (st of settlements; track st.id) {
-                                    <tr>
-                                      <td>{{ st.stage }}</td>
-                                      <td>{{ st.percentage }}%</td>
-                                      <td>¥{{ st.amount | number:'1.0-0' }}</td>
-                                      <td>{{ st.status }}</td>
-                                      <td>{{ (st.settledAt || st.createdAt) | date:'MM-dd HH:mm' }}</td>
-                                    </tr>
                                   }
-                                </tbody>
-                              </table>
+                                }
+                                @if (stage === '客户评价') {
+                                  <div class="empty">此处可扩展客户评价表单/列表</div>
+                                }
+                                @if (stage === '投诉处理') {
+                                  <div class="empty">此处可扩展投诉处理流程/记录</div>
+                                }
+                                @if (stage === '订单创建' || stage === '需求沟通' || stage === '方案确认') {
+                                  <div class="empty">该阶段的详细表单与内容保持与现有功能一致,后续可按需接入</div>
+                                }
+                              </div>
                             </div>
                           }
-                        }
-
-                      </div>
+                        </div>
+                      }
                     </div>
                   }
                 }
-                </div>
               </div>
+
+              <!-- 保留原下方按当前激活阶段显示的旧详情网格(可逐步淘汰) -->
+              <!-- ... existing code ... -->
+
             </div>
           </div>
         </div>
+      </div>
     }
 
     <!-- 项目成员标签页 -->
@@ -433,3 +383,41 @@
     <!-- ... existing code ... -->
   </div>
 </div>
+
+<!-- 渲染阶段:上传大图 弹窗(基于 @if 控制显示) -->
+@if (showRenderUploadModal) {
+  <div class="modal-backdrop" (click)="closeRenderUploadModal()">
+    <div class="modal" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>上传渲染大图</h3>
+        <button class="close-button" (click)="closeRenderUploadModal()">×</button>
+      </div>
+      <div class="modal-body">
+        <div style="display:flex; gap:12px; align-items:center; margin-bottom:12px;">
+          <input #renderFileInput type="file" accept="{{allowedImageTypes}}" multiple (change)="onRenderLargePicsSelected($event)" style="display:none" />
+          <button class="secondary-btn" (click)="renderFileInput.click()">选择文件</button>
+          <span class="hint">支持 {{allowedImageTypes}};将校验4K标准(最大边≥4000px)</span>
+        </div>
+        @if (pendingRenderLargeItems.length > 0) {
+          <div class="thumb-list" style="max-height:320px; overflow:auto;">
+            @for (item of pendingRenderLargeItems; track item.id) {
+              <div class="thumb-item">
+                <img [src]="item.url" [alt]="item.name" />
+                <div class="thumb-meta">
+                  <span class="name" [title]="item.name">{{ item.name }}</span>
+                </div>
+              </div>
+            }
+          </div>
+        }
+        @if (pendingRenderLargeItems.length === 0) {
+          <div class="empty-state" style="padding:12px;color:#6b7280;">尚未选择文件</div>
+        }
+      </div>
+      <div class="modal-footer">
+        <button class="secondary-btn" (click)="closeRenderUploadModal()">取消</button>
+        <button class="primary-btn" [disabled]="pendingRenderLargeItems.length === 0" (click)="confirmRenderUpload()">确认上传</button>
+      </div>
+    </div>
+  </div>
+}

+ 5 - 4
src/app/pages/designer/project-detail/project-detail.scss

@@ -1171,10 +1171,10 @@ h4{font-size:$ios-font-size-sm;font-weight:$ios-font-weight-medium;color:$ios-te
 .upload-header h4 { margin:0; font-size:$ios-font-size-base; }
 .upload-header .hint { color:$ios-text-secondary; font-size:$ios-font-size-xs; }
 .upload-actions { margin-bottom: 12px; }
-.thumb-list { display:flex; gap:12px; flex-wrap:wrap; }
-.thumb-item { width:120px; background:#fff; border:1px solid #eee; border-radius:8px; overflow:hidden; display:flex; flex-direction:column; }
-.thumb-item img { width:100%; height:88px; object-fit:cover; background:#f2f2f2; }
-.thumb-meta { display:flex; flex-direction:column; gap:4px; padding:6px 8px; }
+.thumb-list { display:grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap:12px; }
+.thumb-item { background:#fff; border:1px solid $ios-border; border-radius:8px; overflow:hidden; display:flex; flex-direction:column; }
+.thumb-item img { width:100%; height:100px; object-fit:cover; background:#f2f2f2; }
+.thumb-meta { padding:8px; display:flex; flex-direction:column; gap:4px; }
 .thumb-meta .name { font-size:$ios-font-size-xs; color:$ios-text-primary; line-height:1.2; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
 .thumb-meta .size { font-size:$ios-font-size-xs; color:$ios-text-secondary; }
 button.link { background:none; border:none; color:$ios-primary; cursor:pointer; padding:6px 8px; text-align:left; }
@@ -1186,6 +1186,7 @@ button.link.danger { color:$ios-danger; }
 .modal-header { display:flex; align-items:center; justify-content:space-between; padding:12px 16px; border-bottom:1px solid $ios-border; }
 .modal-body { padding:16px; }
 .modal-footer { padding:12px 16px; border-top:1px solid $ios-border; display:flex; gap:12px; justify-content:flex-end; }
+.close-button { background: transparent; border: none; font-size: 20px; cursor: pointer; line-height: 1; }
 
 /* 兼容暗色系(若未来启用) */
 @media (prefers-color-scheme: dark) {

+ 59 - 4
src/app/pages/designer/project-detail/project-detail.ts

@@ -49,6 +49,9 @@ interface TimelineEvent {
   description: string;
 }
 
+// 新增:四大板块键类型(顶层声明,供组件与模板共同使用)
+type SectionKey = 'order' | 'requirements' | 'delivery' | 'aftercare';
+
 @Component({
   selector: 'app-project-detail',
   imports: [CommonModule, FormsModule],
@@ -74,21 +77,28 @@ export class ProjectDetail implements OnInit, OnDestroy {
   showDropdown: boolean = false;
   currentStage: string = '';
   // 新增:10阶段顺序(串式流程)
-  stageOrder: ProjectStage[] = ['订单创建', '需求沟通', '方案确认', '建模', '软装', '渲染', '后期', '尾款结算', '客户评价', '投诉处理'];
+  stageOrder: ProjectStage[] = ['订单创建', '需求沟通', '方案确认', '建模', '软装', '渲染', '尾款结算', '客户评价', '投诉处理'];
   // 新增:阶段展开状态(默认全部收起,当前阶段在数据加载后自动展开)
-  expandedStages: Record<ProjectStage, boolean> = {
+  expandedStages: Partial<Record<ProjectStage, boolean>> = {
     '订单创建': false,
     '需求沟通': false,
     '方案确认': false,
     '建模': false,
     '软装': false,
     '渲染': false,
-    '后期': false,
     '尾款结算': false,
     '客户评价': false,
     '投诉处理': false,
   };
-  
+
+  // 新增:四大板块定义与展开状态
+  sections: Array<{ key: SectionKey; label: string; stages: ProjectStage[] }> = [
+    { key: 'order', label: '订单创建', stages: ['订单创建'] },
+    { key: 'requirements', label: '确认需求', stages: ['需求沟通', '方案确认'] },
+    { key: 'delivery', label: '交付执行', stages: ['建模', '软装', '渲染'] },
+    { key: 'aftercare', label: '售后', stages: ['尾款结算', '客户评价', '投诉处理'] }
+  ];
+  expandedSection: SectionKey | null = null;
   // 渲染异常反馈相关属性
   exceptionType: 'failed' | 'stuck' | 'quality' | 'other' = 'failed';
   exceptionDescription: string = '';
@@ -518,6 +528,9 @@ export class ProjectDetail implements OnInit, OnDestroy {
         if (this.stageOrder.includes(project.currentStage)) {
           this.expandedStages[project.currentStage] = true;
         }
+        // 新增:根据当前阶段默认展开所属板块
+        const currentSec = this.getSectionKeyForStage(project.currentStage as ProjectStage);
+        this.expandedSection = currentSec;
       }
       // 检查技能匹配度
       this.checkSkillMismatch();
@@ -1139,4 +1152,46 @@ export class ProjectDetail implements OnInit, OnDestroy {
     if (target) this.revokeUrl(target.url);
     this.renderLargeImages = this.renderLargeImages.filter(i => i.id !== id);
   }
+
+// 根据阶段映射所属板块
+getSectionKeyForStage(stage: ProjectStage): SectionKey {
+switch (stage) {
+case '订单创建':
+return 'order';
+case '需求沟通':
+case '方案确认':
+return 'requirements';
+case '建模':
+case '软装':
+case '渲染':
+return 'delivery';
+case '尾款结算':
+case '客户评价':
+case '投诉处理':
+return 'aftercare';
+default:
+return 'order';
+}
+}
+
+// 获取板块状态:completed/active/pending
+getSectionStatus(key: SectionKey): 'completed' | 'active' | 'pending' {
+  const current = this.project?.currentStage as ProjectStage | undefined;
+  if (!current) return 'pending';
+
+  const currentSection = this.getSectionKeyForStage(current);
+  const sectionOrder = this.sections.map(s => s.key);
+  const currentIdx = sectionOrder.indexOf(currentSection);
+  const idx = sectionOrder.indexOf(key);
+  if (idx === -1 || currentIdx === -1) return 'pending';
+
+  if (idx < currentIdx) return 'completed';
+  if (idx === currentIdx) return 'active';
+  return 'pending';
+}
+
+// 切换四大板块(单展开)
+toggleSection(key: SectionKey): void {
+  this.expandedSection = key;
+}
 }

+ 169 - 58
src/app/pages/team-leader/dashboard/dashboard.html

@@ -38,7 +38,101 @@
   <section class="monitoring-section">
     <div class="section-header">
       <h2>项目监控大盘</h2>
+      <div class="section-actions">
+        @if (selectedStatus !== 'all') {
+          <button class="btn-link" (click)="resetStatusFilter()">返回全部项目</button>
+        }
+        <button class="btn-toggle-view" (click)="toggleView()">{{ showGanttView ? '返回看板' : '切换视图' }}</button>
+      </div>
+    </div>
+
+    <!-- 新增:工作量概览(与筛选联动,按设计师/会员类型分组,堆叠显示紧急程度) -->
+    <div class="workload-summary">
+      <div class="summary-header">
+        <h3>工作量概览</h3>
+        <div class="summary-actions">
+          <div class="dimension-switch">
+            <button [class.active]="workloadDimension === 'designer'" (click)="setWorkloadDimension('designer')">按设计师</button>
+            <button [class.active]="workloadDimension === 'member'" (click)="setWorkloadDimension('member')">按会员类型</button>
+          </div>
+        </div>
+      </div>
+      <div #workloadChartRef class="workload-chart"></div>
+    </div>
+    @if (showGanttView) {
+      <div class="gantt-card">
+        <div class="gantt-header">
+          <div class="title">负载日历(甘特)</div>
+          <div class="hint">
+            @if (ganttMode === 'project') { 颜色标识紧急程度:红=高,橙=中,绿=低 } @else { 设计师排班:按项目数量由排满到空闲排列 }
+          </div>
+          <div class="scale-switch">
+            <button [class.active]="ganttScale === 'week'" (click)="setGanttScale('week')">周</button>
+            <button [class.active]="ganttScale === 'month'" (click)="setGanttScale('month')">月</button>
+          </div>
+          <div class="search-box">
+            <input type="search" placeholder="搜索项目/设计师/风格关键词" [(ngModel)]="searchTerm" (input)="onSearchChange()" (focus)="onSearchFocus()" (blur)="onSearchBlur()" />
+            @if (showSuggestions) {
+              <div class="suggestion-panel">
+                @if (searchSuggestions.length > 0) {
+                  <ul>
+                    @for (suggest of searchSuggestions; track suggest.id) {
+                      <li (mousedown)="selectSuggestion(suggest)">
+                        <div class="line-1">
+                          <span class="name">{{ suggest.name }}</span>
+                          <span class="badge" [class.vip]="suggest.memberType==='vip'">{{ suggest.memberType==='vip' ? 'VIP' : '普通' }}</span>
+                          <span class="urgency" [class]="'u-' + suggest.urgency">{{ getUrgencyLabel(suggest.urgency) }}</span>
+                        </div>
+                        <div class="line-2">
+                          <span class="designer">{{ suggest.designerName || '未分配' }}</span>
+                          <span class="deadline">{{ suggest.deadline | date:'MM-dd' }}</span>
+                        </div>
+                      </li>
+                    }
+                  </ul>
+                } @else {
+                  <div class="empty">抱歉,没有检索到哦</div>
+                }
+              </div>
+            }
+          </div>
+          <div class="mode-switch" [attr.data-active]="ganttMode">
+            <button [class.active]="ganttMode === 'project'" (click)="setGanttMode('project')">按项目</button>
+            <button [class.active]="ganttMode === 'designer'" (click)="setGanttMode('designer')">设计师排班</button>
+          </div>
+        </div>
+        <div #ganttChartRef class="gantt-chart"></div>
+      </div>
+    }
+
+    @if (!showGanttView) {
       <div class="section-filters">
+        <div class="search-box">
+          <input type="search" class="input-search" placeholder="搜索项目/设计师/风格关键词" [(ngModel)]="searchTerm" (input)="onSearchChange()" (focus)="onSearchFocus()" (blur)="onSearchBlur()" />
+          @if (showSuggestions) {
+            <div class="suggestion-panel">
+              @if (searchSuggestions.length > 0) {
+                <ul>
+                  @for (suggest of searchSuggestions; track suggest.id) {
+                    <li (mousedown)="selectSuggestion(suggest)">
+                      <div class="line-1">
+                        <span class="name">{{ suggest.name }}</span>
+                        <span class="badge" [class.vip]="suggest.memberType==='vip'">{{ suggest.memberType==='vip' ? 'VIP' : '普通' }}</span>
+                        <span class="urgency" [class]="'u-' + suggest.urgency">{{ getUrgencyLabel(suggest.urgency) }}</span>
+                      </div>
+                      <div class="line-2">
+                        <span class="designer">{{ suggest.designerName || '未分配' }}</span>
+                        <span class="deadline">{{ suggest.deadline | date:'MM-dd' }}</span>
+                      </div>
+                    </li>
+                  }
+                </ul>
+              } @else {
+                <div class="empty">抱歉,没有检索到哦</div>
+              }
+            </div>
+          }
+        </div>
         <select (change)="filterProjects($event)" class="custom-select">
           <option value="all">全部项目</option>
           <option value="soft">软装项目</option>
@@ -85,69 +179,69 @@
           <button [class.active]="selectedTimeWindow === 'sevenDays'" (click)="filterByTimeWindow('sevenDays')">7天内</button>
         </div>
       </div>
-    </div>
-    
-    <!-- 项目看板 - 横向展开10个项目阶段 -->
-    <div class="project-kanban">
-      <!-- 新增:公共横向滚动容器,保证表头与表体同步滚动 -->
-      <div class="kanban-scroll">
-        <!-- 阶段标题 -->
-        <div class="kanban-header">
-          @for (core of corePhases; track core.id) {
-            <div class="kanban-column-header">
-              <h3>{{ core.name }}</h3>
-              <span class="stage-count">{{ getProjectCountByCorePhase(core.id) }}</span>
-            </div>
-          }
-        </div>
-        <!-- 项目卡片 -->
-        <div class="kanban-body">
-          @for (core of corePhases; track core.id) {
-            <div class="kanban-column">
-              @for (project of getProjectsByCorePhase(core.id); track project.id) {
-                <div class="project-card" 
-                     (click)="viewProjectDetails(project.id)"
-                     [class.overdue]="project.isOverdue" 
-                     [class.high-urgency]="project.urgency === 'high'"
-                     [class.due-soon]="project.dueSoon && !project.isOverdue">
-                  <div class="project-card-header">
-                    <h4 [routerLink]="['/team-leader/project-detail', project.id]" (click)="$event.stopPropagation()">{{ project.name }}</h4>
-                    <div class="right-badges">
-                      <span class="member-badge" [class.vip]="project.memberType === 'vip'">{{ project.memberType === 'vip' ? 'VIP' : '普通' }}</span>
-                      <span class="project-urgency" [class]="'urgency-' + project.urgency">{{ getUrgencyLabel(project.urgency) }}</span>
+      
+      <!-- 项目看板 - 横向展开10个项目阶段 -->
+      <div class="project-kanban">
+        <!-- 新增:公共横向滚动容器,保证表头与表体同步滚动 -->
+        <div class="kanban-scroll">
+          <!-- 阶段标题 -->
+          <div class="kanban-header">
+            @for (core of corePhases; track core.id) {
+              <div class="kanban-column-header">
+                <h3>{{ core.name }}</h3>
+                <span class="stage-count">{{ getProjectCountByCorePhase(core.id) }}</span>
+              </div>
+            }
+          </div>
+          <!-- 项目卡片 -->
+          <div class="kanban-body">
+            @for (core of corePhases; track core.id) {
+              <div class="kanban-column">
+                @for (project of getProjectsByCorePhase(core.id); track project.id) {
+                  <div class="project-card" 
+                       (click)="viewProjectDetails(project.id)"
+                       [class.overdue]="project.isOverdue" 
+                       [class.high-urgency]="project.urgency === 'high'"
+                       [class.due-soon]="project.dueSoon && !project.isOverdue">
+                    <div class="project-card-header">
+                      <h4 [routerLink]="['/team-leader/project-detail', project.id]" (click)="$event.stopPropagation()">{{ project.name }}</h4>
+                      <div class="right-badges">
+                        <span class="member-badge" [class.vip]="project.memberType === 'vip'">{{ project.memberType === 'vip' ? 'VIP' : '普通' }}</span>
+                        <span class="project-urgency" [class]="'urgency-' + project.urgency">{{ getUrgencyLabel(project.urgency) }}</span>
+                      </div>
+                    </div>
+                    <div class="project-card-content">
+                      <p>负责人: {{ project.designerName || '未分配' }}</p>
+                      <p class="deadline">{{ project.isOverdue ? '超期' + project.overdueDays + '天' : (project.dueSoon ? '临期: ' + (project.deadline | date:'MM-dd') : '截止: ' + (project.deadline | date:'MM-dd')) }}</p>
+                    </div>
+                    <div class="project-card-footer">
+                      <button (click)="viewProjectDetails(project.id); $event.stopPropagation()" class="btn-view">查看详情</button>
+                      @if (project.currentStage === 'pendingAssignment') {
+                        <button (click)="quickAssignProject(project.id); $event.stopPropagation()" class="btn-assign">分配</button>
+                      }
+                      <!-- 新增:质量评审快捷操作(保留,不影响四大板块分类) -->
+                      @if (project.currentStage === 'review' || project.currentStage === 'delivery') {
+                        <div class="inline-actions">
+                          <button class="btn-secondary" (click)="$event.stopPropagation(); reviewProjectQuality(project.id, 'excellent')">评为优秀</button>
+                          <button class="btn-secondary" (click)="$event.stopPropagation(); reviewProjectQuality(project.id, 'qualified')">评为合格</button>
+                          <button class="btn-secondary" (click)="$event.stopPropagation(); reviewProjectQuality(project.id, 'unqualified')">评为不合格</button>
+                        </div>
+                      }
                     </div>
                   </div>
-                  <div class="project-card-content">
-                    <p>负责人: {{ project.designerName || '未分配' }}</p>
-                    <p class="deadline">{{ project.isOverdue ? '超期' + project.overdueDays + '天' : (project.dueSoon ? '临期: ' + (project.expectedEndDate | date:'MM-dd') : '截止: ' + (project.expectedEndDate | date:'MM-dd')) }}</p>
-                  </div>
-                  <div class="project-card-footer">
-                    <button (click)="viewProjectDetails(project.id); $event.stopPropagation()" class="btn-view">查看详情</button>
-                    @if (project.currentStage === 'pendingAssignment') {
-                      <button (click)="quickAssignProject(project.id); $event.stopPropagation()" class="btn-assign">分配</button>
-                    }
-                    <!-- 新增:质量评审快捷操作 -->
-                    @if (project.currentStage === 'review' || project.currentStage === 'delivery') {
-                      <div class="inline-actions">
-                        <button class="btn-secondary" (click)="$event.stopPropagation(); reviewProjectQuality(project.id, 'excellent')">评为优秀</button>
-                        <button class="btn-secondary" (click)="$event.stopPropagation(); reviewProjectQuality(project.id, 'qualified')">评为合格</button>
-                        <button class="btn-secondary" (click)="$event.stopPropagation(); reviewProjectQuality(project.id, 'unqualified')">评为不合格</button>
-                      </div>
-                    }
+                }
+                @if (getProjectsByCorePhase(core.id).length === 0) {
+                  <div class="empty-column">
+                    <span class="empty-icon">📦</span>
+                    <p>暂无项目</p>
                   </div>
-                </div>
-              }
-              @if (getProjectsByCorePhase(core.id).length === 0) {
-                <div class="empty-column">
-                  <span class="empty-icon">📦</span>
-                  <p>暂无项目</p>
-                </div>
-              }
-            </div>
-          }
+                }
+              </div>
+            }
+          </div>
         </div>
       </div>
-    </div>
+    }
   </section>
 
   <!-- 待办任务优先级排序 -->
@@ -199,4 +293,21 @@
       </div>
     </div>
   }
+  @if (urgentPinnedProjects && urgentPinnedProjects.length > 0) {
+    <div class="urgent-pinned">
+      <div class="pinned-title">紧急任务固定区(超期 + 高紧急)</div>
+      <div class="pinned-list">
+        @for (p of urgentPinnedProjects.slice(0, 3); track $index) {
+          <div class="pinned-item" (click)="filterByStatus('overdue')">
+            <span class="dot dot-high"></span>
+            <span class="name">{{ p.name }}</span>
+            <span class="meta">{{ p.designerName || '未分配' }} · 超期{{ p.overdueDays }}天</span>
+          </div>
+        }
+        @if (urgentPinnedProjects.length > 3) {
+          <button class="btn-view-all" (click)="viewAllOverdueProjects()">更多…</button>
+        }
+      </div>
+    </div>
+  }
 </main>

+ 467 - 206
src/app/pages/team-leader/dashboard/dashboard.scss

@@ -1,4 +1,5 @@
-@use '../ios-theme.scss' as *;
+@import '../../../shared/styles/ios-theme';
+@import '../ios-theme.scss';
 
 :host {
   display: block;
@@ -51,21 +52,11 @@
         background-color: $ios-background;
       }
       
-      .metric-icon.warning {
-        background-color: rgba(255, 149, 0, 0.1);
-      }
+      .metric-icon.warning { background-color: rgba(255, 149, 0, 0.1); }
+      .metric-icon.info { background-color: rgba(59, 130, 246, 0.1); }
+      .metric-icon.primary { background-color: rgba(124, 58, 237, 0.1); }
       
-      .metric-icon.info {
-        background-color: rgba(59, 130, 246, 0.1);
-      }
-      
-      .metric-icon.primary {
-        background-color: rgba(124, 58, 237, 0.1);
-      }
-      
-      .metric-content {
-        flex: 1;
-      }
+      .metric-content { flex: 1; }
       
       .metric-count {
         font-size: 2rem;
@@ -87,9 +78,7 @@
 /* 极窄屏样式优化:减小列间距与列宽,保证可视密度 */
 @media (max-width: 640px) {
   .project-kanban {
-    .kanban-header {
-      gap: $ios-spacing-sm;
-    }
+    .kanban-header { gap: $ios-spacing-sm; }
     .kanban-body {
       gap: $ios-spacing-sm;
       .kanban-column {
@@ -127,12 +116,115 @@
     color: $ios-text-primary;
     margin: 0;
   }
+  .section-actions {
+    display: inline-flex;
+    align-items: center;
+    gap: 12px;
+    .btn-link {
+      background: transparent;
+      border: none;
+      color: #0969da;
+      cursor: pointer;
+      padding: 4px 6px;
+      &:hover { text-decoration: underline; }
+    }
+  }
+}
+
+.btn-toggle-view {
+  padding: $ios-spacing-sm $ios-spacing-md;
+  border-radius: $ios-radius-md;
+  border: 1px solid $ios-border;
+  background: linear-gradient(180deg, #fff, #f8fafc);
+  color: $ios-text-primary;
+  font-size: $ios-font-size-sm;
+  cursor: pointer;
+  box-shadow: $ios-shadow-sm;
+  transition: all .2s ease;
+  &:hover {
+    transform: translateY(-1px);
+    box-shadow: $ios-shadow-card;
+  }
+}
+
+.gantt-card {
+  background: $ios-card-background;
+  border: 1px solid $ios-border;
+  border-radius: $ios-radius-lg;
+  box-shadow: $ios-shadow-card;
+  padding: $ios-spacing-lg;
+  margin-bottom: $ios-spacing-xl;
+  position: relative; // 确保内部绝对定位下拉基于该容器
+  
+  .gantt-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: $ios-spacing-md;
+    overflow: visible; // 避免头部区域裁剪下拉
+    
+    .title {
+      font-size: $ios-font-size-md;
+      font-weight: $ios-font-weight-semibold;
+      color: $ios-text-primary;
+    }
+    .hint {
+      font-size: $ios-font-size-xs;
+      color: $ios-text-secondary;
+    }
+    // 布局调整:搜索框放在同层级最右边,模式切换在其左侧
+    .scale-switch { margin-left: 0; }
+    .mode-switch { order: 3; margin-left: 8px; }
+    .search-box { order: 4; margin-left: auto; position: relative; }
+    // 甘特头部中的搜索建议下拉为悬浮层,不占据文档流
+    .search-box {
+      .suggestion-panel {
+        position: absolute;
+        top: calc(100% + 6px);
+        right: 0;
+        min-width: 260px;
+        width: max(100%, 360px);
+        max-width: 520px;
+        background: #fff;
+        border: 1px solid #e5e7eb;
+        border-radius: 10px;
+        box-shadow: 0 12px 28px rgba(0,0,0,.12), 0 2px 8px rgba(0,0,0,.06);
+        z-index: 20;
+        padding: 6px;
+      }
+      .suggestion-panel ul { list-style: none; margin: 0; padding: 0; max-height: 320px; overflow-y: auto; }
+      .suggestion-panel li {
+        padding: 8px 10px;
+        border-radius: 8px;
+        cursor: pointer;
+        transition: background .15s ease;
+        display: flex;
+        flex-direction: column;
+        gap: 4px;
+      }
+      .suggestion-panel li:hover { background: #f3f4f6; }
+      .suggestion-panel .line-1 { display: flex; align-items: center; gap: 8px; }
+      .suggestion-panel .line-1 .name { font-weight: 600; color: #111827; flex: 1; min-width: 0; }
+      .suggestion-panel .line-1 .badge { font-size: 12px; padding: 2px 6px; border-radius: 999px; background: #eef2ff; color: #4f46e5; }
+      .suggestion-panel .line-1 .badge.vip { background: #ede9fe; color: #7c3aed; }
+      .suggestion-panel .line-1 .urgency { font-size: 12px; }
+      .suggestion-panel .line-2 { display: flex; align-items: center; justify-content: space-between; color: #6b7280; font-size: 12px; }
+      .suggestion-panel .empty { padding: 10px 12px; color: #6b7280; font-size: 13px; }
+    }
+  }
+
+  .gantt-chart {
+    width: 100%;
+    height: 420px;
+  }
 }
 
 .section-filters {
   display: flex;
   gap: $ios-spacing-md;
-  flex-wrap: wrap; // 小屏换行
+  flex-wrap: wrap;
+  align-items: center;
+  overflow: visible; // 避免看板筛选区搜索建议被裁剪
   
   .custom-select {
     padding: $ios-spacing-sm $ios-spacing-md;
@@ -145,113 +237,96 @@
     transition: all 0.2s ease;
     min-width: 140px;
   }
-}
 
-// 项目卡片补充样式
-.project-kanban {
-  .kanban-body {
-    .kanban-column {
-      .project-card {
-        &.due-soon {
-          border-left: 4px solid $ios-warning; // 黄色标识临期
-        }
-        .right-badges {
-          display: flex;
-          align-items: center;
-          gap: 6px;
-        }
-        .member-badge {
-          font-size: 10px;
-          padding: 2px 6px;
-          border-radius: $ios-radius-full;
-          background-color: rgba(59, 130, 246, 0.08);
-          color: $ios-info;
-          &.vip {
-            background-color: rgba(124, 58, 237, 0.12);
-            color: $ios-primary;
-            font-weight: $ios-font-weight-semibold;
-          }
-        }
+  .search-box {
+    margin-right: 8px;
+    position: relative; // 作为下拉定位参考
+    .input-search {
+      width: 260px;
+      padding: 8px 12px;
+      border: 1px solid #e5e7eb;
+      border-radius: 8px;
+      font-size: 14px;
+      outline: none;
+      transition: border-color .2s ease, box-shadow .2s ease;
+      &:focus {
+        border-color: #3b82f6;
+        box-shadow: 0 0 0 3px rgba(59,130,246,0.15);
       }
     }
+    // 看板筛选区域同样使用悬浮层下拉
+    .suggestion-panel {
+      position: absolute;
+      top: calc(100% + 6px);
+      right: 0;
+      min-width: 260px;
+      width: max(100%, 360px);
+      max-width: 520px;
+      background: #fff;
+      border: 1px solid #e5e7eb;
+      border-radius: 10px;
+      box-shadow: 0 12px 28px rgba(0,0,0,.12), 0 2px 8px rgba(0,0,0,.06);
+      z-index: 20;
+      padding: 6px;
+    }
+    .suggestion-panel ul { list-style: none; margin: 0; padding: 0; max-height: 320px; overflow-y: auto; }
+    .suggestion-panel li {
+      padding: 8px 10px;
+      border-radius: 8px;
+      cursor: pointer;
+      transition: background .15s ease;
+      display: flex;
+      flex-direction: column;
+      gap: 4px;
+    }
+    .suggestion-panel li:hover { background: #f3f4f6; }
+    .suggestion-panel .line-1 { display: flex; align-items: center; gap: 8px; }
+    .suggestion-panel .line-1 .name { font-weight: 600; color: #111827; flex: 1; min-width: 0; }
+    .suggestion-panel .line-1 .badge { font-size: 12px; padding: 2px 6px; border-radius: 999px; background: #eef2ff; color: #4f46e5; }
+    .suggestion-panel .line-1 .badge.vip { background: #ede9fe; color: #7c3aed; }
+    .suggestion-panel .line-1 .urgency { font-size: 12px; }
+    .suggestion-panel .line-2 { display: flex; align-items: center; justify-content: space-between; color: #6b7280; font-size: 12px; }
+    .suggestion-panel .empty { padding: 10px 12px; color: #6b7280; font-size: 13px; }
   }
 }
 
-/* 项目监控大盘样式 */
-.monitoring-section {
-  background-color: $ios-card-background;
-  border-radius: $ios-radius-lg;
-  padding: $ios-spacing-xl;
-  margin-bottom: $ios-spacing-xl;
-  box-shadow: $ios-shadow-card;
-  position: relative;
-  overflow: hidden;
-  
-  // 科技感背景元素
-  &::before {
-    content: '';
-    position: absolute;
-    top: 0;
-    right: 0;
-    width: 300px;
-    height: 300px;
-    background: linear-gradient(135deg, rgba(124, 58, 237, 0.05), transparent);
-    border-radius: 50%;
-    transform: translate(50%, -50%);
-    z-index: 0;
-  }
-}
-
-/* 项目看板样式 - 横向展开10个项目阶段 */
+// 项目卡片与看板样式
 .project-kanban {
   position: relative;
   z-index: 1;
   
-  // 新增:公共横向滚动容器,保持表头与表体同步滚动
+  // 公共横向滚动容器
   .kanban-scroll {
     overflow-x: auto;
-    padding-bottom: $ios-spacing-md; // 与原来kanban-body一致的底部留白
-    -webkit-overflow-scrolling: touch; // 触屏设备启用惯性滚动
+    padding-bottom: $ios-spacing-md;
+    -webkit-overflow-scrolling: touch;
 
-    // 滚动条样式沿用原有
-    &::-webkit-scrollbar {
-      height: 6px;
-    }
-    
+    &::-webkit-scrollbar { height: 6px; }
     &::-webkit-scrollbar-track {
       background: $ios-background;
       border-radius: $ios-radius-full;
     }
-    
     &::-webkit-scrollbar-thumb {
       background: $ios-border;
       border-radius: $ios-radius-full;
     }
-    
-    &::-webkit-scrollbar-thumb:hover {
-      background: $ios-text-tertiary;
-    }
+    &::-webkit-scrollbar-thumb:hover { background: $ios-text-tertiary; }
 
-    // 让表头和表体在同一行内布局,从而一起横向滚动
-    .kanban-header,
-    .kanban-body {
-      width: max-content; // 根据列数量自适应宽度
-    }
+    .kanban-header, .kanban-body { width: max-content; }
   }
   
   // 看板标题行
   .kanban-header {
     position: sticky;
-    top: 0; // 如需避开全局固定导航,可改为具体偏移值
-    z-index: 2; // 位于列内容之上
-    background: $ios-card-background; // 与父容器一致,防止滚动时透底
-    border-bottom: 1px solid $ios-border; // 细分隔线,避免视觉抖动
+    top: 0;
+    z-index: 2;
+    background: $ios-card-background;
+    border-bottom: 1px solid $ios-border;
     display: flex;
     gap: $ios-spacing-md;
     margin-bottom: $ios-spacing-md;
     
     .kanban-column-header {
-      // 固定列宽,确保与内容列完美对齐
       flex: 0 0 180px;
       min-width: 180px;
       max-width: 180px;
@@ -286,7 +361,6 @@
     display: flex;
     gap: $ios-spacing-md;
     
-    // 看板列
     .kanban-column {
       flex: 1;
       min-width: 180px;
@@ -297,23 +371,15 @@
       border: 1px solid $ios-border;
       padding: $ios-spacing-sm;
       overflow-y: auto;
-      -webkit-overflow-scrolling: touch; // 移动端列内纵向惯性滚动
-      
-      // 滚动条样式
-      &::-webkit-scrollbar {
-        width: 4px;
-      }
-      
-      &::-webkit-scrollbar-track {
-        background: transparent;
-      }
+      -webkit-overflow-scrolling: touch;
       
+      &::-webkit-scrollbar { width: 4px; }
+      &::-webkit-scrollbar-track { background: transparent; }
       &::-webkit-scrollbar-thumb {
         background: $ios-border;
         border-radius: $ios-radius-full;
       }
       
-      // 项目卡片
       .project-card {
         background-color: $ios-card-background;
         border-radius: $ios-radius-md;
@@ -322,26 +388,18 @@
         border: 1px solid $ios-border;
         box-shadow: $ios-shadow-sm;
         transition: all 0.2s ease;
-        cursor: pointer; // 整卡可点击指针
-        user-select: none; // 避免误选中文字
+        cursor: pointer;
+        user-select: none;
         
         &:hover {
           transform: translateY(-2px);
           box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
         }
         
-        &:active {
-          transform: translateY(-1px);
-          opacity: 0.98;
-        }
-        
-        &.overdue {
-          border-left: 4px solid $ios-danger;
-        }
-        
-        &.high-urgency {
-          border-left: 4px solid $ios-warning;
-        }
+        &:active { transform: translateY(-1px); opacity: 0.98; }
+        &.overdue { border-left: 4px solid $ios-danger; }
+        &.high-urgency { border-left: 4px solid $ios-warning; }
+        &.due-soon { border-left: 4px solid $ios-warning; }
         
         .project-card-header {
           display: flex;
@@ -355,9 +413,7 @@
             color: $ios-primary;
             margin: 0;
             cursor: pointer;
-            &:hover {
-              text-decoration: underline;
-            }
+            &:hover { text-decoration: underline; }
           }
           
           .project-urgency {
@@ -367,20 +423,9 @@
             font-weight: $ios-font-weight-medium;
           }
           
-          .urgency-high {
-            background-color: rgba(239, 68, 68, 0.1);
-            color: $ios-danger;
-          }
-          
-          .urgency-medium {
-            background-color: rgba(255, 149, 0, 0.1);
-            color: $ios-warning;
-          }
-          
-          .urgency-low {
-            background-color: rgba(59, 130, 246, 0.1);
-            color: $ios-info;
-          }
+          .urgency-high { background-color: rgba(239, 68, 68, 0.1); color: $ios-danger; }
+          .urgency-medium { background-color: rgba(255, 149, 0, 0.1); color: $ios-warning; }
+          .urgency-low { background-color: rgba(59, 130, 246, 0.1); color: $ios-info; }
         }
         
         .project-card-content {
@@ -392,10 +437,7 @@
             margin: 0 0 4px 0;
           }
           
-          .deadline {
-            font-size: 10px;
-            color: $ios-text-tertiary;
-          }
+          .deadline { font-size: 10px; color: $ios-text-tertiary; }
         }
         
         .project-card-footer {
@@ -412,23 +454,30 @@
             transition: all 0.2s ease;
           }
           
-          .btn-view {
-            background-color: $ios-primary;
-            color: $ios-background;
-          }
-          
-          .btn-assign {
-            background-color: $ios-success;
-            color: $ios-background;
-          }
-          
-          button:hover {
-            opacity: 0.9;
+          .btn-view { background-color: $ios-primary; color: $ios-background; }
+          .btn-assign { background-color: $ios-success; color: $ios-background; }
+          button:hover { opacity: 0.9; }
+        }
+        
+        .right-badges {
+          display: flex;
+          align-items: center;
+          gap: 6px;
+        }
+        .member-badge {
+          font-size: 10px;
+          padding: 2px 6px;
+          border-radius: $ios-radius-full;
+          background-color: rgba(59, 130, 246, 0.08);
+          color: $ios-info;
+          &.vip {
+            background-color: rgba(124, 58, 237, 0.12);
+            color: $ios-primary;
+            font-weight: $ios-font-weight-semibold;
           }
         }
       }
       
-      // 空状态
       .empty-column {
         display: flex;
         flex-direction: column;
@@ -452,6 +501,33 @@
   }
 }
 
+/* 项目监控大盘样式 */
+.monitoring-section {
+  background-color: $ios-card-background;
+  border-radius: $ios-radius-lg;
+  padding: $ios-spacing-xl;
+  margin-bottom: $ios-spacing-xl;
+  box-shadow: $ios-shadow-card;
+  position: relative;
+  overflow: visible; // 允许搜索建议下拉面板不被裁剪
+  
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    right: 0;
+    width: 300px;
+    height: 300px;
+    background: linear-gradient(135deg, rgba(124, 58, 237, 0.05), transparent);
+    border-radius: 50%;
+    transform: translate(50%, -50%);
+    z-index: 0;
+    pointer-events: none;
+  }
+
+  .gantt-card { position: relative; z-index: 1; }
+}
+
 /* 快速操作面板样式 */
 .quick-actions-section {
   background-color: $ios-card-background;
@@ -520,26 +596,13 @@
     border: 1px solid $ios-border;
     transition: $ios-feedback-hover;
     
-    &:last-child {
-      margin-bottom: 0;
-    }
-    
-    &.priority-high {
-      border-left: 4px solid $ios-danger;
-    }
-    
-    &.priority-medium {
-      border-left: 4px solid $ios-warning;
-    }
+    &:last-child { margin-bottom: 0; }
     
-    &.priority-low {
-      border-left: 4px solid $ios-info;
-    }
+    &.priority-high { border-left: 4px solid $ios-danger; }
+    &.priority-medium { border-left: 4px solid $ios-warning; }
+    &.priority-low { border-left: 4px solid $ios-info; }
     
-    &:hover {
-      transform: translateY(-1px);
-      box-shadow: $ios-shadow-sm;
-    }
+    &:hover { transform: translateY(-1px); box-shadow: $ios-shadow-sm; }
     
     .todo-header {
       display: flex;
@@ -571,10 +634,7 @@
         color: $ios-text-secondary;
       }
       
-      .task-deadline {
-        font-size: $ios-font-size-xs;
-        color: $ios-text-tertiary;
-      }
+      .task-deadline { font-size: $ios-font-size-xs; color: $ios-text-tertiary; }
     }
     
     .todo-actions {
@@ -589,9 +649,7 @@
         cursor: pointer;
         transition: $ios-feedback-tap;
         
-        &:hover {
-          background-color: $ios-primary-light;
-        }
+        &:hover { background-color: $ios-primary-light; }
       }
     }
   }
@@ -612,14 +670,8 @@
   animation: slideIn 0.3s ease-out;
   
   @keyframes slideIn {
-    from {
-      opacity: 0;
-      transform: translate(-50%, -60%);
-    }
-    to {
-      opacity: 1;
-      transform: translate(-50%, -50%);
-    }
+    from { opacity: 0; transform: translate(-50%, -60%); }
+    to { opacity: 1; transform: translate(-50%, -50%); }
   }
   
   .alert-content {
@@ -642,9 +694,7 @@
         color: $ios-text-primary;
         margin-bottom: $ios-spacing-sm;
         
-        &:last-child {
-          margin-bottom: 0;
-        }
+        &:last-child { margin-bottom: 0; }
       }
     }
     
@@ -664,9 +714,7 @@
         cursor: pointer;
         transition: $ios-feedback-tap;
         
-        &:hover {
-          background-color: $ios-primary-light;
-        }
+        &:hover { background-color: $ios-primary-light; }
       }
       
       .btn-close {
@@ -680,14 +728,12 @@
         cursor: pointer;
         transition: $ios-feedback-tap;
         
-        &:hover {
-          background-color: $ios-text-secondary;
-          color: $ios-background;
-        }
+        &:hover { background-color: $ios-text-secondary; color: $ios-background; }
       }
     }
   }
 }
+
 .time-window-buttons {
   display: inline-flex;
   gap: 8px;
@@ -721,20 +767,235 @@
   }
 }
 
-.section-header {
-  display: flex;
+.urgent-pinned {
+  margin: 8px 16px 0;
+  padding: 8px 12px;
+  background: rgba(255, 241, 241, 0.8);
+  border: 1px solid #fecaca;
+  border-radius: 8px;
+  .pinned-title {
+    font-size: 12px;
+    color: #b91c1c;
+    margin-bottom: 6px;
+    font-weight: 600;
+  }
+  .pinned-list {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    flex-wrap: wrap;
+  }
+  .pinned-item {
+    display: inline-flex;
+    align-items: center;
+    gap: 6px;
+    padding: 6px 10px;
+    border-radius: 6px;
+    background: #fff;
+    border: 1px solid #fee2e2;
+    cursor: pointer;
+    transition: box-shadow .2s ease;
+    &:hover { box-shadow: 0 1px 4px rgba(0,0,0,.1); }
+    .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
+    .dot-high { background: #ef4444; }
+    .name { font-weight: 600; color: #111827; }
+    .meta { color: #6b7280; font-size: 12px; }
+  }
+  .btn-view-all {
+    padding: 6px 10px;
+    border: none;
+    background: #ef4444;
+    color: #fff;
+    border-radius: 6px;
+    cursor: pointer;
+  }
+}
+
+/* 全局甘特头部工具条(网格布局,含缩放/搜索/模式切换) */
+.gantt-header {
+  position: relative;
+  z-index: 2;
+  display: grid;
+  grid-template-columns: 1fr auto auto auto; // title/hint | scale | search | mode
+  gap: 12px;
   align-items: center;
-  justify-content: space-between;
-  .section-actions {
+
+  .search-box {
+    input[type='search'] {
+      width: 240px;
+      padding: 8px 12px;
+      border: 1px solid #e5e7eb;
+      border-radius: 8px;
+      font-size: 14px;
+      outline: none;
+      transition: border-color .2s ease, box-shadow .2s ease;
+      &:focus {
+        border-color: #3b82f6;
+        box-shadow: 0 0 0 3px rgba(59,130,246,0.15);
+      }
+    }
+  }
+
+  .scale-switch {
+    margin-left: auto;
     display: inline-flex;
-    gap: 12px;
-    .btn-link {
+    border: 1px solid $ios-border;
+    border-radius: 8px;
+    overflow: hidden;
+    button {
+      background: #fff;
+      color: $ios-text-secondary;
+      padding: 6px 10px;
+      border: none;
+      outline: none;
+      cursor: pointer;
+      font-size: $ios-font-size-sm;
+      &:hover { background: $ios-background-secondary; }
+      &.active {
+        background: $ios-primary-light;
+        color: #fff;
+      }
+      & + button { border-left: 1px solid $ios-border; }
+    }
+  }
+
+  .mode-switch {
+    position: relative;
+    display: inline-flex;
+    align-items: center;
+    background: $ios-background;
+    border: 1px solid $ios-border;
+    border-radius: 999px;
+    padding: 2px;
+    overflow: hidden;
+    box-shadow: 0 1px 2px rgba(0,0,0,.04);
+
+    // 滑块式高亮背景
+    &::before {
+      content: '';
+      position: absolute;
+      top: 2px;
+      left: 2px;
+      bottom: 2px;
+      width: calc(50% - 2px);
+      background: linear-gradient(180deg, $ios-primary-light 0%, darken($ios-primary-light, 4%) 100%);
+      border-radius: 999px;
+      box-shadow: 0 6px 14px rgba(99,102,241,.22);
+      transform: translateX(0%);
+      transition: transform .25s cubic-bezier(.2,.8,.2,1);
+      z-index: 0;
+    }
+    &[data-active='designer']::before { transform: translateX(100%); }
+
+    button {
+      position: relative;
+      z-index: 1;
       background: transparent;
       border: none;
-      color: #0969da;
+      outline: none;
+      color: $ios-text-secondary;
+      padding: 10px 16px 10px 14px;
+      font-size: $ios-font-size-sm;
+      font-weight: $ios-font-weight-semibold;
+      letter-spacing: .2px;
+      border-radius: 999px;
       cursor: pointer;
-      padding: 4px 6px;
-      &:hover { text-decoration: underline; }
+      transition: color .2s ease, transform .05s ease;
+      display: inline-flex;
+      align-items: center;
+      gap: 6px;
+      &:hover { color: $ios-text-primary; }
+      &:active { transform: translateY(0.5px); }
+      &:focus-visible { box-shadow: 0 0 0 3px rgba(99,102,241,.25); }
+      &.active {
+        color: #fff;
+        text-shadow: 0 1px 0 rgba(0,0,0,.08);
+      }
+      &::before {
+        content: '';
+        display: inline-block;
+        width: 14px; height: 14px;
+      }
+      &:first-child::before { content: '📁'; }
+      &:last-child::before { content: '🎨'; }
+    }
+
+    @media (max-width: 640px) { button { padding: 8px 12px 8px 10px; } }
+    @media (max-width: 420px) {
+      button {
+        padding: 7px 10px 7px 9px;
+        font-size: 12px;
+      }
+    }
+    @media (prefers-reduced-motion: reduce) {
+      &::before { transition: none; }
+      button { transition: none; }
+    }
+  }
+}
+
+@media (max-width: 1024px) {
+  .gantt-header {
+    grid-template-columns: 1fr auto auto; // title/hint | scale | mode; search full row
+    grid-auto-rows: auto;
+    .search-box { grid-column: 1 / -1; }
+  }
+  .section-filters {
+    .search-box { width: 100%; }
+    .input-search { width: min(100%, 520px); }
+  }
+}
+
+@media (max-width: 640px) {
+  .gantt-header {
+    gap: 8px;
+    .search-box input[type='search'] { width: 100%; }
+  }
+  .section-filters {
+    gap: 6px;
+    .input-search { width: 100%; }
+  }
+}
+.workload-summary {
+  margin: 12px 0 16px;
+  padding: 12px 14px;
+  background: $ios-background;
+  border: 1px solid $ios-border;
+  border-radius: $ios-radius-lg;
+  box-shadow: 0 1px 2px rgba(0,0,0,.04);
+
+  .summary-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 8px;
+    h3 { font-size: $ios-font-size-md; margin: 0; color: $ios-text-primary; }
+    .summary-actions {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      .dimension-switch {
+        display: inline-flex;
+        border: 1px solid $ios-border;
+        border-radius: 999px;
+        overflow: hidden;
+        button {
+          background: #fff;
+          color: $ios-text-secondary;
+          padding: 6px 10px;
+          border: none;
+          cursor: pointer;
+          font-size: $ios-font-size-sm;
+          &:hover { background: $ios-background-secondary; }
+          &.active { background: $ios-primary-light; color: #fff; }
+          & + button { border-left: 1px solid $ios-border; }
+        }
+      }
     }
   }
+
+  .workload-chart {
+    width: 100%;
+    height: 240px;
+  }
 }

+ 637 - 37
src/app/pages/team-leader/dashboard/dashboard.ts

@@ -1,7 +1,7 @@
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { Router, RouterModule } from '@angular/router';
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy, ElementRef, ViewChild } from '@angular/core';
 import { ProjectService } from '../../../services/project.service';
 
 // 项目阶段定义
@@ -26,7 +26,9 @@ interface Project {
   memberType: 'vip' | 'normal';
   designerName: string;
   status: string;
-  expectedEndDate: Date;
+  expectedEndDate: Date; // TODO: 兼容旧字段,后续可移除
+  deadline: Date; // 真实截止时间字段
+  createdAt?: Date; // 真实开始时间字段(可选)
   isOverdue: boolean;
   overdueDays: number;
   dueSoon: boolean;
@@ -36,6 +38,8 @@ interface Project {
   // 新增:质量评级
   qualityRating?: 'excellent' | 'qualified' | 'unqualified' | 'pending';
   lastCustomerFeedback?: string;
+// 预构建的搜索索引,减少重复 toLowerCase 与拼接
+  searchIndex?: string;
 }
 
 interface TodoTask {
@@ -48,6 +52,7 @@ interface TodoTask {
   targetId: string;
 }
 
+declare const echarts: any;
 @Component({
   selector: 'app-dashboard',
   imports: [CommonModule, FormsModule, RouterModule],
@@ -55,14 +60,26 @@ interface TodoTask {
   styleUrl: './dashboard.scss'
 })
 
-export class Dashboard implements OnInit {
+export class Dashboard implements OnInit, OnDestroy {
   projects: Project[] = [];
   filteredProjects: Project[] = [];
   todoTasks: TodoTask[] = [];
   overdueProjects: Project[] = [];
+  urgentPinnedProjects: Project[] = [];
   showAlert: boolean = false;
   selectedProjectId: string = '';
+  // 新增:关键词搜索
+  searchTerm: string = '';
+  searchSuggestions: Project[] = [];
+  showSuggestions: boolean = false;
+  private hideSuggestionsTimer: any;
   
+  // 搜索性能与交互控制
+  private searchDebounceTimer: any;
+  private readonly SEARCH_DEBOUNCE_MS = 200; // 防抖时长(毫秒)
+  private readonly MIN_SEARCH_LEN = 2; // 最小触发建议长度
+  private readonly MAX_SUGGESTIONS = 8; // 建议最大条数
+  private isSearchFocused: boolean = false; // 是否处于输入聚焦态
   // 新增:临期项目与筛选状态
   dueSoonProjects: Project[] = [];
   selectedType: 'all' | 'soft' | 'hard' = 'all';
@@ -74,15 +91,15 @@ export class Dashboard implements OnInit {
   selectedTimeWindow: 'all' | 'today' | 'threeDays' | 'sevenDays' = 'all';
   designers: string[] = [];
   
-  // 新增:智能推荐设计师配置
+  // 设计师画像(用于智能推荐)
   designerProfiles: any[] = [
     { id: 'zhang', name: '张三', skills: ['现代风格', '北欧风格'], workload: 70, avgRating: 4.5, experience: 3 },
     { id: 'li', name: '李四', skills: ['新中式', '宋式风格'], workload: 45, avgRating: 4.8, experience: 5 },
     { id: 'wang', name: '王五', skills: ['北欧风格', '日式风格'], workload: 85, avgRating: 4.2, experience: 2 },
     { id: 'zhao', name: '赵六', skills: ['现代风格', '轻奢风格'], workload: 30, avgRating: 4.6, experience: 4 }
   ];
-  
-  // 定义10个项目阶段
+
+  // 10个项目阶段
   projectStages: ProjectStage[] = [
     { id: 'pendingApproval', name: '待确认', order: 1 },
     { id: 'pendingAssignment', name: '待分配', order: 2 },
@@ -96,19 +113,32 @@ export class Dashboard implements OnInit {
     { id: 'delivery', name: '交付完成', order: 10 }
   ];
 
-  // 新增:5大核心阶段
+  // 5大核心阶段(聚合展示)
   corePhases: ProjectStage[] = [
-    { id: 'preparation', name: '前期准备', order: 1 }, // 待确认、待分配
-    { id: 'design', name: '方案设计', order: 2 },     // 需求沟通、方案规划
-    { id: 'production', name: '制作执行', order: 3 }, // 建模、渲染、后期
-    { id: 'review', name: '评审修订', order: 4 },    // 评审、修改
-    { id: 'delivery', name: '交付完成', order: 5 }    // 交付
+    { id: 'order', name: '订单创建', order: 1 },        // 待确认、待分配
+    { id: 'requirements', name: '确认需求', order: 2 },  // 需求沟通、方案规划
+    { id: 'delivery', name: '交付执行', order: 3 },      // 建模、渲染、后期/评审/修改
+    { id: 'aftercare', name: '售后', order: 4 }          // 交付完成 → 售后
   ];
+  // 甘特视图开关与实例引用
+  showGanttView: boolean = false;
+  private ganttChart: any | null = null;
+  @ViewChild('ganttChartRef', { static: false }) ganttChartRef!: ElementRef<HTMLDivElement>;
+  // 新增:工作量概览图表引用与实例
+  @ViewChild('workloadChartRef', { static: false }) workloadChartRef!: ElementRef<HTMLDivElement>;
+  private workloadChart: any | null = null;
+  workloadDimension: 'designer' | 'member' = 'designer';
+  // 甘特时间尺度:仅周/月
+  ganttScale: 'week' | 'month' = 'week';
+  // 新增:甘特模式(项目 / 设计师排班)
+  ganttMode: 'project' | 'designer' = 'project';
   constructor(private projectService: ProjectService, private router: Router) {}
 
   ngOnInit(): void {
     this.loadProjects();
     this.loadTodoTasks();
+    // 首次微任务后尝试初始化一次,确保容器已渲染
+    setTimeout(() => this.updateWorkloadChart(), 0);
   }
 
   loadProjects(): void {
@@ -122,6 +152,7 @@ export class Dashboard implements OnInit {
         designerName: '张三',
         status: '进行中',
         expectedEndDate: new Date(2023, 9, 15),
+        deadline: new Date(2023, 9, 15),
         isOverdue: true,
         overdueDays: 2,
         dueSoon: false,
@@ -142,6 +173,7 @@ export class Dashboard implements OnInit {
         designerName: '李四',
         status: '进行中',
         expectedEndDate: new Date(2023, 9, 20),
+        deadline: new Date(2023, 9, 20),
         isOverdue: false,
         overdueDays: 0,
         dueSoon: false,
@@ -162,6 +194,7 @@ export class Dashboard implements OnInit {
         designerName: '王五',
         status: '进行中',
         expectedEndDate: new Date(2023, 9, 25),
+        deadline: new Date(2023, 9, 25),
         isOverdue: false,
         overdueDays: 0,
         dueSoon: false,
@@ -182,6 +215,7 @@ export class Dashboard implements OnInit {
         designerName: '赵六',
         status: '进行中',
         expectedEndDate: new Date(2023, 9, 10),
+        deadline: new Date(2023, 9, 10),
         isOverdue: true,
         overdueDays: 7,
         dueSoon: false,
@@ -203,6 +237,7 @@ export class Dashboard implements OnInit {
         designerName: '',
         status: '待分配',
         expectedEndDate: new Date(2023, 10, 5),
+        deadline: new Date(2023, 10, 5),
         isOverdue: false,
         overdueDays: 0,
         dueSoon: false,
@@ -218,6 +253,7 @@ export class Dashboard implements OnInit {
         designerName: '',
         status: '待确认',
         expectedEndDate: new Date(2023, 10, 10),
+        deadline: new Date(2023, 10, 10),
         isOverdue: false,
         overdueDays: 0,
         dueSoon: false,
@@ -233,6 +269,7 @@ export class Dashboard implements OnInit {
         designerName: '钱七',
         status: '已完成',
         expectedEndDate: new Date(2023, 9, 5),
+        deadline: new Date(2023, 9, 5),
         isOverdue: false,
         overdueDays: 0,
         dueSoon: false,
@@ -283,6 +320,7 @@ export class Dashboard implements OnInit {
         designerName,
         status,
         expectedEndDate,
+        deadline: expectedEndDate,
         isOverdue,
         overdueDays,
         dueSoon,
@@ -293,6 +331,15 @@ export class Dashboard implements OnInit {
     }
     // ===== 示例数据生成结束 =====
 
+    // 统一补齐真实时间字段(deadline/createdAt),以真实字段贯通筛选与甘特
+    const DAY = 24 * 60 * 60 * 1000;
+    this.projects = this.projects.map(p => {
+      const deadline = p.deadline || p.expectedEndDate;
+      const baseDays = p.type === 'hard' ? 30 : 14;
+      const createdAt = p.createdAt || new Date(new Date(deadline).getTime() - baseDays * DAY);
+      return { ...p, deadline, createdAt } as Project;
+    });
+
     // 筛选超期与临期项目
     this.overdueProjects = this.projects.filter(project => project.isOverdue);
     this.dueSoonProjects = this.projects.filter(project => project.dueSoon && !project.isOverdue);
@@ -384,7 +431,9 @@ export class Dashboard implements OnInit {
 
   // 筛选项目状态
   filterByStatus(status: string): void {
-    this.selectedStatus = (status && status.length ? status : 'all') as any;
+    // 点击同一状态时,切换回“全部”,以便恢复全量项目列表
+    const next = (this.selectedStatus === status) ? 'all' : (status && status.length ? status : 'all');
+    this.selectedStatus = next as any;
     this.applyFilters();
   }
   
@@ -395,16 +444,94 @@ export class Dashboard implements OnInit {
     this.applyFilters();
   }
 
+  // 新增:设计师筛选下拉事件处理
+  onDesignerChange(event: Event): void {
+    const target = event.target as HTMLSelectElement;
+    this.selectedDesigner = (target && target.value ? target.value : 'all');
+    this.applyFilters();
+  }
+
+  // 新增:会员类型筛选下拉事件处理
+  onMemberTypeChange(event: Event): void {
+    const target = event.target as HTMLSelectElement;
+    this.selectedMemberType = (target && target.value ? target.value : 'all') as any;
+    this.applyFilters();
+  }
+
   // 时间窗快捷筛选(供UI按钮触发)
   filterByTimeWindow(timeWindow: 'all' | 'today' | 'threeDays' | 'sevenDays'): void {
     this.selectedTimeWindow = timeWindow;
     this.applyFilters();
   }
 
+  // 新增:搜索输入变化
+  onSearchChange(): void {
+    if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer);
+    this.searchDebounceTimer = setTimeout(() => {
+      this.updateSearchSuggestions();
+      this.applyFilters();
+    }, this.SEARCH_DEBOUNCE_MS);
+  }
+
+  // 新增:搜索框聚焦/失焦控制建议显隐
+  onSearchFocus(): void {
+    if (this.hideSuggestionsTimer) clearTimeout(this.hideSuggestionsTimer);
+    this.isSearchFocused = true;
+    this.updateSearchSuggestions();
+  }
+  onSearchBlur(): void {
+    // 延迟隐藏以允许选择项的 mousedown 触发
+    this.isSearchFocused = false;
+    this.hideSuggestionsTimer = setTimeout(() => {
+      this.showSuggestions = false;
+    }, 150);
+  }
+
+  // 新增:更新搜索建议(不叠加其它筛选,仅基于关键字)
+  private updateSearchSuggestions(): void {
+    const q = (this.searchTerm || '').trim().toLowerCase();
+    if (q.length < this.MIN_SEARCH_LEN) {
+      this.searchSuggestions = [];
+      this.showSuggestions = false;
+      return;
+    }
+
+    const scored = this.projects
+      .filter(p => (p.searchIndex || `${(p.name || '')}|${(p.designerName || '')}`.toLowerCase()).includes(q))
+      .map(p => {
+        const dl = p.deadline || p.expectedEndDate;
+        const dlTime = dl ? new Date(dl).getTime() : NaN;
+        const daysToDl = Math.ceil(((isNaN(dlTime) ? 0 : dlTime) - Date.now()) / (1000 * 60 * 60 * 24));
+        const urgencyScore = p.urgency === 'high' ? 3 : p.urgency === 'medium' ? 2 : 1;
+        const overdueScore = p.isOverdue ? 10 : 0;
+        const score = overdueScore + (4 - urgencyScore) * 2 - (isNaN(daysToDl) ? 0 : daysToDl);
+        return { p, score };
+      })
+      .sort((a, b) => b.score - a.score)
+      .slice(0, this.MAX_SUGGESTIONS)
+      .map(x => x.p);
+
+    this.searchSuggestions = scored;
+    this.showSuggestions = this.isSearchFocused && this.searchSuggestions.length > 0;
+  }
+
+  // 新增:选择建议项
+  selectSuggestion(project: Project): void {
+    this.searchTerm = project.name;
+    this.showSuggestions = false;
+    this.viewProjectDetails(project.id);
+  }
+
   // 统一筛选
   private applyFilters(): void {
     let result = [...this.projects];
 
+    // 新增:关键词搜索(项目名 / 设计师名 / 含风格关键词的项目名)
+    const q = (this.searchTerm || '').trim().toLowerCase();
+    if (q) {
+      result = result.filter(p => (p.searchIndex || `${(p.name || '')}|${(p.designerName || '')}`.toLowerCase()).includes(q));
+    }
+
     // 类型筛选
     if (this.selectedType !== 'all') {
       result = result.filter(p => p.type === this.selectedType);
@@ -449,7 +576,7 @@ export class Dashboard implements OnInit {
       const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
       
       result = result.filter(p => {
-        const projectDeadline = new Date(p.expectedEndDate);
+        const projectDeadline = new Date(p.deadline);
         const timeDiff = projectDeadline.getTime() - today.getTime();
         const daysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
         
@@ -467,22 +594,426 @@ export class Dashboard implements OnInit {
     }
 
     this.filteredProjects = result;
+    // 新增:计算紧急任务固定区(超期 + 高紧急),与筛选联动
+    this.urgentPinnedProjects = this.filteredProjects
+      .filter(p => p.isOverdue && p.urgency === 'high')
+      .sort((a, b) => (b.overdueDays - a.overdueDays) || a.name.localeCompare(b.name, 'zh-Hans-CN'));
+    // 当显示甘特卡片时,同步刷新甘特图
+    if (this.showGanttView) {
+      this.updateGantt();
+    }
+    // 同步刷新工作量概览图
+    this.updateWorkloadChart();
   }
 
-  // 新增:设计师筛选
-  onDesignerChange(event: Event): void {
-    const target = event.target as HTMLSelectElement;
-    this.selectedDesigner = target && target.value ? target.value : 'all';
-    this.applyFilters();
+  // 切换项目看板/负载日历(甘特)视图
+  toggleView(): void {
+    this.showGanttView = !this.showGanttView;
+    if (this.showGanttView) {
+      setTimeout(() => this.initOrUpdateGantt(), 0);
+    } else {
+      if (this.ganttChart) {
+        this.ganttChart.dispose();
+        this.ganttChart = null;
+      }
+      if (this.workloadChart) {
+        this.workloadChart.dispose();
+        this.workloadChart = null;
+      }
+    }
   }
 
-  // 新增:会员类型筛选
-  onMemberTypeChange(event: Event): void {
-    const target = event.target as HTMLSelectElement;
-    this.selectedMemberType = (target && target.value ? target.value : 'all') as any;
-    this.applyFilters();
+  // 设置甘特时间尺度
+  setGanttScale(scale: 'week' | 'month'): void {
+    if (this.ganttScale !== scale) {
+      this.ganttScale = scale;
+      this.updateGantt();
+    }
+  }
+
+  // 新增:切换甘特模式
+  setGanttMode(mode: 'project' | 'designer'): void {
+    if (this.ganttMode !== mode) {
+      this.ganttMode = mode;
+      this.updateGantt();
+    }
+  }
+
+  private initOrUpdateGantt(): void {
+    if (!this.ganttChartRef) return;
+    const el = this.ganttChartRef.nativeElement;
+    if (!this.ganttChart) {
+      this.ganttChart = echarts.init(el);
+      window.addEventListener('resize', () => {
+        this.ganttChart && this.ganttChart.resize();
+      });
+    }
+    this.updateGantt();
+  }
+
+  private updateGantt(): void {
+    if (!this.ganttChart) return;
+    if (this.ganttMode === 'designer') {
+      this.updateGanttDesigner();
+      return;
+    }
+
+    // 按紧急程度从上到下排序(高->中->低),同级按到期时间升序
+    const urgencyRank: Record<'high'|'medium'|'low', number> = { high: 0, medium: 1, low: 2 };
+    const projects = [...this.filteredProjects]
+      .sort((a, b) => {
+        const u = urgencyRank[a.urgency] - urgencyRank[b.urgency];
+        if (u !== 0) return u;
+        // 二级排序:临期优先(到期更近)> 已分配人员 > VIP客户 > 其他
+        const endDiff = new Date(a.deadline).getTime() - new Date(b.deadline).getTime();
+        if (endDiff !== 0) return endDiff;
+        const assignedA = !!a.designerName;
+        const assignedB = !!b.designerName;
+        if (assignedA !== assignedB) return assignedA ? -1 : 1; // 已分配在前
+        const vipA = a.memberType === 'vip';
+        const vipB = b.memberType === 'vip';
+        if (vipA !== vipB) return vipA ? -1 : 1; // VIP在前
+        return a.name.localeCompare(b.name, 'zh-CN');
+      });
+
+    const categories = projects.map(p => p.name);
+    const urgencyMap: Record<string, 'high'|'medium'|'low'> = Object.fromEntries(projects.map(p => [p.name, p.urgency])) as any;
+
+    const colorByUrgency: Record<'high'|'medium'|'low', string> = {
+      high: '#ef4444',
+      medium: '#f59e0b',
+      low: '#22c55e'
+    } as const;
+
+    const DAY = 24 * 60 * 60 * 1000;
+
+    const data = projects.map((p, idx) => {
+      const end = new Date(p.deadline).getTime();
+      const baseDays = p.type === 'hard' ? 30 : 14;
+      const start = p.createdAt ? new Date(p.createdAt).getTime() : end - baseDays * DAY;
+      const color = colorByUrgency[p.urgency] || '#60a5fa';
+      return {
+        name: p.name,
+        value: [idx, start, end, p.designerName, p.urgency, p.memberType, p.currentStage],
+        itemStyle: { color }
+      };
+    });
+
+    // 计算时间范围(仅周/月)
+    const now = new Date();
+    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+    const todayTs = today.getTime();
+
+    let xMin: number;
+    let xMax: number;
+    let xSplitNumber: number;
+    let xLabelFormatter: (value: number) => string;
+
+    if (this.ganttScale === 'week') {
+      const day = today.getDay(); // 0=周日
+      const diffToMonday = (day === 0 ? 6 : day - 1);
+      const startOfWeek = new Date(today.getTime() - diffToMonday * DAY);
+      const endOfWeek = new Date(startOfWeek.getTime() + 7 * DAY - 1);
+      xMin = startOfWeek.getTime();
+      xMax = endOfWeek.getTime();
+      xSplitNumber = 7;
+      const WEEK_LABELS = ['周日','周一','周二','周三','周四','周五','周六'];
+      xLabelFormatter = (val) => {
+        const d = new Date(val);
+        return WEEK_LABELS[d.getDay()];
+      };
+    } else { // month
+      const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
+      const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0, 23, 59, 59, 999);
+      xMin = startOfMonth.getTime();
+      xMax = endOfMonth.getTime();
+      xSplitNumber = 4;
+      xLabelFormatter = (val) => {
+        const d = new Date(val);
+        const weekOfMonth = Math.ceil(d.getDate() / 7);
+        return `第${weekOfMonth}周`;
+      };
+    }
+
+    // 计算默认可视区,并尝试保留上一次的滚动/缩放位置
+    const total = categories.length;
+    const visible = Math.min(total, 15); // 默认首屏展开15条
+    const defaultEndPercent = total > 0 ? Math.min(100, (visible / total) * 100) : 100;
+    const prevOpt: any = (this.ganttChart as any).getOption ? (this.ganttChart as any).getOption() : null;
+    const preservedStart = typeof prevOpt?.dataZoom?.[0]?.start === 'number' ? prevOpt.dataZoom[0].start : 0;
+    const preservedEnd = typeof prevOpt?.dataZoom?.[0]?.end === 'number' ? prevOpt.dataZoom[0].end : defaultEndPercent;
+
+    const option = {
+      backgroundColor: 'transparent',
+      tooltip: {
+        trigger: 'item',
+        formatter: (params: any) => {
+          const v = params.value;
+          const start = new Date(v[1]);
+          const end = new Date(v[2]);
+          return `项目:${params.name}<br/>负责人:${v[3] || '未分配'}<br/>阶段:${v[6]}<br/>起止:${start.toLocaleDateString()} ~ ${end.toLocaleDateString()}`;
+        }
+      },
+      grid: { left: 100, right: 64, top: 30, bottom: 30 },
+      xAxis: {
+        type: 'time',
+        min: xMin,
+        max: xMax,
+        splitNumber: xSplitNumber,
+        axisLine: { lineStyle: { color: '#e5e7eb' } },
+        axisLabel: { color: '#6b7280', formatter: (value: number) => xLabelFormatter(value) },
+        splitLine: { lineStyle: { color: '#f1f5f9' } }
+      },
+      yAxis: {
+        type: 'category',
+        data: categories,
+        inverse: true,
+        axisLabel: {
+          color: '#374151',
+          margin: 8,
+          formatter: (val: string) => {
+            const u = urgencyMap[val] || 'low';
+            const text = val.length > 16 ? val.slice(0, 16) + '…' : val;
+            return `{${u}Dot|●} ${text}`;
+          },
+          rich: {
+            highDot: { color: '#ef4444' },
+            mediumDot: { color: '#f59e0b' },
+            lowDot: { color: '#22c55e' }
+          }
+        },
+        axisTick: { show: false },
+        axisLine: { lineStyle: { color: '#e5e7eb' } }
+      },
+      dataZoom: [
+        { type: 'slider', yAxisIndex: 0, orient: 'vertical', right: 6, width: 14, start: preservedStart, end: preservedEnd, zoomLock: false },
+        { type: 'inside', yAxisIndex: 0, zoomOnMouseWheel: true, moveOnMouseMove: true, moveOnMouseWheel: true }
+      ],
+      series: [
+        {
+          type: 'custom',
+          renderItem: (params: any, api: any) => {
+            const categoryIndex = api.value(0);
+            const start = api.coord([api.value(1), categoryIndex]);
+            const end = api.coord([api.value(2), categoryIndex]);
+            const height = Math.max(api.size([0, 1])[1] * 0.5, 8);
+            const rectShape = echarts.graphic.clipRectByRect({
+              x: start[0],
+              y: start[1] - height / 2,
+              width: Math.max(end[0] - start[0], 2),
+              height
+            }, {
+              x: params.coordSys.x,
+              y: params.coordSys.y,
+              width: params.coordSys.width,
+              height: params.coordSys.height
+            });
+            return rectShape ? { type: 'rect', shape: rectShape, style: api.style() } : undefined;
+          },
+          encode: { x: [1, 2], y: 0 },
+          data,
+          itemStyle: { borderRadius: 4 },
+          emphasis: { focus: 'self' },
+          markLine: {
+            silent: true,
+            symbol: 'none',
+            lineStyle: { color: '#ef4444', type: 'dashed', width: 1 },
+            label: { formatter: '今日', color: '#ef4444', fontSize: 10, position: 'end' },
+            data: [ { xAxis: todayTs } ]
+          }
+        }
+      ]
+    };
+
+    // 强制刷新,避免缓存导致坐标轴不更新
+    this.ganttChart.clear();
+    this.ganttChart.setOption(option, true);
+    this.ganttChart.resize();
+  }
+
+  // 新增:设计师排班甘特
+  private updateGanttDesigner(): void {
+    if (!this.ganttChart) return;
+
+    const DAY = 24 * 60 * 60 * 1000;
+    const now = new Date();
+    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+    const todayTs = today.getTime();
+
+    // 时间轴按当前周/月
+    let xMin: number;
+    let xMax: number;
+    let xSplitNumber: number;
+    let xLabelFormatter: (value: number) => string;
+    if (this.ganttScale === 'week') {
+      const day = today.getDay();
+      const diffToMonday = (day === 0 ? 6 : day - 1);
+      const startOfWeek = new Date(today.getTime() - diffToMonday * DAY);
+      const endOfWeek = new Date(startOfWeek.getTime() + 7 * DAY - 1);
+      xMin = startOfWeek.getTime();
+      xMax = endOfWeek.getTime();
+      xSplitNumber = 7;
+      const WEEK_LABELS = ['周日','周一','周二','周三','周四','周五','周六'];
+      xLabelFormatter = (val) => WEEK_LABELS[new Date(val).getDay()];
+    } else {
+      const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
+      const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0, 23, 59, 59, 999);
+      xMin = startOfMonth.getTime();
+      xMax = endOfMonth.getTime();
+      xSplitNumber = 4;
+      xLabelFormatter = (val) => `第${Math.ceil(new Date(val).getDate() / 7)}周`;
+    }
+
+    // 仅统计已分配项目
+    const assigned = this.filteredProjects.filter(p => !!p.designerName);
+    const designers = Array.from(new Set(assigned.map(p => p.designerName)));
+
+    const byDesigner: Record<string, typeof assigned> = {} as any;
+    designers.forEach(n => byDesigner[n] = [] as any);
+    assigned.forEach(p => byDesigner[p.designerName].push(p));
+
+    const busyCountMap: Record<string, number> = Object.fromEntries(designers.map(n => [n, byDesigner[n].length]));
+
+    const sortedDesigners = designers.sort((a, b) => {
+      const diff = (busyCountMap[b] || 0) - (busyCountMap[a] || 0);
+      return diff !== 0 ? diff : a.localeCompare(b, 'zh-CN');
+    });
+
+    const categories = sortedDesigners;
+
+    // 工作量等级(用于左侧小圆点颜色)
+    const workloadLevelMap: Record<string, 'high'|'medium'|'low'> = {} as any;
+    categories.forEach(name => {
+      const cnt = busyCountMap[name] || 0;
+      workloadLevelMap[name] = cnt >= 5 ? 'high' : (cnt >= 3 ? 'medium' : 'low');
+    });
+
+    // 条形颜色仍按项目紧急度
+    const colorByUrgency: Record<'high'|'medium'|'low', string> = {
+      high: '#ef4444',
+      medium: '#f59e0b',
+      low: '#22c55e'
+    } as const;
+
+    const data = assigned.flatMap(p => {
+      const end = new Date(p.deadline).getTime();
+      const baseDays = p.type === 'hard' ? 30 : 14;
+      const start = p.createdAt ? new Date(p.createdAt).getTime() : end - baseDays * DAY;
+      const yIndex = categories.indexOf(p.designerName);
+      if (yIndex === -1) return [] as any[];
+      const color = colorByUrgency[p.urgency] || '#60a5fa';
+      return [{
+        name: p.name,
+        value: [yIndex, start, end, p.designerName, p.urgency, p.memberType, p.currentStage],
+        itemStyle: { color }
+      }];
+    });
+
+    const prevOpt: any = (this.ganttChart as any).getOption ? (this.ganttChart as any).getOption() : null;
+    const total = categories.length || 1;
+    const visible = Math.min(total, 30);
+    const defaultEndPercent = Math.min(100, (visible / total) * 100);
+    const preservedStart = typeof prevOpt?.dataZoom?.[0]?.start === 'number' ? prevOpt.dataZoom[0].start : 0;
+    const preservedEnd = typeof prevOpt?.dataZoom?.[0]?.end === 'number' ? prevOpt.dataZoom[0].end : defaultEndPercent;
+
+    const option = {
+      backgroundColor: 'transparent',
+      tooltip: {
+        trigger: 'item',
+        formatter: (params: any) => {
+          const v = params.value;
+          const start = new Date(v[1]);
+          const end = new Date(v[2]);
+          return `项目:${params.name}<br/>设计师:${v[3]}<br/>阶段:${v[6]}<br/>起止:${start.toLocaleDateString()} ~ ${end.toLocaleDateString()}`;
+        }
+      },
+      grid: { left: 110, right: 64, top: 30, bottom: 30 },
+      xAxis: {
+        type: 'time',
+        min: xMin,
+        max: xMax,
+        splitNumber: xSplitNumber,
+        axisLine: { lineStyle: { color: '#e5e7eb' } },
+        axisLabel: { color: '#6b7280', formatter: (value: number) => xLabelFormatter(value) },
+        splitLine: { lineStyle: { color: '#f1f5f9' } }
+      },
+      yAxis: {
+        type: 'category',
+        data: categories,
+        inverse: true,
+        axisLabel: {
+          color: '#374151',
+          margin: 8,
+          formatter: (val: string) => {
+            const lvl = workloadLevelMap[val] || 'low';
+            const count = busyCountMap[val] || 0;
+            const text = val.length > 8 ? val.slice(0, 8) + '…' : val;
+            return `{${lvl}Dot|●} ${text}(${count}项)`;
+          },
+          rich: {
+            highDot: { color: '#ef4444' },
+            mediumDot: { color: '#f59e0b' },
+            lowDot: { color: '#22c55e' }
+          }
+        },
+        axisTick: { show: false },
+        axisLine: { lineStyle: { color: '#e5e7eb' } }
+      },
+      dataZoom: [
+        { type: 'slider', yAxisIndex: 0, orient: 'vertical', right: 6, start: preservedStart, end: preservedEnd, zoomLock: false },
+        { type: 'inside', yAxisIndex: 0, zoomOnMouseWheel: true, moveOnMouseMove: true, moveOnMouseWheel: true }
+      ],
+      series: [
+        {
+          type: 'custom',
+          renderItem: (params: any, api: any) => {
+            const categoryIndex = api.value(0);
+            const start = api.coord([api.value(1), categoryIndex]);
+            const end = api.coord([api.value(2), categoryIndex]);
+            const height = Math.max(api.size([0, 1])[1] * 0.5, 8);
+            const rectShape = echarts.graphic.clipRectByRect({
+              x: start[0],
+              y: start[1] - height / 2,
+              width: Math.max(end[0] - start[0], 2),
+              height
+            }, {
+              x: params.coordSys.x,
+              y: params.coordSys.y,
+              width: params.coordSys.width,
+              height: params.coordSys.height
+            });
+            return rectShape ? { type: 'rect', shape: rectShape, style: api.style() } : undefined;
+          },
+          encode: { x: [1, 2], y: 0 },
+          data,
+          itemStyle: { borderRadius: 4 },
+          emphasis: { focus: 'self' },
+          markLine: {
+            silent: true,
+            symbol: 'none',
+            lineStyle: { color: '#ef4444', type: 'dashed', width: 1 },
+            label: { formatter: '今日', color: '#ef4444', fontSize: 10, position: 'end' },
+            data: [ { xAxis: todayTs } ]
+          }
+        }
+      ]
+    } as any;
+
+    this.ganttChart.clear();
+    this.ganttChart.setOption(option, true);
+    this.ganttChart.resize();
+  }
+
+  ngOnDestroy(): void {
+    if (this.ganttChart) {
+      this.ganttChart.dispose();
+      this.ganttChart = null;
+    }
+    if (this.workloadChart) {
+      this.workloadChart.dispose();
+      this.workloadChart = null;
+    }
   }
-  
   // 选择单个项目
   selectProject(): void {
     if (this.selectedProjectId) {
@@ -496,12 +1027,15 @@ export class Dashboard implements OnInit {
   }
 
   // 新增:阶段到核心阶段的映射
-  private mapStageToCorePhase(stageId: string): 'preparation' | 'design' | 'production' | 'review' | 'delivery' {
-    if (stageId === 'pendingApproval' || stageId === 'pendingAssignment') return 'preparation';
-    if (stageId === 'requirement' || stageId === 'planning') return 'design';
-    if (stageId === 'modeling' || stageId === 'rendering' || stageId === 'postProduction') return 'production';
-    if (stageId === 'review' || stageId === 'revision') return 'review';
-    return 'delivery';
+  private mapStageToCorePhase(stageId: string): 'order' | 'requirements' | 'delivery' | 'aftercare' {
+    // 订单创建:立项初期
+    if (stageId === 'pendingApproval' || stageId === 'pendingAssignment') return 'order';
+    // 确认需求:需求沟通 + 方案规划
+    if (stageId === 'requirement' || stageId === 'planning') return 'requirements';
+    // 交付执行:制作与评审修订过程
+    if (stageId === 'modeling' || stageId === 'rendering' || stageId === 'postProduction' || stageId === 'review' || stageId === 'revision') return 'delivery';
+    // 售后:交付完成后的跟进(当前数据以交付完成代表进入售后)
+    return 'aftercare';
   }
 
   // 新增:获取核心阶段的项目
@@ -519,6 +1053,18 @@ export class Dashboard implements OnInit {
     return this.getProjectsByStage(stageId).length;
   }
 
+  // 待审批项目:currentStage === 'pendingApproval'
+  get pendingApprovalProjects(): Project[] {
+    const src = (this.filteredProjects && this.filteredProjects.length) ? this.filteredProjects : this.projects;
+    return src.filter(p => p.currentStage === 'pendingApproval');
+  }
+
+  // 待指派项目:currentStage === 'pendingAssignment'
+  get pendingAssignmentProjects(): Project[] {
+    const src = (this.filteredProjects && this.filteredProjects.length) ? this.filteredProjects : this.projects;
+    return src.filter(p => p.currentStage === 'pendingAssignment');
+  }
+
   // 获取紧急程度标签
   getUrgencyLabel(urgency: string): string {
     const labels = {
@@ -666,13 +1212,67 @@ export class Dashboard implements OnInit {
     this.showAlert = false;
   }
 
-  // 获取待确认项目数量
-  get pendingApprovalProjects() {
-    return this.projects.filter(project => project.currentStage === 'pendingApproval');
+  // 维度切换(设计师/会员类型)
+  setWorkloadDimension(dim: 'designer' | 'member'): void {
+    if (this.workloadDimension !== dim) {
+      this.workloadDimension = dim;
+      this.updateWorkloadChart();
+    }
   }
 
-  // 获取待分配项目数量
-  get pendingAssignmentProjects() {
-    return this.projects.filter(project => project.currentStage === 'pendingAssignment');
+  // 刷新“工作量概览”图表
+  private updateWorkloadChart(): void {
+    if (!this.workloadChartRef) { return; }
+    const el = this.workloadChartRef.nativeElement;
+    if (!el) { return; }
+
+    // 初始化实例(使用 SVG 渲染以获得更佳文本清晰度)
+    if (!this.workloadChart) {
+      this.workloadChart = echarts.init(el, null, { renderer: 'svg' });
+    }
+
+    const data = (this.filteredProjects && this.filteredProjects.length) ? this.filteredProjects : this.projects;
+    const byDesigner = this.workloadDimension === 'designer';
+    const groupKey = byDesigner ? 'designerName' : 'memberType';
+
+    const labelMap: Record<string, string> = { vip: 'VIP', normal: '普通' };
+    const groupSet = new Set<string>();
+    data.forEach(p => {
+      const val = (p as any)[groupKey] || (byDesigner ? '未分配' : '未知');
+      groupSet.add(val);
+    });
+    const categories = Array.from(groupSet).sort((a, b) => a.localeCompare(b, 'zh-Hans-CN'));
+
+    const count = (urg: 'high'|'medium'|'low', group: string) =>
+      data.filter(p => (((p as any)[groupKey] || (byDesigner ? '未分配' : '未知')) === group) && p.urgency === urg).length;
+
+    const high = categories.map(c => count('high', c));
+    const medium = categories.map(c => count('medium', c));
+    const low = categories.map(c => count('low', c));
+
+    const option = {
+      tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
+      legend: { data: ['高', '中', '低'] },
+      grid: { left: 12, right: 16, top: 28, bottom: 8, containLabel: true },
+      xAxis: { type: 'value', boundaryGap: [0, 0.01] },
+      yAxis: {
+        type: 'category',
+        data: categories.map(c => byDesigner ? c : (labelMap[c] || c))
+      },
+      series: [
+        { name: '高', type: 'bar', stack: 'workload', data: high, itemStyle: { color: '#ef4444' } },
+        { name: '中', type: 'bar', stack: 'workload', data: medium, itemStyle: { color: '#f59e0b' } },
+        { name: '低', type: 'bar', stack: 'workload', data: low, itemStyle: { color: '#10b981' } }
+      ]
+    } as any;
+
+    this.workloadChart.clear();
+    this.workloadChart.setOption(option, true);
+    this.workloadChart.resize();
+  }
+
+  resetStatusFilter(): void {
+    this.selectedStatus = 'all';
+    this.applyFilters();
   }
 }

+ 1 - 1
src/index.html

@@ -16,7 +16,7 @@
         }
       }
     </script>
-    <!-- Local fallback for ECharts UMD build to support window.echarts usage -->
+    <!-- Use local UMD build to provide window.echarts globally (avoid external CDN errors) -->
     <script defer src="/assets/echarts/echarts.min.js"></script>
   </head>
 <body>