Sfoglia il codice sorgente

feat(project-detail): 添加项目进度下拉菜单功能

实现进度展示区域的下拉菜单功能,包含以下改进:
- 添加可点击的进度显示区域,展示详细板块进度
- 实现下拉菜单的显示/隐藏逻辑和动画效果
- 添加板块状态、进度百分比计算和跳转功能
- 优化样式和交互体验
0235711 21 ore fa
parent
commit
13f4480588

+ 28 - 1
src/app/pages/designer/project-detail/project-detail.html

@@ -25,10 +25,37 @@
 
     <!-- 进度展示区域 - 移动到右侧 -->
     <div class="header-center">
-      <div class="progress-display">
+      <div class="progress-display" 
+           [class.dropdown-open]="showProgressDropdown"
+           (click)="toggleProgressDropdown($event)">
         <span class="progress-status">{{ getProjectStatusText() }}</span>
         <span class="progress-stage">{{ getCurrentStageText() }}</span>
         <span class="progress-percentage">({{ getOverallProgress() }}%)</span>
+        <span class="dropdown-arrow" [class.rotated]="showProgressDropdown">▼</span>
+        
+        <!-- 下拉菜单 -->
+        <div class="progress-dropdown" *ngIf="showProgressDropdown" (click)="$event.stopPropagation()">
+          <div class="dropdown-header">四大板块进度</div>
+          <div class="dropdown-item" 
+               *ngFor="let section of sections" 
+               [class.active]="getActiveSectionKey() === section.key"
+               (click)="jumpToSection(section.key)">
+            <div class="section-info">
+              <span class="section-label">{{ section.label }}</span>
+              <span class="section-status" [class]="getSectionStatus(section.key)">
+                {{ getSectionStatusText(section.key) }}
+              </span>
+            </div>
+            <div class="section-progress">
+              <div class="progress-bar">
+                <div class="progress-fill" 
+                     [style.width.%]="getSectionProgress(section.key)">
+                </div>
+              </div>
+              <span class="progress-text">{{ getSectionProgress(section.key) }}%</span>
+            </div>
+          </div>
+        </div>
       </div>
     </div>
     

+ 156 - 3
src/app/pages/designer/project-detail/project-detail.scss

@@ -1131,7 +1131,8 @@
       transform: scale(1.05); // 适度放大
       transition: all 0.3s ease-in-out; // 保持平滑过渡
       position: relative;
-      overflow: hidden;
+      overflow: visible; // 改为visible以显示下拉菜单
+      cursor: pointer; // 添加指针样式
 
       // 更柔和的闪烁效果
       &::before {
@@ -1151,6 +1152,23 @@
         box-shadow: 0 4px 12px rgba(148, 163, 184, 0.2), 0 0 0 2px rgba(148, 163, 184, 0.1);
       }
 
+      // 下拉菜单打开状态
+      &.dropdown-open {
+        background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
+        border-color: #0056b3;
+        color: white;
+        
+        .progress-status,
+        .progress-stage,
+        .progress-percentage {
+          color: white;
+        }
+        
+        .dropdown-arrow {
+          color: white;
+        }
+      }
+
       .progress-status {
         font-weight: 600; // 减轻字体粗细
         color: #1e40af; // 使用更协调的蓝色
@@ -1167,6 +1185,129 @@
         color: #059669; // 使用更协调的绿色
         text-shadow: none; // 移除文字阴影
       }
+
+      // 下拉箭头样式
+      .dropdown-arrow {
+        margin-left: 4px;
+        font-size: 12px;
+        color: #64748b;
+        transition: transform 0.3s ease, color 0.3s ease;
+        
+        &.rotated {
+          transform: rotate(180deg);
+        }
+      }
+
+      // 下拉菜单样式
+      .progress-dropdown {
+        position: absolute;
+        top: calc(100% + 8px);
+        left: 0;
+        right: 0;
+        background: white;
+        border: 1px solid #e9ecef;
+        border-radius: 8px;
+        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+        z-index: 99999 !important;
+        margin-top: 4px;
+        overflow: hidden;
+        animation: dropdownSlideIn 0.3s ease-out;
+        min-width: 280px;
+
+        .dropdown-header {
+          padding: 12px 16px;
+          background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
+          border-bottom: 1px solid #cbd5e1;
+          font-weight: 600;
+          font-size: 14px;
+          color: #475569;
+          text-align: center;
+        }
+
+        .dropdown-item {
+          padding: 12px 16px;
+          border-bottom: 1px solid #f1f5f9;
+          cursor: pointer;
+          transition: background-color 0.2s ease;
+
+          &:last-child {
+            border-bottom: none;
+          }
+
+          &:hover {
+            background-color: #f8fafc;
+          }
+
+          &.active {
+            background: linear-gradient(135deg, #e0f2fe 0%, #b3e5fc 100%);
+            border-left: 4px solid #007bff;
+          }
+
+          .section-info {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: 8px;
+
+            .section-label {
+              font-weight: 600;
+              font-size: 14px;
+              color: #1e293b;
+            }
+
+            .section-status {
+              padding: 2px 8px;
+              border-radius: 12px;
+              font-size: 12px;
+              font-weight: 500;
+
+              &.completed {
+                background-color: #dcfce7;
+                color: #166534;
+              }
+
+              &.active {
+                background-color: #fef3c7;
+                color: #92400e;
+              }
+
+              &.pending {
+                background-color: #fee2e2;
+                color: #991b1b;
+              }
+            }
+          }
+
+          .section-progress {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+
+            .progress-bar {
+              flex: 1;
+              height: 6px;
+              background-color: #e2e8f0;
+              border-radius: 3px;
+              overflow: hidden;
+
+              .progress-fill {
+                height: 100%;
+                background: linear-gradient(90deg, #10b981 0%, #059669 100%);
+                border-radius: 3px;
+                transition: width 0.3s ease;
+              }
+            }
+
+            .progress-text {
+              font-size: 12px;
+              font-weight: 600;
+              color: #64748b;
+              min-width: 35px;
+              text-align: right;
+            }
+          }
+        }
+      }
     }
   }
 
