文件:e:\yinsanse\yss-project\src\modules\project\components\quotation-editor.component.ts
// ❌ 旧代码:默认创建所有场景空间
presetScenes: { [key: string]: string[] } = {
'家装': ['客厅', '餐厅', '主卧', '次卧', '儿童房', '书房', '厨房', '卫生间', '阳台'], // 9个空间
'工装': ['大堂', '接待区', '会议室', '办公区', '休息区', '展示区', '洽谈区'], // 7个空间
'建筑类': ['门头', '小型单体', '大型单体', '鸟瞰'] // 4个空间
};
问题:
getDefaultRoomsForProjectType() 方法直接返回 presetScenes// ❌ 旧代码:创建完产品后没有自动生成报价
private async createDefaultProducts(): Promise<void> {
const defaultRooms = this.getDefaultRoomsForProjectType(); // 返回8-9个空间
for (const roomName of defaultRooms) {
await this.createProduct(roomName); // 逐个创建产品
}
await this.loadProjectProducts(); // 重新加载
// ❌ 缺少:await this.generateQuotationFromProducts(); // 未自动生成报价
}
问题:
generateQuotationFromProducts()quotation.spaces 数组为空或未初始化// ✅ createProduct() 方法会设置产品的 basePrice
product.set('quotation', {
basePrice: Math.round(basePrice),
price: Math.round(basePrice),
...
});
// ❌ 但这个价格不会自动同步到 quotation.spaces
// 必须调用 generateQuotationFromProducts() 才能生成最终的 quotation 对象
// ✅ 新增:默认初始空间(仅创建1-2个)
defaultInitialSpaces: { [key: string]: string[] } = {
'家装': ['主卧'], // 只创建1个
'工装': ['大堂'], // 只创建1个
'建筑类': ['门头'] // 只创建1个
};
// ✅ 保留完整列表用于快速添加
presetScenes: { [key: string]: string[] } = {
'家装': ['客厅', '餐厅', '主卧', '次卧', '儿童房', '书房', '厨房', '卫生间', '阳台'],
'工装': ['大堂', '接待区', '会议室', '办公区', '休息区', '展示区', '洽谈区'],
'建筑类': ['门头', '小型单体', '大型单体', '鸟瞰']
};
改进:
defaultInitialSpaces:用于初始化时创建(1个空间)presetScenes:保留完整列表,用于"快速添加"功能// ✅ 修改后:只返回初始空间
private getDefaultRoomsForProjectType(): string[] {
return this.defaultInitialSpaces[this.projectInfo.projectType] || ['主卧'];
}
改进:
// ✅ 修改后:创建产品后自动生成报价
private async createDefaultProducts(): Promise<void> {
if (!this.project || !this.projectInfo.projectType) return;
try {
console.log('🏗️ [报价编辑器] 开始创建默认产品...');
const defaultRooms = this.getDefaultRoomsForProjectType();
console.log(`📋 [报价编辑器] 将创建 ${defaultRooms.length} 个初始空间:`, defaultRooms);
// 创建产品
for (const roomName of defaultRooms) {
await this.createProduct(roomName);
console.log(` ✅ 已创建空间: ${roomName}`);
}
// 🔥 重新加载产品列表
await this.loadProjectProducts();
// 🔥 自动生成报价,确保显示正确的价格(不是¥0)
console.log('💰 [报价编辑器] 自动生成初始报价...');
await this.generateQuotationFromProducts();
console.log('✅ [报价编辑器] 默认产品创建完成,报价总额:', this.quotation.total);
} catch (error) {
console.error('❌ 创建默认产品失败:', error);
}
}
关键改进:
generateQuotationFromProducts() 自动生成报价quotation.spaces 和 quotation.total 正确初始化新项目创建
↓
loadProjectProducts() → products.length === 0
↓
createDefaultProducts()
↓
创建8-9个空间产品(家装项目)
↓
loadProjectProducts() → 加载产品列表
↓
❌ 没有调用 generateQuotationFromProducts()
↓
前端显示:8-9个空间,每个报价 ¥0 ❌
新项目创建
↓
loadProjectProducts() → products.length === 0
↓
createDefaultProducts()
↓
创建1个初始空间(如"主卧")
↓
loadProjectProducts() → 加载产品列表
↓
✅ 自动调用 generateQuotationFromProducts()
↓
计算基础价格(如 ¥300)
↓
生成工序分配(建模10%、软装渲染40%、公司50%)
↓
更新 quotation.total 和 quotation.spaces
↓
前端显示:1个空间"主卧",报价 ¥300 ✅
产品价格管理
设计产品 (8个空间) ❌ 过多
报价明细 (8个设计空间)
生成时间:11-17 16:54 | 有效期至:2025-12-17
┌─────────────────────────────────┐
│ 🔷 餐厅 未开始 ¥0 13% │ ❌ 报价为0
│ 🔷 餐厅 未开始 ¥0 13% │
│ 🔷 主卧 未开始 ¥0 13% │
│ 🔷 次卧 未开始 ¥0 13% │
│ 🔷 儿童房 未开始 ¥0 13% │
│ 🔷 中房 未开始 ¥0 13% │
│ 🔷 厨房 未开始 ¥0 13% │
│ 🔷 卫生间 未开始 ¥0 13% │
└─────────────────────────────────┘
案价总计:¥0 ❌ 总价为0
产品价格管理
设计产品 (1个空间) ✅ 合理
报价明细 (1个设计空间)
生成时间:11-17 17:30 | 有效期至:2025-12-17
┌─────────────────────────────────┐
│ 🔷 主卧 未开始 ¥300 100% │ ✅ 正确报价
│ │- 建模阶段: ¥30 (10%) │
│ │- 软装渲染: ¥120 (40%) │
│ └- 公司分配: ¥150 (50%) │
└─────────────────────────────────┘
[➕ 添加产品] ✅ 支持添加更多
案价总计:¥300 ✅ 正确总价
// 步骤1:创建产品时计算基础价格
const basePrice = this.calculateBasePrice({
priceLevel: '一级',
projectType: '家装',
renderType: '静态单张',
spaceType: '平层',
styleLevel: '基础风格组'
});
// basePrice = 300 (根据报价规则计算)
// 步骤2:设置产品报价
product.set('quotation', {
basePrice: 300,
price: 300,
breakdown: { ... }
});
// 步骤3:生成报价空间
await this.generateQuotationFromProducts();
// 遍历所有产品,为每个产品生成报价空间
// 步骤4:生成工序分配
const processes = {
modeling: { enabled: true, amount: 30, percentage: 10 },
decoration: { enabled: true, amount: 120, percentage: 40 },
company: { enabled: true, amount: 150, percentage: 50 }
};
// 步骤5:计算总价
this.quotation.total = 300; // ✅ 正确的总价
基础价格表(来自 quotation-rules.ts):
| 价格等级 | 平层(家装) | 门厅(工装) | 门头(建筑) |
|---|---|---|---|
| 一级 | 300 | 500 | 800 |
| 二级 | 350 | 600 | 1000 |
| 三级 | 450 | 700 | 1200 |
工序分配规则:
1. 用户点击"添加产品"按钮
↓
2. 弹出模态框,显示预设场景列表
- 家装:客厅、餐厅、主卧、次卧、儿童房、书房、厨房、卫生间、阳台
- 工装:大堂、接待区、会议室、办公区、休息区、展示区、洽谈区
- 建筑类:门头、小型单体、大型单体、鸟瞰
↓
3. 用户选择场景(或输入自定义名称)
↓
4. 系统自动:
- 创建产品到 Product 表
- 计算基础价格
- 重新生成报价
- 更新前端显示
↓
5. 用户看到:新空间已添加,报价已更新 ✅
// 添加产品方法(简化版)
async addProduct(productName?: string): Promise<void> {
const name = productName || await window?.fmode?.input('请输入产品名称:');
if (!name) return;
try {
// 创建新产品
const newProduct = await this.createProduct(name);
if (!newProduct) {
window?.fmode?.alert('创建产品失败');
return;
}
// 重新加载产品列表
await this.loadProjectProducts();
// 重新生成报价
await this.generateQuotationFromProducts();
// 发出产品变更事件
this.productsUpdated.emit({
action: 'add',
count: this.products.length
});
console.log('✅ [产品变更] 已添加产品,当前数量:', this.products.length);
} catch (error) {
console.error('添加产品失败:', error);
window?.fmode?.alert('添加失败,请重试');
}
}
// Project 表 - data 字段
{
quotation: {
spaces: [
{
name: '主卧',
productId: 'xxx',
processes: {
modeling: { enabled: true, amount: 30, percentage: 10 },
decoration: { enabled: true, amount: 120, percentage: 40 },
company: { enabled: true, amount: 150, percentage: 50 }
},
subtotal: 300
}
],
total: 300,
spaceBreakdown: [...],
generatedAt: '2024-11-17T09:30:00.000Z',
validUntil: '2025-12-17T09:30:00.000Z'
},
unifiedSpaces: [
{
id: 'xxx',
name: '主卧',
type: 'bedroom',
status: 'not_started',
quotation: {
price: 300,
processes: { ... },
subtotal: 300
}
}
]
}
// 保存报价到项目
private async saveQuotationToProject(): Promise<void> {
const data = this.project.get('data') || {};
// 保存报价数据
data.quotation = this.quotation;
// 同步 unifiedSpaces(确保各阶段数据一致)
data.unifiedSpaces = this.products.map((product, index) => ({
id: product.id,
name: product.get('productName'),
quotation: { ... },
...
}));
this.project.set('data', data);
await this.project.save(); // 🔥 持久化保存到数据库
console.log('✅ 报价已保存到数据库');
}
操作步骤:
预期结果:
✅ 只显示1个空间:"主卧"
✅ 报价显示:¥300(不是¥0)
✅ 工序分配:
- 建模阶段:¥30 (10%)
- 软装渲染:¥120 (40%)
- 公司分配:¥150 (50%)
✅ 报价总计:¥300
控制台日志:
🏗️ [报价编辑器] 开始创建默认产品...
📋 [报价编辑器] 将创建 1 个初始空间: ['主卧']
✅ 已创建空间: 主卧
💰 [报价编辑器] 自动生成初始报价...
✅ [报价编辑器] 默认产品创建完成,报价总额: 300
操作步骤:
预期结果:
✅ 空间数量变为2个:"主卧"、"客厅"
✅ 报价总额更新:¥600(假设客厅也是¥300)
✅ 报价明细:
- 主卧:¥300 (50%)
- 客厅:¥300 (50%)
✅ 数据已保存到数据库
操作步骤:
预期结果:
✅ 确认需求阶段显示3个空间(与订单分配一致)
✅ 报价总额一致
✅ 空间名称一致
✅ 数据同步无误
// 1. 创建默认产品
🏗️ [报价编辑器] 开始创建默认产品...
📋 [报价编辑器] 将创建 1 个初始空间: ['主卧']
✅ 已创建空间: 主卧
// 2. 加载产品列表
🔍 [报价编辑器] Product表查询结果: 1 条记录
✅ 保留空间: 主卧 (ID: xxx)
✅ [报价编辑器] 最终加载 1 个唯一空间
// 3. 生成报价
💰 [报价编辑器] 自动生成初始报价...
✅ 报价空间生成完成: 1 个唯一空间 (原始产品: 1 个)
🔄 [报价编辑器] 同步更新 unifiedSpaces: 1 个空间
// 4. 完成
✅ [报价编辑器] 默认产品创建完成,报价总额: 300
// 检测到重复空间
⚠️ 检测到重复空间: 主卧 (产品ID: yyy)
⚠️ 去重后跳过了 1 个重复空间: ['主卧']
⚠️ 检测到 1 个重复产品,建议清理
// 创建失败
❌ 创建默认产品失败: Error: ...
defaultInitialSpaces 配置,只创建1个初始空间getDefaultRoomsForProjectType() 方法,返回初始空间而非完整列表createDefaultProducts() 方法,自动调用 generateQuotationFromProducts()修复已完成,请刷新页面测试! 🎉
如有任何问题,请查看控制台日志输出。