Bläddra i källkod

refactor: update project timeline component for improved UI and functionality

- Replaced the list view with a timeline view for better project visualization.
- Introduced time scale controls for switching between week and month views.
- Enhanced the refresh button functionality for real-time data updates.
- Streamlined the project display logic, ensuring consistent styling and improved responsiveness.
- Removed deprecated list view styles and components to simplify the codebase.
0235711 2 dagar sedan
förälder
incheckning
97f4b9d95e

+ 153 - 238
src/app/pages/team-leader/project-timeline/project-timeline.html

@@ -37,50 +37,32 @@
         </button>
       </div>
 
-      <!-- 视图切换 -->
-      <div class="filter-group view-controls">
+      <!-- 时间尺度切换 -->
+      <div class="filter-group time-scale-controls">
+        <label>时间范围:</label>
         <button 
-          class="view-btn"
-          [class.active]="viewMode === 'list'"
-          (click)="toggleViewMode('list')">
-          📋 列表
+          class="scale-btn"
+          [class.active]="timelineScale === 'week'"
+          (click)="toggleTimelineScale('week')">
+          📆 7天
         </button>
         <button 
-          class="view-btn"
-          [class.active]="viewMode === 'timeline'"
-          (click)="toggleViewMode('timeline')">
-          📅 时间轴
+          class="scale-btn"
+          [class.active]="timelineScale === 'month'"
+          (click)="toggleTimelineScale('month')">
+          📅 30天
         </button>
       </div>
       
-      <!-- 时间尺度切换(仅在时间轴视图显示) -->
-      @if (viewMode === 'timeline') {
-        <div class="filter-group time-scale-controls">
-          <label>时间范围:</label>
-          <button 
-            class="scale-btn"
-            [class.active]="timelineScale === 'week'"
-            (click)="toggleTimelineScale('week')">
-            📆 7天
-          </button>
-          <button 
-            class="scale-btn"
-            [class.active]="timelineScale === 'month'"
-            (click)="toggleTimelineScale('month')">
-            📅 30天
-          </button>
-        </div>
-        
-        <!-- 🆕 手动刷新按钮 -->
-        <div class="filter-group refresh-controls">
-          <button 
-            class="refresh-btn"
-            (click)="refresh()"
-            title="刷新数据和时间线(自动10分钟刷新一次)">
-            🔄 刷新
-          </button>
-        </div>
-      }
+      <!-- 🆕 手动刷新按钮 -->
+      <div class="filter-group refresh-controls">
+        <button 
+          class="refresh-btn"
+          (click)="refresh()"
+          title="刷新数据和时间线(自动10分钟刷新一次)">
+          🔄 刷新
+        </button>
+      </div>
 
       <!-- 排序方式 -->
       <div class="filter-group sort-controls">
@@ -132,227 +114,160 @@
   </div>
 
   <!-- 时间轴主体 -->
-  <div class="timeline-body" [class.timeline-view]="viewMode === 'timeline'">
-    @if (viewMode === 'timeline') {
-      <!-- 时间轴视图 -->
-      <div class="timeline-view-container">
-        <!-- 图例说明 -->
-        <div class="timeline-legend">
-          <div class="legend-item">
-            <span class="legend-icon start-icon">▶️</span>
-            <span class="legend-label">项目开始</span>
-          </div>
-          <div class="legend-item">
-            <span class="legend-icon review-icon">📋</span>
-            <span class="legend-label">小图对图(灰色=已完成)</span>
-          </div>
-          <div class="legend-item">
-            <span class="legend-icon delivery-icon">📦</span>
-            <span class="legend-label">交付日期</span>
-          </div>
-          <div class="legend-separator"></div>
-          <div class="legend-item legend-phase">
-            <span class="legend-label">🎨 建模截止</span>
-          </div>
-          <div class="legend-item legend-phase">
-            <span class="legend-label">🪑 软装截止</span>
-          </div>
-          <div class="legend-item legend-phase">
-            <span class="legend-label">🖼️ 渲染截止</span>
-          </div>
-          <div class="legend-item legend-phase">
-            <span class="legend-label">✨ 后期截止</span>
-          </div>
-          <div class="legend-separator"></div>
-          <div class="legend-item">
-            <div class="legend-bar-demo legend-bar-green"></div>
-            <span class="legend-label">🟢 正常进行(2天+)</span>
-          </div>
-          <div class="legend-item">
-            <div class="legend-bar-demo legend-bar-yellow"></div>
-            <span class="legend-label">🟡 前一天(24小时内)</span>
-          </div>
-          <div class="legend-item">
-            <div class="legend-bar-demo legend-bar-orange"></div>
-            <span class="legend-label">🟠 事件当天(6小时+)</span>
-          </div>
-          <div class="legend-item">
-            <div class="legend-bar-demo legend-bar-red"></div>
-            <span class="legend-label">🔴 紧急(6小时内)</span>
-          </div>
-          <div class="legend-item legend-note">
-            <span class="legend-label">💡 仅显示今日线之后的关键事件和阶段截止时间</span>
-          </div>
+  <div class="timeline-body timeline-view">
+    <!-- 时间轴视图 -->
+    <div class="timeline-view-container">
+      <!-- 图例说明 -->
+      <div class="timeline-legend">
+        <div class="legend-item">
+          <span class="legend-icon start-icon">▶️</span>
+          <span class="legend-label">项目开始</span>
         </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) { 周六 }
