您报告的问题:删除订单分配阶段的空间后,其他阶段(需求确认、交付执行、设计师分配)仍显示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() ✅ │
│ └─ 从统一存储读取空间 ✅ │
│ │
└─────────────────────────────────────────────────────────────┘
// 用户点击"添加产品"
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()!
// 用户点击"删除产品"
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()!
现状:
QuotationEditorComponent 正确操作 Product表(创建、删除)QuotationEditorComponent 正确保存 Project.data.quotationQuotationEditorComponent 没有调用 ProductSpaceService.saveUnifiedSpaceData()QuotationEditorComponent 没有更新 Project.data.unifiedSpaces后果:
订单分配阶段删除空间:
✅ Product表记录被删除
✅ Project.data.quotation.spaces 更新
❌ Project.data.unifiedSpaces 未更新 ← 这是关键问题!
其他阶段读取空间:
✅ 调用 ProductSpaceService.getUnifiedSpaceData()
❌ 读取到的是旧的 Project.data.unifiedSpaces(9个空间)
❌ 显示不一致!
QuotationEditorComponent
↓ (只保存到)
Project.data.quotation.spaces
↓ (没有同步到)
Project.data.unifiedSpaces ← 断裂点!
↓ (其他阶段读取)
其他阶段显示旧数据
saveQuotationToProject() 方法// 📁 quotation-editor.component.ts
import { ProductSpaceService } from '../services/product-space.service';
export class QuotationEditorComponent {
// 注入 ProductSpaceService
constructor(
private productSpaceService: ProductSpaceService
) {}
/**
* 保存报价到项目 - 集成统一空间管理
*/
private async saveQuotationToProject(): Promise<void> {
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() 方法/**
* 删除产品 - 集成统一空间管理
*/
async deleteProduct(productId: string): Promise<void> {
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('删除失败,请重试');
}
}
productsChange 事件// 📁 stage-order.component.html
<app-quotation-editor
[project]="project"
[canEdit]="canEdit"
(productsChange)="onProductsChange($event)"
(quotationChange)="onQuotationChange($event)"
></app-quotation-editor>
// 📁 stage-order.component.ts
/**
* 监听报价管理器的产品变更
*/
async onProductsChange(products: any[]): Promise<void> {
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<void> {
console.log('💰 [订单分配] 报价数据已更新');
this.quotation = quotation;
// 更新项目数据
const data = this.project!.get('data') || {};
data.quotation = quotation;
this.project!.set('data', data);
// 注意:这里不需要再次调用 saveUnifiedSpaceData
// 因为 QuotationEditorComponent 已经在 saveQuotationToProject 中调用了
}
quotation-editor.component.ts ⭐ 核心修改
ProductSpaceServicesaveQuotationToProject() 方法deleteProduct() 方法generateQuotationFromProducts() 方法stage-order.component.ts
onProductsChange() 方法onQuotationChange() 方法stage-order.component.html
(productsChange)="onProductsChange($event)"(quotationChange)="onQuotationChange($event)"检查控制台日志:
💾 [报价管理器] 开始保存报价数据...
🔄 [统一空间管理] 开始保存统一空间数据,项目ID: xxx, 空间数: 6
✅ [统一空间管理] 统一空间数据保存完成
✅ [报价管理器] 报价数据已保存,空间数: 6
切换到需求确认阶段,验证显示6个空间 ✅
切换到交付执行阶段,验证显示6个空间 ✅
打开设计师分配弹窗,验证显示6个空间 ✅
检查控制台日志:
🗑️ [报价管理器] 开始删除产品: xxx
✓ Product表记录已删除
💾 [报价管理器] 开始保存报价数据...
🔄 [统一空间管理] 开始保存统一空间数据,项目ID: xxx, 空间数: 5
✅ [统一空间管理] 统一空间数据保存完成
✅ [报价管理器] 产品删除完成,剩余 5 个产品
切换到需求确认阶段,验证显示5个空间 ✅
切换到交付执行阶段,验证显示5个空间 ✅
打开设计师分配弹窗,验证显示5个空间 ✅
检查数据库:
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️⃣ Product表(物理存储)
2️⃣ Project.data.quotation.spaces(报价数据,向后兼容)
3️⃣ Project.data.unifiedSpaces(统一空间数据,单一数据源) ⭐
Project.data.unifiedSpaces 是所有阶段的唯一数据来源saveUnifiedSpaceData() 会自动同步到 Product表 和 quotation.spacesquotation.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 ⭐ |
// 在浏览器控制台执行
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');
ProductSpaceServicesaveQuotationToProject() 方法(添加统一空间同步)deleteProduct() 调用 saveQuotationToProject()onProductsChange() 事件处理如果现有项目的数据不一致,运行修复脚本:
// 修复单个项目
await productSpaceService.forceRepairSpaceData('项目ID');
// 或在浏览器控制台执行
const service = // 获取ProductSpaceService实例
await service.forceRepairSpaceData('项目ID');
修改完成后:
修复完成时间: ___________
测试人员: ___________
测试结果: ✅ 通过 / ❌ 未通过