@@ -1180,6 +1321,18 @@
     }
   }
 
+  // 下拉菜单滑入动画
+  @keyframes dropdownSlideIn {
+    0% {
+      opacity: 0;
+      transform: translateY(-10px);
+    }
+    100% {
+      opacity: 1;
+      transform: translateY(0);
+    }
+  }
+
   // 导航按钮区域样式
   .header-nav {
     flex: 0 0 auto; // 改为固定宽度,不拉伸
@@ -1344,7 +1497,7 @@
           border: 1px solid #e9ecef;
           border-radius: 8px;
           box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
-          z-index: 1000;
+          z-index: 99999 !important;
           min-width: 280px;
           max-height: 300px;
           overflow-y: auto;
@@ -1588,7 +1741,7 @@
         min-width: 280px;
         max-height: 300px;
         overflow-y: auto;
-        z-index: 1000;
+        z-index: 99999 !important;
         
         // 添加动画效果
         animation: dropdown-fade-in 0.2s ease-out;

+ 80 - 1
src/app/pages/designer/project-detail/project-detail.ts

@@ -113,6 +113,9 @@ export class ProjectDetail implements OnInit, OnDestroy {
     { key: 'aftercare', label: '售后', stages: ['尾款结算', '客户评价', '投诉处理'] }
   ];
   expandedSection: SectionKey | null = null;
+  
+  // 新增:进度下拉菜单状态
+  showProgressDropdown: boolean = false;
   // 渲染异常反馈相关属性
   exceptionType: 'failed' | 'stuck' | 'quality' | 'other' = 'failed';
   exceptionDescription: string = '';
@@ -522,7 +525,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
   }
 
   // 计算当前激活板块:优先用户点击的 expandedSection;否则取当前阶段所属板块;再否则回退首个板块
-  private getActiveSectionKey(): SectionKey {
+  getActiveSectionKey(): SectionKey {
     if (this.expandedSection) return this.expandedSection;
     const current = this.project?.currentStage as ProjectStage | undefined;
     return current ? this.getSectionKeyForStage(current) : this.sections[0].key;
@@ -1654,6 +1657,82 @@ export class ProjectDetail implements OnInit, OnDestroy {
     }
   }
 
+  // 新增:切换进度下拉菜单显示状态
+  toggleProgressDropdown(event: Event): void {
+    event.stopPropagation();
+    this.showProgressDropdown = !this.showProgressDropdown;
+    
+    // 点击其他地方时关闭下拉菜单
+    if (this.showProgressDropdown) {
+      const closeDropdown = (e: Event) => {
+        if (!((e.target as Element)?.closest('.progress-display'))) {
+          this.showProgressDropdown = false;
+          document.removeEventListener('click', closeDropdown);
+        }
+      };
+      setTimeout(() => document.addEventListener('click', closeDropdown), 0);
+    }
+  }
+
+  // 新增:跳转到指定板块
+  jumpToSection(key: SectionKey): void {
+    this.showProgressDropdown = false; // 关闭下拉菜单
+    this.toggleSection(key); // 使用现有的toggleSection方法
+  }
+
+  // 新增:获取板块状态文本
+  getSectionStatusText(key: SectionKey): string {
+    const status = this.getSectionStatus(key);
+    switch (status) {
+      case 'completed': return '已完成';
+      case 'active': return '进行中';
+      case 'pending': return '待开始';
+      default: return '未知';
+    }
+  }
+
+  // 新增:获取板块进度百分比
+  getSectionProgress(key: SectionKey): number {
+    const section = this.sections.find(s => s.key === key);
+    if (!section) return 0;
+
+    const current = (this.currentStage || this.project?.currentStage) as ProjectStage | undefined;
+    if (!current) return 0;
+
+    // 获取当前阶段在整体流程中的权重
+    const stageWeights: Record<ProjectStage, number> = {
+      '订单创建': 10,
+      '需求沟通': 25,
+      '方案确认': 35,
+      '建模': 50,
+      '软装': 65,
+      '渲染': 75,
+      '后期': 85,
+      '尾款结算': 90,
+      '客户评价': 95,
+      '投诉处理': 100
+    };
+
+    const currentWeight = stageWeights[current] || 0;
+    const status = this.getSectionStatus(key);
+
+    if (status === 'completed') {
+      return 100;
+    } else if (status === 'active') {
+      // 计算当前板块内的进度
+      const sectionStages = section.stages;
+      const currentStageIndex = sectionStages.indexOf(current);
+      if (currentStageIndex === -1) return 0;
+      
+      // 基于阶段在板块内的位置计算进度
+      const baseProgress = Math.floor((currentStageIndex / sectionStages.length) * 100);
+      const stageProgress = Math.min(100, baseProgress + 25); // 每个阶段至少25%进度
+      return stageProgress;
+    } else {
+      return 0;
+    }
+  }
+
   // 阶段到锚点的映射
   stageToAnchor(stage: ProjectStage): string {
     const map: Record<ProjectStage, string> = {