-                    }
-                  </div>
-                }
-              </div>
-            }
-          </div>
+        <div class="legend-item">
+          <span class="legend-icon review-icon">📋</span>
+          <span class="legend-label">小图对图(灰色=已完成)</span>
         </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 class="legend-item">
+          <span class="legend-icon delivery-icon">📦</span>
+          <span class="legend-label">交付日期</span>
+        </div>
+        <div class="legend-separator"></div>
+        <div class="legend-item legend-phase">
+          <span class="legend-icon phase-icon modeling-icon">建</span>
+          <span class="legend-label">建模截止</span>
+        </div>
+        <div class="legend-item legend-phase">
+          <span class="legend-icon phase-icon softDecor-icon">软</span>
+          <span class="legend-label">软装截止</span>
+        </div>
+        <div class="legend-item legend-phase">
+          <span class="legend-icon phase-icon rendering-icon">渲</span>
+          <span class="legend-label">渲染截止</span>
+        </div>
+        <div class="legend-item legend-phase">
+          <span class="legend-icon phase-icon postProcessing-icon">后</span>
+          <span class="legend-label">后期截止</span>
+        </div>
+        <div class="legend-separator"></div>
+        <div class="legend-item">
+          <div class="legend-bar-demo legend-bar-green"></div>
+          <span class="legend-label">🟢 正常进行(2天+)</span>
+        </div>
+        <div class="legend-item">
+          <div class="legend-bar-demo legend-bar-yellow"></div>
+          <span class="legend-label">🟡 前一天(24小时内)</span>
+        </div>
+        <div class="legend-item">
+          <div class="legend-bar-demo legend-bar-orange"></div>
+          <span class="legend-label">🟠 事件当天(6小时+)</span>
         </div>
+        <div class="legend-item">
+          <div class="legend-bar-demo legend-bar-red"></div>
+          <span class="legend-label">🔴 紧急(6小时内)</span>
+        </div>
+        <div class="legend-item legend-note">
+          <span class="legend-label">💡 仅显示今日线之后的关键事件和阶段截止时间</span>
+        </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">
-                  <span class="project-name-label" [title]="project.projectName">
-                    {{ project.projectName }}
-                  </span>
-                  <span class="designer-label">{{ project.designerName }}</span>
-                  @if (project.priority === 'critical' || project.priority === 'high') {
-                    <span class="priority-badge" [class]="'badge-' + project.priority">
-                      @if (project.priority === 'critical') { ‼️ }
-                      @else { 🔥 }
-                    </span>
-                  }
-                  <!-- 🆕 空间与交付物统计徽章 -->
-                  @if (getSpaceDeliverableSummary(project.projectId); as summary) {
-                    <span class="space-deliverable-badge" 
-                          [title]="formatSpaceDeliverableTooltip(project.projectId)"
-                          [style.background-color]="getProjectDeliveryStatusColor(project.projectId)">
-                      📦 {{ summary.spacesWithDeliverables }}/{{ summary.totalSpaces }}
-                    </span>
-                  }
-                </div>
-                
-                <!-- 时间轴区域 -->
-                <div class="timeline-track">
-                  <!-- 项目条形图 -->
-                  <div class="project-bar"
-                       [style.left]="getProjectPosition(project).left"
-                       [style.width]="getProjectPosition(project).width"
-                       [style.background]="getProjectPosition(project).background"
-                       [class.status-overdue]="project.status === 'overdue'"
-                       [title]="project.projectName + ' | ' + project.stageName + ' ' + project.stageProgress + '%'">
-                    <!-- 进度填充 -->
-                    <div class="progress-fill" [style.width]="project.stageProgress + '%'"></div>
-                  </div>
-                  
-                  <!-- 🆕 使用统一的事件标记方法 -->
-                  @for (event of getProjectEvents(project); track event.date) {
-                    <div class="event-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.phase ? ' (' + getPhaseLabel(event.phase) + ')' : '')">
-                      {{ event.icon }}
-                    </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) { 周六 }
                   }
                 </div>
