Procházet zdrojové kódy

```
refactor: extract dashboard navbar into standalone component

- Created DashboardNavbarComponent with user profile, date display, and title
- Moved navbar template and styles from dashboard.html/scss to new component
- Added currentUser and currentDate as @Input properties
- Created dashboard.constants.ts with PROJECT_STAGES and CORE_PHASES definitions
- Removed navbar-related styles from dashboard.scss (120+ lines)
- Updated dashboard.html to use new app-dashboard-navbar component
```

0235711 před 3 dny
rodič
revize
5927b0e4d6

+ 103 - 0
src/app/pages/team-leader/dashboard/components/dashboard-navbar/dashboard-navbar.component.ts

@@ -0,0 +1,103 @@
+import { Component, Input } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+@Component({
+  selector: 'app-dashboard-navbar',
+  standalone: true,
+  imports: [CommonModule],
+  template: `
+    <nav class="top-navbar">
+      <div class="navbar-left">
+        <h2 class="navbar-title">设计组长工作台</h2>
+      </div>
+      <div class="navbar-right">
+        <div class="date-display">
+          {{ currentDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }) }}
+        </div>
+        <div class="user-profile">
+          <img [src]="currentUser.avatar" [alt]="currentUser.name + '头像'" class="user-avatar">
+          <span class="user-name">{{ currentUser.name }}</span>
+          <span class="user-role">{{ currentUser.roleName }}</span>
+        </div>
+      </div>
+    </nav>
+  `,
+  styles: [`
+    .top-navbar {
+      position: fixed;
+      top: 0;
+      left: 0;
+      right: 0;
+      z-index: 1000;
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      padding: 0.625rem 2rem;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+
+      .navbar-left {
+        .navbar-title {
+          font-size: 1.25rem;
+          font-weight: 600;
+          color: white;
+          margin: 0;
+        }
+      }
+
+      .navbar-right {
+        display: flex;
+        align-items: center;
+        gap: 1.5rem;
+
+        .date-display {
+          color: rgba(255, 255, 255, 0.9);
+          font-size: 0.875rem;
+          font-weight: 500;
+        }
+
+        .user-profile {
+          display: flex;
+          align-items: center;
+          gap: 0.625rem;
+          background: rgba(255, 255, 255, 0.15);
+          padding: 0.375rem 0.875rem;
+          border-radius: 50px;
+          backdrop-filter: blur(10px);
+          transition: background 0.3s ease;
+
+          &:hover {
+            background: rgba(255, 255, 255, 0.25);
+          }
+
+          .user-avatar {
+            width: 36px;
+            height: 36px;
+            border-radius: 50%;
+            object-fit: cover;
+            border: 2px solid white;
+            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+          }
+
+          .user-name {
+            color: white;
+            font-weight: 600;
+            font-size: 0.9375rem;
+          }
+
+          .user-role {
+            color: rgba(255, 255, 255, 0.8);
+            font-size: 0.8125rem;
+            padding: 0.1875rem 0.625rem;
+            background: rgba(255, 255, 255, 0.2);
+            border-radius: 12px;
+          }
+        }
+      }
+    }
+  `]
+})
+export class DashboardNavbarComponent {
+  @Input() currentUser: any;
+  @Input() currentDate: Date = new Date();
+}

+ 23 - 0
src/app/pages/team-leader/dashboard/dashboard.constants.ts

@@ -0,0 +1,23 @@
+import { ProjectStage } from './dashboard.model';
+
+// 10个项目阶段
+export const PROJECT_STAGES: ProjectStage[] = [
+  { id: 'pendingApproval', name: '待确认', order: 1 },
+  { id: 'pendingAssignment', name: '待分配', order: 2 },
+  { id: 'requirement', name: '需求沟通', order: 3 },
+  { id: 'planning', name: '方案规划', order: 4 },
+  { id: 'modeling', name: '建模阶段', order: 5 },
+  { id: 'rendering', name: '渲染阶段', order: 6 },
+  { id: 'postProduction', name: '后期处理', order: 7 },
+  { id: 'review', name: '方案评审', order: 8 },
+  { id: 'revision', name: '方案修改', order: 9 },
+  { id: 'delivery', name: '交付完成', order: 10 }
+];
+
+// 5大核心阶段(聚合展示)
+export const CORE_PHASES: ProjectStage[] = [
+  { id: 'order', name: '订单分配', order: 1 },        // 待确认、待分配
+  { id: 'requirements', name: '确认需求', order: 2 },  // 需求沟通、方案规划
+  { id: 'delivery', name: '交付执行', order: 3 },      // 建模、渲染、后期/评审/修改
+  { id: 'aftercare', name: '售后', order: 4 }          // 交付完成 → 售后
+];

+ 4 - 15
src/app/pages/team-leader/dashboard/dashboard.html

@@ -1,19 +1,8 @@
 <!-- 顶部导航栏 -->
-<nav class="top-navbar">
-  <div class="navbar-left">
-    <h2 class="navbar-title">设计组长工作台</h2>
-  </div>
-  <div class="navbar-right">
-    <div class="date-display">
-      {{ currentDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }) }}
-    </div>
-    <div class="user-profile">
-      <img [src]="currentUser.avatar" [alt]="currentUser.name + '头像'" class="user-avatar">
-      <span class="user-name">{{ currentUser.name }}</span>
-      <span class="user-role">{{ currentUser.roleName }}</span>
-    </div>
-  </div>
-</nav>
+<app-dashboard-navbar
+  [currentUser]="currentUser"
+  [currentDate]="currentDate">
+</app-dashboard-navbar>
 
 <header class="dashboard-header">
   <!-- 统计指标卡片组件 -->

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 1163
src/app/pages/team-leader/dashboard/dashboard.scss


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 125 - 817
src/app/pages/team-leader/dashboard/dashboard.ts


+ 134 - 0
src/app/pages/team-leader/services/dashboard-filter.service.ts

