project-phase-deadlines-design.md 15 KB

Project表阶段截止时间字段设计方案

📋 需求背景

为了支持项目负载时间轴的阶段查看功能,需要在Project表中添加各个设计阶段的截止时间信息,包括:

  • 建模阶段
  • 软装阶段
  • 渲染阶段
  • 后期阶段

根据schemas.md说明,这些信息应存储在Project.data字段(Object类型)中,可以自由扩展子级属性。


🎯 数据结构设计

1. Project.data.phaseDeadlines 字段结构

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示例

完整示例(所有阶段)

{
  "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": "后期处理与润色"
    }
  }
}

简化示例(仅关键字段)

{
  "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. 创建项目时设置阶段截止时间

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. 更新阶段状态

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. 查询特定阶段的项目

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. 类型定义

// 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. 获取阶段信息

// 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. 项目时间轴组件中使用

// 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());
}

📈 时间轴可视化建议

阶段颜色映射

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: '后期'
  }
};

状态图标映射

const statusIcons = {
  not_started: '⏸️',
  in_progress: '▶️',
  completed: '✅',
  delayed: '⚠️'
};

🔄 数据迁移建议

为现有项目添加阶段截止时间

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中添加默认工期配置:

{
  "phaseDefaultDurations": {
    "modeling": 7,        // 建模默认7天
    "softDecor": 4,       // 软装默认4天
    "rendering": 6,       // 渲染默认6天
    "postProcessing": 3   // 后期默认3天
  }
}

使用配置创建项目

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. 扩展性:支持添加负责人、优先级、备注等更多信息

建议按照本方案实施,可以有效支持项目负载图的阶段查看功能。