# 项目空间数量同步分析报告 - 第二部分 ## 4. 实现细节 ### 4.1 ProductSpaceService 核心方法 **文件**:`src/modules/project/services/product-space.service.ts` ```typescript /** * 获取项目的所有空间产品 * @param projectId 项目ID * @returns ProductSpace数组 */ async getProjectProductSpaces(projectId: string): Promise { const query = new Parse.Query('Product'); query.equalTo('project', projectId); query.notEqualTo('isDeleted', true); query.ascending('order'); return await query.find(); } /** * 创建项目空间产品 * @param projectId 项目ID * @param spaceData 空间数据 * @returns 创建的ProductSpace */ async createProductSpace( projectId: string, spaceData: Partial ): Promise { const Product = Parse.Object.extend('Product'); const product = new Product(); product.set('name', spaceData.name); product.set('type', spaceData.type); product.set('project', projectId); product.set('company', localStorage.getItem('company')); product.set('status', spaceData.status || 'not_started'); product.set('complexity', spaceData.complexity || 'medium'); product.set('order', spaceData.order || 0); return await product.save(); } /** * 更新项目空间产品 * @param spaceId 空间ID * @param updates 更新数据 */ async updateProductSpace( spaceId: string, updates: Partial ): Promise { const query = new Parse.Query('Product'); const product = await query.get(spaceId); Object.keys(updates).forEach(key => { product.set(key, (updates as any)[key]); }); await product.save(); } /** * 删除项目空间产品(软删除) * @param spaceId 空间ID */ async deleteProductSpace(spaceId: string): Promise { const query = new Parse.Query('Product'); const product = await query.get(spaceId); product.set('isDeleted', true); await product.save(); } /** * 计算空间进度 * @param spaceId 空间ID * @param processTypes 工序类型列表 * @returns 进度百分比 */ calculateProductProgress(spaceId: string, processTypes: string[]): number { // 计算该空间的完成进度 // 基于关联的ProjectDeliverable记录 // 返回 0-100 之间的百分比 } ``` ### 4.2 数据同步触发点 **订单分配阶段**: ```typescript // 1. 组件初始化时 ngOnInit() { await this.loadProjectSpaces(); } // 2. 项目类型改变时 onProjectTypeChange() { this.quotation.spaces = []; this.quotation.total = 0; } // 3. 空间模式改变时 async onProjectSpaceModeChange() { this.isMultiSpaceProject = this.projectInfo.spaceType === 'multi'; await this.regenerateQuotationFromSpaces(); } // 4. 添加/编辑/删除空间时 async addSpace() { ... } async editSpace(spaceId: string) { ... } async deleteSpace(spaceId: string) { ... } ``` **交付执行阶段**: ```typescript // 1. 组件初始化时 ngOnInit() { await this.syncProductsWithQuotation(); } // 2. 项目数据变化时 ngOnChanges(changes: SimpleChanges) { if (changes['project']) { await this.syncProductsWithQuotation(); } } ``` **设计师分配弹窗**: ```typescript // 1. 弹窗打开时 async ngOnInit() { if (this.loadRealSpaces && this.projectId) { await this.loadRealProjectSpaces(); } } // 2. 输入属性变化时 async ngOnChanges(changes: SimpleChanges) { if (changes['visible'] || changes['isVisible']) { if (currentVisible && !previousVisible && this.loadRealData) { await this.enrichMembersWithProjectAssignments(); } } } ``` ### 4.3 空间分配数据持久化 **设计师分配结果结构**: ```typescript export interface DesignerAssignmentResult { selectedDesigners: Designer[]; // 选中的设计师 primaryTeamId: string; // 主要项目组ID crossTeamCollaborators: Designer[]; // 跨组合作设计师 quotationAssignments: any[]; // 报价分配 spaceAssignments: DesignerSpaceAssignment[]; // ⭐ 空间分配 projectLeader?: Designer; // 项目负责人 } // 空间分配数据 spaceAssignments: [ { designerId: "designer-1", designerName: "张设计师", spaceIds: ["space-1", "space-2", "space-3"] }, { designerId: "designer-2", designerName: "李设计师", spaceIds: ["space-4", "space-5"] } ] ``` **保存位置**: - 临时存储:组件内存中的 `designerSpaceMap: Map` - 持久化:需要通过 `confirm` 事件传递给父组件,由父组件保存到 `ProjectTeam` 或 `Project` 表 --- ## 5. 建议的改进方案 ### 5.1 确保数据一致性 **问题**:报价修改后未同步Product表 **解决方案**: ```typescript // 在quotation-editor中添加同步方法 async saveQuotation(): Promise { // 1. 保存报价数据 await this.saveQuotationData(); // 2. ⭐ 同步Product表 await this.syncQuotationToProducts(); } private async syncQuotationToProducts(): Promise { const quotationSpaces = this.quotation.spaces; const existingProducts = await this.productSpaceService .getProjectProductSpaces(this.projectId); // 找出需要创建的空间 const existingNames = new Set( existingProducts.map(p => (p.name || '').trim().toLowerCase()) ); for (const space of quotationSpaces) { const spaceName = (space.name || '').trim().toLowerCase(); if (!existingNames.has(spaceName)) { await this.productSpaceService.createProductSpace( this.projectId, { name: space.name, type: 'other', status: 'not_started', complexity: 'medium' } ); } } } ``` ### 5.2 添加空间同步验证 **问题**:无法检测空间数据不一致 **解决方案**: ```typescript // 添加同步检查方法 async validateSpaceSync(): Promise<{ isConsistent: boolean; productCount: number; quotationCount: number; missingInProduct: string[]; missingInQuotation: string[]; }> { const products = await this.productSpaceService .getProjectProductSpaces(this.projectId); const quotationSpaces = this.project.get('data')?.quotation?.spaces || []; const productNames = new Set( products.map(p => (p.name || '').trim().toLowerCase()) ); const quotationNames = new Set( quotationSpaces.map(s => (s.name || '').trim().toLowerCase()) ); const missingInProduct = quotationSpaces .filter(s => !productNames.has((s.name || '').trim().toLowerCase())) .map(s => s.name); const missingInQuotation = products .filter(p => !quotationNames.has((p.name || '').trim().toLowerCase())) .map(p => p.name); return { isConsistent: missingInProduct.length === 0 && missingInQuotation.length === 0, productCount: products.length, quotationCount: quotationSpaces.length, missingInProduct, missingInQuotation }; } ``` ### 5.3 改进设计师空间分配的持久化 **问题**:空间分配结果未保存到数据库 **解决方案**: ```typescript // 在ProjectTeam表中添加空间分配字段 interface ProjectTeamData { // ... 其他字段 spaceAssignments?: { [designerId: string]: string[]; // 设计师ID -> 空间ID列表 }; } // 保存空间分配 async saveSpaceAssignments( projectId: string, assignments: DesignerSpaceAssignment[] ): Promise { const query = new Parse.Query('Project'); const project = await query.get(projectId); const data = project.get('data') || {}; data.designerSpaceAssignments = {}; for (const assignment of assignments) { data.designerSpaceAssignments[assignment.designerId] = assignment.spaceIds; } project.set('data', data); await project.save(); } // 加载空间分配 async loadSpaceAssignments(projectId: string): Promise> { const query = new Parse.Query('Project'); const project = await query.get(projectId); const data = project.get('data') || {}; const assignments = data.designerSpaceAssignments || {}; return new Map(Object.entries(assignments)); } ``` --- ## 6. 完整的数据流图 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 项目空间数据流 │ └─────────────────────────────────────────────────────────────────┘ 订单分配阶段 ├─ 输入:项目ID ├─ 加载Product表 → projectSpaces[] ├─ 同步报价数据 → 创建缺失的Product └─ 输出:quotation.spaces[] └─ 每个空间包含 spaceId(指向Product.id) ↓ 保存到 Project.data.quotation 报价明细 ├─ 读取:Project.data.quotation.spaces[] ├─ 显示:空间列表和价格明细 └─ 修改:编辑空间信息 └─ 问题:修改后未同步Product表 ⚠️ ↓ 项目进入交付执行阶段 交付执行 ├─ 加载Product表 → projectProducts[] ├─ 同步报价中的缺失空间 → 创建Product ├─ 去重处理 → 按名称去重 └─ 显示:交付物列表 ↓ 设计师分配 设计师分配弹窗 ├─ 加载Product表 → spaceScenes[] ├─ 转换格式 → SpaceScene[] ├─ 分配空间给设计师 → designerSpaceMap └─ 输出:DesignerAssignmentResult └─ spaceAssignments: DesignerSpaceAssignment[] └─ 问题:未保存到数据库 ⚠️ ``` --- ## 7. 字段存储位置总结表 | 数据项 | 存储表 | 字段路径 | 数据类型 | 备注 | |--------|--------|---------|---------|------| | 空间名称 | Product | name | string | 如"客厅"、"卧室" | | 空间类型 | Product | type | string | living_room, bedroom等 | | 空间面积 | Product | area | number | 单位:㎡ | | 空间状态 | Product | status | string | not_started, in_progress等 | | 空间复杂度 | Product | complexity | string | low, medium, high | | 空间预算 | Product | estimatedBudget | number | 预算金额 | | 空间排序 | Product | order | number | 排序顺序 | | 项目关联 | Product | project | Pointer | 指向Project表 | | 公司关联 | Product | company | Pointer | 指向Company表 | | 报价信息 | Project | data.quotation | Object | 包含spaces数组 | | 报价空间 | Project | data.quotation.spaces[] | Array | 每个空间包含spaceId | | 特殊需求 | Project | data.spaceSpecialRequirements | Object | 按spaceId存储 | | 空间分配 | Project | data.designerSpaceAssignments | Object | 设计师ID -> 空间ID[] | --- ## 8. 关键函数速查表 ### 8.1 ProductSpaceService ```typescript // 查询空间 getProjectProductSpaces(projectId: string): Promise // 创建空间 createProductSpace(projectId: string, spaceData: Partial): Promise // 更新空间 updateProductSpace(spaceId: string, updates: Partial): Promise // 删除空间 deleteProductSpace(spaceId: string): Promise // 计算进度 calculateProductProgress(spaceId: string, processTypes: string[]): number ``` ### 8.2 stage-order.component.ts ```typescript // 加载空间 loadProjectSpaces(): Promise // 从报价创建空间 createSpacesFromQuotation(quotationSpaces: any[]): Promise // 推断空间类型 inferSpaceType(spaceName: string): string // 计算空间预算 calculateSpaceRate(spaceData: any): number // 从空间生成报价 regenerateQuotationFromSpaces(): Promise // 更新空间占比 updateSpaceBreakdown(): void ``` ### 8.3 stage-delivery.component.ts ```typescript // 同步报价中的空间到Product表 syncProductsWithQuotation(): Promise ``` ### 8.4 designer-team-assignment-modal.component.ts ```typescript // 加载真实空间数据 loadRealProjectSpaces(): Promise // 获取设计师分配的空间 getDesignerSpaces(designerId: string): SpaceScene[] // 获取设计师空间文本 getDesignerSpacesText(designerId: string): string // 检查空间是否被选中 isSpaceSelected(designerId: string, spaceId: string): boolean // 切换空间选择 toggleSpaceSelection(designerId: string, spaceId: string): void ``` --- ## 9. 常见问题排查 ### Q1: 为什么设计师分配弹窗显示的空间数量与报价不一致? **原因**: 1. 报价中添加了新空间,但未同步到Product表 2. Product表中的空间被删除,但报价未更新 **解决**: ```typescript // 在保存报价时调用 await this.syncQuotationToProducts(); // 或在交付执行时自动同步 await this.syncProductsWithQuotation(); ``` ### Q2: 如何确保空间数据的一致性? **方案**: ```typescript // 定期验证 const validation = await this.validateSpaceSync(); if (!validation.isConsistent) { console.warn('空间数据不一致:', validation); // 自动修复 await this.syncQuotationToProducts(); } ``` ### Q3: 设计师空间分配如何持久化? **方案**: ```typescript // 在确认分配时保存 async onConfirmAssignment(result: DesignerAssignmentResult) { // 保存空间分配 await this.saveSpaceAssignments(this.projectId, result.spaceAssignments); } ``` --- ## 10. 总结 ### 核心要点: 1. ✅ **Product表**是空间数据的唯一真实来源 2. ✅ **Project.data.quotation**存储报价信息 3. ✅ 各阶段通过**ProductSpaceService**统一查询空间 4. ⚠️ **报价修改**后需要同步Product表 5. ⚠️ **设计师分配**结果需要持久化存储 ### 改进优先级: 1. **高**:添加报价保存时的Product同步 2. **高**:实现设计师空间分配的持久化 3. **中**:添加空间数据一致性验证 4. **中**:完善错误处理和日志 ### 相关文件: - `src/modules/project/services/product-space.service.ts` - `src/modules/project/pages/project-detail/stages/stage-order.component.ts` - `src/modules/project/pages/project-detail/stages/stage-delivery.component.ts` - `src/app/pages/designer/project-detail/components/designer-team-assignment-modal/designer-team-assignment-modal.component.ts`