Эх сурвалжийг харах

```
refactor: optimize dashboard layout and enhance timeline visualization

- Removed test page routes (atmosphere preview, wxwork activation, data cleanup)
- Increased modal widths: smart-match (800px→900px), stagnation-reason (520px→700px)
- Enhanced todo section tag filter styling with larger buttons, improved spacing, and hover effects
- Added updatedAt field to Project model and enhanced time field retrieval logic in dashboard.ts
- Unified updatedAt type to Date in interfaces.ts
- Refactored project

0235711 14 цаг өмнө
parent
commit
93db4dc137

+ 0 - 18
src/app/app.routes.ts

@@ -321,24 +321,6 @@ export const routes: Routes = [
     title: '下拉列表组件演示'
   },
 
-  // 测试页面路由
-  {
-    path: 'test-atmosphere-preview',
-    loadComponent: () => import('./test-atmosphere-preview/test-atmosphere-preview').then(m => m.TestAtmospherePreviewComponent),
-    title: '氛围感预览图测试'
-  },
-  {
-    path: 'test-wxwork-activation/:cid',
-    loadComponent: () => import('../test-pages/wxwork-activation-test/wxwork-activation-test.component').then(m => m.WxworkActivationTestComponent),
-    title: '企微身份激活测试'
-  },
-
-  // 数据清理工具
-  {
-    path: 'test-data-cleanup',
-    loadComponent: () => import('../test-pages/data-cleanup/data-cleanup.component').then(m => m.DataCleanupComponent),
-    title: '测试数据清理工具'
-  },
 
   // 企微项目管理模块路由
   // 支持两种进入方式:

+ 1 - 1
src/app/pages/team-leader/dashboard/components/smart-match-modal/smart-match-modal.component.scss

@@ -52,7 +52,7 @@ $ios-info: #3b82f6;
   .modal-content {
     position: relative;
     width: 90%;
-    max-width: 800px;
+    max-width: 900px;
     max-height: 90vh;
     background: white;
     border-radius: 16px;

+ 1 - 1
src/app/pages/team-leader/dashboard/components/stagnation-reason-modal/stagnation-reason-modal.component.scss

@@ -26,7 +26,7 @@
   border-radius: 12px;
   box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
   width: 90%;
-  max-width: 520px;
+  max-width: 700px;
   max-height: 90vh;
   overflow: hidden;
   display: flex;

+ 17 - 11
src/app/pages/team-leader/dashboard/components/todo-section/todo-section.component.scss

@@ -502,8 +502,8 @@
       // 标签过滤器样式
       .tag-filter-bar {
         display: flex;
-        gap: 8px;
-        padding: 12px;
+        gap: 12px;
+        padding: 16px;
         background: #fff7ed;
         border-bottom: 1px solid #fed7aa;
         overflow-x: auto;
@@ -511,43 +511,49 @@
         .tag-button {
           display: flex;
           align-items: center;
-          gap: 4px;
-          padding: 4px 10px;
+          gap: 8px;
+          padding: 8px 16px;
           background: white;
           border: 1px solid #fed7aa;
-          border-radius: 16px;
-          font-size: 12px;
+          border-radius: 20px;
+          font-size: 14px;
           color: #7c2d12;
           cursor: pointer;
           transition: all 0.2s;
           white-space: nowrap;
+          font-weight: 500;
           
           &:hover {
             background: #fff2cc;
+            transform: translateY(-1px);
+            box-shadow: 0 2px 4px rgba(249, 115, 22, 0.1);
           }
           
           &.active {
             background: #f97316;
             color: white;
             border-color: #ea580c;
+            box-shadow: 0 2px 4px rgba(249, 115, 22, 0.3);
             
             .tag-count {
-              background: rgba(255,255,255,0.2);
+              background: rgba(255,255,255,0.25);
               color: white;
             }
           }
           
           .tag-icon {
-            font-size: 12px;
+            font-size: 16px;
           }
           
           .tag-count {
             background: #fed7aa;
             color: #9a3412;
-            padding: 0 6px;
-            border-radius: 10px;
-            font-size: 10px;
+            padding: 2px 8px;
+            border-radius: 12px;
+            font-size: 12px;
             font-weight: 600;
+            min-width: 20px;
+            text-align: center;
           }
         }
       }

+ 1 - 0
src/app/pages/team-leader/dashboard/dashboard.model.ts

@@ -24,6 +24,7 @@ export interface Project {
   expectedEndDate: Date; // TODO: 兼容旧字段,后续可移除
   deadline: Date; // 真实截止时间字段
   createdAt?: Date; // 真实开始时间字段(可选)
+  updatedAt?: Date; // 🆕 真实更新时间字段(可选)
   isOverdue: boolean;
   overdueDays: number;
   dueSoon: boolean;

+ 2 - 1
src/app/pages/team-leader/dashboard/dashboard.scss

@@ -31,8 +31,9 @@
 }
 
 .dashboard-main {
-  max-width: 1400px;
+  width: 100%;
   margin: 0 auto;
+  padding: 0;
 }
 
 .section-header {

+ 21 - 1
src/app/pages/team-leader/dashboard/dashboard.ts

@@ -203,6 +203,25 @@ export class Dashboard implements OnInit, OnDestroy {
         const now = new Date();
         const overdueDays = Math.ceil((now.getTime() - deadline.getTime()) / (1000 * 60 * 60 * 24));
         
+        // 🔧 增强时间字段获取逻辑
+        // 尝试从多种路径获取 createdAt
+        let rawCreatedAt = p.createdAt;
+        if (!rawCreatedAt && typeof p.get === 'function') {
+          rawCreatedAt = p.get('createdAt');
+        }
+        // 如果还是没有,尝试从 data 字段
+        if (!rawCreatedAt && p.data && p.data.createdAt) {
+          rawCreatedAt = p.data.createdAt;
+        }
+        const createdAtDate = rawCreatedAt ? new Date(rawCreatedAt) : undefined;
+
+        // 尝试获取 updatedAt
+        let rawUpdatedAt = p.updatedAt;
+        if (!rawUpdatedAt && typeof p.get === 'function') {
+          rawUpdatedAt = p.get('updatedAt');
+        }
+        const updatedAtDate = rawUpdatedAt ? new Date(rawUpdatedAt) : undefined;
+
         // 简单的类型推断
         let type: 'soft' | 'hard' = 'soft';
         if (p.customerTags && Array.isArray(p.customerTags)) {
@@ -226,7 +245,8 @@ export class Dashboard implements OnInit, OnDestroy {
           name: p.name,
           status: p.status,
           currentStage: p.currentStage || '订单分配',
-          createdAt: p.createdAt ? new Date(p.createdAt) : undefined,
+          createdAt: createdAtDate, // ✅ 使用增强后的 createdAt
+          updatedAt: updatedAtDate, // ✅ 传递 updatedAt
           deadline: deadline,
           
           // 字段名称转换

+ 1 - 1
src/app/pages/team-leader/dashboard/interfaces.ts

@@ -53,7 +53,7 @@ export interface Project {
   designerIds?: string[];
   space?: string;
   demoday?: Date;
-  updatedAt?: Date | string;
+  updatedAt?: Date; // 统一类型为 Date,移除 string
   data?: any;
 }
 

+ 147 - 185
src/app/pages/team-leader/project-timeline/project-timeline.html

@@ -44,7 +44,7 @@
           class="scale-btn"
           [class.active]="timelineScale === 'week'"
           (click)="toggleTimelineScale('week')">
-          📆 7天
+          📅 7天
         </button>
         <button 
           class="scale-btn"
@@ -54,7 +54,7 @@
         </button>
       </div>
       
-      <!-- 🆕 手动刷新按钮 -->
+      <!-- 手动刷新按钮 -->
       <div class="filter-group refresh-controls">
         <button 
           class="refresh-btn"
@@ -113,209 +113,171 @@
     }
   </div>
 
-  <!-- 时间轴主体 -->
-  <div class="timeline-body timeline-view">
-    <!-- 时间轴视图 -->
-    <div class="timeline-view-container">
-      <!-- 图例说明 - 简化版 -->
-      <div class="timeline-legend">
-        <div class="legend-group">
-            <div class="legend-item">
-                <div class="legend-dot status-start" style="background: #10b981; border: 2px solid #fff;"></div>
-                <span class="legend-label">开始</span>
-            </div>
-            <div class="legend-item">
-                <span class="legend-icon delivery-icon">📦</span>
-                <span class="legend-label">交付</span>
-            </div>
-        </div>
-
-        <div class="legend-separator"></div>
+  <!-- 新版列表式时间轴主体 -->
+  <div class="timeline-body">
+    <!-- 表头 -->
+    <div class="dashboard-header-row">
+      <div class="header-cell">项目信息</div>
+      <div class="header-cell">交付进度 (建/软/渲/后)</div>
+      <div class="header-cell timeline-header-cell">
+        <span>时间规划</span>
+        <span class="timeline-dates">{{ formatDate(timeRangeStart) }} - {{ formatDate(timeRangeEnd) }}</span>
+      </div>
+    </div>
 
-        <div class="legend-group">
-            <div class="legend-item">
-                <div class="legend-dot status-phase" style="background: #6b7280; border: 2px solid #fff;"></div>
-                <span class="legend-label">阶段截止</span>
-            </div>
-            <div class="legend-item">
-                <div class="legend-dot status-delayed" style="background: #dc2626; border: 2px solid #fff;"></div>
-                <span class="legend-label">已延期/驳回</span>
-            </div>
+    <!-- 列表内容 -->
+    <div class="dashboard-list">
+      @if (filteredProjects.length === 0) {
+        <div class="empty-state">
+          <p>暂无项目数据</p>
         </div>
-
-        <div class="legend-separator"></div>
-        
-        <div class="legend-group">
-            <div class="legend-item legend-highlight">
-            <span class="legend-icon review-icon">📸</span>
-            <span class="legend-label">小图对图</span>
+      } @else {
+        @for (project of filteredProjects; track project.projectId) {
+          <div class="dashboard-row" (click)="onProjectClick(project.projectId)">
+            
+            <!-- 列1:项目信息 -->
+            <div class="project-info-cell">
+              <div class="project-name-row">
+                <h4 [title]="project.projectName">{{ project.projectName }}</h4>
+                @if (project.priority === 'critical' || project.priority === 'high') {
+                  <span class="priority-tag" title="紧急">🔥</span>
+                }
+              </div>
+              <div class="project-meta-row">
+                <span class="designer-name">
+                  {{ project.designerName }}
+                </span>
+                <span class="status-text" 
+                      [class.urgent]="project.status === 'urgent'"
+                      [class.overdue]="project.status === 'overdue'">
+                  {{ getStatusLabel(project.status) }}
+                  @if (project.isStalled) { (已停滞) }
+                </span>
+              </div>
             </div>
-        </div>
-
-        <div class="legend-separator"></div>
 
-        <div class="legend-group status-legend">
-            <div class="legend-item">
-            <div class="legend-dot status-normal"></div>
-            <span class="legend-label">正常</span>
-            </div>
-            <div class="legend-item">
-            <div class="legend-dot status-warning"></div>
-            <span class="legend-label">即将截止</span>
-            </div>
-            <div class="legend-item">
-            <div class="legend-dot status-urgent"></div>
-            <span class="legend-label">今日截止</span>
-            </div>
-            <div class="legend-item">
-            <div class="legend-dot status-overdue"></div>
-            <span class="legend-label">逾期/紧急</span>
-            </div>
-        </div>
-      </div>
-        
-      <!-- 时间刻度尺 -->
-      <div class="timeline-ruler">
-        <div class="ruler-header">
-          <span class="project-name-header">项目列表</span>
-        </div>
-        <div class="ruler-ticks">
-          @for (date of timeRange; track date; let i = $index) {
-            <div class="ruler-tick" [class.first]="i === 0">
-              <div class="tick-date">{{ date.getMonth() + 1 }}/{{ date.getDate() }}</div>
-              @if (timelineScale === 'week') {
-                <div class="tick-weekday">
-                  @switch (date.getDay()) {
-                    @case (0) { 周日 }
-                    @case (1) { 周一 }
-                    @case (2) { 周二 }
-                    @case (3) { 周三 }
-                    @case (4) { 周四 }
-                    @case (5) { 周五 }
-                    @case (6) { 周六 }
-                  }
+            <!-- 列2:进度统计 (建/软/渲/后) -->
+            <div class="progress-stats-cell">
+              <!-- 尝试获取统计数据 -->
+              @if (getSpaceDeliverableSummary(project.projectId); as summary) {
+                <div class="progress-summary">
+                  <span class="rate-value" 
+                        [class.rate-low]="summary.overallCompletionRate < 30"
+                        [class.rate-mid]="summary.overallCompletionRate >= 30 && summary.overallCompletionRate < 80"
+                        [class.rate-high]="summary.overallCompletionRate >= 80"
+                        [class.rate-done]="summary.overallCompletionRate === 100">
+                    {{ summary.overallCompletionRate }}%
+                  </span>
+                  <span class="rate-label">总完成度</span>
+                </div>
+                
+                <!-- 4段式进度胶囊 -->
+                <div class="progress-pills">
+                  <div class="pill type-model" 
+                       [style.--fill-percent.%]="summary.phaseProgress.modeling.completionRate"
+                       [title]="'建模: ' + summary.phaseProgress.modeling.completionRate + '%'"></div>
+                  <div class="pill type-soft" 
+                       [style.--fill-percent.%]="summary.phaseProgress.softDecor.completionRate"
+                       [title]="'软装: ' + summary.phaseProgress.softDecor.completionRate + '%'"></div>
+                  <div class="pill type-render" 
+                       [style.--fill-percent.%]="summary.phaseProgress.rendering.completionRate"
+                       [title]="'渲染: ' + summary.phaseProgress.rendering.completionRate + '%'"></div>
+                  <div class="pill type-post" 
+                       [style.--fill-percent.%]="summary.phaseProgress.postProcessing.completionRate"
+                       [title]="'后期: ' + summary.phaseProgress.postProcessing.completionRate + '%'"></div>
+                </div>
+                
+                <div class="pill-labels">
+                  <span>M</span><span>S</span><span>R</span><span>P</span>
+                </div>
+              } @else {
+                <!-- 无数据时的占位 -->
+                <div class="progress-summary">
+                  <span class="rate-value rate-low">0%</span>
+                  <span class="rate-label">加载中...</span>
+                </div>
+                <div class="progress-pills">
+                  <div class="pill" style="background: #f1f5f9"></div>
                 </div>
               }
             </div>
-          }
-        </div>
-      </div>
-        
-      <!-- 今日标记线(实时移动,精确到分钟) -->
-      <div class="today-line" 
-           [style.left]="getTodayPosition()">
-        <div class="today-label">
-          {{ getTodayLabel() }}
-        </div>
-        <div class="today-dot"></div>
-        <div class="today-bar"></div>
-      </div>
-        
-      <!-- 项目时间轴 -->
-      <div class="timeline-projects">
-        @if (filteredProjects.length === 0) {
-          <div class="empty-state">
-            <p>暂无项目数据</p>
-          </div>
-        } @else {
-          @for (project of filteredProjects; track project.projectId) {
-            <div class="timeline-row" (click)="onProjectClick(project.projectId)">
-              <!-- 项目名称标签 - 优化布局 -->
-              <div class="project-label">
-                <div class="project-main-info">
-                    <span class="project-name-label" [title]="project.projectName">
-                        {{ project.projectName }}
-                    </span>
-                    @if (project.priority === 'critical' || project.priority === 'high') {
-                        <span class="priority-icon" [title]="project.priority === 'critical' ? '非常紧急' : '紧急'">
-                        🔥
-                        </span>
-                    }
-                </div>
-                <div class="project-sub-info">
-                    <span class="designer-label">{{ project.designerName }}</span>
-                    <!-- 🆕 空间与交付物统计 - 简化显示 -->
-                    @if (getSpaceDeliverableSummary(project.projectId); as summary) {
-                        <span class="deliverable-info" 
-                              [title]="formatSpaceDeliverableTooltip(project.projectId)"
-                              [class.has-data]="summary.spacesWithDeliverables > 0">
-                          <span class="icon">📦</span>
-                          <span class="count">{{ summary.spacesWithDeliverables }}/{{ summary.totalSpaces }}</span>
-                        </span>
-                    }
+
+            <!-- 列3:纯净时间轴 -->
+            <div class="timeline-track-cell">
+              <div class="track-container">
+                <!-- 背景网格线 (辅助对齐) -->
+                <div class="grid-lines">
+                  @for (date of timeRange; track date) {
+                    <div class="grid-line" 
+                         [style.left]="getEventPosition(date)"
+                         [class.weekend]="date.getDay() === 0 || date.getDay() === 6"></div>
+                  }
                 </div>
-              </div>
-              
-              <!-- 时间轴区域 -->
-              <div class="timeline-track">
-                <!-- 项目条形图 -->
-                <div class="project-bar"
-                     [ngClass]="getProjectStatusClass(project)"
+
+                <!-- 今日红线 -->
+                <div class="today-line" [style.left]="getTodayPosition()"></div>
+
+                <!-- 项目周期条 -->
+                <div class="project-duration-bar"
+                     [class]="'status-' + project.status"
                      [style.left]="getProjectPosition(project).left"
-                     [style.width]="getProjectPosition(project).width"
-                     [style.background]="getProjectPosition(project).background"
-                     [title]="project.projectName + ' | ' + project.stageName + ' 当前环节进度: ' + getProjectCompletionRate(project) + '%'">
-                  <!-- 🆕 进度条:从现在到最近的一个事件(显示任务完成度) -->
-                  <div class="progress-bar-container"
-                       [style.left]="getProgressBarPosition(project).left"
-                       [style.width]="getProgressBarPosition(project).width"
-                       [title]="'从现在到下一个事件的时间跨度,当前环节任务完成度: ' + getProjectCompletionRate(project) + '%'">
-                    <!-- 任务完成度填充(相对于进度条容器的宽度) -->
-                    <div class="progress-fill"
-                         [style.width.%]="getProjectCompletionRate(project)"
-                         [style.background]="getProjectCompletionColor(project)">
-                      <!-- 任务完成度百分比文本(仅在填充足够宽时显示) -->
-                      @if (getProjectCompletionRate(project) >= 15) {
-                        <span class="progress-text">{{ getProjectCompletionRate(project) }}%</span>
-                      }
-                    </div>
+                     [style.width]="getProjectPosition(project).width">
+                </div>
+
+                <!-- 事件标记:开始 -->
+                @if (isEventInRange(project.startDate)) {
+                  <div class="t-marker type-start"
+                       [style.left]="getEventPosition(project.startDate)"
+                       title="开始: {{ formatTime(project.startDate) }}">
+                    🚀
                   </div>
-                  
-                  <!-- 任务完成度标记 -->
-                  <div class="completion-marker"
-                       [style.left]="getCompletionMarkerLeft(project)">
-                    <span class="marker-label"
-                          [style.background]="getProjectCompletionColor(project)"
-                          [title]="'当前环节任务完成度: ' + getProjectCompletionRate(project) + '%'">
-                      {{ getProjectCompletionRate(project) }}%
-                    </span>
-                    <span class="marker-dot"
-                          [style.background]="getProjectCompletionColor(project)"></span>
+                }
+
+                <!-- 事件标记:交付 -->
+                @if (project.deliveryDate && isEventInRange(project.deliveryDate)) {
+                  <div class="t-marker type-delivery"
+                       [class.urgent]="project.status === 'urgent'"
+                       [class.overdue]="project.status === 'overdue'"
+                       [style.left]="getEventPosition(project.deliveryDate)"
+                       title="交付: {{ formatTime(project.deliveryDate) }}">
+                    📦
                   </div>
-                </div>
-                
-                <!-- 🆕 使用统一的事件标记方法 - 分离图标和圆点样式 -->
-                @for (event of getProjectEvents(project); track event.date) {
-                  @if (event.displayType === 'dot') {
-                    <!-- 小圆点样式 (开始、阶段截止) -->
-                    <div class="event-marker dot-marker"
-                         [class]="event.type"
-                         [style.left]="getEventPosition(event.date)"
-                         [style.background]="event.color"
-                         [title]="event.label + ':' + formatTime(event.date)">
-                    </div>
-                  } @else {
-                    <!-- 图标样式 (小图对图、交付) -->
-                    <div class="event-marker icon-marker"
-                         [class]="event.type"
-                         [style.left]="getEventPosition(event.date)"
-                         [style.background]="event.color"
-                         [class.blink]="project.status === 'overdue' && event.type === 'delivery'"
-                         [title]="event.label + ':' + formatTime(event.date)">
-                      {{ event.icon }}
-                    </div>
+                }
+
+                <!-- 事件标记:小图对图 -->
+                @if (project.reviewDate && isEventInRange(project.reviewDate)) {
+                  <div class="t-marker type-review"
+                       [style.left]="getEventPosition(project.reviewDate)"
+                       title="小图对图: {{ formatTime(project.reviewDate) }}">
+                    📸
+                  </div>
+                }
+
+                <!-- 阶段截止标记 (简化为小点) -->
+                @if (project.phaseDeadlines) {
+                  @for (phase of ['modeling', 'softDecor', 'rendering', 'postProcessing']; track phase) {
+                    @if (getPhaseDeadlineDate(project, phase); as deadline) {
+                      @if (isEventInRange(deadline)) {
+                        <div class="t-marker type-deadline"
+                             [class]="'phase-' + phase"
+                             [style.left]="getEventPosition(deadline)"
+                             [title]="getPhaseLabel(phase) + '截止: ' + formatTime(deadline)">
+                        </div>
+                      }
+                    }
                   }
                 }
+
               </div>
             </div>
-          }
+          </div>
         }
-      </div>
+      }
     </div>
   </div>
 </div>
 
-<!-- 🆕 项目进度详情弹窗 -->
+<!-- 项目进度详情弹窗 -->
 <app-project-progress-modal
   [visible]="showProgressModal"
   [summary]="selectedProjectSummary"

+ 403 - 730
src/app/pages/team-leader/project-timeline/project-timeline.scss

@@ -1,834 +1,507 @@
-.project-timeline-container {
-  width: 100%;
+@import '../../../../styles/variables.scss';
+
+:host {
+  display: block;
   height: 100%;
-  display: flex;
-  flex-direction: column;
-  background: #ffffff;
-  border-radius: 8px;
+  background-color: #f8fafc;
   overflow: hidden;
 }
 
-// 顶部筛选栏
-.timeline-header {
-  padding: 16px;
-  background: #f9fafb;
-  border-bottom: 1px solid #e5e7eb;
-}
-
-.filter-section {
+.project-timeline-container {
   display: flex;
-  align-items: center;
-  gap: 16px;
-  flex-wrap: wrap;
+  flex-direction: column;
+  height: 100%;
+  padding: 16px;
+  box-sizing: border-box;
 }
 
-.filter-group {
-  display: flex;
-  align-items: center;
-  gap: 8px;
+/* ==================== Header & Filters ==================== */
+.timeline-header {
+  flex-shrink: 0;
+  margin-bottom: 16px;
+  background: white;
+  padding: 16px;
+  border-radius: 12px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
 
-  label {
-    font-size: 14px;
-    font-weight: 500;
-    color: #374151;
-    white-space: nowrap;
+  .filter-section {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    flex-wrap: wrap;
   }
-}
 
-.filter-select {
-  padding: 6px 12px;
-  border: 1px solid #d1d5db;
-  border-radius: 6px;
-  font-size: 14px;
-  background: #ffffff;
-  cursor: pointer;
-  transition: all 0.2s;
+  .filter-group {
+    display: flex;
+    align-items: center;
+    gap: 8px;
 
-  &:hover {
-    border-color: #9ca3af;
+    label {
+      font-size: 13px;
+      color: #64748b;
+      font-weight: 500;
+    }
   }
 
-  &:focus {
+  .filter-select {
+    padding: 6px 12px;
+    border: 1px solid #e2e8f0;
+    border-radius: 6px;
+    font-size: 13px;
+    color: #334155;
+    background-color: #fff;
     outline: none;
-    border-color: #3b82f6;
-    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
-  }
-}
-
-.quick-filters {
-  margin-left: auto;
-}
-
-.filter-btn {
-  padding: 6px 12px;
-  border: 1px solid #d1d5db;
-  border-radius: 6px;
-  background: #ffffff;
-  font-size: 13px;
-  cursor: pointer;
-  transition: all 0.2s;
-
-  &:hover {
-    background: #f3f4f6;
-  }
+    transition: all 0.2s;
+    min-width: 140px;
 
-  &.active {
-    background: #3b82f6;
-    color: #ffffff;
-    border-color: #3b82f6;
+    &:focus {
+      border-color: $ios-primary;
+      box-shadow: 0 0 0 2px rgba($ios-primary, 0.1);
+    }
   }
 }
 
-.sort-controls,
-.time-scale-controls {
-  border-left: 1px solid #e5e7eb;
-  padding-left: 16px;
-}
-
-.sort-btn,
-.scale-btn,
-.refresh-btn {
+/* Buttons */
+.filter-btn, .scale-btn, .refresh-btn, .sort-btn {
   padding: 6px 12px;
-  border: 1px solid #d1d5db;
+  border: 1px solid #e2e8f0;
   border-radius: 6px;
-  background: #ffffff;
+  background: white;
+  color: #64748b;
   font-size: 13px;
   cursor: pointer;
   transition: all 0.2s;
+  display: flex;
+  align-items: center;
+  gap: 6px;
 
   &:hover {
-    background: #f3f4f6;
+    background: #f8fafc;
+    color: #334155;
+    border-color: #cbd5e1;
   }
 
   &.active {
-    background: #3b82f6;
-    color: #ffffff;
-    border-color: #3b82f6;
-  }
-}
-
-// 时间尺度切换按钮特殊样式
-.time-scale-controls {
-  .scale-btn.active {
-    background: #10b981;
-    border-color: #10b981;
-    font-weight: 600;
-  }
-}
-
-// 🆕 刷新按钮特殊样式
-.refresh-btn {
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-  color: #ffffff;
-  border: none;
-  font-weight: 600;
-  
-  &:hover {
-    background: linear-gradient(135deg, #5568d3 0%, #63408b 100%);
-    transform: scale(1.05);
-  }
-  
-  &:active {
-    animation: refresh-spin 0.6s ease-in-out;
-  }
-}
-
-@keyframes refresh-spin {
-  from {
-    transform: rotate(0deg);
-  }
-  to {
-    transform: rotate(360deg);
+    background: #eff6ff;
+    color: $ios-primary;
+    border-color: $ios-primary;
+    font-weight: 500;
   }
 }
 
-// 设计师统计面板
+/* Designer Stats Panel */
 .designer-stats-panel {
   margin-top: 12px;
-  padding: 12px 16px;
-  background: #ffffff;
-  border: 1px solid #e5e7eb;
-  border-radius: 6px;
-}
-
-.stats-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 12px;
-
-  h3 {
-    margin: 0;
-    font-size: 16px;
-    font-weight: 600;
-    color: #111827;
-  }
-}
-
-.workload-badge {
-  padding: 4px 12px;
-  border-radius: 12px;
-  font-size: 13px;
-  font-weight: 500;
+  padding-top: 12px;
+  border-top: 1px solid #f1f5f9;
+  
+  animation: slideDown 0.2s ease-out;
 
-  &.level-low {
-    background: #d1fae5;
-    color: #065f46;
-  }
+  .stats-header {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    margin-bottom: 8px;
 
-  &.level-medium {
-    background: #fef3c7;
-    color: #92400e;
-  }
+    h3 {
+      margin: 0;
+      font-size: 15px;
+      font-weight: 600;
+      color: #1e293b;
+    }
 
-  &.level-high {
-    background: #fee2e2;
-    color: #991b1b;
+    .workload-badge {
+      font-size: 12px;
+      padding: 2px 8px;
+      border-radius: 12px;
+      background: #f1f5f9;
+      color: #64748b;
+
+      &.level-high { background: #fee2e2; color: #ef4444; }
+      &.level-medium { background: #fef3c7; color: #d97706; }
+      &.level-low { background: #dcfce7; color: #16a34a; }
+    }
   }
-}
-
-.stats-body {
-  display: grid;
-  grid-template-columns: repeat(3, 1fr);
-  gap: 12px;
-}
-
-.stat-item {
-  display: flex;
-  flex-direction: column;
-  padding: 8px;
-  background: #f9fafb;
-  border-radius: 4px;
-}
-
-.stat-label {
-  font-size: 12px;
-  color: #6b7280;
-  margin-bottom: 4px;
-}
 
-.stat-value {
-  font-size: 18px;
-  font-weight: 600;
-  color: #111827;
+  .stats-body {
+    display: flex;
+    gap: 24px;
 
-  &.urgent {
-    color: #ea580c;
-  }
+    .stat-item {
+      display: flex;
+      align-items: center;
+      gap: 6px;
+      font-size: 13px;
 
-  &.overdue {
-    color: #dc2626;
+      .stat-label { color: #64748b; }
+      .stat-value { 
+        font-weight: 600; 
+        color: #334155;
+        &.urgent { color: #f59e0b; }
+        &.overdue { color: #ef4444; }
+      }
+    }
   }
 }
 
-// 时间轴主体
+/* ==================== Main Dashboard Table ==================== */
 .timeline-body {
   flex: 1;
-  overflow-y: auto;
-  padding: 16px;
-}
-
-
-// 空状态
-.empty-state {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 60px 20px;
-  text-align: center;
-
-  p {
-    margin: 0;
-    font-size: 15px;
-    color: #9ca3af;
-  }
-}
-
-// 时间轴视图容器
-.timeline-view-container {
-  position: relative;
-  width: 100%;
-  min-height: 400px;
-}
-
-// 图例说明 - 简化版
-.timeline-legend {
-  display: flex;
-  align-items: center;
-  padding: 12px 20px;
-  background: #ffffff;
-  border-bottom: 1px solid #f3f4f6;
-  flex-wrap: wrap;
-  gap: 24px;
-}
-
-.legend-group {
-  display: flex;
-  align-items: center;
-  gap: 16px;
-}
-
-.legend-item {
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
   display: flex;
-  align-items: center;
-  gap: 6px;
+  flex-direction: column;
+  overflow: hidden;
   
-  &.legend-highlight {
-    background: #fff7ed;
-    padding: 4px 10px;
-    border-radius: 4px;
-    border: 1px solid #fed7aa;
+  /* Table Header */
+  .dashboard-header-row {
+    display: grid;
+    grid-template-columns: 260px 240px 1fr; /* Fixed | Fixed | Fluid Timeline */
+    padding: 16px 24px;
+    border-bottom: 1px solid #e2e8f0;
+    background: #f8fafc;
     
-    .legend-label {
-      color: #ea580c;
+    .header-cell {
+      font-size: 13px;
       font-weight: 600;
+      color: #64748b;
+      text-transform: uppercase;
+      letter-spacing: 0.5px;
+      
+      &.timeline-header-cell {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        
+        .timeline-dates {
+          font-size: 11px;
+          font-weight: normal;
+          color: #94a3b8;
+        }
+      }
     }
   }
-}
-
-.legend-label {
-  font-size: 12px;
-  color: #4b5563;
-  font-weight: 500;
-}
-
-.legend-separator {
-  width: 1px;
-  height: 16px;
-  background: #e5e7eb;
-}
 
-.legend-dot {
-  width: 10px;
-  height: 10px;
-  border-radius: 2px; // Slightly rounded square for status
-  
-  &.status-normal { background: #dcfce7; border: 1px solid #86efac; }
-  &.status-warning { background: #fef9c3; border: 1px solid #fde047; }
-  &.status-urgent { background: #ffedd5; border: 1px solid #fdba74; }
-  &.status-overdue { background: #fee2e2; border: 1px solid #fca5a5; }
-}
+  /* Scrollable List */
+  .dashboard-list {
+    flex: 1;
+    overflow-y: auto;
+    padding: 0;
 
-.legend-icon {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  width: 20px;
-  height: 20px;
-  font-size: 14px;
-  
-  &.start-icon { color: #10b981; }
-  &.delivery-icon { color: #f59e0b; }
-  
-  &.phase-icon {
-    width: 20px;
-    height: 20px;
-    border-radius: 50%;
-    background: #f3f4f6;
-    color: #6b7280;
-    font-size: 10px;
-    font-weight: 600;
-    border: 1px solid #e5e7eb;
-  }
-  
-  &.review-icon {
-    font-size: 16px;
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
+    &::-webkit-scrollbar-track {
+      background: transparent;
+    }
+    &::-webkit-scrollbar-thumb {
+      background: #cbd5e1;
+      border-radius: 3px;
+      &:hover { background: #94a3b8; }
+    }
   }
-}
-
-// 项目标签区 - 优化
-.project-label {
-  width: 200px; // Slightly wider
-  min-width: 200px;
-  padding: 12px 16px;
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  gap: 6px;
-  border-right: 1px solid #f3f4f6; // Lighter border
-  background: #ffffff;
-}
-
-.project-main-info {
-  display: flex;
-  align-items: center;
-  gap: 6px;
-  overflow: hidden;
-}
-
-.project-name-label {
-  font-size: 14px;
-  font-weight: 600;
-  color: #111827;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-
-.priority-icon {
-  font-size: 14px;
-  flex-shrink: 0;
-}
-
-.project-sub-info {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-}
 
-.designer-label {
-  font-size: 12px;
-  color: #9ca3af;
-}
+  .dashboard-row {
+    display: grid;
+    grid-template-columns: 260px 240px 1fr;
+    padding: 16px 24px;
+    border-bottom: 1px solid #f1f5f9;
+    align-items: center;
+    transition: background-color 0.2s;
+    cursor: pointer;
 
-.deliverable-info {
-  display: flex;
-  align-items: center;
-  gap: 4px;
-  font-size: 11px;
-  color: #d1d5db; // Inactive color
-  padding: 1px 6px;
-  border-radius: 4px;
-  background: #f9fafb;
-  
-  &.has-data {
-    color: #6b7280;
-    background: #f3f4f6;
+    &:hover {
+      background-color: #f8fafc;
+    }
     
-    .count {
-        font-weight: 500;
-        color: #4b5563;
+    &:last-child {
+      border-bottom: none;
     }
   }
-  
-  .icon { font-size: 12px; }
 }
 
-// 时间刻度尺
-.timeline-ruler {
-  display: flex;
-  position: sticky;
-  top: 0;
-  z-index: 10;
-  background: #ffffff;
-  border-bottom: 1px solid #f3f4f6;
-  padding: 8px 0;
-}
+/* ==================== Column 1: Project Info ==================== */
+.project-info-cell {
+  padding-right: 16px;
 
-.ruler-header {
-  width: 200px;
-  min-width: 200px;
-  padding: 12px 16px;
-  font-weight: 600;
-  font-size: 14px;
-  color: #111827;
-  border-right: 1px solid #f3f4f6;
-  background: #ffffff;
-  display: flex;
-  align-items: center;
-}
-
-.ruler-ticks {
-  flex: 1;
-  display: flex;
-  position: relative;
-}
+  .project-name-row {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    margin-bottom: 6px;
 
-.ruler-tick {
-  flex: 1;
-  text-align: center;
-  border-right: 1px solid #f3f4f6;
-  padding: 8px 4px;
-  display: flex;
-  flex-direction: column;
-  gap: 2px;
+    h4 {
+      margin: 0;
+      font-size: 15px;
+      font-weight: 600;
+      color: #1e293b;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
 
-  &:last-child {
-    border-right: none;
-  }
-  
-  &.first {
-    border-left: 1px solid #e5e7eb;
-    background: #f9fafb;
+    .priority-tag {
+      font-size: 12px;
+    }
   }
-}
-
-.tick-date {
-  font-size: 13px;
-  color: #374151;
-  font-weight: 600;
-  line-height: 1.2;
-}
-
-.tick-weekday {
-  font-size: 11px;
-  color: #9ca3af;
-  font-weight: 500;
-  line-height: 1.2;
-}
 
-// 时间轴轨道
-.timeline-track {
-  flex: 1;
-  position: relative;
-  height: 72px; // Slightly taller for breathing room
-  padding: 0; // Remove padding, center bar vertically
-  display: flex;
-  align-items: center;
-  background: repeating-linear-gradient(
-    90deg,
-    transparent,
-    transparent calc(100% / 7 - 1px),
-    #e5e7eb calc(100% / 7 - 1px), // Slightly darker grid line
-    #e5e7eb calc(100% / 7)
-  );
-}
-
-// 项目条形图 - 扁平化
-.project-bar {
-  position: absolute;
-  height: 36px; // Slightly taller
-  border-radius: 6px;
-  transition: all 0.2s;
-  box-shadow: 0 1px 2px rgba(0,0,0,0.05);
-  border: 1px solid rgba(0,0,0,0.1); // Explicit border for better definition
-  opacity: 1;
-  
-  &::before {
-    display: none; // Remove the top gloss effect
-  }
-  
-  &:hover {
-    transform: scaleY(1.05);
-    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
-    z-index: 5;
+  .project-meta-row {
+    display: flex;
+    flex-direction: column;
+    gap: 2px;
+    
+    .designer-name {
+      font-size: 13px;
+      color: #475569;
+      display: flex;
+      align-items: center;
+      gap: 4px;
+      
+      i { font-size: 12px; color: #94a3b8; }
+    }
+    
+    .status-text {
+      font-size: 12px;
+      color: #64748b;
+      
+      &.urgent { color: #f59e0b; }
+      &.overdue { color: #ef4444; }
+    }
   }
 }
 
-// 进度条容器 - 简化
-.progress-bar-container {
-  position: absolute;
-  top: 0;
-  bottom: 0;
-  border-radius: 5px;
-  background: rgba(0,0,0,0.05); // Subtle darkening for progress area
+/* ==================== Column 2: Progress Stats ==================== */
+.progress-stats-cell {
+  padding-right: 32px;
   
-  .progress-fill {
-    background: rgba(0,0,0,0.15) !important; // Darker overlay for completion
-    box-shadow: none;
+  .progress-summary {
+    display: flex;
+    align-items: baseline;
+    gap: 8px;
+    margin-bottom: 8px;
     
-    .progress-text {
-      font-size: 11px;
-      color: #374151; // Darker text for contrast
-      text-shadow: none;
+    .rate-value {
+      font-size: 20px;
       font-weight: 700;
+      line-height: 1;
+      
+      &.rate-low { color: #94a3b8; }
+      &.rate-mid { color: #f59e0b; }
+      &.rate-high { color: #16a34a; }
+      &.rate-done { color: #16a34a; }
+    }
+    
+    .rate-label {
+      font-size: 12px;
+      color: #64748b;
     }
   }
-}
-
-// 任务完成度标记 - 简化
-.completion-marker {
-    top: -26px; // Closer to bar
+  
+  .progress-pills {
+    display: flex;
+    gap: 4px;
+    height: 6px;
+    width: 100%;
+    background: #f1f5f9;
+    border-radius: 3px;
+    overflow: hidden;
     
-    .marker-label {
-        color: #374151;
-        background: #ffffff !important;
-        border: 1px solid #d1d5db;
-        box-shadow: 0 2px 4px rgba(0,0,0,0.05);
-        font-size: 11px;
-        padding: 2px 8px;
+    .pill {
+      flex: 1;
+      height: 100%;
+      background: transparent;
+      position: relative;
+      
+      &::after {
+        content: '';
+        position: absolute;
+        left: 0;
+        top: 0;
+        height: 100%;
+        width: var(--fill-percent, 0%);
+        background: var(--fill-color, #cbd5e1);
+        border-radius: 2px;
+        transition: width 0.3s ease;
+      }
+      
+      /* Pill Colors */
+      &.type-model { --fill-color: #3b82f6; }
+      &.type-soft { --fill-color: #f59e0b; }
+      &.type-render { --fill-color: #8b5cf6; }
+      &.type-post { --fill-color: #10b981; }
     }
+  }
+  
+  .pill-labels {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 4px;
+    padding: 0 2px;
     
-    .marker-dot {
-        width: 8px;
-        height: 8px;
-        border: 2px solid #ffffff;
-        box-shadow: 0 1px 2px rgba(0,0,0,0.1);
+    span {
+      font-size: 10px;
+      color: #94a3b8;
+      width: 25%;
+      text-align: center;
     }
+  }
 }
 
-// 事件标记 - 简化重构
-.event-marker {
-    position: absolute;
-    top: 50%;
-    transform: translateY(-50%);
+/* ==================== Column 3: Timeline Track ==================== */
+.timeline-track-cell {
+  position: relative;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  padding: 0 10px; /* Buffer for end markers */
+  
+  .track-container {
+    position: relative;
+    width: 100%;
+    height: 40px; /* Interaction area height */
     display: flex;
     align-items: center;
-    justify-content: center;
-    transition: all 0.2s;
-    cursor: pointer;
     
-    // 🔵 小圆点样式 (开始、阶段截止) - 极简,无文字,不占空间
-    &.dot-marker {
-        width: 10px;
-        height: 10px;
-        border-radius: 50%;
-        border: 2px solid #ffffff;
-        box-shadow: 0 1px 3px rgba(0,0,0,0.2);
-        z-index: 7; // Above progress bar, below icons
-        
-        &:hover {
-            transform: translateY(-50%) scale(1.5);
-            z-index: 10;
-        }
+    /* The gray background line */
+    &::before {
+      content: '';
+      position: absolute;
+      left: 0;
+      right: 0;
+      top: 50%;
+      height: 4px;
+      background: #f1f5f9;
+      border-radius: 2px;
+      margin-top: -2px;
     }
-
-    // 🔶 图标样式 (交付、小图对图) - 保留视觉重心
-    &.icon-marker {
-        z-index: 8;
-        color: #ffffff;
-        border-radius: 50%;
-        border: 2px solid #ffffff;
-        box-shadow: 0 2px 5px rgba(0,0,0,0.15);
+    
+    /* Grid lines for days */
+    .grid-lines {
+      position: absolute;
+      top: 0;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      pointer-events: none;
+      
+      .grid-line {
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        width: 1px;
+        background: rgba(0, 0, 0, 0.02);
         
-        &.delivery {
-            width: 24px;
-            height: 24px;
-            font-size: 14px;
-            border-radius: 4px; // Square for box
-            transform: translateY(-50%) rotate(45deg); // Diamond shape
-            
-            &:hover {
-                transform: translateY(-50%) rotate(45deg) scale(1.2);
-                z-index: 15;
-            }
-        }
-
-        &.review {
-            width: 32px;
-            height: 32px;
-            font-size: 18px;
-            // border-color: #f59e0b; // Gold border
-            box-shadow: 0 4px 8px rgba(245, 158, 11, 0.3);
-            z-index: 9;
-            
-            &:hover {
-                 transform: translateY(-50%) scale(1.15);
-                 z-index: 15;
-            }
+        &.weekend {
+          background: rgba(0, 0, 0, 0.04);
         }
+      }
     }
-    
-    &.blink {
-        animation: blink 1s infinite;
-    }
-}
-
-// 今日标记线 - 修复版
-.today-line {
-  position: absolute;
-  top: 0;
-  bottom: 0;
-  z-index: 20;
-  pointer-events: none;
-  // left is set dynamically via style attribute
-  width: 0; // Container width is 0, content flows from it
-}
-
-.today-bar {
-  position: absolute;
-  top: 0;
-  bottom: 0;
-  left: 0;
-  width: 2px;
-  background: #ef4444;
-  box-shadow: 0 0 4px rgba(239, 68, 68, 0.3);
-}
-
-.today-dot {
-  position: absolute;
-  top: -5px; // Align with ruler ticks
-  left: -4px; // Center on the 2px line
-  width: 10px;
-  height: 10px;
-  background: #ef4444;
-  border-radius: 50%;
-  border: 2px solid #ffffff;
-  box-shadow: 0 0 0 1px #ef4444;
-  animation: none;
-}
-
-.today-label {
-  position: absolute;
-  top: -35px;
-  left: 0;
-  transform: translateX(-50%);
-  background: #ef4444;
-  color: #ffffff;
-  padding: 4px 10px;
-  font-size: 11px;
-  font-weight: 600;
-  border-radius: 20px;
-  box-shadow: 0 2px 6px rgba(239, 68, 68, 0.3);
-  white-space: nowrap;
-  animation: none;
-  
-  &::after {
-    content: '';
-    position: absolute;
-    top: 100%;
-    left: 50%;
-    transform: translateX(-50%);
-    width: 0;
-    height: 0;
-    border-left: 5px solid transparent;
-    border-right: 5px solid transparent;
-    border-top: 5px solid #ef4444;
-  }
-}
-
-// 移除之前的复杂样式
-.space-deliverable-badge, .priority-badge {
-    display: none; // We moved these or restyled them
-}
-
-// 动画
-@keyframes pulse {
-  0%, 100% {
-    opacity: 1;
-  }
-  50% {
-    opacity: 0.7;
-  }
-}
-
-@keyframes blink {
-  0%, 100% {
-    opacity: 1;
-  }
-  50% {
-    opacity: 0.3;
-  }
-}
-
-// 🔥 小图对图高亮脉冲动画
-@keyframes pulse-highlight {
-  0%, 100% {
-    transform: translateY(-50%) scale(1);
-    box-shadow: 0 0 25px rgba(245, 158, 11, 0.8), 0 4px 15px rgba(0, 0, 0, 0.3);
-  }
-  50% {
-    transform: translateY(-50%) scale(1.15);
-    box-shadow: 0 0 35px rgba(245, 158, 11, 1), 0 6px 20px rgba(0, 0, 0, 0.4);
-  }
-}
-
-// 🔥 图例发光动画
-@keyframes legend-glow {
-  0%, 100% {
-    box-shadow: 0 0 10px rgba(251, 191, 36, 0.3);
-  }
-  50% {
-    box-shadow: 0 0 20px rgba(251, 191, 36, 0.6);
-  }
-}
-
-// 响应式设计
-@media (max-width: 768px) {
-  .filter-section {
-    flex-direction: column;
-    align-items: stretch;
-  }
-
-  .filter-group {
-    width: 100%;
-
-    &.quick-filters {
-      margin-left: 0;
-    }
-
-    &.view-controls,
-    &.sort-controls {
-      border-left: none;
-      border-top: 1px solid #e5e7eb;
-      padding-left: 0;
-      padding-top: 12px;
-    }
-  }
-
-  .stats-body {
-    grid-template-columns: 1fr;
-  }
-  
-  // 时间轴视图响应式
-  .timeline-legend {
-    flex-wrap: wrap;
-    gap: 12px;
-    padding: 8px 12px;
   }
   
-  .legend-item {
-    gap: 6px;
-  }
-  
-  .legend-label {
-    font-size: 11px;
-  }
-  
-  .ruler-header,
-  .project-label {
-    width: 100px;
-    min-width: 100px;
-    padding: 8px;
-  }
-  
-  .project-name-label {
-    font-size: 11px;
-  }
-  
-  .designer-label {
-    font-size: 10px;
-  }
-  
-  .tick-date {
-    font-size: 11px;
-  }
-  
-  .tick-weekday {
-    font-size: 9px;
-  }
-  
-  .timeline-track {
-    height: 50px;
-    padding: 14px 0;
+  /* The colored project duration bar */
+  .project-duration-bar {
+    position: absolute;
+    top: 50%;
+    height: 6px;
+    margin-top: -3px;
+    border-radius: 3px;
+    background: #bfdbfe; /* Default blue-ish */
+    opacity: 0.8;
+    
+    &.status-normal { background: #bbf7d0; }
+    &.status-warning { background: #fde047; }
+    &.status-urgent { background: #fdba74; }
+    &.status-overdue { background: #fca5a5; }
+    &.status-stalled { background: #e2e8f0; }
   }
   
-  .project-bar {
-    height: 22px;
+  /* Today Line */
+  .today-line {
+    position: absolute;
+    top: -5px;
+    bottom: -5px;
+    width: 2px;
+    background: #ef4444;
+    z-index: 10;
+    pointer-events: none;
+    
+    &::after {
+      content: 'NOW';
+      position: absolute;
+      top: -16px;
+      left: 50%;
+      transform: translateX(-50%);
+      background: #ef4444;
+      color: white;
+      font-size: 9px;
+      padding: 1px 4px;
+      border-radius: 2px;
+      white-space: nowrap;
+    }
   }
   
-  .event-marker {
+  /* Markers */
+  .t-marker {
+    position: absolute;
+    top: 50%;
+    transform: translate(-50%, -50%);
     width: 20px;
     height: 20px;
-    font-size: 14px;
+    background: white;
+    border: 2px solid #cbd5e1;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 10px;
+    z-index: 5;
+    box-shadow: 0 1px 2px rgba(0,0,0,0.05);
+    transition: transform 0.2s;
     
-    &.start {
-      width: 18px;
-      height: 18px;
-      font-size: 12px;
+    &:hover {
+      transform: translate(-50%, -50%) scale(1.2);
+      z-index: 20;
     }
     
-    &.review {
-      width: 19px;
-      height: 19px;
-      font-size: 13px;
+    &.type-start { border-color: #10b981; color: #10b981; }
+    &.type-delivery { border-color: #3b82f6; background: #eff6ff; }
+    &.type-review { border-color: #f59e0b; color: #f59e0b; }
+    &.type-deadline { 
+      width: 8px; 
+      height: 8px; 
+      border: none; 
+      background: #94a3b8;
+      
+      &.phase-modeling { background: #3b82f6; }
+      &.phase-softDecor { background: #f59e0b; }
+      &.phase-rendering { background: #8b5cf6; }
+      &.phase-postProcessing { background: #10b981; }
     }
     
-    &.delivery {
-      width: 22px;
-      height: 22px;
-      font-size: 16px;
-    }
+    /* Overdue/Urgent states for markers */
+    &.urgent { border-color: #f97316; color: #f97316; }
+    &.overdue { border-color: #ef4444; color: #ef4444; background: #fef2f2; }
   }
 }
 
-@keyframes fadeInDown {
-  from {
-    opacity: 0;
-    transform: translateX(-50%) translateY(-10px);
-  }
-  to {
-    opacity: 1;
-    transform: translateX(-50%) translateY(0);
+/* Empty State */
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 60px;
+  color: #94a3b8;
+  
+  p {
+    margin-top: 16px;
+    font-size: 14px;
   }
-}
+}
+
+/* Animation */
+@keyframes slideDown {
+  from { opacity: 0; transform: translateY(-10px); }
+  to { opacity: 1; transform: translateY(0); }
+}

+ 34 - 30
src/app/pages/team-leader/project-timeline/project-timeline.ts

@@ -113,6 +113,9 @@ export class ProjectTimelineComponent implements OnInit, OnDestroy {
   }
 
   private initializeData(): void {
+    // 🔧 关键修复:每次初始化时更新当前时间,确保过滤逻辑使用最新的时间戳
+    this.updateCurrentTime();
+    
     // 🆕 如果设置了 defaultDesigner,使用它作为初始筛选条件
     if (this.defaultDesigner && this.defaultDesigner !== 'all') {
       this.selectedDesigner = this.defaultDesigner;
@@ -598,8 +601,8 @@ export class ProjectTimelineComponent implements OnInit, OnDestroy {
   /**
    * 🆕 获取阶段信息(用于工具提示)
    */
-  getPhaseLabel(phaseName: PhaseName): string {
-    return PHASE_INFO[phaseName]?.label || phaseName;
+  getPhaseLabel(phaseName: PhaseName | string): string {
+    return PHASE_INFO[phaseName as PhaseName]?.label || phaseName;
   }
   
   /**
@@ -1032,39 +1035,40 @@ export class ProjectTimelineComponent implements OnInit, OnDestroy {
   
   /**
    * 🆕 获取今日线的精确位置(含时分)
-   * 注意:需要考虑左侧项目名称列的宽度(180px)
+   * 在新版布局中,今日线位于独立的 timeline-track-cell 中,因此只需返回相对位置即可
    */
   getTodayPosition(): string {
-    const rangeStart = this.timeRangeStart.getTime();
-    const rangeEnd = this.timeRangeEnd.getTime();
-    const rangeDuration = rangeEnd - rangeStart;
-    const currentTimeMs = this.currentTime.getTime();
-    
-    // 如果当前时间不在范围内,返回空(不显示今日线)
-    if (currentTimeMs < rangeStart || currentTimeMs > rangeEnd) {
-      console.log('⚠️ 今日线:当前时间不在时间范围内');
-      return 'calc(-1000%)'; // 移出可视区域
-    }
-    
-    // 计算在时间范围内的相对位置(0-100%)
-    const relativePosition = ((currentTimeMs - rangeStart) / rangeDuration) * 100;
-    const clampedPosition = Math.max(0, Math.min(100, relativePosition));
+    return this.getEventPosition(this.currentTime);
+  }
+
+  /**
+   * 🆕 获取状态的中文标签
+   */
+  getStatusLabel(status: string): string {
+    const labels: Record<string, string> = {
+      'normal': '正常',
+      'warning': '即将截止',
+      'urgent': '紧急',
+      'overdue': '已逾期',
+      'stalled': '已停滞'
+    };
+    return labels[status] || status;
+  }
+
+  /**
+   * 🆕 获取特定阶段的截止日期(如果存在且在时间范围内)
+   */
+  getPhaseDeadlineDate(project: ProjectTimeline, phase: string): Date | null {
+    if (!project.phaseDeadlines) return null;
     
-    // 🔧 关键修复:考虑左侧项目名称列的宽度(200px)
-    // 今日线的位置 = 200px + (剩余宽度 × 相对位置)
-    const result = `calc(200px + (100% - 200px) * ${clampedPosition / 100})`;
+    // 映射 phase 参数到 ProjectTimeline 中的 phaseDeadlines 键
+    const phaseInfo = project.phaseDeadlines[phase as PhaseName];
     
-    // 调试日志
-    console.log('📍 今日线位置计算:', {
-      当前时间: this.currentTime.toLocaleString('zh-CN'),
-      范围开始: new Date(rangeStart).toLocaleString('zh-CN'),
-      范围结束: new Date(rangeEnd).toLocaleString('zh-CN'),
-      相对位置百分比: `${clampedPosition.toFixed(2)}%`,
-      最终CSS值: result,
-      说明: `在${this.timelineScale === 'week' ? '7天' : '30天'}视图中,当前时间占${clampedPosition.toFixed(2)}%`
-    });
+    if (phaseInfo && phaseInfo.deadline) {
+      return new Date(phaseInfo.deadline);
+    }
     
-    return result;
+    return null;
   }
 }
 

+ 25 - 8
src/app/pages/team-leader/services/designer-workload.service.ts

@@ -241,22 +241,33 @@ export class DesignerWorkloadService {
       const projectData = project.data || {};
       
       // 1. 获取真实的项目开始时间
+      // 🔧 修复:尝试多种来源获取开始时间。
+      // 如果所有来源都无效,不要默认为当前时间(new Date()),这会导致旧项目复活。
+      // 应该默认为一个很久以前的时间,或者根据是否有 updatedAt 来判断。
+      let rawStartDate = projectData.phaseDeadlines?.modeling?.startDate ||
+                         projectData.requirementsConfirmedAt ||
+                         project.createdAt ||
+                         project.updatedAt;
+
       const realStartDate = normalizeDateInput(
-        projectData.phaseDeadlines?.modeling?.startDate ||
-          projectData.requirementsConfirmedAt ||
-          project.createdAt,
-        new Date()
+        rawStartDate,
+        // 🔧 关键修复:如果连 updatedAt 都没有,说明这是一个异常的旧数据,
+        // 默认为很久以前(如2020-01-01),确保它不会出现在当前时间轴上
+        new Date('2020-01-01') 
       );
       
+      // 🆕 判断项目是否已实质完成
+      const isProjectCompleted = project.status === '已完成' || 
+                                project.currentStage === '已完成' || 
+                                project.currentStage === '交付' ||
+                                project.status === 'delivery';
+
       // 2. 获取真实的交付日期
       let proposedEndDate = project.deadline || projectData.phaseDeadlines?.postProcessing?.deadline;
       let realEndDate: Date;
       
       if (proposedEndDate) {
         const proposed = normalizeDateInput(proposedEndDate, realStartDate);
-        // fix: 只要有明确的deadline,就使用它,即使它在过去(这样会导致逾期,但不应该强行延长周期)
-        // 之前的逻辑是 if (proposed > now) use proposed else use start + 30
-        // 这会导致过期的项目强行变成 "开始+30天",这对于短期测试项目(如11.22测试)是不对的
         realEndDate = proposed;
         
         // 只有当结束时间早于开始时间这种异常情况,才做修正
@@ -264,7 +275,13 @@ export class DesignerWorkloadService {
              realEndDate = addDays(realStartDate, 30);
         }
       } else {
-        realEndDate = addDays(realStartDate, 30);
+        // 🔧 修复:如果是已完成的项目且无截止日期,不要强行延长30天,而是设定为开始时间(或保持在过去)
+        // 这样可以确保它被正确过滤掉,而不是跳到未来显示为"进行中"
+        if (isProjectCompleted) {
+          realEndDate = new Date(realStartDate);
+        } else {
+          realEndDate = addDays(realStartDate, 30);
+        }
       }
       
       // 3. 获取真实的对图时间

+ 0 - 459
src/test-pages/data-cleanup/data-cleanup.component.ts

@@ -1,459 +0,0 @@
-import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { FormsModule } from '@angular/forms';
-import { FmodeParse } from 'fmode-ng/core';
-
-/**
- * 数据清理工具
- */
-@Component({
-  selector: 'app-data-cleanup',
-  standalone: true,
-  imports: [CommonModule, FormsModule],
-  template: `
-    <div class="cleanup-container">
-      <div class="cleanup-card">
-        <h1>🧹 数据清理工具</h1>
-        
-        <div class="info-box">
-          <p>⚠️ 此工具用于清理问卷数据</p>
-          <p>请谨慎使用!</p>
-        </div>
-
-        <div class="action-section">
-          <h2>清理操作</h2>
-          
-          <div class="input-group">
-            <label>用户ID(留空清理 test_user_001):</label>
-            <input 
-              type="text" 
-              [(ngModel)]="targetUserid" 
-              placeholder="例如:woAs2qCQAAPjkaSBZg3GVdXjlG3vxAOg"
-              class="input-field"
-            />
-          </div>
-          
-          <button class="btn-primary" (click)="resetUserActivation()" [disabled]="loading">
-            {{ loading ? '重置中...' : '🔄 重置指定用户激活状态' }}
-          </button>
-
-          <button class="btn-danger" (click)="cleanupUserData()" [disabled]="loading">
-            {{ loading ? '清理中...' : '🗑️ 清理指定用户问卷数据' }}
-          </button>
-          
-          <button class="btn-warning" (click)="cleanupTestData()" [disabled]="loading">
-            {{ loading ? '清理中...' : '🧪 清理测试用户 (test_user_001)' }}
-          </button>
-        </div>
-
-        <div class="log-section" *ngIf="logs.length > 0">
-          <h2>执行日志</h2>
-          <div class="log-list">
-            <div *ngFor="let log of logs" [class]="'log-item log-' + log.type">
-              {{ log.message }}
-            </div>
-          </div>
-        </div>
-
-        <div class="result-section" *ngIf="result">
-          <h2>✅ 操作结果</h2>
-          <pre>{{ result }}</pre>
-        </div>
-      </div>
-    </div>
-  `,
-  styles: [`
-    .cleanup-container {
-      min-height: 100vh;
-      padding: 40px 20px;
-      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-      display: flex;
-      justify-content: center;
-      align-items: flex-start;
-    }
-
-    .cleanup-card {
-      background: white;
-      border-radius: 16px;
-      padding: 40px;
-      max-width: 600px;
-      width: 100%;
-      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
-    }
-
-    h1 {
-      font-size: 24px;
-      margin: 0 0 20px 0;
-      color: #333;
-    }
-
-    h2 {
-      font-size: 18px;
-      margin: 20px 0 16px 0;
-      color: #555;
-    }
-
-    .info-box {
-      background: #fff3cd;
-      border: 1px solid #ffc107;
-      border-radius: 8px;
-      padding: 16px;
-      margin-bottom: 24px;
-    }
-
-    .info-box p {
-      margin: 4px 0;
-      color: #856404;
-      font-size: 14px;
-    }
-
-    .action-section {
-      margin-bottom: 24px;
-    }
-
-    button {
-      width: 100%;
-      padding: 14px 24px;
-      border: none;
-      border-radius: 8px;
-      font-size: 15px;
-      font-weight: 600;
-      cursor: pointer;
-      margin-bottom: 12px;
-      transition: all 0.2s;
-    }
-
-    .btn-primary {
-      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-      color: white;
-    }
-
-    .btn-primary:hover:not(:disabled) {
-      transform: translateY(-1px);
-      box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
-    }
-
-    .btn-danger {
-      background: #dc3545;
-      color: white;
-    }
-
-    .btn-danger:hover:not(:disabled) {
-      background: #c82333;
-    }
-
-    .btn-secondary {
-      background: #6c757d;
-      color: white;
-    }
-
-    .btn-secondary:hover:not(:disabled) {
-      background: #5a6268;
-    }
-
-    .btn-warning {
-      background: #ffc107;
-      color: #333;
-    }
-
-    .btn-warning:hover:not(:disabled) {
-      background: #e0a800;
-    }
-
-    button:disabled {
-      opacity: 0.6;
-      cursor: not-allowed;
-    }
-
-    .input-group {
-      margin-bottom: 20px;
-    }
-
-    .input-group label {
-      display: block;
-      margin-bottom: 8px;
-      font-size: 14px;
-      font-weight: 600;
-      color: #555;
-    }
-
-    .input-field {
-      width: 100%;
-      padding: 12px;
-      border: 2px solid #ddd;
-      border-radius: 8px;
-      font-size: 14px;
-      font-family: monospace;
-      transition: border-color 0.2s;
-    }
-
-    .input-field:focus {
-      outline: none;
-      border-color: #667eea;
-    }
-
-    .log-section {
-      background: #f8f9fa;
-      border-radius: 8px;
-      padding: 16px;
-      margin-bottom: 24px;
-    }
-
-    .log-list {
-      max-height: 300px;
-      overflow-y: auto;
-    }
-
-    .log-item {
-      padding: 8px;
-      margin-bottom: 4px;
-      border-radius: 4px;
-      font-size: 13px;
-      font-family: monospace;
-    }
-
-    .log-success {
-      background: #d4edda;
-      color: #155724;
-    }
-
-    .log-info {
-      background: #d1ecf1;
-      color: #0c5460;
-    }
-
-    .log-warning {
-      background: #fff3cd;
-      color: #856404;
-    }
-
-    .log-error {
-      background: #f8d7da;
-      color: #721c24;
-    }
-
-    .result-section {
-      background: #e7f3ff;
-      border-radius: 8px;
-      padding: 16px;
-    }
-
-    pre {
-      margin: 8px 0 0 0;
-      font-size: 13px;
-      color: #333;
-      white-space: pre-wrap;
-      word-wrap: break-word;
-    }
-  `]
-})
-export class DataCleanupComponent {
-  loading: boolean = false;
-  logs: Array<{type: string, message: string}> = [];
-  result: string = '';
-  targetUserid: string = '';
-
-  addLog(message: string, type: 'success' | 'info' | 'warning' | 'error' = 'info') {
-    this.logs.push({ type, message });
-    console.log(message);
-  }
-
-  /**
-   * 清理指定用户的问卷数据
-   */
-  async cleanupUserData() {
-    if (this.loading) return;
-    
-    const userid = this.targetUserid.trim() || 'test_user_001';
-    
-    this.loading = true;
-    this.logs = [];
-    this.result = '';
-    
-    try {
-      const Parse = FmodeParse.with('nova');
-      
-      this.addLog(`🔍 查找用户: ${userid}`, 'info');
-      
-      // 1. 查找用户 Profile
-      const profileQuery = new Parse.Query('Profile');
-      profileQuery.equalTo('userid', userid);
-      const profile = await profileQuery.first();
-      
-      if (!profile) {
-        this.addLog(`⚠️ 未找到用户: ${userid}`, 'warning');
-        this.result = `未找到 userid = ${userid} 的用户`;
-        return;
-      }
-      
-      this.addLog(`✅ 找到用户: ${profile.get('name') || profile.get('realname') || userid}`, 'success');
-      this.addLog(`📋 Profile ID: ${profile.id}`, 'info');
-      
-      // 2. 清理 Profile 的问卷字段
-      this.addLog('🧹 清理 Profile 问卷字段...', 'info');
-      profile.set('surveyCompleted', false);
-      profile.unset('surveyCompletedAt');
-      profile.unset('surveyLogId');
-      await profile.save();
-      this.addLog('✅ Profile 已清理', 'success');
-      
-      // 3. 删除 SurveyLog
-      this.addLog('🔍 查找 SurveyLog...', 'info');
-      const surveyQuery = new Parse.Query('SurveyLog');
-      surveyQuery.equalTo('type', 'survey-profile');
-      surveyQuery.equalTo('profile', profile.toPointer());
-      const surveys = await surveyQuery.find();
-      
-      this.addLog(`📋 找到 ${surveys.length} 条 SurveyLog`, 'info');
-      
-      for (const survey of surveys) {
-        await survey.destroy();
-      }
-      
-      this.addLog(`✅ 已删除 ${surveys.length} 条 SurveyLog`, 'success');
-      
-      this.result = `清理完成!
-- 用户: ${profile.get('name') || userid}
-- Profile 已重置
-- 删除了 ${surveys.length} 条问卷记录
-
-现在可以重新填写问卷了!`;
-      
-    } catch (err: any) {
-      this.addLog(`❌ 清理失败: ${err.message}`, 'error');
-      this.result = `清理失败: ${err.message}`;
-    } finally {
-      this.loading = false;
-    }
-  }
-
-  /**
-   * 清理测试数据(test_user_001)
-   */
-  async cleanupTestData() {
-    if (this.loading) return;
-    
-    this.loading = true;
-    this.logs = [];
-    this.result = '';
-    
-    try {
-      const Parse = FmodeParse.with('nova');
-      
-      this.addLog('🔍 查找测试用户...', 'info');
-      
-      // 1. 查找测试用户
-      const profileQuery = new Parse.Query('Profile');
-      profileQuery.equalTo('userid', 'test_user_001');
-      const profile = await profileQuery.first();
-      
-      if (!profile) {
-        this.addLog('⚠️ 未找到测试用户', 'warning');
-        this.result = '未找到 userid = test_user_001 的测试用户';
-        return;
-      }
-      
-      this.addLog(`✅ 找到测试用户: ${profile.id}`, 'success');
-      
-      // 2. 清理 Profile 的问卷字段
-      this.addLog('🧹 清理 Profile 问卷字段...', 'info');
-      profile.set('surveyCompleted', false);
-      profile.unset('surveyCompletedAt');
-      profile.unset('surveyLogId');
-      await profile.save();
-      this.addLog('✅ Profile 已清理', 'success');
-      
-      // 3. 删除 SurveyLog
-      this.addLog('🔍 查找 SurveyLog...', 'info');
-      const surveyQuery = new Parse.Query('SurveyLog');
-      surveyQuery.equalTo('type', 'survey-profile');
-      surveyQuery.equalTo('profile', profile.toPointer());
-      const surveys = await surveyQuery.find();
-      
-      this.addLog(`📋 找到 ${surveys.length} 条 SurveyLog`, 'info');
-      
-      for (const survey of surveys) {
-        await survey.destroy();
-      }
-      
-      this.addLog(`✅ 已删除 ${surveys.length} 条 SurveyLog`, 'success');
-      
-      this.result = `清理完成!
-- Profile 已重置
-- 删除了 ${surveys.length} 条问卷记录
-      
-现在可以重新测试问卷流程了!`;
-      
-    } catch (err: any) {
-      this.addLog(`❌ 清理失败: ${err.message}`, 'error');
-      this.result = `清理失败: ${err.message}`;
-    } finally {
-      this.loading = false;
-    }
-  }
-
-  /**
-   * 重置指定用户的激活状态
-   */
-  async resetUserActivation() {
-    if (this.loading) return;
-    
-    const userid = this.targetUserid.trim() || 'test_user_001';
-    
-    this.loading = true;
-    this.logs = [];
-    this.result = '';
-    
-    try {
-      const Parse = FmodeParse.with('nova');
-      
-      this.addLog(`🔍 查找用户: ${userid}`, 'info');
-      
-      const profileQuery = new Parse.Query('Profile');
-      profileQuery.equalTo('userid', userid);
-      const profile = await profileQuery.first();
-      
-      if (!profile) {
-        this.addLog(`⚠️ 未找到用户: ${userid}`, 'warning');
-        this.result = `未找到 userid = ${userid} 的用户`;
-        return;
-      }
-      
-      this.addLog(`✅ 找到用户: ${profile.get('name') || profile.get('realname') || userid}`, 'success');
-      this.addLog(`📋 Profile ID: ${profile.id}`, 'info');
-      
-      // 重置激活状态
-      this.addLog('🔄 重置激活状态...', 'info');
-      profile.set('isActivated', false);
-      profile.unset('activatedAt');
-      await profile.save();
-      
-      this.addLog('✅ 激活状态已重置', 'success');
-      
-      this.result = `激活状态已重置!
-- 用户: ${profile.get('name') || userid}
-- Profile ID: ${profile.id}
-- isActivated: false
-
-⚠️ 重要提示:
-不要清除浏览器 localStorage!
-直接刷新激活页面即可看到"身份确认"界面。`;
-      
-    } catch (err: any) {
-      this.addLog(`❌ 重置失败: ${err.message}`, 'error');
-      this.result = `重置失败: ${err.message}`;
-    } finally {
-      this.loading = false;
-    }
-  }
-
-  /**
-   * 重置测试用户激活状态(快捷方式)
-   */
-  async resetActivation() {
-    this.targetUserid = 'test_user_001';
-    await this.resetUserActivation();
-  }
-}
-
-

+ 0 - 533
src/test-pages/wxwork-activation-test/wxwork-activation-test.component.ts

@@ -1,533 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { Router, ActivatedRoute } from '@angular/router';
-import { WxworkAuth } from 'fmode-ng/core';
-
-/**
- * 企业微信身份激活测试组件
- * 用于调试身份激活 + 问卷整合流程
- */
-@Component({
-  selector: 'app-wxwork-activation-test',
-  standalone: true,
-  imports: [CommonModule],
-  host: {
-    'class': 'full-page-component'
-  },
-  template: `
-    <div class="test-wrapper">
-    <div class="test-container">
-      <h1>🧪 企业微信身份激活测试</h1>
-      
-      <div class="test-section">
-        <h2>测试步骤:</h2>
-        <ol>
-          <li [class.active]="currentStep >= 1">初始化企微认证</li>
-          <li [class.active]="currentStep >= 2">执行身份激活</li>
-          <li [class.active]="currentStep >= 3">检查问卷状态</li>
-          <li [class.active]="currentStep >= 4">跳转到相应页面</li>
-        </ol>
-      </div>
-
-      <div class="test-section" *ngIf="logs.length > 0">
-        <h2>执行日志:</h2>
-        <div class="log-container">
-          <div *ngFor="let log of logs" [class]="'log-' + log.type">
-            <span class="log-time">{{ log.time }}</span>
-            <span class="log-message">{{ log.message }}</span>
-          </div>
-        </div>
-      </div>
-
-      <div class="test-section" *ngIf="profileInfo">
-        <h2>员工信息:</h2>
-        <table>
-          <tr>
-            <td>员工ID:</td>
-            <td>{{ profileInfo.id }}</td>
-          </tr>
-          <tr>
-            <td>姓名:</td>
-            <td>{{ profileInfo.realname }}</td>
-          </tr>
-          <tr>
-            <td>角色:</td>
-            <td>{{ profileInfo.roleName }}</td>
-          </tr>
-          <tr>
-            <td>问卷状态:</td>
-            <td>
-              <span [class]="profileInfo.surveyCompleted ? 'status-completed' : 'status-pending'">
-                {{ profileInfo.surveyCompleted ? '✅ 已完成' : '❌ 未完成' }}
-              </span>
-            </td>
-          </tr>
-        </table>
-      </div>
-
-      <div class="test-section">
-        <h2>测试操作:</h2>
-        <button (click)="startTest()" [disabled]="testing">
-          {{ testing ? '测试中...' : '开始测试' }}
-        </button>
-        <button (click)="resetSurvey()" [disabled]="!profileInfo">
-          重置问卷状态
-        </button>
-        <button (click)="goToSurvey()" [disabled]="!profileInfo">
-          前往问卷页面
-        </button>
-        <button (click)="goToDashboard()" [disabled]="!profileInfo">
-          前往工作台
-        </button>
-      </div>
-
-      <div class="test-section" *ngIf="error">
-        <h2 style="color: red;">❌ 错误信息:</h2>
-        <pre>{{ error }}</pre>
-      </div>
-    </div>
-    </div>
-  `,
-  styles: [`
-    /* 强制突破父容器限制 */
-    :host {
-      display: block;
-      position: fixed !important;
-      top: 0 !important;
-      left: 0 !important;
-      right: 0 !important;
-      bottom: 0 !important;
-      width: 100vw !important;
-      height: 100vh !important;
-      overflow-y: auto !important;
-      overflow-x: hidden !important;
-      z-index: 1;
-      background: #f5f5f5;
-      -webkit-overflow-scrolling: touch;
-    }
-
-    .test-wrapper {
-      min-height: 100vh;
-      width: 100%;
-      overflow-y: auto;
-      overflow-x: hidden;
-    }
-
-    .test-container {
-      max-width: 900px;
-      margin: 40px auto;
-      padding: 20px 20px 60px;
-      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-    }
-
-    h1 {
-      color: #333;
-      margin-bottom: 30px;
-      font-size: 28px;
-    }
-
-    .test-section {
-      background: white;
-      border-radius: 12px;
-      padding: 24px;
-      margin-bottom: 20px;
-      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
-    }
-
-    h2 {
-      color: #555;
-      font-size: 18px;
-      margin: 0 0 16px 0;
-    }
-
-    ol {
-      margin: 0;
-      padding-left: 24px;
-    }
-
-    li {
-      padding: 8px 0;
-      color: #999;
-      transition: all 0.3s;
-    }
-
-    li.active {
-      color: #007aff;
-      font-weight: 500;
-    }
-
-    .log-container {
-      background: #f5f5f5;
-      border-radius: 8px;
-      padding: 16px;
-      max-height: 400px;
-      height: 400px;
-      overflow-y: auto;
-      overflow-x: hidden;
-      font-family: 'Courier New', monospace;
-      font-size: 13px;
-      -webkit-overflow-scrolling: touch;
-    }
-
-    .log-container > div {
-      padding: 4px 0;
-      display: flex;
-      gap: 12px;
-    }
-
-    .log-time {
-      color: #999;
-      min-width: 80px;
-    }
-
-    .log-info { color: #007aff; }
-    .log-success { color: #34c759; }
-    .log-warning { color: #ff9500; }
-    .log-error { color: #ff3b30; }
-
-    table {
-      width: 100%;
-      border-collapse: collapse;
-    }
-
-    td {
-      padding: 12px;
-      border-bottom: 1px solid #eee;
-    }
-
-    td:first-child {
-      font-weight: 500;
-      color: #666;
-      width: 120px;
-    }
-
-    .status-completed {
-      color: #34c759;
-      font-weight: 500;
-    }
-
-    .status-pending {
-      color: #ff9500;
-      font-weight: 500;
-    }
-
-    button {
-      padding: 12px 24px;
-      margin-right: 12px;
-      border: none;
-      border-radius: 8px;
-      background: #007aff;
-      color: white;
-      font-size: 14px;
-      font-weight: 500;
-      cursor: pointer;
-      transition: all 0.2s;
-    }
-
-    button:hover:not(:disabled) {
-      background: #0051d5;
-      transform: translateY(-1px);
-    }
-
-    button:disabled {
-      background: #ccc;
-      cursor: not-allowed;
-      transform: none;
-    }
-
-    pre {
-      background: #f5f5f5;
-      padding: 16px;
-      border-radius: 8px;
-      overflow-x: auto;
-      white-space: pre-wrap;
-      word-wrap: break-word;
-    }
-
-    /* 确保移动端也能滚动 */
-    @media (max-width: 768px) {
-      .test-container {
-        margin: 20px auto;
-        padding: 16px 16px 40px;
-      }
-
-      .log-container {
-        height: 300px;
-        max-height: 300px;
-      }
-
-      button {
-        width: 100%;
-        margin-right: 0;
-        margin-bottom: 8px;
-      }
-    }
-  `]
-})
-export class WxworkActivationTestComponent implements OnInit {
-  cid: string = '';
-  currentStep: number = 0;
-  testing: boolean = false;
-  logs: Array<{ time: string; message: string; type: string }> = [];
-  profileInfo: any = null;
-  error: string = '';
-  
-  private wxAuth: WxworkAuth | null = null;
-
-  constructor(
-    private router: Router,
-    private route: ActivatedRoute
-  ) {}
-
-  ngOnInit() {
-    // 从URL获取cid(同步处理)
-    const snapshot = this.route.snapshot;
-    this.cid = snapshot.paramMap.get('cid') || 'test';
-    this.addLog('获取公司ID: ' + this.cid, 'info');
-    
-    // 设置localStorage用于测试模式
-    if (this.cid === 'test' || this.cid === 'demo') {
-      localStorage.setItem('company', this.cid);
-      this.addLog('🧪 测试模式已启用', 'info');
-    }
-  }
-
-  /**
-   * 开始测试
-   */
-  async startTest() {
-    this.testing = true;
-    this.error = '';
-    this.logs = [];
-    this.currentStep = 0;
-
-    try {
-      // 检查cid
-      if (!this.cid) {
-        throw new Error('公司ID未设置,请刷新页面重试');
-      }
-      
-      // Step 1: 初始化企微认证(仅非测试模式)
-      this.currentStep = 1;
-      if (this.cid !== 'test' && this.cid !== 'demo') {
-        this.addLog('初始化企微认证...', 'info');
-        await this.initAuth();
-      } else {
-        this.addLog('🧪 测试模式,跳过企微认证初始化', 'info');
-      }
-      
-      // Step 2: 执行身份激活
-      this.currentStep = 2;
-      this.addLog('执行身份激活...', 'info');
-      await this.activate();
-      
-      // Step 3: 检查问卷状态
-      this.currentStep = 3;
-      this.addLog('检查问卷状态...', 'info');
-      await this.checkSurvey();
-      
-      // Step 4: 决定跳转
-      this.currentStep = 4;
-      this.addLog('测试完成!', 'success');
-      
-      if (!this.profileInfo.surveyCompleted) {
-        this.addLog('检测到问卷未完成,可以点击"前往问卷页面"按钮测试', 'warning');
-      } else {
-        this.addLog('问卷已完成,可以点击"前往工作台"按钮测试', 'success');
-      }
-      
-    } catch (err: any) {
-      this.error = err.message || String(err);
-      this.addLog('测试失败: ' + this.error, 'error');
-    } finally {
-      this.testing = false;
-    }
-  }
-
-  /**
-   * 初始化认证
-   */
-  private async initAuth() {
-    try {
-      // 检查cid是否有效
-      if (!this.cid) {
-        throw new Error('公司ID(cid)未设置');
-      }
-      
-      this.wxAuth = new WxworkAuth({ cid: this.cid, appId: 'crm' });
-      this.addLog('✅ 企微认证初始化成功', 'success');
-    } catch (error) {
-      throw new Error('企微认证初始化失败: ' + error);
-    }
-  }
-
-  /**
-   * 执行激活
-   */
-  private async activate() {
-    try {
-      // 测试模式:使用模拟数据
-      if (this.cid === 'test' || this.cid === 'demo') {
-        this.addLog('🧪 使用测试模式,创建模拟Profile', 'warning');
-        await this.createTestProfile();
-        return;
-      }
-      
-      // 真实模式:执行企微认证
-      const { user, profile } = await this.wxAuth!.authenticateAndLogin();
-      
-      if (!profile) {
-        throw new Error('未能获取Profile信息');
-      }
-      
-      this.profileInfo = {
-        id: profile.id,
-        realname: profile.get('realname') || profile.get('name'),
-        roleName: profile.get('roleName'),
-        surveyCompleted: profile.get('surveyCompleted') || false
-      };
-      
-      this.addLog(`✅ 身份激活成功: ${this.profileInfo.realname}`, 'success');
-      this.addLog(`   角色: ${this.profileInfo.roleName}`, 'info');
-      
-    } catch (error) {
-      throw new Error('身份激活失败: ' + error);
-    }
-  }
-  
-  /**
-   * 创建测试Profile
-   */
-  private async createTestProfile() {
-    const Parse = await import('fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
-    
-    try {
-      // 尝试查找现有的测试Profile
-      const query = new Parse.Query('Profile');
-      query.equalTo('name', '测试员工');
-      query.limit(1);
-      
-      let profile = await query.first();
-      
-      // 如果不存在,创建新的
-      if (!profile) {
-        const Profile = Parse.Object.extend('Profile');
-        profile = new Profile();
-        
-        profile.set('name', '测试员工');
-        profile.set('realname', '王刚');
-        profile.set('roleName', '组员');
-        profile.set('surveyCompleted', false);
-        
-        profile = await profile.save();
-        this.addLog('✅ 已创建新的测试Profile', 'success');
-      } else {
-        this.addLog('✅ 找到现有测试Profile', 'success');
-      }
-      
-      // 保存Profile信息
-      this.profileInfo = {
-        id: profile.id,
-        realname: profile.get('realname') || profile.get('name'),
-        roleName: profile.get('roleName'),
-        surveyCompleted: profile.get('surveyCompleted') || false
-      };
-      
-      // 缓存Profile ID
-      localStorage.setItem('Parse/ProfileId', profile.id);
-      
-      this.addLog(`   员工姓名: ${this.profileInfo.realname}`, 'info');
-      this.addLog(`   员工角色: ${this.profileInfo.roleName}`, 'info');
-      
-    } catch (error) {
-      throw new Error('创建测试Profile失败: ' + error);
-    }
-  }
-
-  /**
-   * 检查问卷状态
-   */
-  private async checkSurvey() {
-    const Parse = await import('fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
-    
-    try {
-      const query = new Parse.Query('Profile');
-      const profile = await query.get(this.profileInfo.id);
-      
-      const surveyCompleted = profile.get('surveyCompleted') || false;
-      this.profileInfo.surveyCompleted = surveyCompleted;
-      
-      if (surveyCompleted) {
-        this.addLog('✅ 问卷已完成', 'success');
-      } else {
-        this.addLog('⚠️ 问卷未完成', 'warning');
-      }
-      
-    } catch (error) {
-      this.addLog('⚠️ 无法查询问卷状态: ' + error, 'warning');
-    }
-  }
-
-  /**
-   * 重置问卷状态
-   */
-  async resetSurvey() {
-    if (!this.profileInfo) return;
-    
-    const Parse = await import('fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
-    
-    try {
-      this.addLog('重置问卷状态...', 'info');
-      
-      const query = new Parse.Query('Profile');
-      const profile = await query.get(this.profileInfo.id);
-      
-      profile.set('surveyCompleted', false);
-      profile.unset('surveyCompletedAt');
-      profile.unset('surveyLogId');
-      
-      await profile.save();
-      
-      this.profileInfo.surveyCompleted = false;
-      this.addLog('✅ 问卷状态已重置', 'success');
-      
-    } catch (error) {
-      this.addLog('❌ 重置失败: ' + error, 'error');
-    }
-  }
-
-  /**
-   * 前往问卷页面
-   */
-  goToSurvey() {
-    this.addLog('跳转到问卷页面...', 'info');
-    this.router.navigate(['/wxwork', this.cid, 'survey', 'profile']);
-  }
-
-  /**
-   * 前往工作台
-   */
-  goToDashboard() {
-    this.addLog('跳转到工作台...', 'info');
-    this.router.navigate(['/wxwork', this.cid, 'designer', 'dashboard']);
-  }
-
-  /**
-   * 添加日志
-   */
-  private addLog(message: string, type: 'info' | 'success' | 'warning' | 'error') {
-    const time = new Date().toLocaleTimeString();
-    this.logs.push({ time, message, type });
-    
-    // 自动滚动到底部
-    setTimeout(() => {
-      const container = document.querySelector('.log-container');
-      if (container) {
-        container.scrollTop = container.scrollHeight;
-      }
-    }, 100);
-  }
-}
-
-
-