# 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; // 负责人 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 { 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 { // 获取公司配置 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. ✅ **扩展性**:支持添加负责人、优先级、备注等更多信息 建议按照本方案实施,可以有效支持项目负载图的阶段查看功能。