0235711 3 дней назад
Родитель
Сommit
6e6ee0e3a5

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

@@ -74,7 +74,8 @@
       <app-project-timeline 
         [projects]="projectTimelineData"
         [companyId]="currentUser.name"
-        (projectClick)="onProjectTimelineClick($event)">
+        (projectClick)="onProjectTimelineClick($event)"
+        (refreshData)="loadDashboardData()">
       </app-project-timeline>
     }
 

+ 16 - 2
src/app/pages/team-leader/dashboard/dashboard.ts

@@ -181,6 +181,19 @@ export class Dashboard implements OnInit, OnDestroy {
     // 新增:加载用户Profile信息
     await this.loadUserProfile();
     
+    // 加载看板数据
+    await this.loadDashboardData();
+    
+    // 启动自动刷新
+    this.startAutoRefresh();
+  }
+
+  /**
+   * 🆕 加载看板所有数据
+   */
+  async loadDashboardData(): Promise<void> {
+    console.log('🔄 开始加载看板数据...');
+    
     // 🔥 优先尝试从云函数加载数据
     const cloudData = await this.dashboardDataService.getTeamLeaderDataFromCloud();
     
@@ -198,8 +211,9 @@ export class Dashboard implements OnInit, OnDestroy {
     
     // 🆕 计算紧急事件
     this.calculateUrgentEvents();
-    // 启动自动刷新
-    this.startAutoRefresh();
+    
+    // 触发变更检测
+    this.cdr.markForCheck();
   }
 
   /**

+ 109 - 2
src/app/pages/team-leader/employee-detail-panel/employee-detail-panel.ts

@@ -78,6 +78,7 @@ export class EmployeeDetailPanelComponent implements OnInit, OnChanges {
   
   // 组件内部状态
   internalEmployeeDetail: EmployeeDetail | null = null; // 内部生成的详情
+  private cachedAllProjects: any[] = []; // 🔥 缓存加载的所有项目(含历史)
   showFullSurvey: boolean = false;
   refreshingSurvey: boolean = false;
 
@@ -199,8 +200,12 @@ export class EmployeeDetailPanelComponent implements OnInit, OnChanges {
     // 生成红色标记说明
     const redMarkExplanation = this.generateRedMarkExplanation(employeeName, employeeLeaveRecords, currentProjects);
     
+    // 🔥 加载该设计师的所有项目(包括历史项目)用于日历显示
+    const allDesignerProjects = await this.loadAllDesignerProjects(employeeName);
+    this.cachedAllProjects = allDesignerProjects; // 缓存用于月份切换
+    
     // 生成日历数据 (传入所有项目以显示历史负载)
-    const calendarData = this.generateEmployeeCalendar(employeeName, this.projects);
+    const calendarData = this.generateEmployeeCalendar(employeeName, allDesignerProjects);
     
     // 构建基础对象
     this.internalEmployeeDetail = {
@@ -220,6 +225,104 @@ export class EmployeeDetailPanelComponent implements OnInit, OnChanges {
     this.cdr.markForCheck();
   }
 
+  /**
+   * 加载设计师的所有项目(包括历史已完成项目)
+   * 用于日历显示历史负载
+   */
+  private async loadAllDesignerProjects(employeeName: string): Promise<any[]> {
+    try {
+      const Parse = await import('fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
+      const cid = localStorage.getItem('company');
+      
+      if (!cid) {
+        console.warn('⚠️ 未找到公司ID,使用传入的项目数据');
+        return this.projects;
+      }
+
+      // 1. 先通过员工名字查找 Profile
+      const realnameQuery = new Parse.Query('Profile');
+      realnameQuery.equalTo('realname', employeeName);
+      
+      const nameQuery = new Parse.Query('Profile');
+      nameQuery.equalTo('name', employeeName);
+      
+      const profileQuery = Parse.Query.or(realnameQuery, nameQuery);
+      profileQuery.equalTo('company', cid);
+      profileQuery.limit(1);
+      
+      const profileResults = await profileQuery.find();
+      
+      if (profileResults.length === 0) {
+        console.warn(`⚠️ 未找到设计师 ${employeeName} 的 Profile,使用传入的项目数据`);
+        return this.projects;
+      }
+      
+      const profile = profileResults[0];
+      
+      // 2. 查询该设计师参与的所有项目(通过 ProjectTeam 表)
+      // 不过滤已完成状态,以便显示历史项目
+      const projectQuery = new Parse.Query('Project');
+      projectQuery.equalTo('company', cid);
+      projectQuery.notEqualTo('isDeleted', true);
+      
+      const teamQuery = new Parse.Query('ProjectTeam');
+      teamQuery.equalTo('profile', profile.toPointer());
+      teamQuery.notEqualTo('isDeleted', true);
+      teamQuery.matchesQuery('project', projectQuery);
+      teamQuery.include('project');
+      // 🔥 关键:限制查询范围为最近6个月的项目,避免数据量过大
+      const sixMonthsAgo = new Date();
+      sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
+      teamQuery.greaterThan('createdAt', sixMonthsAgo);
+      teamQuery.limit(200);
+      
+      const teamRecords = await teamQuery.find();
+      
+      console.log(`📊 加载设计师 ${employeeName} 的历史项目: ${teamRecords.length} 条记录`);
+      
+      // 3. 提取项目数据
+      const allProjects: any[] = [];
+      const projectIds = new Set<string>(); // 用于去重
+      
+      teamRecords.forEach((record: any) => {
+        const project = record.get('project');
+        if (!project || projectIds.has(project.id)) return;
+        
+        projectIds.add(project.id);
+        
+        const projectData = project.get('data') || {};
+        
+        allProjects.push({
+          id: project.id,
+          name: project.get('title') || '未命名项目',
+          status: project.get('status') || '进行中',
+          currentStage: project.get('currentStage') || '未知阶段',
+          deadline: project.get('deadline') || project.get('deliveryDate') || project.get('expectedDeliveryDate'),
+          createdAt: project.get('createdAt') || project.createdAt,
+          updatedAt: project.get('updatedAt') || project.updatedAt,
+          data: projectData,
+          designerName: employeeName
+        });
+      });
+      
+      // 4. 合并传入的项目数据(可能包含一些未通过 ProjectTeam 关联的项目)
+      this.projects.forEach(p => {
+        if (!projectIds.has(p.id)) {
+          allProjects.push(p);
+        }
+      });
+      
+      console.log(`✅ 设计师 ${employeeName} 共有 ${allProjects.length} 个项目(含历史)`);
+      
+      return allProjects;
+      
+    } catch (error) {
+      console.error(`❌ 加载设计师 ${employeeName} 历史项目失败:`, error);
+      // 降级:使用传入的项目数据
+      return this.projects;
+    }
+  }
+
   /**
    * 加载问卷数据
    */
@@ -432,6 +535,7 @@ export class EmployeeDetailPanelComponent implements OnInit, OnChanges {
     this.close.emit();
     this.showFullSurvey = false;
     this.closeCalendarProjectList();
+    this.cachedAllProjects = []; // 清理缓存
   }
   
   /**
@@ -453,10 +557,13 @@ export class EmployeeDetailPanelComponent implements OnInit, OnChanges {
     const newMonth = new Date(currentMonth);
     newMonth.setMonth(newMonth.getMonth() + direction);
     
+    // 🔥 使用缓存的完整项目数据(含历史项目)重新生成日历
+    const projectsForCalendar = this.cachedAllProjects.length > 0 ? this.cachedAllProjects : this.projects;
+    
     // 重新生成日历数据
     const newCalendarData = this.generateEmployeeCalendar(
       this.employeeName, 
-      this.projects, 
+      projectsForCalendar, 
       newMonth
     );
     

+ 2 - 0
src/app/pages/team-leader/project-timeline/project-timeline.ts

@@ -63,6 +63,7 @@ export class ProjectTimelineComponent implements OnInit, OnDestroy {
   @Input() companyId: string = '';
   @Input() defaultDesigner: string = 'all'; // 🆕 默认选择的设计师
   @Output() projectClick = new EventEmitter<string>();
+  @Output() refreshData = new EventEmitter<void>(); // 🆕 请求父组件刷新数据
   
   // 筛选状态
   selectedDesigner: string = 'all';
@@ -900,6 +901,7 @@ export class ProjectTimelineComponent implements OnInit, OnDestroy {
    */
   refresh(): void {
     console.log('🔄 手动刷新项目时间轴');
+    this.refreshData.emit(); // 🆕 通知父组件刷新数据
     this.updateCurrentTime();
     this.initializeData();
     this.cdr.markForCheck();

+ 38 - 18
src/modules/project/components/project-progress-modal/project-progress-modal.component.html

@@ -111,7 +111,7 @@
           <div class="phase-overview" (click)="togglePhase(phase.name)">
             <div class="phase-header">
               <span class="phase-icon">{{ PHASE_INFO[phase.name].icon }}</span>
-              <span class="phase-label">{{ phase.info.phaseLabel }}</span>
+              <span class="phase-label">{{ getPhaseLabel(phase.name) }}</span>
               <span class="phase-progress-badge" [style.background-color]="getPhaseProgressColor(phase.info.completionRate)">
                 {{ phase.info.completionRate }}%
               </span>
@@ -137,28 +137,48 @@
             </div>
           </div>
 
-          <!-- 未完成空间详情(展开时显示) -->
+          <!-- 详情(展开时显示) -->
           <div class="phase-details" *ngIf="isPhaseExpanded(phase.name)">
-            <div class="details-header">
-              <span class="details-title">未完成空间列表</span>
-              <span class="details-count">({{ phase.info.incompleteSpaces.length }} 个)</span>
+            <!-- 未完成列表 -->
+            <div class="details-section" *ngIf="phase.info.incompleteSpaces.length > 0">
+              <div class="details-header">
+                <span class="details-title">未完成空间列表</span>
+                <span class="details-count">({{ phase.info.incompleteSpaces.length }} 个)</span>
+              </div>
+              
+              <div class="incomplete-spaces">
+                <div 
+                  *ngFor="let space of phase.info.incompleteSpaces"
+                  class="space-item">
+                  <span class="space-icon">📦</span>
+                  <span class="space-name">{{ space.spaceName }}</span>
+                  <span class="assignee" *ngIf="space.assignee">
+                    负责人:{{ space.assignee }}
+                  </span>
+                </div>
+              </div>
             </div>
-            
-            <div class="incomplete-spaces" *ngIf="phase.info.incompleteSpaces.length > 0">
-              <div 
-                *ngFor="let space of phase.info.incompleteSpaces"
-                class="space-item">
-                <span class="space-icon">📦</span>
-                <span class="space-name">{{ space.spaceName }}</span>
-                <span class="assignee" *ngIf="space.assignee">
-                  负责人:{{ space.assignee }}
-                </span>
+
+            <!-- 已完成列表 (新增) -->
+            <div class="details-section" *ngIf="getCompletedSpaces(phase.name).length > 0">
+              <div class="details-header">
+                <span class="details-title">已完成空间列表</span>
+                <span class="details-count">({{ getCompletedSpaces(phase.name).length }} 个)</span>
+              </div>
+              
+              <div class="completed-spaces">
+                <div 
+                  *ngFor="let space of getCompletedSpaces(phase.name)"
+                  class="space-item completed">
+                  <span class="space-icon">✅</span>
+                  <span class="space-name">{{ space.spaceName }}</span>
+                </div>
               </div>
             </div>
             
-            <div class="no-incomplete" *ngIf="phase.info.incompleteSpaces.length === 0">
-              <span class="success-icon">✅</span>
-              <span>所有空间已完成此阶段交付</span>
+            <div class="no-incomplete" *ngIf="phase.info.incompleteSpaces.length === 0 && getCompletedSpaces(phase.name).length === 0">
+              <span class="info-icon">ℹ️</span>
+              <span>暂无空间数据</span>
             </div>
           </div>
         </div>

+ 26 - 0
src/modules/project/components/project-progress-modal/project-progress-modal.component.scss

@@ -446,12 +446,27 @@
   }
 }
 
+// 🆕 详情区块
+.details-section {
+  margin-bottom: 16px;
+  
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
 .incomplete-spaces {
   display: flex;
   flex-direction: column;
   gap: 8px;
 }
 
+.completed-spaces {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
 .space-item {
   display: flex;
   align-items: center;
@@ -478,6 +493,17 @@
     padding: 4px 8px;
     border-radius: 4px;
   }
+
+  // 🆕 已完成状态样式
+  &.completed {
+    background: #f1f8e9;
+    border-color: #c8e6c9;
+    
+    .space-name {
+      color: #2e7d32;
+      font-weight: 500;
+    }
+  }
 }
 
 .no-incomplete {

+ 22 - 0
src/modules/project/components/project-progress-modal/project-progress-modal.component.ts

@@ -164,5 +164,27 @@ export class ProjectProgressModalComponent implements OnInit {
     };
     return map[phaseKey] || phaseKey;
   }
+
+  /**
+   * 获取指定阶段的已完成空间列表
+   */
+  getCompletedSpaces(phaseName: string): any[] {
+    if (!this.summary || !this.summary.spaces) return [];
+
+    const phaseKeyMap: Record<string, string> = {
+      'modeling': 'whiteModel',
+      'softDecor': 'softDecor',
+      'rendering': 'rendering',
+      'postProcessing': 'postProcess'
+    };
+
+    const key = phaseKeyMap[phaseName];
+    if (!key) return [];
+
+    return this.summary.spaces.filter(space => {
+      // @ts-ignore - 动态访问属性
+      return space.deliverableTypes[key] > 0;
+    });
+  }
 }