浏览代码

fix: space with product

ryanemax 1 天之前
父节点
当前提交
61de0dd766

+ 40 - 40
src/modules/project/pages/project-detail/stages/stage-aftercare.component.ts

@@ -5,12 +5,12 @@ import { ActivatedRoute } from '@angular/router';
 import { FmodeObject, FmodeParse } from 'fmode-ng/parse';
 import { FmodeObject, FmodeParse } from 'fmode-ng/parse';
 import { NovaUploadService } from 'fmode-ng/storage';
 import { NovaUploadService } from 'fmode-ng/storage';
 import { ProjectFileService } from '../../../services/project-file.service';
 import { ProjectFileService } from '../../../services/project-file.service';
-import { MultiSpaceService, ProjectSpace } from '../../../services/multi-space.service';
+import { ProductSpaceService, Project } from '../../../services/product-space.service';
 
 
 const Parse = FmodeParse.with('nova');
 const Parse = FmodeParse.with('nova');
 
 
 /**
 /**
- * 售后归档阶段组件
+ * 售后归档阶段组件 - Product表统一空间管理
  *
  *
  * 功能:
  * 功能:
  * 1. 尾款管理
  * 1. 尾款管理
@@ -37,11 +37,11 @@ export class StageAftercareComponent implements OnInit {
   cid: string = '';
   cid: string = '';
   projectId: string = '';
   projectId: string = '';
 
 
-  // 空间管理
-  projectSpaces: ProjectSpace[] = [];
-  isMultiSpaceProject: boolean = false;
-  activeSpaceId: string = '';
-  selectedSpaceIds: string[] = [];
+  // Product-based 空间管理
+  projectProducts: Project[] = [];
+  isMultiProductProject: boolean = false;
+  activeProductId: string = '';
+  selectedProductIds: string[] = [];
 
 
   // 售后视图切换
   // 售后视图切换
   aftercareView: string = 'overview'; // overview | spaces | complaints | panorama
   aftercareView: string = 'overview'; // overview | spaces | complaints | panorama
@@ -155,7 +155,7 @@ export class StageAftercareComponent implements OnInit {
   constructor(
   constructor(
     private route: ActivatedRoute,
     private route: ActivatedRoute,
     private projectFileService: ProjectFileService,
     private projectFileService: ProjectFileService,
-    private multiSpaceService: MultiSpaceService,
+    private productSpaceService: ProductSpaceService,
     private cdr: ChangeDetectorRef
     private cdr: ChangeDetectorRef
   ) {}
   ) {}
 
 
@@ -214,12 +214,12 @@ export class StageAftercareComponent implements OnInit {
     if (!this.project) return;
     if (!this.project) return;
 
 
     try {
     try {
-      this.projectSpaces = await this.multiSpaceService.getProjectSpaces(this.project.id!);
-      this.isMultiSpaceProject = this.projectSpaces.length > 1;
+      this.projectProducts = await this.productSpaceService.getProjectProductSpaces(this.project.id!);
+      this.isMultiProductProject = this.projectProducts.length > 1;
 
 
-      // 如果有空间,默认选中第一个
-      if (this.projectSpaces.length > 0 && !this.activeSpaceId) {
-        this.activeSpaceId = this.projectSpaces[0].id;
+      // 如果有产品,默认选中第一个
+      if (this.projectProducts.length > 0 && !this.activeProductId) {
+        this.activeProductId = this.projectProducts[0].id;
       }
       }
 
 
     } catch (error) {
     } catch (error) {
@@ -289,22 +289,22 @@ export class StageAftercareComponent implements OnInit {
   }
   }
 
 
   /**
   /**
-   * 选择空间
+   * 选择产品
    */
    */
-  selectSpace(spaceId: string): void {
-    this.activeSpaceId = spaceId;
+  selectProduct(productId: string): void {
+    this.activeProductId = productId;
     this.cdr.markForCheck();
     this.cdr.markForCheck();
   }
   }
 
 
   /**
   /**
-   * 切换空间选择状态
+   * 切换产品选择状态
    */
    */
-  toggleSpaceSelection(spaceId: string): void {
-    const index = this.selectedSpaceIds.indexOf(spaceId);
+  toggleProductSelection(productId: string): void {
+    const index = this.selectedProductIds.indexOf(productId);
     if (index > -1) {
     if (index > -1) {
-      this.selectedSpaceIds.splice(index, 1);
+      this.selectedProductIds.splice(index, 1);
     } else {
     } else {
-      this.selectedSpaceIds.push(spaceId);
+      this.selectedProductIds.push(productId);
     }
     }
     this.cdr.markForCheck();
     this.cdr.markForCheck();
   }
   }
