# 报价空间管理器数据同步完整分析 ## 🎯 核心问题 您报告的问题:**删除订单分配阶段的空间后,其他阶段(需求确认、交付执行、设计师分配)仍显示9个空间,数据未同步。** ## 📊 数据流分析 ### 当前数据存储架构 ``` ┌─────────────────────────────────────────────────────────────┐ │ 数据存储层 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ ┌──────────────────┐ │ │ │ Product 表 │ │ Project 表 │ │ │ │ (空间产品) │ │ (项目数据) │ │ │ ├─────────────────┤ ├──────────────────┤ │ │ │ • productName │ │ data.quotation │ │ │ │ • productType │ │ └─ spaces[] │ │ │ │ • space {...} │ │ └─ total │ │ │ │ • quotation{..} │ │ │ │ │ │ • requirements │ │ data.unifiedSpaces[] ✅ │ │ │ • status │ │ (统一空间数据) │ │ │ │ • profile │ │ │ │ │ └─────────────────┘ └──────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 业务逻辑层 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ QuotationEditorComponent (报价空间管理器) │ │ │ ├──────────────────────────────────────────────────────┤ │ │ │ • loadProjectProducts() - 从Product表加载 │ │ │ │ • createProduct() - 创建Product记录 │ │ │ │ • deleteProduct() - 删除Product记录 │ │ │ │ • generateQuotationFromProducts() - 生成报价 │ │ │ │ • saveQuotationToProject() - 保存到Project.data │ │ │ └──────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ ProductSpaceService (统一空间管理) │ │ │ ├──────────────────────────────────────────────────────┤ │ │ │ • saveUnifiedSpaceData() - 保存统一空间数据 ✅ │ │ │ │ • getUnifiedSpaceData() - 获取统一空间数据 ✅ │ │ │ │ • syncUnifiedSpacesToProducts() - 同步到Product ✅ │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 展示层 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 订单分配阶段 (stage-order.component.ts) │ │ ├─ 使用 QuotationEditorComponent │ │ ├─ 调用 ProductSpaceService.saveUnifiedSpaceData() ✅ │ │ └─ 删除空间后同步到统一存储 ✅ │ │ │ │ 需求确认阶段 (stage-requirements.component.ts) │ │ ├─ 调用 ProductSpaceService.getUnifiedSpaceData() ✅ │ │ └─ 从统一存储读取空间 ✅ │ │ │ │ 交付执行阶段 (stage-delivery.component.ts) │ │ ├─ 调用 ProductSpaceService.getUnifiedSpaceData() ✅ │ │ └─ 从统一存储读取空间 ✅ │ │ │ │ 设计师分配弹窗 (designer-team-assignment-modal.component) │ │ ├─ 调用 ProductSpaceService.getUnifiedSpaceData() ✅ │ │ └─ 从统一存储读取空间 ✅ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 🔍 QuotationEditorComponent 数据同步逻辑 ### 1. 创建空间产品流程 ```typescript // 用户点击"添加产品" addProduct(productName) ↓ // 创建Product记录 createProduct(productName) ↓ new Parse.Object('Product') product.set('productName', productName) product.set('space', { spaceName, area, spaceType, ... }) product.set('quotation', { price, basePrice, ... }) product.set('requirements', { ... }) await product.save() // ✅ 保存到Product表 ↓ // 重新加载产品列表 loadProjectProducts() ↓ const productQuery = new Parse.Query('Product') productQuery.equalTo('project', this.project.toPointer()) this.products = await productQuery.find() // ✅ 从Product表查询 ↓ // 生成报价 generateQuotationFromProducts() ↓ for (const product of this.products) { this.quotation.spaces.push({ name: product.get('productName'), productId: product.id, // ✅ 关联Product ID processes: {...}, subtotal: ... }) } ↓ // 保存报价到Project saveQuotationToProject() ↓ const data = this.project.get('data') || {} data.quotation = this.quotation // ✅ 保存到Project.data.quotation this.project.set('data', data) await this.project.save() ``` **❌ 问题**:`saveQuotationToProject()` **没有调用** `ProductSpaceService.saveUnifiedSpaceData()`! --- ### 2. 删除空间产品流程 ```typescript // 用户点击"删除产品" deleteProduct(productId) ↓ const product = this.products.find(p => p.id === productId) await product.destroy() // ✅ 从Product表删除 ↓ // 更新本地产品列表 this.products = this.products.filter(p => p.id !== productId) ↓ // 更新报价spaces this.quotation.spaces = this.quotation.spaces.filter( (s: any) => s.productId !== productId ) ↓ // 重新计算总价 this.calculateTotal() this.updateProductBreakdown() ↓ // 保存报价 await this.saveQuotationToProject() ↓ const data = this.project.get('data') || {} data.quotation = this.quotation // ✅ 保存到Project.data.quotation this.project.set('data', data) await this.project.save() ``` **❌ 问题**:`deleteProduct()` **没有调用** `ProductSpaceService.saveUnifiedSpaceData()`! --- ## ❌ 根本原因分析 ### 问题1:QuotationEditorComponent 未使用统一空间管理 **现状**: - ✅ `QuotationEditorComponent` 正确操作 **Product表**(创建、删除) - ✅ `QuotationEditorComponent` 正确保存 **Project.data.quotation** - ❌ `QuotationEditorComponent` **没有调用** `ProductSpaceService.saveUnifiedSpaceData()` - ❌ `QuotationEditorComponent` **没有更新** `Project.data.unifiedSpaces` **后果**: ``` 订单分配阶段删除空间: ✅ Product表记录被删除 ✅ Project.data.quotation.spaces 更新 ❌ Project.data.unifiedSpaces 未更新 ← 这是关键问题! 其他阶段读取空间: ✅ 调用 ProductSpaceService.getUnifiedSpaceData() ❌ 读取到的是旧的 Project.data.unifiedSpaces(9个空间) ❌ 显示不一致! ``` ### 问题2:数据同步链路断裂 ``` QuotationEditorComponent ↓ (只保存到) Project.data.quotation.spaces ↓ (没有同步到) Project.data.unifiedSpaces ← 断裂点! ↓ (其他阶段读取) 其他阶段显示旧数据 ``` --- ## ✅ 解决方案 ### 方案1:在 QuotationEditorComponent 中集成统一空间管理 #### 修改 `saveQuotationToProject()` 方法 ```typescript // 📁 quotation-editor.component.ts import { ProductSpaceService } from '../services/product-space.service'; export class QuotationEditorComponent { // 注入 ProductSpaceService constructor( private productSpaceService: ProductSpaceService ) {} /** * 保存报价到项目 - 集成统一空间管理 */ private async saveQuotationToProject(): Promise { if (!this.project) return; try { console.log('💾 [报价管理器] 开始保存报价数据...'); // 1️⃣ 保存到 Project.data.quotation(保持向后兼容) const data = this.project.get('data') || {}; data.quotation = this.quotation; this.project.set('data', data); // 2️⃣ 🔥 关键:同步到统一空间管理 const unifiedSpaces = this.products.map((product, index) => { const quotation = product.get('quotation') || {}; const space = product.get('space') || {}; const requirements = product.get('requirements') || {}; const profile = product.get('profile'); // 从报价数据中查找对应的空间信息 const quotationSpace = this.quotation.spaces.find( (s: any) => s.productId === product.id ); return { id: product.id, name: product.get('productName'), type: product.get('productType') || 'other', area: space.area || 0, priority: space.priority || 5, status: product.get('status') || 'not_started', complexity: space.complexity || 'medium', estimatedBudget: quotation.price || 0, order: index, // 报价信息 quotation: { price: quotation.price || 0, processes: quotationSpace?.processes || {}, subtotal: quotationSpace?.subtotal || 0 }, // 需求信息 requirements: requirements, // 设计师分配 designerId: profile?.id || null, // 进度信息 progress: [], // 时间戳 createdAt: product.createdAt?.toISOString() || new Date().toISOString(), updatedAt: new Date().toISOString() }; }); // 调用统一空间管理服务保存 await this.productSpaceService.saveUnifiedSpaceData( this.project.id || '', unifiedSpaces ); console.log(`✅ [报价管理器] 报价数据已保存,空间数: ${unifiedSpaces.length}`); this.quotationChange.emit(this.quotation); } catch (error) { console.error('❌ [报价管理器] 保存报价失败:', error); throw error; } } } ``` #### 修改 `deleteProduct()` 方法 ```typescript /** * 删除产品 - 集成统一空间管理 */ async deleteProduct(productId: string): Promise { if (!await window?.fmode?.confirm('确定要删除这个产品吗?相关数据将被清除。')) return; try { console.log(`🗑️ [报价管理器] 开始删除产品: ${productId}`); // 1️⃣ 从Product表删除 const product = this.products.find(p => p.id === productId); if (product) { await product.destroy(); console.log(` ✓ Product表记录已删除`); } // 2️⃣ 更新本地产品列表 this.products = this.products.filter(p => p.id !== productId); this.productsChange.emit(this.products); // 3️⃣ 更新报价spaces this.quotation.spaces = this.quotation.spaces.filter( (s: any) => s.productId !== productId ); // 4️⃣ 重新计算总价 this.calculateTotal(); this.updateProductBreakdown(); // 5️⃣ 🔥 关键:保存到统一空间管理(会自动同步) await this.saveQuotationToProject(); console.log(`✅ [报价管理器] 产品删除完成,剩余 ${this.products.length} 个产品`); window?.fmode?.alert('删除成功'); } catch (error) { console.error('❌ [报价管理器] 删除产品失败:', error); window?.fmode?.alert('删除失败,请重试'); } } ``` --- ### 方案2:在 stage-order.component.ts 中监听 QuotationEditorComponent 的变更 #### 监听 `productsChange` 事件 ```typescript // 📁 stage-order.component.html ``` ```typescript // 📁 stage-order.component.ts /** * 监听报价管理器的产品变更 */ async onProductsChange(products: any[]): Promise { console.log('📦 [订单分配] 产品列表已更新:', products.length, '个产品'); // 🔥 关键:同步到统一空间管理 const unifiedSpaces = products.map((product, index) => { const quotation = product.get('quotation') || {}; const space = product.get('space') || {}; const requirements = product.get('requirements') || {}; const profile = product.get('profile'); return { id: product.id, name: product.get('productName'), type: product.get('productType') || 'other', area: space.area || 0, priority: space.priority || 5, status: product.get('status') || 'not_started', complexity: space.complexity || 'medium', estimatedBudget: quotation.price || 0, order: index, quotation: { price: quotation.price || 0, processes: {}, subtotal: quotation.price || 0 }, requirements: requirements, designerId: profile?.id || null, progress: [], createdAt: product.createdAt?.toISOString() || new Date().toISOString(), updatedAt: new Date().toISOString() }; }); await this.productSpaceService.saveUnifiedSpaceData( this.project!.id || '', unifiedSpaces ); console.log(`✅ [订单分配] 统一空间数据已同步,空间数: ${unifiedSpaces.length}`); } /** * 监听报价管理器的报价变更 */ async onQuotationChange(quotation: any): Promise { console.log('💰 [订单分配] 报价数据已更新'); this.quotation = quotation; // 更新项目数据 const data = this.project!.get('data') || {}; data.quotation = quotation; this.project!.set('data', data); // 注意:这里不需要再次调用 saveUnifiedSpaceData // 因为 QuotationEditorComponent 已经在 saveQuotationToProject 中调用了 } ``` --- ## 📋 修改文件清单 ### 必须修改的文件 1. **`quotation-editor.component.ts`** ⭐ 核心修改 - 注入 `ProductSpaceService` - 修改 `saveQuotationToProject()` 方法 - 修改 `deleteProduct()` 方法 - 修改 `generateQuotationFromProducts()` 方法 2. **`stage-order.component.ts`** - 添加 `onProductsChange()` 方法 - 添加 `onQuotationChange()` 方法 - 在模板中绑定事件 3. **`stage-order.component.html`** - 添加事件绑定:`(productsChange)="onProductsChange($event)"` - 添加事件绑定:`(quotationChange)="onQuotationChange($event)"` --- ## 🧪 验证步骤 ### 测试场景1:添加空间产品 1. 进入订单分配阶段 2. 点击"添加产品",添加一个新空间(例如:"书房") 3. 检查控制台日志: ``` 💾 [报价管理器] 开始保存报价数据... 🔄 [统一空间管理] 开始保存统一空间数据,项目ID: xxx, 空间数: 6 ✅ [统一空间管理] 统一空间数据保存完成 ✅ [报价管理器] 报价数据已保存,空间数: 6 ``` 4. 切换到需求确认阶段,验证显示6个空间 ✅ 5. 切换到交付执行阶段,验证显示6个空间 ✅ 6. 打开设计师分配弹窗,验证显示6个空间 ✅ ### 测试场景2:删除空间产品 1. 进入订单分配阶段 2. 删除一个空间(例如:"书房") 3. 检查控制台日志: ``` 🗑️ [报价管理器] 开始删除产品: xxx ✓ Product表记录已删除 💾 [报价管理器] 开始保存报价数据... 🔄 [统一空间管理] 开始保存统一空间数据,项目ID: xxx, 空间数: 5 ✅ [统一空间管理] 统一空间数据保存完成 ✅ [报价管理器] 产品删除完成,剩余 5 个产品 ``` 4. 切换到需求确认阶段,验证显示5个空间 ✅ 5. 切换到交付执行阶段,验证显示5个空间 ✅ 6. 打开设计师分配弹窗,验证显示5个空间 ✅ ### 测试场景3:刷新页面验证持久化 1. 完成添加/删除操作后 2. 刷新页面(F5) 3. 进入各个阶段验证空间数量一致 4. 检查数据库: ```javascript const Parse = window.Parse || FmodeParse.with('nova'); const query = new Parse.Query('Project'); const project = await query.get('项目ID'); const data = project.get('data'); console.log('unifiedSpaces数量:', data.unifiedSpaces?.length); console.log('quotation.spaces数量:', data.quotation?.spaces?.length); const productQuery = new Parse.Query('Product'); productQuery.equalTo('project', project.toPointer()); const products = await productQuery.find(); console.log('Product表数量:', products.length); ``` **预期结果**:三个数据源的数量完全一致 ✅ --- ## 🎯 关键要点 ### 1. 数据同步的黄金法则 ``` 任何对空间的增删改操作,必须同时更新三个地方: 1️⃣ Product表(物理存储) 2️⃣ Project.data.quotation.spaces(报价数据,向后兼容) 3️⃣ Project.data.unifiedSpaces(统一空间数据,单一数据源) ⭐ ``` ### 2. 统一空间管理的核心价值 - **单一数据源**:`Project.data.unifiedSpaces` 是所有阶段的唯一数据来源 - **自动同步**:`saveUnifiedSpaceData()` 会自动同步到 Product表 和 `quotation.spaces` - **一致性保证**:所有阶段读取同一数据源,确保显示一致 ### 3. QuotationEditorComponent 的职责 - ✅ 管理 Product表的 CRUD 操作 - ✅ 生成报价数据(`quotation.spaces`) - ✅ **新增**:调用 `ProductSpaceService.saveUnifiedSpaceData()` 同步数据 --- ## 📊 数据一致性检查清单 ### 操作后必须验证的数据 | 数据源 | 字段路径 | 验证方法 | |--------|---------|---------| | Product表 | `Product.productName` | `Parse.Query('Product').find()` | | Project.data | `data.quotation.spaces[]` | `project.get('data').quotation.spaces` | | Project.data | `data.unifiedSpaces[]` | `project.get('data').unifiedSpaces` ⭐ | ### 一致性验证脚本 ```javascript // 在浏览器控制台执行 async function checkSpaceConsistency(projectId) { const Parse = window.Parse || FmodeParse.with('nova'); // 1. 查询Project const projectQuery = new Parse.Query('Project'); const project = await projectQuery.get(projectId); const data = project.get('data') || {}; // 2. 查询Product表 const productQuery = new Parse.Query('Product'); productQuery.equalTo('project', project.toPointer()); productQuery.notEqualTo('isDeleted', true); const products = await productQuery.find(); // 3. 对比数量 const unifiedCount = data.unifiedSpaces?.length || 0; const quotationCount = data.quotation?.spaces?.length || 0; const productCount = products.length; console.log('📊 空间数量一致性检查:'); console.log(` unifiedSpaces: ${unifiedCount}`); console.log(` quotation.spaces: ${quotationCount}`); console.log(` Product表: ${productCount}`); if (unifiedCount === quotationCount && quotationCount === productCount) { console.log('✅ 数据一致!'); return true; } else { console.error('❌ 数据不一致!需要修复。'); return false; } } // 使用方法 await checkSpaceConsistency('你的项目ID'); ``` --- ## 🚀 实施步骤 ### 第1步:修改 QuotationEditorComponent 1. 注入 `ProductSpaceService` 2. 修改 `saveQuotationToProject()` 方法(添加统一空间同步) 3. 确保 `deleteProduct()` 调用 `saveQuotationToProject()` ### 第2步:修改 stage-order.component 1. 添加 `onProductsChange()` 事件处理 2. 在模板中绑定事件 ### 第3步:测试验证 1. 清除浏览器缓存 2. 刷新页面 3. 执行测试场景1、2、3 4. 运行一致性检查脚本 ### 第4步:数据修复(如果需要) 如果现有项目的数据不一致,运行修复脚本: ```javascript // 修复单个项目 await productSpaceService.forceRepairSpaceData('项目ID'); // 或在浏览器控制台执行 const service = // 获取ProductSpaceService实例 await service.forceRepairSpaceData('项目ID'); ``` --- ## ✅ 预期结果 修改完成后: 1. ✅ 订单分配阶段添加/删除空间 → 立即同步到统一存储 2. ✅ 需求确认阶段 → 显示正确的空间数量 3. ✅ 交付执行阶段 → 显示正确的空间数量 4. ✅ 设计师分配弹窗 → 显示正确的空间数量 5. ✅ 刷新页面后 → 所有阶段数据保持一致 6. ✅ 数据库三个数据源 → 数量完全一致 --- **修复完成时间**: ___________ **测试人员**: ___________ **测试结果**: ✅ 通过 / ❌ 未通过