소스 검색

refactor: replace Gantt chart with Project Timeline component in dashboard

- Removed the Gantt chart section and integrated the new Project Timeline component for improved project visualization.
- Updated the dashboard to enhance user interaction with project timelines and streamline the display of project data.
0235711 3 일 전
부모
커밋
5067730328
2개의 변경된 파일585개의 추가작업 그리고 39개의 파일을 삭제
  1. 579 0
      docs/schema/project-phase-deadlines-design.md
  2. 6 39
      src/app/pages/team-leader/dashboard/dashboard.html

+ 579 - 0
docs/schema/project-phase-deadlines-design.md

@@ -0,0 +1,579 @@
+# Project表阶段截止时间字段设计方案
+
+## 📋 需求背景
+
+为了支持项目负载时间轴的阶段查看功能,需要在Project表中添加各个设计阶段的截止时间信息,包括:
+- 建模阶段
+- 软装阶段  
+- 渲染阶段
+- 后期阶段
+
+根据schemas.md说明,这些信息应存储在`Project.data`字段(Object类型)中,可以自由扩展子级属性。
+
+---
+
+## 🎯 数据结构设计
+
+### 1. Project.data.phaseDeadlines 字段结构
+
+```typescript
+interface PhaseDeadlines {
+  modeling?: PhaseInfo;      // 建模阶段
+  softDecor?: PhaseInfo;     // 软装阶段
+  rendering?: PhaseInfo;     // 渲染阶段
+  postProcessing?: PhaseInfo; // 后期阶段
+}
+
+interface PhaseInfo {
+  startDate?: Date;          // 阶段开始时间
+  deadline: Date;            // 阶段截止时间
+  estimatedDays?: number;    // 预计工期(天数)
+  status?: 'not_started' | 'in_progress' | 'completed' | 'delayed'; // 阶段状态
+  completedAt?: Date;        // 实际完成时间
+  assignee?: Pointer<Profile>; // 负责人
+  priority?: 'low' | 'medium' | 'high' | 'urgent'; // 优先级
+  notes?: string;            // 备注信息
+}
+```
+
+---
+
+## 📊 JSON示例
+
+### 完整示例(所有阶段)
+
+```json
+{
+  "phaseDeadlines": {
+    "modeling": {
+      "startDate": "2024-12-01T00:00:00.000Z",
+      "deadline": "2024-12-08T23:59:59.999Z",
+      "estimatedDays": 7,
+      "status": "completed",
+      "completedAt": "2024-12-07T18:30:00.000Z",
+      "assignee": {
+        "__type": "Pointer",
+        "className": "Profile",
+        "objectId": "prof001"
+      },
+      "priority": "high",
+      "notes": "客户要求加急,优先处理"
+    },
+    "softDecor": {
+      "startDate": "2024-12-09T00:00:00.000Z",
+      "deadline": "2024-12-13T23:59:59.999Z",
+      "estimatedDays": 4,
+      "status": "in_progress",
+      "assignee": {
+        "__type": "Pointer",
+        "className": "Profile",
+        "objectId": "prof002"
+      },
+      "priority": "medium"
+    },
+    "rendering": {
+      "startDate": "2024-12-14T00:00:00.000Z",
+      "deadline": "2024-12-20T23:59:59.999Z",
+      "estimatedDays": 6,
+      "status": "not_started",
+      "priority": "high",
+      "notes": "需要高质量渲染,预留充足时间"
+    },
+    "postProcessing": {
+      "startDate": "2024-12-21T00:00:00.000Z",
+      "deadline": "2024-12-24T23:59:59.999Z",
+      "estimatedDays": 3,
+      "status": "not_started",
+      "priority": "medium",
+      "notes": "后期处理与润色"
+    }
+  }
+}
+```
+
+### 简化示例(仅关键字段)
+
+```json
+{
+  "phaseDeadlines": {
+    "modeling": {
+      "deadline": "2024-12-08T23:59:59.999Z",
+      "status": "in_progress"
+    },
+    "softDecor": {
+      "deadline": "2024-12-13T23:59:59.999Z",
+      "status": "not_started"
+    },
+    "rendering": {
+      "deadline": "2024-12-20T23:59:59.999Z",
+      "status": "not_started"
+    },
+    "postProcessing": {
+      "deadline": "2024-12-24T23:59:59.999Z",
+      "status": "not_started"
+    }
+  }
+}
+```
+
+---
+
+## 💾 Parse Cloud Code 使用示例
+
+### 1. 创建项目时设置阶段截止时间
+
+```javascript
+Parse.Cloud.define("createProjectWithPhases", async (request) => {
+  const { projectTitle, contactId, companyId, phaseConfig } = request.params;
+  
+  const Project = Parse.Object.extend("Project");
+  const project = new Project();
+  
+  project.set("title", projectTitle);
+  project.set("contact", { __type: "Pointer", className: "ContactInfo", objectId: contactId });
+  project.set("company", { __type: "Pointer", className: "Company", objectId: companyId });
+  project.set("status", "进行中");
+  project.set("currentStage", "建模");
+  
+  // 设置阶段截止时间
+  const baseDate = new Date();
+  project.set("data", {
+    phaseDeadlines: {
+      modeling: {
+        startDate: new Date(baseDate),
+        deadline: new Date(baseDate.getTime() + 7 * 24 * 60 * 60 * 1000), // 7天后
+        estimatedDays: 7,
+        status: "in_progress",
+        priority: "high"
+      },
+      softDecor: {
+        startDate: new Date(baseDate.getTime() + 7 * 24 * 60 * 60 * 1000),
+        deadline: new Date(baseDate.getTime() + 11 * 24 * 60 * 60 * 1000), // 11天后
+        estimatedDays: 4,
+        status: "not_started",
+        priority: "medium"
+      },
+      rendering: {
+        startDate: new Date(baseDate.getTime() + 11 * 24 * 60 * 60 * 1000),
+        deadline: new Date(baseDate.getTime() + 17 * 24 * 60 * 60 * 1000), // 17天后
+        estimatedDays: 6,
+        status: "not_started",
+        priority: "high"
+      },
+      postProcessing: {
+        startDate: new Date(baseDate.getTime() + 17 * 24 * 60 * 60 * 1000),
+        deadline: new Date(baseDate.getTime() + 20 * 24 * 60 * 60 * 1000), // 20天后
+        estimatedDays: 3,
+        status: "not_started",
+        priority: "medium"
+      }
+    }
+  });
+  
+  await project.save(null, { useMasterKey: true });
+  return project;
+});
+```
+
+### 2. 更新阶段状态
+
+```javascript
+Parse.Cloud.define("updatePhaseStatus", async (request) => {
+  const { projectId, phaseName, status, completedAt } = request.params;
+  
+  const projectQuery = new Parse.Query("Project");
+  const project = await projectQuery.get(projectId, { useMasterKey: true });
+  
+  const data = project.get("data") || {};
+  const phaseDeadlines = data.phaseDeadlines || {};
+  
+  if (!phaseDeadlines[phaseName]) {
+    throw new Error(`Phase ${phaseName} not found`);
+  }
+  
+  phaseDeadlines[phaseName].status = status;
+  if (status === "completed") {
+    phaseDeadlines[phaseName].completedAt = completedAt || new Date();
+  }
+  
+  data.phaseDeadlines = phaseDeadlines;
+  project.set("data", data);
+  
+  await project.save(null, { useMasterKey: true });
+  return project;
+});
+```
+
+### 3. 查询特定阶段的项目
+
+```javascript
+Parse.Cloud.define("getProjectsByPhaseStatus", async (request) => {
+  const { companyId, phaseName, status } = request.params;
+  
+  const projectQuery = new Parse.Query("Project");
+  projectQuery.equalTo("company", { __type: "Pointer", className: "Company", objectId: companyId });
+  projectQuery.exists(`data.phaseDeadlines.${phaseName}`);
+  
+  const projects = await projectQuery.find({ useMasterKey: true });
+  
+  // 前端过滤(Parse Query不支持深层嵌套查询)
+  return projects.filter(project => {
+    const data = project.get("data");
+    return data?.phaseDeadlines?.[phaseName]?.status === status;
+  });
+});
+```
+
+---
+
+## 🎨 前端使用示例(Angular/TypeScript)
+
+### 1. 类型定义
+
+```typescript
+// src/app/models/project-phase.model.ts
+export interface PhaseInfo {
+  startDate?: Date;
+  deadline: Date;
+  estimatedDays?: number;
+  status?: 'not_started' | 'in_progress' | 'completed' | 'delayed';
+  completedAt?: Date;
+  assignee?: {
+    __type: 'Pointer';
+    className: 'Profile';
+    objectId: string;
+  };
+  priority?: 'low' | 'medium' | 'high' | 'urgent';
+  notes?: string;
+}
+
+export interface PhaseDeadlines {
+  modeling?: PhaseInfo;
+  softDecor?: PhaseInfo;
+  rendering?: PhaseInfo;
+  postProcessing?: PhaseInfo;
+}
+
+export interface ProjectData {
+  phaseDeadlines?: PhaseDeadlines;
+  // ... 其他data字段
+}
+```
+
+### 2. 获取阶段信息
+
+```typescript
+// src/app/services/project.service.ts
+getProjectPhaseDeadlines(projectId: string): Observable<PhaseDeadlines | null> {
+  const query = new Parse.Query('Project');
+  return from(query.get(projectId)).pipe(
+    map(project => {
+      const data = project.get('data') as ProjectData;
+      return data?.phaseDeadlines || null;
+    })
+  );
+}
+```
+
+### 3. 项目时间轴组件中使用
+
+```typescript
+// src/app/pages/team-leader/project-timeline/project-timeline.component.ts
+interface TimelineEvent {
+  date: Date;
+  label: string;
+  type: 'start' | 'milestone' | 'deadline';
+  phase: string;
+  status?: string;
+}
+
+generateTimelineEvents(project: any): TimelineEvent[] {
+  const events: TimelineEvent[] = [];
+  const phaseDeadlines = project.get('data')?.phaseDeadlines;
+  
+  if (!phaseDeadlines) return events;
+  
+  const phaseLabels = {
+    modeling: '建模',
+    softDecor: '软装',
+    rendering: '渲染',
+    postProcessing: '后期'
+  };
+  
+  // 为每个阶段生成事件
+  Object.entries(phaseDeadlines).forEach(([phaseName, phaseInfo]: [string, any]) => {
+    // 开始事件
+    if (phaseInfo.startDate) {
+      events.push({
+        date: new Date(phaseInfo.startDate),
+        label: `${phaseLabels[phaseName as keyof typeof phaseLabels]}开始`,
+        type: 'start',
+        phase: phaseName,
+        status: phaseInfo.status
+      });
+    }
+    
+    // 截止事件
+    if (phaseInfo.deadline) {
+      events.push({
+        date: new Date(phaseInfo.deadline),
+        label: `${phaseLabels[phaseName as keyof typeof phaseLabels]}截止`,
+        type: 'deadline',
+        phase: phaseName,
+        status: phaseInfo.status
+      });
+    }
+    
+    // 完成事件
+    if (phaseInfo.completedAt) {
+      events.push({
+        date: new Date(phaseInfo.completedAt),
+        label: `${phaseLabels[phaseName as keyof typeof phaseLabels]}完成`,
+        type: 'milestone',
+        phase: phaseName,
+        status: 'completed'
+      });
+    }
+  });
+  
+  // 按时间排序
+  return events.sort((a, b) => a.date.getTime() - b.date.getTime());
+}
+```
+
+---
+
+## 📈 时间轴可视化建议
+
+### 阶段颜色映射
+
+```typescript
+const phaseColors = {
+  modeling: {
+    bg: '#E3F2FD',      // 浅蓝色
+    border: '#2196F3',  // 蓝色
+    label: '建模'
+  },
+  softDecor: {
+    bg: '#F3E5F5',      // 浅紫色
+    border: '#9C27B0',  // 紫色
+    label: '软装'
+  },
+  rendering: {
+    bg: '#FFF3E0',      // 浅橙色
+    border: '#FF9800',  // 橙色
+    label: '渲染'
+  },
+  postProcessing: {
+    bg: '#E8F5E9',      // 浅绿色
+    border: '#4CAF50',  // 绿色
+    label: '后期'
+  }
+};
+```
+
+### 状态图标映射
+
+```typescript
+const statusIcons = {
+  not_started: '⏸️',
+  in_progress: '▶️',
+  completed: '✅',
+  delayed: '⚠️'
+};
+```
+
+---
+
+## 🔄 数据迁移建议
+
+### 为现有项目添加阶段截止时间
+
+```javascript
+Parse.Cloud.job("migrateProjectPhaseDeadlines", async (request) => {
+  const { params, message } = request;
+  
+  const query = new Parse.Query("Project");
+  query.notEqualTo("isDeleted", true);
+  query.limit(1000);
+  
+  let count = 0;
+  const projects = await query.find({ useMasterKey: true });
+  
+  for (const project of projects) {
+    const data = project.get("data") || {};
+    
+    // 如果已有phaseDeadlines,跳过
+    if (data.phaseDeadlines) continue;
+    
+    // 根据项目deadline推算各阶段截止时间
+    const deadline = project.get("deadline");
+    if (!deadline) continue;
+    
+    const deadlineTime = deadline.getTime();
+    const modelingDeadline = new Date(deadlineTime - 13 * 24 * 60 * 60 * 1000); // 提前13天
+    const softDecorDeadline = new Date(deadlineTime - 9 * 24 * 60 * 60 * 1000);  // 提前9天
+    const renderingDeadline = new Date(deadlineTime - 3 * 24 * 60 * 60 * 1000);  // 提前3天
+    const postProcessingDeadline = deadline;
+    
+    data.phaseDeadlines = {
+      modeling: {
+        deadline: modelingDeadline,
+        estimatedDays: 7,
+        status: "not_started"
+      },
+      softDecor: {
+        deadline: softDecorDeadline,
+        estimatedDays: 4,
+        status: "not_started"
+      },
+      rendering: {
+        deadline: renderingDeadline,
+        estimatedDays: 6,
+        status: "not_started"
+      },
+      postProcessing: {
+        deadline: postProcessingDeadline,
+        estimatedDays: 3,
+        status: "not_started"
+      }
+    };
+    
+    project.set("data", data);
+    await project.save(null, { useMasterKey: true });
+    count++;
+    
+    message(`Migrated ${count} projects`);
+  }
+  
+  message(`Migration completed: ${count} projects updated`);
+});
+```
+
+---
+
+## ⚙️ 配置建议
+
+### 默认工期配置
+
+建议在Company.data中添加默认工期配置:
+
+```json
+{
+  "phaseDefaultDurations": {
+    "modeling": 7,        // 建模默认7天
+    "softDecor": 4,       // 软装默认4天
+    "rendering": 6,       // 渲染默认6天
+    "postProcessing": 3   // 后期默认3天
+  }
+}
+```
+
+### 使用配置创建项目
+
+```typescript
+async createProjectWithDefaultPhases(
+  projectData: any, 
+  companyId: string
+): Promise<Parse.Object> {
+  // 获取公司配置
+  const companyQuery = new Parse.Query('Company');
+  const company = await companyQuery.get(companyId);
+  const companyData = company.get('data') || {};
+  const durations = companyData.phaseDefaultDurations || {
+    modeling: 7,
+    softDecor: 4,
+    rendering: 6,
+    postProcessing: 3
+  };
+  
+  // 计算各阶段截止时间
+  const startDate = new Date();
+  const phaseDeadlines: any = {};
+  let currentDate = new Date(startDate);
+  
+  ['modeling', 'softDecor', 'rendering', 'postProcessing'].forEach(phase => {
+    const days = durations[phase];
+    const deadline = new Date(currentDate.getTime() + days * 24 * 60 * 60 * 1000);
+    
+    phaseDeadlines[phase] = {
+      startDate: new Date(currentDate),
+      deadline: deadline,
+      estimatedDays: days,
+      status: phase === 'modeling' ? 'in_progress' : 'not_started',
+      priority: 'medium'
+    };
+    
+    currentDate = new Date(deadline.getTime() + 1); // 下一阶段从前一阶段结束后开始
+  });
+  
+  // 创建项目
+  const Project = Parse.Object.extend('Project');
+  const project = new Project();
+  project.set('data', { phaseDeadlines, ...projectData.data });
+  // ... 设置其他字段
+  
+  await project.save(null, { useMasterKey: true });
+  return project;
+}
+```
+
+---
+
+## 📝 注意事项
+
+### 1. 数据验证
+- 确保`deadline`字段为有效的Date对象
+- 确保阶段顺序逻辑正确(前一阶段结束 < 下一阶段开始)
+- 状态枚举值必须在允许范围内
+
+### 2. 性能优化
+- Parse Query不支持深层嵌套查询,需要在前端过滤
+- 大量项目查询时建议使用Cloud Code
+- 考虑添加索引优化查询性能
+
+### 3. 兼容性
+- 保持向后兼容,旧项目可能没有`phaseDeadlines`字段
+- 代码中需要做空值检查:`project.get('data')?.phaseDeadlines`
+
+### 4. 扩展性
+- 可以根据项目类型(软装/硬装)调整默认工期
+- 可以添加更多阶段(如量房、方案设计等)
+- 可以为每个阶段添加子任务
+
+---
+
+## 🎯 实施步骤
+
+### 第一步:更新schemas.md文档
+在Project表的data字段说明中添加phaseDeadlines结构说明
+
+### 第二步:编写数据迁移脚本
+为现有项目添加默认的阶段截止时间
+
+### 第三步:更新前端类型定义
+添加PhaseInfo和PhaseDeadlines接口定义
+
+### 第四步:修改项目创建逻辑
+在创建项目时自动生成阶段截止时间
+
+### 第五步:更新时间轴组件
+读取并展示阶段截止时间信息
+
+### 第六步:添加阶段管理界面
+允许手动调整各阶段的截止时间和状态
+
+---
+
+## ✅ 总结
+
+通过在`Project.data.phaseDeadlines`中存储各阶段截止时间信息:
+
+1. ✅ **灵活性**:Object类型允许自由扩展,易于添加新阶段
+2. ✅ **清晰性**:每个阶段的信息结构一致,便于维护
+3. ✅ **可视化**:为项目负载时间轴提供完整的阶段数据支持
+4. ✅ **兼容性**:不影响现有表结构,易于向后兼容
+5. ✅ **扩展性**:支持添加负责人、优先级、备注等更多信息
+
+建议按照本方案实施,可以有效支持项目负载图的阶段查看功能。
+