@@ -375,14 +375,14 @@ export class StageAftercareComponent implements OnInit {
    * 添加空间评价
    * 添加空间评价
    */
    */
   addSpaceFeedback(spaceId: string): void {
   addSpaceFeedback(spaceId: string): void {
-    const space = this.projectSpaces.find(s => s.id === spaceId);
+    const space = this.projectProducts.find(s => s.id === spaceId);
     if (!space) return;
     if (!space) return;
 
 
     const existingFeedback = this.customerFeedback.spaceFeedbacks.find(f => f.spaceId === spaceId);
     const existingFeedback = this.customerFeedback.spaceFeedbacks.find(f => f.spaceId === spaceId);
     if (!existingFeedback) {
     if (!existingFeedback) {
       this.customerFeedback.spaceFeedbacks.push({
       this.customerFeedback.spaceFeedbacks.push({
         spaceId,
         spaceId,
-        spaceName: space.name || this.multiSpaceService.getSpaceTypeName(space.type),
+        spaceName: space.name || this.productSpaceService.getProductTypeName(space.type),
         rating: 0,
         rating: 0,
         comments: '',
         comments: '',
         issues: []
         issues: []
@@ -411,12 +411,12 @@ export class StageAftercareComponent implements OnInit {
     description: string;
     description: string;
     severity: 'low' | 'medium' | 'high';
     severity: 'low' | 'medium' | 'high';
   }): Promise<void> {
   }): Promise<void> {
-    const space = complaint.spaceId ? this.projectSpaces.find(s => s.id === complaint.spaceId) : undefined;
+    const space = complaint.spaceId ? this.projectProducts.find(s => s.id === complaint.spaceId) : undefined;
 
 
     const newComplaint = {
     const newComplaint = {
       id: `complaint_${Date.now()}`,
       id: `complaint_${Date.now()}`,
       spaceId: complaint.spaceId,
       spaceId: complaint.spaceId,
-      spaceName: space?.name || this.multiSpaceService.getSpaceTypeName(space?.type || ''),
+      spaceName: space?.name || this.productSpaceService.getProductTypeName(space?.type || ''),
       type: complaint.type,
       type: complaint.type,
       description: complaint.description,
       description: complaint.description,
       severity: complaint.severity,
       severity: complaint.severity,
@@ -439,7 +439,7 @@ export class StageAftercareComponent implements OnInit {
     const files = event.target.files;
     const files = event.target.files;
     if (!files || files.length === 0) return;
     if (!files || files.length === 0) return;
 
 
-    const space = this.projectSpaces.find(s => s.id === spaceId);
+    const space = this.projectProducts.find(s => s.id === spaceId);
     if (!space) return;
     if (!space) return;
 
 
     try {
     try {
@@ -468,7 +468,7 @@ export class StageAftercareComponent implements OnInit {
         this.panoramaCollection.images.push({
         this.panoramaCollection.images.push({
           id: uploadedFile.md5 || '',
           id: uploadedFile.md5 || '',
           spaceId,
           spaceId,
-          spaceName: space.name || this.multiSpaceService.getSpaceTypeName(space.type),
+          spaceName: space.name || this.productSpaceService.getProductTypeName(space.type),
           url: uploadedFile.url || '',
           url: uploadedFile.url || '',
           uploadTime: new Date(),
           uploadTime: new Date(),
           uploadedBy: this.currentUser?.get('name') || '',
           uploadedBy: this.currentUser?.get('name') || '',
@@ -552,10 +552,10 @@ export class StageAftercareComponent implements OnInit {
           '增加可视化沟通工具',
           '增加可视化沟通工具',
           '完善项目管理流程'
           '完善项目管理流程'
         ],
         ],
-        spaceRetrospectives: this.isMultiSpaceProject
-          ? this.projectSpaces.map(space => ({
+        spaceRetrospectives: this.isMultiProductProject
+          ? this.projectProducts.map(space => ({
               spaceId: space.id,
               spaceId: space.id,
-              spaceName: space.name || this.multiSpaceService.getSpaceTypeName(space.type),
+              spaceName: space.name || this.productSpaceService.getProductTypeName(space.type),
               performance: this.customerFeedback.spaceFeedbacks.find(f => f.spaceId === space.id)?.rating || 0,
               performance: this.customerFeedback.spaceFeedbacks.find(f => f.spaceId === space.id)?.rating || 0,
               issues: [],
               issues: [],
               improvements: []
               improvements: []
@@ -726,20 +726,20 @@ export class StageAftercareComponent implements OnInit {
    * 获取空间图标
    * 获取空间图标
    */
    */
   getSpaceIcon(spaceType: string): string {
   getSpaceIcon(spaceType: string): string {
-    return this.multiSpaceService.getSpaceIcon(spaceType);
+    return this.productSpaceService.getProductIcon(spaceType);
   }
   }
 
 
   /**
   /**
    * 获取空间类型名称
    * 获取空间类型名称
    */
    */
   getSpaceTypeName(spaceType: string): string {
   getSpaceTypeName(spaceType: string): string {
-    return this.multiSpaceService.getSpaceTypeName(spaceType);
+    return this.productSpaceService.getProductTypeName(spaceType);
   }
   }
 
 
   /**
   /**
    * 获取空间显示名称
    * 获取空间显示名称
    */
    */
-  getSpaceDisplayName(space: ProjectSpace): string {
+  getSpaceDisplayName(space: Project): string {
     return space.name || this.getSpaceTypeName(space.type);
     return space.name || this.getSpaceTypeName(space.type);
   }
   }
 
 
@@ -747,8 +747,8 @@ export class StageAftercareComponent implements OnInit {
    * 获取当前空间的评价
    * 获取当前空间的评价
    */
    */
   getCurrentSpaceFeedback(): any {
   getCurrentSpaceFeedback(): any {
-    if (!this.activeSpaceId) return null;
-    return this.customerFeedback.spaceFeedbacks.find(f => f.spaceId === this.activeSpaceId);
+    if (!this.activeProductId) return null;
+    return this.customerFeedback.spaceFeedbacks.find(f => f.spaceId === this.activeProductId);
   }
   }
 
 
   /**
   /**
@@ -827,13 +827,13 @@ export class StageAftercareComponent implements OnInit {
    * 计算整体项目完成度
    * 计算整体项目完成度
    */
    */
   calculateOverallCompletion(): number {
   calculateOverallCompletion(): number {
-    if (this.projectSpaces.length === 0) return 0;
+    if (this.projectProducts.length === 0) return 0;
 
 
-    const totalCompletion = this.projectSpaces.reduce((total, space) => {
+    const totalCompletion = this.projectProducts.reduce((total, space) => {
       return total + this.calculateSpaceCompletion(space.id);
       return total + this.calculateSpaceCompletion(space.id);
     }, 0);
     }, 0);
 
 
-    return Math.round(totalCompletion / this.projectSpaces.length);
+    return Math.round(totalCompletion / this.projectProducts.length);
   }
   }
 
 
   /**
   /**
@@ -867,8 +867,8 @@ export class StageAftercareComponent implements OnInit {
     const summary = [];
     const summary = [];
     summary.push(`项目整体满意度:${this.customerFeedback.overallRating}/5星`);
     summary.push(`项目整体满意度:${this.customerFeedback.overallRating}/5星`);
 
 
-    if (this.isMultiSpaceProject) {
-      summary.push(`涉及${this.projectSpaces.length}个空间`);
+    if (this.isMultiProductProject) {
+      summary.push(`涉及${this.projectProducts.length}个空间`);
 
 
       const spaceRatings = this.customerFeedback.spaceFeedbacks.map(f =>
       const spaceRatings = this.customerFeedback.spaceFeedbacks.map(f =>
         `${f.spaceName}:${f.rating}/5星`
         `${f.spaceName}:${f.rating}/5星`

+ 30 - 30
src/modules/project/pages/project-detail/stages/stage-delivery.component.ts

@@ -5,15 +5,15 @@ import { ActivatedRoute } from '@angular/router';
 import { FmodeObject, FmodeParse } from 'fmode-ng/parse';
 import { FmodeObject, FmodeParse } from 'fmode-ng/parse';
 import { NovaUploadService } from 'fmode-ng/storage';
 import { NovaUploadService } from 'fmode-ng/storage';
 import { ProjectFileService } from '../../../services/project-file.service';
 import { ProjectFileService } from '../../../services/project-file.service';
-import { MultiSpaceService, ProjectSpace } from '../../../services/multi-space.service';
+import { ProductSpaceService, Project } from '../../../services/product-space.service';
 
 
 const Parse = FmodeParse.with('nova');
 const Parse = FmodeParse.with('nova');
 
 
 /**
 /**
- * 交付执行阶段组件
+ * 交付执行阶段组件 - Product表统一空间管理
  *
  *
  * 功能:
  * 功能:
- * 1. 按空间+工序上传交付物(从order阶段quotation动态加载)
+ * 1. 按产品+工序上传交付物(从order阶段quotation动态加载)
  * 2. 质量自查清单
  * 2. 质量自查清单
  * 3. 组长审核与问题反馈
  * 3. 组长审核与问题反馈
  * 4. 发起交付流程
  * 4. 发起交付流程
@@ -36,11 +36,11 @@ export class StageDeliveryComponent implements OnInit {
   cid: string = '';
   cid: string = '';
   projectId: string = '';
   projectId: string = '';
 
 
-  // 空间管理
-  projectSpaces: ProjectSpace[] = [];
-  isMultiSpaceProject: boolean = false;
-  activeSpaceId: string = '';
-  selectedSpaceIds: string[] = [];
+  // Product-based 空间管理
+  projectProducts: Project[] = [];
+  isMultiProductProject: boolean = false;
+  activeProductId: string = '';
+  selectedProductIds: string[] = [];
 
 
   // 交付视图切换
   // 交付视图切换
   deliveryView: string = 'spaces'; // spaces | processes | timeline
   deliveryView: string = 'spaces'; // spaces | processes | timeline
@@ -172,7 +172,7 @@ export class StageDeliveryComponent implements OnInit {
   constructor(
   constructor(
     private route: ActivatedRoute,
     private route: ActivatedRoute,
     private projectFileService: ProjectFileService,
     private projectFileService: ProjectFileService,
-    private multiSpaceService: MultiSpaceService,
+    private productSpaceService: ProductSpaceService,
     private cdr: ChangeDetectorRef
     private cdr: ChangeDetectorRef
   ) {}
   ) {}
 
 
@@ -240,13 +240,13 @@ export class StageDeliveryComponent implements OnInit {
 
 
     try {
     try {
       if(this.project?.id){  
       if(this.project?.id){  
-        this.projectSpaces = await this.multiSpaceService.getProjectSpaces(this.project.id);
+        this.projectProducts = await this.productSpaceService.getProjectProductSpaces(this.project.id);
       }
       }
-      this.isMultiSpaceProject = this.projectSpaces.length > 1;
+      this.isMultiProductProject = this.projectProducts.length > 1;
 
 
-      // 如果有空间,默认选中第一个
-      if (this.projectSpaces.length > 0 && !this.activeSpaceId) {
-        this.activeSpaceId = this.projectSpaces[0].id;
+      // 如果有产品,默认选中第一个
+      if (this.projectProducts.length > 0 && !this.activeProductId) {
+        this.activeProductId = this.projectProducts[0].id;
       }
       }
 
 
     } catch (error) {
     } catch (error) {
@@ -312,7 +312,7 @@ export class StageDeliveryComponent implements OnInit {
     try {
     try {
       if(this.project?.id){
       if(this.project?.id){
         let dependencies
         let dependencies
-        dependencies = await this.multiSpaceService.getSpaceDependencies(this.project.id);
+        dependencies = await this.productSpaceService.getProductDependencies(this.project.id);
         
         
         this.crossSpaceDependencies = dependencies.map((dep: any) => ({
         this.crossSpaceDependencies = dependencies.map((dep: any) => ({
           id: dep.id,
           id: dep.id,
@@ -376,13 +376,13 @@ export class StageDeliveryComponent implements OnInit {
       }
       }
     } else {
     } else {
       // 从项目空间初始化
       // 从项目空间初始化
-      for (const space of this.projectSpaces) {
+      for (const space of this.projectProducts) {
         // 为每个空间创建基础工序交付物
         // 为每个空间创建基础工序交付物
         const defaultProcesses = ['modeling', 'softDecor', 'rendering', 'postProcess'];
         const defaultProcesses = ['modeling', 'softDecor', 'rendering', 'postProcess'];
         for (const processType of defaultProcesses) {
         for (const processType of defaultProcesses) {
           deliverables.push(this.createDeliverable(
           deliverables.push(this.createDeliverable(
             space.id,
             space.id,
-            space.name || this.multiSpaceService.getSpaceTypeName(space.type),
+            space.name || this.productSpaceService.getProductTypeName(space.type),
             processType,
             processType,
             processTypeMap[processType]
             processTypeMap[processType]
           ));
           ));
@@ -456,7 +456,7 @@ export class StageDeliveryComponent implements OnInit {
    * 选择空间
    * 选择空间
    */
    */
   selectSpace(spaceId: string): void {
   selectSpace(spaceId: string): void {
-    this.activeSpaceId = spaceId;
+    this.activeProductId = spaceId;
     this.cdr.markForCheck();
     this.cdr.markForCheck();
   }
   }
 
 
@@ -464,11 +464,11 @@ export class StageDeliveryComponent implements OnInit {
    * 切换空间选择状态
    * 切换空间选择状态
    */
    */
   toggleSpaceSelection(spaceId: string): void {
   toggleSpaceSelection(spaceId: string): void {
-    const index = this.selectedSpaceIds.indexOf(spaceId);
+    const index = this.selectedProductIds.indexOf(spaceId);
     if (index > -1) {
     if (index > -1) {
-      this.selectedSpaceIds.splice(index, 1);
+      this.selectedProductIds.splice(index, 1);
     } else {
     } else {
-      this.selectedSpaceIds.push(spaceId);
+      this.selectedProductIds.push(spaceId);
     }
     }
     this.cdr.markForCheck();
     this.cdr.markForCheck();
   }
   }
@@ -749,13 +749,13 @@ export class StageDeliveryComponent implements OnInit {
       id: `batch_${Date.now()}`,
       id: `batch_${Date.now()}`,
       name,
       name,
       description,
       description,
-      deliverableIds: [...this.selectedSpaceIds],
+      deliverableIds: [...this.selectedProductIds],
       status: 'draft',
       status: 'draft',
       createdAt: new Date()
       createdAt: new Date()
     };
     };
 
 
     this.deliveryBatches.push(batch);
     this.deliveryBatches.push(batch);
-    this.selectedSpaceIds = [];
+    this.selectedProductIds = [];
     this.cdr.markForCheck();
     this.cdr.markForCheck();
   }
   }
 
 
@@ -869,20 +869,20 @@ export class StageDeliveryComponent implements OnInit {
    * 获取空间图标
    * 获取空间图标
    */
    */
   getSpaceIcon(spaceType: string): string {
   getSpaceIcon(spaceType: string): string {
-    return this.multiSpaceService.getSpaceIcon(spaceType);
+    return this.productSpaceService.getProductIcon(spaceType);
   }
   }
 
 
   /**
   /**
    * 获取空间类型名称
    * 获取空间类型名称
    */
    */
   getSpaceTypeName(spaceType: string): string {
   getSpaceTypeName(spaceType: string): string {
-    return this.multiSpaceService.getSpaceTypeName(spaceType);
+    return this.productSpaceService.getProductTypeName(spaceType);
   }
   }
 
 
   /**
   /**
    * 获取空间显示名称
    * 获取空间显示名称
    */
    */
-  getSpaceDisplayName(space: ProjectSpace): string {
+  getSpaceDisplayName(space: Project): string {
     return space.name || this.getSpaceTypeName(space.type);
     return space.name || this.getSpaceTypeName(space.type);
   }
   }
 
 
@@ -890,8 +890,8 @@ export class StageDeliveryComponent implements OnInit {
    * 获取当前空间的交付物
    * 获取当前空间的交付物
    */
    */
   getCurrentSpaceDeliverables(): any[] {
   getCurrentSpaceDeliverables(): any[] {
-    if (!this.activeSpaceId) return this.deliverables;
-    return this.deliverables.filter(d => d.spaceId === this.activeSpaceId);
+    if (!this.activeProductId) return this.deliverables;
+    return this.deliverables.filter(d => d.spaceId === this.activeProductId);
   }
   }
 
 
   /**
   /**
@@ -900,8 +900,8 @@ export class StageDeliveryComponent implements OnInit {
   getFilteredDeliverables(): any[] {
   getFilteredDeliverables(): any[] {
     let filtered = this.deliverables;
     let filtered = this.deliverables;
 
 
-    if (this.deliveryView === 'spaces' && this.activeSpaceId) {
-      filtered = filtered.filter(d => d.spaceId === this.activeSpaceId);
+    if (this.deliveryView === 'spaces' && this.activeProductId) {
+      filtered = filtered.filter(d => d.spaceId === this.activeProductId);
     }
     }
 
 
     if (this.deliveryView === 'processes' && this.selectedProcessType) {
     if (this.deliveryView === 'processes' && this.selectedProcessType) {

+ 13 - 13
src/modules/project/pages/project-detail/stages/stage-order.component.ts

@@ -4,7 +4,7 @@ import { FormsModule } from '@angular/forms';
 import { ActivatedRoute } from '@angular/router';
 import { ActivatedRoute } from '@angular/router';
 import { FmodeObject, FmodeParse } from 'fmode-ng/parse';
 import { FmodeObject, FmodeParse } from 'fmode-ng/parse';
 import { ProjectFileService } from '../../../services/project-file.service';
 import { ProjectFileService } from '../../../services/project-file.service';
-import { MultiSpaceService, ProjectSpace } from '../../../services/multi-space.service';
+import { ProductSpaceService, Project } from '../../../services/product-space.service';
 import {
 import {
   QUOTATION_PRICE_TABLE,
   QUOTATION_PRICE_TABLE,
   STYLE_LEVELS,
   STYLE_LEVELS,
@@ -60,7 +60,7 @@ export class StageOrderComponent implements OnInit {
   };
   };
 
 
   // 空间管理
   // 空间管理
-  projectSpaces: ProjectSpace[] = [];
+  projectSpaces: Project[] = [];
   isMultiSpaceProject: boolean = false;
   isMultiSpaceProject: boolean = false;
   activeSpaceId: string = '';
   activeSpaceId: string = '';
 
 
@@ -204,7 +204,7 @@ export class StageOrderComponent implements OnInit {
   constructor(
   constructor(
     private route: ActivatedRoute,
     private route: ActivatedRoute,
     private projectFileService: ProjectFileService,
     private projectFileService: ProjectFileService,
-    private multiSpaceService: MultiSpaceService
+    private productSpaceService: ProductSpaceService
   ) {
   ) {
     this.checkWxWorkSupport();
     this.checkWxWorkSupport();
   }
   }
@@ -391,7 +391,7 @@ export class StageOrderComponent implements OnInit {
       this.loadingSpaces = true;
       this.loadingSpaces = true;
 
 
       // 从ProjectSpace表加载空间数据
       // 从ProjectSpace表加载空间数据
-      this.projectSpaces = await this.multiSpaceService.getProjectSpaces(this.project.id || '');
+      this.projectSpaces = await this.productSpaceService.getProjectProductSpaces(this.project.id || '');
 
 
       // 如果没有空间数据,但从项目数据中有报价信息,则转换创建默认空间
       // 如果没有空间数据,但从项目数据中有报价信息,则转换创建默认空间
       if (this.projectSpaces.length === 0) {
       if (this.projectSpaces.length === 0) {
@@ -419,7 +419,7 @@ export class StageOrderComponent implements OnInit {
   private async createSpacesFromQuotation(quotationSpaces: any[]): Promise<void> {
   private async createSpacesFromQuotation(quotationSpaces: any[]): Promise<void> {
     for (const spaceData of quotationSpaces) {
     for (const spaceData of quotationSpaces) {
       try {
       try {
-        const space: Partial<ProjectSpace> = {
+        const space: Partial<Project> = {
           name: spaceData.name,
           name: spaceData.name,
           type: this.inferSpaceType(spaceData.name),
           type: this.inferSpaceType(spaceData.name),
           priority: 5,
           priority: 5,
@@ -429,7 +429,7 @@ export class StageOrderComponent implements OnInit {
           order: quotationSpaces.indexOf(spaceData)
           order: quotationSpaces.indexOf(spaceData)
         };
         };
 
 
-        await this.multiSpaceService.createSpace(this.project!.id || '', space);
+        await this.productSpaceService.createProductSpace(this.project!.id || '', space);
       } catch (error) {
       } catch (error) {
         console.error(`创建空间 ${spaceData.name} 失败:`, error);
         console.error(`创建空间 ${spaceData.name} 失败:`, error);
       }
       }
@@ -516,7 +516,7 @@ export class StageOrderComponent implements OnInit {
     if (!spaceName) return;
     if (!spaceName) return;
 
 
     try {
     try {
-      const spaceData: Partial<ProjectSpace> = {
+      const spaceData: Partial<Project> = {
         name: spaceName,
         name: spaceName,
         type: 'other',
         type: 'other',
         priority: 5,
         priority: 5,
@@ -525,7 +525,7 @@ export class StageOrderComponent implements OnInit {
         order: this.projectSpaces.length
         order: this.projectSpaces.length
       };
       };
 
 
-      const createdSpace = await this.multiSpaceService.createSpace(this.projectId!, spaceData);
+      const createdSpace = await this.productSpaceService.createProductSpace(this.projectId!, spaceData);
       this.projectSpaces.push(createdSpace);
       this.projectSpaces.push(createdSpace);
 
 
       // 如果是第一个空间,设置为当前空间
       // 如果是第一个空间,设置为当前空间
@@ -554,7 +554,7 @@ export class StageOrderComponent implements OnInit {
     if (!newName || newName === space.name) return;
     if (!newName || newName === space.name) return;
 
 
     try {
     try {
-      await this.multiSpaceService.updateSpace(spaceId, { name: newName });
+      await this.productSpaceService.updateProductSpace(spaceId, { name: newName });
       space.name = newName;
       space.name = newName;
 
 
       // 更新报价中的空间名称
       // 更新报价中的空间名称
@@ -580,7 +580,7 @@ export class StageOrderComponent implements OnInit {
     if (!confirm('确定要删除这个空间吗?相关数据将被清除。')) return;
     if (!confirm('确定要删除这个空间吗?相关数据将被清除。')) return;
 
 
     try {
     try {
-      await this.multiSpaceService.deleteSpace(spaceId);
+      await this.productSpaceService.deleteProductSpace(spaceId);
 
 
       // 从本地列表中移除
       // 从本地列表中移除
       this.projectSpaces = this.projectSpaces.filter(s => s.id !== spaceId);
       this.projectSpaces = this.projectSpaces.filter(s => s.id !== spaceId);
@@ -702,13 +702,13 @@ export class StageOrderComponent implements OnInit {
    */
    */
   getSpaceProgress(spaceId: string): number {
   getSpaceProgress(spaceId: string): number {
     if (!spaceId || !this.isMultiSpaceProject) return 0;
     if (!spaceId || !this.isMultiSpaceProject) return 0;
-    return this.multiSpaceService.calculateSpaceProgress(spaceId, this.processTypes.map(p => p.key));
+    return this.productSpaceService.calculateProductProgress(spaceId, this.processTypes.map(p => p.key));
   }
   }
 
 
   /**
   /**
    * 获取空间状态
    * 获取空间状态
    */
    */
-  getSpaceStatus(space: ProjectSpace): string {
+  getSpaceStatus(space: Project): string {
     if (!space) return 'unknown';
     if (!space) return 'unknown';
     return space.status;
     return space.status;
   }
   }
@@ -1522,7 +1522,7 @@ export class StageOrderComponent implements OnInit {
   /**
   /**
    * 获取当前选中的空间信息
    * 获取当前选中的空间信息
    */
    */
-  get currentSpace(): ProjectSpace | null {
+  get currentSpace(): Project | null {
     if (!this.isMultiSpaceProject || !this.activeSpaceId) return null;
     if (!this.isMultiSpaceProject || !this.activeSpaceId) return null;
     return this.projectSpaces.find(s => s.id === this.activeSpaceId) || null;
     return this.projectSpaces.find(s => s.id === this.activeSpaceId) || null;
   }
   }

+ 25 - 25
src/modules/project/pages/project-detail/stages/stage-requirements.component.html

@@ -11,18 +11,18 @@
 <!-- 确认需求内容 -->
 <!-- 确认需求内容 -->
 @if (!loading) {
 @if (!loading) {
   <div class="stage-requirements-container">
   <div class="stage-requirements-container">
-    <!-- 多空间切换器 -->
-    @if (isMultiSpaceProject) {
+    <!-- 多产品切换器 -->
+    @if (isMultiProductProject) {
       <div class="space-selector">
       <div class="space-selector">
         <div class="space-tabs">
         <div class="space-tabs">
-          @for (space of projectSpaces; track space.id) {
+          @for (product of projectProducts; track product.id) {
             <button
             <button
               class="space-tab"
               class="space-tab"
-              [class.active]="activeSpaceId === space.id"
-              (click)="selectSpace(space.id)">
-              <span class="space-icon">{{ getSpaceIcon(space.type) }}</span>
-              <span>{{ getSpaceDisplayName(space) }}</span>
-              <span class="progress-indicator" [style.width.%]="calculateSpaceCompletion(space.id)"></span>
+              [class.active]="activeProductId === product.id"
+              (click)="selectProduct(product.id)">
+              <span class="space-icon">{{ getSpaceIcon(product.type) }}</span>
+              <span>{{ getProductDisplayName(product) }}</span>
+              <span class="progress-indicator" [style.width.%]="calculateProductCompletion(product.id)"></span>
             </button>
             </button>
           }
           }
         </div>
         </div>
@@ -38,18 +38,18 @@
           (click)="selectRequirementsSegment('global')">
           (click)="selectRequirementsSegment('global')">
           全局需求
           全局需求
         </button>
         </button>
-        @if (isMultiSpaceProject) {
+        @if (isMultiProductProject) {
           <button
           <button
             class="segment-btn"
             class="segment-btn"
             [class.active]="requirementsSegment === 'spaces'"
             [class.active]="requirementsSegment === 'spaces'"
             (click)="selectRequirementsSegment('spaces')">
             (click)="selectRequirementsSegment('spaces')">
-            空间需求
+            产品需求
           </button>
           </button>
           <button
           <button
             class="segment-btn"
             class="segment-btn"
             [class.active]="requirementsSegment === 'cross-space'"
             [class.active]="requirementsSegment === 'cross-space'"
             (click)="selectRequirementsSegment('cross-space')">
             (click)="selectRequirementsSegment('cross-space')">
-            跨空间协调
+            跨产品协调
           </button>
           </button>
         }
         }
       </div>
       </div>
@@ -77,7 +77,7 @@
                       {{ getImageTypeLabel(image.type) }}
                       {{ getImageTypeLabel(image.type) }}
                     </span>
                     </span>
                     @if (image.spaceId) {
                     @if (image.spaceId) {
-                      <span class="badge badge-outline">{{ getSpaceDisplayNameById(image.spaceId || '') }}</span>
+                      <span class="badge badge-outline">{{ getProductDisplayNameById(image.spaceId || '') }}</span>
                     }
                     }
                     @if (canEdit) {
                     @if (canEdit) {
                       <button
                       <button
@@ -97,7 +97,7 @@
                     id="referenceFileInput"
                     id="referenceFileInput"
                     accept="image/*"
                     accept="image/*"
                     multiple
                     multiple
-                    (change)="uploadReferenceImage($event, activeSpaceId)"
+                    (change)="uploadReferenceImage($event, activeProductId)"
                     [disabled]="uploading"
                     [disabled]="uploading"
                     hidden />
                     hidden />
                   <button
                   <button
@@ -137,7 +137,7 @@
                       <h4>{{ file.name }}</h4>
                       <h4>{{ file.name }}</h4>
                       <p>{{ formatFileSize(file.size) }} · {{ file.uploadTime | date:'yyyy-MM-dd HH:mm' }}</p>
                       <p>{{ formatFileSize(file.size) }} · {{ file.uploadTime | date:'yyyy-MM-dd HH:mm' }}</p>
                       @if (file.spaceId) {
                       @if (file.spaceId) {
-                        <span class="badge badge-outline">{{ getSpaceDisplayNameById(file.spaceId || '') }}</span>
+                        <span class="badge badge-outline">{{ getProductDisplayNameById(file.spaceId || '') }}</span>
                       }
                       }
                     </div>
                     </div>
                     @if (canEdit) {
                     @if (canEdit) {
@@ -157,7 +157,7 @@
                 type="file"
                 type="file"
                 accept=".dwg,.dxf,.pdf"
                 accept=".dwg,.dxf,.pdf"
                 multiple
                 multiple
-                (change)="uploadCAD($event, activeSpaceId)"
+                (change)="uploadCAD($event, activeProductId)"
                 [disabled]="uploading"
                 [disabled]="uploading"
                 hidden
                 hidden
                 id="cadFileInput" />
                 id="cadFileInput" />
@@ -314,16 +314,16 @@
     }
     }
 
 
     <!-- 空间需求 -->
     <!-- 空间需求 -->
-    @if (requirementsSegment === 'spaces' && isMultiSpaceProject) {
+    @if (requirementsSegment === 'spaces' && isMultiProductProject) {
       <div class="space-requirements">
       <div class="space-requirements">
-        @for (space of projectSpaces; track space.id) {
-          <div class="card space-requirement-card" [class.active]="activeSpaceId === space.id">
+        @for (space of projectProducts; track space.id) {
+          <div class="card space-requirement-card" [class.active]="activeProductId === space.id">
             <div class="card-header">
             <div class="card-header">
               <h3 class="card-title">
               <h3 class="card-title">
                 <span class="space-icon">{{ getSpaceIcon(space.type) }}</span>
                 <span class="space-icon">{{ getSpaceIcon(space.type) }}</span>
                 {{ getSpaceDisplayName(space) }}
                 {{ getSpaceDisplayName(space) }}
               </h3>
               </h3>
-              <span class="completion-badge">{{ calculateSpaceCompletion(space.id) }}%</span>
+              <span class="completion-badge">{{ calculateProductCompletion(space.id) }}%</span>
             </div>
             </div>
             <div class="card-content">
             <div class="card-content">
               <!-- 这里可以添加空间特定的需求字段 -->
               <!-- 这里可以添加空间特定的需求字段 -->
@@ -331,7 +331,7 @@
                 <label class="form-label">空间特殊要求</label>
                 <label class="form-label">空间特殊要求</label>
                 <textarea
                 <textarea
                   class="form-textarea"
                   class="form-textarea"
-                  [(ngModel)]="currentSpaceSpecificRequirements"
+                  [(ngModel)]="currentProductSpecificRequirements"
                   [disabled]="!canEdit"
                   [disabled]="!canEdit"
                   rows="2"
                   rows="2"
                   placeholder="描述该空间的特殊要求"></textarea>
                   placeholder="描述该空间的特殊要求"></textarea>
@@ -383,7 +383,7 @@
     }
     }
 
 
     <!-- 跨空间协调需求 -->
     <!-- 跨空间协调需求 -->
-    @if (requirementsSegment === 'cross-space' && isMultiSpaceProject) {
+    @if (requirementsSegment === 'cross-space' && isMultiProductProject) {
       <div class="cross-space-requirements">
       <div class="cross-space-requirements">
         <div class="card">
         <div class="card">
           <div class="card-header">
           <div class="card-header">
@@ -391,7 +391,7 @@
               <ion-icon name="links"></ion-icon>
               <ion-icon name="links"></ion-icon>
               跨空间协调需求
               跨空间协调需求
             </h3>
             </h3>
-            <button class="btn btn-outline btn-sm" (click)="createCrossSpaceRequirement()">
+            <button class="btn btn-outline btn-sm" (click)="createCrossProductRequirement()">
               <ion-icon name="add"></ion-icon>
               <ion-icon name="add"></ion-icon>
               添加协调需求
               添加协调需求
             </button>
             </button>
@@ -410,7 +410,7 @@
                       <span class="badge badge-primary">{{ getCrossSpaceRequirementTypeName(requirement.type) }}</span>
                       <span class="badge badge-primary">{{ getCrossSpaceRequirementTypeName(requirement.type) }}</span>
                       <button
                       <button
                         class="btn-icon btn-danger"
                         class="btn-icon btn-danger"
-                        (click)="deleteCrossSpaceRequirement(requirement.id)">
+                        (click)="deleteCrossProductRequirement(requirement.id)">
                         <ion-icon name="trash"></ion-icon>
                         <ion-icon name="trash"></ion-icon>
                       </button>
                       </button>
                     </div>
                     </div>
@@ -420,7 +420,7 @@
                       <div class="space-tags">
                       <div class="space-tags">
                         @for (spaceId of getRelatedSpaceIds(requirement); track spaceId) {
                         @for (spaceId of getRelatedSpaceIds(requirement); track spaceId) {
                           <span class="badge badge-outline">
                           <span class="badge badge-outline">
-                            {{ getSpaceDisplayNameById(spaceId) }}
+                            {{ getProductDisplayNameById(spaceId) }}
                           </span>
                           </span>
                         }
                         }
                       </div>
                       </div>
@@ -546,7 +546,7 @@
             </div>
             </div>
 
 
             <!-- 跨空间协调方案 -->
             <!-- 跨空间协调方案 -->
-            @if (aiSolution.crossSpaceCoordination && isMultiSpaceProject) {
+            @if (aiSolution.crossSpaceCoordination && isMultiProductProject) {
               <div class="cross-space-coordination">
               <div class="cross-space-coordination">
                 <h4>跨空间协调方案</h4>
                 <h4>跨空间协调方案</h4>
                 <div class="coordination-items">
                 <div class="coordination-items">

+ 91 - 80
src/modules/project/pages/project-detail/stages/stage-requirements.component.ts

@@ -3,9 +3,10 @@ import { CommonModule } from '@angular/common';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { ActivatedRoute } from '@angular/router';
 import { ActivatedRoute } from '@angular/router';
 import { IonIcon } from '@ionic/angular/standalone';
 import { IonIcon } from '@ionic/angular/standalone';
+import { ProductSpaceService, Project } from '../../../services/product-space.service';
 
 
 /**
 /**
- * 确认需求阶段组件 - 多空间支持
+ * 确认需求阶段组件 - Product表统一空间管理
  */
  */
 @Component({
 @Component({
   selector: 'app-stage-requirements',
   selector: 'app-stage-requirements',
@@ -25,11 +26,11 @@ export class StageRequirementsComponent implements OnInit {
   cid: string = '';
   cid: string = '';
   projectId: string = '';
   projectId: string = '';
 
 
-  // 空间管理
-  projectSpaces: any[] = [];
-  isMultiSpaceProject: boolean = false;
-  activeSpaceId: string = '';
-  selectedSpaceIds: string[] = [];
+  // Product-based 空间管理
+  projectProducts: Project[] = [];
+  isMultiProductProject: boolean = false;
+  activeProductId: string = '';
+  selectedProductIds: string[] = [];
 
 
   // 需求分段管理
   // 需求分段管理
   requirementsSegment: string = 'global'; // global | spaces | cross-space
   requirementsSegment: string = 'global'; // global | spaces | cross-space
@@ -127,7 +128,8 @@ export class StageRequirementsComponent implements OnInit {
 
 
   constructor(
   constructor(
     private route: ActivatedRoute,
     private route: ActivatedRoute,
-    private cdr: ChangeDetectorRef
+    private cdr: ChangeDetectorRef,
+    private productSpaceService: ProductSpaceService
   ) {}
   ) {}
 
 
   async ngOnInit() {
   async ngOnInit() {
@@ -148,17 +150,19 @@ export class StageRequirementsComponent implements OnInit {
     try {
     try {
       this.loading = true;
       this.loading = true;
 
 
-      // 模拟加载项目空间数据
-      this.projectSpaces = [
-        { id: '1', name: '客厅', type: 'living_room', area: 25, priority: 5, status: 'not_started', complexity: 'medium', order: 0 },
-        { id: '2', name: '主卧', type: 'bedroom', area: 18, priority: 4, status: 'not_started', complexity: 'medium', order: 1 },
-        { id: '3', name: '厨房', type: 'kitchen', area: 12, priority: 5, status: 'not_started', complexity: 'high', order: 2 }
-      ];
-      this.isMultiSpaceProject = this.projectSpaces.length > 1;
+      // 从项目ID加载Product数据
+      if (this.projectId) {
+        this.projectProducts = await this.productSpaceService.getProjectProductSpaces(this.projectId);
+      } else if (this.project) {
+        // 如果有project对象,使用其ID
+        this.projectProducts = await this.productSpaceService.getProjectProductSpaces(this.project.id);
+      }
 
 
-      // 如果有空间,默认选中第一个
-      if (this.projectSpaces.length > 0 && !this.activeSpaceId) {
-        this.activeSpaceId = this.projectSpaces[0].id;
+      this.isMultiProductProject = this.projectProducts.length > 1;
+
+      // 如果有产品,默认选中第一个
+      if (this.projectProducts.length > 0 && !this.activeProductId) {
+        this.activeProductId = this.projectProducts[0].id;
       }
       }
 
 
       // 模拟加载需求数据
       // 模拟加载需求数据
@@ -216,22 +220,22 @@ export class StageRequirementsComponent implements OnInit {
   }
   }
 
 
   /**
   /**
-   * 选择空间
+   * 选择产品空间
    */
    */
-  selectSpace(spaceId: string): void {
-    this.activeSpaceId = spaceId;
+  selectProduct(productId: string): void {
+    this.activeProductId = productId;
     this.cdr.markForCheck();
     this.cdr.markForCheck();
   }
   }
 
 
   /**
   /**
-   * 切换空间选择状态
+   * 切换产品选择状态
    */
    */
-  toggleSpaceSelection(spaceId: string): void {
-    const index = this.selectedSpaceIds.indexOf(spaceId);
+  toggleProductSelection(productId: string): void {
+    const index = this.selectedProductIds.indexOf(productId);
     if (index > -1) {
     if (index > -1) {
-      this.selectedSpaceIds.splice(index, 1);
+      this.selectedProductIds.splice(index, 1);
     } else {
     } else {
-      this.selectedSpaceIds.push(spaceId);
+      this.selectedProductIds.push(productId);
     }
     }
     this.cdr.markForCheck();
     this.cdr.markForCheck();
   }
   }
@@ -239,7 +243,7 @@ export class StageRequirementsComponent implements OnInit {
   /**
   /**
    * 上传参考图片
    * 上传参考图片
    */
    */
-  async uploadReferenceImage(event: any, spaceId?: string): Promise<void> {
+  async uploadReferenceImage(event: any, productId?: string): Promise<void> {
     const files = event.target.files;
     const files = event.target.files;
     if (!files || files.length === 0) return;
     if (!files || files.length === 0) return;
 
 
@@ -268,7 +272,7 @@ export class StageRequirementsComponent implements OnInit {
           name: file.name,
           name: file.name,
           type: 'style',
           type: 'style',
           uploadTime: new Date(),
           uploadTime: new Date(),
-          spaceId: spaceId || this.activeSpaceId,
+          spaceId: productId || this.activeProductId, // 保持兼容性,后续可改为productId
           tags: []
           tags: []
         };
         };
 
 
@@ -302,7 +306,7 @@ export class StageRequirementsComponent implements OnInit {
   /**
   /**
    * 上传CAD文件
    * 上传CAD文件
    */
    */
-  async uploadCAD(event: any, spaceId?: string): Promise<void> {
+  async uploadCAD(event: any, productId?: string): Promise<void> {
     const files = event.target.files;
     const files = event.target.files;
     if (!files || files.length === 0) return;
     if (!files || files.length === 0) return;
 
 
@@ -333,7 +337,7 @@ export class StageRequirementsComponent implements OnInit {
           name: file.name,
           name: file.name,
           uploadTime: new Date(),
           uploadTime: new Date(),
           size: file.size,
           size: file.size,
-          spaceId: spaceId || this.activeSpaceId
+          spaceId: productId || this.activeProductId // 保持兼容性,后续可改为productId
         };
         };
 
 
         // 添加到CAD文件列表
         // 添加到CAD文件列表
@@ -376,16 +380,16 @@ export class StageRequirementsComponent implements OnInit {
       this.aiSolution = {
       this.aiSolution = {
         generated: true,
         generated: true,
         content: `基于您的${this.globalRequirements.stylePreference}风格需求,我们为您设计了以下方案...`,
         content: `基于您的${this.globalRequirements.stylePreference}风格需求,我们为您设计了以下方案...`,
-        spaces: this.projectSpaces.map(space => ({
-          id: space.id,
-          name: space.name,
-          type: space.type,
-          styleDescription: `${this.globalRequirements.stylePreference}风格${space.name}设计`,
+        spaces: this.projectProducts.map(product => ({
+          id: product.id,
+          name: product.name,
+          type: product.type,
+          styleDescription: `${this.globalRequirements.stylePreference}风格${product.name}设计`,
           colorPalette: [this.globalRequirements.colorScheme.primary, this.globalRequirements.colorScheme.secondary, this.globalRequirements.colorScheme.accent],
           colorPalette: [this.globalRequirements.colorScheme.primary, this.globalRequirements.colorScheme.secondary, this.globalRequirements.colorScheme.accent],
           materials: ['实木', '大理石', '布艺'],
           materials: ['实木', '大理石', '布艺'],
-          furnitureRecommendations: this.getFurnitureRecommendations(space.type),
-          estimatedCost: this.calculateSpaceEstimatedCost(space),
-          timeline: this.calculateSpaceTimeline(space)
+          furnitureRecommendations: this.getFurnitureRecommendations(product.type),
+          estimatedCost: this.calculateProductEstimatedCost(product),
+          timeline: this.calculateProductTimeline(product)
         })),
         })),
         estimatedCost: this.globalRequirements.overallBudget.max || this.calculateTotalEstimatedCost(),
         estimatedCost: this.globalRequirements.overallBudget.max || this.calculateTotalEstimatedCost(),
         timeline: this.calculateTotalTimeline(),
         timeline: this.calculateTotalTimeline(),
@@ -417,9 +421,9 @@ export class StageRequirementsComponent implements OnInit {
   }
   }
 
 
   /**
   /**
-   * 计算空间估算成本
+   * 计算产品估算成本
    */
    */
-  private calculateSpaceEstimatedCost(space: any): number {
+  private calculateProductEstimatedCost(product: Project): number {
     const baseCostPerSqm: Record<string, number> = {
     const baseCostPerSqm: Record<string, number> = {
       'living_room': 1500,
       'living_room': 1500,
       'bedroom': 1200,
       'bedroom': 1200,
@@ -428,7 +432,7 @@ export class StageRequirementsComponent implements OnInit {
       'dining_room': 1300,
       'dining_room': 1300,
       'study': 1100
       'study': 1100
     };
     };
-    const baseCost = (baseCostPerSqm[space.type] || 1000) * (space.area || 10);
+    const baseCost = (baseCostPerSqm[product.type] || 1000) * (product.area || 10);
     const qualityMultiplier: Record<string, number> = {
     const qualityMultiplier: Record<string, number> = {
       'standard': 1.0,
       'standard': 1.0,
       'premium': 1.5,
       'premium': 1.5,
@@ -441,15 +445,15 @@ export class StageRequirementsComponent implements OnInit {
    * 计算总估算成本
    * 计算总估算成本
    */
    */
   private calculateTotalEstimatedCost(): number {
   private calculateTotalEstimatedCost(): number {
-    return this.projectSpaces.reduce((total, space) => {
-      return total + this.calculateSpaceEstimatedCost(space);
+    return this.projectProducts.reduce((total, product) => {
+      return total + this.calculateProductEstimatedCost(product);
     }, 0);
     }, 0);
   }
   }
 
 
   /**
   /**
-   * 计算空间工期
+   * 计算产品工期
    */
    */
-  private calculateSpaceTimeline(space: any): string {
+  private calculateProductTimeline(product: Project): string {
     const baseDays: Record<string, number> = {
     const baseDays: Record<string, number> = {
       'living_room': 15,
       'living_room': 15,
       'bedroom': 12,
       'bedroom': 12,
@@ -458,14 +462,14 @@ export class StageRequirementsComponent implements OnInit {
       'dining_room': 10,
       'dining_room': 10,
       'study': 8
       'study': 8
     };
     };
-    return `${baseDays[space.type] || 10}-15个工作日`;
+    return `${baseDays[product.type] || 10}-15个工作日`;
   }
   }
 
 
   /**
   /**
    * 计算总工期
    * 计算总工期
    */
    */
   private calculateTotalTimeline(): string {
   private calculateTotalTimeline(): string {
-    const totalDays = this.projectSpaces.reduce((total, space) => {
+    const totalDays = this.projectProducts.reduce((total, product) => {
       const baseDays: Record<string, number> = {
       const baseDays: Record<string, number> = {
         'living_room': 15,
         'living_room': 15,
         'bedroom': 12,
         'bedroom': 12,
@@ -474,7 +478,7 @@ export class StageRequirementsComponent implements OnInit {
         'dining_room': 10,
         'dining_room': 10,
         'study': 8
         'study': 8
       };
       };
-      return total + (baseDays[space.type] || 10);
+      return total + (baseDays[product.type] || 10);
     }, 0);
     }, 0);
     return `预计${Math.ceil(totalDays * 0.7)}-${totalDays}个工作日(考虑并行施工)`;
     return `预计${Math.ceil(totalDays * 0.7)}-${totalDays}个工作日(考虑并行施工)`;
   }
   }
@@ -589,12 +593,12 @@ export class StageRequirementsComponent implements OnInit {
   }
   }
 
 
   /**
   /**
-   * 获取当前空间的需求
+   * 获取当前产品的需求
    */
    */
-  getCurrentSpaceRequirement(): any {
-    if (!this.activeSpaceId) return null;
-    return this.spaceRequirements.find(req => req.spaceId === this.activeSpaceId) || {
-      spaceId: this.activeSpaceId,
+  getCurrentProductRequirement(): any {
+    if (!this.activeProductId) return null;
+    return this.spaceRequirements.find(req => req.productId === this.activeProductId) || {
+      productId: this.activeProductId,
       colorRequirement: {},
       colorRequirement: {},
       spaceStructureRequirement: {},
       spaceStructureRequirement: {},
       materialRequirement: {},
       materialRequirement: {},
@@ -604,24 +608,24 @@ export class StageRequirementsComponent implements OnInit {
   }
   }
 
 
   /**
   /**
-   * 获取当前空间特殊需求的 getter/setter
+   * 获取当前产品特殊需求的 getter/setter
    */
    */
-  get currentSpaceSpecificRequirements(): string {
-    const requirement = this.getCurrentSpaceRequirement();
+  get currentProductSpecificRequirements(): string {
+    const requirement = this.getCurrentProductRequirement();
     return requirement?.specificRequirements || '';
     return requirement?.specificRequirements || '';
   }
   }
 
 
-  set currentSpaceSpecificRequirements(value: string) {
-    const requirement = this.getCurrentSpaceRequirement();
+  set currentProductSpecificRequirements(value: string) {
+    const requirement = this.getCurrentProductRequirement();
     if (requirement) {
     if (requirement) {
       requirement.specificRequirements = value;
       requirement.specificRequirements = value;
     }
     }
   }
   }
 
 
   /**
   /**
-   * 获取当前空间的评价
+   * 获取当前产品的评价
    */
    */
-  getCurrentSpaceFeedback(): any {
+  getCurrentProductFeedback(): any {
     return null;
     return null;
   }
   }
 
 
@@ -629,20 +633,20 @@ export class StageRequirementsComponent implements OnInit {
    * 过滤参考图片
    * 过滤参考图片
    */
    */
   getFilteredReferenceImages(): typeof this.referenceImages {
   getFilteredReferenceImages(): typeof this.referenceImages {
-    if (!this.isMultiSpaceProject || !this.activeSpaceId) {
+    if (!this.isMultiProductProject || !this.activeProductId) {
       return this.referenceImages;
       return this.referenceImages;
     }
     }
-    return this.referenceImages.filter(img => !img.spaceId || img.spaceId === this.activeSpaceId);
+    return this.referenceImages.filter(img => !img.spaceId || img.spaceId === this.activeProductId);
   }
   }
 
 
   /**
   /**
    * 过滤CAD文件
    * 过滤CAD文件
    */
    */
   getFilteredCADFiles(): typeof this.cadFiles {
   getFilteredCADFiles(): typeof this.cadFiles {
-    if (!this.isMultiSpaceProject || !this.activeSpaceId) {
+    if (!this.isMultiProductProject || !this.activeProductId) {
       return this.cadFiles;
       return this.cadFiles;
     }
     }
-    return this.cadFiles.filter(file => !file.spaceId || file.spaceId === this.activeSpaceId);
+    return this.cadFiles.filter(file => !file.spaceId || file.spaceId === this.activeProductId);
   }
   }
 
 
   /**
   /**
@@ -752,10 +756,10 @@ export class StageRequirementsComponent implements OnInit {
     if (this.globalRequirements.timeline) completedItems++;
     if (this.globalRequirements.timeline) completedItems++;
     totalItems++;
     totalItems++;
 
 
-    // 空间需求检查
-    for (const space of this.projectSpaces) {
-      const spaceFeedback = this.spaceRequirements.find(req => req.spaceId === space.id);
-      if (spaceFeedback) {
+    // 产品需求检查
+    for (const product of this.projectProducts) {
+      const productFeedback = this.spaceRequirements.find(req => req.productId === product.id);
+      if (productFeedback) {
         completedItems++;
         completedItems++;
       }
       }
       totalItems++;
       totalItems++;
@@ -765,25 +769,25 @@ export class StageRequirementsComponent implements OnInit {
   }
   }
 
 
   /**
   /**
-   * 计算空间完成度
+   * 计算产品完成度
    */
    */
-  calculateSpaceCompletion(_spaceId: string): number {
-    // 简化计算,实际应该基于空间的具体需求完成情况
+  calculateProductCompletion(_productId: string): number {
+    // 简化计算,实际应该基于产品的具体需求完成情况
     return Math.floor(Math.random() * 100);
     return Math.floor(Math.random() * 100);
   }
   }
 
 
   /**
   /**
-   * 创建跨空间需求
+   * 创建跨产品需求
    */
    */
-  async createCrossSpaceRequirement(requirement?: any): Promise<void> {
-    console.log('创建跨空间需求:', requirement || {});
+  async createCrossProductRequirement(requirement?: any): Promise<void> {
+    console.log('创建跨产品需求:', requirement || {});
   }
   }
 
 
   /**
   /**
-   * 删除跨空间需求
+   * 删除跨产品需求
    */
    */
-  async deleteCrossSpaceRequirement(requirementId: string): Promise<void> {
-    console.log('删除跨空间需求:', requirementId);
+  async deleteCrossProductRequirement(requirementId: string): Promise<void> {
+    console.log('删除跨产品需求:', requirementId);
   }
   }
 
 
   /**
   /**
@@ -830,11 +834,18 @@ export class StageRequirementsComponent implements OnInit {
   }
   }
 
 
   /**
   /**
-   * 根据空间ID获取空间显示名称(用于模板)
+   * 根据产品ID获取产品显示名称(用于模板)
+   */
+  getProductDisplayNameById(productId: string): string {
+    const product = this.projectProducts.find(p => p.id === productId);
+    return this.getProductDisplayName(product);
+  }
+
+  /**
+   * 获取产品显示名称
    */
    */
-  getSpaceDisplayNameById(spaceId: string): string {
-    const space = this.projectSpaces.find(s => s.id === spaceId);
-    return this.getSpaceDisplayName(space);
+  getProductDisplayName(product: Project | undefined): string {
+    return product?.name || '未知产品';
   }
   }
 
 
   /**
   /**

+ 0 - 495
src/modules/project/services/multi-space.service.ts

@@ -1,495 +0,0 @@
-import { Injectable } from '@angular/core';
-import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
-
-const Parse = FmodeParse.with('nova');
-
-export interface ProjectSpace {
-  id: string;
-  name: string;
-  type: string;
-  area?: number;
-  priority: number;
-  status: string;
-  complexity: string;
-  metadata?: any;
-  estimatedBudget?: number;
-  estimatedDuration?: number;
-  order: number;
-}
-
-export interface SpaceProgress {
-  spaceId: string;
-  stage: string;
-  progress: number;
-  status: string;
-  timeline?: any[];
-  blockers?: string[];
-  estimatedCompletion?: Date;
-  actualCompletion?: Date;
-}
-
-export interface SpaceRequirement {
-  spaceId: string;
-  spaceName: string;
-  spaceType: string;
-  colorRequirement: any;
-  spaceStructureRequirement: any;
-  materialRequirement: any;
-  lightingRequirement: any;
-  specificRequirements: any;
-  referenceImages?: string[];
-  referenceFiles?: any[];
-}
-
-export interface CrossSpaceRequirement {
-  id: string;
-  type: string;
-  description: string;
-  primarySpaceId: string;
-  relatedSpaceIds: string[];
-  requirements: any;
-}
-
-@Injectable({
-  providedIn: 'root'
-})
-export class MultiSpaceService {
-
-  constructor() {}
-
-  /**
-   * 创建项目空间
-   */
-  async createSpace(projectId: string, spaceData: Partial<ProjectSpace>): Promise<ProjectSpace> {
-    try {
-      const ProjectSpace = Parse.Object.extend('ProjectSpace');
-      const space = new ProjectSpace();
-
-      // 获取项目
-      const projectQuery = new Parse.Query('Project');
-      const project = await projectQuery.get(projectId);
-
-      // 设置空间字段
-      space.set('project', project);
-      space.set('name', spaceData.name || '');
-      space.set('type', spaceData.type || 'other');
-      space.set('area', spaceData.area || 0);
-      space.set('priority', spaceData.priority || 5);
-      space.set('status', spaceData.status || 'not_started');
-      space.set('complexity', spaceData.complexity || 'medium');
-      space.set('metadata', spaceData.metadata || {});
-      space.set('estimatedBudget', spaceData.estimatedBudget || 0);
-      space.set('estimatedDuration', spaceData.estimatedDuration || 0);
-      space.set('order', spaceData.order || 0);
-
-      const savedSpace = await space.save();
-      return this.parseSpaceData(savedSpace);
-
-    } catch (error) {
-      console.error('创建空间失败:', error);
-      throw error;
-    }
-  }
-
-  /**
-   * 获取项目空间列表
-   */
-  async getProjectSpaces(projectId: string): Promise<ProjectSpace[]> {
-    try {
-      const query = new Parse.Query('ProjectSpace');
-      query.equalTo('project', {
-        __type: 'Pointer',
-        className: 'Project',
-        objectId: projectId
-      });
-      query.ascending('order');
-      query.equalTo('isDeleted', false);
-
-      const results = await query.find();
-      return results.map(space => this.parseSpaceData(space));
-
-    } catch (error) {
-      console.error('获取项目空间失败:', error);
-      return [];
-    }
-  }
-
-  /**
-   * 更新空间信息
-   */
-  async updateSpace(spaceId: string, updateData: Partial<ProjectSpace>): Promise<ProjectSpace> {
-    try {
-      const query = new Parse.Query('ProjectSpace');
-      const space = await query.get(spaceId);
-
-      // 更新字段
-      if (updateData.name !== undefined) space.set('name', updateData.name);
-      if (updateData.type !== undefined) space.set('type', updateData.type);
-      if (updateData.area !== undefined) space.set('area', updateData.area);
-      if (updateData.priority !== undefined) space.set('priority', updateData.priority);
-      if (updateData.status !== undefined) space.set('status', updateData.status);
-      if (updateData.complexity !== undefined) space.set('complexity', updateData.complexity);
-      if (updateData.metadata !== undefined) space.set('metadata', updateData.metadata);
-      if (updateData.estimatedBudget !== undefined) space.set('estimatedBudget', updateData.estimatedBudget);
-      if (updateData.estimatedDuration !== undefined) space.set('estimatedDuration', updateData.estimatedDuration);
-      if (updateData.order !== undefined) space.set('order', updateData.order);
-
-      const savedSpace = await space.save();
-      return this.parseSpaceData(savedSpace);
-
-    } catch (error) {
-      console.error('更新空间失败:', error);
-      throw error;
-    }
-  }
-
-  /**
-   * 删除空间
-   */
-  async deleteSpace(spaceId: string): Promise<void> {
-    try {
-      const query = new Parse.Query('ProjectSpace');
-      const space = await query.get(spaceId);
-      space.set('isDeleted', true);
-      await space.save();
-
-    } catch (error) {
-      console.error('删除空间失败:', error);
-      throw error;
-    }
-  }
-
-  /**
-   * 更新空间进度
-   */
-  async updateSpaceProgress(progressData: SpaceProgress): Promise<void> {
-    try {
-      // 查找现有进度记录
-      const query = new Parse.Query('SpaceProgress');
-      query.equalTo('spaceId', {
-        __type: 'Pointer',
-        className: 'ProjectSpace',
-        objectId: progressData.spaceId
-      });
-      query.equalTo('stage', progressData.stage);
-
-      let progressRecord = await query.first();
-
-      if (!progressRecord) {
-        // 创建新记录
-        const SpaceProgress = Parse.Object.extend('SpaceProgress');
-        progressRecord = new SpaceProgress();
-        progressRecord.set('spaceId', {
-          __type: 'Pointer',
-          className: 'ProjectSpace',
-          objectId: progressData.spaceId
-        });
-      }
-
-      // 更新进度数据
-      progressRecord.set('stage', progressData.stage);
-      progressRecord.set('progress', progressData.progress);
-      progressRecord.set('status', progressData.status);
-      progressRecord.set('timeline', progressData.timeline || []);
-      progressRecord.set('blockers', progressData.blockers || []);
-      progressRecord.set('estimatedCompletion', progressData.estimatedCompletion);
-      progressRecord.set('actualCompletion', progressData.actualCompletion);
-
-      await progressRecord.save();
-
-    } catch (error) {
-      console.error('更新空间进度失败:', error);
-      throw error;
-    }
-  }
-
-  /**
-   * 获取空间进度
-   */
-  async getSpaceProgress(spaceId: string, stage?: string): Promise<SpaceProgress[]> {
-    try {
-      const query = new Parse.Query('SpaceProgress');
-      query.equalTo('spaceId', {
-        __type: 'Pointer',
-        className: 'ProjectSpace',
-        objectId: spaceId
-      });
-
-      if (stage) {
-        query.equalTo('stage', stage);
-      }
-
-      const results = await query.find();
-      return results.map(record => ({
-        spaceId: record.get('spaceId')?.objectId,
-        stage: record.get('stage'),
-        progress: record.get('progress'),
-        status: record.get('status'),
-        timeline: record.get('timeline'),
-        blockers: record.get('blockers'),
-        estimatedCompletion: record.get('estimatedCompletion'),
-        actualCompletion: record.get('actualCompletion')
-      }));
-
-    } catch (error) {
-      console.error('获取空间进度失败:', error);
-      return [];
-    }
-  }
-
-  /**
-   * 创建空间依赖关系
-   */
-  async createSpaceDependency(
-    projectId: string,
-    fromSpaceId: string,
-    toSpaceId: string,
-    dependencyType: string,
-    description: string
-  ): Promise<void> {
-    try {
-      const SpaceDependency = Parse.Object.extend('SpaceDependency');
-      const dependency = new SpaceDependency();
-
-      // 获取项目
-      const projectQuery = new Parse.Query('Project');
-      const project = await projectQuery.get(projectId);
-
-      dependency.set('project', project);
-      dependency.set('fromSpace', {
-        __type: 'Pointer',
-        className: 'ProjectSpace',
-        objectId: fromSpaceId
-      });
-      dependency.set('toSpace', {
-        __type: 'Pointer',
-        className: 'ProjectSpace',
-        objectId: toSpaceId
-      });
-      dependency.set('type', dependencyType);
-      dependency.set('description', description);
-      dependency.set('status', 'pending');
-      dependency.set('confidence', 0.8);
-
-      await dependency.save();
-
-    } catch (error) {
-      console.error('创建空间依赖失败:', error);
-      throw error;
-    }
-  }
-
-  /**
-   * 获取空间依赖关系
-   */
-  async getSpaceDependencies(projectId: string): Promise<any[]> {
-    try {
-      const query = new Parse.Query('SpaceDependency');
-      query.equalTo('project', {
-        __type: 'Pointer',
-        className: 'Project',
-        objectId: projectId
-      });
-      query.include('fromSpace', 'toSpace');
-
-      const results = await query.find();
-      return results.map(record => ({
-        id: record.id,
-        fromSpace: record.get('fromSpace'),
-        toSpace: record.get('toSpace'),
-        type: record.get('type'),
-        description: record.get('description'),
-        status: record.get('status'),
-        confidence: record.get('confidence')
-      }));
-
-    } catch (error) {
-      console.error('获取空间依赖失败:', error);
-      return [];
-    }
-  }
-
-  /**
-   * 保存空间需求
-   */
-  async saveSpaceRequirements(
-    projectId: string,
-    spaceId: string,
-    requirements: SpaceRequirement
-  ): Promise<void> {
-    try {
-      // 查找多空间需求记录
-      const query = new Parse.Query('MultiSpaceRequirement');
-      query.equalTo('project', {
-        __type: 'Pointer',
-        className: 'Project',
-        objectId: projectId
-      });
-
-      let multiSpaceReq = await query.first();
-
-      if (!multiSpaceReq) {
-        // 创建多空间需求记录
-        const MultiSpaceRequirement = Parse.Object.extend('MultiSpaceRequirement');
-        multiSpaceReq = new MultiSpaceRequirement();
-        multiSpaceReq.set('project', {
-          __type: 'Pointer',
-          className: 'Project',
-          objectId: projectId
-        });
-        multiSpaceReq.set('requirementId', `req_${Date.now()}`);
-        multiSpaceReq.set('globalRequirements', {});
-        multiSpaceReq.set('spaceRequirements', []);
-        multiSpaceReq.set('crossSpaceRequirements', []);
-        multiSpaceReq.set('status', 'draft');
-        multiSpaceReq.set('completenessCheck', {});
-        multiSpaceReq.set('createdAt', new Date());
-        multiSpaceReq.set('updatedAt', new Date());
-
-        await multiSpaceReq.save();
-      }
-
-      // 创建空间需求记录
-      const SpaceRequirement = Parse.Object.extend('SpaceRequirement');
-      const spaceRequirement = new SpaceRequirement();
-
-      spaceRequirement.set('multiSpaceReqId', multiSpaceReq);
-      spaceRequirement.set('spaceId', {
-        __type: 'Pointer',
-        className: 'ProjectSpace',
-        objectId: spaceId
-      });
-      spaceRequirement.set('spaceName', requirements.spaceName);
-      spaceRequirement.set('spaceType', requirements.spaceType);
-      spaceRequirement.set('colorRequirement', requirements.colorRequirement);
-      spaceRequirement.set('spaceStructureRequirement', requirements.spaceStructureRequirement);
-      spaceRequirement.set('materialRequirement', requirements.materialRequirement);
-      spaceRequirement.set('lightingRequirement', requirements.lightingRequirement);
-      spaceRequirement.set('specificRequirements', requirements.specificRequirements);
-
-      await spaceRequirement.save();
-
-    } catch (error) {
-      console.error('保存空间需求失败:', error);
-      throw error;
-    }
-  }
-
-  /**
-   * 获取空间需求
-   */
-  async getSpaceRequirements(projectId: string, spaceId?: string): Promise<SpaceRequirement[]> {
-    try {
-      // 获取多空间需求记录
-      const multiSpaceQuery = new Parse.Query('MultiSpaceRequirement');
-      multiSpaceQuery.equalTo('project', {
-        __type: 'Pointer',
-        className: 'Project',
-        objectId: projectId
-      });
-
-      const multiSpaceReq = await multiSpaceQuery.first();
-      if (!multiSpaceReq) return [];
-
-      // 获取空间需求记录
-      const query = new Parse.Query('SpaceRequirement');
-      query.equalTo('multiSpaceReqId', multiSpaceReq);
-
-      if (spaceId) {
-        query.equalTo('spaceId', {
-          __type: 'Pointer',
-          className: 'ProjectSpace',
-          objectId: spaceId
-        });
-      }
-
-      const results = await query.find();
-      return results.map(record => ({
-        spaceId: record.get('spaceId')?.objectId,
-        spaceName: record.get('spaceName'),
-        spaceType: record.get('spaceType'),
-        colorRequirement: record.get('colorRequirement'),
-        spaceStructureRequirement: record.get('spaceStructureRequirement'),
-        materialRequirement: record.get('materialRequirement'),
-        lightingRequirement: record.get('lightingRequirement'),
-        specificRequirements: record.get('specificRequirements'),
-        referenceImages: record.get('referenceImages') || [],
-        referenceFiles: record.get('referenceFiles') || []
-      }));
-
-    } catch (error) {
-      console.error('获取空间需求失败:', error);
-      return [];
-    }
-  }
-
-  /**
-   * 计算空间完成进度
-   */
-  calculateSpaceProgress(spaceId: string, allStages: string[]): number {
-    // 这里需要从缓存或数据库中获取各阶段进度
-    // 暂时返回模拟数据
-    return Math.floor(Math.random() * 100);
-  }
-
-  /**
-   * 获取空间类型图标
-   */
-  getSpaceIcon(spaceType: string): string {
-    const iconMap: Record<string, string> = {
-      'living_room': 'living-room',
-      'bedroom': 'bedroom',
-      'kitchen': 'kitchen',
-      'bathroom': 'bathroom',
-      'dining_room': 'dining-room',
-      'study': 'study',
-      'balcony': 'balcony',
-      'corridor': 'corridor',
-      'storage': 'storage',
-      'entrance': 'entrance',
-      'other': 'room'
-    };
-
-    return iconMap[spaceType] || 'room';
-  }
-
-  /**
-   * 获取空间类型名称
-   */
-  getSpaceTypeName(spaceType: string): string {
-    const nameMap: Record<string, string> = {
-      'living_room': '客厅',
-      'bedroom': '卧室',
-      'kitchen': '厨房',
-      'bathroom': '卫生间',
-      'dining_room': '餐厅',
-      'study': '书房',
-      'balcony': '阳台',
-      'corridor': '走廊',
-      'storage': '储物间',
-      'entrance': '玄关',
-      'other': '其他'
-    };
-
-    return nameMap[spaceType] || '其他';
-  }
-
-  /**
-   * 解析空间数据
-   */
-  private parseSpaceData(space: any): ProjectSpace {
-    return {
-      id: space.id,
-      name: space.get('name'),
-      type: space.get('type'),
-      area: space.get('area'),
-      priority: space.get('priority'),
-      status: space.get('status'),
-      complexity: space.get('complexity'),
-      metadata: space.get('metadata'),
-      estimatedBudget: space.get('estimatedBudget'),
-      estimatedDuration: space.get('estimatedDuration'),
-      order: space.get('order')
-    };
-  }
-}

+ 493 - 0
src/modules/project/services/product-space.service.ts

@@ -0,0 +1,493 @@
+import { Injectable } from '@angular/core';
+import { FmodeParse } from 'fmode-ng/parse';
+
+const Parse = FmodeParse.with('nova');
+
+export interface Project {
+  id: string;
+  name: string; // productName
+  type: string; // productType
+  area?: number; // space.area
+  priority: number; // space.priority
+  status: string; // Product.status
+  complexity: string; // space.complexity
+  metadata?: any; // space metadata
+  estimatedBudget?: number; // quotation.price
+  estimatedDuration?: number; // space metadata
+  order: number;
+  projectId: string;
+  designerId?: string; // profile pointer
+  quotation?: any; // Product.quotation
+  requirements?: any; // Product.requirements
+  reviews?: any; // Product.reviews
+}
+
+export interface ProductProgress {
+  productId: string;
+  stage: string;
+  progress: number;
+  status: string;
+  timeline?: any[];
+  blockers?: string[];
+  estimatedCompletion?: Date;
+  actualCompletion?: Date;
+}
+
+export interface ProductRequirement {
+  productId: string;
+  productName: string;
+  productType: string;
+  colorRequirement: any;
+  spaceStructureRequirement: any;
+  materialRequirement: any;
+  lightingRequirement: any;
+  specificRequirements: any;
+  referenceImages?: string[];
+  referenceFiles?: any[];
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ProductSpaceService {
+
+  constructor() {}
+
+  /**
+   * 创建产品空间(即创建Product)
+   */
+  async createProductSpace(projectId: string, spaceData: Partial<Project>): Promise<Project> {
+    try {
+      const Product = Parse.Object.extend('Product');
+      const product = new Product();
+
+      // 获取项目
+      const projectQuery = new Parse.Query('Project');
+      const project = await projectQuery.get(projectId);
+
+      // 设置产品字段
+      product.set('project', project);
+      product.set('productName', spaceData.name || '');
+      product.set('productType', spaceData.type || 'other');
+      product.set('status', spaceData.status || 'not_started');
+
+      // 设置空间信息
+      product.set('space', {
+        spaceName: spaceData.name || '',
+        area: spaceData.area || 0,
+        dimensions: { length: 0, width: 0, height: 0 },
+        features: [],
+        constraints: [],
+        priority: spaceData.priority || 5,
+        complexity: spaceData.complexity || 'medium'
+      });
+
+      // 设置报价信息
+      if (spaceData.estimatedBudget) {
+        product.set('quotation', {
+          price: spaceData.estimatedBudget,
+          currency: 'CNY',
+          breakdown: {},
+          status: 'draft',
+          validUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
+        });
+      }
+
+      // 设置需求信息
+      product.set('requirements', {
+        colorRequirement: spaceData.requirements?.colorRequirement || {},
+        spaceStructureRequirement: spaceData.requirements?.spaceStructureRequirement || {},
+        materialRequirement: spaceData.requirements?.materialRequirement || {},
+        lightingRequirement: spaceData.requirements?.lightingRequirement || {},
+        specificRequirements: spaceData.requirements?.specificRequirements || [],
+        constraints: {}
+      });
+
+      // 设置设计师
+      if (spaceData.designerId) {
+        const designerQuery = new Parse.Query('Profile');
+        const designer = await designerQuery.get(spaceData.designerId);
+        product.set('profile', designer);
+      }
+
+      const savedProduct = await product.save();
+      return this.parseProductData(savedProduct);
+
+    } catch (error) {
+      console.error('创建产品空间失败:', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 获取项目产品空间列表
+   */
+  async getProjectProductSpaces(projectId: string): Promise<Project[]> {
+    try {
+      const query = new Parse.Query('Product');
+      query.equalTo('project', {
+        __type: 'Pointer',
+        className: 'Project',
+        objectId: projectId
+      });
+      query.include('profile');
+      query.ascending('createdAt');
+
+      const results = await query.find();
+      return results.map(product => this.parseProductData(product));
+
+    } catch (error) {
+      console.error('获取项目产品空间失败:', error);
+      return [];
+    }
+  }
+
+  /**
+   * 更新产品空间信息
+   */
+  async updateProductSpace(productId: string, updateData: Partial<Project>): Promise<Project> {
+    try {
+      const query = new Parse.Query('Product');
+      const product = await query.get(productId);
+
+      // 更新产品基本信息
+      if (updateData.name !== undefined) product.set('productName', updateData.name);
+      if (updateData.type !== undefined) product.set('productType', updateData.type);
+      if (updateData.status !== undefined) product.set('status', updateData.status);
+
+      // 更新空间信息
+      if (updateData.area !== undefined || updateData.priority !== undefined || updateData.complexity !== undefined) {
+        const space = product.get('space') || {};
+        if (updateData.area !== undefined) space.area = updateData.area;
+        if (updateData.priority !== undefined) space.priority = updateData.priority;
+        if (updateData.complexity !== undefined) space.complexity = updateData.complexity;
+        product.set('space', space);
+      }
+
+      // 更新报价信息
+      if (updateData.estimatedBudget !== undefined) {
+        const quotation = product.get('quotation') || {};
+        quotation.price = updateData.estimatedBudget;
+        product.set('quotation', quotation);
+      }
+
+      // 更新需求信息
+      if (updateData.requirements !== undefined) {
+        const requirements = product.get('requirements') || {};
+        Object.assign(requirements, updateData.requirements);
+        product.set('requirements', requirements);
+      }
+
+      // 更新设计师
+      if (updateData.designerId !== undefined) {
+        if (updateData.designerId) {
+          const designerQuery = new Parse.Query('Profile');
+          const designer = await designerQuery.get(updateData.designerId);
+          product.set('profile', designer);
+        } else {
+          product.unset('profile');
+        }
+      }
+
+      const savedProduct = await product.save();
+      return this.parseProductData(savedProduct);
+
+    } catch (error) {
+      console.error('更新产品空间失败:', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 删除产品空间
+   */
+  async deleteProductSpace(productId: string): Promise<void> {
+    try {
+      const query = new Parse.Query('Product');
+      const product = await query.get(productId);
+      await product.destroy();
+
+    } catch (error) {
+      console.error('删除产品空间失败:', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 更新产品进度
+   */
+  async updateProductProgress(progressData: ProductProgress): Promise<void> {
+    try {
+      // 在Product的data字段中存储进度信息
+      const query = new Parse.Query('Product');
+      const product = await query.get(progressData.productId);
+
+      const data = product.get('data') || {};
+      if (!data.progress) data.progress = [];
+
+      // 查找现有进度记录
+      let progressRecord = data.progress.find((p: any) => p.stage === progressData.stage);
+
+      if (!progressRecord) {
+        // 创建新进度记录
+        progressRecord = {
+          stage: progressData.stage,
+          progress: 0,
+          status: 'not_started',
+          timeline: [],
+          blockers: []
+        };
+        data.progress.push(progressRecord);
+      }
+
+      // 更新进度数据
+      progressRecord.progress = progressData.progress;
+      progressRecord.status = progressData.status;
+      progressRecord.timeline = progressData.timeline || [];
+      progressRecord.blockers = progressData.blockers || [];
+      progressRecord.estimatedCompletion = progressData.estimatedCompletion;
+      progressRecord.actualCompletion = progressData.actualCompletion;
+
+      product.set('data', data);
+      await product.save();
+
+    } catch (error) {
+      console.error('更新产品进度失败:', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 获取产品进度
+   */
+  async getProductProgress(productId: string, stage?: string): Promise<ProductProgress[]> {
+    try {
+      const query = new Parse.Query('Product');
+      const product = await query.get(productId);
+
+      const data = product.get('data') || {};
+      const progressData = data.progress || [];
+
+      let result = progressData;
+      if (stage) {
+        result = progressData.filter((p: any) => p.stage === stage);
+      }
+
+      return result.map((p: any) => ({
+        productId: productId,
+        stage: p.stage,
+        progress: p.progress,
+        status: p.status,
+        timeline: p.timeline,
+        blockers: p.blockers,
+        estimatedCompletion: p.estimatedCompletion,
+        actualCompletion: p.actualCompletion
+      }));
+
+    } catch (error) {
+      console.error('获取产品进度失败:', error);
+      return [];
+    }
+  }
+
+  /**
+   * 创建产品依赖关系
+   */
+  async createProductDependency(
+    projectId: string,
+    fromProductId: string,
+    toProductId: string,
+    dependencyType: string,
+    description: string
+  ): Promise<void> {
+    try {
+      // 在项目的data字段中存储产品依赖关系
+      const projectQuery = new Parse.Query('Project');
+      const project = await projectQuery.get(projectId);
+
+      const data = project.get('data') || {};
+      if (!data.productDependencies) data.productDependencies = [];
+
+      const dependency = {
+        id: `dep_${Date.now()}`,
+        fromProductId: fromProductId,
+        toProductId: toProductId,
+        type: dependencyType,
+        description: description,
+        status: 'pending',
+        confidence: 0.8,
+        createdAt: new Date()
+      };
+
+      data.productDependencies.push(dependency);
+      project.set('data', data);
+      await project.save();
+
+    } catch (error) {
+      console.error('创建产品依赖失败:', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 获取产品依赖关系
+   */
+  async getProductDependencies(projectId: string): Promise<any[]> {
+    try {
+      const projectQuery = new Parse.Query('Project');
+      const project = await projectQuery.get(projectId);
+
+      const data = project.get('data') || {};
+      return data.productDependencies || [];
+
+    } catch (error) {
+      console.error('获取产品依赖失败:', error);
+      return [];
+    }
+  }
+
+  /**
+   * 保存产品需求
+   */
+  async saveProductRequirements(
+    _projectId: string,
+    productId: string,
+    requirements: ProductRequirement
+  ): Promise<void> {
+    try {
+      const query = new Parse.Query('Product');
+      const product = await query.get(productId);
+
+      // 更新产品的需求信息
+      product.set('requirements', {
+        colorRequirement: requirements.colorRequirement,
+        spaceStructureRequirement: requirements.spaceStructureRequirement,
+        materialRequirement: requirements.materialRequirement,
+        lightingRequirement: requirements.lightingRequirement,
+        specificRequirements: requirements.specificRequirements,
+        constraints: {}
+      });
+
+      await product.save();
+
+    } catch (error) {
+      console.error('保存产品需求失败:', error);
+      throw error;
+    }
+  }
+
+  /**
+   * 获取产品需求
+   */
+  async getProductRequirements(projectId: string, productId?: string): Promise<ProductRequirement[]> {
+    try {
+      let query = new Parse.Query('Product');
+      query.equalTo('project', {
+        __type: 'Pointer',
+        className: 'Project',
+        objectId: projectId
+      });
+
+      if (productId) {
+        query.equalTo('objectId', productId);
+      }
+
+      const results = await query.find();
+      return results.map(product => ({
+        productId: product.id || '',
+        productName: product.get('productName'),
+        productType: product.get('productType'),
+        colorRequirement: product.get('requirements')?.colorRequirement || {},
+        spaceStructureRequirement: product.get('requirements')?.spaceStructureRequirement || {},
+        materialRequirement: product.get('requirements')?.materialRequirement || {},
+        lightingRequirement: product.get('requirements')?.lightingRequirement || {},
+        specificRequirements: product.get('requirements')?.specificRequirements || [],
+        referenceImages: product.get('requirements')?.referenceImages || [],
+        referenceFiles: product.get('requirements')?.referenceFiles || []
+      }));
+
+    } catch (error) {
+      console.error('获取产品需求失败:', error);
+      return [];
+    }
+  }
+
+  /**
+   * 计算产品完成进度
+   */
+  calculateProductProgress(_productId: string, _allStages: string[]): number {
+    // 这里需要从缓存或数据库中获取各阶段进度
+    // 暂时返回模拟数据
+    return Math.floor(Math.random() * 100);
+  }
+
+  /**
+   * 获取产品类型图标
+   */
+  getProductIcon(productType: string): string {
+    const iconMap: Record<string, string> = {
+      'living_room': 'living-room',
+      'bedroom': 'bedroom',
+      'kitchen': 'kitchen',
+      'bathroom': 'bathroom',
+      'dining_room': 'dining-room',
+      'study': 'study',
+      'balcony': 'balcony',
+      'corridor': 'corridor',
+      'storage': 'storage',
+      'entrance': 'entrance',
+      'other': 'room'
+    };
+
+    return iconMap[productType] || 'room';
+  }
+
+  /**
+   * 获取产品类型名称
+   */
+  getProductTypeName(productType: string): string {
+    const nameMap: Record<string, string> = {
+      'living_room': '客厅',
+      'bedroom': '卧室',
+      'kitchen': '厨房',
+      'bathroom': '卫生间',
+      'dining_room': '餐厅',
+      'study': '书房',
+      'balcony': '阳台',
+      'corridor': '走廊',
+      'storage': '储物间',
+      'entrance': '玄关',
+      'other': '其他'
+    };
+
+    return nameMap[productType] || '其他';
+  }
+
+  /**
+   * 解析产品数据
+   */
+  private parseProductData(product: any): Project {
+    const space = product.get('space') || {};
+    const quotation = product.get('quotation') || {};
+    const profile = product.get('profile');
+
+    return {
+      id: product.id,
+      name: product.get('productName'),
+      type: product.get('productType'),
+      area: space.area,
+      priority: space.priority || 5,
+      status: product.get('status'),
+      complexity: space.complexity || 'medium',
+      metadata: space.metadata || {},
+      estimatedBudget: quotation.price,
+      estimatedDuration: space.estimatedDuration,
+      order: 0, // 使用创建时间排序
+      projectId: product.get('project')?.objectId,
+      designerId: profile?.id,
+      quotation: quotation,
+      requirements: product.get('requirements'),
+      reviews: product.get('reviews')
+    };
+  }
+}