QUOTATION_INITIAL_FIX.md 16 KB

报价初始化问题修复方案

🎯 问题描述

用户反馈的问题

  1. 默认创建过多空间:每个项目起初默认生成8-9个报价空间
  2. 报价显示错误:所有空间的报价初始显示为 ¥0
  3. 操作逻辑混乱:只有在点击"生成报价"按钮后,报价才会变为正常值

期望的行为

  1. 初始化时只创建1-2个空间:不是8-9个
  2. 自动显示正确报价:不需要手动点击"生成报价"按钮
  3. 支持动态添加:用户可以通过"添加产品"按钮添加更多空间
  4. 数据同步:所有阶段看到的空间数量和报价应该一致

🔍 问题根源分析

代码位置

文件e:\yinsanse\yss-project\src\modules\project\components\quotation-editor.component.ts

原有逻辑问题

1. 预设场景列表(第102-107行)

// ❌ 旧代码:默认创建所有场景空间
presetScenes: { [key: string]: string[] } = {
  '家装': ['客厅', '餐厅', '主卧', '次卧', '儿童房', '书房', '厨房', '卫生间', '阳台'],  // 9个空间
  '工装': ['大堂', '接待区', '会议室', '办公区', '休息区', '展示区', '洽谈区'],       // 7个空间
  '建筑类': ['门头', '小型单体', '大型单体', '鸟瞰']                                  // 4个空间
};

问题

  • getDefaultRoomsForProjectType() 方法直接返回 presetScenes
  • 导致初始化时创建所有预设空间(8-9个)

2. 创建默认产品(第322-340行)

// ❌ 旧代码:创建完产品后没有自动生成报价
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 数组为空或未初始化
  • 前端显示时报价为 ¥0

3. 生成报价时机

// ✅ createProduct() 方法会设置产品的 basePrice
product.set('quotation', {
  basePrice: Math.round(basePrice),
  price: Math.round(basePrice),
  ...
});

// ❌ 但这个价格不会自动同步到 quotation.spaces
// 必须调用 generateQuotationFromProducts() 才能生成最终的 quotation 对象

✅ 修复方案

修复1:新增默认初始空间配置

// ✅ 新增:默认初始空间(仅创建1-2个)
defaultInitialSpaces: { [key: string]: string[] } = {
  '家装': ['主卧'],      // 只创建1个
  '工装': ['大堂'],      // 只创建1个
  '建筑类': ['门头']     // 只创建1个
};

// ✅ 保留完整列表用于快速添加
presetScenes: { [key: string]: string[] } = {
  '家装': ['客厅', '餐厅', '主卧', '次卧', '儿童房', '书房', '厨房', '卫生间', '阳台'],
  '工装': ['大堂', '接待区', '会议室', '办公区', '休息区', '展示区', '洽谈区'],
  '建筑类': ['门头', '小型单体', '大型单体', '鸟瞰']
};

改进

  • defaultInitialSpaces:用于初始化时创建(1个空间)
  • presetScenes:保留完整列表,用于"快速添加"功能

修复2:更新获取默认房间方法

// ✅ 修改后:只返回初始空间
private getDefaultRoomsForProjectType(): string[] {
  return this.defaultInitialSpaces[this.projectInfo.projectType] || ['主卧'];
}

改进

  • 初始化时只创建1个空间
  • 减少不必要的数据创建

修复3:创建产品后自动生成报价

// ✅ 修改后:创建产品后自动生成报价
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);
  }
}

关键改进

  1. ✅ 添加详细的控制台日志
  2. ✅ 调用 generateQuotationFromProducts() 自动生成报价
  3. ✅ 确保 quotation.spacesquotation.total 正确初始化

📊 修复效果对比

修复前 ❌

初始化流程

新项目创建
  ↓
loadProjectProducts() → products.length === 0
  ↓
createDefaultProducts()
  ↓
创建8-9个空间产品(家装项目)
  ↓
loadProjectProducts() → 加载产品列表
  ↓
❌ 没有调用 generateQuotationFromProducts()
  ↓
前端显示:8-9个空间,每个报价 ¥0  ❌

用户体验

  • ❌ 看到8-9个空间,不知道从哪里开始
  • ❌ 所有报价显示 ¥0,误以为系统出错
  • ❌ 必须点击"生成报价"按钮才能看到正确价格
  • ❌ 数据冗余,影响性能

修复后 ✅

初始化流程

新项目创建
  ↓
loadProjectProducts() → products.length === 0
  ↓
createDefaultProducts()
  ↓
创建1个初始空间(如"主卧")
  ↓
loadProjectProducts() → 加载产品列表
  ↓
✅ 自动调用 generateQuotationFromProducts()
  ↓
计算基础价格(如 ¥300)
  ↓
生成工序分配(建模10%、软装渲染40%、公司50%)
  ↓
更新 quotation.total 和 quotation.spaces
  ↓
前端显示:1个空间"主卧",报价 ¥300  ✅