-              </div>
-            }
+              }
+            </div>
           }
         </div>
       </div>
-    } @else {
-      <!-- 列表视图 -->
-      <div class="projects-list">
+        
+      <!-- 今日标记线(实时移动,精确到分钟) -->
+      <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; let i = $index) {
-            <div 
-              class="project-item"
-              [class]="'status-' + project.status"
-              (click)="onProjectClick(project.projectId)">
-              
-              <!-- 优先级指示条 -->
-              <div class="priority-bar" [class]="'priority-' + project.priority"></div>
-              
-              <!-- 项目信息 -->
-              <div class="project-content">
-                <div class="project-header">
-                  <h4 class="project-name">{{ project.projectName }}</h4>
-                  <div class="project-badges">
-                    @if (project.isStalled) {
-                      <span class="badge badge-stalled">⏸️ 停滞{{ project.stalledDays }}天</span>
-                    }
-                    @if (project.urgentCount > 0) {
-                      <span class="badge badge-urgent">🔥 催办{{ project.urgentCount }}次</span>
-                    }
-                    @if (project.status === 'overdue') {
-                      <span class="badge badge-overdue">⚠️ 逾期</span>
-                    } @else if (project.status === 'urgent') {
-                      <span class="badge badge-warning">⏰ 紧急</span>
-                    }
-                    <!-- 🆕 空间与交付物统计徽章 -->
-                    @if (getSpaceDeliverableSummary(project.projectId); as summary) {
-                      <span class="badge badge-deliverable"
-                            [title]="formatSpaceDeliverableTooltip(project.projectId)"
-                            [style.background-color]="getProjectDeliveryStatusColor(project.projectId)"
-                            [style.color]="'white'">
-                        📦 空间 {{ summary.spacesWithDeliverables }}/{{ summary.totalSpaces }} | 文件 {{ summary.totalDeliverableFiles }}
-                      </span>
-                    }
-                  </div>
-                </div>
-                
-                <div class="project-meta">
-                  <span class="meta-item">
-                    <span class="meta-label">设计师:</span>
-                    <span class="meta-value">{{ project.designerName }}</span>
-                  </span>
-                  <span class="meta-item">
-                    <span class="meta-label">阶段:</span>
-                    <span class="meta-value">{{ project.stageName }}</span>
+          @for (project of filteredProjects; track project.projectId) {
+            <div class="timeline-row" (click)="onProjectClick(project.projectId)">
+              <!-- 项目名称标签 -->
+              <div class="project-label">
+                <span class="project-name-label" [title]="project.projectName">
+                  {{ project.projectName }}
+                </span>
+                <span class="designer-label">{{ project.designerName }}</span>
+                @if (project.priority === 'critical' || project.priority === 'high') {
+                  <span class="priority-badge" [class]="'badge-' + project.priority">
+                    @if (project.priority === 'critical') { ‼️ }
+                    @else { 🔥 }
                   </span>
-                  <span class="meta-item">
-                    <span class="meta-label">进度:</span>
-                    <span class="meta-value">{{ project.stageProgress }}%</span>
-                  </span>
-                  <span class="meta-item">
-                    <span class="meta-label">截止:</span>
-                    <span class="meta-value" [class.text-danger]="project.status === 'overdue'">
-                      {{ formatDate(project.endDate) }}
-                    </span>
+                }
+                <!-- 🆕 空间与交付物统计徽章 -->
+                @if (getSpaceDeliverableSummary(project.projectId); as summary) {
+                  <span class="space-deliverable-badge" 
+                        [title]="formatSpaceDeliverableTooltip(project.projectId)"
+                        [style.background-color]="getProjectDeliveryStatusColor(project.projectId)">
+                    📦 {{ summary.spacesWithDeliverables }}/{{ summary.totalSpaces }}
                   </span>
+                }
+              </div>
+              
+              <!-- 时间轴区域 -->
+              <div class="timeline-track">
+                <!-- 项目条形图 -->
+                <div class="project-bar"
+                     [style.left]="getProjectPosition(project).left"
+                     [style.width]="getProjectPosition(project).width"
+                     [style.background]="getProjectPosition(project).background"
+                     [class.status-overdue]="project.status === 'overdue'"
+                     [title]="project.projectName + ' | ' + project.stageName + ' ' + project.stageProgress + '%'">
+                  <!-- 进度填充 -->
+                  <div class="progress-fill" [style.width]="project.stageProgress + '%'"></div>
                 </div>
+                
+                <!-- 🆕 使用统一的事件标记方法 -->
+                @for (event of getProjectEvents(project); track event.date) {
+                  <div class="event-marker"
+                       [class]="event.type"
+                       [class.phase-deadline]="event.type === 'phase_deadline'" 
+                       [style.left]="getEventPosition(event.date)"
+                       [style.background]="event.color"
+                       [class.blink]="project.status === 'overdue' && event.type === 'delivery'"
+                       [title]="event.label + ':' + formatTime(event.date) + (event.phase ? ' (' + getPhaseLabel(event.phase) + ')' : '')">
+                    {{ event.icon }}
+                  </div>
+                }
               </div>
             </div>
           }
         }
       </div>