+ 6 - 39
src/app/pages/team-leader/dashboard/dashboard.html

@@ -98,46 +98,13 @@
       </div>
       <div class="gantt-container" #workloadGanttContainer></div>
     </div>
+    <!-- 项目负载时间轴(切换视图时显示) -->
     @if (showGanttView) {
-      <div class="gantt-card">
-        <div class="gantt-header">
-          <div class="title">项目负载时间轴</div>
-          <div class="hint">
-            👤 设计师按负载由高到低排列 | 🎨 每个条形代表一个项目 | 🔴超期 🟠临期 🟢正常
-          </div>
-          <div class="scale-switch">
-            <button [class.active]="ganttScale === 'week'" (click)="setGanttScale('week')">周</button>
-            <button [class.active]="ganttScale === 'month'" (click)="setGanttScale('month')">月</button>
-          </div>
-          <div class="search-box">
-            <input type="search" placeholder="搜索项目/设计师/风格关键词" [(ngModel)]="searchTerm" (input)="onSearchChange()" (focus)="onSearchFocus()" (blur)="onSearchBlur()" />
-            @if (showSuggestions) {
-              <div class="suggestion-panel">
-                @if (searchSuggestions.length > 0) {
-                  <ul>
-                    @for (suggest of searchSuggestions; track suggest.id) {
-                      <li (mousedown)="selectSuggestion(suggest)">
-                        <div class="line-1">
-                          <span class="name">{{ suggest.name }}</span>
-                          <span class="badge" [class.vip]="suggest.memberType==='vip'">{{ suggest.memberType==='vip' ? 'VIP' : '普通' }}</span>
-                          <span class="urgency" [class]="'u-' + suggest.urgency">{{ getUrgencyLabel(suggest.urgency) }}</span>
-                        </div>
-                        <div class="line-2">
-                          <span class="designer">{{ suggest.designerName || '未分配' }}</span>
-                          <span class="deadline">{{ suggest.deadline | date:'MM-dd' }}</span>
-                        </div>
-                      </li>
-                    }
-                  </ul>
-                } @else {
-                  <div class="empty">抱歉,没有检索到哦</div>
-                }
-              </div>
-            }
-          </div>
-        </div>
-        <div #ganttChartRef class="gantt-chart"></div>
-      </div>
+      <app-project-timeline 
+        [projects]="projectTimelineData"
+        [companyId]="currentUser.name"
+        (projectClick)="onProjectTimelineClick($event)">
+      </app-project-timeline>
     }
 
     @if (!showGanttView) {