用户体验

  • ✅ 只看到1个初始空间,清晰明了
  • ✅ 报价自动显示正确金额(如 ¥300)
  • ✅ 无需点击"生成报价",开箱即用
  • ✅ 用户可以通过"添加产品"按钮添加更多空间
  • ✅ 数据精简,性能更好

🎨 UI 显示效果

修复前

产品价格管理
设计产品 (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. 数据流程

// 步骤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;  // ✅ 正确的总价

2. 价格计算规则

基础价格表(来自 quotation-rules.ts):

价格等级 平层(家装) 门厅(工装) 门头(建筑)
一级 300 500 800
二级 350 600 1000
三级 450 700 1200

工序分配规则

  • 建模阶段:10%
  • 软装渲染:40%
  • 公司分配:50%

🚀 添加产品功能

用户操作流程

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
      }
    }
  ]
}

同步时机

  1. 创建默认产品后:自动生成报价并保存
  2. 添加产品后:重新生成报价并保存
  3. 编辑产品后:更新报价并保存
  4. 删除产品后:重新生成报价并保存

持久化保存

// 保存报价到项目
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:新项目初始化

操作步骤

  1. 创建一个新的项目
  2. 项目类型选择"家装"
  3. 价格等级选择"一级"
  4. 进入订单分配页面

预期结果

✅ 只显示1个空间:"主卧"
✅ 报价显示:¥300(不是¥0)
✅ 工序分配:
   - 建模阶段:¥30 (10%)
   - 软装渲染:¥120 (40%)
   - 公司分配:¥150 (50%)
✅ 报价总计:¥300

控制台日志

🏗️ [报价编辑器] 开始创建默认产品...
📋 [报价编辑器] 将创建 1 个初始空间: ['主卧']
  ✅ 已创建空间: 主卧
💰 [报价编辑器] 自动生成初始报价...
✅ [报价编辑器] 默认产品创建完成,报价总额: 300

测试场景2:添加更多空间

操作步骤

  1. 在订单分配页面
  2. 点击"添加产品"按钮
  3. 选择"客厅"
  4. 确认添加

预期结果

✅ 空间数量变为2个:"主卧"、"客厅"
✅ 报价总额更新:¥600(假设客厅也是¥300)
✅ 报价明细:
   - 主卧:¥300 (50%)
   - 客厅:¥300 (50%)
✅ 数据已保存到数据库

测试场景3:跨阶段数据一致性

操作步骤

  1. 在订单分配页面添加3个空间
  2. 提交订单分配
  3. 进入"确认需求"阶段
  4. 查看产品空间数量

预期结果

✅ 确认需求阶段显示3个空间(与订单分配一致)
✅ 报价总额一致
✅ 空间名称一致
✅ 数据同步无误

📋 控制台日志说明

正常流程日志

// 1. 创建默认产品
🏗️ [报价编辑器] 开始创建默认产品...
📋 [报价编辑器] 将创建 1 个初始空间: ['主卧']
  ✅ 已创建空间: 主卧

// 2. 加载产品列表
🔍 [报价编辑器] Product表查询结果: 1 条记录
  ✅ 保留空间: 主卧 (ID: xxx)
✅ [报价编辑器] 最终加载 1 个唯一空间

// 3. 生成报价
💰 [报价编辑器] 自动生成初始报价...
✅ 报价空间生成完成: 1 个唯一空间 (原始产品: 1 个)
🔄 [报价编辑器] 同步更新 unifiedSpaces: 1 个空间

// 4. 完成
✅ [报价编辑器] 默认产品创建完成,报价总额: 300

异常情况日志

// 检测到重复空间
⚠️ 检测到重复空间: 主卧 (产品ID: yyy)
⚠️ 去重后跳过了 1 个重复空间: ['主卧']
⚠️ 检测到 1 个重复产品,建议清理

// 创建失败
❌ 创建默认产品失败: Error: ...

🎊 总结

修复内容

  1. ✅ 新增 defaultInitialSpaces 配置,只创建1个初始空间
  2. ✅ 修改 getDefaultRoomsForProjectType() 方法,返回初始空间而非完整列表
  3. ✅ 增强 createDefaultProducts() 方法,自动调用 generateQuotationFromProducts()
  4. ✅ 添加详细的控制台日志,便于调试和追踪

修复效果

  1. ✅ 新项目只创建1个空间,不是8-9个
  2. ✅ 报价自动显示正确金额(如¥300),不是¥0
  3. ✅ 无需手动点击"生成报价"按钮
  4. ✅ 用户可以通过"添加产品"动态添加更多空间
  5. ✅ 数据在所有阶段保持一致(持久化保存)

用户体验提升

  • 🎯 清晰明了:只看到必要的初始空间
  • 🚀 开箱即用:报价自动计算,无需额外操作
  • 🎨 灵活扩展:支持动态添加和管理空间
  • 💾 数据可靠:所有变更都持久化保存到数据库

修复已完成,请刷新页面测试! 🎉

如有任何问题,请查看控制台日志输出。