-    }
+    </div>
   </div>
 </div>

+ 23 - 167
src/app/pages/team-leader/project-timeline/project-timeline.scss

@@ -79,14 +79,12 @@
   }
 }
 
-.view-controls,
 .sort-controls,
 .time-scale-controls {
   border-left: 1px solid #e5e7eb;
   padding-left: 16px;
 }
 
-.view-btn,
 .sort-btn,
 .scale-btn,
 .refresh-btn {
@@ -230,160 +228,6 @@
   padding: 16px;
 }
 
-// 列表视图
-.projects-list {
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-}
-
-.project-item {
-  position: relative;
-  display: flex;
-  padding: 16px;
-  background: #ffffff;
-  border: 1px solid #e5e7eb;
-  border-radius: 8px;
-  cursor: pointer;
-  transition: all 0.2s;
-
-  &:hover {
-    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
-    transform: translateY(-2px);
-  }
-
-  &.status-overdue {
-    border-left-width: 4px;
-    border-left-color: #dc2626;
-  }
-
-  &.status-urgent {
-    border-left-width: 4px;
-    border-left-color: #ea580c;
-  }
-
-  &.status-warning {
-    border-left-width: 4px;
-    border-left-color: #f59e0b;
-  }
-}
-
-.priority-bar {
-  position: absolute;
-  left: 0;
-  top: 0;
-  bottom: 0;
-  width: 4px;
-  border-radius: 8px 0 0 8px;
-
-  &.priority-critical {
-    background: linear-gradient(180deg, #dc2626 0%, #991b1b 100%);
-  }
-
-  &.priority-high {
-    background: linear-gradient(180deg, #ea580c 0%, #c2410c 100%);
-  }
-
-  &.priority-medium {
-    background: linear-gradient(180deg, #f59e0b 0%, #d97706 100%);
-  }
-
-  &.priority-low {
-    background: linear-gradient(180deg, #10b981 0%, #059669 100%);
-  }
-}
-
-.project-content {
-  flex: 1;
-  padding-left: 8px;
-}
-
-.project-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 12px;
-}
-
-.project-name {
-  margin: 0;
-  font-size: 16px;
-  font-weight: 600;
-  color: #111827;
-}
-
-.project-badges {
-  display: flex;
-  gap: 8px;
-}
-
-.badge {
-  padding: 4px 8px;
-  border-radius: 4px;
-  font-size: 12px;
-  font-weight: 500;
-  white-space: nowrap;
-
-  &.badge-stalled {
-    background: #f3f4f6;
-    color: #6b7280;
-  }
-
-  &.badge-urgent {
-    background: #fee2e2;
-    color: #991b1b;
-  }
-
-  &.badge-overdue {
-    background: #fecaca;
-    color: #7f1d1d;
-  }
-
-  &.badge-warning {
-    background: #fed7aa;
-    color: #9a3412;
-  }
-  
-  // 🆕 空间与交付物统计徽章(列表视图)
-  &.badge-deliverable {
-    padding: 4px 10px;
-    border-radius: 12px;
-    font-size: 11px;
-    font-weight: 600;
-    cursor: help;
-    transition: transform 0.2s, box-shadow 0.2s;
-    
-    &:hover {
-      transform: scale(1.05);
-      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
-    }
-  }
-}
-
-.project-meta {
-  display: flex;
-  gap: 16px;
-  flex-wrap: wrap;
-}
-
-.meta-item {
-  display: flex;
-  gap: 4px;
-  font-size: 13px;
-}
-
-.meta-label {
-  color: #6b7280;
-}
-
-.meta-value {
-  color: #374151;
-  font-weight: 500;
-
-  &.text-danger {
-    color: #dc2626;
-  }
-}
 
 // 空状态
 .empty-state {
@@ -449,6 +293,19 @@
     border-radius: 4px;
     transform: rotate(45deg);
   }
+
+  // 🆕 阶段文本图标样式
+  &.phase-icon {
+    font-size: 14px; // 调整字体大小使其在小圆圈中居中
+    font-weight: bold;
+    width: 28px;
+    height: 28px;
+    border-radius: 50%;
+    background: rgba(255, 255, 255, 0.2); // 半透明背景
+    border: 1px solid rgba(255, 255, 255, 0.4); // 描边
+    color: #ffffff;
+    box-shadow: none; // 移除阴影
+  }
 }
 
 .legend-bar-demo {
@@ -848,6 +705,16 @@
     animation: blink 1s infinite;
   }
 
+  &.phase-deadline {
+    font-size: 14px; // 调整字体大小使其在小圆圈中居中
+    font-weight: bold;
+    width: 28px;
+    height: 28px;
+    border-radius: 50%;
+    // background 会由 [style.background] 动态设置
+    color: #ffffff;
+  }
+
   &:hover {
     transform: translateY(-50%) scale(1.4);
     z-index: 20;
@@ -900,17 +767,6 @@
   .stats-body {
     grid-template-columns: 1fr;
   }
-
-  .project-header {
-    flex-direction: column;
-    align-items: flex-start;
-    gap: 8px;
-  }
-
-  .project-meta {
-    flex-direction: column;
-    gap: 8px;
-  }
   
   // 时间轴视图响应式
   .timeline-legend {

+ 1 - 7
src/app/pages/team-leader/project-timeline/project-timeline.ts

@@ -65,7 +65,6 @@ export class ProjectTimelineComponent implements OnInit, OnDestroy {
   selectedDesigner: string = 'all';
   selectedStatus: 'all' | 'normal' | 'warning' | 'urgent' | 'overdue' | 'stalled' = 'all';
   selectedPriority: 'all' | 'low' | 'medium' | 'high' | 'critical' = 'all';
-  viewMode: 'timeline' | 'list' = 'list';
   sortBy: 'priority' | 'time' = 'priority';
   
   // 设计师统计
@@ -389,7 +388,7 @@ export class ProjectTimelineComponent implements OnInit, OnDestroy {
               phase: phaseName as PhaseName,
               projectId: project.projectId,
               color: isDelayed ? '#dc2626' : phaseConfig.color,
-              icon: phaseConfig.icon
+              icon: phaseConfig.label.charAt(0)
             });
           }
         }
@@ -515,11 +514,6 @@ export class ProjectTimelineComponent implements OnInit, OnDestroy {
     this.cdr.markForCheck(); // 触发变更检测
   }
 
-  toggleViewMode(mode: 'timeline' | 'list'): void {
-    this.viewMode = mode;
-    this.calculateTimeRange(); // 重新计算时间范围
-  }
-
   toggleSortBy(sortBy: 'priority' | 'time'): void {
     this.sortBy = sortBy;
     this.applyFilters();