@@ -0,0 +1,134 @@
+import { Injectable } from '@angular/core';
+import { Project } from '../dashboard/dashboard.model';
+
+export interface DashboardFilterCriteria {
+  searchTerm: string;
+  type: 'all' | 'soft' | 'hard';
+  urgency: 'all' | 'high' | 'medium' | 'low';
+  status: 'all' | 'progress' | 'completed' | 'overdue' | 'pendingApproval' | 'pendingAssignment' | 'dueSoon';
+  designer: string;
+  memberType: 'all' | 'vip' | 'normal';
+  corePhase: 'all' | 'order' | 'requirements' | 'delivery' | 'aftercare';
+  timeWindow: 'all' | 'today' | 'threeDays' | 'sevenDays';
+}
+
+export interface FilterResult {
+  filteredProjects: Project[];
+  urgentPinnedProjects: Project[];
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class DashboardFilterService {
+
+  constructor() { }
+
+  filterProjects(projects: Project[], criteria: DashboardFilterCriteria): FilterResult {
+    let result = [...projects];
+
+    // 关键词搜索
+    const q = (criteria.searchTerm || '').trim().toLowerCase();
+    if (q) {
+      result = result.filter(p => (p.searchIndex || `${(p.name || '')}|${(p.designerName || '')}`.toLowerCase()).includes(q));
+    }
+
+    // 类型筛选
+    if (criteria.type !== 'all') {
+      result = result.filter(p => p.type === criteria.type);
+    }
+
+    // 紧急程度筛选
+    if (criteria.urgency !== 'all') {
+      result = result.filter(p => p.urgency === criteria.urgency);
+    }
+
+    // 项目状态筛选
+    if (criteria.status !== 'all') {
+      if (criteria.status === 'overdue') {
+        result = result.filter(p => p.isOverdue);
+      } else if (criteria.status === 'dueSoon') {
+        result = result.filter(p => p.dueSoon && !p.isOverdue);
+      } else if (criteria.status === 'pendingApproval') {
+        result = result.filter(p => p.currentStage === 'pendingApproval');
+      } else if (criteria.status === 'pendingAssignment') {
+        result = result.filter(p => p.currentStage === 'pendingAssignment');
+      } else if (criteria.status === 'progress') {
+        const progressStages = ['requirement','planning','modeling','rendering','postProduction','review','revision'];
+        result = result.filter(p => progressStages.includes(p.currentStage));
+      } else if (criteria.status === 'completed') {
+        result = result.filter(p => p.currentStage === 'delivery');
+      }
+    }
+
+    // 四大板块筛选
+    if (criteria.corePhase !== 'all') {
+      result = result.filter(p => this.mapStageToCorePhase(p.currentStage) === criteria.corePhase);
+    }
+
+    // 设计师筛选
+    if (criteria.designer !== 'all') {
+      result = result.filter(p => p.designerName === criteria.designer);
+    }
+
+    // 会员类型筛选
+    if (criteria.memberType !== 'all') {
+      result = result.filter(p => p.memberType === criteria.memberType);
+    }
+
+    // 时间窗筛选
+    if (criteria.timeWindow !== 'all') {
+      const now = new Date();
+      const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+      
+      result = result.filter(p => {
+        const projectDeadline = new Date(p.deadline);
+        const timeDiff = projectDeadline.getTime() - today.getTime();
+        const daysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
+        
+        switch (criteria.timeWindow) {
+          case 'today':
+            return daysDiff <= 1 && daysDiff >= 0;
+          case 'threeDays':
+            return daysDiff <= 3 && daysDiff >= 0;
+          case 'sevenDays':
+            return daysDiff <= 7 && daysDiff >= 0;
+          default:
+            return true;
+        }
+      });
+    }
+
+    // 计算紧急任务固定区(超期 + 高紧急)
+    const urgentPinnedProjects = result
+      .filter(p => p.isOverdue && p.urgency === 'high')
+      .sort((a, b) => (b.overdueDays - a.overdueDays) || a.name.localeCompare(b.name, 'zh-Hans-CN'));
+
+    return {
+      filteredProjects: result,
+      urgentPinnedProjects
+    };
+  }
+
+  // 阶段映射(简化版,用于筛选)
+  private mapStageToCorePhase(stageId: string): string {
+    // 规范化输入
+    const stage = (stageId || '').trim();
+    
+    const orderStages = ['pendingApproval', 'pendingAssignment', '待确认', '待分配', '订单分配'];
+    const requirementStages = ['requirement', 'planning', '需求沟通', '方案规划'];
+    const deliveryStages = [
+      'modeling', 'rendering', 'postProduction', 'review', 'revision',
+      '建模', '建模阶段', '渲染', '渲染阶段', '后期', '后期处理', '方案评审', '方案修改', '进行中'
+    ];
+    const aftercareStages = ['delivery', '交付', '交付完成', '售后', '售后归档'];
+
+    if (orderStages.includes(stage)) return 'order';
+    if (requirementStages.includes(stage)) return 'requirements';
+    if (deliveryStages.includes(stage)) return 'delivery';
+    if (aftercareStages.includes(stage)) return 'aftercare';
+    
+    // 默认归类到交付执行
+    return 'delivery';
+  }
+}

+ 181 - 0
src/app/pages/team-leader/services/dashboard-navigation.helper.ts

@@ -0,0 +1,181 @@
+import { Injectable } from '@angular/core';
+import { Router } from '@angular/router';
+import { Project } from '../dashboard/dashboard.model';
+
+/**
+ * 仪表盘导航助手服务
+ * 统一管理组长端仪表盘的各种跳转逻辑
+ */
+@Injectable({
+  providedIn: 'root'
+})
+export class DashboardNavigationHelper {
+
+  constructor(private router: Router) { }
+
+  /**
+   * 标记从组长看板进入
+   * 用于跳过激活检查和显示审批按钮
+   */
+  private markAsTeamLeaderMode(): void {
+    try {
+      localStorage.setItem('enterAsTeamLeader', '1');
+      localStorage.setItem('teamLeaderMode', 'true');
+      // 🔥 关键:清除客服端标记,避免冲突
+      localStorage.removeItem('enterFromCustomerService');
+      localStorage.removeItem('customerServiceMode');
+      console.log('✅ 已标记从组长看板进入,启用组长模式');
+    } catch (e) {
+      console.warn('无法设置 localStorage 标记:', e);
+    }
+  }
+
+  /**
+   * 获取项目阶段对应的路由路径
+   */
+  getStagePath(currentStage: string): string {
+    // 阶段映射:项目阶段 → 路由路径
+    const stageRouteMap: Record<string, string> = {
+      '订单分配': 'order',
+      '待确认': 'order',
+      '待分配': 'order',
+      'pendingApproval': 'order',
+      'pendingAssignment': 'order',
+      
+      '确认需求': 'requirements',
+      '需求沟通': 'requirements',
+      '方案规划': 'requirements',
+      '方案深化': 'requirements',
+      'requirement': 'requirements',
+      'planning': 'requirements',
+      
+      '建模': 'delivery', // 旧版映射可能为 requirements,新版统一到 delivery phase
+      '建模阶段': 'delivery',
+      'modeling': 'delivery',
+      
+      '软装': 'delivery',
+      '软装设计': 'delivery',
+      'decoration': 'delivery',
+      
+      '渲染': 'delivery',
+      '渲染阶段': 'delivery',
+      'rendering': 'delivery',
+      
+      '后期': 'delivery',
+      '后期处理': 'delivery',
+      'postProduction': 'delivery',
+      
+      '方案评审': 'delivery',
+      'review': 'delivery',
+      
+      '方案修改': 'delivery',
+      'revision': 'delivery',
+      
+      '交付执行': 'delivery',
+      '交付': 'delivery',
+      '交付完成': 'delivery',
+      'delivery': 'delivery',
+      
+      '售后归档': 'aftercare',
+      '已完成': 'aftercare',
+      'aftercare': 'aftercare'
+    };
+    
+    // 默认映射规则:如果是 delivery 阶段的细分,也可以映射到 requirements
+    // 但为了简化,我们遵循 mapStageToCorePhase 的逻辑
+    // order: pendingApproval, pendingAssignment
+    // requirements: requirement, planning
+    // delivery: modeling, rendering, postProduction, review, revision, delivery
+    // aftercare: (completed)
+    
+    // 注意:上方的映射可能需要根据实际路由结构微调
+    // 目前 dashboard.ts 中的逻辑是将 modeling~postProduction 映射到 requirements 或 delivery
+    // 我们这里保留 dashboard.ts 原有的逻辑:
+    /*
+    const stageRouteMap: Record<string, string> = {
+      '订单分配': 'order',
+      '确认需求': 'requirements',
+      '方案深化': 'requirements',
+      '建模': 'requirements',
+      '软装': 'requirements',
+      '渲染': 'requirements',
+      '后期': 'requirements',
+      '交付执行': 'delivery',
+      '交付': 'delivery',
+      '售后归档': 'aftercare',
+      '已完成': 'aftercare'
+    };
+    */
+    
+    const legacyMap: Record<string, string> = {
+      '订单分配': 'order',
+      '确认需求': 'requirements',
+      '方案深化': 'requirements',
+      '建模': 'requirements',
+      '软装': 'requirements',
+      '渲染': 'requirements',
+      '后期': 'requirements',
+      '交付执行': 'delivery',
+      '交付': 'delivery',
+      '售后归档': 'aftercare',
+      '已完成': 'aftercare'
+    };
+    
+    return legacyMap[currentStage] || 'order';
+  }
+
+  /**
+   * 跳转到项目详情页
+   * @param projectId 项目ID
+   * @param currentStage 当前阶段(用于决定跳转到哪个子页面)
+   * @param queryParams 额外的查询参数
+   */
+  navigateToProject(projectId: string, currentStage: string = '订单分配', queryParams: any = {}): void {
+    if (!projectId) return;
+
+    this.markAsTeamLeaderMode();
+
+    const cid = localStorage.getItem('company') || 'cDL6R1hgSi';
+    const stagePath = this.getStagePath(currentStage);
+
+    console.log(`🎯 项目跳转: ID=${projectId}, 阶段=${currentStage} -> Path=${stagePath}`);
+
+    this.router.navigate(['/wxwork', cid, 'project', projectId, stagePath], {
+      queryParams: { 
+        roleName: 'team-leader',
+        ...queryParams
+      }
+    });
+  }
+
+  /**
+   * 根据看板列ID跳转
+   * @param projectId 项目ID
+   * @param corePhaseId 核心阶段ID (order/requirements/delivery/aftercare)
+   */
+  navigateToProjectByPhase(projectId: string, corePhaseId: string): void {
+    if (!projectId) return;
+
+    this.markAsTeamLeaderMode();
+
+    const cid = localStorage.getItem('company') || 'cDL6R1hgSi';
+    // corePhaseId 已经是路由路径格式
+    const stagePath = corePhaseId;
+
+    console.log(`🎯 看板跳转: ID=${projectId}, Phase=${corePhaseId} -> Path=${stagePath}`);
+
+    this.router.navigate(['/wxwork', cid, 'project', projectId, stagePath], {
+      queryParams: { roleName: 'team-leader' }
+    });
+  }
+  
+  /**
+   * 跳转到问题详情
+   */
+  navigateToIssue(projectId: string, issueId: string): void {
+    this.navigateToProject(projectId, '订单分配', {
+      openIssues: 'true',
+      highlightIssue: issueId
+    });
+  }
+}

+ 338 - 0
src/app/pages/team-leader/services/designer-workload.service.ts

@@ -0,0 +1,338 @@
+import { Injectable } from '@angular/core';
+import { normalizeDateInput, addDays } from '../../../utils/date-utils';
+import { generatePhaseDeadlines } from '../../../utils/phase-deadline.utils';
+import { PhaseDeadlines, PhaseName } from '../../../models/project-phase.model';
+import { ProjectTimeline } from '../project-timeline/project-timeline';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class DesignerWorkloadService {
+
+  constructor() { }
+
+  /**
+   * 加载设计师工作量数据
+   * @param cid 公司ID
+   */
+  async loadWorkload(cid: string): Promise<Map<string, any[]>> {
+    try {
+      const Parse = await import('fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
+      
+      // 先查询当前公司的所有项目
+      const projectQuery = new Parse.Query('Project');
+      projectQuery.equalTo('company', cid);
+      projectQuery.notEqualTo('isDeleted', true);
+      
+      // 查询当前公司项目的 ProjectTeam
+      const teamQuery = new Parse.Query('ProjectTeam');
+      teamQuery.matchesQuery('project', projectQuery);
+      teamQuery.notEqualTo('isDeleted', true);
+      teamQuery.include('project');
+      teamQuery.include('profile');
+      teamQuery.limit(1000);
+      
+      const teamRecords = await teamQuery.find();
+      
+      // 如果 ProjectTeam 表为空,使用降级方案
+      if (teamRecords.length === 0) {
+        return this.loadWorkloadFromProjects(cid);
+      }
+      
+      const workloadMap = new Map<string, any[]>();
+      
+      teamRecords.forEach((record: any) => {
+        const profile = record.get('profile');
+        const project = record.get('project');
+        
+        if (!profile || !project) {
+          return;
+        }
+        
+        const profileId = profile.id;
+        const profileName = profile.get('name') || profile.get('user')?.get?.('name') || `设计师-${profileId.slice(-4)}`;
+        
+        const projectData = this.extractProjectData(project, profileId, profileName);
+        
+        // 添加到映射 (by ID)
+        if (!workloadMap.has(profileId)) {
+          workloadMap.set(profileId, []);
+        }
+        workloadMap.get(profileId)!.push(projectData);
+        
+        // 同时建立 name -> projects 的映射(用于甘特图)
+        if (!workloadMap.has(profileName)) {
+          workloadMap.set(profileName, []);
+        }
+        workloadMap.get(profileName)!.push(projectData);
+      });
+      
+      return workloadMap;
+      
+    } catch (error) {
+      console.error('加载设计师工作量失败:', error);
+      return new Map();
+    }
+  }
+  
+  /**
+   * 降级方案:从 Project.assignee 统计工作量
+   */
+  private async loadWorkloadFromProjects(cid: string): Promise<Map<string, any[]>> {
+    try {
+      const Parse = await import('fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
+      
+      // 查询所有项目
+      const projectQuery = new Parse.Query('Project');
+      projectQuery.equalTo('company', cid);
+      projectQuery.equalTo('isDeleted', false);
+      projectQuery.include('assignee');
+      projectQuery.include('department');
+      projectQuery.limit(1000);
+      
+      const projects = await projectQuery.find();
+      const workloadMap = new Map<string, any[]>();
+      
+      projects.forEach((project: any) => {
+        const assignee = project.get('assignee');
+        if (!assignee) return;
+        
+        // 只统计组员角色的项目
+        const assigneeRole = assignee.get('roleName');
+        if (assigneeRole !== '组员') {
+          return;
+        }
+        
+        const assigneeName = assignee.get('name') || assignee.get('user')?.get?.('name') || `设计师-${assignee.id.slice(-4)}`;
+        const projectData = this.extractProjectData(project, assignee.id, assigneeName);
+        
+        // 添加到映射
+        if (!workloadMap.has(assigneeName)) {
+          workloadMap.set(assigneeName, []);
+        }
+        workloadMap.get(assigneeName)!.push(projectData);
+      });
+      
+      console.log(`📊 [降级方案] 加载了 ${projects.length} 个项目`);
+      return workloadMap;
+      
+    } catch (error) {
+      console.error('[降级方案] 加载工作量失败:', error);
+      return new Map();
+    }
+  }
+  
+  /**
+   * 提取项目通用数据结构
+   */
+  private extractProjectData(project: any, designerId: string, designerName: string): any {
+    const createdAtValue = project.get('createdAt');
+    const updatedAtValue = project.get('updatedAt');
+    const deadlineValue = project.get('deadline');
+    const deliveryDateValue = project.get('deliveryDate');
+    const expectedDeliveryDateValue = project.get('expectedDeliveryDate');
+    const demodayValue = project.get('demoday');
+    
+    let finalCreatedAt = createdAtValue || updatedAtValue;
+    if (!finalCreatedAt && project.createdAt) {
+      finalCreatedAt = project.createdAt;
+    }
+    if (!finalCreatedAt && project.updatedAt) {
+      finalCreatedAt = project.updatedAt;
+    }
+    
+    const projectDataField = project.get('data') || {};
+    
+    return {
+      id: project.id,
+      name: project.get('title') || '未命名项目',
+      status: project.get('status') || '进行中',
+      currentStage: project.get('currentStage') || '未知阶段',
+      deadline: deadlineValue || deliveryDateValue || expectedDeliveryDateValue,
+      demoday: demodayValue,
+      createdAt: finalCreatedAt,
+      updatedAt: updatedAtValue || project.updatedAt,
+      designerName: designerName,
+      designerId: designerId,
+      data: projectDataField,
+      contact: project.get('contact'),
+      space: projectDataField.quotation?.spaces?.[0]?.name || ''
+    };
+  }
+  
+  /**
+   * 将工作量映射转换为时间轴数据
+   */
+  transformToTimeline(workloadMap: Map<string, any[]>): ProjectTimeline[] {
+    const allDesignerProjects: any[] = [];
+    
+    workloadMap.forEach((projects, designerKey) => {
+      // 只要不是Parse ID,就认为是设计师名称
+      // Parse ID通常是10位字母数字组合
+      const isParseId = typeof designerKey === 'string' 
+        && designerKey.length === 10 
+        && /^[a-zA-Z0-9]{10}$/.test(designerKey);
+      
+      // 修改逻辑:只要不是ID且非空,就作为设计师名称(兼容英文名)
+      const isDesignerName = !isParseId && typeof designerKey === 'string' && designerKey.length > 0;
+      
+      if (isDesignerName) {
+        projects.forEach(proj => {
+          const projectWithDesigner = {
+            ...proj,
+            designerName: designerKey
+          };
+          allDesignerProjects.push(projectWithDesigner);
+        });
+      }
+    });
+    
+    return allDesignerProjects.map((project) => {
+      const now = new Date();
+      const projectData = project.data || {};
+      
+      // 1. 获取真实的项目开始时间
+      const realStartDate = normalizeDateInput(
+        projectData.phaseDeadlines?.modeling?.startDate ||
+          projectData.requirementsConfirmedAt ||
+          project.createdAt,
+        new Date()
+      );
+      
+      // 2. 获取真实的交付日期
+      let proposedEndDate = project.deadline || projectData.phaseDeadlines?.postProcessing?.deadline;
+      let realEndDate: Date;
+      
+      if (proposedEndDate) {
+        const proposed = normalizeDateInput(proposedEndDate, realStartDate);
+        if (proposed.getTime() > now.getTime()) {
+          realEndDate = proposed;
+        } else {
+          realEndDate = addDays(realStartDate, 30);
+        }
+      } else {
+        realEndDate = addDays(realStartDate, 30);
+      }
+      
+      // 3. 获取真实的对图时间
+      let realReviewDate: Date;
+      if (project.demoday) {
+        realReviewDate = normalizeDateInput(project.demoday, new Date());
+      } else if (projectData.phaseDeadlines?.softDecor?.deadline) {
+        const softDecorDeadline = normalizeDateInput(projectData.phaseDeadlines.softDecor.deadline, new Date());
+        realReviewDate = new Date(softDecorDeadline.getTime() + 12 * 60 * 60 * 1000);
+      } else {
+        const defaultReviewPoint = new Date(
+          realStartDate.getTime() + (realEndDate.getTime() - realStartDate.getTime()) * 0.6
+        );
+        defaultReviewPoint.setHours(14, 0, 0, 0);
+        realReviewDate = defaultReviewPoint;
+      }
+      
+      // 4. 计算距离交付还有几天
+      const daysUntilDeadline = Math.ceil((realEndDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
+      
+      // 5. 计算项目状态
+      let status: 'normal' | 'warning' | 'urgent' | 'overdue' = 'normal';
+      if (daysUntilDeadline < 0) {
+        status = 'overdue';
+      } else if (daysUntilDeadline <= 1) {
+        status = 'urgent';
+      } else if (daysUntilDeadline <= 3) {
+        status = 'warning';
+      }
+      
+      // 6. 映射阶段
+      const stageMap: Record<string, 'plan' | 'model' | 'decoration' | 'render' | 'delivery'> = {
+        '方案设计': 'plan',
+        '方案规划': 'plan',
+        '建模': 'model',
+        '建模阶段': 'model',
+        '软装': 'decoration',
+        '软装设计': 'decoration',
+        '渲染': 'render',
+        '渲染阶段': 'render',
+        '后期': 'render',
+        '交付': 'delivery',
+        '已完成': 'delivery'
+      };
+      const currentStage = stageMap[project.currentStage || '建模阶段'] || 'model';
+      const stageName = project.currentStage || '建模阶段';
+      
+      // 7. 阶段任务完成度
+      const stageProgress = 50;
+      
+      // 8. 检查是否停滞
+      let isStalled = false;
+      let stalledDays = 0;
+      if (project.updatedAt) {
+        const updatedAt = project.updatedAt instanceof Date ? project.updatedAt : new Date(project.updatedAt);
+        const daysSinceUpdate = Math.floor((now.getTime() - updatedAt.getTime()) / (1000 * 60 * 60 * 24));
+        isStalled = daysSinceUpdate > 7;
+        stalledDays = daysSinceUpdate;
+      }
+      
+      // 9. 催办次数
+      const urgentCount = status === 'overdue' ? 2 : status === 'urgent' ? 1 : 0;
+      
+      // 10. 优先级
+      let priority: 'low' | 'medium' | 'high' | 'critical' = 'medium';
+      if (status === 'overdue') {
+        priority = 'critical';
+      } else if (status === 'urgent') {
+        priority = 'high';
+      } else if (status === 'warning') {
+        priority = 'medium';
+      } else {
+        priority = 'low';
+      }
+      
+      // 11. 获取或生成阶段截止时间数据
+      let phaseDeadlines: PhaseDeadlines | undefined = projectData.phaseDeadlines;
+      if (!phaseDeadlines) {
+        phaseDeadlines = generatePhaseDeadlines(realStartDate, realEndDate);
+      }
+      if (phaseDeadlines) {
+        (Object.keys(phaseDeadlines) as PhaseName[]).forEach((phaseKey) => {
+          const info = phaseDeadlines![phaseKey];
+          if (!info) return;
+          const phaseStart = normalizeDateInput(info.startDate, realStartDate);
+          const phaseEnd = normalizeDateInput(info.deadline, realEndDate);
+          if (now >= phaseEnd) {
+            info.status = 'completed';
+          } else if (now >= phaseStart) {
+            info.status = 'in_progress';
+          } else {
+            info.status = info.status || 'not_started';
+          }
+        });
+      }
+      
+      const spaceName = project.space || projectData.quotation?.spaces?.[0]?.name || '';
+      const customerName = project.customer || project.contact?.name || '';
+      
+      return {
+        projectId: project.id || `proj-${Math.random().toString(36).slice(2, 9)}`,
+        projectName: project.name || '未命名项目',
+        designerId: project.designerId || project.designerName || '未分配',
+        designerName: project.designerName || '未分配',
+        startDate: realStartDate,
+        endDate: realEndDate,
+        deliveryDate: realEndDate,
+        reviewDate: realReviewDate,
+        currentStage,
+        stageName,
+        stageProgress: Math.round(stageProgress),
+        status,
+        isStalled,
+        stalledDays,
+        urgentCount,
+        priority,
+        spaceName,
+        customerName,
+        phaseDeadlines: phaseDeadlines,
+        data: projectData
+      };
+    });
+  }
+}

+ 128 - 0
src/app/pages/team-leader/services/todo-task.service.ts

@@ -0,0 +1,128 @@
+import { Injectable } from '@angular/core';
+import { FmodeParse } from 'fmode-ng/parse';
+import { IssueStatus, IssuePriority, IssueType } from '../../../../modules/project/services/project-issue.service';
+import { TodoTaskFromIssue } from '../dashboard/dashboard.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class TodoTaskService {
+
+  constructor() { }
+
+  /**
+   * 从问题板块加载待办任务
+   */
+  async getTodoTasks(): Promise<TodoTaskFromIssue[]> {
+    try {
+      const Parse: any = FmodeParse.with('nova');
+      const query = new Parse.Query('ProjectIssue');
+      
+      // 筛选条件:待处理 + 处理中
+      query.containedIn('status', ['待处理', '处理中']);
+      query.notEqualTo('isDeleted', true);
+      
+      // 关联数据
+      query.include(['project', 'creator', 'assignee']);
+      
+      // 排序:更新时间倒序
+      query.descending('updatedAt');
+      
+      // 限制数量
+      query.limit(50);
+      
+      const results = await query.find();
+      
+      console.log(`📥 查询到 ${results.length} 条问题记录`);
+      
+      // 数据转换
+      const tasks = await Promise.all(results.map(async (obj: any) => {
+        let project = obj.get('project');
+        const assignee = obj.get('assignee');
+        const creator = obj.get('creator');
+        const data = obj.get('data') || {};
+        
+        let projectName = '未知项目';
+        let projectId = '';
+        
+        if (project) {
+          projectId = project.id;
+          projectName = project.get('name');
+          
+          if (!projectName && projectId) {
+            try {
+              const projectQuery = new Parse.Query('Project');
+              const fetchedProject = await projectQuery.get(projectId);
+              projectName = fetchedProject.get('name') || fetchedProject.get('title') || '未知项目';
+            } catch (error) {
+              console.warn(`⚠️ 无法加载项目 ${projectId}:`, error);
+              projectName = `项目-${projectId.slice(0, 6)}`;
+            }
+          }
+        } else {
+          console.warn('⚠️ 问题缺少关联项目:', {
+            issueId: obj.id,
+            title: obj.get('title')
+          });
+        }
+        
+        return {
+          id: obj.id,
+          title: obj.get('title') || obj.get('description')?.slice(0, 40) || '未命名问题',
+          description: obj.get('description'),
+          priority: obj.get('priority') as IssuePriority || 'medium',
+          type: obj.get('issueType') as IssueType || 'task',
+          status: this.zh2enStatus(obj.get('status')) as IssueStatus,
+          projectId,
+          projectName,
+          relatedSpace: obj.get('relatedSpace') || data.relatedSpace,
+          relatedStage: obj.get('relatedStage') || data.relatedStage,
+          assigneeName: assignee?.get('name') || assignee?.get('realname') || '未指派',
+          creatorName: creator?.get('name') || creator?.get('realname') || '未知',
+          createdAt: obj.createdAt || new Date(),
+          updatedAt: obj.updatedAt || new Date(),
+          dueDate: obj.get('dueDate'),
+          tags: (data.tags || []) as string[]
+        } as TodoTaskFromIssue;
+      }));
+      
+      // 排序:优先级 -> 时间
+      return tasks.sort((a, b) => {
+        const priorityA = this.getPriorityOrder(a.priority);
+        const priorityB = this.getPriorityOrder(b.priority);
+        
+        if (priorityA !== priorityB) {
+          return priorityA - priorityB;
+        }
+        
+        return +new Date(b.updatedAt) - +new Date(a.updatedAt);
+      });
+      
+    } catch (error) {
+      console.error('❌ 加载待办任务失败:', error);
+      throw error;
+    }
+  }
+
+  // 状态映射辅助方法
+  private zh2enStatus(status: string): IssueStatus {
+    const map: Record<string, IssueStatus> = {
+      '待处理': 'open',
+      '处理中': 'in_progress',
+      '已解决': 'resolved',
+      '已关闭': 'closed'
+    };
+    return map[status] || 'open';
+  }
+  
+  private getPriorityOrder(priority: IssuePriority): number {
+    const config: Record<IssuePriority, number> = {
+      urgent: 0,
+      critical: 0,
+      high: 1,
+      medium: 2,
+      low: 3
+    };
+    return config[priority] || 2;
+  }
+}

+ 304 - 0
src/app/pages/team-leader/services/urgent-event.service.ts

@@ -0,0 +1,304 @@
+import { Injectable } from '@angular/core';
+import { UrgentEvent } from '../dashboard/dashboard.model';
+import type { ProjectTimeline } from '../project-timeline/project-timeline';
+
+/**
+ * 紧急事件服务
+ * 负责识别和生成项目中的紧急事件(如即将到期、逾期、停滞等)
+ */
+@Injectable({
+  providedIn: 'root'
+})
+export class UrgentEventService {
+
+  constructor() { }
+
+  /**
+   * 计算紧急事件
+   * 识别截止时间已到或即将到达但未完成的关键节点
+   * @param projectTimelineData 项目时间轴数据
+   * @param handledEventIds 已处理的事件ID集合
+   * @param mutedEventIds 已静音的事件ID集合
+   */
+  calculateUrgentEvents(
+    projectTimelineData: ProjectTimeline[],
+    handledEventIds: Set<string> = new Set(),
+    mutedEventIds: Set<string> = new Set()
+  ): UrgentEvent[] {
+    const events: UrgentEvent[] = [];
+    const now = new Date();
+    const oneDayMs = 24 * 60 * 60 * 1000;
+    const projectEventCount = new Map<string, number>(); // 追踪每个项目生成的事件数
+    const MAX_EVENTS_PER_PROJECT = 2; // 每个项目最多生成2个最紧急的事件
+
+    // 辅助函数:解析事件分类
+    const resolveCategory = (
+      eventType: UrgentEvent['eventType'],
+      category?: 'customer' | 'phase' | 'review' | 'delivery'
+    ): 'customer' | 'phase' | 'review' | 'delivery' => {
+      if (category) return category;
+      switch (eventType) {
+        case 'phase_deadline':
+          return 'phase';
+        case 'delivery':
+          return 'delivery';
+        case 'customer_alert':
+          return 'customer';
+        default:
+          return 'review';
+      }
+    };
+
+    // 辅助函数:获取逾期原因
+    const getOverdueReason = (daysDiff: number, stalledDays: number = 0) => {
+      if (daysDiff >= 0) return ''; // 未逾期
+      
+      // 如果项目超过3天未更新/无反馈,推测为客户原因
+      if (stalledDays >= 3) {
+        return '原因:客户未跟进反馈导致逾期';
+      }
+      
+      // 否则推测为设计师进度原因
+      return '原因:设计师进度原因导致逾期';
+    };
+
+    // 辅助函数:添加事件
+    const addEvent = (
+      partial: Omit<UrgentEvent, 'category' | 'statusType' | 'labels' | 'allowConfirmOnTime' | 'allowMarkHandled' | 'allowCreateTodo' | 'followUpNeeded'> &
+        Partial<UrgentEvent>
+    ) => {
+      // 检查该项目是否已达到事件数量上限
+      const currentCount = projectEventCount.get(partial.projectId) || 0;
+      if (currentCount >= MAX_EVENTS_PER_PROJECT) {
+        return; // 跳过,避免单个项目产生过多事件
+      }
+      
+      const category = resolveCategory(partial.eventType, partial.category);
+      const statusType: UrgentEvent['statusType'] =
+        partial.statusType || (partial.overdueDays && partial.overdueDays > 0 ? 'overdue' : 'dueSoon');
+      
+      // 简化描述,避免过长字符串
+      const description = partial.description?.substring(0, 100) || '';
+      
+      const event: UrgentEvent = {
+        ...partial,
+        description,
+        category,
+        statusType,
+        labels: partial.labels ?? [],
+        followUpNeeded: partial.followUpNeeded ?? false,
+        allowConfirmOnTime:
+          partial.allowConfirmOnTime ?? (category !== 'customer' && statusType === 'dueSoon'),
+        allowMarkHandled: partial.allowMarkHandled ?? true,
+        allowCreateTodo: partial.allowCreateTodo ?? category === 'customer'
+      };
+      events.push(event);
+      projectEventCount.set(partial.projectId, currentCount + 1);
+    };
+    
+    try {
+      // 从 projectTimelineData 中提取数据
+      projectTimelineData.forEach(project => {
+        // 1. 检查小图对图事件
+        if (project.reviewDate) {
+          const reviewTime = project.reviewDate.getTime();
+          const timeDiff = reviewTime - now.getTime();
+          const daysDiff = Math.ceil(timeDiff / oneDayMs);
+          
+          // 如果小图对图已经到期或即将到期(1天内),且不在已完成状态
+          if (daysDiff <= 1 && project.currentStage !== 'delivery') {
+            const reason = getOverdueReason(daysDiff, project.stalledDays);
+            const descSuffix = reason ? `,${reason}` : '';
+            
+            addEvent({
+              id: `${project.projectId}-review`,
+              title: `小图对图截止`,
+              description: `项目「${project.projectName}」的小图对图时间已${daysDiff < 0 ? '逾期' : '临近'}${descSuffix}`,
+              eventType: 'review',
+              deadline: project.reviewDate,
+              projectId: project.projectId,
+              projectName: project.projectName,
+              designerName: project.designerName,
+              urgencyLevel: daysDiff < 0 ? 'critical' : daysDiff === 0 ? 'high' : 'medium',
+              overdueDays: -daysDiff,
+              labels: daysDiff < 0 ? ['逾期'] : ['临近'],
+              followUpNeeded: (project.stageName || '').includes('图') || project.status === 'warning'
+            });
+          }
+        }
+        
+        // 2. 检查交付事件
+        if (project.deliveryDate) {
+          const deliveryTime = project.deliveryDate.getTime();
+          const timeDiff = deliveryTime - now.getTime();
+          const daysDiff = Math.ceil(timeDiff / oneDayMs);
+          
+          // 如果交付已经到期或即将到期(1天内),且不在已完成状态
+          if (daysDiff <= 1 && project.currentStage !== 'delivery') {
+            const summary = project.spaceDeliverableSummary;
+            const completionRate = summary?.overallCompletionRate || 0;
+            const reason = getOverdueReason(daysDiff, project.stalledDays);
+            const descSuffix = reason ? `,${reason}` : '';
+            
+            addEvent({
+              id: `${project.projectId}-delivery`,
+              title: `项目交付截止`,
+              description: `项目「${project.projectName}」需要在 ${project.deliveryDate.toLocaleDateString()} 交付(当前完成率 ${completionRate}%)${descSuffix}`,
+              eventType: 'delivery',
+              deadline: project.deliveryDate,
+              projectId: project.projectId,
+              projectName: project.projectName,
+              designerName: project.designerName,
+              urgencyLevel: daysDiff < 0 ? 'critical' : daysDiff === 0 ? 'high' : 'medium',
+              overdueDays: -daysDiff,
+              completionRate,
+              labels: daysDiff < 0 ? ['逾期'] : ['临近']
+            });
+          }
+        }
+        
+        // 3. 检查各阶段截止时间
+        if (project.phaseDeadlines) {
+          const phaseMap = {
+            modeling: '建模',
+            softDecor: '软装',
+            rendering: '渲染',
+            postProcessing: '后期'
+          };
+          
+          Object.entries(project.phaseDeadlines).forEach(([key, phaseInfo]: [string, any]) => {
+            if (phaseInfo && phaseInfo.deadline) {
+              const deadline = new Date(phaseInfo.deadline);
+              const phaseTime = deadline.getTime();
+              const timeDiff = phaseTime - now.getTime();
+              const daysDiff = Math.ceil(timeDiff / oneDayMs);
+              
+              // 如果阶段已经到期或即将到期(1天内),且状态不是已完成
+              if (daysDiff <= 1 && phaseInfo.status !== 'completed') {
+                const phaseName = phaseMap[key as keyof typeof phaseMap] || key;
+                
+                // 获取该阶段的完成率
+                const summary = project.spaceDeliverableSummary;
+                let completionRate = 0;
+                if (summary && summary.phaseProgress) {
+                  const phaseProgress = summary.phaseProgress[key as keyof typeof summary.phaseProgress];
+                  completionRate = phaseProgress?.completionRate || 0;
+                }
+                
+                const reason = getOverdueReason(daysDiff, project.stalledDays);
+                const descSuffix = reason ? `,${reason}` : '';
+
+                addEvent({
+                  id: `${project.projectId}-phase-${key}`,
+                  title: `${phaseName}阶段截止`,
+                  description: `项目「${project.projectName}」的${phaseName}阶段截止时间已${daysDiff < 0 ? '逾期' : '临近'}(完成率 ${completionRate}%)${descSuffix}`,
+                  eventType: 'phase_deadline',
+                  phaseName,
+                  deadline,
+                  projectId: project.projectId,
+                  projectName: project.projectName,
+                  designerName: project.designerName,
+                  urgencyLevel: daysDiff < 0 ? 'critical' : daysDiff === 0 ? 'high' : 'medium',
+                  overdueDays: -daysDiff,
+                  completionRate,
+                  labels: daysDiff < 0 ? ['逾期'] : ['临近']
+                });
+              }
+            }
+          });
+        }
+        
+        if (project.stalledDays && project.stalledDays >= 7) {
+          addEvent({
+            id: `${project.projectId}-stagnant`,
+            title: project.stalledDays >= 14 ? '客户停滞预警' : '停滞期提醒',
+            description: `项目「${project.projectName}」已有 ${project.stalledDays} 天未收到客户反馈,请主动跟进。`,
+            eventType: 'customer_alert',
+            deadline: new Date(),
+            projectId: project.projectId,
+            projectName: project.projectName,
+            designerName: project.designerName,
+            urgencyLevel: project.stalledDays >= 14 ? 'critical' : 'high',
+            statusType: 'stagnant',
+            stagnationDays: project.stalledDays,
+            labels: ['停滞期'],
+            followUpNeeded: true,
+            allowCreateTodo: true,
+            allowConfirmOnTime: false,
+            category: 'customer'
+          });
+        }
+
+        const inReviewStage = (project.stageName || '').includes('图') || (project.currentStage || '').includes('图');
+        if (inReviewStage && project.status === 'warning') {
+          addEvent({
+            id: `${project.projectId}-review-followup`,
+            title: '对图反馈待跟进',
+            description: `项目「${project.projectName}」客户反馈尚未处理,请尽快跟进。`,
+            eventType: 'customer_alert',
+            deadline: project.reviewDate || new Date(),
+            projectId: project.projectId,
+            projectName: project.projectName,
+            designerName: project.designerName,
+            urgencyLevel: 'high',
+            statusType: project.reviewDate && project.reviewDate < now ? 'overdue' : 'dueSoon',
+            labels: ['对图期'],
+            followUpNeeded: true,
+            allowCreateTodo: true,
+            customerIssueType: 'feedback_pending',
+            category: 'customer'
+          });
+        }
+
+        if (project.priority === 'critical') {
+          addEvent({
+            id: `${project.projectId}-customer-alert`,
+            title: '客户服务预警',
+            description: `项目「${project.projectName}」存在客户不满或抱怨,需要立即处理并记录。`,
+            eventType: 'customer_alert',
+            deadline: new Date(),
+            projectId: project.projectId,
+            projectName: project.projectName,
+            designerName: project.designerName,
+            urgencyLevel: 'critical',
+            statusType: 'dueSoon',
+            labels: ['客户预警'],
+            followUpNeeded: true,
+            allowCreateTodo: true,
+            customerIssueType: 'complaint',
+            category: 'customer'
+          });
+        }
+      });
+      
+      // 按紧急程度和时间排序
+      events.sort((a, b) => {
+        // 首先按紧急程度排序
+        const urgencyOrder = { critical: 0, high: 1, medium: 2 };
+        const urgencyDiff = urgencyOrder[a.urgencyLevel] - urgencyOrder[b.urgencyLevel];
+        if (urgencyDiff !== 0) return urgencyDiff;
+        
+        // 相同紧急程度,按截止时间排序(越早越靠前)
+        return a.deadline.getTime() - b.deadline.getTime();
+      });
+      
+      // 过滤已处理和静音的事件
+      const filteredEvents = events.filter(event => !handledEventIds.has(event.id) && !mutedEventIds.has(event.id));
+      
+      // 限制最大显示数量
+      const MAX_URGENT_EVENTS = 50;
+      
+      if (filteredEvents.length > MAX_URGENT_EVENTS) {
+        console.warn(`⚠️ 紧急事件过多(${filteredEvents.length}个),已限制为前 ${MAX_URGENT_EVENTS} 个最紧急的事件`);
+      }
+      
+      console.log(`✅ 计算紧急事件完成,共 ${filteredEvents.slice(0, MAX_URGENT_EVENTS).length} 个紧急事件`);
+      
+      return filteredEvents.slice(0, MAX_URGENT_EVENTS);
+      
+    } catch (error) {
+      console.error('❌ 计算紧急事件失败:', error);
+      return [];
+    }
+  }
+}

+ 67 - 22
src/app/services/project.service.ts

@@ -572,7 +572,21 @@ export class ProjectService {
 
   // 获取当前设计师的项目列表
   getProjects(): Observable<Project[]> {
-    return of(this.projects);
+    return new Observable(observer => {
+      this.getProjectsFromParse().then(projects => {
+        if (projects && projects.length > 0) {
+          observer.next(projects);
+        } else {
+          // 降级:使用模拟数据
+          observer.next(this.projects);
+        }
+        observer.complete();
+      }).catch(error => {
+        console.error('查询项目列表失败,使用模拟数据:', error);
+        observer.next(this.projects);
+        observer.complete();
+      });
+    });
   }
 
   // 获取项目详情
@@ -596,7 +610,29 @@ export class ProjectService {
   }
 
   /**
-   * 从Parse查询项目
+   * 从Parse查询所有项目
+   */
+  private async getProjectsFromParse(): Promise<Project[]> {
+    try {
+      const companyId = localStorage.getItem('company') || 'cDL6R1hgSi';
+      const query = new Parse.Query('Project');
+      query.equalTo('company', companyId);
+      query.notEqualTo('isDeleted', true);
+      query.descending('createdAt');
+      query.limit(1000);
+      query.include('customer', 'assignee');
+
+      const projects = await query.find();
+      
+      return projects.map(p => this.convertToProject(p));
+    } catch (error) {
+      console.error('Parse查询项目列表失败:', error);
+      return [];
+    }
+  }
+
+  /**
+   * 从Parse查询单个项目
    */
   private async getProjectFromParse(id: string): Promise<Project | undefined> {
     try {
@@ -609,32 +645,41 @@ export class ProjectService {
         return undefined;
       }
       
-      // 转换为前端Project格式
-      const customer = project.get('customer');
-      const assignee = project.get('assignee');
-      const data = project.get('data') || {};
-      
-      return {
-        id: project.id,
-        name: project.get('title') || '未命名项目',
-        customerName: customer?.get('name') || '未知客户',
-        customerTags: [],
-        highPriorityNeeds: data.highPriorityNeeds || [],
-        status: project.get('status') || '待分配',
-        currentStage: project.get('currentStage') || '订单分配',
-        stage: project.get('currentStage') || '订单分配',
-        createdAt: project.get('createdAt') || new Date(),
-        deadline: project.get('deadline') || new Date(),
-        assigneeId: assignee?.id || '',
-        assigneeName: assignee?.get('name') || '未分配',
-        skillsRequired: data.skillsRequired || []
-      };
+      return this.convertToProject(project);
     } catch (error) {
       console.error('Parse查询失败:', error);
       return undefined;
     }
   }
 
+  /**
+   * 将Parse对象转换为前端Project模型
+   */
+  private convertToProject(project: any): Project {
+    const customer = project.get('customer');
+    const assignee = project.get('assignee');
+    const data = project.get('data') || {};
+    
+    return {
+      id: project.id,
+      name: project.get('title') || '未命名项目',
+      customerName: customer?.get('name') || '未知客户',
+      customerTags: [],
+      highPriorityNeeds: data.highPriorityNeeds || [],
+      status: project.get('status') || '待分配',
+      currentStage: project.get('currentStage') || '订单分配',
+      stage: project.get('currentStage') || '订单分配',
+      createdAt: project.get('createdAt') || new Date(),
+      deadline: project.get('deadline') || new Date(),
+      assigneeId: assignee?.id || '',
+      assigneeName: assignee?.get('name') || '未分配',
+      skillsRequired: data.skillsRequired || [],
+      // 保留原始数据
+      data: data,
+      contact: project.get('contact')
+    } as Project;
+  }
+
   // 获取待办任务
   getTasks(mockSize?: number): Observable<Task[]> {
     if (typeof mockSize === 'number' && mockSize > 0) {

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů