用户点击"生成报价"按钮后,系统自动生成9个默认空间(客厅、餐厅、主卧、次卧、儿童房、书房、厨房、卫生间、阳台),而不是一开始默认1-2个或没有,让用户自己添加。
系统中存在两个不同的空间初始化路径,导致行为不一致:
文件:product-space.service.ts 第674-780行
async createInitialSpaces(projectId: string, projectType: '家装' | '工装' = '家装'): Promise<any[]> {
if (projectType === '家装') {
// ✅ 只创建2个初始空间:客厅 + 主卧
initialSpaces = [
{ name: '客厅', type: 'living_room', ... },
{ name: '主卧', type: 'bedroom', ... }
];
} else {
// ✅ 工装只创建1个主要空间
initialSpaces = [
{ name: '主要空间', type: 'other', ... }
];
}
}
特点:
文件:quotation-editor.component.ts 第103-105行 + 第325-340行
// ❌ 预设场景列表 - 包含所有9个房间
presetScenes: { [key: string]: string[] } = {
'家装': ['客厅', '餐厅', '主卧', '次卧', '儿童房', '书房', '厨房', '卫生间', '阳台'], // 9个!
'工装': ['大堂', '接待区', '会议室', '办公区', '休息区', '展示区', '洽谈区'], // 7个!
'建筑类': ['门头', '小型单体', '大型单体', '鸟瞰'] // 4个!
};
// 第325-340行:创建默认产品
private async createDefaultProducts(): Promise<void> {
const defaultRooms = this.getDefaultRoomsForProjectType(); // 获取9个房间
for (const roomName of defaultRooms) {
await this.createProduct(roomName); // ❌ 循环创建每个房间
}
}
触发条件:(第310-311行)
if (this.products.length === 0) {
await this.createDefaultProducts(); // ❌ Product表为空时自动创建9个空间
}
特点:
订单分配页面加载
↓
stage-order.loadProjectSpaces() (第704行)
↓
ProductSpaceService.getUnifiedSpaceData() (第712行)
↓
【分支1】如果有统一空间数据
→ 直接加载 ✅
↓
【分支2】如果没有,尝试从Product表加载 (第738行)
→ ProductSpaceService.getProjectProductSpaces()
↓
【分支3】如果Product表也没有
↓
3.1 如果有报价数据
→ 从报价数据创建空间 (第745行)
→ createSpacesFromQuotation()
↓
3.2 如果没有报价数据 (第748-752行)
→ ✅ 调用 ProductSpaceService.createInitialSpaces()
→ 只创建1-2个初始空间
QuotationEditor组件初始化
↓
loadProjectProducts() (第272行)
↓
查询Product表
↓
【分支1】如果Product表有数据
→ 加载并去重 ✅
↓
【分支2】如果Product表为空 (第310-311行)
→ ❌ 调用 createDefaultProducts()
→ ❌ 根据 presetScenes 创建9个空间
→ ❌ 循环创建Product记录
用户进入订单分配阶段
↓
没有空间数据
↓
stage-order调用createInitialSpaces() (✅ 创建2个空间)
↓
用户点击"生成报价"
↓
QuotationEditor.generateQuotationFromProducts() (第541行)
↓
loadProjectProducts() 检测到Product表有数据 (✅ 只有2个空间)
↓
正常生成2个空间的报价 ✅
但如果用户直接点击"生成报价"而没有先保存空间:
用户进入订单分配阶段
↓
用户直接点击"生成报价"(没有先保存空间)
↓
QuotationEditor.loadProjectProducts()
↓
Product表为空
↓
❌ 调用 createDefaultProducts()
↓
❌ 从 presetScenes['家装'] 获取9个房间
↓
❌ 循环创建9个Product记录
↓
❌ 生成9个空间的报价
{
objectId: string; // 产品/空间ID
project: Pointer<Project>; // 关联的项目
productName: string; // 空间名称,例如:"客厅"
productType: string; // 空间类型,例如:"living_room"
status: string; // 空间状态:"not_started" | "in_progress" | "completed"
// 空间信息
space: {
spaceName: string; // 空间名称(冗余)
area: number; // 面积(平方米)
priority: number; // 优先级(1-10)
complexity: string; // 复杂度:"low" | "medium" | "high"
styleLevel: string; // 风格等级
businessType: string; // 业务类型
architectureType: string; // 建筑类型
},
// 报价信息
quotation: {
price: number; // 单价
currency: string; // 货币单位:"CNY"
breakdown: object; // 价格明细
status: string; // 报价状态
processes: object; // 工序分配
subtotal: number; // 小计
},
// 需求信息
requirements: object; // 空间需求
// 设计师分配
profile: Pointer<Profile>; // 分配的设计师
// 进度信息
data: {
progress: Array; // 进度数组
}
}
{
spaces: [
{
name: string; // 空间名称,例如:"客厅"
spaceId: string; // 关联的Product ID
productId: string; // 同上(别名)
// 工序分配(新版:按公司分配方式)
processes: {
modeling: { // 建模阶段(10%)
enabled: boolean,
amount: number,
percentage: number,
description: string
},
decoration: { // 软装渲染阶段(40%)
enabled: boolean,
amount: number,
percentage: number,
description: string
},
company: { // 公司运营与利润(50%)
enabled: boolean,
amount: number,
percentage: number,
description: string
}
},
subtotal: number // 空间小计
}
],
total: number, // 报价总额
spaceBreakdown: [ // 空间占比明细
{
spaceName: string,
spaceId: string,
amount: number,
percentage: number
}
],
generatedAt: Date, // 生成时间
validUntil: Date // 有效期至
}
{
unifiedSpaces: [
{
id: string, // 空间ID(对应Product.objectId)
name: string, // 空间名称
type: string, // 空间类型
area: number, // 面积
priority: number, // 优先级
status: string, // 状态
complexity: string, // 复杂度
estimatedBudget: number, // 预估预算
order: number, // 排序
quotation: { // 报价信息(与quotation.spaces同步)
price: number,
processes: object,
subtotal: number
},
requirements: object, // 需求信息
designerId: string | null, // 分配的设计师ID
progress: Array, // 进度
createdAt: string, // 创建时间
updatedAt: string // 更新时间
}
]
}
目标:让用户手动添加空间,而不是自动生成
修改文件:quotation-editor.component.ts 第103-107行
// ❌ 旧代码
presetScenes: { [key: string]: string[] } = {
'家装': ['客厅', '餐厅', '主卧', '次卧', '儿童房', '书房', '厨房', '卫生间', '阳台'],
'工装': ['大堂', '接待区', '会议室', '办公区', '休息区', '展示区', '洽谈区'],
'建筑类': ['门头', '小型单体', '大型单体', '鸟瞰']
};
// ✅ 新代码:改为空数组或只保留1-2个默认空间
presetScenes: { [key: string]: string[] } = {
'家装': [], // 不自动创建,让用户添加
'工装': [],
'建筑类': []
};
// 或者只保留1-2个默认空间
presetScenes: { [key: string]: string[] } = {
'家装': ['客厅', '主卧'], // 只创建2个初始空间
'工装': ['主要空间'], // 只创建1个初始空间
'建筑类': ['鸟瞰']
};
优点:
缺点:
目标:不自动创建Product记录,依赖订单分配阶段的初始化
修改文件:quotation-editor.component.ts 第310-316行
// ❌ 旧代码
if (this.products.length === 0) {
await this.createDefaultProducts(); // 自动创建9个空间
} else {
await this.loadProductCollaborations();
}
// ✅ 新代码:不自动创建,提示用户添加
if (this.products.length === 0) {
console.warn('⚠️ 没有找到空间数据,请先在订单分配阶段创建空间');
// 不自动创建,等待用户手动添加
// await this.createDefaultProducts(); // ❌ 注释掉
} else {
await this.loadProductCollaborations();
}
优点:
缺点:
目标:复用 ProductSpaceService.createInitialSpaces() 的逻辑
修改文件:quotation-editor.component.ts 第310-316行
// ✅ 新代码:调用统一的初始化方法
if (this.products.length === 0) {
console.log('🏠 没有找到空间数据,创建初始空间...');
// 调用ProductSpaceService的初始化方法(只创建1-2个空间)
const initialSpaces = await this.productSpaceService.createInitialSpaces(
this.project!.id || '',
this.projectInfo.projectType as '家装' | '工装'
);
console.log(`✅ 已创建 ${initialSpaces.length} 个初始空间`);
// 重新加载产品列表
await this.loadProjectProducts();
} else {
await this.loadProductCollaborations();
}
需要注入依赖:(第1行附近)
import { ProductSpaceService } from '../services/product-space.service';
constructor(
private cdr: ChangeDetectorRef,
private productSpaceService: ProductSpaceService // 注入
) {}
优点:
缺点:
目标:在自动创建前询问用户
修改文件:quotation-editor.component.ts 第310-316行
if (this.products.length === 0) {
// 询问用户是否要创建默认空间
const confirmed = await window?.fmode?.confirm(
'检测到没有空间数据,是否创建默认空间(客厅+主卧)?\n' +
'您也可以点击"取消"后手动添加空间。'
);
if (confirmed) {
// 调用统一的初始化方法(只创建2个空间)
await this.productSpaceService.createInitialSpaces(
this.project!.id || '',
this.projectInfo.projectType as '家装' | '工装'
);
await this.loadProjectProducts();
} else {
console.log('用户取消了创建默认空间,等待手动添加');
}
} else {
await this.loadProductCollaborations();
}
优点:
修改 presetScenes 为少量默认值(方案1)
presetScenes: { [key: string]: string[] } = {
'家装': ['客厅', '主卧'], // 只保留2个
'工装': ['主要空间'], // 只保留1个
'建筑类': ['鸟瞰']
};
修改 createDefaultProducts 调用统一方法(方案3)
if (this.products.length === 0) {
// 调用统一的初始化方法
await this.productSpaceService.createInitialSpaces(
this.project!.id || '',
this.projectInfo.projectType as '家装' | '工装'
);
await this.loadProjectProducts();
}
可选:添加用户确认对话框(方案4)
效果:
用户在报价编辑器中修改空间
↓
QuotationEditor.generateQuotationFromProducts() (第541行)
↓
生成 quotation.spaces 数据
↓
QuotationEditor.saveQuotationToProject() (第664行)
↓
【同步1】保存到 Project.data.quotation ✅
↓
【同步2】保存到 Project.data.unifiedSpaces ✅ (第672-702行)
↓
【同步3】同步到 Product 表 (需要调用 syncUnifiedSpacesToProducts)
private async saveQuotationToProject(): Promise<void> {
const data = this.project.get('data') || {};
// 保存报价数据
data.quotation = this.quotation;
// 🔥 同步到统一空间存储
data.unifiedSpaces = this.products.map((product, index) => {
const quotationSpace = this.quotation.spaces.find(s => s.productId === product.id);
return {
id: product.id,
name: product.get('productName'),
quotation: {
price: quotation.price,
processes: quotationSpace?.processes || {},
subtotal: quotationSpace?.subtotal || 0
},
// ... 其他字段
};
});
await this.project.save();
this.quotationChange.emit(this.quotation);
}
async createProduct(productName: string) {
// 创建Product记录
const Product = Parse.Object.extend('Product');
const product = new Product();
product.set('productName', productName);
product.set('project', this.project.toPointer());
// ...设置其他字段
await product.save();
// 重新加载产品列表
await this.loadProjectProducts();
// 🔥 需要同步到 unifiedSpaces
// TODO: 调用 saveQuotationToProject() 或 ProductSpaceService.saveUnifiedSpaceData()
}
async deleteProduct(productId: string) {
// 删除Product记录
const product = this.products.find(p => p.id === productId);
await product.destroy();
// 重新加载产品列表
await this.loadProjectProducts();
// 🔥 需要同步到 unifiedSpaces
// TODO: 调用 saveQuotationToProject()
}
检查控制台日志:
🔍 [报价编辑器] Product表查询结果: 0 条记录
创建默认产品...
✅ 已创建产品: 客厅
✅ 已创建产品: 餐厅
✅ 已创建产品: 主卧
✅ 已创建产品: 次卧
✅ 已创建产品: 儿童房
✅ 已创建产品: 书房
✅ 已创建产品: 厨房
✅ 已创建产品: 卫生间
✅ 已创建产品: 阳台
🔍 [报价编辑器] Product表查询结果: 9 条记录 ← ❌ 问题!
检查报价明细:显示9个空间
自动初始化:
🏠 [初始空间] 为项目 abc123 创建初始空间,类型: 家装
✅ [初始空间] 已创建 2 个初始空间 ← ✅ 正确!
点击"生成报价"按钮
检查控制台日志:
🔍 [报价编辑器] Product表查询结果: 2 条记录 ← ✅ 正确!
✅ 报价空间生成完成: 2 个唯一空间 (原始产品: 2 个)
检查报价明细:只显示2个空间(客厅 + 主卧)
用户手动添加更多空间:点击"添加空间"按钮
presetScenes 为少量默认值(方案1)ProductSpaceService 依赖quotation-editor.component.ts
quotation-editor.component.html
presetScenes 包含9个默认房间presetScenes 为少量默认值(1-2个)createDefaultProducts 调用统一的 createInitialSpaces 方法