Browse Source

feat(requirements-confirm-card): 新增需求映射功能与相关组件

- 添加需求映射测试进度显示,支持上传文件的实时分析与映射结果展示。
- 引入色轮可视化,增强色彩分析结果的可视化效果。
- 更新需求确认卡片,整合映射数据更新事件,提升用户交互体验。
- 优化样式以支持新功能,确保界面美观与功能性。

此更新旨在提升需求映射的可用性和用户体验,确保用户能够顺畅地进行需求确认与映射操作。
徐福静0235668 5 months ago
parent
commit
c9d8f86ecb
22 changed files with 15351 additions and 1715 deletions
  1. 798 0
      copy/color-analysis.service.ts
  2. 5283 0
      copy/project-detail.ts
  3. 2425 0
      copy/requirements-confirm-card.scss
  4. 2086 0
      copy/requirements-confirm-card.ts
  5. 866 0
      copy/requirements-confirm-card1.html
  6. 888 0
      copy/requirements-confirm-card2.html
  7. 546 0
      copy/upload-success-modal.component.html
  8. 0 5
      src/app/app.routes.ts
  9. 0 284
      src/app/components/test-requirement-mapping/test-requirement-mapping.component.html
  10. 0 558
      src/app/components/test-requirement-mapping/test-requirement-mapping.component.scss
  11. 0 299
      src/app/components/test-requirement-mapping/test-requirement-mapping.component.ts
  12. 48 6
      src/app/pages/designer/project-detail/horizontal-panel.scss
  13. 187 143
      src/app/pages/designer/project-detail/project-detail.html
  14. 94 10
      src/app/pages/designer/project-detail/project-detail.scss
  15. 272 69
      src/app/pages/designer/project-detail/project-detail.ts
  16. 252 272
      src/app/shared/components/requirements-confirm-card/requirements-confirm-card.html
  17. 607 0
      src/app/shared/components/requirements-confirm-card/requirements-confirm-card.scss
  18. 585 9
      src/app/shared/components/requirements-confirm-card/requirements-confirm-card.ts
  19. 135 33
      src/app/shared/components/upload-success-modal/upload-success-modal.component.html
  20. 15 0
      src/app/shared/components/upload-success-modal/upload-success-modal.component.scss
  21. 218 1
      src/app/shared/components/upload-success-modal/upload-success-modal.component.ts
  22. 46 26
      src/app/shared/services/color-analysis.service.ts

+ 798 - 0
copy/color-analysis.service.ts

@@ -0,0 +1,798 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable, BehaviorSubject, throwError, forkJoin, of } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
+import { FormAnalysisService } from './form-analysis.service';
+import { TextureAnalysisService } from './texture-analysis.service';
+import { PatternAnalysisService } from './pattern-analysis.service';
+import { LightingAnalysisService } from './lighting-analysis.service';
+
+export interface UploadedFile {
+  id: string;
+  name: string;
+  url: string;
+  size?: number;
+  type?: string;
+  preview?: string;
+}
+
+export interface ColorAnalysisResult {
+  colors: Array<{
+    hex: string;
+    rgb: { r: number; g: number; b: number };
+    percentage: number;
+    name?: string; // 添加可选的name属性
+  }>;
+  originalImage: string;
+  mosaicImage: string;
+  reportPath: string;
+  // 新增增强分析结果
+  enhancedAnalysis?: EnhancedColorAnalysis;
+  // 新增其他分析结果
+  formAnalysis?: any;
+  textureAnalysis?: any;
+  patternAnalysis?: any;
+  lightingAnalysis?: any;
+}
+
+// 新增:增强色彩分析结果接口
+export interface EnhancedColorAnalysis {
+  colorWheel: ColorWheelData;
+  colorHarmony: ColorHarmonyAnalysis;
+  colorTemperature: ColorTemperatureAnalysis;
+  colorPsychology: ColorPsychologyAnalysis;
+}
+
+// 色轮数据
+export interface ColorWheelData {
+  dominantHue: number; // 主色调角度 (0-360)
+  saturationRange: { min: number; max: number }; // 饱和度范围
+  brightnessRange: { min: number; max: number }; // 亮度范围
+  colorDistribution: Array<{
+    hue: number;
+    saturation: number;
+    brightness: number;
+    percentage: number;
+  }>;
+}
+
+// 色彩和谐度分析
+export interface ColorHarmonyAnalysis {
+  harmonyType: 'monochromatic' | 'analogous' | 'complementary' | 'triadic' | 'tetradic' | 'split-complementary';
+  harmonyScore: number; // 0-100
+  suggestions: string[];
+  relationships: Array<{
+    color1: string;
+    color2: string;
+    relationship: string;
+    strength: number;
+  }>;
+}
+
+// 色温详细分析
+export interface ColorTemperatureAnalysis {
+  averageTemperature: number; // 开尔文值
+  temperatureRange: { min: number; max: number };
+  warmCoolBalance: number; // -100(冷) 到 100(暖)
+  temperatureDescription: string;
+  lightingRecommendations: string[];
+}
+
+// 色彩心理学分析
+export interface ColorPsychologyAnalysis {
+  mood: string; // 整体情绪
+  atmosphere: string; // 氛围描述
+  psychologicalEffects: string[];
+  suitableSpaces: string[]; // 适合的空间类型
+  emotionalImpact: {
+    energy: number; // 0-100
+    warmth: number; // 0-100
+    sophistication: number; // 0-100
+    comfort: number; // 0-100
+  };
+}
+
+export interface AnalysisProgress {
+  stage: 'preparing' | 'processing' | 'extracting' | 'generating' | 'completed' | 'error';
+  message: string;
+  progress: number;
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ColorAnalysisService {
+  private readonly API_BASE = '/api/color-analysis';
+  private analysisProgress$ = new BehaviorSubject<AnalysisProgress>({
+    stage: 'preparing',
+    message: '准备分析...',
+    progress: 0
+  });
+
+  constructor(
+    private http: HttpClient,
+    private formAnalysisService: FormAnalysisService,
+    private textureAnalysisService: TextureAnalysisService,
+    private patternAnalysisService: PatternAnalysisService,
+    private lightingAnalysisService: LightingAnalysisService
+  ) {}
+
+  /**
+   * 获取分析进度
+   */
+  getAnalysisProgress(): Observable<AnalysisProgress> {
+    return this.analysisProgress$.asObservable();
+  }
+
+  /**
+   * 分析图片颜色
+   * @param imageFile 图片文件
+   * @param options 分析选项
+   */
+  analyzeImageColors(imageFile: File, options?: {
+    mosaicSize?: number;
+    maxColors?: number;
+  }): Observable<ColorAnalysisResult> {
+    const formData = new FormData();
+    formData.append('image', imageFile);
+    
+    if (options?.mosaicSize) {
+      formData.append('mosaicSize', options.mosaicSize.toString());
+    }
+    if (options?.maxColors) {
+      formData.append('maxColors', options.maxColors.toString());
+    }
+
+    return this.http.post<any>(`${this.API_BASE}/analyze`, formData).pipe(
+      map((response: any) => {
+        if (response && response.success) {
+          return this.parseAnalysisResult(response.data);
+        }
+        throw new Error('分析结果无效');
+      }),
+      catchError(error => {
+        console.error('颜色分析失败:', error);
+        return throwError(() => new Error('颜色分析服务暂时不可用'));
+      })
+    );
+  }
+
+  /**
+   * 分析上传的图片文件
+   * @param file 上传的文件信息
+   */
+  analyzeImage(file: UploadedFile): Observable<ColorAnalysisResult> {
+    // 暂时使用模拟分析,避免API 404错误
+    console.log('使用模拟分析处理文件:', file.name);
+    
+    // 创建一个File对象用于模拟分析
+    return new Observable(observer => {
+      // 如果有预览URL,尝试获取文件
+      if (file.preview || file.url) {
+        fetch(file.preview || file.url)
+          .then(response => response.blob())
+          .then(blob => {
+            const mockFile = new File([blob], file.name, { type: file.type || 'image/jpeg' });
+            this.simulateAnalysis(mockFile).subscribe({
+              next: (result) => observer.next(result),
+              error: (error) => observer.error(error),
+              complete: () => observer.complete()
+            });
+          })
+          .catch(error => {
+            console.warn('无法获取文件内容,使用默认模拟数据:', error);
+            // 如果无法获取文件,直接返回模拟结果
+            this.getDefaultMockResult(file).subscribe({
+              next: (result) => observer.next(result),
+              error: (error) => observer.error(error),
+              complete: () => observer.complete()
+            });
+          });
+      } else {
+        // 没有文件URL,直接返回模拟结果
+        this.getDefaultMockResult(file).subscribe({
+          next: (result) => observer.next(result),
+          error: (error) => observer.error(error),
+          complete: () => observer.complete()
+        });
+      }
+    });
+  }
+
+  /**
+   * 获取默认模拟结果
+   */
+  private getDefaultMockResult(file: UploadedFile): Observable<ColorAnalysisResult> {
+    return new Observable(observer => {
+      this.updateProgress('processing', '开始分析...', 10);
+      
+      setTimeout(() => {
+        this.updateProgress('extracting', '提取颜色信息...', 50);
+        
+        setTimeout(() => {
+          this.updateProgress('generating', '生成分析报告...', 80);
+          
+          setTimeout(() => {
+            const mockResult: ColorAnalysisResult = {
+              colors: [
+                { hex: '#FF6B6B', rgb: { r: 255, g: 107, b: 107 }, percentage: 25.5, name: '珊瑚红' },
+                { hex: '#4ECDC4', rgb: { r: 78, g: 205, b: 196 }, percentage: 18.3, name: '青绿色' },
+                { hex: '#45B7D1', rgb: { r: 69, g: 183, b: 209 }, percentage: 15.7, name: '天蓝色' },
+                { hex: '#96CEB4', rgb: { r: 150, g: 206, b: 180 }, percentage: 12.1, name: '薄荷绿' },
+                { hex: '#FFEAA7', rgb: { r: 255, g: 234, b: 167 }, percentage: 10.8, name: '柠檬黄' },
+                { hex: '#DDA0DD', rgb: { r: 221, g: 160, b: 221 }, percentage: 8.9, name: '紫罗兰' },
+                { hex: '#98D8C8', rgb: { r: 152, g: 216, b: 200 }, percentage: 8.7, name: '海泡石绿' }
+              ],
+              originalImage: file.preview || file.url || '/assets/images/placeholder.jpg',
+              mosaicImage: file.preview || file.url || '/assets/images/placeholder.jpg',
+              reportPath: '/mock-report.html',
+              enhancedAnalysis: this.performEnhancedColorAnalysis([
+                { hex: '#FF6B6B', rgb: { r: 255, g: 107, b: 107 }, percentage: 25.5 },
+                { hex: '#4ECDC4', rgb: { r: 78, g: 205, b: 196 }, percentage: 18.3 },
+                { hex: '#45B7D1', rgb: { r: 69, g: 183, b: 209 }, percentage: 15.7 },
+                { hex: '#96CEB4', rgb: { r: 150, g: 206, b: 180 }, percentage: 12.1 },
+                { hex: '#FFEAA7', rgb: { r: 255, g: 234, b: 167 }, percentage: 10.8 }
+              ])
+            };
+
+            this.updateProgress('completed', '分析完成', 100);
+            observer.next(mockResult);
+            observer.complete();
+          }, 300);
+        }, 400);
+      }, 300);
+    });
+  }
+
+  /**
+   * 获取分析报告
+   * @param reportId 报告ID
+   */
+  getAnalysisReport(reportId: string): Observable<string> {
+    return this.http.get(`${this.API_BASE}/report/${reportId}`, {
+      responseType: 'text'
+    });
+  }
+
+  /**
+   * 批量分析多个图片
+   * @param imageFiles 图片文件数组
+   */
+  analyzeBatchImages(imageFiles: File[]): Observable<ColorAnalysisResult[]> {
+    this.updateProgress('preparing', '准备批量分析...', 0);
+
+    const formData = new FormData();
+    imageFiles.forEach((file, index) => {
+      formData.append(`images`, file);
+    });
+
+    return this.http.post<any>(`${this.API_BASE}/analyze-batch`, formData).pipe(
+      map(response => {
+        this.updateProgress('completed', '批量分析完成', 100);
+        return response.results.map((result: any) => this.parseAnalysisResult(result));
+      }),
+      catchError(error => {
+        this.updateProgress('error', '批量分析失败: ' + (error.message || '未知错误'), 0);
+        return throwError(() => error);
+      })
+    );
+  }
+
+  /**
+   * 检查color-get服务状态
+   */
+  checkServiceStatus(): Observable<boolean> {
+    return this.http.get<{ status: string }>(`${this.API_BASE}/status`).pipe(
+      map(response => response.status === 'ready'),
+      catchError(() => throwError(() => new Error('颜色分析服务不可用')))
+    );
+  }
+
+  /**
+   * 增强色彩分析 - 包含色轮、和谐度、色温、心理学分析
+   * @param colors 基础颜色分析结果
+   */
+  performEnhancedColorAnalysis(colors: Array<{hex: string; rgb: {r: number; g: number; b: number}; percentage: number}>): EnhancedColorAnalysis {
+    return {
+      colorWheel: this.analyzeColorWheel(colors),
+      colorHarmony: this.analyzeColorHarmony(colors),
+      colorTemperature: this.analyzeColorTemperature(colors),
+      colorPsychology: this.analyzeColorPsychology(colors)
+    };
+  }
+
+  /**
+   * 色轮分析
+   */
+  private analyzeColorWheel(colors: Array<{hex: string; rgb: {r: number; g: number; b: number}; percentage: number}>): ColorWheelData {
+    const colorDistribution = colors.map(color => {
+      const hsl = this.rgbToHsl(color.rgb.r, color.rgb.g, color.rgb.b);
+      return {
+        hue: hsl.h,
+        saturation: hsl.s,
+        brightness: hsl.l,
+        percentage: color.percentage
+      };
+    });
+
+    const dominantColor = colorDistribution.reduce((prev, current) => 
+      prev.percentage > current.percentage ? prev : current
+    );
+
+    const saturationValues = colorDistribution.map(c => c.saturation);
+    const brightnessValues = colorDistribution.map(c => c.brightness);
+
+    return {
+      dominantHue: dominantColor.hue,
+      saturationRange: {
+        min: Math.min(...saturationValues),
+        max: Math.max(...saturationValues)
+      },
+      brightnessRange: {
+        min: Math.min(...brightnessValues),
+        max: Math.max(...brightnessValues)
+      },
+      colorDistribution
+    };
+  }
+
+  /**
+   * 色彩和谐度分析
+   */
+  private analyzeColorHarmony(colors: Array<{hex: string; rgb: {r: number; g: number; b: number}; percentage: number}>): ColorHarmonyAnalysis {
+    const hues = colors.map(color => {
+      const hsl = this.rgbToHsl(color.rgb.r, color.rgb.g, color.rgb.b);
+      return { hue: hsl.h, hex: color.hex, percentage: color.percentage };
+    });
+
+    // 分析色彩关系
+    const relationships = [];
+    for (let i = 0; i < hues.length; i++) {
+      for (let j = i + 1; j < hues.length; j++) {
+        const hueDiff = Math.abs(hues[i].hue - hues[j].hue);
+        const minDiff = Math.min(hueDiff, 360 - hueDiff);
+        
+        let relationship = '';
+        let strength = 0;
+        
+        if (minDiff < 30) {
+          relationship = '相似色';
+          strength = 90 - minDiff;
+        } else if (minDiff > 150 && minDiff < 210) {
+          relationship = '互补色';
+          strength = 100 - Math.abs(minDiff - 180);
+        } else if (minDiff > 110 && minDiff < 130) {
+          relationship = '三角色';
+          strength = 100 - Math.abs(minDiff - 120);
+        }
+
+        if (relationship) {
+          relationships.push({
+            color1: hues[i].hex,
+            color2: hues[j].hex,
+            relationship,
+            strength
+          });
+        }
+      }
+    }
+
+    // 确定和谐类型
+    let harmonyType: ColorHarmonyAnalysis['harmonyType'] = 'monochromatic';
+    let harmonyScore = 60;
+
+    if (relationships.some(r => r.relationship === '互补色' && r.strength > 80)) {
+      harmonyType = 'complementary';
+      harmonyScore = 85;
+    } else if (relationships.filter(r => r.relationship === '相似色').length >= 2) {
+      harmonyType = 'analogous';
+      harmonyScore = 75;
+    } else if (relationships.some(r => r.relationship === '三角色')) {
+      harmonyType = 'triadic';
+      harmonyScore = 80;
+    }
+
+    return {
+      harmonyType,
+      harmonyScore,
+      relationships,
+      suggestions: this.generateHarmonySuggestions(harmonyType, harmonyScore)
+    };
+  }
+
+  /**
+   * 色温分析
+   */
+  private analyzeColorTemperature(colors: Array<{hex: string; rgb: {r: number; g: number; b: number}; percentage: number}>): ColorTemperatureAnalysis {
+    const temperatures = colors.map(color => {
+      // 简化的色温计算
+      const temp = this.calculateColorTemperature(color.rgb);
+      return { temperature: temp, percentage: color.percentage };
+    });
+
+    const weightedAverage = temperatures.reduce((sum, t) => sum + t.temperature * t.percentage, 0) / 100;
+    const tempValues = temperatures.map(t => t.temperature);
+    
+    const warmCoolBalance = this.calculateWarmCoolBalance(colors);
+    
+    return {
+      averageTemperature: Math.round(weightedAverage),
+      temperatureRange: {
+        min: Math.min(...tempValues),
+        max: Math.max(...tempValues)
+      },
+      warmCoolBalance,
+      temperatureDescription: this.getTemperatureDescription(weightedAverage),
+      lightingRecommendations: this.generateLightingRecommendations(weightedAverage, warmCoolBalance)
+    };
+  }
+
+  /**
+   * 色彩心理学分析
+   */
+  private analyzeColorPsychology(colors: Array<{hex: string; rgb: {r: number; g: number; b: number}; percentage: number}>): ColorPsychologyAnalysis {
+    const psychologyData = colors.map(color => {
+      const psychology = this.getColorPsychology(color.hex);
+      return { ...psychology, percentage: color.percentage };
+    });
+
+    // 计算加权平均情感影响
+    const emotionalImpact = {
+      energy: Math.round(psychologyData.reduce((sum, p) => sum + p.energy * p.percentage, 0) / 100),
+      warmth: Math.round(psychologyData.reduce((sum, p) => sum + p.warmth * p.percentage, 0) / 100),
+      sophistication: Math.round(psychologyData.reduce((sum, p) => sum + p.sophistication * p.percentage, 0) / 100),
+      comfort: Math.round(psychologyData.reduce((sum, p) => sum + p.comfort * p.percentage, 0) / 100)
+    };
+
+    return {
+      mood: this.determineMood(emotionalImpact),
+      atmosphere: this.determineAtmosphere(emotionalImpact),
+      psychologicalEffects: this.aggregatePsychologicalEffects(psychologyData),
+      suitableSpaces: this.determineSuitableSpaces(emotionalImpact),
+      emotionalImpact
+    };
+  }
+
+  // 辅助方法
+  private rgbToHsl(r: number, g: number, b: number): {h: number, s: number, l: number} {
+    r /= 255; g /= 255; b /= 255;
+    const max = Math.max(r, g, b), min = Math.min(r, g, b);
+    let h = 0, s = 0, l = (max + min) / 2;
+
+    if (max !== min) {
+      const d = max - min;
+      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+      switch (max) {
+        case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+        case g: h = (b - r) / d + 2; break;
+        case b: h = (r - g) / d + 4; break;
+      }
+      h /= 6;
+    }
+
+    return { h: h * 360, s: s * 100, l: l * 100 };
+  }
+
+  private calculateColorTemperature(rgb: {r: number, g: number, b: number}): number {
+    // 简化的色温计算公式
+    const ratio = rgb.b / (rgb.r + rgb.g + rgb.b);
+    return Math.round(2000 + ratio * 4000); // 2000K-6000K范围
+  }
+
+  private calculateWarmCoolBalance(colors: Array<{hex: string; rgb: {r: number; g: number; b: number}; percentage: number}>): number {
+    let warmScore = 0;
+    colors.forEach(color => {
+      const { r, g, b } = color.rgb;
+      const warmness = (r - b) / 255 * 100; // 红色减蓝色的比例
+      warmScore += warmness * color.percentage / 100;
+    });
+    return Math.max(-100, Math.min(100, warmScore));
+  }
+
+  private getTemperatureDescription(temp: number): string {
+    if (temp < 3000) return '暖色调';
+    if (temp < 4000) return '中性偏暖';
+    if (temp < 5000) return '中性色调';
+    if (temp < 6000) return '中性偏冷';
+    return '冷色调';
+  }
+
+  private generateLightingRecommendations(temp: number, balance: number): string[] {
+    const recommendations = [];
+    if (temp < 3500) {
+      recommendations.push('适合使用暖白光照明(2700K-3000K)');
+      recommendations.push('营造温馨舒适氛围');
+    } else if (temp > 5000) {
+      recommendations.push('适合使用冷白光照明(5000K-6500K)');
+      recommendations.push('营造清爽现代的感觉');
+    } else {
+      recommendations.push('适合使用中性白光照明(4000K-4500K)');
+      recommendations.push('平衡温暖与清爽的感觉');
+    }
+    return recommendations;
+  }
+
+  private generateHarmonySuggestions(harmonyType: string, score: number): string[] {
+    const suggestions = [];
+    if (score < 70) {
+      suggestions.push('考虑调整色彩比例以提高和谐度');
+      suggestions.push('可以添加中性色作为过渡');
+    }
+    
+    switch (harmonyType) {
+      case 'complementary':
+        suggestions.push('互补色搭配,建议一主一辅的比例');
+        break;
+      case 'analogous':
+        suggestions.push('相似色搭配,可以添加少量对比色增加活力');
+        break;
+      case 'triadic':
+        suggestions.push('三角色搭配,注意控制各色彩的饱和度');
+        break;
+    }
+    
+    return suggestions;
+  }
+
+  private getColorPsychology(hex: string): {energy: number, warmth: number, sophistication: number, comfort: number} {
+    // 基于色相的心理学属性(简化版)
+    const rgb = this.hexToRgb(hex);
+    if (!rgb) return { energy: 50, warmth: 50, sophistication: 50, comfort: 50 };
+    
+    const hsl = this.rgbToHsl(rgb.r, rgb.g, rgb.b);
+    const hue = hsl.h;
+    
+    // 根据色相确定心理属性
+    if (hue >= 0 && hue < 60) { // 红-橙
+      return { energy: 85, warmth: 90, sophistication: 60, comfort: 70 };
+    } else if (hue >= 60 && hue < 120) { // 黄-绿
+      return { energy: 75, warmth: 70, sophistication: 50, comfort: 80 };
+    } else if (hue >= 120 && hue < 180) { // 绿-青
+      return { energy: 45, warmth: 30, sophistication: 70, comfort: 85 };
+    } else if (hue >= 180 && hue < 240) { // 青-蓝
+      return { energy: 35, warmth: 20, sophistication: 85, comfort: 75 };
+    } else if (hue >= 240 && hue < 300) { // 蓝-紫
+      return { energy: 55, warmth: 40, sophistication: 90, comfort: 65 };
+    } else { // 紫-红
+      return { energy: 70, warmth: 60, sophistication: 80, comfort: 60 };
+    }
+  }
+
+  private hexToRgb(hex: string): {r: number, g: number, b: number} | null {
+    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+    return result ? {
+      r: parseInt(result[1], 16),
+      g: parseInt(result[2], 16),
+      b: parseInt(result[3], 16)
+    } : null;
+  }
+
+  private determineMood(impact: {energy: number, warmth: number, sophistication: number, comfort: number}): string {
+    if (impact.energy > 70 && impact.warmth > 70) return '活力温暖';
+    if (impact.sophistication > 80) return '优雅精致';
+    if (impact.comfort > 80) return '舒适宁静';
+    if (impact.energy > 70) return '充满活力';
+    return '平和稳定';
+  }
+
+  private determineAtmosphere(impact: {energy: number, warmth: number, sophistication: number, comfort: number}): string {
+    if (impact.warmth > 70 && impact.comfort > 70) return '温馨舒适的家居氛围';
+    if (impact.sophistication > 80 && impact.energy < 50) return '高雅静谧的商务氛围';
+    if (impact.energy > 70) return '活跃动感的现代氛围';
+    return '平衡和谐的中性氛围';
+  }
+
+  private aggregatePsychologicalEffects(data: any[]): string[] {
+    const effects = new Set<string>();
+    data.forEach(d => {
+      if (d.energy > 70) effects.add('提升活力和注意力');
+      if (d.warmth > 70) effects.add('营造温暖亲切感');
+      if (d.sophistication > 80) effects.add('增强空间品质感');
+      if (d.comfort > 80) effects.add('促进放松和舒适感');
+    });
+    return Array.from(effects);
+  }
+
+  private determineSuitableSpaces(impact: {energy: number, warmth: number, sophistication: number, comfort: number}): string[] {
+    const spaces = [];
+    if (impact.warmth > 70 && impact.comfort > 70) {
+      spaces.push('客厅', '卧室', '餐厅');
+    }
+    if (impact.sophistication > 80) {
+      spaces.push('办公室', '会议室', '接待区');
+    }
+    if (impact.energy > 70) {
+      spaces.push('工作区', '健身房', '娱乐区');
+    }
+    if (impact.comfort > 80 && impact.energy < 50) {
+      spaces.push('休息区', '阅读角', '冥想室');
+    }
+    return spaces.length > 0 ? spaces : ['通用空间'];
+  }
+
+  /**
+   * 更新分析进度
+   */
+  private updateProgress(stage: AnalysisProgress['stage'], message: string, progress: number): void {
+    this.analysisProgress$.next({ stage, message, progress });
+  }
+
+  /**
+   * 解析分析结果
+   */
+  private parseAnalysisResult(data: any): ColorAnalysisResult {
+    return {
+      colors: data.colors || [],
+      originalImage: data.originalImage || '',
+      mosaicImage: data.mosaicImage || '',
+      reportPath: data.reportPath || ''
+    };
+  }
+
+  /**
+   * 模拟color-get分析过程(用于开发测试)
+   */
+  simulateAnalysis(imageFile: File): Observable<ColorAnalysisResult> {
+    return new Observable(observer => {
+      this.updateProgress('processing', '开始分析...', 10);
+      
+      setTimeout(() => {
+        this.updateProgress('extracting', '提取颜色信息...', 30);
+        
+        setTimeout(() => {
+          this.updateProgress('generating', '生成分析报告...', 70);
+          
+          // 使用forkJoin来并行处理所有分析服务,添加错误处理
+          const analysisObservables = {
+            formAnalysis: this.formAnalysisService.analyzeImageForm(imageFile).pipe(
+              catchError(error => {
+                console.warn('形体分析失败,使用默认值:', error);
+                return of(null);
+              })
+            ),
+            textureAnalysis: this.textureAnalysisService.analyzeImageTexture(imageFile).pipe(
+              catchError(error => {
+                console.warn('质感分析失败,使用默认值:', error);
+                return of(null);
+              })
+            ),
+            patternAnalysis: this.patternAnalysisService.analyzeImagePattern(imageFile).pipe(
+              catchError(error => {
+                console.warn('纹理分析失败,使用默认值:', error);
+                return of(null);
+              })
+            ),
+            lightingAnalysis: this.lightingAnalysisService.analyzeImageLighting(imageFile).pipe(
+              catchError(error => {
+                console.warn('灯光分析失败,使用默认值:', error);
+                return of(null);
+              })
+            )
+          };
+
+          forkJoin(analysisObservables).subscribe({
+            next: (analysisResults) => {
+              const mockResult: ColorAnalysisResult = {
+                colors: [
+                  { hex: '#FF6B6B', rgb: { r: 255, g: 107, b: 107 }, percentage: 25.5 },
+                  { hex: '#4ECDC4', rgb: { r: 78, g: 205, b: 196 }, percentage: 18.3 },
+                  { hex: '#45B7D1', rgb: { r: 69, g: 183, b: 209 }, percentage: 15.7 },
+                  { hex: '#96CEB4', rgb: { r: 150, g: 206, b: 180 }, percentage: 12.1 },
+                  { hex: '#FFEAA7', rgb: { r: 255, g: 234, b: 167 }, percentage: 10.8 },
+                  { hex: '#DDA0DD', rgb: { r: 221, g: 160, b: 221 }, percentage: 8.9 },
+                  { hex: '#98D8C8', rgb: { r: 152, g: 216, b: 200 }, percentage: 8.7 }
+                ],
+                originalImage: URL.createObjectURL(imageFile),
+                mosaicImage: URL.createObjectURL(imageFile), // 在实际应用中这里应该是处理后的图片
+                reportPath: '/mock-report.html',
+                // 添加增强色彩分析
+                enhancedAnalysis: this.performEnhancedColorAnalysis([
+                  { hex: '#FF6B6B', rgb: { r: 255, g: 107, b: 107 }, percentage: 25.5 },
+                  { hex: '#4ECDC4', rgb: { r: 78, g: 205, b: 196 }, percentage: 18.3 },
+                  { hex: '#45B7D1', rgb: { r: 69, g: 183, b: 209 }, percentage: 15.7 },
+                  { hex: '#96CEB4', rgb: { r: 150, g: 206, b: 180 }, percentage: 12.1 },
+                  { hex: '#FFEAA7', rgb: { r: 255, g: 234, b: 167 }, percentage: 10.8 }
+                ]),
+                // 添加其他分析结果,如果分析失败则使用默认值
+                formAnalysis: analysisResults.formAnalysis || this.getDefaultFormAnalysis(),
+                textureAnalysis: analysisResults.textureAnalysis || this.getDefaultTextureAnalysis(),
+                patternAnalysis: analysisResults.patternAnalysis || this.getDefaultPatternAnalysis(),
+                lightingAnalysis: analysisResults.lightingAnalysis || this.getDefaultLightingAnalysis()
+              };
+
+              this.updateProgress('completed', '分析完成', 100);
+              observer.next(mockResult);
+              observer.complete();
+            },
+            error: (error) => {
+              console.error('分析服务出错:', error);
+              // 即使分析服务出错,也返回基本的颜色分析结果
+              const fallbackResult: ColorAnalysisResult = {
+                colors: [
+                  { hex: '#FF6B6B', rgb: { r: 255, g: 107, b: 107 }, percentage: 25.5, name: '珊瑚红' },
+                  { hex: '#4ECDC4', rgb: { r: 78, g: 205, b: 196 }, percentage: 18.3, name: '青绿色' },
+                  { hex: '#45B7D1', rgb: { r: 69, g: 183, b: 209 }, percentage: 15.7, name: '天蓝色' }
+                ],
+                originalImage: URL.createObjectURL(imageFile),
+                mosaicImage: URL.createObjectURL(imageFile),
+                reportPath: '/mock-report.html',
+                enhancedAnalysis: this.performEnhancedColorAnalysis([
+                  { hex: '#FF6B6B', rgb: { r: 255, g: 107, b: 107 }, percentage: 25.5 },
+                  { hex: '#4ECDC4', rgb: { r: 78, g: 205, b: 196 }, percentage: 18.3 },
+                  { hex: '#45B7D1', rgb: { r: 69, g: 183, b: 209 }, percentage: 15.7 }
+                ]),
+                formAnalysis: this.getDefaultFormAnalysis(),
+                textureAnalysis: this.getDefaultTextureAnalysis(),
+                patternAnalysis: this.getDefaultPatternAnalysis(),
+                lightingAnalysis: this.getDefaultLightingAnalysis()
+              };
+              
+              this.updateProgress('completed', '分析完成(部分功能降级)', 100);
+              observer.next(fallbackResult);
+              observer.complete();
+            }
+          });
+        }, 500);
+      }, 300);
+    });
+  }
+
+  /**
+   * 获取默认形体分析结果
+   */
+  private getDefaultFormAnalysis(): any {
+    return {
+      spaceAnalysis: {
+        spaceType: '现代简约'
+      },
+      lineAnalysis: {
+        dominantLineType: 'straight',
+        lineDirection: { horizontal: 60, vertical: 30, diagonal: 10 }
+      }
+    };
+  }
+
+  /**
+   * 获取默认质感分析结果
+   */
+  private getDefaultTextureAnalysis(): any {
+    return {
+      materialClassification: {
+        primary: '光滑表面'
+      },
+      surfaceProperties: {
+        roughness: { level: 'smooth', value: 30 },
+        glossiness: { level: 'satin', value: 50 }
+      }
+    };
+  }
+
+  /**
+   * 获取默认纹理分析结果
+   */
+  private getDefaultPatternAnalysis(): any {
+    return {
+      patternRecognition: {
+        primaryPatterns: [
+          { type: 'geometric', confidence: 70, coverage: 40 }
+        ],
+        patternComplexity: { level: 'moderate', score: 50 }
+      }
+    };
+  }
+
+  /**
+   * 获取默认灯光分析结果
+   */
+  private getDefaultLightingAnalysis(): any {
+    return {
+      ambientAnalysis: {
+        lightingMood: '温馨舒适'
+      },
+      illuminationAnalysis: {
+        brightness: { overall: 70 },
+        colorTemperature: { kelvin: 3000, warmth: 'warm' }
+      }
+    };
+  }
+}

+ 5283 - 0
copy/project-detail.ts

@@ -0,0 +1,5283 @@
+import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ActivatedRoute, Router } from '@angular/router';
+import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { ProjectService } from '../../../services/project.service';
+import { PaymentVoucherRecognitionService } from '../../../services/payment-voucher-recognition.service';
+import { ProjectReviewService, ReviewReportExportRequest, ReviewReportShareRequest } from '../../../services/project-review.service';
+import {
+  Project,
+  RenderProgress,
+  CustomerFeedback,
+  DesignerChange,
+  Settlement,
+  ProjectStage,
+  PanoramicSynthesis,
+  ModelCheckItem
+} from '../../../models/project.model';
+import { RequirementsConfirmCardComponent } from '../../../shared/components/requirements-confirm-card/requirements-confirm-card';
+import { SettlementCardComponent } from '../../../shared/components/settlement-card/settlement-card';
+import { CustomerReviewCardComponent, DetailedCustomerReview } from '../../../shared/components/customer-review-card/customer-review-card';
+import { CustomerReviewFormComponent } from '../../../shared/components/customer-review-form/customer-review-form';
+import { ComplaintCardComponent } from '../../../shared/components/complaint-card/complaint-card';
+import { PanoramicSynthesisCardComponent } from '../../../shared/components/panoramic-synthesis-card/panoramic-synthesis-card';
+import { QuotationDetailsComponent, QuotationData } from './components/quotation-details/quotation-details.component';
+import { DesignerAssignmentComponent, DesignerAssignmentData, Designer as AssignmentDesigner } from './components/designer-assignment/designer-assignment.component';
+// 引入客户服务模块的设计师日历组件
+import { DesignerCalendarComponent, Designer as CalendarDesigner, ProjectGroup as CalendarProjectGroup } from '../../customer-service/consultation-order/components/designer-calendar/designer-calendar.component';
+
+import { ColorAnalysisResult } from '../../../shared/services/color-analysis.service';
+
+interface ExceptionHistory {
+  id: string;
+  type: 'failed' | 'stuck' | 'quality' | 'other';
+  description: string;
+  submitTime: Date;
+  status: '待处理' | '处理中' | '已解决';
+  response?: string;
+}
+
+interface ProjectMember {
+  id: string;
+  name: string;
+  role: string;
+  avatar: string;
+  skillMatch: number;
+  progress: number;
+  contribution: number;
+}
+
+interface ProjectFile {
+  id: string;
+  name: string;
+  type: string;
+  size: string;
+  date: string;
+  url: string;
+}
+
+interface TimelineEvent {
+  id: string;
+  time: string;
+  title: string;
+  action: string;
+  description: string;
+}
+
+// 新增:四大板块键类型(顶层声明,供组件与模板共同使用)
+type SectionKey = 'order' | 'requirements' | 'delivery' | 'aftercare';
+
+// 素材解析后的方案数据结构
+interface MaterialAnalysis {
+  category: string;
+  specifications: {
+    type: string;
+    grade: string;
+    thickness?: string;
+    finish?: string;
+    durability: string;
+  };
+  usage: {
+    area: string;
+    percentage: number;
+    priority: 'primary' | 'secondary' | 'accent';
+  };
+  properties: {
+    texture: string;
+    color: string;
+    maintenance: string;
+  };
+}
+
+interface DesignStyleAnalysis {
+  primaryStyle: string;
+  styleElements: {
+    element: string;
+    description: string;
+    influence: number; // 影响程度 0-100
+  }[];
+  characteristics: {
+    feature: string;
+    value: string;
+    importance: 'high' | 'medium' | 'low';
+  }[];
+  compatibility: {
+    withMaterials: string[];
+    withColors: string[];
+    score: number; // 兼容性评分 0-100
+  };
+}
+
+interface ColorSchemeAnalysis {
+  palette: {
+    color: string;
+    hex: string;
+    rgb: string;
+    percentage: number;
+    role: 'dominant' | 'secondary' | 'accent' | 'neutral';
+  }[];
+  harmony: {
+    type: string; // 如:互补色、类似色、三角色等
+    temperature: 'warm' | 'cool' | 'neutral';
+    contrast: number; // 对比度 0-100
+  };
+  psychology: {
+    mood: string;
+    atmosphere: string;
+    suitability: string[];
+  };
+}
+
+interface SpaceAnalysis {
+  dimensions: {
+    length: number;
+    width: number;
+    height: number;
+    area: number;
+    volume: number;
+  };
+  functionalZones: {
+    zone: string;
+    area: number;
+    percentage: number;
+    requirements: string[];
+    furniture: string[];
+  }[];
+  circulation: {
+    mainPaths: string[];
+    pathWidth: number;
+    efficiency: number; // 动线效率 0-100
+  };
+  lighting: {
+    natural: {
+      direction: string[];
+      intensity: string;
+      duration: string;
+    };
+    artificial: {
+      zones: string[];
+      requirements: string[];
+    };
+  };
+}
+
+// 新增:项目复盘数据结构
+interface ProjectReview {
+  id: string;
+  projectId: string;
+  generatedAt: Date;
+  overallScore: number; // 项目总评分 0-100
+  sopAnalysis: {
+    stageName: string;
+    plannedDuration: number; // 计划天数
+    actualDuration: number; // 实际天数
+    score: number; // 阶段评分 0-100
+    executionStatus: 'excellent' | 'good' | 'average' | 'poor';
+    issues?: string[]; // 问题列表
+  }[];
+  keyHighlights: string[]; // 项目亮点
+  improvementSuggestions: string[]; // 改进建议
+  customerSatisfaction: {
+    overallRating: number; // 1-5星
+    feedback?: string; // 客户反馈
+    responseTime: number; // 响应时间(小时)
+    completionTime: number; // 完成时间(天)
+  };
+  teamPerformance: {
+    designerScore: number;
+    communicationScore: number;
+    timelinessScore: number;
+    qualityScore: number;
+  };
+  budgetAnalysis: {
+    plannedBudget: number;
+    actualBudget: number;
+    variance: number; // 预算偏差百分比
+    costBreakdown: {
+      category: string;
+      planned: number;
+      actual: number;
+    }[];
+  };
+  lessonsLearned: string[]; // 经验教训
+  recommendations: string[]; // 后续项目建议
+}
+
+interface ProposalAnalysis {
+  id: string;
+  name: string;
+  version: string;
+  createdAt: Date;
+  status: 'analyzing' | 'completed' | 'approved' | 'rejected';
+  materials: MaterialAnalysis[];
+  designStyle: DesignStyleAnalysis;
+  colorScheme: ColorSchemeAnalysis;
+  spaceLayout: SpaceAnalysis;
+  budget: {
+    total: number;
+    breakdown: {
+      category: string;
+      amount: number;
+      percentage: number;
+    }[];
+  };
+  timeline: {
+    phase: string;
+    duration: number;
+    dependencies: string[];
+  }[];
+  feasibility: {
+    technical: number; // 技术可行性 0-100
+    budget: number; // 预算可行性 0-100
+    timeline: number; // 时间可行性 0-100
+    overall: number; // 综合可行性 0-100
+  };
+}
+
+// 交付执行板块数据结构
+interface DeliverySpace {
+  id: string;
+  name: string; // 空间名称:卧室、餐厅、厨房等
+  isExpanded: boolean; // 是否展开
+  order: number; // 排序
+}
+
+interface DeliveryProcess {
+  id: string;
+  name: string; // 流程名称:建模、软装、渲染、后期
+  type: 'modeling' | 'softDecor' | 'rendering' | 'postProcess';
+  spaces: DeliverySpace[]; // 该流程下的空间列表
+  isExpanded: boolean; // 是否展开
+  content: {
+    [spaceId: string]: {
+      // 每个空间的具体内容
+      images: Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected' }>;
+      progress: number; // 进度百分比
+      status: 'pending' | 'in_progress' | 'completed' | 'approved';
+      notes: string; // 备注
+      lastUpdated: Date;
+    };
+  };
+}
+
+@Component({
+  selector: 'app-project-detail',
+  standalone: true,
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, RequirementsConfirmCardComponent, SettlementCardComponent, CustomerReviewCardComponent, CustomerReviewFormComponent, ComplaintCardComponent, PanoramicSynthesisCardComponent, QuotationDetailsComponent, DesignerAssignmentComponent, DesignerCalendarComponent],
+  templateUrl: './project-detail.html',
+  styleUrls: ['./project-detail.scss', './debug-styles.scss', './horizontal-panel.scss']
+})
+export class ProjectDetail implements OnInit, OnDestroy {
+  // 项目基本数据
+  projectId: string = '';
+  project: Project | undefined;
+  renderProgress: RenderProgress | undefined;
+  feedbacks: CustomerFeedback[] = [];
+  detailedReviews: DetailedCustomerReview[] = [];
+  designerChanges: DesignerChange[] = [];
+  settlements: Settlement[] = [];
+  requirementChecklist: string[] = [];
+  reminderMessage: string = '';
+  isLoadingRenderProgress: boolean = false;
+  errorLoadingRenderProgress: boolean = false;
+  feedbackTimeoutCountdown: number = 0;
+  private countdownInterval: any;
+  projects: {id: string, name: string, status: string}[] = [];
+  showDropdown: boolean = false;
+  currentStage: string = '';
+  
+  // 新增:尾款结算完成状态
+  isSettlementCompleted: boolean = false;
+  isConfirmingSettlement: boolean = false;
+  isSettlementInitiated: boolean = false;
+
+  // 新增:自动化功能状态跟踪
+  miniprogramPaymentStatus: 'active' | 'inactive' = 'inactive';
+  voucherRecognitionCount: number = 0;
+  notificationsSent: number = 0;
+  isAutoSettling: boolean = false;
+  isPaymentVerified: boolean = false;
+  
+  // 客户信息卡片展开/收起状态
+  isCustomerInfoExpanded: boolean = false;
+  
+  // 新增:订单创建表单相关
+  orderCreationForm!: FormGroup;
+  optionalForm!: FormGroup;
+  isOptionalFormExpanded: boolean = false;
+  orderCreationData: any = null;
+  
+  // 新增:方案分析相关数据
+  proposalAnalysis: ProposalAnalysis | null = null;
+  isAnalyzing: boolean = false;
+  analysisProgress: number = 0;
+  // 新增:右侧色彩分析结果展示
+  colorAnalysisResult: ColorAnalysisResult | null = null;
+  dominantColorHex: string | null = null;
+  // 新增:区分展示的参考图片与CAD文件(来源于需求确认子组件)
+  referenceImages: any[] = [];
+  cadFiles: any[] = [];
+  
+  // 新增:详细分析数据属性
+  enhancedColorAnalysis: any = null;
+  formAnalysis: any = null;
+  textureAnalysis: any = null;
+  patternAnalysis: any = null;
+  lightingAnalysis: any = null;
+  materialAnalysisData: any[] = [];
+  
+  // 新增:9阶段顺序(串式流程)- 包含后期阶段
+  stageOrder: ProjectStage[] = ['订单创建', '需求沟通', '方案确认', '建模', '软装', '渲染', '后期', '尾款结算', '客户评价', '投诉处理'];
+  // 新增:阶段展开状态(默认全部收起,当前阶段在数据加载后自动展开)
+  expandedStages: Partial<Record<ProjectStage, boolean>> = {
+    '订单创建': false,
+    '需求沟通': false,
+    '方案确认': false,
+    '建模': false,
+    '软装': false,
+    '渲染': false,
+    '后期': false,
+    '尾款结算': false,
+    '客户评价': false,
+    '投诉处理': false,
+  };
+
+  // 新增:四大板块定义与展开状态 - 交付执行板块调整为三个阶段
+  sections: Array<{ key: SectionKey; label: string; stages: ProjectStage[] }> = [
+    { key: 'order', label: '订单创建', stages: ['订单创建'] },
+    { key: 'requirements', label: '确认需求', stages: ['需求沟通', '方案确认'] },
+    { key: 'delivery', label: '交付执行', stages: ['建模', '软装', '渲染', '后期'] },
+    { key: 'aftercare', label: '售后', stages: [] }
+  ];
+  expandedSection: SectionKey | null = null;
+  // 渲染异常反馈相关属性
+  exceptionType: 'failed' | 'stuck' | 'quality' | 'other' = 'failed';
+  exceptionDescription: string = '';
+  exceptionScreenshotUrl: string | null = null;
+  exceptionHistories: ExceptionHistory[] = [];
+  isSubmittingFeedback: boolean = false;
+  selectedScreenshot: File | null = null;
+  screenshotPreview: string | null = null;
+  showExceptionForm: boolean = false;
+  
+  // 标签页相关
+  activeTab: 'progress' | 'members' | 'files' = 'progress';
+  tabs: Array<{ id: 'progress' | 'members' | 'files'; name: string }> = [
+    { id: 'progress', name: '项目进度' },
+    { id: 'members', name: '项目成员' },
+    { id: 'files', name: '项目文件' }
+  ];
+
+  // 标准化阶段(视图层映射)
+  standardPhases: Array<'待分配' | '需求方案' | '项目执行' | '收尾验收' | '归档'> = ['待分配', '需求方案', '项目执行', '收尾验收', '归档'];
+
+  // 文件上传(通用)
+  acceptedFileTypes: string = '.doc,.docx,.pdf,.jpg,.jpeg,.png,.zip,.rar,.max,.obj';
+  isUploadingFile: boolean = false;
+  projectMembers: ProjectMember[] = [];
+  
+  // 项目文件数据
+  projectFiles: ProjectFile[] = [];
+  
+  // 团队协作时间轴
+  timelineEvents: TimelineEvent[] = [];
+
+  // 团队分配弹窗相关
+  selectedDesigner: any = null;
+  projectData: any = null;
+
+  // ============ 阶段图片上传状态(新增) ============
+  allowedImageTypes: string = '.jpg,.jpeg,.png';
+  // 增加审核状态reviewStatus与是否已同步synced标记(仅由组长操作)
+  whiteModelImages: Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = [];
+  softDecorImages: Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = [];
+  renderLargeImages: Array<{ id: string; name: string; url: string; size?: string; locked?: boolean; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = [];
+  postProcessImages: Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = [];
+  showRenderUploadModal: boolean = false;
+  pendingRenderLargeItems: Array<{ id: string; name: string; url: string; file: File }> = [];
+
+  // 视图上下文:根据路由前缀识别角色视角(客服/设计师/组长)
+  roleContext: 'customer-service' | 'designer' | 'team-leader' | 'technical' = 'designer';
+
+  // ============ 模型检查项数据 ============
+  modelCheckItems: ModelCheckItem[] = [
+    { id: 'check-1', name: '户型匹配度检查', isPassed: false, notes: '' },
+    { id: 'check-2', name: '尺寸精度验证', isPassed: false, notes: '' },
+    { id: 'check-3', name: '材质贴图检查', isPassed: false, notes: '' },
+    { id: 'check-4', name: '光影效果验证', isPassed: false, notes: '' },
+    { id: 'check-5', name: '细节完整性检查', isPassed: false, notes: '' }
+  ];
+
+  // ============ 交付执行板块数据 ============
+  deliveryProcesses: DeliveryProcess[] = [
+    {
+      id: 'modeling',
+      name: '建模',
+      type: 'modeling',
+      isExpanded: true, // 默认展开第一个
+      spaces: [
+        { id: 'bedroom', name: '卧室', isExpanded: false, order: 1 },
+        { id: 'living', name: '客厅', isExpanded: false, order: 2 },
+        { id: 'kitchen', name: '厨房', isExpanded: false, order: 3 }
+      ],
+      content: {
+        'bedroom': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'living': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'kitchen': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }
+      }
+    },
+    {
+      id: 'softDecor',
+      name: '软装',
+      type: 'softDecor',
+      isExpanded: false,
+      spaces: [
+        { id: 'bedroom', name: '卧室', isExpanded: false, order: 1 },
+        { id: 'living', name: '客厅', isExpanded: false, order: 2 },
+        { id: 'kitchen', name: '厨房', isExpanded: false, order: 3 }
+      ],
+      content: {
+        'bedroom': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'living': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'kitchen': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }
+      }
+    },
+    {
+      id: 'rendering',
+      name: '渲染',
+      type: 'rendering',
+      isExpanded: false,
+      spaces: [
+        { id: 'bedroom', name: '卧室', isExpanded: false, order: 1 },
+        { id: 'living', name: '客厅', isExpanded: false, order: 2 },
+        { id: 'kitchen', name: '厨房', isExpanded: false, order: 3 }
+      ],
+      content: {
+        'bedroom': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'living': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'kitchen': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }
+      }
+    },
+    {
+      id: 'postProcess',
+      name: '后期',
+      type: 'postProcess',
+      isExpanded: false,
+      spaces: [
+        { id: 'bedroom', name: '卧室', isExpanded: false, order: 1 },
+        { id: 'living', name: '客厅', isExpanded: false, order: 2 },
+        { id: 'kitchen', name: '厨房', isExpanded: false, order: 3 }
+      ],
+      content: {
+        'bedroom': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'living': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() },
+        'kitchen': { images: [], progress: 0, status: 'pending', notes: '', lastUpdated: new Date() }
+      }
+    }
+  ];
+
+  // 新增空间输入框状态
+  newSpaceName: { [processId: string]: string } = {};
+  showAddSpaceInput: { [processId: string]: boolean } = {};
+
+  constructor(
+    private route: ActivatedRoute,
+    private projectService: ProjectService,
+    private router: Router,
+    private fb: FormBuilder,
+    private cdr: ChangeDetectorRef,
+    private paymentVoucherService: PaymentVoucherRecognitionService,
+    private projectReviewService: ProjectReviewService
+  ) {}
+
+  // 切换标签页
+  switchTab(tabId: 'progress' | 'members' | 'files') {
+    this.activeTab = tabId;
+  }
+
+  // 类型安全的标签页检查方法
+  isActiveTab(tabId: 'progress' | 'members' | 'files'): boolean {
+    return this.activeTab === tabId;
+  }
+
+  // 根据事件类型获取作者名称
+  getEventAuthor(action: string): string {
+    // 根据不同的action类型返回对应的作者名称
+    switch(action) {
+      case '完成':
+      case '更新':
+      case '优化':
+        return '李设计师';
+      case '收到':
+        return '李客服';
+      case '提交':
+        return '赵建模师';
+      default:
+        return '王组长';
+    }
+  }
+
+  // 切换项目
+  switchProject(projectId: string): void {
+    this.projectId = projectId;
+    this.loadProjectData();
+    this.loadProjectMembers();
+    this.loadProjectFiles();
+    this.loadTimelineEvents();
+    // 更新URL但不触发组件重载
+    this.router.navigate([], { relativeTo: this.route, queryParamsHandling: 'merge', queryParams: { id: projectId } });
+  }
+
+  // 检查是否处于订单创建阶段,用于红色高亮显示
+  isCurrentOrderCreation(): boolean {
+    // 只有当订单创建阶段是当前活跃阶段时才标红显示
+    // 修复逻辑:完成前序环节后才标红当前阶段
+    const currentStage = this.project?.currentStage;
+    if (!currentStage) return false;
+    
+    // 如果当前阶段就是订单创建阶段,说明需要标红显示
+    if (currentStage === '订单创建') return true;
+    
+    // 如果当前阶段在订单创建之后,说明订单创建已完成,不应标红
+    const stageOrder = [
+      '订单创建', '需求沟通', '方案确认', '建模', '软装', 
+      '渲染', '尾款结算', '客户评价', '投诉处理'
+    ];
+    
+    const currentIndex = stageOrder.indexOf(currentStage);
+    const orderCreationIndex = stageOrder.indexOf('订单创建');
+    
+    // 如果当前阶段在订单创建之后,说明订单创建已完成
+    return currentIndex <= orderCreationIndex;
+  }
+
+  // 返回工作台
+  backToWorkbench(): void {
+    this.router.navigate(['/designer/dashboard']);
+  }
+  
+  // 检查阶段是否已完成
+  isStageCompleted(stage: ProjectStage): boolean {
+    if (!this.project) return false;
+    
+    // 定义阶段顺序
+    const stageOrder = [
+      '订单创建', '需求沟通', '方案确认', '建模', '软装', 
+      '渲染', '尾款结算', '客户评价', '投诉处理'
+    ];
+    
+    // 获取当前阶段和检查阶段的索引
+    const currentStageIndex = stageOrder.indexOf(this.project.currentStage);
+    const checkStageIndex = stageOrder.indexOf(stage);
+    
+    // 如果检查阶段在当前阶段之前,则已完成
+    return checkStageIndex < currentStageIndex;
+  }
+
+  // 获取阶段状态:completed/active/pending
+  getStageStatus(stage: ProjectStage): 'completed' | 'active' | 'pending' {
+    const order = this.stageOrder;
+    // 优先使用 currentStage 属性,如果没有则使用 project.currentStage
+    const current = (this.currentStage as ProjectStage) || (this.project?.currentStage as ProjectStage | undefined);
+    const currentIdx = current ? order.indexOf(current) : -1;
+    const idx = order.indexOf(stage);
+    if (idx === -1) return 'pending';
+    if (currentIdx === -1) return 'pending';
+    if (idx < currentIdx) return 'completed';
+    if (idx === currentIdx) return 'active';
+    return 'pending';
+  }
+
+  // 切换阶段展开/收起,并保持单展开
+  toggleStage(stage: ProjectStage): void {
+    // 已移除所有展开按钮,本方法保留以兼容模板其它引用,如无需可进一步删除调用点和方法
+    const exclusivelyOpen = true;
+    if (exclusivelyOpen) {
+      Object.keys(this.expandedStages).forEach((key) => (this.expandedStages[key as ProjectStage] = false));
+      this.expandedStages[stage] = true;
+    } else {
+      this.expandedStages[stage] = !this.expandedStages[stage];
+    }
+  }
+
+  // 查看阶段详情(已不再通过按钮触发,保留以兼容日志或未来调用)
+  viewStageDetails(stage: ProjectStage): void {
+    // 以往这里有 alert/导航行为,现清空用户交互,避免误触
+    return;
+  }
+
+  ngOnInit(): void {
+    // 初始化表单
+    this.initializeForms();
+    
+    // 初始化需求关键信息数据
+    this.ensureRequirementData();
+    
+    // 重置方案分析状态,确保需求信息展示区域能够显示
+    this.resetProposalAnalysis();
+    
+    // 初始化售后模块示例数据
+    this.initializeAftercareData();
+    
+    this.route.paramMap.subscribe(params => {
+      this.projectId = params.get('id') || '';
+      // 根据当前URL检测视图上下文
+      this.roleContext = this.detectRoleContextFromUrl();
+      this.loadProjectData();
+      this.loadExceptionHistories();
+      this.loadProjectMembers();
+      this.loadProjectFiles();
+      this.loadTimelineEvents();
+      
+      // 启动客户信息自动同步
+      this.startAutoSync();
+    });
+
+    // 新增:监听查询参数,支持通过 activeTab 设置初始标签页和 currentStage 设置当前阶段
+    this.route.queryParamMap.subscribe(qp => {
+      const raw = qp.get('activeTab');
+      const alias: Record<string, 'progress' | 'members' | 'files'> = {
+        requirements: 'progress',
+        overview: 'progress'
+      };
+      const tab = raw && (raw in alias ? alias[raw] : raw);
+      if (tab === 'progress' || tab === 'members' || tab === 'files') {
+        this.activeTab = tab;
+      }
+      
+      // 处理 currentStage 查询参数
+      const currentStageParam = qp.get('currentStage');
+      if (currentStageParam) {
+        this.currentStage = currentStageParam;
+        
+        // 根据当前阶段设置项目状态和展开相应区域
+        this.initializeStageFromRoute(currentStageParam as ProjectStage);
+        
+        // 根据当前阶段自动展开对应的区域
+        const sectionKey = this.getSectionKeyForStage(currentStageParam as ProjectStage);
+        if (sectionKey) {
+          this.expandedSection = sectionKey;
+        }
+        
+        // 延迟滚动到对应阶段
+        setTimeout(() => {
+          this.scrollToStage(currentStageParam as ProjectStage);
+        }, 500);
+      }
+
+      // 处理从客服项目列表传递的同步数据
+      const syncDataParam = qp.get('syncData');
+      if (syncDataParam) {
+        try {
+          const syncData = JSON.parse(syncDataParam);
+          console.log('接收到客服同步数据:', syncData);
+          
+          // 设置同步状态
+          this.isSyncingCustomerInfo = true;
+          
+          // 存储订单创建数据用于显示
+          this.orderCreationData = syncData;
+          
+          // 更新projectData以传递给子组件
+          this.projectData = {
+            customerInfo: syncData.customerInfo,
+            requirementInfo: syncData.requirementInfo,
+            preferenceTags: syncData.preferenceTags
+          };
+          
+          // 同步客户信息到表单
+          if (syncData.customerInfo) {
+            this.customerForm.patchValue({
+              name: syncData.customerInfo.name || '',
+              phone: syncData.customerInfo.phone || '',
+              wechat: syncData.customerInfo.wechat || '',
+              customerType: syncData.customerInfo.customerType || '新客户',
+              source: syncData.customerInfo.source || '小程序',
+              remark: syncData.customerInfo.remark || '',
+              demandType: syncData.customerInfo.demandType || '',
+              followUpStatus: syncData.customerInfo.followUpStatus || '待分配'
+            });
+
+            // 设置选中的客户
+            this.selectedOrderCustomer = {
+              id: syncData.customerInfo.id || 'temp-' + Date.now(),
+              name: syncData.customerInfo.name,
+              phone: syncData.customerInfo.phone,
+              wechat: syncData.customerInfo.wechat,
+              customerType: syncData.customerInfo.customerType,
+              source: syncData.customerInfo.source,
+              remark: syncData.customerInfo.remark
+            };
+          }
+
+          // 同步需求信息
+          if (syncData.requirementInfo) {
+            this.syncRequirementKeyInfo(syncData.requirementInfo);
+          }
+
+          // 同步偏好标签到项目数据
+          if (syncData.preferenceTags && this.project) {
+            this.project.customerTags = syncData.preferenceTags.map((tag: string) => ({
+              source: '客服填写',
+              needType: tag,
+              preference: tag,
+              colorAtmosphere: tag
+            }));
+          }
+
+          // 模拟同步完成
+          setTimeout(() => {
+            this.isSyncingCustomerInfo = false;
+            this.lastSyncTime = new Date();
+            this.cdr.detectChanges();
+            console.log('客户信息同步完成');
+          }, 1500);
+
+          // 触发界面更新
+          this.cdr.detectChanges();
+          
+          console.log('客服数据同步完成,orderCreationData:', this.orderCreationData);
+        } catch (error) {
+          console.error('解析同步数据失败:', error);
+          this.isSyncingCustomerInfo = false;
+        }
+      }
+    });
+    
+    // 添加点击事件监听器,当点击页面其他位置时关闭下拉菜单
+    document.addEventListener('click', this.closeDropdownOnClickOutside);
+  
+    // 初始化客户表单(与客服端保持一致)
+    this.customerForm = this.fb.group({
+      name: ['', Validators.required],
+      phone: ['', [Validators.required, Validators.pattern(/^1[3-9]\d{9}$/)]],
+      wechat: [''],
+      customerType: ['新客户'],
+      source: [''],
+      remark: [''],
+      demandType: [''],
+      followUpStatus: ['']
+    });
+  
+    // 自动生成下单时间
+    this.orderTime = new Date().toLocaleString('zh-CN', {
+      year: 'numeric', month: '2-digit', day: '2-digit',
+      hour: '2-digit', minute: '2-digit', second: '2-digit'
+    });
+  }
+
+  // 新增:根据路由参数初始化阶段状态
+  private initializeStageFromRoute(targetStage: ProjectStage): void {
+    // 设置当前阶段
+    this.currentStage = targetStage;
+    
+    // 根据目标阶段设置之前的阶段为已完成
+    const targetIndex = this.stageOrder.indexOf(targetStage);
+    if (targetIndex > 0) {
+      // 将目标阶段之前的所有阶段设置为已完成
+      for (let i = 0; i < targetIndex; i++) {
+        const stage = this.stageOrder[i];
+        this.expandedStages[stage] = false; // 已完成的阶段收起
+      }
+    }
+    
+    // 展开当前阶段
+    this.expandedStages[targetStage] = true;
+    
+    // 如果项目对象存在,更新项目的当前阶段
+    if (this.project) {
+      this.project.currentStage = targetStage;
+    }
+    
+    // 触发变更检测以更新UI
+    this.cdr.detectChanges();
+  }
+  ngOnDestroy(): void {
+    if (this.countdownInterval) {
+      clearInterval(this.countdownInterval);
+    }
+    
+    // 停止自动同步
+    this.stopAutoSync();
+    
+    document.removeEventListener('click', this.closeDropdownOnClickOutside);
+    // 释放所有 blob 预览 URL
+    const revokeList: string[] = [];
+    this.whiteModelImages.forEach(i => { if (i.url.startsWith('blob:')) revokeList.push(i.url); });
+    this.softDecorImages.forEach(i => { if (i.url.startsWith('blob:')) revokeList.push(i.url); });
+    this.renderLargeImages.forEach(i => { if (i.url.startsWith('blob:')) revokeList.push(i.url); });
+    this.pendingRenderLargeItems.forEach(i => { if (i.url.startsWith('blob:')) revokeList.push(i.url); });
+    revokeList.forEach(u => URL.revokeObjectURL(u));
+  }
+
+  // ============ 角色视图与只读控制(新增) ============
+  private detectRoleContextFromUrl(): 'customer-service' | 'designer' | 'team-leader' | 'technical' {
+    const url = this.router.url || '';
+    
+    // 首先检查查询参数中的role
+    const queryParams = this.route.snapshot.queryParamMap;
+    const roleParam = queryParams.get('role');
+    if (roleParam === 'customer-service') {
+      return 'customer-service';
+    }
+    if (roleParam === 'technical') {
+      return 'technical';
+    }
+    
+    // 如果没有role查询参数,则根据URL路径判断
+    if (url.includes('/customer-service/')) return 'customer-service';
+    if (url.includes('/team-leader/')) return 'team-leader';
+    if (url.includes('/technical/')) return 'technical';
+    return 'designer';
+  }
+
+  isDesignerView(): boolean { return this.roleContext === 'designer'; }
+  isTeamLeaderView(): boolean { return this.roleContext === 'team-leader'; }
+  isCustomerServiceView(): boolean { return this.roleContext === 'customer-service'; }
+  isTechnicalView(): boolean { return this.roleContext === 'technical'; }
+  // 只读规则:客服视角为只读
+  isReadOnly(): boolean { return this.isCustomerServiceView(); }
+
+  // 权限控制:客服只能编辑订单创建、确认需求、售后板块
+  canEditSection(sectionKey: SectionKey): boolean {
+    if (this.isCustomerServiceView()) {
+      return sectionKey === 'order' || sectionKey === 'requirements' || sectionKey === 'aftercare';
+    }
+    return true; // 设计师和组长可以编辑所有板块
+  }
+
+  // 权限控制:客服只能编辑特定阶段
+  canEditStage(stage: ProjectStage): boolean {
+    if (this.isCustomerServiceView()) {
+      const editableStages: ProjectStage[] = [
+        '订单创建', '需求沟通', '方案确认', // 订单创建和确认需求板块
+        '尾款结算', '客户评价', '投诉处理' // 售后板块
+      ];
+      return editableStages.includes(stage);
+    }
+    return true; // 设计师和组长可以编辑所有阶段
+  }
+
+  // 计算当前激活板块:优先用户点击的 expandedSection;否则取当前阶段所属板块;再否则回退首个板块
+  private getActiveSectionKey(): SectionKey {
+    if (this.expandedSection) return this.expandedSection;
+    const current = this.project?.currentStage as ProjectStage | undefined;
+    return current ? this.getSectionKeyForStage(current) : this.sections[0].key;
+  }
+
+  // 返回当前板块的全部阶段(所有角色一致):
+  // 设计师也可查看 订单创建/确认需求/售后 板块内容
+  getVisibleStages(): ProjectStage[] {
+    const activeKey = this.getActiveSectionKey();
+    const sec = this.sections.find(s => s.key === activeKey);
+    return sec ? sec.stages : [];
+  }
+
+  // ============ 组长:同步上传与审核(新增,模拟实现) ============
+  syncUploadedImages(phase: 'white' | 'soft' | 'render' | 'postProcess'): void {
+    if (!this.isTeamLeaderView()) return;
+    const markSynced = (arr: Array<{ reviewStatus?: 'pending'|'approved'|'rejected'; synced?: boolean }>) => {
+      arr.forEach(img => {
+        if (!img.synced) img.synced = true;
+        if (!img.reviewStatus) img.reviewStatus = 'pending';
+      });
+    };
+    if (phase === 'white') markSynced(this.whiteModelImages);
+    if (phase === 'soft') markSynced(this.softDecorImages);
+    if (phase === 'render') markSynced(this.renderLargeImages);
+    if (phase === 'postProcess') markSynced(this.postProcessImages);
+    alert('已同步该阶段的图片信息(模拟)');
+  }
+
+  reviewImage(imageId: string, phase: 'white' | 'soft' | 'render' | 'postProcess', status: 'approved' | 'rejected'): void {
+    if (!this.isTeamLeaderView()) return;
+    const setStatus = (arr: Array<{ id: string; reviewStatus?: 'pending'|'approved'|'rejected'; synced?: boolean }>) => {
+      const target = arr.find(i => i.id === imageId);
+      if (target) {
+        target.reviewStatus = status;
+        if (!target.synced) target.synced = true; // 审核时自动视为已同步
+      }
+    };
+    if (phase === 'white') setStatus(this.whiteModelImages);
+    if (phase === 'soft') setStatus(this.softDecorImages);
+    if (phase === 'render') setStatus(this.renderLargeImages);
+    if (phase === 'postProcess') setStatus(this.postProcessImages);
+  }
+
+  getImageReviewStatusText(img: { reviewStatus?: 'pending'|'approved'|'rejected'; synced?: boolean }): string {
+    const synced = img.synced ? '已同步' : '未同步';
+    const map: Record<string, string> = {
+      'pending': '待审',
+      'approved': '已通过',
+      'rejected': '已驳回'
+    };
+    const st = img.reviewStatus ? map[img.reviewStatus] : '未标记';
+    return `${st} · ${synced}`;
+  }
+
+  // 点击页面其他位置时关闭下拉菜单
+  private closeDropdownOnClickOutside = (event: MouseEvent): void => {
+    const targetElement = event.target as HTMLElement;
+    const projectSwitcher = targetElement.closest('.project-switcher');
+    
+    if (!projectSwitcher && this.showDropdown) {
+      this.showDropdown = false;
+    }
+  };
+
+  loadProjectData(): void {
+    if (this.projectId) {
+      this.loadProjectDetails();
+      this.loadRenderProgress();
+      this.loadCustomerFeedbacks();
+      this.loadDesignerChanges();
+      this.loadSettlements();
+      this.loadRequirementChecklist();
+    }
+    
+    // 初始化项目列表数据(模拟)
+    this.projects = [
+      { id: '1', name: '现代风格客厅设计', status: '进行中' },
+      { id: '2', name: '北欧风卧室装修', status: '已完成' },
+      { id: '3', name: '新中式书房改造', status: '进行中' },
+      { id: '4', name: '工业风餐厅设计', status: '待处理' }
+    ];
+  }
+  
+  // 加载项目成员数据
+  loadProjectMembers(): void {
+    // 模拟API请求获取项目成员数据
+    setTimeout(() => {
+      this.projectMembers = [
+        {
+          id: '1',
+          name: '李设计师',
+          role: '主设计师',
+          avatar: '李',
+          skillMatch: 95,
+          progress: 65,
+          contribution: 75
+        },
+        {
+          id: '2',
+          name: '陈设计师',
+          role: '助理设计师',
+          avatar: '陈',
+          skillMatch: 88,
+          progress: 80,
+          contribution: 60
+        },
+        {
+          id: '3',
+          name: '王组长',
+          role: '项目组长',
+          avatar: '王',
+          skillMatch: 92,
+          progress: 70,
+          contribution: 70
+        },
+        {
+          id: '4',
+          name: '赵建模师',
+          role: '3D建模师',
+          avatar: '赵',
+          skillMatch: 90,
+          progress: 90,
+          contribution: 85
+        }
+      ];
+    }, 600);
+  }
+  
+  // 加载项目文件数据
+  loadProjectFiles(): void {
+    // 模拟API请求获取项目文件数据
+    setTimeout(() => {
+      this.projectFiles = [
+        {
+          id: '1',
+          name: '客厅设计方案V2.0.pdf',
+          type: 'pdf',
+          size: '2.5MB',
+          date: '2024-02-10',
+          url: '#'
+        },
+        {
+          id: '2',
+          name: '材质库集合.rar',
+          type: 'rar',
+          size: '45.8MB',
+          date: '2024-02-08',
+          url: '#'
+        },
+        {
+          id: '3',
+          name: '客厅渲染预览1.jpg',
+          type: 'jpg',
+          size: '3.2MB',
+          date: '2024-02-14',
+          url: '#'
+        },
+        {
+          id: '4',
+          name: '3D模型文件.max',
+          type: 'max',
+          size: '87.5MB',
+          date: '2024-02-12',
+          url: '#'
+        },
+        {
+          id: '5',
+          name: '客户需求文档.docx',
+          type: 'docx',
+          size: '1.2MB',
+          date: '2024-01-15',
+          url: '#'
+        },
+        {
+          id: '6',
+          name: '客厅渲染预览2.jpg',
+          type: 'jpg',
+          size: '3.8MB',
+          date: '2024-02-15',
+          url: '#'
+        }
+      ];
+    }, 700);
+  }
+  
+  // 加载团队协作时间轴数据
+  loadTimelineEvents(): void {
+    // 模拟API请求获取时间轴数据
+    setTimeout(() => {
+      this.timelineEvents = [
+        {
+          id: '1',
+          time: '2024-02-15 14:30',
+          title: '渲染完成',
+          action: '完成',
+          description: '客厅主视角渲染已完成,等待客户确认'
+        },
+        {
+          id: '2',
+          time: '2024-02-14 10:15',
+          title: '材质调整',
+          action: '更新',
+          description: '根据客户反馈调整了沙发和窗帘材质'
+        },
+        {
+          id: '3',
+          time: '2024-02-12 16:45',
+          title: '模型优化',
+          action: '优化',
+          description: '优化了模型面数,提高渲染效率'
+        },
+        {
+          id: '4',
+          time: '2024-02-10 09:30',
+          title: '客户反馈',
+          action: '收到',
+          description: '收到客户关于颜色和储物空间的反馈意见'
+        },
+        {
+          id: '5',
+          time: '2024-02-08 15:20',
+          title: '模型提交',
+          action: '提交',
+          description: '完成3D模型搭建并提交审核'
+        }
+      ];
+    }, 800);
+  }
+  
+  // 加载历史反馈记录
+  loadExceptionHistories(): void {
+    this.projectService.getExceptionHistories(this.projectId).subscribe(histories => {
+      this.exceptionHistories = histories;
+    });
+  }
+
+  loadProjectDetails(): void {
+    console.log('=== loadProjectDetails 开始加载项目数据 ===');
+    console.log('当前项目ID:', this.projectId);
+    this.projectService.getProjectById(this.projectId).subscribe(project => {
+      console.log('获取到的项目数据:', project);
+      if (!project) {
+        console.error('未找到项目数据,项目ID:', this.projectId);
+        // 如果找不到项目,尝试使用默认项目ID
+        console.log('尝试使用默认项目ID: proj-001');
+        this.projectService.getProjectById('proj-001').subscribe(defaultProject => {
+          console.log('默认项目数据:', defaultProject);
+          if (defaultProject) {
+            this.project = defaultProject;
+            this.currentStage = defaultProject.currentStage || '';
+            console.log('使用默认项目,设置当前阶段:', this.currentStage);
+            this.setupStageExpansion(defaultProject);
+          }
+        });
+        return;
+      }
+      
+      this.project = project;
+      // 设置当前阶段
+      if (project) {
+        this.currentStage = project.currentStage || '';
+        console.log('设置当前阶段:', this.currentStage);
+        this.setupStageExpansion(project);
+      }
+      // 检查技能匹配度 - 已注释掉以取消弹窗警告
+      // this.checkSkillMismatch();
+    });
+  }
+
+  private setupStageExpansion(project: any): void {
+    // 重置展开状态并默认展开当前阶段
+    this.stageOrder.forEach(s => this.expandedStages[s] = false);
+    const currentStage = project.currentStage as ProjectStage;
+    if (this.stageOrder.includes(currentStage)) {
+      this.expandedStages[currentStage] = true;
+      console.log('展开当前阶段:', currentStage);
+    }
+    // 新增:根据当前阶段默认展开所属板块
+    const currentSec = this.getSectionKeyForStage(currentStage);
+    this.expandedSection = currentSec;
+    console.log('展开板块:', currentSec);
+    
+    // 新增:如果当前阶段是建模、软装或渲染,自动展开对应的折叠面板
+    if (currentStage === '建模' || currentStage === '软装' || currentStage === '渲染') {
+      const processTypeMap = {
+        '建模': 'modeling',
+        '软装': 'softDecor', 
+        '渲染': 'rendering'
+      };
+      const processType = processTypeMap[currentStage] as 'modeling' | 'softDecor' | 'rendering';
+      const targetProcess = this.deliveryProcesses.find(p => p.type === processType);
+      if (targetProcess) {
+        // 展开对应的流程面板
+        targetProcess.isExpanded = true;
+        // 展开第一个空间以便用户操作
+        if (targetProcess.spaces.length > 0) {
+          targetProcess.spaces[0].isExpanded = true;
+          // 关闭其他空间
+          targetProcess.spaces.slice(1).forEach(space => space.isExpanded = false);
+        }
+        console.log('自动展开折叠面板:', currentStage, processType);
+      }
+    }
+  }
+  
+  // 整理项目详情
+  organizeProject(): void {
+    // 模拟整理项目逻辑
+    alert('项目详情已整理');
+  }
+  
+  // 检查当前阶段是否显示特定卡片
+  shouldShowCard(cardType: string): boolean {
+    // 改为始终显示:各阶段详情在看板下方就地展示,不再受当前阶段限制
+    return true;
+  }
+
+  loadRenderProgress(): void {
+    this.isLoadingRenderProgress = true;
+    this.errorLoadingRenderProgress = false;
+    
+    // 模拟API加载过程
+    setTimeout(() => {
+      this.projectService.getRenderProgress(this.projectId).subscribe(progress => {
+        this.renderProgress = progress;
+        this.isLoadingRenderProgress = false;
+        
+        // 模拟API加载失败的情况
+        if (!progress) {
+          this.errorLoadingRenderProgress = true;
+          // 通知技术组长
+          this.notifyTeamLeader('render-failed');
+        } else {
+          // 检查是否需要显示超时预警
+          this.checkRenderTimeout();
+        }
+      });
+    }, 1000);
+  }
+
+  loadCustomerFeedbacks(): void {
+    this.projectService.getCustomerFeedbacks().subscribe(feedbacks => {
+      this.feedbacks = feedbacks.filter(f => f.projectId === this.projectId);
+      // 为反馈添加分类标签
+      this.tagCustomerFeedbacks();
+      // 检查是否有需要处理的反馈并启动倒计时
+      this.checkFeedbackTimeout();
+    });
+  }
+
+  loadDesignerChanges(): void {
+    // 在实际应用中,这里应该从服务中获取设计师变更记录
+    // 这里使用模拟数据
+    this.designerChanges = [
+      {
+        id: 'dc1',
+        projectId: this.projectId,
+        oldDesignerId: 'designer2',
+        oldDesignerName: '设计师B',
+        newDesignerId: 'designer1',
+        newDesignerName: '设计师A',
+        changeTime: new Date('2025-09-05'),
+        acceptanceTime: new Date('2025-09-05'),
+        historicalAchievements: ['完成初步建模', '确定色彩方案'],
+        completedWorkload: 30
+      }
+    ];
+  }
+
+  loadSettlements(): void {
+    this.projectService.getSettlements().subscribe(settlements => {
+      this.settlements = settlements.filter(s => s.projectId === this.projectId);
+    });
+  }
+
+  loadRequirementChecklist(): void {
+    this.projectService.generateRequirementChecklist(this.projectId).subscribe(checklist => {
+      this.requirementChecklist = checklist;
+    });
+  }
+
+  updateFeedbackStatus(feedbackId: string, status: '处理中' | '已解决'): void {
+    this.projectService.updateFeedbackStatus(feedbackId, status).subscribe(() => {
+      this.loadCustomerFeedbacks(); // 重新加载反馈
+      // 清除倒计时
+      if (this.countdownInterval) {
+        clearInterval(this.countdownInterval);
+        this.feedbackTimeoutCountdown = 0;
+      }
+    });
+  }
+
+  updateProjectStage(stage: ProjectStage): void {
+    if (this.project) {
+      this.projectService.updateProjectStage(this.projectId, stage).subscribe(() => {
+        this.currentStage = stage; // 同步更新本地状态
+        this.project!.currentStage = stage; // 同步更新project对象的currentStage
+        this.loadProjectDetails(); // 重新加载项目详情
+        this.cdr.detectChanges(); // 触发变更检测以更新导航栏颜色
+      });
+    }
+  }
+
+  // 新增:根据给定阶段跳转到下一阶段
+  advanceToNextStage(afterStage: ProjectStage): void {
+    const idx = this.stageOrder.indexOf(afterStage);
+    if (idx >= 0 && idx < this.stageOrder.length - 1) {
+      const next = this.stageOrder[idx + 1];
+      this.updateProjectStage(next);
+      // 更新展开状态,折叠当前、展开下一阶段,提升体验
+      if (this.expandedStages[afterStage] !== undefined) this.expandedStages[afterStage] = false as any;
+      if (this.expandedStages[next] !== undefined) this.expandedStages[next] = true as any;
+      // 更新板块展开状态
+      const nextSection = this.getSectionKeyForStage(next);
+      this.expandedSection = nextSection;
+      
+      // 新增:自动展开对应阶段的折叠面板
+      if (next === '软装' || next === '渲染') {
+        const processType = next === '软装' ? 'softDecor' : 'rendering';
+        const targetProcess = this.deliveryProcesses.find(p => p.type === processType);
+        if (targetProcess) {
+          // 展开对应的流程面板
+          targetProcess.isExpanded = true;
+          // 展开第一个空间以便用户操作
+          if (targetProcess.spaces.length > 0) {
+            targetProcess.spaces[0].isExpanded = true;
+            // 关闭其他空间
+            targetProcess.spaces.slice(1).forEach(space => space.isExpanded = false);
+          }
+        }
+      }
+      
+      // 触发变更检测以更新导航栏颜色
+      this.cdr.detectChanges();
+    }
+  }
+  generateReminderMessage(): void {
+    this.projectService.generateReminderMessage('stagnation').subscribe(message => {
+      this.reminderMessage = message;
+      
+      // 3秒后自动清除提醒
+      setTimeout(() => {
+        this.reminderMessage = '';
+      }, 3000);
+    });
+  }
+
+  // ============ 新增:标准化阶段映射与紧急程度 ============
+  // 计算距离截止日期的天数(向下取整)
+  getDaysToDeadline(): number | null {
+    if (!this.project?.deadline) return null;
+    const now = new Date();
+    const deadline = new Date(this.project.deadline);
+    const diffMs = deadline.getTime() - now.getTime();
+    return Math.floor(diffMs / (1000 * 60 * 60 * 24));
+  }
+
+  // 是否延期/临期/提示
+  getUrgencyBadge(): 'overdue' | 'due_3' | 'due_7' | null {
+    const d = this.getDaysToDeadline();
+    if (d === null) return null;
+    if (d < 0) return 'overdue';
+    if (d <= 3) return 'due_3';
+    if (d <= 7) return 'due_7';
+    return null;
+  }
+
+  // 是否存在不满意或待处理投诉/反馈
+  hasPendingComplaint(): boolean {
+    return this.feedbacks.some(f => !f.isSatisfied || f.status === '待处理');
+  }
+
+  // 将现有细分阶段映射为标准化阶段
+  mapToStandardPhase(stage: ProjectStage): '待分配' | '需求方案' | '项目执行' | '收尾验收' | '归档' {
+    const mapping: Record<ProjectStage, '待分配' | '需求方案' | '项目执行' | '收尾验收' | '归档'> = {
+      '订单创建': '待分配',
+      '需求沟通': '需求方案',
+      '方案确认': '需求方案',
+      '建模': '项目执行',
+      '软装': '项目执行',
+      '渲染': '项目执行',
+      '后期': '项目执行',
+      '尾款结算': '收尾验收',
+      '客户评价': '收尾验收',
+      '投诉处理': '收尾验收'
+    };
+    return mapping[stage] ?? '待分配';
+  }
+
+  getStandardPhaseIndex(): number {
+    if (!this.project?.currentStage) return 0;
+    const phase = this.mapToStandardPhase(this.project.currentStage);
+    return this.standardPhases.indexOf(phase);
+  }
+
+  isStandardPhaseCompleted(phase: '待分配' | '需求方案' | '项目执行' | '收尾验收' | '归档'): boolean {
+    return this.standardPhases.indexOf(phase) < this.getStandardPhaseIndex();
+  }
+
+  isStandardPhaseCurrent(phase: '待分配' | '需求方案' | '项目执行' | '收尾验收' | '归档'): boolean {
+    return this.standardPhases.indexOf(phase) === this.getStandardPhaseIndex();
+  }
+
+  // ============ 新增:项目报告导出 ============
+  exportProjectReport(): void {
+    if (!this.project) return;
+    const lines: string[] = [];
+    const d = this.getDaysToDeadline();
+    lines.push(`项目名称: ${this.project.name}`);
+    lines.push(`当前阶段(细分): ${this.project.currentStage}`);
+    lines.push(`当前阶段(标准化): ${this.mapToStandardPhase(this.project.currentStage)}`);
+    if (this.project.deadline) {
+      lines.push(`截止日期: ${this.formatDate(this.project.deadline)}`);
+      lines.push(`剩余天数: ${d !== null ? d : '-'}天`);
+    }
+    lines.push(`技能需求: ${(this.project.skillsRequired || []).join('、')}`);
+    lines.push('—— 渲染进度 ——');
+    lines.push(this.renderProgress ? `状态: ${this.renderProgress.status}, 完成度: ${this.renderProgress.completionRate}%` : '无渲染进度数据');
+    lines.push('—— 客户反馈 ——');
+    lines.push(this.feedbacks.length ? `${this.feedbacks.length} 条` : '暂无');
+    lines.push('—— 设计师变更 ——');
+    lines.push(this.designerChanges.length ? `${this.designerChanges.length} 条` : '暂无');
+    lines.push('—— 交付文件 ——');
+    lines.push(this.projectFiles.length ? this.projectFiles.map(f => `• ${f.name} (${f.type}, ${f.size})`).join('\n') : '暂无');
+
+    const content = lines.join('\n');
+    const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
+    const url = URL.createObjectURL(blob);
+    const a = document.createElement('a');
+    a.href = url;
+    a.download = `${this.project.name || '项目'}-阶段报告.txt`;
+    a.click();
+    URL.revokeObjectURL(url);
+  }
+
+  // ============ 新增:通用文件上传(含4K图片校验) ============
+  async onGeneralFilesSelected(event: Event): Promise<void> {
+    const input = event.target as HTMLInputElement;
+    if (!input.files || input.files.length === 0) return;
+    const files = Array.from(input.files);
+    this.isUploadingFile = true;
+
+    for (const file of files) {
+      // 对图片进行4K校验(最大边 >= 4000px)
+      if (/\.(jpg|jpeg|png)$/i.test(file.name)) {
+        const ok = await this.validateImage4K(file).catch(() => false);
+        if (!ok) {
+          alert(`图片不符合4K标准(最大边需≥4000像素):${file.name}`);
+          continue;
+        }
+      }
+      // 简化:直接追加到本地列表(实际应上传到服务器)
+      const fakeType = (file.name.split('.').pop() || '').toLowerCase();
+      const sizeMB = (file.size / (1024 * 1024)).toFixed(1) + 'MB';
+      const nowStr = this.formatDate(new Date());
+      this.projectFiles.unshift({
+        id: `file-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
+        name: file.name,
+        type: fakeType,
+        size: sizeMB,
+        date: nowStr,
+        url: '#'
+      });
+    }
+
+    this.isUploadingFile = false;
+    // 清空选择
+    input.value = '';
+  }
+
+  validateImage4K(file: File): Promise<boolean> {
+    return new Promise((resolve, reject) => {
+      const reader = new FileReader();
+      reader.onload = () => {
+        const img = new Image();
+        img.onload = () => {
+          const maxSide = Math.max(img.width, img.height);
+          resolve(maxSide >= 4000);
+        };
+        img.onerror = () => reject('image load error');
+        img.src = reader.result as string;
+      };
+      reader.onerror = () => reject('read error');
+      reader.readAsDataURL(file);
+    });
+  }
+
+  // 可选:列表 trackBy,优化渲染
+  trackById(_: number, item: { id: string }): string { return item.id; }
+
+  retryLoadRenderProgress(): void {
+    this.loadRenderProgress();
+  }
+
+  // 检查是否所有模型检查项都已通过
+
+
+  // 获取技能匹配度警告
+  getSkillMismatchWarning(): string | null {
+    if (!this.project) return null;
+    
+    // 模拟技能匹配度检查
+    const designerSkills = ['现代风格', '硬装'];
+    const requiredSkills = this.project.skillsRequired;
+    
+    const mismatchedSkills = requiredSkills.filter(skill => !designerSkills.includes(skill));
+    
+    if (mismatchedSkills.length > 0) {
+      return `警告:您不擅长${mismatchedSkills.join('、')},建议联系组长协调`;
+    }
+    
+    return null;
+  }
+
+  // 检查渲染是否超时
+  checkRenderTimeout(): void {
+    if (!this.renderProgress || !this.project) return;
+    
+    // 模拟交付前3小时预警
+    const deliveryTime = new Date(this.project.deadline);
+    const currentTime = new Date();
+    const timeDifference = deliveryTime.getTime() - currentTime.getTime();
+    const hoursRemaining = Math.floor(timeDifference / (1000 * 60 * 60));
+    
+    if (hoursRemaining <= 3 && hoursRemaining > 0) {
+      // 弹窗预警
+      alert('渲染进度预警:交付前3小时,请关注渲染进度');
+    }
+    
+    if (hoursRemaining <= 1 && hoursRemaining > 0) {
+      // 更严重的预警
+      alert('渲染进度严重预警:交付前1小时,渲染可能无法按时完成!');
+    }
+  }
+
+  // 为客户反馈添加分类标签
+  tagCustomerFeedbacks(): void {
+    this.feedbacks.forEach(feedback => {
+      // 添加分类标签
+      if (feedback.content.includes('色彩') || feedback.problemLocation?.includes('色彩')) {
+        (feedback as any).tag = '色彩问题';
+      } else if (feedback.content.includes('家具') || feedback.problemLocation?.includes('家具')) {
+        (feedback as any).tag = '家具款式问题';
+      } else if (feedback.content.includes('光线') || feedback.content.includes('照明')) {
+        (feedback as any).tag = '光线问题';
+      } else {
+        (feedback as any).tag = '其他问题';
+      }
+    });
+  }
+  
+  // 获取反馈标签的辅助方法
+  getFeedbackTag(feedback: CustomerFeedback): string {
+    return (feedback as any).tag || '';
+  }
+
+  // 检查反馈超时
+  checkFeedbackTimeout(): void {
+    const pendingFeedbacks = this.feedbacks.filter(f => f.status === '待处理');
+    if (pendingFeedbacks.length > 0) {
+      // 启动1小时倒计时
+      this.feedbackTimeoutCountdown = 3600; // 3600秒 = 1小时
+      this.startCountdown();
+    }
+  }
+
+  // 启动倒计时
+  startCountdown(): void {
+    this.countdownInterval = setInterval(() => {
+      if (this.feedbackTimeoutCountdown > 0) {
+        this.feedbackTimeoutCountdown--;
+      } else {
+        clearInterval(this.countdownInterval);
+        // 超时提醒
+        alert('客户反馈已超过1小时未响应,请立即处理!');
+        this.notifyTeamLeader('feedback-overdue');
+      }
+    }, 1000);
+  }
+
+  // 通知技术组长
+  notifyTeamLeader(type: 'render-failed' | 'feedback-overdue' | 'skill-mismatch'): void {
+    // 实际应用中应调用消息服务通知组长
+    console.log(`通知技术组长:${type} - 项目ID: ${this.projectId}`);
+  }
+
+  // 检查技能匹配度并提示
+  checkSkillMismatch(): void {
+    const warning = this.getSkillMismatchWarning();
+    if (warning) {
+      // 显示技能不匹配警告
+      if (confirm(`${warning}\n是否联系技术组长协调支持?`)) {
+        this.notifyTeamLeader('skill-mismatch');
+      }
+    }
+  }
+
+  // 发起设计师变更
+  initiateDesignerChange(reason: string): void {
+    // 实际应用中应调用API发起变更
+    console.log(`发起设计师变更,原因:${reason}`);
+    alert('已发起设计师变更申请,请等待新设计师承接');
+  }
+
+  // 确认承接变更项目
+  acceptDesignerChange(changeId: string): void {
+    // 实际应用中应调用API确认承接
+    console.log(`确认承接设计师变更:${changeId}`);
+    alert('已确认承接项目,系统已记录时间戳和责任人');
+  }
+
+  // 格式化倒计时显示
+  formatCountdown(seconds: number): string {
+    const hours = Math.floor(seconds / 3600);
+    const minutes = Math.floor((seconds % 3600) / 60);
+    const remainingSeconds = seconds % 60;
+    return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
+  }
+
+
+
+  // 提交异常反馈
+  submitExceptionFeedback(): void {
+    if (!this.exceptionDescription.trim() || this.isSubmittingFeedback) {
+      alert('请填写异常类型和描述');
+      return;
+    }
+
+    this.isSubmittingFeedback = true;
+    
+    // 模拟提交反馈到服务器
+    setTimeout(() => {
+      const newException: ExceptionHistory = {
+        id: `exception-${Date.now()}`,
+        type: this.exceptionType,
+        description: this.exceptionDescription,
+        submitTime: new Date(),
+        status: '待处理'
+      };
+
+      // 添加到历史记录中
+      this.exceptionHistories.unshift(newException);
+      
+      // 通知客服和技术支持
+      this.notifyTechnicalSupport(newException);
+      
+      // 清空表单
+      this.exceptionDescription = '';
+      this.clearExceptionScreenshot();
+      this.showExceptionForm = false;
+      
+      // 显示成功消息
+      alert('异常反馈已提交,技术支持将尽快处理');
+      
+      this.isSubmittingFeedback = false;
+    }, 1000);
+  }
+
+  // 上传异常截图
+  uploadExceptionScreenshot(event: Event): void {
+    const input = event.target as HTMLInputElement;
+    if (input.files && input.files[0]) {
+      const file = input.files[0];
+      // 在实际应用中,这里应该上传文件到服务器
+      // 这里我们使用FileReader来生成一个预览URL
+      const reader = new FileReader();
+      reader.onload = (e) => {
+        this.exceptionScreenshotUrl = e.target?.result as string;
+      };
+      reader.readAsDataURL(file);
+    }
+  }
+
+  // 清除异常截图
+  clearExceptionScreenshot(): void {
+    this.exceptionScreenshotUrl = null;
+    const input = document.getElementById('screenshot-upload') as HTMLInputElement;
+    if (input) {
+      input.value = '';
+    }
+  }
+
+  // 联系组长
+  contactTeamLeader() {
+    alert(`已联系${this.project?.assigneeName || '项目组长'}`);
+  }
+
+  // 处理渲染超时预警
+  handleRenderTimeout() {
+    alert('已发送渲染超时预警通知');
+  }
+
+  // 通知技术支持
+  notifyTechnicalSupport(exception: ExceptionHistory): void {
+    // 实际应用中应调用消息服务通知技术支持和客服
+    console.log(`通知技术支持和客服:渲染异常 - 项目ID: ${this.projectId}`);
+    console.log(`异常类型: ${this.getExceptionTypeText(exception.type)}, 描述: ${exception.description}`);
+  }
+
+  // 获取异常类型文本
+  getExceptionTypeText(type: string): string {
+    const typeMap: Record<string, string> = {
+      'failed': '渲染失败',
+      'stuck': '渲染卡顿',
+      'quality': '渲染质量问题',
+      'other': '其他问题'
+    };
+    return typeMap[type] || type;
+  }
+
+  // 格式化日期
+  formatDate(date: Date | string): string {
+    const d = typeof date === 'string' ? new Date(date) : date;
+    const year = d.getFullYear();
+    const month = String(d.getMonth() + 1).padStart(2, '0');
+    const day = String(d.getDate()).padStart(2, '0');
+    const hours = String(d.getHours()).padStart(2, '0');
+    const minutes = String(d.getMinutes()).padStart(2, '0');
+    return `${year}-${month}-${day} ${hours}:${minutes}`;
+  }
+
+  // 将字节格式化为易读尺寸
+  private formatFileSize(bytes: number): string {
+    if (bytes < 1024) return `${bytes}B`;
+    const kb = bytes / 1024;
+    if (kb < 1024) return `${kb.toFixed(1)}KB`;
+    const mb = kb / 1024;
+    if (mb < 1024) return `${mb.toFixed(1)}MB`;
+    const gb = mb / 1024;
+    return `${gb.toFixed(2)}GB`;
+  }
+
+  // 生成缩略图条目(并创建本地预览URL)
+  private makeImageItem(file: File): { id: string; name: string; url: string; size: string } {
+    const id = `img-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
+    const url = URL.createObjectURL(file);
+    return { id, name: file.name, url, size: this.formatFileSize(file.size) };
+  }
+
+  // 释放对象URL
+  private revokeUrl(url: string): void {
+    try { if (url && url.startsWith('blob:')) URL.revokeObjectURL(url); } catch {}
+  }
+
+  // =========== 建模阶段:白模上传 ===========
+  onWhiteModelSelected(event: Event): void {
+    const input = event.target as HTMLInputElement;
+    if (!input.files || input.files.length === 0) return;
+    const files = Array.from(input.files).filter(f => /\.(jpg|jpeg|png)$/i.test(f.name));
+    const items = files.map(f => this.makeImageItem(f));
+    this.whiteModelImages.unshift(...items);
+    input.value = '';
+  }
+  removeWhiteModelImage(id: string): void {
+    const target = this.whiteModelImages.find(i => i.id === id);
+    if (target) this.revokeUrl(target.url);
+    this.whiteModelImages = this.whiteModelImages.filter(i => i.id !== id);
+  }
+
+  // 新增:建模阶段 确认上传并自动进入下一阶段(软装)
+  confirmWhiteModelUpload(): void {
+    // 检查建模阶段的图片数据
+    const modelingProcess = this.deliveryProcesses.find(p => p.id === 'modeling');
+    if (!modelingProcess) return;
+    
+    // 检查是否有任何空间上传了图片
+    const hasImages = modelingProcess.spaces.some(space => {
+      const content = modelingProcess.content[space.id];
+      return content && content.images && content.images.length > 0;
+    });
+    
+    if (!hasImages) return;
+    this.advanceToNextStage('建模');
+  }
+
+  // =========== 软装阶段:小图上传(建议≤1MB,不强制) ===========
+  onSoftDecorSmallPicsSelected(event: Event): void {
+    const input = event.target as HTMLInputElement;
+    if (!input.files || input.files.length === 0) return;
+    const files = Array.from(input.files).filter(f => /\.(jpg|jpeg|png)$/i.test(f.name));
+    const warnOversize = files.filter(f => f.size > 1024 * 1024);
+    if (warnOversize.length > 0) {
+      // 仅提示,不阻断
+      console.warn('软装小图建议≤1MB,以下文件较大:', warnOversize.map(f => f.name));
+    }
+    const items = files.map(f => this.makeImageItem(f));
+    this.softDecorImages.unshift(...items);
+    input.value = '';
+  }
+  // 拖拽上传相关属性
+  isDragOver: boolean = false;
+
+  // 图片预览相关属性
+  showImagePreview: boolean = false;
+  previewImageData: any = null;
+
+  // 图片预览方法(含渲染大图加锁校验)
+  previewImage(img: any): void {
+    const isRenderLarge = !!this.renderLargeImages.find(i => i.id === img?.id);
+    if (isRenderLarge && img?.locked) {
+      alert('该渲染大图已加锁,需完成尾款结算并上传/识别支付凭证后方可预览。');
+      return;
+    }
+    this.previewImageData = img;
+    this.showImagePreview = true;
+  }
+
+  closeImagePreview(): void {
+    this.showImagePreview = false;
+    this.previewImageData = null;
+  }
+
+  downloadImage(img: any): void {
+    const isRenderLarge = !!this.renderLargeImages.find(i => i.id === img?.id);
+    if (isRenderLarge && img?.locked) {
+      alert('该渲染大图已加锁,需完成尾款结算并上传/识别支付凭证后方可下载。');
+      return;
+    }
+    if (img) {
+      const link = document.createElement('a');
+      link.href = img.url;
+      link.download = img.name;
+      link.click();
+    }
+  }
+
+  removeImageFromPreview(): void {
+    if (this.previewImageData) {
+      // 首先检查新的 deliveryProcesses 结构
+      let imageFound = false;
+      
+      for (const process of this.deliveryProcesses) {
+        for (const space of process.spaces) {
+          if (process.content[space.id]?.images) {
+            const imageIndex = process.content[space.id].images.findIndex(img => img.id === this.previewImageData.id);
+            if (imageIndex > -1) {
+              this.removeSpaceImage(process.id, space.id, this.previewImageData.id);
+              imageFound = true;
+              break;
+            }
+          }
+        }
+        if (imageFound) break;
+      }
+      
+      // 如果在新结构中没找到,检查旧的图片数组
+      if (!imageFound) {
+        if (this.whiteModelImages.find(i => i.id === this.previewImageData.id)) {
+          this.removeWhiteModelImage(this.previewImageData.id);
+        } else if (this.softDecorImages.find(i => i.id === this.previewImageData.id)) {
+          this.removeSoftDecorImage(this.previewImageData.id);
+        } else if (this.renderLargeImages.find(i => i.id === this.previewImageData.id)) {
+          this.removeRenderLargeImage(this.previewImageData.id);
+        } else if (this.postProcessImages.find(i => i.id === this.previewImageData.id)) {
+          this.removePostProcessImage(this.previewImageData.id);
+        }
+      }
+      
+      this.closeImagePreview();
+    }
+  }
+
+  // 拖拽事件处理
+  onDragOver(event: DragEvent): void {
+    event.preventDefault();
+    event.stopPropagation();
+    this.isDragOver = true;
+  }
+
+  onDragLeave(event: DragEvent): void {
+    event.preventDefault();
+    event.stopPropagation();
+    this.isDragOver = false;
+  }
+
+  onFileDrop(event: DragEvent, type: 'whiteModel' | 'softDecor' | 'render' | 'postProcess'): void {
+    event.preventDefault();
+    event.stopPropagation();
+    this.isDragOver = false;
+
+    const files = event.dataTransfer?.files;
+    if (!files || files.length === 0) return;
+
+    // 创建模拟的input事件
+    const mockEvent = {
+      target: {
+        files: files
+      }
+    } as any;
+
+    // 根据类型调用相应的处理方法
+    switch (type) {
+      case 'whiteModel':
+        this.onWhiteModelSelected(mockEvent);
+        break;
+      case 'softDecor':
+        this.onSoftDecorSmallPicsSelected(mockEvent);
+        break;
+      case 'render':
+        this.onRenderLargePicsSelected(mockEvent);
+        break;
+      case 'postProcess':
+        this.onPostProcessPicsSelected(mockEvent);
+        break;
+    }
+  }
+
+  // 触发文件输入框
+  triggerFileInput(type: 'whiteModel' | 'softDecor' | 'render' | 'postProcess'): void {
+    let inputId: string;
+    switch (type) {
+      case 'whiteModel':
+        inputId = 'whiteModelFileInput';
+        break;
+      case 'softDecor':
+        inputId = 'softDecorFileInput';
+        break;
+      case 'render':
+        inputId = 'renderFileInput';
+        break;
+      case 'postProcess':
+        inputId = 'postProcessFileInput';
+        break;
+    }
+    const input = document.querySelector(`#${inputId}`) as HTMLInputElement;
+    if (input) {
+      input.click();
+    }
+  }
+
+  removeSoftDecorImage(id: string): void {
+    const target = this.softDecorImages.find(i => i.id === id);
+    if (target) this.revokeUrl(target.url);
+    this.softDecorImages = this.softDecorImages.filter(i => i.id !== id);
+  }
+
+  // 新增:后期阶段图片上传处理
+  async onPostProcessPicsSelected(event: Event): Promise<void> {
+    const input = event.target as HTMLInputElement;
+    if (!input.files || input.files.length === 0) return;
+    const files = Array.from(input.files).filter(f => /\.(jpg|jpeg|png)$/i.test(f.name));
+
+    for (const f of files) {
+      const item = this.makeImageItem(f);
+      this.postProcessImages.unshift({ 
+        id: item.id, 
+        name: item.name, 
+        url: item.url, 
+        size: this.formatFileSize(f.size) 
+      });
+    }
+    input.value = '';
+  }
+
+  removePostProcessImage(id: string): void {
+    const target = this.postProcessImages.find(i => i.id === id);
+    if (target) this.revokeUrl(target.url);
+    this.postProcessImages = this.postProcessImages.filter(i => i.id !== id);
+  }
+
+  // 新增:后期阶段 确认上传并自动进入下一阶段(尾款结算)
+  confirmPostProcessUpload(): void {
+    // 检查后期阶段的图片数据
+    const postProcessProcess = this.deliveryProcesses.find(p => p.id === 'post-processing');
+    if (!postProcessProcess) return;
+    
+    // 检查是否有任何空间上传了图片
+    const hasImages = postProcessProcess.spaces.some(space => {
+      const content = postProcessProcess.content[space.id];
+      return content && content.images && content.images.length > 0;
+    });
+    
+    if (!hasImages) return;
+    this.advanceToNextStage('后期');
+  }
+
+  // 新增:尾款结算阶段确认并自动进入下一阶段(客户评价)
+  confirmSettlement(): void {
+    if (this.isConfirmingSettlement) return;
+    
+    this.isConfirmingSettlement = true;
+    
+    // 模拟API调用延迟
+    setTimeout(() => {
+      this.isSettlementCompleted = true;
+      this.isConfirmingSettlement = false;
+      
+      // 显示成功提示
+      alert('尾款结算已确认完成!');
+      
+      // 进入下一阶段
+      this.advanceToNextStage('尾款结算');
+    }, 1500);
+  }
+
+  // 新增:投诉处理阶段确认并完成项目(基础版本,详细版本在售后模块中)
+  confirmComplaintBasic(): void {
+    console.log('确认投诉处理完成');
+    // 可以在这里添加更多逻辑,比如标记项目完成等
+    // 调用服务更新后端数据
+    // this.projectService.confirmComplaintResolution(this.projectId);
+    this.advanceToNextStage('投诉处理');
+  }
+
+  // 新增:软装阶段 确认上传并自动进入下一阶段(渲染)
+  confirmSoftDecorUpload(): void {
+    // 检查软装阶段的图片数据
+    const softDecorProcess = this.deliveryProcesses.find(p => p.id === 'soft-decoration');
+    if (!softDecorProcess) return;
+    
+    // 检查是否有任何空间上传了图片
+    const hasImages = softDecorProcess.spaces.some(space => {
+      const content = softDecorProcess.content[space.id];
+      return content && content.images && content.images.length > 0;
+    });
+    
+    if (!hasImages) return;
+    this.advanceToNextStage('软装');
+  }
+
+  // 新增:渲染阶段 确认上传并自动进入下一阶段(后期)
+  confirmRenderUpload(): void {
+    // 检查渲染阶段的图片数据
+    const renderProcess = this.deliveryProcesses.find(p => p.id === 'rendering');
+    if (!renderProcess) return;
+    
+    // 检查是否有任何空间上传了图片
+    const hasImages = renderProcess.spaces.some(space => {
+      const content = renderProcess.content[space.id];
+      return content && content.images && content.images.length > 0;
+    });
+    
+    if (!hasImages) return;
+    this.advanceToNextStage('渲染');
+  }
+
+  // =========== 渲染阶段:大图上传(弹窗 + 4K校验) ===========
+  openRenderUploadModal(): void {
+    this.showRenderUploadModal = true;
+    this.pendingRenderLargeItems = [];
+  }
+  
+  closeRenderUploadModal(): void {
+    // 关闭时释放临时预览URL
+    this.pendingRenderLargeItems.forEach(i => this.revokeUrl(i.url));
+    this.pendingRenderLargeItems = [];
+    this.showRenderUploadModal = false;
+  }
+  
+  async onRenderLargePicsSelected(event: Event): Promise<void> {
+    const input = event.target as HTMLInputElement;
+    if (!input.files || input.files.length === 0) return;
+    const files = Array.from(input.files).filter(f => /\.(jpg|jpeg|png)$/i.test(f.name));
+
+    for (const f of files) {
+      const ok = await this.validateImage4K(f).catch(() => false);
+      if (!ok) {
+        alert(`图片不符合4K标准(最大边需≥4000像素):${f.name}`);
+        continue;
+      }
+      const item = this.makeImageItem(f);
+      // 直接添加到正式列表,不再使用待确认列表
+      this.renderLargeImages.unshift({ 
+        id: item.id, 
+        name: item.name, 
+        url: item.url, 
+        size: this.formatFileSize(f.size),
+        locked: true 
+      });
+    }
+    input.value = '';
+  }
+  
+  removeRenderLargeImage(id: string): void {
+    const target = this.renderLargeImages.find(i => i.id === id);
+    if (target) this.revokeUrl(target.url);
+    this.renderLargeImages = this.renderLargeImages.filter(i => i.id !== id);
+  }
+
+  // 根据阶段映射所属板块
+  getSectionKeyForStage(stage: ProjectStage): SectionKey {
+    switch (stage) {
+      case '订单创建':
+        return 'order';
+      case '需求沟通':
+      case '方案确认':
+        return 'requirements';
+      case '建模':
+      case '软装':
+      case '渲染':
+      case '后期':
+        return 'delivery';
+      case '尾款结算':
+      case '客户评价':
+      case '投诉处理':
+        return 'aftercare';
+      default:
+        return 'order';
+    }
+  }
+
+  // 获取板块状态:completed | 'active' | 'pending'
+  getSectionStatus(key: SectionKey): 'completed' | 'active' | 'pending' {
+    // 优先使用本地的currentStage,如果没有则使用project.currentStage
+    const current = (this.currentStage || this.project?.currentStage) as ProjectStage | undefined;
+    
+    // 如果没有当前阶段(新创建的项目),默认订单创建板块为active(红色)
+    if (!current || current === '订单创建') {
+      return key === 'order' ? 'active' : 'pending';
+    }
+
+    // 获取当前阶段所属的板块
+    const currentSection = this.getSectionKeyForStage(current);
+    const sectionOrder = this.sections.map(s => s.key);
+    const currentIdx = sectionOrder.indexOf(currentSection);
+    const idx = sectionOrder.indexOf(key);
+    
+    if (idx === -1 || currentIdx === -1) return 'pending';
+
+    // 已完成的板块:当前阶段所在板块之前的所有板块
+    if (idx < currentIdx) return 'completed';
+    
+    // 当前进行中的板块:当前阶段所在的板块
+    if (idx === currentIdx) return 'active';
+    
+    // 未开始的板块:当前阶段所在板块之后的所有板块
+    return 'pending';
+  }
+
+  // 切换四大板块(单展开)
+  toggleSection(key: SectionKey): void {
+    this.expandedSection = key;
+    // 点击板块按钮时,滚动到该板块的第一个可见阶段卡片
+    const sec = this.sections.find(s => s.key === key);
+    if (sec) {
+      // 设计师仅滚动到可见的三大执行阶段,否则取该板块第一个阶段
+      const candidate = this.isDesignerView()
+        ? sec.stages.find(st => ['建模', '软装', '渲染'].includes(st)) || sec.stages[0]
+        : sec.stages[0];
+      this.scrollToStage(candidate);
+    }
+  }
+
+  // 阶段到锚点的映射
+  stageToAnchor(stage: ProjectStage): string {
+    const map: Record<ProjectStage, string> = {
+      '订单创建': 'order',
+      '需求沟通': 'requirements-talk',
+      '方案确认': 'proposal-confirm',
+      '建模': 'modeling',
+      '软装': 'softdecor',
+      '渲染': 'render',
+      '后期': 'postprocess',
+      '尾款结算': 'settlement',
+      '客户评价': 'customer-review',
+      '投诉处理': 'complaint'
+    };
+    return `stage-${map[stage] || 'unknown'}`;
+  }
+
+  // 平滑滚动到指定阶段卡片
+  scrollToStage(stage: ProjectStage): void {
+    const anchor = this.stageToAnchor(stage);
+    const el = document.getElementById(anchor);
+    if (el) {
+      el.scrollIntoView({ behavior: 'smooth', block: 'start' });
+    }
+  }
+
+  // 订单创建阶段:客户信息(迁移自客服端"客户信息"卡片)
+  orderCreationMethod: 'miniprogram' | 'manual' = 'miniprogram';
+  isSyncing: boolean = false;
+  orderTime: string = '';
+
+  // 客户信息实时同步相关变量
+  isSyncingCustomerInfo: boolean = false;
+  lastSyncTime: Date | null = null;
+  syncInterval: any = null;
+
+  customerForm!: FormGroup;
+  customerSearchKeyword: string = '';
+  customerSearchResults: Array<{ id: string; name: string; phone: string; wechat?: string; avatar?: string; customerType?: string; source?: string; remark?: string }> = [];
+  selectedOrderCustomer: { id: string; name: string; phone: string; wechat?: string; avatar?: string; customerType?: string; source?: string; remark?: string } | null = null;
+
+  demandTypes = [
+    { value: 'price', label: '价格敏感' },
+    { value: 'quality', label: '质量敏感' },
+    { value: 'comprehensive', label: '综合要求' }
+  ];
+  
+  followUpStatus = [
+    { value: 'quotation', label: '待报价' },
+    { value: 'confirm', label: '待确认需求' },
+    { value: 'lost', label: '已失联' }
+  ];
+
+  // 需求关键信息同步数据
+  requirementKeyInfo = {
+    colorAtmosphere: {
+      description: '',
+      mainColor: '',
+      colorTemp: '',
+      materials: [] as string[]
+    },
+    spaceStructure: {
+      lineRatio: 0,
+      blankRatio: 0,
+      flowWidth: 0,
+      aspectRatio: 0,
+      ceilingHeight: 0
+    },
+    materialWeights: {
+      fabricRatio: 0,
+      woodRatio: 0,
+      metalRatio: 0,
+      smoothness: 0,
+      glossiness: 0
+    },
+    presetAtmosphere: {
+      name: '',
+      rgb: '',
+      colorTemp: '',
+      materials: [] as string[]
+    }
+  };
+
+  // 客户信息:搜索/选择/清空/同步/快速填写 逻辑
+  searchCustomer(): void {
+    if (this.customerSearchKeyword.trim().length >= 2) {
+      this.customerSearchResults = [
+        { id: '1', name: '张先生', phone: '138****5678', customerType: '老客户', source: '官网咨询', avatar: "data:image/svg+xml,%3Csvg width='64' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23E6E6E6'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='13.333333333333334' font-weight='bold' text-anchor='middle' fill='%23555555' dy='0.3em'%3EIMG%3C/text%3E%3C/svg%3E" },
+        { id: '2', name: '李女士', phone: '139****1234', customerType: 'VIP客户', source: '推荐介绍', avatar: "data:image/svg+xml,%3Csvg width='65' height='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='100%25' height='100%25' fill='%23DCDCDC'/%3E%3Ctext x='50%25' y='50%25' font-family='Arial' font-size='13.333333333333334' font-weight='bold' text-anchor='middle' fill='%23555555' dy='0.3em'%3EIMG%3C/text%3E%3C/svg%3E" }
+      ];
+    } else {
+      this.customerSearchResults = [];
+    }
+  }
+
+  selectCustomer(customer: { id: string; name: string; phone: string; wechat?: string; avatar?: string; customerType?: string; source?: string; remark?: string }): void {
+    this.selectedOrderCustomer = customer;
+    this.customerForm.patchValue({
+      name: customer.name,
+      phone: customer.phone,
+      wechat: customer.wechat || '',
+      customerType: customer.customerType || '新客户',
+      source: customer.source || '',
+      remark: customer.remark || ''
+    });
+    this.customerSearchResults = [];
+    this.customerSearchKeyword = '';
+  }
+
+  clearSelectedCustomer(): void {
+    this.selectedOrderCustomer = null;
+    this.customerForm.reset({ customerType: '新客户' });
+  }
+
+  quickFillCustomerInfo(keyword: string): void {
+    const k = (keyword || '').trim();
+    if (!k) return;
+    // 模拟:若有搜索结果,选择第一条
+    if (this.customerSearchResults.length === 0) this.searchCustomer();
+    if (this.customerSearchResults.length > 0) {
+      this.selectCustomer(this.customerSearchResults[0]);
+    }
+  }
+
+  syncMiniprogramCustomerInfo(): void {
+    if (this.isSyncing) return;
+    this.isSyncing = true;
+    setTimeout(() => {
+      // 模拟从小程序同步到客户表单
+      this.customerForm.patchValue({
+        name: '小程序用户',
+        phone: '13800001234',
+        wechat: 'wx_user_001',
+        customerType: '新客户',
+        source: '小程序下单'
+      });
+      this.isSyncing = false;
+      
+      // 触发客户信息同步显示
+      this.syncCustomerInfoDisplay();
+    }, 1000);
+  }
+
+  // 同步客户信息显示
+  syncCustomerInfoDisplay(): void {
+    this.isSyncingCustomerInfo = true;
+    
+    // 模拟同步过程
+    setTimeout(() => {
+      this.lastSyncTime = new Date();
+      this.isSyncingCustomerInfo = false;
+      
+      // 触发界面更新
+      this.cdr.detectChanges();
+      
+      console.log('客户信息显示已同步:', this.lastSyncTime);
+    }, 800);
+  }
+
+  // 启动自动同步
+  startAutoSync(): void {
+    if (this.syncInterval) {
+      clearInterval(this.syncInterval);
+    }
+    
+    // 每30秒自动同步一次
+    this.syncInterval = setInterval(() => {
+      this.syncCustomerInfoDisplay();
+    }, 30000);
+  }
+
+  // 停止自动同步
+  stopAutoSync(): void {
+    if (this.syncInterval) {
+      clearInterval(this.syncInterval);
+      this.syncInterval = null;
+    }
+  }
+
+  // 格式化时间显示
+  formatTime(date: Date): string {
+    const now = new Date();
+    const diff = now.getTime() - date.getTime();
+    const minutes = Math.floor(diff / 60000);
+    
+    if (minutes < 1) {
+      return '刚刚';
+    } else if (minutes < 60) {
+      return `${minutes}分钟前`;
+    } else {
+      const hours = Math.floor(minutes / 60);
+      return `${hours}小时前`;
+    }
+  }
+
+  downloadFile(file: ProjectFile): void {
+    // 实现文件下载逻辑
+    const link = document.createElement('a');
+    link.href = file.url;
+    link.download = file.name;
+    link.click();
+  }
+
+  previewFile(file: ProjectFile): void {
+    // 预览文件逻辑
+    console.log('预览文件:', file.name);
+  }
+
+  // 同步需求关键信息到客户信息卡片
+  syncRequirementKeyInfo(requirementData: any): void {
+    if (requirementData) {
+      // 同步色彩氛围信息
+      if (requirementData.colorIndicators) {
+        this.requirementKeyInfo.colorAtmosphere = {
+          description: requirementData.colorIndicators.colorRange || '',
+          mainColor: `rgb(${requirementData.colorIndicators.mainColor?.r || 0}, ${requirementData.colorIndicators.mainColor?.g || 0}, ${requirementData.colorIndicators.mainColor?.b || 0})`,
+          colorTemp: `${requirementData.colorIndicators.colorTemperature || 0}K`,
+          materials: []
+        };
+      }
+
+      // 同步空间结构信息
+      if (requirementData.spaceIndicators) {
+        this.requirementKeyInfo.spaceStructure = {
+          lineRatio: requirementData.spaceIndicators.lineRatio || 0,
+          blankRatio: requirementData.spaceIndicators.blankRatio || 0,
+          flowWidth: requirementData.spaceIndicators.flowWidth || 0,
+          aspectRatio: requirementData.spaceIndicators.aspectRatio || 0,
+          ceilingHeight: requirementData.spaceIndicators.ceilingHeight || 0
+        };
+      }
+
+      // 同步材质权重信息
+      if (requirementData.materialIndicators) {
+        this.requirementKeyInfo.materialWeights = {
+          fabricRatio: requirementData.materialIndicators.fabricRatio || 0,
+          woodRatio: requirementData.materialIndicators.woodRatio || 0,
+          metalRatio: requirementData.materialIndicators.metalRatio || 0,
+          smoothness: requirementData.materialIndicators.smoothness || 0,
+          glossiness: requirementData.materialIndicators.glossiness || 0
+        };
+      }
+
+      // 同步预设氛围信息
+      if (requirementData.selectedPresetAtmosphere) {
+        this.requirementKeyInfo.presetAtmosphere = {
+          name: requirementData.selectedPresetAtmosphere.name || '',
+          rgb: requirementData.selectedPresetAtmosphere.rgb || '',
+          colorTemp: requirementData.selectedPresetAtmosphere.colorTemp || '',
+          materials: requirementData.selectedPresetAtmosphere.materials || []
+        };
+      }
+
+      // 新增:实时更新左侧客户信息显示
+      if (this.project) {
+        // 更新项目的客户信息
+        if (requirementData.colorIndicators && requirementData.colorIndicators.length > 0) {
+          this.project.customerInfo = {
+            ...this.project.customerInfo,
+            colorPreference: requirementData.colorIndicators.map((indicator: any) => indicator.name).join(', ')
+          };
+        }
+        
+        // 更新空间结构信息
+        if (requirementData.spaceIndicators && requirementData.spaceIndicators.length > 0) {
+          this.project.customerInfo = {
+            ...this.project.customerInfo,
+            spaceRequirements: requirementData.spaceIndicators.map((indicator: any) => `${indicator.name}: ${indicator.value}`).join(', ')
+          };
+        }
+        
+        // 更新材质偏好
+        if (requirementData.materialIndicators && requirementData.materialIndicators.length > 0) {
+          this.project.customerInfo = {
+            ...this.project.customerInfo,
+            materialPreference: requirementData.materialIndicators.map((indicator: any) => `${indicator.name}: ${indicator.value}%`).join(', ')
+          };
+        }
+        
+        // 触发变更检测以更新UI
+        this.cdr.detectChanges();
+      }
+
+      console.log('需求关键信息已同步:', this.requirementKeyInfo);
+    } else {
+      // 模拟数据用于演示
+      this.requirementKeyInfo = {
+        colorAtmosphere: {
+          description: '温馨暖调',
+          mainColor: 'rgb(255, 230, 180)',
+          colorTemp: '2700K',
+          materials: ['木质', '布艺']
+        },
+        spaceStructure: {
+          lineRatio: 60,
+          blankRatio: 30,
+          flowWidth: 0.9,
+          aspectRatio: 1.6,
+          ceilingHeight: 2.8
+        },
+        materialWeights: {
+          fabricRatio: 50,
+          woodRatio: 30,
+          metalRatio: 20,
+          smoothness: 7,
+          glossiness: 4
+        },
+        presetAtmosphere: {
+          name: '现代简约',
+          rgb: '200,220,240',
+          colorTemp: '5000K',
+          materials: ['金属', '玻璃']
+        }
+      };
+    }
+  }
+
+  // 新增:处理需求阶段完成事件
+  onRequirementsStageCompleted(event: { stage: string; allStagesCompleted: boolean }): void {
+    console.log('需求阶段完成事件:', event);
+    
+    if (event.allStagesCompleted && event.stage === 'requirements-communication') {
+      // 自动推进到方案确认阶段
+      this.currentStage = '方案确认';
+      this.expandedStages['方案确认'] = true;
+      this.expandedStages['需求沟通'] = false;
+      
+      // 更新项目状态
+      this.updateProjectStage('方案确认');
+      
+      console.log('自动推进到方案确认阶段');
+    }
+  }
+
+  // 新增:确认方案方法
+  confirmProposal(): void {
+    console.log('确认方案按钮被点击');
+    
+    // 使用统一的阶段推进方法
+    this.advanceToNextStage('方案确认');
+    
+    console.log('已跳转到建模阶段');
+  }
+
+  // 获取同步的关键信息摘要
+  getRequirementSummary(): string[] {
+    const summary: string[] = [];
+    
+    if (this.requirementKeyInfo.colorAtmosphere.description) {
+      summary.push(`色彩氛围: ${this.requirementKeyInfo.colorAtmosphere.description}`);
+    }
+    
+    if (this.requirementKeyInfo.spaceStructure.aspectRatio > 0) {
+      summary.push(`空间比例: ${this.requirementKeyInfo.spaceStructure.aspectRatio.toFixed(1)}`);
+    }
+    
+    if (this.requirementKeyInfo.materialWeights.woodRatio > 0) {
+      summary.push(`木质占比: ${this.requirementKeyInfo.materialWeights.woodRatio}%`);
+    }
+    
+    if (this.requirementKeyInfo.presetAtmosphere.name) {
+      summary.push(`预设氛围: ${this.requirementKeyInfo.presetAtmosphere.name}`);
+    }
+    
+    return summary;
+  }
+
+  // 检查必需阶段是否全部完成(流程进度 > 确认需求 > 需求沟通四个流程)
+  areRequiredStagesCompleted(): boolean {
+    // 检查项目是否已经进入方案确认阶段或更后的阶段
+    if (!this.project) {
+      return false;
+    }
+    
+    const stageOrder = [
+      '订单创建', '需求沟通', '方案确认', '建模', '软装', 
+      '渲染', '尾款结算', '客户评价', '投诉处理'
+    ];
+    
+    const currentStageIndex = stageOrder.indexOf(this.project.currentStage);
+    const proposalStageIndex = stageOrder.indexOf('方案确认');
+    const requirementStageIndex = stageOrder.indexOf('需求沟通');
+    
+    // 如果当前阶段是方案确认或之后的阶段,则认为需求阶段已完成
+    if (currentStageIndex >= proposalStageIndex) {
+      // 确保有基本的需求信息数据,如果没有则初始化模拟数据
+      this.ensureRequirementData();
+      return true;
+    }
+    
+    // 如果当前阶段是需求沟通,检查需求沟通是否已完成
+    if (currentStageIndex === requirementStageIndex) {
+      // 检查需求关键信息是否有数据,或者检查需求沟通组件的完成状态
+      const hasRequirementData = this.getRequirementSummary().length > 0 && 
+             (!!this.requirementKeyInfo.colorAtmosphere.description || 
+              this.requirementKeyInfo.spaceStructure.aspectRatio > 0 ||
+              this.requirementKeyInfo.materialWeights.woodRatio > 0 ||
+              !!this.requirementKeyInfo.presetAtmosphere.name);
+      
+      // 只有在真正有需求数据时才返回true,不再自动初始化模拟数据
+      if (hasRequirementData) {
+        return true;
+      }
+      
+      // 如果没有需求数据,返回false,不允许进入下一阶段
+      return false;
+    }
+    
+    // 其他情况返回false
+    return false;
+  }
+
+  // 确保有需求数据用于方案确认显示
+  private ensureRequirementData(): void {
+    // console.log('=== ensureRequirementData 开始确保需求数据 ===');
+    // console.log('当前requirementKeyInfo:', this.requirementKeyInfo);
+    
+    // 修复条件判断:检查是否需要初始化数据
+    const needsInitialization = 
+      !this.requirementKeyInfo.colorAtmosphere.description || 
+      this.requirementKeyInfo.spaceStructure.aspectRatio === 0 ||
+      this.requirementKeyInfo.materialWeights.woodRatio === 0 ||
+      !this.requirementKeyInfo.presetAtmosphere.name;
+    
+    console.log('是否需要初始化数据:', needsInitialization);
+    
+    if (needsInitialization) {
+      console.log('需求关键信息为空,初始化默认数据');
+      // 初始化模拟的需求数据
+      this.requirementKeyInfo = {
+        colorAtmosphere: {
+          description: '现代简约风格,以白色和灰色为主调',
+          mainColor: '#F5F5F5',
+          colorTemp: '冷色调',
+          materials: ['木质', '金属', '玻璃']
+        },
+        spaceStructure: {
+          lineRatio: 0.6,
+          blankRatio: 0.4,
+          flowWidth: 1.2,
+          aspectRatio: 1.8,
+          ceilingHeight: 2.8
+        },
+        materialWeights: {
+          fabricRatio: 20,
+          woodRatio: 45,
+          metalRatio: 25,
+          smoothness: 0.7,
+          glossiness: 0.3
+        },
+        presetAtmosphere: {
+          name: '现代简约',
+          rgb: '#F5F5F5',
+          colorTemp: '5000K',
+          materials: ['木质', '金属']
+        }
+      };
+      // console.log('初始化后的requirementKeyInfo:', this.requirementKeyInfo);
+    } else {
+      // console.log('需求关键信息已存在,无需初始化');
+    }
+  }
+
+  // 获取项目状态文本
+  getProjectStatusText(): string {
+    const current = (this.currentStage || this.project?.currentStage) as ProjectStage | undefined;
+    
+    if (!current || current === '订单创建') {
+      return '待开始';
+    }
+    
+    // 检查是否已完成所有阶段
+    const allStages: ProjectStage[] = ['订单创建', '需求沟通', '方案确认', '建模', '软装', '渲染', '尾款结算', '客户评价', '投诉处理'];
+    const currentIndex = allStages.indexOf(current);
+    
+    if (currentIndex === allStages.length - 1) {
+      return '已完成';
+    }
+    
+    return '进行中';
+  }
+
+  // 获取当前阶段文本
+  getCurrentStageText(): string {
+    const current = (this.currentStage || this.project?.currentStage) as ProjectStage | undefined;
+    
+    if (!current || current === '订单创建') {
+      return '订单创建阶段';
+    }
+    
+    return `${current}阶段`;
+  }
+
+  // 获取整体进度百分比
+  getOverallProgress(): number {
+    const current = (this.currentStage || this.project?.currentStage) as ProjectStage | undefined;
+    
+    if (!current) {
+      return 0;
+    }
+    
+    // 定义所有阶段及其权重
+    const stageWeights: Record<ProjectStage, number> = {
+      '订单创建': 5,
+      '需求沟通': 15,
+      '方案确认': 25,
+      '建模': 40,
+      '软装': 55,
+      '渲染': 70,
+      '后期': 80,
+      '尾款结算': 90,
+      '客户评价': 95,
+      '投诉处理': 100
+    };
+    
+    return stageWeights[current] || 0;
+  }
+
+  // 获取必需阶段的完成进度百分比
+  getRequiredStagesProgress(): number {
+    let completedCount = 0;
+    const totalCount = 4; // 四个必需流程
+
+    // 检查各个关键信息是否已确认
+    if (this.requirementKeyInfo.colorAtmosphere.description) completedCount++;
+    if (this.requirementKeyInfo.spaceStructure.aspectRatio > 0) completedCount++;
+    if (this.requirementKeyInfo.materialWeights.woodRatio > 0 || 
+        this.requirementKeyInfo.materialWeights.fabricRatio > 0 || 
+        this.requirementKeyInfo.materialWeights.metalRatio > 0) completedCount++;
+    if (this.requirementKeyInfo.presetAtmosphere.name) completedCount++;
+
+    return Math.round((completedCount / totalCount) * 100);
+  }
+
+  // 订单金额
+  orderAmount: number = 0;
+  
+  // 报价明细
+  quotationDetails: Array<{
+    id: string;
+    room: string;
+    amount: number;
+    description?: string;
+  }> = [];
+
+  // AI生成报价明细
+  generateQuotationDetails(): void {
+    // 基于项目信息生成报价明细
+    const rooms = ['客餐厅', '主卧', '次卧', '厨房', '卫生间'];
+    this.quotationDetails = rooms.map((room, index) => ({
+      id: `quote_${index + 1}`,
+      room: room,
+      amount: Math.floor(Math.random() * 1000) + 300, // 示例金额
+      description: `${room}装修设计费用`
+    }));
+    
+    // 更新总订单金额
+    this.orderAmount = this.quotationDetails.reduce((total, item) => total + item.amount, 0);
+  }
+
+  // 添加报价明细项
+  addQuotationItem(): void {
+    this.quotationDetails.push({
+      id: `quote_${Date.now()}`,
+      room: '',
+      amount: 0,
+      description: ''
+    });
+  }
+
+  // 删除报价明细项
+  removeQuotationItem(id: string): void {
+    this.quotationDetails = this.quotationDetails.filter(item => item.id !== id);
+    this.updateOrderAmount();
+  }
+
+  // 更新订单总金额
+  updateOrderAmount(): void {
+    this.orderAmount = this.quotationDetails.reduce((total, item) => total + item.amount, 0);
+  }
+
+  // 报价组件数据
+  quotationData: QuotationData = {
+    items: [],
+    totalAmount: 0,
+    materialCost: 0,
+    laborCost: 0,
+    designFee: 0,
+    managementFee: 0
+  };
+
+  // 设计师指派数据
+  designerAssignmentData?: DesignerAssignmentData;
+
+  // 设计师日历弹窗状态与数据
+  showDesignerCalendar: boolean = false;
+  selectedCalendarDate: Date = new Date();
+  calendarDesigners: CalendarDesigner[] = [];
+  calendarGroups: CalendarProjectGroup[] = [];
+
+  onQuotationDataChange(data: QuotationData): void {
+    this.quotationData = { ...data };
+    this.orderAmount = data.totalAmount || 0;
+  }
+
+  onDesignerAssignmentChange(data: DesignerAssignmentData): void {
+    this.designerAssignmentData = { ...data };
+  }
+
+  onDesignerClick(designer: AssignmentDesigner): void {
+    const mapped = this.mapAssignmentDesignerToCalendar(designer);
+    this.calendarDesigners = [mapped];
+    this.calendarGroups = [{ id: designer.teamId, name: designer.teamName, leaderId: designer.id, memberIds: [designer.id] }];
+    this.selectedCalendarDate = new Date();
+    this.showDesignerCalendar = true;
+  }
+
+  closeDesignerCalendar(): void {
+    this.showDesignerCalendar = false;
+  }
+
+  onCalendarDesignerSelected(designer: CalendarDesigner): void {
+    this.selectedDesigner = designer;
+    this.closeDesignerCalendar();
+  }
+
+  onCalendarAssignmentRequested(designer: CalendarDesigner): void {
+    this.selectedDesigner = designer;
+    this.closeDesignerCalendar();
+  }
+
+  private mapAssignmentDesignerToCalendar(d: AssignmentDesigner): CalendarDesigner {
+    return {
+      id: d.id,
+      name: d.name,
+      avatar: d.avatar,
+      groupId: d.teamId,
+      groupName: d.teamName,
+      isLeader: !!d.isTeamLeader,
+      status: d.status === 'idle' ? 'available' : 'busy',
+      currentProjects: Math.max(0, d.recentOrders || 0),
+      lastOrderDate: d.lastOrderDate,
+      idleDays: Math.max(0, d.idleDays || 0),
+      completedThisMonth: Math.max(0, d.recentOrders || 0),
+      averageCycle: 15,
+      upcomingEvents: (d.reviewDates || []).map((dateStr, idx) => ({
+        id: `${d.id}-review-${idx}`,
+        date: new Date(dateStr),
+        title: '对图评审',
+        type: 'review',
+        projectId: undefined,
+        duration: 2
+      })),
+      workload: Math.max(0, d.workload || 0),
+      nextAvailableDate: new Date()
+    };
+  }
+
+  // 处理咨询订单表单提交
+  // 存储订单创建时的客户信息和需求信息
+
+  onConsultationOrderSubmit(formData: any): void {
+    console.log('咨询订单表单提交:', formData);
+    
+    // 保存订单创建数据
+    this.orderCreationData = formData;
+    
+    // 更新projectData以便传递给子组件(集成报价与指派信息)
+    this.projectData = {
+      customerInfo: formData.customerInfo,
+      requirementInfo: formData.requirementInfo,
+      preferenceTags: formData.preferenceTags,
+      quotation: this.quotationData ? { ...this.quotationData } : undefined,
+      assignment: this.designerAssignmentData ? { ...this.designerAssignmentData } : undefined,
+      orderAmount: this.quotationData?.totalAmount ?? this.orderAmount ?? 0
+    };
+    
+    // 实时更新左侧客户信息显示
+    this.updateCustomerInfoDisplay(formData);
+    
+    // 根据角色上下文处理数据同步
+    if (formData.roleContext === 'customer-service') {
+      // 客服端创建的订单需要同步到设计师端
+      this.syncOrderToDesignerView(formData);
+    }
+
+    // 根据报价数据更新订单金额
+    this.orderAmount = this.quotationData?.totalAmount ?? this.orderAmount ?? 0;
+    this.updateOrderAmount();
+    
+    // 触发变更检测以更新UI
+    this.cdr.detectChanges();
+  }
+
+  // 新增:更新客户信息显示 - 优化后只更新实际存在的字段
+  private updateCustomerInfoDisplay(formData: any): void {
+    if (formData.customerInfo) {
+      // 更新项目对象中的客户信息 - 只更新实际存在的字段
+      if (this.project) {
+        // 由于已移除客户姓名和手机号字段,只更新微信和客户类型
+        if (formData.customerInfo.wechat) {
+          this.project.customerWechat = formData.customerInfo.wechat;
+        }
+        if (formData.customerInfo.customerType) {
+          this.project.customerType = formData.customerInfo.customerType;
+        }
+        if (formData.customerInfo.source) {
+          this.project.customerSource = formData.customerInfo.source;
+        }
+        if (formData.customerInfo.remark) {
+          this.project.customerRemark = formData.customerInfo.remark;
+        }
+      }
+      
+      // 更新客户标签
+      if (formData.preferenceTags) {
+        this.project = {
+          ...this.project,
+          customerTags: formData.preferenceTags
+        } as any;
+      }
+      
+      // 更新需求信息 - 只更新实际存在的字段
+      if (formData.requirementInfo) {
+        this.project = {
+          ...this.project,
+          // 移除已删除的字段:decorationType, firstDraftDate, style, budget, area, houseType
+          downPayment: formData.requirementInfo.downPayment,
+          smallImageTime: formData.requirementInfo.smallImageTime,
+          spaceRequirements: formData.requirementInfo.spaceRequirements,
+          designAngles: formData.requirementInfo.designAngles,
+          specialAreaHandling: formData.requirementInfo.specialAreaHandling,
+          materialRequirements: formData.requirementInfo.materialRequirements,
+          lightingRequirements: formData.requirementInfo.lightingRequirements
+        } as any;
+      }
+      
+      console.log('客户信息已实时更新:', this.project);
+    }
+  }
+
+  // 新增:同步订单数据到设计师视图
+  private syncOrderToDesignerView(formData: any): void {
+    // 创建项目数据
+    const projectData = {
+      customerId: formData.customerInfo.id || 'customer-' + Date.now(),
+      customerName: formData.customerInfo.name,
+      requirement: formData.requirementInfo,
+      referenceCases: [],
+      tags: {
+        demandType: formData.customerInfo.demandType,
+        preferenceTags: formData.preferenceTags,
+        followUpStatus: formData.customerInfo.followUpStatus
+      },
+      // 新增:报价与指派信息(可选)
+      quotation: this.quotationData ? { ...this.quotationData } : undefined,
+      assignment: this.designerAssignmentData ? { ...this.designerAssignmentData } : undefined,
+      orderAmount: this.quotationData?.totalAmount ?? this.orderAmount ?? 0
+    };
+
+    // 调用项目服务创建项目
+    this.projectService.createProject(projectData).subscribe(
+      result => {
+        if (result.success) {
+          console.log('订单数据已同步到设计师端,项目ID:', result.projectId);
+          // 可以在这里添加成功提示或其他处理逻辑
+        }
+      },
+      error => {
+        console.error('同步订单数据到设计师端失败:', error);
+      }
+    );
+  }
+
+  // 确认团队分配
+  confirmTeamAssignment(designer: any): void {
+    if (designer) {
+      this.selectedDesigner = designer;
+      console.log('团队分配确认:', designer);
+      
+      // 这里可以添加实际的团队分配逻辑
+      // 例如调用服务来分配设计师到项目
+      
+      // 进入下一个阶段:需求沟通
+      this.updateProjectStage('需求沟通');
+      this.expandedStages['需求沟通'] = true;
+      this.expandedStages['订单创建'] = false;
+      
+      // 显示成功消息
+      alert('团队分配成功!已进入需求沟通阶段');
+      
+      console.log('团队分配完成,已跳转到需求沟通阶段');
+    }
+  }
+
+  // 项目创建完成事件处理
+  onProjectCreated(projectData: any): void {
+    console.log('项目创建完成:', projectData);
+    this.projectData = projectData;
+
+    // 团队分配已在子组件中完成并触发该事件:推进到需求沟通阶段
+    this.updateProjectStage('需求沟通');
+    
+    // 更新项目对象的当前阶段,确保四大板块状态正确显示
+    if (this.project) {
+      this.project.currentStage = '需求沟通';
+    }
+    
+    // 展开需求沟通阶段,收起订单创建阶段
+    this.expandedStages['需求沟通'] = true;
+    this.expandedStages['订单创建'] = false;
+    
+    // 自动展开确认需求板块
+    this.expandedSection = 'requirements';
+
+    // 强制触发变更检测,确保UI更新
+    this.cdr.detectChanges();
+
+    // 延迟滚动到需求沟通阶段,确保DOM更新完成
+    setTimeout(() => {
+      this.scrollToStage('需求沟通');
+      // 再次触发变更检测,确保所有状态都已正确更新
+      this.cdr.detectChanges();
+    }, 100);
+
+    console.log('项目创建成功,已推进到需求沟通阶段,四大板块状态已更新');
+  }
+
+  // 新增:处理实时需求数据更新
+  onRequirementDataUpdated(data: any): void {
+    console.log('收到需求数据更新:', data);
+    
+    // 同步关键信息
+    this.syncRequirementKeyInfo(data);
+    
+    // 更新客户信息显示
+    if (data && this.project) {
+      // 更新项目的客户信息
+      if (data.colorIndicators && data.colorIndicators.length > 0) {
+        this.project.customerInfo = {
+          ...this.project.customerInfo,
+          colorPreference: data.colorIndicators.map((indicator: any) => indicator.name).join(', ')
+        };
+      }
+      
+      // 更新空间结构信息
+      if (data.spaceIndicators && data.spaceIndicators.length > 0) {
+        this.project.customerInfo = {
+          ...this.project.customerInfo,
+          spaceRequirements: data.spaceIndicators.map((indicator: any) => `${indicator.name}: ${indicator.value}`).join(', ')
+        };
+      }
+      
+      // 更新材质偏好
+      if (data.materialIndicators && data.materialIndicators.length > 0) {
+        this.project.customerInfo = {
+          ...this.project.customerInfo,
+          materialPreference: data.materialIndicators.map((indicator: any) => `${indicator.name}: ${indicator.value}%`).join(', ')
+        };
+      }
+      
+      // 更新需求项目
+      if (data.requirementItems && data.requirementItems.length > 0) {
+        this.project.requirements = data.requirementItems.map((item: any) => ({
+          id: item.id,
+          description: item.description,
+          status: item.status,
+          priority: item.priority || 'medium'
+        }));
+      }
+      
+      // 接收色彩分析结果并存储用于右侧展示
+      if (data.colorAnalysisResult) {
+        console.log('接收到色彩分析结果:', data.colorAnalysisResult);
+        this.colorAnalysisResult = data.colorAnalysisResult as ColorAnalysisResult;
+        console.log('设置colorAnalysisResult后:', this.colorAnalysisResult);
+        
+        // 计算主色:按占比最高的颜色
+        const colors = this.colorAnalysisResult?.colors || [];
+        if (colors.length > 0) {
+          const dominant = colors.reduce((max, cur) => cur.percentage > max.percentage ? cur : max, colors[0]);
+          this.dominantColorHex = dominant.hex;
+          console.log('计算出的主色:', this.dominantColorHex);
+        } else {
+          this.dominantColorHex = null;
+          console.log('没有颜色数据,主色设为null');
+        }
+      } else {
+        console.log('没有接收到色彩分析结果');
+      }
+
+      // 新增:处理详细的分析数据
+      if (data.detailedAnalysis) {
+        console.log('接收到详细分析数据:', data.detailedAnalysis);
+        
+        // 存储各类分析结果
+        this.enhancedColorAnalysis = data.detailedAnalysis.enhancedColorAnalysis;
+        this.formAnalysis = data.detailedAnalysis.formAnalysis;
+        this.textureAnalysis = data.detailedAnalysis.textureAnalysis;
+        this.patternAnalysis = data.detailedAnalysis.patternAnalysis;
+        this.lightingAnalysis = data.detailedAnalysis.lightingAnalysis;
+        
+        console.log('详细分析数据已存储:', {
+          enhancedColorAnalysis: this.enhancedColorAnalysis,
+          formAnalysis: this.formAnalysis,
+          textureAnalysis: this.textureAnalysis,
+          patternAnalysis: this.patternAnalysis,
+          lightingAnalysis: this.lightingAnalysis
+        });
+      }
+
+      // 新增:处理材料分析数据
+      if (data.materialAnalysisData && data.materialAnalysisData.length > 0) {
+        console.log('接收到材料分析数据:', data.materialAnalysisData);
+        this.materialAnalysisData = data.materialAnalysisData;
+      }
+
+      // 新增:根据上传来源拆分材料文件用于左右区展示
+      const materials = Array.isArray(data?.materials) ? data.materials : [];
+      this.referenceImages = materials.filter((m: any) => m?.type === 'image');
+      this.cadFiles = materials.filter((m: any) => m?.type === 'cad');
+
+      // 触发变更检测以更新UI
+      this.cdr.detectChanges();
+      
+      console.log('客户信息已实时更新,当前colorAnalysisResult状态:', !!this.colorAnalysisResult);
+    }
+  }
+
+  // 预览右侧色彩分析参考图
+  previewColorRefImage(): void {
+    const url = this.colorAnalysisResult?.originalImage;
+    if (url) {
+      window.open(url, '_blank');
+    }
+  }
+
+  // 新增:点击预览参考图片与CAD文件
+  previewImageFile(url?: string): void {
+    if (!url) return;
+    window.open(url, '_blank');
+  }
+
+  previewCadFile(url?: string): void {
+    if (!url) return;
+    window.open(url, '_blank');
+  }
+
+
+  // 切换客户信息卡片展开状态
+  toggleCustomerInfo(): void {
+    this.isCustomerInfoExpanded = !this.isCustomerInfoExpanded;
+  }
+
+  // 新增:重置方案分析状态的方法
+  resetProposalAnalysis(): void {
+    this.proposalAnalysis = null;
+    this.isAnalyzing = false;
+    this.analysisProgress = 0;
+    console.log('方案分析状态已重置');
+  }
+
+  // 新增:模拟素材解析方法
+  startMaterialAnalysis(): void {
+    this.isAnalyzing = true;
+    this.analysisProgress = 0;
+    const progressInterval = setInterval(() => {
+      this.analysisProgress += Math.random() * 15;
+      if (this.analysisProgress >= 100) {
+        this.analysisProgress = 100;
+        clearInterval(progressInterval);
+        this.completeMaterialAnalysis();
+      }
+    }, 500);
+  }
+
+  // 完成素材解析,生成方案数据
+  private completeMaterialAnalysis(): void {
+    this.isAnalyzing = false;
+    
+    // 生成模拟的方案分析数据
+    this.proposalAnalysis = {
+      id: 'proposal-' + Date.now(),
+      name: '现代简约风格方案',
+      version: 'v1.0',
+      createdAt: new Date(),
+      status: 'completed',
+      materials: [
+        {
+          category: '地面材料',
+          specifications: {
+            type: '复合木地板',
+            grade: 'E0级',
+            thickness: '12mm',
+            finish: '哑光面',
+            durability: '家用33级'
+          },
+          usage: {
+            area: '客厅、卧室',
+            percentage: 65,
+            priority: 'primary'
+          },
+          properties: {
+            texture: '木纹理',
+            color: '浅橡木色',
+            maintenance: '日常清洁'
+          }
+        },
+        {
+          category: '墙面材料',
+          specifications: {
+            type: '乳胶漆',
+            grade: '净味抗甲醛',
+            finish: '丝光面',
+            durability: '15年'
+          },
+          usage: {
+            area: '全屋墙面',
+            percentage: 80,
+            priority: 'primary'
+          },
+          properties: {
+            texture: '平滑',
+            color: '暖白色',
+            maintenance: '可擦洗'
+          }
+        },
+        {
+          category: '软装面料',
+          specifications: {
+            type: '亚麻混纺',
+            grade: 'A级',
+            finish: '防污处理',
+            durability: '5-8年'
+          },
+          usage: {
+            area: '沙发、窗帘',
+            percentage: 25,
+            priority: 'secondary'
+          },
+          properties: {
+            texture: '自然纹理',
+            color: '米灰色系',
+            maintenance: '干洗'
+          }
+        }
+      ],
+      designStyle: {
+        primaryStyle: '现代简约',
+        styleElements: [
+          {
+            element: '线条设计',
+            description: '简洁流畅的直线条,避免繁复装饰',
+            influence: 85
+          },
+          {
+            element: '色彩搭配',
+            description: '以中性色为主,局部点缀暖色',
+            influence: 75
+          },
+          {
+            element: '材质选择',
+            description: '天然材质与现代工艺结合',
+            influence: 70
+          }
+        ],
+        characteristics: [
+          {
+            feature: '空间感',
+            value: '开放通透',
+            importance: 'high'
+          },
+          {
+            feature: '功能性',
+            value: '实用至上',
+            importance: 'high'
+          },
+          {
+            feature: '装饰性',
+            value: '简约精致',
+            importance: 'medium'
+          }
+        ],
+        compatibility: {
+          withMaterials: ['木材', '金属', '玻璃', '石材'],
+          withColors: ['白色', '灰色', '米色', '原木色'],
+          score: 92
+        }
+      },
+      colorScheme: {
+        palette: [
+          {
+            color: '暖白色',
+            hex: '#F8F6F0',
+            rgb: '248, 246, 240',
+            percentage: 45,
+            role: 'dominant'
+          },
+          {
+            color: '浅灰色',
+            hex: '#E5E5E5',
+            rgb: '229, 229, 229',
+            percentage: 30,
+            role: 'secondary'
+          },
+          {
+            color: '原木色',
+            hex: '#D4A574',
+            rgb: '212, 165, 116',
+            percentage: 20,
+            role: 'accent'
+          },
+          {
+            color: '深灰色',
+            hex: '#4A4A4A',
+            rgb: '74, 74, 74',
+            percentage: 5,
+            role: 'neutral'
+          }
+        ],
+        harmony: {
+          type: '类似色调和',
+          temperature: 'warm',
+          contrast: 65
+        },
+        psychology: {
+          mood: '宁静舒适',
+          atmosphere: '温馨自然',
+          suitability: ['居住', '办公', '休闲']
+        }
+      },
+      spaceLayout: {
+        dimensions: {
+          length: 12.5,
+          width: 8.2,
+          height: 2.8,
+          area: 102.5,
+          volume: 287
+        },
+        functionalZones: [
+          {
+            zone: '客厅区域',
+            area: 35.2,
+            percentage: 34.3,
+            requirements: ['会客', '娱乐', '休息'],
+            furniture: ['沙发', '茶几', '电视柜', '边几']
+          },
+          {
+            zone: '餐厅区域',
+            area: 18.5,
+            percentage: 18.0,
+            requirements: ['用餐', '储物'],
+            furniture: ['餐桌', '餐椅', '餐边柜']
+          },
+          {
+            zone: '厨房区域',
+            area: 12.8,
+            percentage: 12.5,
+            requirements: ['烹饪', '储存', '清洁'],
+            furniture: ['橱柜', '岛台', '吧台椅']
+          },
+          {
+            zone: '主卧区域',
+            area: 25.6,
+            percentage: 25.0,
+            requirements: ['睡眠', '储衣', '梳妆'],
+            furniture: ['床', '衣柜', '梳妆台', '床头柜']
+          },
+          {
+            zone: '次卧区域',
+            area: 10.4,
+            percentage: 10.2,
+            requirements: ['睡眠', '学习'],
+            furniture: ['床', '书桌', '衣柜']
+          }
+        ],
+        circulation: {
+          mainPaths: ['入户-客厅', '客厅-餐厅', '餐厅-厨房', '客厅-卧室'],
+          pathWidth: 1.2,
+          efficiency: 88
+        },
+        lighting: {
+          natural: {
+            direction: ['南向', '东向'],
+            intensity: '充足',
+            duration: '8-10小时'
+          },
+          artificial: {
+            zones: ['主照明', '局部照明', '装饰照明'],
+            requirements: ['无主灯设计', '分层控制', '调光调色']
+          }
+        }
+      },
+      budget: {
+        total: 285000,
+        breakdown: [
+          {
+            category: '基础装修',
+            amount: 142500,
+            percentage: 50
+          },
+          {
+            category: '主材采购',
+            amount: 85500,
+            percentage: 30
+          },
+          {
+            category: '软装配饰',
+            amount: 42750,
+            percentage: 15
+          },
+          {
+            category: '设计费用',
+            amount: 14250,
+            percentage: 5
+          }
+        ]
+      },
+      timeline: [
+        {
+          phase: '设计深化',
+          duration: 7,
+          dependencies: ['需求确认']
+        },
+        {
+          phase: '材料采购',
+          duration: 5,
+          dependencies: ['设计深化']
+        },
+        {
+          phase: '基础施工',
+          duration: 30,
+          dependencies: ['材料采购']
+        },
+        {
+          phase: '软装进场',
+          duration: 7,
+          dependencies: ['基础施工']
+        },
+        {
+          phase: '验收交付',
+          duration: 3,
+          dependencies: ['软装进场']
+        }
+      ],
+      feasibility: {
+        technical: 95,
+        budget: 88,
+        timeline: 92,
+        overall: 92
+      }
+    };
+  }
+
+  // 获取材质分类统计
+  getMaterialCategories(): { category: string; count: number; percentage: number }[] {
+    if (!this.proposalAnalysis) return [];
+    
+    const categories = this.proposalAnalysis.materials.reduce((acc, material) => {
+      acc[material.category] = (acc[material.category] || 0) + 1;
+      return acc;
+    }, {} as Record<string, number>);
+    
+    const total = Object.values(categories).reduce((sum, count) => sum + count, 0);
+    
+    return Object.entries(categories).map(([category, count]) => ({
+      category,
+      count,
+      percentage: Math.round((count / total) * 100)
+    }));
+  }
+
+  // 获取设计风格特征摘要
+  getStyleSummary(): string {
+    if (!this.proposalAnalysis) return '';
+    
+    const style = this.proposalAnalysis.designStyle;
+    const topElements = style.styleElements
+      .sort((a, b) => b.influence - a.influence)
+      .slice(0, 2)
+      .map(el => el.element)
+      .join('、');
+    
+    return `${style.primaryStyle}风格,主要体现在${topElements}等方面`;
+  }
+
+  // 获取色彩方案摘要
+  getColorSummary(): string {
+    if (!this.proposalAnalysis) return '';
+    
+    const scheme = this.proposalAnalysis.colorScheme;
+    const dominantColor = scheme.palette.find(p => p.role === 'dominant')?.color || '';
+    const accentColor = scheme.palette.find(p => p.role === 'accent')?.color || '';
+    
+    return `以${dominantColor}为主调,${accentColor}作点缀,营造${scheme.psychology.mood}的氛围`;
+  }
+
+  // 获取空间效率评分
+  getSpaceEfficiency(): number {
+    if (!this.proposalAnalysis) return 0;
+    return this.proposalAnalysis.spaceLayout.circulation.efficiency;
+  }
+
+  private handlePaymentProofUpload(file: File): void {
+    // 显示上传进度
+    const uploadingMessage = `正在上传支付凭证:${file.name}...`;
+    console.log(uploadingMessage);
+    
+    // 使用支付凭证识别服务处理上传
+    const settlementId = this.project?.id || 'default_settlement';
+    
+    this.paymentVoucherService.processPaymentVoucherUpload(file, settlementId).subscribe({
+      next: (result) => {
+        if (result.success && result.recognitionResult) {
+          const recognition = result.recognitionResult;
+          
+          // 更新识别计数
+          this.voucherRecognitionCount++;
+          
+          // 显示识别结果
+          const successMessage = `
+            支付凭证识别成功!
+            支付方式:${recognition.paymentMethod}
+            支付金额:¥${recognition.amount}
+            交易号:${recognition.transactionNumber}
+            置信度:${(recognition.confidence * 100).toFixed(1)}%
+          `;
+          
+          alert(successMessage);
+          console.log('支付凭证识别完成', recognition);
+          
+          // 自动标记验证通过并解锁渲染大图
+          this.isPaymentVerified = true;
+          this.renderLargeImages = this.renderLargeImages.map(img => ({ ...img, locked: false }));
+          
+          // 触发自动通知流程
+          this.triggerPaymentCompletedNotification(recognition);
+          
+        } else {
+          const errorMessage = `支付凭证识别失败:${result.error || '未知错误'}`;
+          alert(errorMessage);
+          console.error('支付凭证识别失败', result.error);
+        }
+      },
+      error: (error) => {
+        const errorMessage = `支付凭证处理出错:${error.message || '网络错误'}`;
+        alert(errorMessage);
+        console.error('支付凭证处理出错', error);
+      }
+    });
+  }
+
+  /**
+   * 触发支付完成通知流程
+   */
+  private triggerPaymentCompletedNotification(recognition: any): void {
+    console.log('触发支付完成自动通知流程...');
+    
+    // 模拟调用通知服务发送多渠道通知
+    this.sendMultiChannelNotifications(recognition);
+    
+    // 模拟发送通知
+    setTimeout(() => {
+      const notificationMessage = `
+        🎉 尾款已到账,大图已解锁!
+        
+        支付信息:
+        • 支付方式:${recognition.paymentMethod}
+        • 支付金额:¥${recognition.amount}
+        • 处理时间:${new Date().toLocaleString()}
+        
+        📱 系统已自动发送通知至:
+        • 短信通知:138****8888
+        • 微信通知:已推送至微信
+        • 邮件通知:customer@example.com
+        
+        🖼️ 高清渲染图下载链接已发送
+        您现在可以下载4K高清渲染图了!
+      `;
+      
+      alert(notificationMessage);
+      console.log('自动通知发送完成');
+    }, 1000);
+  }
+
+  /**
+   * 发送多渠道通知
+   */
+  private sendMultiChannelNotifications(recognition: any): void {
+    console.log('开始发送多渠道通知...');
+    
+    // 更新通知发送计数
+    this.notificationsSent++;
+    
+    // 模拟发送短信通知
+    console.log('📱 发送短信通知: 尾款已到账,大图已解锁');
+    
+    // 模拟发送微信通知
+    console.log('💬 发送微信通知: 支付成功,高清图片已准备就绪');
+    
+    // 模拟发送邮件通知
+    console.log('📧 发送邮件通知: 包含下载链接的详细通知');
+    
+    // 模拟发送应用内通知
+    console.log('🔔 发送应用内通知: 实时推送支付状态更新');
+    
+    // 模拟通知发送结果
+    setTimeout(() => {
+      console.log('✅ 所有渠道通知发送完成');
+      console.log(`通知发送统计: 短信✅ 微信✅ 邮件✅ 应用内✅ (总计: ${this.notificationsSent} 次)`);
+    }, 500);
+  }
+
+  // 获取当前设计师名称
+  getCurrentDesignerName(): string {
+    // 这里应该从用户服务或认证信息中获取当前用户名称
+    // 暂时返回一个默认值
+    return '张设计师';
+  }
+
+  // ==================== 售后相关变量 ====================
+
+  // 售后标签页控制
+  activeAftercareTab: string = 'services'; // 当前激活的售后标签页
+  
+  // 售后状态管理
+  afterSalesStage: string = '未开始'; // 售后阶段:未开始、进行中、已完成
+  afterSalesStatus: 'pending' | 'in_progress' | 'completed' | 'cancelled' = 'pending'; // 售后状态
+  afterSalesProgress: number = 0; // 售后进度百分比
+  
+  // 售后服务数据
+  afterSalesServices: Array<{
+    id: string;
+    type: string; // 服务类型:维修、保养、咨询、投诉处理
+    description: string;
+    assignedTo: string; // 指派给的人员
+    scheduledDate?: Date; // 计划服务日期
+    completedDate?: Date; // 完成日期
+    status: 'pending' | 'scheduled' | 'in_progress' | 'completed' | 'cancelled';
+    priority: 'low' | 'medium' | 'high';
+  }> = [];
+
+  // 售后评价和反馈
+  afterSalesRating: number = 0; // 售后评分(0-5)
+  afterSalesFeedback: string = ''; // 售后反馈内容
+  afterSalesFeedbackDate?: Date; // 反馈日期
+  
+  // 售后时间跟踪
+  afterSalesResponseTime?: Date; // 首次响应时间
+  afterSalesStartTime?: Date; // 售后开始时间
+  afterSalesCompletionTime?: Date; // 售后完成时间
+  afterSalesTotalDuration: number = 0; // 总耗时(小时)
+
+  // 售后联系人信息
+  afterSalesContact: {
+    name: string;
+    phone: string;
+    wechat?: string;
+    email?: string;
+  } = {
+    name: '',
+    phone: ''
+  };
+
+  // 售后问题记录
+  afterSalesIssues: Array<{
+    id: string;
+    title: string;
+    description: string;
+    severity: 'low' | 'medium' | 'high' | 'critical';
+    reportedDate: Date;
+    resolvedDate?: Date;
+    status: 'reported' | 'investigating' | 'resolved' | 'closed';
+  }> = [];
+
+  // 售后文件记录
+  afterSalesDocuments: Array<{
+    id: string;
+    name: string;
+    type: string; // 文件类型:合同、报告、照片、视频
+    uploadDate: Date;
+    url: string;
+  }> = [];
+
+  // 售后费用记录
+  afterSalesCosts: Array<{
+    id: string;
+    description: string;
+    amount: number;
+    category: 'material' | 'labor' | 'transportation' | 'other';
+    date: Date;
+    status: 'pending' | 'approved' | 'paid' | 'rejected';
+  }> = [];
+
+  // 售后沟通记录
+  afterSalesCommunications: Array<{
+    id: string;
+    type: 'phone' | 'wechat' | 'email' | 'visit';
+    date: Date;
+    summary: string;
+    participants: string[];
+  }> = [];
+
+  // 新增:项目复盘相关属性
+  projectReview: ProjectReview | null = null;
+  isGeneratingReview: boolean = false;
+  
+  // 项目复盘 Tab 切换
+  activeReviewTab: 'sop' | 'experience' | 'suggestions' = 'sop';
+  
+  // SOP执行数据
+  sopMetrics: any = {
+    communicationCount: 6,
+    avgCommunication: 5,
+    revisionCount: 3,
+    avgRevision: 2,
+    deliveryCycle: 18,
+    standardCycle: 15,
+    customerSatisfaction: 4.5
+  };
+  
+  sopStagesData: any[] = [
+    { name: '订单创建', plannedDuration: 1, actualDuration: 1, score: 95, status: 'completed', statusText: '已完成', isDelayed: false, issues: [] },
+    { name: '需求沟通', plannedDuration: 3, actualDuration: 4, score: 75, status: 'completed', statusText: '已完成', isDelayed: true, issues: ['沟通次数超标'] },
+    { name: '方案确认', plannedDuration: 2, actualDuration: 2, score: 90, status: 'completed', statusText: '已完成', isDelayed: false, issues: [] },
+    { name: '建模', plannedDuration: 4, actualDuration: 5, score: 80, status: 'completed', statusText: '已完成', isDelayed: true, issues: ['细节调整耗时'] },
+    { name: '软装', plannedDuration: 2, actualDuration: 2, score: 92, status: 'completed', statusText: '已完成', isDelayed: false, issues: [] },
+    { name: '渲染', plannedDuration: 3, actualDuration: 4, score: 85, status: 'ongoing', statusText: '进行中', isDelayed: false, issues: [] }
+  ];
+  
+  // 经验复盘数据
+  experienceData: any = {
+    customerNeeds: [
+      { text: '希望客厅采用现代简约风格,注重收纳功能', timestamp: '2024-01-15 10:30', source: '初次沟通' },
+      { text: '卧室需要温馨氛围,色调以暖色为主', timestamp: '2024-01-16 14:20', source: '需求确认' },
+      { text: '厨房要求实用性强,采用白色橱柜', timestamp: '2024-01-17 09:15', source: '细节讨论' }
+    ],
+    customerConcerns: [
+      { text: '担心渲染图与实际效果有差异', timestamp: '2024-01-18 16:40', resolved: true },
+      { text: '预算控制在合理范围内', timestamp: '2024-01-19 11:25', resolved: true }
+    ],
+    complaintPoints: [
+      { text: '首版方案色彩搭配不符合预期', timestamp: '2024-01-20 15:10', severity: 'medium', severityText: '中等', resolution: '已调整为客户偏好的配色方案' }
+    ],
+    projectHighlights: [
+      { text: '设计师对空间的功能分区把握精准', category: '设计亮点', praised: true },
+      { text: '材质选择符合客户品味且性价比高', category: '材质选择', praised: true },
+      { text: '渲染效果图质量优秀,客户非常满意', category: '技术表现', praised: true }
+    ],
+    communications: [
+      { timestamp: '2024-01-15 10:30', participant: '客户', type: 'requirement', typeText: '需求提出', message: '希望整体风格简约现代,注重实用性...', attachments: [] },
+      { timestamp: '2024-01-16 09:00', participant: '设计师', type: 'response', typeText: '设计反馈', message: '根据您的需求,我们建议采用...', attachments: ['方案草图.jpg'] },
+      { timestamp: '2024-01-20 14:30', participant: '客户', type: 'concern', typeText: '疑虑表达', message: '对配色方案有些疑虑...', attachments: [] }
+    ]
+  };
+  
+  // 优化建议数据
+  optimizationSuggestions: any[] = [
+    {
+      priority: 'high',
+      priorityText: '高优先级',
+      category: '需求沟通',
+      expectedImprovement: '减少30%改图次数',
+      problem: '该项目因需求理解不够深入导致改图3次,超出平均水平',
+      dataPoints: [
+        { label: '实际改图次数', value: '3次', isWarning: true },
+        { label: '平均改图次数', value: '2次', isWarning: false },
+        { label: '超出比例', value: '+50%', isWarning: true }
+      ],
+      solution: '建议在需求沟通阶段增加确认环节,通过详细的需求确认清单和参考案例,确保设计师完全理解客户需求',
+      actionPlan: [
+        '制定标准化的需求确认清单,覆盖风格、色彩、材质、功能等核心要素',
+        '在方案设计前与客户进行一次需求复核会议',
+        '准备3-5个同类型案例供客户参考,明确设计方向',
+        '使用可视化工具(如情绪板)帮助客户表达偏好'
+      ],
+      references: ['项目A-123', '项目B-456']
+    },
+    {
+      priority: 'medium',
+      priorityText: '中优先级',
+      category: '时间管理',
+      expectedImprovement: '缩短15%交付周期',
+      problem: '项目交付周期为18天,超出标准周期15天',
+      dataPoints: [
+        { label: '实际周期', value: '18天', isWarning: true },
+        { label: '标准周期', value: '15天', isWarning: false },
+        { label: '延期', value: '+3天', isWarning: true }
+      ],
+      solution: '优化建模和渲染阶段的时间分配,采用并行工作模式提高效率',
+      actionPlan: [
+        '建模和软装选择可以部分并行进行',
+        '提前准备常用材质库,减少临时查找时间',
+        '设置阶段里程碑提醒,避免单一阶段耗时过长'
+      ],
+      references: ['时间优化案例-001']
+    },
+    {
+      priority: 'low',
+      priorityText: '低优先级',
+      category: '客户体验',
+      expectedImprovement: '提升客户满意度至4.8分',
+      problem: '当前客户满意度为4.5分,仍有提升空间',
+      dataPoints: [
+        { label: '当前满意度', value: '4.5/5', isWarning: false },
+        { label: '目标满意度', value: '4.8/5', isWarning: false }
+      ],
+      solution: '增加交付过程中的沟通频率,及时展示阶段性成果',
+      actionPlan: [
+        '每个关键节点完成后主动向客户汇报进度',
+        '提供阶段性预览图,让客户参与到创作过程中',
+        '建立客户反馈快速响应机制'
+      ],
+      references: []
+    }
+  ];
+
+  switchAftercareTab(tab: string): void {
+    this.activeAftercareTab = tab;
+    console.log('切换到售后标签页:', tab);
+  }
+
+  // ==================== 自动结算相关 ====================
+  
+  // 检查项目是否已完成验收
+  isProjectAccepted(): boolean {
+    // 检查所有交付阶段是否完成
+    const deliveryStages: ProjectStage[] = ['建模', '软装', '渲染', '后期'];
+    return deliveryStages.every(stage => this.getStageStatus(stage) === 'completed');
+  }
+  
+  // 启动自动结算(只有技术人员可触发)
+  initiateAutoSettlement(): void {
+    if (this.isAutoSettling) return;
+    
+    // 权限检查
+    if (!this.isTechnicalView()) {
+      alert('⚠️ 仅技术人员可以启动自动化结算流程');
+      return;
+    }
+    
+    // 验收状态检查
+    if (!this.isProjectAccepted()) {
+      const confirmStart = confirm('项目尚未完成全部验收,确定要启动结算流程吗?');
+      if (!confirmStart) return;
+    }
+    
+    this.isAutoSettling = true;
+    console.log('启动自动化结算流程...');
+    
+    // 模拟启动各个自动化功能
+    setTimeout(() => {
+      // 1. 启动小程序支付监听
+      this.miniprogramPaymentStatus = 'active';
+      this.isSettlementInitiated = true;
+      
+      console.log('✅ 自动化结算已启动');
+      console.log('🟢 小程序支付监听已激活');
+      console.log('🔍 支付凭证智能识别已就绪');
+      console.log('📱 自动通知系统已就绪');
+      
+      // 2. 自动生成尾款结算记录
+      this.createFinalPaymentRecord();
+      
+      // 3. 通知客服跟进尾款
+      this.notifyCustomerServiceForFinalPayment();
+      
+      // 4. 设置支付监听和自动化流程
+      this.setupPaymentAutomation();
+      
+      this.isAutoSettling = false;
+      
+      // 显示启动成功消息
+      alert(`🚀 自动化结算已成功启动!
+
+✅ 已启动功能:
+• 小程序支付自动监听
+• 支付凭证智能识别  
+• 多渠道自动通知
+• 大图自动解锁
+• 已通知客服跟进尾款
+
+系统将自动处理后续支付流程。`);
+      
+    }, 2000);
+  }
+
+  // 创建尾款结算记录
+  private createFinalPaymentRecord(): void {
+    const finalPaymentRecord: Settlement = {
+      id: `settlement_${Date.now()}`,
+      projectId: this.projectId,
+      projectName: this.project?.name || '',
+      type: 'final_payment',
+      amount: this.project?.finalPaymentAmount || 0,
+      percentage: 30,
+      status: 'pending',
+      createdAt: new Date(),
+      initiatedBy: 'technical',
+      initiatedAt: new Date(),
+      notifiedCustomerService: false,
+      paymentReceived: false,
+      imagesUnlocked: false
+    };
+    
+    // 添加到结算列表
+    if (!this.settlements.find(s => s.type === 'final_payment')) {
+      this.settlements.unshift(finalPaymentRecord);
+    }
+    
+    console.log('📝 已创建尾款结算记录:', finalPaymentRecord);
+  }
+
+  // 通知客服跟进尾款
+  private notifyCustomerServiceForFinalPayment(): void {
+    const projectInfo = {
+      projectId: this.projectId,
+      projectName: this.project?.name || '未知项目',
+      customerName: this.project?.customerName || '未知客户',
+      customerPhone: this.project?.customerPhone || '',
+      finalPaymentAmount: this.project?.finalPaymentAmount || 0,
+      notificationTime: new Date(),
+      status: 'pending_followup',
+      priority: 'high',
+      autoGenerated: true,
+      message: `项目【${this.project?.name}】已完成技术验收,请及时跟进客户尾款支付。`
+    };
+
+    // 发送通知到客服系统
+    console.log('📢 正在通知客服跟进尾款...', projectInfo);
+    
+    // 模拟API调用到客服通知系统
+    // this.customerServiceAPI.addPendingTask(projectInfo).subscribe(...)
+    
+    setTimeout(() => {
+      console.log('✅ 客服通知已发送成功');
+      
+      // 更新结算记录状态
+      const settlement = this.settlements.find(s => s.type === 'final_payment');
+      if (settlement) {
+        settlement.notifiedCustomerService = true;
+      }
+    }, 500);
+  }
+  
+  // 设置支付自动化流程
+  private setupPaymentAutomation(): void {
+    console.log('⚙️ 设置支付自动化监听...');
+    
+    // 模拟支付监听(实际应使用WebSocket或轮询)
+    // 这里仅作演示
+    this.monitorPaymentStatus();
+  }
+  
+  // 监听支付状态
+  private monitorPaymentStatus(): void {
+    // 实际应该使用WebSocket连接或定时轮询API
+    // 这里仅作演示用setTimeout模拟
+    console.log('👀 开始监听支付状态...');
+    
+    // 当检测到支付完成时,自动触发后续流程
+    // this.onPaymentReceived();
+  }
+  
+  // 支付到账回调
+  onPaymentReceived(paymentInfo?: any): void {
+    console.log('💰 检测到支付到账:', paymentInfo);
+    
+    const settlement = this.settlements.find(s => s.type === 'final_payment');
+    if (!settlement) return;
+    
+    // 更新结算状态
+    settlement.status = '已结算';
+    settlement.settledAt = new Date();
+    settlement.paymentReceived = true;
+    settlement.paymentReceivedAt = new Date();
+    
+    // 自动解锁并发送大图
+    this.autoUnlockAndSendImages();
+    
+    // 通知客户和客服
+    this.sendPaymentConfirmationNotifications();
+  }
+  
+  // 自动解锁并发送大图
+  private autoUnlockAndSendImages(): void {
+    console.log('🔓 自动解锁大图...');
+    
+    // 更新结算记录
+    const settlement = this.settlements.find(s => s.type === 'final_payment');
+    if (settlement) {
+      settlement.imagesUnlocked = true;
+      settlement.imagesUnlockedAt = new Date();
+    }
+    
+    // 自动发送大图给客户(通过客服)
+    this.autoSendImagesToCustomer();
+    
+    console.log('✅ 大图已解锁并准备发送');
+  }
+  
+  // 自动发送图片给客户
+  private autoSendImagesToCustomer(): void {
+    console.log('📤 自动发送大图给客户...');
+    
+    // 收集所有渲染大图
+    const renderProcess = this.deliveryProcesses.find(p => p.id === 'rendering');
+    const images: string[] = [];
+    
+    if (renderProcess) {
+      Object.keys(renderProcess.content).forEach(spaceId => {
+        const content = renderProcess.content[spaceId];
+        if (content.images) {
+          content.images.forEach(img => {
+            images.push(img.url);
+          });
+        }
+      });
+    }
+    
+    const sendInfo = {
+      projectId: this.projectId,
+      customerName: this.project?.customerName || '',
+      images: images,
+      sendMethod: 'wechat',
+      autoGenerated: true
+    };
+    
+    console.log('📨 准备发送的大图信息:', sendInfo);
+    
+    // 调用客服系统API一键发图
+    // this.customerServiceAPI.sendImagesToCustomer(sendInfo).subscribe(...)
+  }
+  
+  // 发送支付确认通知
+  private sendPaymentConfirmationNotifications(): void {
+    console.log('📱 发送支付确认通知...');
+    
+    // 通知客户
+    const customerMessage = `尊敬的${this.project?.customerName || '客户'},您的尾款支付已确认,大图已自动解锁并发送,请查收。感谢您的信任!`;
+    
+    // 通知客服
+    const csMessage = `项目【${this.project?.name}】尾款已到账,大图已自动解锁,请一键发送给客户。`;
+    
+    console.log('📧 客户通知:', customerMessage);
+    console.log('📧 客服通知:', csMessage);
+    
+    // 实际发送通知
+    // this.notificationService.send({ to: 'customer', message: customerMessage });
+    // this.notificationService.send({ to: 'customer_service', message: csMessage });
+  }
+
+  // ==================== 全景图合成相关 ====================
+  
+  // 全景图合成数据
+  panoramicSyntheses: PanoramicSynthesis[] = [];
+  isUploadingPanoramicImages: boolean = false;
+  panoramicUploadProgress: number = 0;
+
+  // 启动全景图合成流程
+  // 上传支付凭证
+  uploadPaymentProof(): void {
+    console.log('📎 打开支付凭证上传...');
+    
+    const fileInput = document.createElement('input');
+    fileInput.type = 'file';
+    fileInput.accept = 'image/*';
+    fileInput.onchange = (event: any) => {
+      const file = (event.target as HTMLInputElement).files?.[0];
+      if (!file) return;
+      
+      console.log('📄 上传的凭证文件:', file.name);
+      alert(`📎 支付凭证已上传:${file.name}\n\n系统将自动识别支付金额和支付方式。`);
+      
+      // 模拟凭证识别和处理
+      setTimeout(() => {
+        const mockPaymentInfo = {
+          amount: this.project?.finalPaymentAmount || 5000,
+          method: '微信支付',
+          imageUrl: URL.createObjectURL(file),
+          uploadTime: new Date()
+        };
+        
+        console.log('✅ 支付凭证识别完成:', mockPaymentInfo);
+        this.onPaymentReceived(mockPaymentInfo);
+      }, 1500);
+    };
+    fileInput.click();
+  }
+  
+  startPanoramicSynthesis(): void {
+    console.log('🎨 启动全景图合成...');
+    
+    // 打开文件选择对话框,支持多文件选择
+    const fileInput = document.createElement('input');
+    fileInput.type = 'file';
+    fileInput.accept = 'image/*';
+    fileInput.multiple = true;
+    fileInput.onchange = (event: any) => {
+      const files = Array.from(event.target.files || []) as File[];
+      if (files.length === 0) return;
+      
+      console.log(`📸 选择了 ${files.length} 张图片进行合成`);
+      this.processPanoramicImages(files);
+    };
+    fileInput.click();
+  }
+  
+  // 处理全景图片上传和合成
+  private processPanoramicImages(files: File[]): void {
+    this.isUploadingPanoramicImages = true;
+    this.panoramicUploadProgress = 0;
+    
+    console.log(`📸 开始处理 ${files.length} 张全景图片...`);
+    
+    // 模拟上传进度
+    const uploadInterval = setInterval(() => {
+      this.panoramicUploadProgress += 10;
+      if (this.panoramicUploadProgress >= 100) {
+        clearInterval(uploadInterval);
+        this.panoramicUploadProgress = 100;
+        
+        // 上传完成后开始合成
+        this.synthesizePanoramicView(files);
+      }
+    }, 300);
+  }
+  
+  // 合成全景漫游
+  private synthesizePanoramicView(files: File[]): void {
+    console.log('🔄 开始合成全景漫游...');
+    
+    // 创建合成记录
+    const synthesis: PanoramicSynthesis = {
+      id: 'panoramic-' + Date.now(),
+      projectId: this.projectId,
+      projectName: this.project?.name || '未知项目',
+      spaces: [],
+      status: 'processing',
+      quality: 'high',
+      createdAt: new Date(),
+      updatedAt: new Date(),
+      progress: 0
+    };
+    
+    // 根据上传的文件创建空间列表
+    files.forEach((file, index) => {
+      // 从文件名提取空间名称(如"客厅-角度1.jpg")
+      const fileName = file.name.replace(/\.(jpg|jpeg|png|gif)$/i, '');
+      const match = fileName.match(/^(.+?)-/);
+      const spaceName = match ? match[1] : `空间${index + 1}`;
+      
+      // 根据空间名称推断类型
+      let spaceType: 'living_room' | 'bedroom' | 'kitchen' | 'bathroom' | 'dining_room' | 'study' | 'balcony' = 'living_room';
+      if (spaceName.includes('客厅')) spaceType = 'living_room';
+      else if (spaceName.includes('卧室')) spaceType = 'bedroom';
+      else if (spaceName.includes('厨房')) spaceType = 'kitchen';
+      else if (spaceName.includes('卫生间') || spaceName.includes('浴室')) spaceType = 'bathroom';
+      else if (spaceName.includes('餐厅')) spaceType = 'dining_room';
+      else if (spaceName.includes('书房')) spaceType = 'study';
+      else if (spaceName.includes('阳台')) spaceType = 'balcony';
+      
+      synthesis.spaces.push({
+        id: `space_${Date.now()}_${index}`,
+        name: spaceName,
+        type: spaceType,
+        imageCount: 1,
+        viewAngle: fileName
+      });
+    });
+    
+    this.panoramicSyntheses.unshift(synthesis);
+    
+    // 模拟KR Panel合成进度
+    let progress = 0;
+    const synthesisInterval = setInterval(() => {
+      progress += 15;
+      synthesis.progress = Math.min(progress, 100);
+      synthesis.updatedAt = new Date();
+      
+      if (progress >= 100) {
+        clearInterval(synthesisInterval);
+        
+        // 合成完成
+        synthesis.status = 'completed';
+        synthesis.completedAt = new Date();
+        synthesis.previewUrl = this.generateMockPanoramicUrl(synthesis.id);
+        synthesis.downloadUrl = this.generateMockDownloadUrl(synthesis.id);
+        synthesis.renderTime = 120 + Math.floor(Math.random() * 60);
+        synthesis.fileSize = files.reduce((sum, f) => sum + f.size, 0);
+        
+        this.isUploadingPanoramicImages = false;
+        
+        console.log('✅ 全景图合成完成:', synthesis);
+        
+        // 自动生成分享链接
+        this.generatePanoramicShareLink(synthesis);
+      }
+    }, 500);
+  }
+  
+  // 生成全景图分享链接
+  private generatePanoramicShareLink(synthesis: PanoramicSynthesis): void {
+    const shareLink = `https://panoramic.example.com/view/${synthesis.id}`;
+    synthesis.shareLink = shareLink;
+    
+    console.log('🔗 全景图分享链接:', shareLink);
+    
+    // 自动通知客服发送给客户
+    this.notifyCustomerServiceForPanoramicLink(synthesis);
+  }
+  
+  // 通知客服发送全景图链接
+  private notifyCustomerServiceForPanoramicLink(synthesis: PanoramicSynthesis): void {
+    const notification = {
+      type: 'panoramic_ready',
+      projectId: this.projectId,
+      projectName: synthesis.projectName,
+      shareLink: synthesis.shareLink,
+      message: `项目【${synthesis.projectName}】的全景漫游已生成完成,请发送给客户查看。`,
+      timestamp: new Date()
+    };
+    
+    console.log('📢 通知客服发送全景图链接:', notification);
+    
+    // 调用客服通知API
+    // this.customerServiceAPI.notifyPanoramicReady(notification).subscribe(...)
+  }
+  
+  // 生成模拟全景图URL
+  private generateMockPanoramicUrl(id: string): string {
+    return `https://panoramic.example.com/preview/${id}`;
+  }
+  
+  // 生成模拟下载URL
+  private generateMockDownloadUrl(id: string): string {
+    return `https://panoramic.example.com/download/${id}`;
+  }
+
+  // 查看全景图画廊
+  viewPanoramicGallery(): void {
+    console.log('打开全景图画廊');
+    
+    if (this.panoramicSyntheses.length === 0) {
+      alert('暂无全景图记录');
+      return;
+    }
+    
+    // 显示全景图列表
+    const galleryInfo = this.panoramicSyntheses.map((s, i) => 
+      `${i + 1}. ${s.projectName} - ${s.status === 'completed' ? '已完成' : '处理中'} (${s.spaces.length}个空间)`
+    ).join('\n');
+    
+    alert(`全景图画廊\n\n${galleryInfo}\n\n点击查看详情功能开发中...`);
+  }
+  
+  // 复制全景图链接
+  copyPanoramicLink(synthesis: PanoramicSynthesis): void {
+    if (!synthesis.shareLink) {
+      alert('全景图链接尚未生成');
+      return;
+    }
+    
+    navigator.clipboard.writeText(synthesis.shareLink).then(() => {
+      alert(`✅ 全景图链接已复制!\n\n${synthesis.shareLink}`);
+    }).catch(() => {
+      alert(`全景图链接:\n${synthesis.shareLink}`);
+    });
+  }
+
+  // ==================== 评价统计相关 ====================
+  
+  // 评价统计数据
+  reviewStats: {
+    overallScore: number;
+    timelinessScore: number;
+    qualityScore: number;
+    communicationScore: number;
+  } = {
+    overallScore: 4.8,
+    timelinessScore: 4.7,
+    qualityScore: 4.9,
+    communicationScore: 4.6
+  };
+
+  // ==================== 客户评价相关 ====================
+  
+  // 生成评价链接
+  generateReviewLink(): void {
+    console.log('📋 生成客户评价链接...');
+    
+    // 生成唯一的评价令牌
+    const reviewToken = this.generateReviewToken();
+    const reviewLink = `https://review.yss.com/project/${this.projectId}?token=${reviewToken}`;
+    
+    // 保存评价链接记录
+    const reviewRecord = {
+      projectId: this.projectId,
+      projectName: this.project?.name || '',
+      customerName: this.project?.customerName || '',
+      reviewLink: reviewLink,
+      token: reviewToken,
+      createdAt: new Date(),
+      expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30天有效期
+      status: 'active',
+      accessed: false
+    };
+    
+    console.log('✅ 评价链接已生成:', reviewRecord);
+    
+    // 复制到剪贴板
+    navigator.clipboard.writeText(reviewLink).then(() => {
+      alert(`✅ 评价链接已复制到剪贴板!\n\n链接:${reviewLink}\n\n有效期:30天\n\n请通过企业微信发送给客户`);
+    }).catch(() => {
+      alert(`评价链接:\n\n${reviewLink}\n\n有效期:30天\n\n请通过企业微信发送给客户`);
+    });
+    
+    // 通知客服发送评价链接
+    this.notifyCustomerServiceForReviewLink(reviewRecord);
+  }
+  
+  // 生成评价令牌
+  private generateReviewToken(): string {
+    return `review_${this.projectId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+  }
+  
+  // 通知客服发送评价链接
+  private notifyCustomerServiceForReviewLink(reviewRecord: any): void {
+    const notification = {
+      type: 'review_link_ready',
+      projectId: this.projectId,
+      projectName: reviewRecord.projectName,
+      customerName: reviewRecord.customerName,
+      reviewLink: reviewRecord.reviewLink,
+      message: `项目【${reviewRecord.projectName}】的客户评价链接已生成,请发送给客户。`,
+      timestamp: new Date()
+    };
+    
+    console.log('📢 通知客服发送评价链接:', notification);
+    
+    // 调用客服通知API
+    // this.customerServiceAPI.notifyReviewLinkReady(notification).subscribe(...)
+  }
+  // 确认客户评价完成
+  confirmCustomerReview(): void {
+    console.log('✅ 确认客户评价完成');
+    
+    // 更新项目状态
+    if (this.project) {
+      this.project.customerReviewCompleted = true;
+      this.project.customerReviewCompletedAt = new Date();
+    }
+    
+    alert('✅ 客户评价已确认完成!');
+    
+    // 可选:自动进入下一阶段
+    // this.advanceToNextStage('客户评价');
+  }
+
+  // ==================== 投诉管理相关 ====================
+  
+  // 关键词监控配置
+  complaintKeywords: string[] = ['不满意', '投诉', '退款', '差评', '质量问题', '延期', '态度差'];
+  isKeywordMonitoringActive: boolean = false;
+  
+  // 手动创建投诉
+  createComplaintManually(): void {
+    console.log('📝 手动创建投诉');
+    
+    // 弹出创建投诉表单
+    const complaintReason = prompt('请输入投诉原因:');
+    if (!complaintReason || complaintReason.trim() === '') return;
+    
+    const complaintStage = prompt('请输入投诉环节(如:需求沟通、建模、渲染等):') || '未指定';
+    
+    // 创建投诉记录
+    const complaint: any = {
+      id: `complaint_${Date.now()}`,
+      projectId: this.projectId,
+      projectName: this.project?.name || '',
+      customerName: this.project?.customerName || '',
+      type: '人工创建',
+      stage: complaintStage,
+      reason: complaintReason,
+      severity: 'medium',
+      status: 'pending',
+      createdBy: 'manual',
+      createdAt: new Date(),
+      handler: '',
+      resolution: '',
+      resolvedAt: null
+    };
+    
+    // 智能标注核心问题
+    complaint.tags = this.analyzeComplaintTags(complaintReason);
+    
+    // 添加到投诉列表
+    this.exceptionHistories.unshift(complaint);
+    
+    console.log('✅ 投诉记录已创建:', complaint);
+    
+    alert(`✅ 投诉记录已创建!\n\n投诉环节:${complaintStage}\n核心问题:${complaint.tags.join('、')}\n\n系统将自动跟踪处理进度。`);
+    
+    // 通知相关人员
+    this.notifyComplaintHandlers(complaint);
+  }
+  
+  // 分析投诉标签
+  private analyzeComplaintTags(reason: string): string[] {
+    const tags: string[] = [];
+    
+    const tagPatterns = {
+      '需求理解': ['需求', '理解', '沟通', '误解'],
+      '设计质量': ['质量', '效果', '不好', '不满意'],
+      '交付延期': ['延期', '超时', '慢', '着急'],
+      '服务态度': ['态度', '不礼貌', '敷衍', '回复慢'],
+      '价格问题': ['价格', '费用', '贵', '退款']
+    };
+    
+    Object.entries(tagPatterns).forEach(([tag, keywords]) => {
+      if (keywords.some(keyword => reason.includes(keyword))) {
+        tags.push(tag);
+      }
+    });
+    
+    return tags.length > 0 ? tags : ['其他'];
+  }
+  
+  // 通知投诉处理人员
+  private notifyComplaintHandlers(complaint: any): void {
+    const notification = {
+      type: 'new_complaint',
+      projectId: this.projectId,
+      complaintId: complaint.id,
+      projectName: complaint.projectName,
+      customerName: complaint.customerName,
+      severity: complaint.severity,
+      tags: complaint.tags,
+      message: `项目【${complaint.projectName}】收到新投诉,请及时处理。`,
+      timestamp: new Date()
+    };
+    
+    console.log('📢 通知投诉处理人员:', notification);
+    
+    // 调用通知API
+    // this.complaintService.notifyHandlers(notification).subscribe(...)
+  }
+
+  // 设置关键词监控
+  setupKeywordMonitoring(): void {
+    console.log('⚙️ 设置关键词监控');
+    
+    if (this.isKeywordMonitoringActive) {
+      const confirmStop = confirm('关键词监控已激活,是否停止监控?');
+      if (confirmStop) {
+        this.isKeywordMonitoringActive = false;
+        alert('✅ 关键词监控已停止');
+      }
+      return;
+    }
+    
+    // 显示当前监控关键词
+    const currentKeywords = this.complaintKeywords.join('、');
+    const newKeywords = prompt(`当前监控关键词:\n\n${currentKeywords}\n\n请输入要添加的关键词(多个关键词用逗号分隔):`);
+    
+    if (newKeywords && newKeywords.trim()) {
+      const keywords = newKeywords.split(/[,,、]/).map(k => k.trim()).filter(k => k);
+      this.complaintKeywords = [...new Set([...this.complaintKeywords, ...keywords])];
+    }
+    
+    // 激活监控
+    this.isKeywordMonitoringActive = true;
+    this.startKeywordMonitoring();
+    
+    alert(`✅ 关键词监控已激活!\n\n监控关键词:${this.complaintKeywords.join('、')}\n\n系统将自动检测企业微信群消息中的关键词并创建投诉记录。`);
+  }
+  
+  // 开始关键词监控
+  private startKeywordMonitoring(): void {
+    console.log('👀 开始关键词监控...');
+    console.log('监控关键词:', this.complaintKeywords);
+    
+    // 模拟监控企业微信消息(实际应使用企业微信API或webhook)
+    // 这里仅作演示
+    
+    // 监控到关键词后自动创建投诉
+    // this.onKeywordDetected(message, keyword);
+  }
+  
+  // 关键词检测回调
+  onKeywordDetected(message: string, keyword: string): void {
+    console.log('🚨 检测到关键词:', keyword);
+    console.log('消息内容:', message);
+    
+    // 自动创建投诉记录
+    const complaint: any = {
+      id: `complaint_auto_${Date.now()}`,
+      projectId: this.projectId,
+      projectName: this.project?.name || '',
+      customerName: this.project?.customerName || '',
+      type: '关键词自动抓取',
+      keyword: keyword,
+      message: message,
+      severity: this.assessComplaintSeverity(message),
+      status: 'pending',
+      createdBy: 'auto',
+      createdAt: new Date(),
+      handler: '',
+      resolution: '',
+      resolvedAt: null
+    };
+    
+    // 智能标注问题环节和核心问题
+    complaint.stage = this.identifyComplaintStage(message);
+    complaint.tags = this.analyzeComplaintTags(message);
+    
+    // 添加到投诉列表
+    this.exceptionHistories.unshift(complaint);
+    
+    console.log('✅ 自动投诉记录已创建:', complaint);
+    
+    // 实时通知相关人员
+    this.notifyComplaintHandlers(complaint);
+  }
+  
+  // 评估投诉严重程度
+  private assessComplaintSeverity(message: string): 'low' | 'medium' | 'high' {
+    const highSeverityKeywords = ['退款', '投诉', '举报', '律师', '曝光'];
+    const mediumSeverityKeywords = ['不满意', '差评', '质量问题'];
+    
+    if (highSeverityKeywords.some(k => message.includes(k))) return 'high';
+    if (mediumSeverityKeywords.some(k => message.includes(k))) return 'medium';
+    return 'low';
+  }
+  
+  // 识别投诉环节
+  private identifyComplaintStage(message: string): string {
+    const stageKeywords = {
+      '需求沟通': ['需求', '沟通', '理解'],
+      '方案确认': ['方案', '设计', '效果'],
+      '建模': ['建模', '模型', '白模'],
+      '软装': ['软装', '家具', '配饰'],
+      '渲染': ['渲染', '出图', '大图'],
+      '交付': ['交付', '发送', '收到']
+    };
+    
+    for (const [stage, keywords] of Object.entries(stageKeywords)) {
+      if (keywords.some(k => message.includes(k))) {
+        return stage;
+      }
+    }
+    
+    return '未指定';
+  }
+  
+  // 确认投诉处理完成
+  confirmComplaint(): void {
+    console.log('✅ 确认投诉处理完成');
+    
+    // 检查是否有未处理的投诉
+    const pendingComplaints = this.exceptionHistories.filter(c => c.status === '待处理');
+    
+    if (pendingComplaints.length > 0) {
+      const confirmAnyway = confirm(`还有 ${pendingComplaints.length} 个投诉未处理,确定要标记为已完成吗?`);
+      if (!confirmAnyway) return;
+    }
+    
+    alert('✅ 所有投诉已确认处理完成!');
+  }
+
+  // 处理评价表单提交
+  onReviewSubmitted(reviewData: any): void {
+    console.log('客户评价已提交:', reviewData);
+    
+    // 这里应该调用API将评价数据保存到服务器
+    // 模拟API调用
+    setTimeout(() => {
+      alert('客户评价提交成功!评价数据已保存。');
+      
+      // 更新本地反馈数据
+      const newFeedback: CustomerFeedback = {
+        id: Date.now().toString(),
+        customerName: '客户', // 应该从项目信息中获取
+        rating: reviewData.overallRating,
+        content: reviewData.improvementSuggestions || '无具体建议',
+        createdAt: new Date(),
+        status: '已解决',
+        isSatisfied: reviewData.overallRating >= 4,
+        projectId: this.projectId
+      };
+      
+      this.feedbacks = [...this.feedbacks, newFeedback];
+      
+      // 自动标记客户评价完成
+      this.confirmCustomerReview();
+    }, 1000);
+  }
+
+  // 处理评价表单保存草稿
+  onReviewSaved(reviewData: any): void {
+    console.log('客户评价草稿已保存:', reviewData);
+    
+    // 这里应该调用API将草稿数据保存到服务器
+    // 模拟API调用
+    setTimeout(() => {
+      alert('评价草稿保存成功!您可以稍后继续完善。');
+    }, 500);
+  }
+
+  // ============ 缺少的方法实现 ============
+  
+  // 初始化售后模块数据
+  private initializeAftercareData(): void {
+    // 初始化一些示例全景图合成记录
+    this.panoramicSyntheses = [
+      {
+        id: 'panoramic_001',
+        projectId: this.projectId,
+        projectName: '示例项目',
+        spaces: [
+          { id: 'space_001', name: '客厅', type: 'living_room' as const, imageCount: 3, viewAngle: '客厅-角度1' },
+          { id: 'space_002', name: '卧室', type: 'bedroom' as const, imageCount: 2, viewAngle: '卧室-角度1' }
+        ],
+        status: 'completed' as const,
+        quality: 'high' as const,
+        createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
+        updatedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
+        completedAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
+        previewUrl: 'https://panoramic.example.com/preview/panoramic_001',
+        downloadUrl: 'https://panoramic.example.com/download/panoramic_001',
+        shareLink: 'https://panoramic.example.com/view/panoramic_001',
+        renderTime: 135,
+        fileSize: 52428800,
+        progress: 100
+      }
+    ];
+    
+    // 初始化一些示例结算记录
+    if (this.settlements.length === 0) {
+      this.settlements = [
+        {
+          id: 'settlement_001',
+          projectId: this.projectId,
+          projectName: '示例项目',
+          type: 'deposit',
+          amount: 5000,
+          percentage: 30,
+          status: '已结算',
+          createdAt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
+          settledAt: new Date(Date.now() - 28 * 24 * 60 * 60 * 1000)
+        },
+        {
+          id: 'settlement_002',
+          projectId: this.projectId,
+          projectName: '示例项目',
+          type: 'progress',
+          amount: 7000,
+          percentage: 40,
+          status: '已结算',
+          createdAt: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000),
+          settledAt: new Date(Date.now() - 13 * 24 * 60 * 60 * 1000)
+        }
+      ];
+    }
+    
+    // 初始化一些示例客户反馈
+    if (this.feedbacks.length === 0) {
+      this.feedbacks = [
+        {
+          id: 'feedback_001',
+          projectId: this.projectId,
+          customerName: '张先生',
+          rating: 5,
+          content: '设计师非常专业,效果图很满意!',
+          isSatisfied: true,
+          status: '已解决',
+          createdAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000)
+        }
+      ];
+    }
+    
+    // 初始化一些示例投诉记录
+    if (this.exceptionHistories.length === 0) {
+      this.exceptionHistories = [];
+    }
+  }
+  
+  // 初始化表单
+  initializeForms(): void {
+    // 初始化订单创建表单(必填项)
+    this.orderCreationForm = this.fb.group({
+      orderAmount: ['', [Validators.required, Validators.min(0)]],
+      smallImageDeliveryTime: ['', Validators.required],
+      decorationType: ['', Validators.required],
+      requirementReason: ['', Validators.required],
+      isMultiDesigner: [false] // 移除requiredTrue验证,改为普通布尔值
+    });
+
+    // 初始化可选信息表单
+    this.optionalForm = this.fb.group({
+      largeImageDeliveryTime: [''],
+      spaceRequirements: [''],
+      designAngles: [''],
+      specialAreaHandling: [''],
+      materialRequirements: [''],
+      lightingRequirements: ['']
+    });
+  }
+
+  // 检查是否可以创建订单
+  canCreateOrder(): boolean {
+    return this.orderCreationForm ? this.orderCreationForm.valid : false;
+  }
+
+  // 创建订单
+  createOrder(): void {
+    if (!this.canCreateOrder()) {
+      // 标记所有字段为已触摸,以显示验证错误
+      this.orderCreationForm.markAllAsTouched();
+      return;
+    }
+
+    const orderData = {
+      ...this.orderCreationForm.value,
+      ...this.optionalForm.value,
+      customerInfo: this.orderCreationData?.customerInfo,
+      quotationData: this.quotationData,
+      designerAssignment: this.designerAssignmentData
+    };
+
+    console.log('创建订单:', orderData);
+    
+    // 这里应该调用API创建订单
+    // 模拟API调用
+    setTimeout(() => {
+      alert('订单创建成功!');
+      // 订单创建成功后自动切换到下一环节
+      this.advanceToNextStage('订单创建');
+    }, 500);
+  }
+  
+  // 处理空间文件选择
+  onSpaceFileSelected(event: Event, processId: string, spaceId: string): void {
+    const input = event.target as HTMLInputElement;
+    if (!input.files || input.files.length === 0) return;
+    
+    const files = Array.from(input.files);
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (!process || !process.content[spaceId]) return;
+    
+    files.forEach(file => {
+      if (/\.(jpg|jpeg|png)$/i.test(file.name)) {
+        const imageItem = this.makeImageItem(file);
+        process.content[spaceId].images.push({
+          id: imageItem.id,
+          name: imageItem.name,
+          url: imageItem.url,
+          size: this.formatFileSize(file.size)
+        });
+      }
+    });
+    
+    // 清空输入
+    input.value = '';
+  }
+
+
+
+  // 更新模型检查项状态
+  updateModelCheckItem(itemId: string, isPassed: boolean): void {
+    const item = this.modelCheckItems.find(i => i.id === itemId);
+    if (item) {
+      item.isPassed = isPassed;
+      console.log(`模型检查项 ${item.name} 状态更新为: ${isPassed ? '已通过' : '待处理'}`);
+    }
+  }
+
+  // 删除空间图片
+  removeSpaceImage(processId: string, spaceId: string, imageId: string): void {
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (process && process.content[spaceId]) {
+      const images = process.content[spaceId].images;
+      const imageIndex = images.findIndex(img => img.id === imageId);
+      if (imageIndex > -1) {
+        // 释放URL资源
+        const image = images[imageIndex];
+        if (image.url && image.url.startsWith('blob:')) {
+          URL.revokeObjectURL(image.url);
+        }
+        // 从数组中移除
+        images.splice(imageIndex, 1);
+        console.log(`已删除空间图片: ${processId}/${spaceId}/${imageId}`);
+      }
+    }
+  }
+
+  // 项目复盘相关方法
+  getReviewStatus(): 'not_started' | 'generating' | 'completed' {
+    if (this.isGeneratingReview) return 'generating';
+    if (this.projectReview) return 'completed';
+    return 'not_started';
+  }
+
+  getReviewStatusText(): string {
+    const status = this.getReviewStatus();
+    switch (status) {
+      case 'not_started': return '未开始';
+      case 'generating': return '生成中';
+      case 'completed': return '已完成';
+      default: return '未知';
+    }
+  }
+
+  getScoreClass(score: number): string {
+    if (score >= 90) return 'excellent';
+    if (score >= 80) return 'good';
+    if (score >= 70) return 'average';
+    return 'poor';
+  }
+
+  getExecutionStatusText(status: 'excellent' | 'good' | 'average' | 'poor'): string {
+    switch (status) {
+      case 'excellent': return '优秀';
+      case 'good': return '良好';
+      case 'average': return '一般';
+      case 'poor': return '较差';
+      default: return '未知';
+    }
+  }
+
+  generateReviewReport(): void {
+    if (this.isGeneratingReview) return;
+    
+    this.isGeneratingReview = true;
+    
+    // 基于真实项目数据生成复盘报告
+    setTimeout(() => {
+      const sopAnalysisData = this.analyzeSopExecution();
+      const experienceInsights = this.generateExperienceInsights();
+      const performanceMetrics = this.calculatePerformanceMetrics();
+      const plannedBudget = this.quotationData.totalAmount || 150000;
+      const actualBudget = this.calculateActualBudget();
+      
+      this.projectReview = {
+        id: 'review_' + Date.now(),
+        projectId: this.projectId,
+        generatedAt: new Date(),
+        overallScore: this.calculateOverallScore(),
+        sopAnalysis: sopAnalysisData,
+        keyHighlights: experienceInsights.keyHighlights,
+        improvementSuggestions: experienceInsights.improvementSuggestions,
+        customerSatisfaction: {
+          overallRating: this.reviewStats.overallScore,
+          feedback: this.detailedReviews.length > 0 ? this.detailedReviews[0].overallFeedback : '客户反馈良好,对项目整体满意',
+          responseTime: this.calculateAverageResponseTime(),
+          completionTime: this.calculateProjectDuration()
+        },
+        teamPerformance: performanceMetrics,
+        budgetAnalysis: {
+          plannedBudget: plannedBudget,
+          actualBudget: actualBudget,
+          variance: this.calculateBudgetVariance(plannedBudget, actualBudget),
+          costBreakdown: [
+            { category: '设计费', planned: plannedBudget * 0.3, actual: actualBudget * 0.3 },
+            { category: '材料费', planned: plannedBudget * 0.6, actual: actualBudget * 0.57 },
+            { category: '人工费', planned: plannedBudget * 0.1, actual: actualBudget * 0.13 }
+          ]
+        },
+        lessonsLearned: experienceInsights.lessonsLearned,
+        recommendations: experienceInsights.recommendations
+      };
+      
+      this.isGeneratingReview = false;
+      alert('复盘报告生成完成!基于真实SOP执行数据和智能分析生成。');
+    }, 3000);
+  }
+
+  regenerateReviewReport(): void {
+    this.projectReview = null;
+    this.generateReviewReport();
+  }
+
+  exportReviewReport(): void {
+    if (!this.projectReview) return;
+    
+    const exportRequest: ReviewReportExportRequest = {
+      projectId: this.projectId,
+      reviewId: this.projectReview.id,
+      format: 'pdf',
+      includeCharts: true,
+      includeDetails: true,
+      language: 'zh-CN'
+    };
+    
+    this.projectReviewService.exportReviewReport(exportRequest).subscribe({
+      next: (response) => {
+        if (response.success && response.downloadUrl) {
+          // 创建下载链接
+          const link = document.createElement('a');
+          link.href = response.downloadUrl;
+          link.download = response.fileName || `复盘报告_${this.project?.name || '项目'}_${new Date().toISOString().split('T')[0]}.pdf`;
+          document.body.appendChild(link);
+          link.click();
+          document.body.removeChild(link);
+          
+          alert('复盘报告导出成功!');
+        } else {
+          alert('导出失败:' + (response.message || '未知错误'));
+        }
+      },
+      error: (error) => {
+        console.error('导出复盘报告失败:', error);
+        alert('导出失败,请稍后重试');
+      }
+    });
+  }
+
+  shareReviewReport(): void {
+    if (!this.projectReview) return;
+    
+    const shareRequest: ReviewReportShareRequest = {
+      projectId: this.projectId,
+      reviewId: this.projectReview.id,
+      shareType: 'link',
+      expirationDays: 30,
+      allowDownload: true,
+      requirePassword: false
+    };
+    
+    this.projectReviewService.shareReviewReport(shareRequest).subscribe({
+      next: (response) => {
+        if (response.success && response.shareUrl) {
+          // 复制到剪贴板
+          if (navigator.clipboard) {
+            navigator.clipboard.writeText(response.shareUrl).then(() => {
+              alert(`复盘报告分享链接已复制到剪贴板!\n链接有效期:${response.expirationDate ? new Date(response.expirationDate).toLocaleDateString() : '30天'}`);
+            }).catch(() => {
+              alert(`复盘报告分享链接:\n${response.shareUrl}\n\n链接有效期:${response.expirationDate ? new Date(response.expirationDate).toLocaleDateString() : '30天'}`);
+            });
+          } else {
+            alert(`复盘报告分享链接:\n${response.shareUrl}\n\n链接有效期:${response.expirationDate ? new Date(response.expirationDate).toLocaleDateString() : '30天'}`);
+          }
+        } else {
+          alert('分享失败:' + (response.message || '未知错误'));
+        }
+      },
+      error: (error) => {
+        console.error('分享复盘报告失败:', error);
+        alert('分享失败,请稍后重试');
+      }
+    });
+  }
+
+  // 项目复盘工具方法
+  getMaxDuration(): number {
+    if (!this.sopStagesData || this.sopStagesData.length === 0) return 1;
+    return Math.max(...this.sopStagesData.map(s => Math.max(s.plannedDuration, s.actualDuration)));
+  }
+  
+  getAverageScore(): number {
+    if (!this.sopStagesData || this.sopStagesData.length === 0) return 0;
+    const sum = this.sopStagesData.reduce((acc, s) => acc + s.score, 0);
+    return Math.round(sum / this.sopStagesData.length);
+  }
+  
+  getSuggestionCountByPriority(priority: string): number {
+    if (!this.optimizationSuggestions) return 0;
+    return this.optimizationSuggestions.filter(s => s.priority === priority).length;
+  }
+  
+  getAverageImprovementPercent(): number {
+    if (!this.optimizationSuggestions || this.optimizationSuggestions.length === 0) return 0;
+    return 25;
+  }
+  
+  acceptSuggestion(suggestion: any): void {
+    console.log('采纳建议:', suggestion);
+  }
+  
+  viewSuggestionDetail(suggestion: any): void {
+    console.log('查看建议详情:', suggestion);
+  }
+
+  // 分析SOP执行情况
+  private analyzeSopExecution(): any[] {
+    const sopStages = [
+      { name: '需求沟通', planned: 3, actual: 2.5 },
+      { name: '方案确认', planned: 5, actual: 4 },
+      { name: '建模', planned: 7, actual: 8 },
+      { name: '软装', planned: 3, actual: 3.5 },
+      { name: '渲染', planned: 5, actual: 4.5 },
+      { name: '后期', planned: 2, actual: 2 }
+    ];
+
+    return sopStages.map(stage => {
+      const variance = ((stage.actual - stage.planned) / stage.planned) * 100;
+      let executionStatus: 'excellent' | 'good' | 'average' | 'poor';
+      let score: number;
+
+      if (variance <= -10) {
+        executionStatus = 'excellent';
+        score = 95;
+      } else if (variance <= 0) {
+        executionStatus = 'good';
+        score = 85;
+      } else if (variance <= 20) {
+        executionStatus = 'average';
+        score = 70;
+      } else {
+        executionStatus = 'poor';
+        score = 50;
+      }
+
+      const issues: string[] = [];
+      if (variance > 20) {
+        issues.push('执行时间超出计划较多');
+      }
+      if (stage.name === '建模' && variance > 0) {
+        issues.push('建模阶段需要优化流程');
+      }
+
+      return {
+        stageName: stage.name,
+        plannedDuration: stage.planned,
+        actualDuration: stage.actual,
+        score,
+        executionStatus,
+        issues: issues.length > 0 ? issues : undefined
+      };
+    });
+  }
+
+  // 生成经验洞察
+  private generateExperienceInsights(): { keyHighlights: string[]; improvementSuggestions: string[]; lessonsLearned: string[]; recommendations: string[] } {
+    return {
+      keyHighlights: [
+        '需求沟通阶段效率显著提升,客户满意度高',
+        '渲染质量获得客户高度认可',
+        '团队协作配合默契,沟通顺畅',
+        '项目交付时间控制良好'
+      ],
+      improvementSuggestions: [
+        '建模阶段可以进一步优化工作流程',
+        '加强前期需求确认的深度和准确性',
+        '建立更完善的质量检查机制',
+        '提升跨部门协作效率'
+      ],
+      lessonsLearned: [
+        '充分的前期沟通能显著减少后期修改',
+        '标准化流程有助于提高执行效率',
+        '及时的客户反馈对项目成功至关重要',
+        '团队技能匹配度直接影响项目质量'
+      ],
+      recommendations: [
+        '建议在类似项目中复用成功的沟通模式',
+        '可以将本项目的渲染标准作为团队参考',
+        '建议建立项目经验知识库',
+        '推荐定期进行团队技能培训'
+      ]
+    };
+  }
+
+  // 计算绩效指标
+  private calculatePerformanceMetrics(): { designerScore: number; communicationScore: number; timelinessScore: number; qualityScore: number } {
+    return {
+      designerScore: 88,
+      communicationScore: 92,
+      timelinessScore: 85,
+      qualityScore: 90
+    };
+  }
+
+  // 计算总体评分
+  private calculateOverallScore(): number {
+    const metrics = this.calculatePerformanceMetrics();
+    return Math.round((metrics.designerScore + metrics.communicationScore + metrics.timelinessScore + metrics.qualityScore) / 4);
+  }
+
+  // 计算平均响应时间
+  private calculateAverageResponseTime(): number {
+    // 模拟计算平均响应时间(小时)
+    return 2.5;
+  }
+
+  // 计算项目持续时间
+  private calculateProjectDuration(): number {
+    // 模拟计算项目持续时间(天)
+    return 28;
+  }
+
+  // 计算实际预算
+  private calculateActualBudget(): number {
+    // 基于订单金额计算实际预算
+    return this.orderAmount || 150000;
+  }
+
+  // 计算预算偏差
+  private calculateBudgetVariance(plannedBudget: number, actualBudget: number): number {
+    return ((actualBudget - plannedBudget) / plannedBudget) * 100;
+  }
+
+  formatDateTime(date: Date): string {
+    return date.toLocaleString('zh-CN', {
+      year: 'numeric',
+      month: '2-digit',
+      day: '2-digit',
+      hour: '2-digit',
+      minute: '2-digit'
+    });
+  }
+
+  // ============ 空间管理相关方法 ============
+  
+  // 添加新空间
+  addSpace(processId: string): void {
+    const spaceName = this.newSpaceName[processId]?.trim();
+    if (!spaceName) return;
+    
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (!process) return;
+    
+    // 生成新的空间ID
+    const spaceId = `space_${Date.now()}`;
+    
+    // 添加到spaces数组
+    const newSpace: DeliverySpace = {
+      id: spaceId,
+      name: spaceName,
+      isExpanded: false,
+      order: process.spaces.length + 1
+    };
+    
+    process.spaces.push(newSpace);
+    
+    // 初始化content数据
+    process.content[spaceId] = {
+      images: [],
+      progress: 0,
+      status: 'pending',
+      notes: '',
+      lastUpdated: new Date()
+    };
+    
+    // 清空输入框并隐藏
+    this.newSpaceName[processId] = '';
+    this.showAddSpaceInput[processId] = false;
+    
+    console.log(`已添加空间: ${spaceName} 到流程 ${process.name}`);
+  }
+  
+  // 取消添加空间
+  cancelAddSpace(processId: string): void {
+    this.newSpaceName[processId] = '';
+    this.showAddSpaceInput[processId] = false;
+  }
+  
+  // 获取指定流程的活跃空间列表
+  getActiveProcessSpaces(processId: string): DeliverySpace[] {
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (!process) return [];
+    return process.spaces.sort((a, b) => a.order - b.order);
+  }
+  
+  // 切换空间展开状态
+  toggleSpace(processId: string, spaceId: string): void {
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (!process) return;
+    
+    const space = process.spaces.find(s => s.id === spaceId);
+    if (space) {
+      space.isExpanded = !space.isExpanded;
+    }
+  }
+  
+  // 获取空间进度
+  getSpaceProgress(processId: string, spaceId: string): number {
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (!process || !process.content[spaceId]) return 0;
+    
+    return process.content[spaceId].progress || 0;
+  }
+  
+  // 删除空间
+  removeSpace(processId: string, spaceId: string): void {
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (!process) return;
+    
+    // 从spaces数组中移除
+    const spaceIndex = process.spaces.findIndex(s => s.id === spaceId);
+    if (spaceIndex > -1) {
+      const spaceName = process.spaces[spaceIndex].name;
+      process.spaces.splice(spaceIndex, 1);
+      
+      // 清理content数据
+      if (process.content[spaceId]) {
+        // 释放图片URL资源
+        process.content[spaceId].images.forEach(img => {
+          if (img.url && img.url.startsWith('blob:')) {
+            URL.revokeObjectURL(img.url);
+          }
+        });
+        delete process.content[spaceId];
+      }
+      
+      console.log(`已删除空间: ${spaceName} 从流程 ${process.name}`);
+    }
+  }
+  
+  // 触发空间文件输入
+  triggerSpaceFileInput(processId: string, spaceId: string): void {
+    const inputId = `space-file-input-${processId}-${spaceId}`;
+    const input = document.getElementById(inputId) as HTMLInputElement;
+    if (input) {
+      input.click();
+    }
+  }
+  
+  // 处理空间文件拖拽
+  onSpaceFileDrop(event: DragEvent, processId: string, spaceId: string): void {
+    event.preventDefault();
+    event.stopPropagation();
+    
+    const files = event.dataTransfer?.files;
+    if (!files || files.length === 0) return;
+    
+    this.handleSpaceFiles(Array.from(files), processId, spaceId);
+  }
+  
+  // 处理空间文件
+  private handleSpaceFiles(files: File[], processId: string, spaceId: string): void {
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (!process || !process.content[spaceId]) return;
+    
+    files.forEach(file => {
+      if (/\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name)) {
+        const imageItem = this.makeImageItem(file);
+        process.content[spaceId].images.push({
+          id: imageItem.id,
+          name: imageItem.name,
+          url: imageItem.url,
+          size: this.formatFileSize(file.size),
+          reviewStatus: 'pending'
+        });
+        
+        // 更新进度
+        this.updateSpaceProgress(processId, spaceId);
+      }
+    });
+  }
+  
+  // 获取空间图片列表
+  getSpaceImages(processId: string, spaceId: string): Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected' }> {
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (!process || !process.content[spaceId]) return [];
+    
+    return process.content[spaceId].images || [];
+  }
+  
+  // 确认阶段上传并推进工作流
+  confirmStageUpload(stageId: string): void {
+    const stageMap: { [key: string]: ProjectStage } = {
+      'modeling': '建模',
+      'softDecor': '软装', 
+      'rendering': '渲染',
+      'postProduction': '后期'
+    };
+    
+    const currentStage = stageMap[stageId];
+    if (!currentStage) return;
+    
+    // 检查当前阶段是否有上传的图片
+    const process = this.deliveryProcesses.find(p => p.id === stageId);
+    if (!process) return;
+    
+    const hasImages = Object.values(process.content).some(space => 
+      space.images && space.images.length > 0
+    );
+    
+    if (!hasImages) {
+      alert('请先上传图片再确认');
+      return;
+    }
+    
+    // 推进到下一阶段
+    const currentIndex = this.stageOrder.indexOf(currentStage);
+    if (currentIndex < this.stageOrder.length - 1) {
+      const nextStage = this.stageOrder[currentIndex + 1];
+      
+      // 更新当前阶段
+      this.currentStage = nextStage;
+      if (this.project) {
+        this.project.currentStage = nextStage;
+      }
+      
+      // 展开下一阶段
+      this.expandedStages[nextStage] = true;
+      
+      // 滚动到下一阶段
+      setTimeout(() => {
+        this.scrollToStage(nextStage);
+      }, 100);
+      
+      console.log(`阶段推进: ${currentStage} -> ${nextStage}`);
+    } else {
+      // 如果是最后一个阶段,标记为完成
+      console.log(`交付执行阶段完成: ${currentStage}`);
+      alert('交付执行阶段已完成!');
+    }
+  }
+  
+  // 检查是否可以确认阶段上传
+  canConfirmStageUpload(stageId: string): boolean {
+    // 检查是否有上传的图片
+    const process = this.deliveryProcesses.find(p => p.id === stageId);
+    if (!process) return false;
+    
+    return Object.values(process.content).some(space => 
+      space.images && space.images.length > 0
+    );
+  }
+  
+  // 获取空间备注
+  getSpaceNotes(processId: string, spaceId: string): string {
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (!process || !process.content[spaceId]) return '';
+    
+    return process.content[spaceId].notes || '';
+  }
+  
+  // 更新空间备注
+  updateSpaceNotes(processId: string, spaceId: string, notes: string): void {
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (!process || !process.content[spaceId]) return;
+    
+    process.content[spaceId].notes = notes;
+    process.content[spaceId].lastUpdated = new Date();
+    
+    console.log(`已更新空间备注: ${processId}/${spaceId}`);
+  }
+  
+  // 更新空间进度
+  private updateSpaceProgress(processId: string, spaceId: string): void {
+    const process = this.deliveryProcesses.find(p => p.id === processId);
+    if (!process || !process.content[spaceId]) return;
+    
+    const content = process.content[spaceId];
+    const imageCount = content.images.length;
+    
+    // 根据图片数量和状态计算进度
+    if (imageCount === 0) {
+      content.progress = 0;
+      content.status = 'pending';
+    } else if (imageCount < 3) {
+      content.progress = Math.min(imageCount * 30, 90);
+      content.status = 'in_progress';
+    } else {
+      content.progress = 100;
+      content.status = 'completed';
+    }
+    
+    content.lastUpdated = new Date();
+  }
+
+  // ==================== 功能卡片点击事件 ====================
+  
+  /**
+   * 显示功能详情
+   * @param title 功能标题
+   * @param description 功能详细描述
+   */
+  showFeatureDetail(title: string, description: string): void {
+    console.log(`📋 功能详情: ${title}`);
+    console.log(`📝 ${description}`);
+    
+    alert(`✨ ${title}\n\n${description}\n\n点击确定关闭`);
+  }
+}

+ 2425 - 0
copy/requirements-confirm-card.scss

@@ -0,0 +1,2425 @@
+@use '../../styles/_ios-theme.scss' as *;
+@use 'sass:color';
+@use './requirements-confirm-card-alternative.scss' as *;
+
+:host { 
+  display: block; 
+  height: 100%; 
+  position: relative; // 确保子元素定位基准
+}
+
+// 新增:文本输入区域独立样式
+.text-upload-section {
+  margin-bottom: 1.5rem;
+  
+  .upload-item {
+    background: white;
+    border: 1px solid #e0e0e0;
+    border-radius: 8px;
+    padding: 1rem;
+    
+    h5 {
+      margin: 0 0 0.75rem 0;
+      font-size: 0.9rem;
+      color: #333;
+      font-weight: 600;
+    }
+    
+    textarea {
+      width: 100%;
+      padding: 0.75rem;
+      border: 1px solid #ddd;
+      border-radius: 4px;
+      font-size: 0.85rem;
+      resize: vertical;
+      margin-bottom: 0.75rem;
+      
+      &:focus {
+        outline: none;
+        border-color: #007bff;
+      }
+    }
+    
+    .btn-primary {
+      background: #007bff;
+      border: none;
+      border-radius: 4px;
+      padding: 0.5rem 1rem;
+      color: white;
+      font-size: 0.85rem;
+      cursor: pointer;
+      
+      &:disabled {
+        opacity: 0.6;
+        cursor: not-allowed;
+      }
+    }
+  }
+}
+
+// 新增:参考图片和CAD图纸并排布局 - 优化弹窗出现时的布局稳定性
+.file-upload-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 1rem;
+  position: relative;
+  z-index: 1; // 确保在正常文档流中
+  margin-bottom: $ios-spacing-lg;
+  
+  // 移动端响应式
+  @media (max-width: 768px) {
+    grid-template-columns: 1fr;
+    gap: $ios-spacing-sm;
+  }
+  
+  // 平板端响应式
+  @media (min-width: 769px) and (max-width: 1024px) {
+    gap: $ios-spacing-md;
+  }
+  
+  // 当弹窗出现时保持布局稳定
+  .upload-item {
+    position: relative;
+    background: white;
+    border: 1px solid #e0e0e0;
+    border-radius: 8px;
+    padding: 1rem;
+    transition: transform 0.2s ease, box-shadow 0.2s ease;
+    
+    &:hover {
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+    }
+    
+    // 确保在弹窗出现时不会被遮挡或错位,优化层级管理
+    &.image-item, &.cad-item {
+      z-index: 2;
+      
+      // 当有弹窗显示时,降低交互优先级
+      &.modal-active {
+        pointer-events: none;
+        opacity: 0.8;
+      }
+    }
+    
+    h5 {
+      margin: 0 0 $ios-spacing-sm 0;
+      font-size: $ios-font-size-xs;
+      font-weight: $ios-font-weight-semibold;
+      color: $ios-text-primary;
+    }
+    
+    .file-upload-zone {
+      border: 2px dashed $ios-border;
+      border-radius: 8px;
+      padding: $ios-spacing-lg;
+      text-align: center;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      
+      &:hover {
+        border-color: $ios-primary;
+        background: rgba(0, 122, 255, 0.02);
+      }
+      
+      svg {
+        color: $ios-text-secondary;
+        margin-bottom: $ios-spacing-sm;
+      }
+      
+      p {
+        margin: 0 0 $ios-spacing-xs 0;
+        font-size: $ios-font-size-xs;
+        color: $ios-text-primary;
+        font-weight: $ios-font-weight-medium;
+      }
+      
+      .hint {
+        font-size: $ios-font-size-xs;
+        color: $ios-text-secondary;
+      }
+    }
+  }
+}
+
+.requirements-confirm-card {
+  .card-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: $ios-spacing-md;
+    
+    h4 {
+      margin: 0;
+      font-size: $ios-font-size-sm;
+      font-weight: $ios-font-weight-semibold;
+      color: $ios-text-primary;
+    }
+    
+    .header-actions {
+      display: flex;
+      align-items: center;
+      gap: $ios-spacing-md;
+      
+      .btn-ghost {
+        white-space: nowrap;
+      }
+    }
+    
+    .progress-indicator {
+      display: flex;
+      align-items: center;
+      gap: $ios-spacing-xs;
+      
+      .progress-bar {
+        width: 80px;
+        height: 4px;
+        background: $ios-border;
+        border-radius: 2px;
+        overflow: hidden;
+        
+        .progress-fill {
+          height: 100%;
+          background: linear-gradient(90deg, #007AFF 0%, #34C759 100%);
+          transition: width 0.3s ease;
+        }
+      }
+      
+      .progress-text {
+        font-size: $ios-font-size-xs;
+        color: $ios-text-secondary;
+        font-weight: $ios-font-weight-medium;
+      }
+    }
+  }
+
+  // 滑动条输入框样式
+  .slider-container {
+    display: flex;
+    align-items: center;
+    gap: $ios-spacing-xs;
+    
+    input[type="range"] {
+      flex: 1;
+    }
+    
+    .slider-input {
+      width: 60px;
+      padding: 4px 8px;
+      border: 1px solid $ios-border;
+      border-radius: 4px;
+      font-size: $ios-font-size-xs;
+      text-align: center;
+      background: white;
+      
+      &:focus {
+        outline: none;
+        border-color: $ios-primary;
+        box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2);
+      }
+    }
+    
+    .unit-label {
+      font-size: $ios-font-size-xs;
+      color: $ios-text-secondary;
+      min-width: 20px;
+    }
+  }
+
+  // 进度条样式
+  .progress-container {
+    margin-bottom: $ios-spacing-md;
+    
+    &.progress-updated {
+      .progress-bar-fill {
+        animation: progressPulse 0.5s ease;
+      }
+    }
+    
+    .progress-visual {
+      display: flex;
+      align-items: center;
+      gap: $ios-spacing-md;
+      
+      .linear-progress {
+        flex: 1;
+        display: flex;
+        align-items: center;
+        gap: $ios-spacing-sm;
+        
+        .progress-track {
+          flex: 1;
+          height: 8px;
+          background: $ios-background-secondary;
+          border-radius: 4px;
+          overflow: hidden;
+          
+          .progress-bar-fill {
+            height: 100%;
+            background: linear-gradient(90deg, #007AFF 0%, #34C759 100%);
+            border-radius: 4px;
+            transition: width 0.3s ease;
+          }
+        }
+        
+        .progress-text {
+          font-size: $ios-font-size-sm;
+          font-weight: $ios-font-weight-semibold;
+          color: $ios-text-primary;
+          min-width: 40px;
+        }
+      }
+      
+      .circular-progress {
+        position: relative;
+        width: 120px;
+        height: 120px;
+        
+        .progress-circle {
+          width: 100%;
+          height: 100%;
+          transform: rotate(-90deg);
+          
+          circle {
+            transition: stroke-dashoffset 0.3s ease;
+          }
+        }
+        
+        .progress-text {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
+          text-align: center;
+          
+          .progress-percentage {
+            font-size: $ios-font-size-lg;
+            font-weight: $ios-font-weight-bold;
+            color: $ios-text-primary;
+          }
+          
+          .progress-label {
+            font-size: $ios-font-size-xs;
+            color: $ios-text-secondary;
+          }
+        }
+      }
+    }
+  }
+
+  // 进度统计样式
+  .progress-stats {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
+    gap: $ios-spacing-sm;
+    margin-bottom: $ios-spacing-md;
+    
+    .stat-item {
+      text-align: center;
+      padding: $ios-spacing-sm;
+      background: $ios-background-secondary;
+      border-radius: 8px;
+      
+      .stat-number {
+        font-size: $ios-font-size-lg;
+        font-weight: $ios-font-weight-bold;
+        color: $ios-text-primary;
+      }
+      
+      .stat-label {
+        font-size: $ios-font-size-xs;
+        color: $ios-text-secondary;
+        margin-top: 2px;
+      }
+    }
+  }
+
+  // 标签页导航
+  .tab-navigation {
+    display: flex;
+    background: $ios-background-secondary;
+    border-radius: 8px;
+    padding: 2px;
+    margin-bottom: $ios-spacing-md;
+    
+    .tab-button {
+      flex: 1;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: $ios-spacing-xs;
+      padding: $ios-spacing-sm $ios-spacing-xs;
+      border: none;
+      background: transparent;
+      color: $ios-text-secondary;
+      font-size: $ios-font-size-xs;
+      font-weight: $ios-font-weight-medium;
+      border-radius: 6px;
+      transition: all 0.2s ease;
+      cursor: pointer;
+      
+      svg {
+        width: 14px;
+        height: 14px;
+      }
+      
+      &:hover {
+        color: $ios-text-primary;
+        background: rgba(0, 122, 255, 0.1);
+      }
+      
+      &.active {
+        background: white;
+        color: $ios-primary;
+        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+      }
+    }
+  }
+
+  // 标签页内容
+  .tab-content {
+    min-height: 300px;
+  }
+
+  // 素材解析部分
+  .materials-section {
+    // 文本输入区域 - 独立一行
+    .text-upload-section {
+      margin-bottom: $ios-spacing-lg;
+      
+      .upload-item.text-item {
+        h5 {
+          margin: 0 0 $ios-spacing-sm 0;
+          font-size: $ios-font-size-xs;
+          font-weight: $ios-font-weight-semibold;
+          color: $ios-text-primary;
+        }
+        
+        textarea {
+          width: 100%;
+          padding: $ios-spacing-sm;
+          border: 1px solid $ios-border;
+          border-radius: 6px;
+          font-size: $ios-font-size-xs;
+          resize: vertical;
+          margin-bottom: $ios-spacing-sm;
+          
+          &:focus {
+            outline: none;
+            border-color: $ios-primary;
+            box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
+          }
+        }
+      }
+    }
+
+    // 参考图片和CAD图纸并排布局
+    .file-upload-grid {
+      display: grid;
+      grid-template-columns: 1fr 1fr;
+      gap: 2%;
+      margin-bottom: $ios-spacing-lg;
+      
+      // 移动端响应式 - 改为垂直布局
+      @media (max-width: 768px) {
+        grid-template-columns: 1fr;
+        gap: $ios-spacing-md;
+      }
+      
+      .upload-item {
+        &.image-item, &.cad-item {
+          h5 {
+            margin: 0 0 $ios-spacing-sm 0;
+            font-size: $ios-font-size-xs;
+            font-weight: $ios-font-weight-semibold;
+            color: $ios-text-primary;
+          }
+          
+          .file-upload-zone {
+            border: 2px dashed $ios-border;
+            border-radius: 8px;
+            padding: $ios-spacing-lg;
+            text-align: center;
+            cursor: pointer;
+            transition: all 0.2s ease;
+            min-height: 120px;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            
+            &:hover {
+              border-color: $ios-primary;
+              background: rgba(0, 122, 255, 0.02);
+            }
+            
+            svg {
+              color: $ios-text-secondary;
+              margin-bottom: $ios-spacing-sm;
+            }
+            
+            p {
+              margin: 0 0 $ios-spacing-xs 0;
+              font-size: $ios-font-size-xs;
+              color: $ios-text-primary;
+              font-weight: $ios-font-weight-medium;
+            }
+            
+            .hint {
+              font-size: $ios-font-size-xs;
+              color: $ios-text-secondary;
+            }
+          }
+        }
+      }
+    }
+
+    // 保留原有的upload-grid样式作为备选方案
+    .upload-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+      gap: $ios-spacing-md;
+      margin-bottom: $ios-spacing-lg;
+      
+      .upload-item {
+        h5 {
+          margin: 0 0 $ios-spacing-sm 0;
+          font-size: $ios-font-size-xs;
+          font-weight: $ios-font-weight-semibold;
+          color: $ios-text-primary;
+        }
+        
+        textarea {
+          width: 100%;
+          padding: $ios-spacing-sm;
+          border: 1px solid $ios-border;
+          border-radius: 6px;
+          font-size: $ios-font-size-xs;
+          resize: vertical;
+          margin-bottom: $ios-spacing-sm;
+          
+          &:focus {
+            outline: none;
+            border-color: $ios-primary;
+            box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
+          }
+        }
+        
+        .file-upload-zone {
+          border: 2px dashed $ios-border;
+          border-radius: 8px;
+          padding: $ios-spacing-lg;
+          text-align: center;
+          cursor: pointer;
+          transition: all 0.2s ease;
+          
+          &:hover {
+            border-color: $ios-primary;
+            background: rgba(0, 122, 255, 0.02);
+          }
+          
+          svg {
+            color: $ios-text-secondary;
+            margin-bottom: $ios-spacing-sm;
+          }
+          
+          p {
+            margin: 0 0 $ios-spacing-xs 0;
+            font-size: $ios-font-size-xs;
+            color: $ios-text-primary;
+            font-weight: $ios-font-weight-medium;
+          }
+          
+          .hint {
+            font-size: $ios-font-size-xs;
+            color: $ios-text-secondary;
+          }
+        }
+      }
+    }
+    
+    .materials-list {
+      position: relative;
+      z-index: 3; // 确保素材列表在弹窗层级之下但高于其他内容
+      
+      h5 {
+        margin: 0 0 $ios-spacing-sm 0;
+        font-size: $ios-font-size-xs;
+        font-weight: $ios-font-weight-semibold;
+        color: $ios-text-primary;
+      }
+      
+      .material-cards {
+        display: grid;
+        grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+        gap: $ios-spacing-sm;
+        
+        // 响应式布局优化
+        @media (max-width: 768px) {
+          grid-template-columns: 1fr;
+          gap: $ios-spacing-xs;
+        }
+        
+        @media (min-width: 769px) and (max-width: 1024px) {
+          grid-template-columns: repeat(2, 1fr);
+        }
+        
+        .material-card {
+          border: 1px solid $ios-border;
+          border-radius: 6px;
+          padding: $ios-spacing-sm;
+          background: white;
+          position: relative;
+          transition: transform 0.2s ease, box-shadow 0.2s ease;
+          
+          // 防止弹窗出现时布局错乱,优化对齐
+          &:hover {
+            transform: translateY(-1px);
+            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+            z-index: 4; // 悬停时提升层级
+          }
+          
+          &.material-text {
+            border-left: 3px solid #34C759;
+          }
+          
+          &.material-image {
+            border-left: 3px solid #FF9500;
+          }
+          
+          &.material-cad {
+            border-left: 3px solid #007AFF;
+          }
+          
+          .material-header {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: $ios-spacing-xs;
+            
+            .material-type {
+              font-size: $ios-font-size-xs;
+              font-weight: $ios-font-weight-medium;
+              color: $ios-text-secondary;
+              text-transform: uppercase;
+            }
+          }
+          
+          .material-name {
+            font-size: $ios-font-size-xs;
+            color: $ios-text-primary;
+            margin-bottom: $ios-spacing-xs;
+            font-weight: $ios-font-weight-medium;
+          }
+          
+          .parsed-info {
+            .parsed-tags {
+              .tag-group {
+                margin-bottom: $ios-spacing-xs;
+                
+                .tag-label {
+                  font-size: $ios-font-size-xs;
+                  color: $ios-text-secondary;
+                  margin-right: $ios-spacing-xs;
+                }
+                
+                .tag {
+                  display: inline-block;
+                  background: $ios-background-secondary;
+                  color: $ios-text-primary;
+                  padding: 2px 6px;
+                  border-radius: 4px;
+                  font-size: $ios-font-size-xs;
+                  margin-right: 4px;
+                }
+              }
+            }
+            
+            .analysis-results {
+              .color-info {
+                margin-bottom: $ios-spacing-sm;
+                
+                .color-temp {
+                  font-size: $ios-font-size-xs;
+                  color: $ios-text-secondary;
+                }
+              }
+              
+              .enhanced-analysis {
+                .analysis-section {
+                  margin-bottom: $ios-spacing-md;
+                  padding: $ios-spacing-sm;
+                  background: rgba(0, 122, 255, 0.02);
+                  border-radius: 6px;
+                  border: 1px solid rgba(0, 122, 255, 0.1);
+                  
+                  h6 {
+                    margin: 0 0 $ios-spacing-xs 0;
+                    font-size: $ios-font-size-xs;
+                    font-weight: $ios-font-weight-semibold;
+                    color: $ios-primary;
+                  }
+                  
+                  .color-wheel-info {
+                    display: flex;
+                    align-items: center;
+                    gap: $ios-spacing-xs;
+                    margin-bottom: $ios-spacing-xs;
+                    
+                    .color-wheel-icon {
+                      display: flex;
+                      align-items: center;
+                      justify-content: center;
+                      width: 24px;
+                      height: 24px;
+                      
+                      svg {
+                        color: $ios-primary;
+                      }
+                    }
+                    
+                    span {
+                      font-size: $ios-font-size-xs;
+                      color: $ios-text-secondary;
+                      margin-right: $ios-spacing-sm;
+                    }
+                  }
+                  
+                  .psychology-info {
+                    display: flex;
+                    gap: $ios-spacing-xs;
+                    flex-wrap: wrap;
+                    
+                    .mood-tag, .atmosphere-tag {
+                      display: inline-block;
+                      background: rgba(52, 199, 89, 0.1);
+                      color: #34C759;
+                      padding: 2px 6px;
+                      border-radius: 4px;
+                      font-size: $ios-font-size-xs;
+                      font-weight: $ios-font-weight-medium;
+                    }
+                    
+                    .atmosphere-tag {
+                      background: rgba(255, 149, 0, 0.1);
+                      color: #FF9500;
+                    }
+                  }
+                  
+                  .form-metrics {
+                    .metric-item {
+                      display: flex;
+                      align-items: center;
+                      gap: $ios-spacing-sm;
+                      margin-bottom: $ios-spacing-xs;
+                      
+                      .metric-label {
+                        font-size: $ios-font-size-xs;
+                        color: $ios-text-secondary;
+                        min-width: 60px;
+                      }
+                      
+                      .metric-bar {
+                        flex: 1;
+                        height: 4px;
+                        background: $ios-border;
+                        border-radius: 2px;
+                        overflow: hidden;
+                        
+                        .metric-fill {
+                          height: 100%;
+                          background: linear-gradient(90deg, #34C759, #007AFF);
+                          transition: width 0.3s ease;
+                        }
+                      }
+                    }
+                  }
+                  
+                  .material-info {
+                    display: flex;
+                    gap: $ios-spacing-xs;
+                    flex-wrap: wrap;
+                    
+                    .material-tag, .surface-tag {
+                      display: inline-block;
+                      background: rgba(175, 82, 222, 0.1);
+                      color: #AF52DE;
+                      padding: 2px 6px;
+                      border-radius: 4px;
+                      font-size: $ios-font-size-xs;
+                      font-weight: $ios-font-weight-medium;
+                    }
+                    
+                    .surface-tag {
+                      background: rgba(255, 45, 85, 0.1);
+                      color: #FF2D55;
+                    }
+                  }
+                  
+                  .pattern-info {
+                    display: flex;
+                    gap: $ios-spacing-xs;
+                    flex-wrap: wrap;
+                    
+                    .pattern-tag {
+                      display: inline-block;
+                      background: rgba(88, 86, 214, 0.1);
+                      color: #5856D6;
+                      padding: 2px 6px;
+                      border-radius: 4px;
+                      font-size: $ios-font-size-xs;
+                      font-weight: $ios-font-weight-medium;
+                    }
+                  }
+                  
+                  .lighting-info {
+                    display: flex;
+                    gap: $ios-spacing-xs;
+                    flex-wrap: wrap;
+                    
+                    .mood-tag {
+                      display: inline-block;
+                      background: rgba(255, 204, 0, 0.1);
+                      color: #FFCC00;
+                      padding: 2px 6px;
+                      border-radius: 4px;
+                      font-size: $ios-font-size-xs;
+                      font-weight: $ios-font-weight-medium;
+                    }
+                    
+                    .brightness-tag {
+                      display: inline-block;
+                      background: rgba(255, 149, 0, 0.1);
+                      color: #FF9500;
+                      padding: 2px 6px;
+                      border-radius: 4px;
+                      font-size: $ios-font-size-xs;
+                      font-weight: $ios-font-weight-medium;
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 需求映射部分
+  .mapping-section {
+    .consistency-warning {
+      display: flex;
+      align-items: center;
+      gap: $ios-spacing-xs;
+      padding: $ios-spacing-sm;
+      background: rgba(255, 59, 48, 0.1);
+      border: 1px solid rgba(255, 59, 48, 0.2);
+      border-radius: 6px;
+      color: #FF3B30;
+      font-size: $ios-font-size-xs;
+      margin-bottom: $ios-spacing-md;
+      
+      svg {
+        width: 16px;
+        height: 16px;
+        flex-shrink: 0;
+      }
+    }
+    
+    .indicator-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+      gap: $ios-spacing-lg;
+      
+      .indicator-group {
+        h5 {
+          margin: 0 0 $ios-spacing-sm 0;
+          font-size: $ios-font-size-xs;
+          font-weight: $ios-font-weight-semibold;
+          color: $ios-text-primary;
+          padding-bottom: $ios-spacing-xs;
+          border-bottom: 1px solid $ios-border;
+        }
+        
+        .indicator-item {
+          margin-bottom: $ios-spacing-md;
+          
+          label {
+            display: block;
+            font-size: $ios-font-size-xs;
+            color: $ios-text-secondary;
+            margin-bottom: $ios-spacing-xs;
+            font-weight: $ios-font-weight-medium;
+          }
+          
+          .slider-container {
+            display: flex;
+            align-items: center;
+            gap: $ios-spacing-sm;
+            
+            input[type="range"] {
+              flex: 1;
+              height: 4px;
+              background: $ios-border;
+              border-radius: 2px;
+              outline: none;
+              -webkit-appearance: none;
+              appearance: none;
+              
+              &::-webkit-slider-thumb {
+                  -webkit-appearance: none;
+                  appearance: none;
+                  width: 16px;
+                height: 16px;
+                background: #007AFF;
+                border-radius: 50%;
+                cursor: pointer;
+              }
+              
+              &::-moz-range-thumb {
+                width: 16px;
+                height: 16px;
+                background: #007AFF;
+                border-radius: 50%;
+                cursor: pointer;
+                border: none;
+              }
+            }
+            
+            .slider-value {
+              font-size: $ios-font-size-xs;
+              color: $ios-text-primary;
+              font-weight: $ios-font-weight-medium;
+              min-width: 40px;
+              text-align: right;
+            }
+          }
+
+          // RGB控件样式
+          .rgb-controls {
+            display: flex;
+            flex-direction: column;
+            gap: $ios-spacing-xs;
+            
+            .rgb-slider {
+              display: flex;
+              align-items: center;
+              gap: $ios-spacing-xs;
+              
+              span:first-child {
+                font-size: $ios-font-size-xs;
+                font-weight: $ios-font-weight-semibold;
+                color: $ios-text-secondary;
+                width: 12px;
+              }
+              
+              input[type="range"] {
+                flex: 1;
+                height: 4px;
+                background: $ios-border;
+                border-radius: 2px;
+                outline: none;
+                -webkit-appearance: none;
+                appearance: none;
+                
+                &::-webkit-slider-thumb {
+                  -webkit-appearance: none;
+                  appearance: none;
+                  width: 14px;
+                  height: 14px;
+                  background: #007AFF;
+                  border-radius: 50%;
+                  cursor: pointer;
+                }
+              }
+              
+              span:last-child {
+                font-size: $ios-font-size-xs;
+                color: $ios-text-primary;
+                font-weight: $ios-font-weight-medium;
+                min-width: 30px;
+                text-align: right;
+              }
+            }
+            
+            .color-preview {
+              width: 100%;
+              height: 30px;
+              border-radius: 6px;
+              border: 1px solid $ios-border;
+              margin-top: $ios-spacing-xs;
+              transition: background-color 0.1s ease; // 减少过渡时间,提高响应速度
+              // 移除默认背景色,完全依赖Angular绑定
+              will-change: background-color; // 优化GPU渲染
+            }
+          }
+        }
+      }
+    }
+
+    // 预设氛围样式
+    .atmosphere-presets {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+      gap: $ios-spacing-sm;
+      
+      .preset-card {
+        display: flex;
+        align-items: center;
+        gap: $ios-spacing-sm;
+        padding: $ios-spacing-sm;
+        border: 1px solid $ios-border;
+        border-radius: 8px;
+        background: white;
+        cursor: pointer;
+        transition: all 0.2s ease;
+        
+        &:hover {
+          border-color: #007AFF;
+          background: rgba(0, 122, 255, 0.02);
+        }
+        
+        .preset-color {
+          width: 40px;
+          height: 40px;
+          border-radius: 6px;
+          border: 1px solid $ios-border;
+          flex-shrink: 0;
+        }
+        
+        .preset-info {
+          flex: 1;
+          
+          .preset-name {
+            font-size: $ios-font-size-xs;
+            font-weight: $ios-font-weight-semibold;
+            color: $ios-text-primary;
+            margin-bottom: 2px;
+          }
+          
+          .preset-details {
+            font-size: $ios-font-size-xs;
+            color: $ios-text-secondary;
+            margin-bottom: $ios-spacing-xs;
+          }
+          
+          .preset-materials {
+            display: flex;
+            gap: 4px;
+            flex-wrap: wrap;
+            
+            .material-tag {
+              display: inline-block;
+              background: $ios-background-secondary;
+              color: $ios-text-secondary;
+              padding: 2px 6px;
+              border-radius: 4px;
+              font-size: $ios-font-size-xs;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 进度管理样式
+  .progress-section {
+    .progress-overview {
+      display: flex;
+      gap: 2rem;
+      margin-bottom: 2rem;
+      
+      .progress-stats {
+        flex: 1;
+        display: grid;
+        grid-template-columns: repeat(4, 1fr);
+        gap: 1rem;
+        
+        .stat-item {
+          text-align: center;
+          padding: 1rem;
+          background: #f8f9fa;
+          border-radius: 8px;
+          
+          .stat-number {
+            font-size: 2rem;
+            font-weight: 600;
+            color: #007AFF;
+            margin-bottom: 0.5rem;
+          }
+          
+          .stat-label {
+            font-size: 0.875rem;
+            color: #666;
+          }
+        }
+      }
+      
+      .progress-chart {
+        .chart-container {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          
+          .progress-circle {
+            position: relative;
+            
+            .progress-text {
+              position: absolute;
+              top: 50%;
+              left: 50%;
+              transform: translate(-50%, -50%);
+              text-align: center;
+              
+              .progress-percentage {
+                font-size: 1.5rem;
+                font-weight: 600;
+                color: #34C759;
+              }
+              
+              .progress-label {
+                font-size: 0.75rem;
+                color: #666;
+                margin-top: 0.25rem;
+              }
+            }
+          }
+        }
+      }
+    }
+    
+    .history-section {
+      margin-bottom: 2rem;
+      
+      .section-header {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 1rem;
+        
+        h5 {
+          margin: 0;
+          color: #333;
+        }
+        
+        .history-controls {
+          display: flex;
+          gap: 1rem;
+          align-items: center;
+          
+          .history-select {
+            padding: 0.5rem;
+            border: 1px solid #ddd;
+            border-radius: 4px;
+            background: white;
+            min-width: 200px;
+          }
+        }
+      }
+      
+      .history-timeline {
+        .timeline-item {
+          display: flex;
+          align-items: flex-start;
+          margin-bottom: 1rem;
+          cursor: pointer;
+          padding: 0.5rem;
+          border-radius: 4px;
+          transition: background-color 0.2s;
+          
+          &:hover {
+            background: #f8f9fa;
+          }
+          
+          .timeline-marker {
+            width: 12px;
+            height: 12px;
+            border-radius: 50%;
+            background: #007AFF;
+            margin-right: 1rem;
+            margin-top: 0.25rem;
+            flex-shrink: 0;
+          }
+          
+          .timeline-content {
+            flex: 1;
+            
+            .timeline-header {
+              display: flex;
+              justify-content: space-between;
+              margin-bottom: 0.25rem;
+              
+              .timeline-time {
+                font-weight: 500;
+                color: #333;
+              }
+              
+              .timeline-author {
+                color: #666;
+                font-size: 0.875rem;
+              }
+            }
+            
+            .timeline-summary {
+              color: #666;
+              font-size: 0.875rem;
+            }
+          }
+        }
+      }
+      
+      .empty-history {
+        text-align: center;
+        padding: 2rem;
+        color: #666;
+        
+        p {
+          margin-bottom: 1rem;
+        }
+      }
+    }
+    
+    .status-distribution {
+      h5 {
+        margin-bottom: 1rem;
+        color: #333;
+      }
+      
+      .status-bars {
+        .status-bar {
+          margin-bottom: 1rem;
+          
+          .status-info {
+            display: flex;
+            justify-content: space-between;
+            margin-bottom: 0.5rem;
+            
+            .status-name {
+              font-weight: 500;
+              color: #333;
+            }
+            
+            .status-count {
+              color: #666;
+            }
+          }
+          
+          .status-progress {
+            height: 8px;
+            background: #e5e5ea;
+            border-radius: 4px;
+            overflow: hidden;
+            
+            .progress-bar {
+              height: 100%;
+              transition: width 0.3s ease;
+              
+              &.confirmed {
+                background: #34C759;
+              }
+              
+              &.pending {
+                background: #FF9500;
+              }
+              
+              &.rejected {
+                background: #FF3B30;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 协作验证部分
+  .collaboration-section {
+    .list-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: $ios-spacing-md;
+      padding-bottom: $ios-spacing-sm;
+      border-bottom: 1px solid $ios-border;
+      
+      h5 {
+        margin: 0;
+        font-size: $ios-font-size-sm;
+        font-weight: $ios-font-weight-semibold;
+        color: $ios-text-primary;
+      }
+      
+      .list-controls {
+        display: flex;
+        gap: $ios-spacing-sm;
+      }
+    }
+    
+    .requirements-list {
+      .requirement-item {
+        border: 1px solid $ios-border;
+        border-radius: 8px;
+        padding: $ios-spacing-md;
+        margin-bottom: $ios-spacing-md;
+        background: white;
+        transition: all 0.2s ease;
+        
+        &.confirmed {
+          border-color: #34C759;
+          background: rgba(52, 199, 89, 0.02);
+        }
+        
+        &.rejected {
+          border-color: #FF3B30;
+          background: rgba(255, 59, 48, 0.02);
+        }
+        
+        &.pending {
+          border-color: #FF9500;
+          background: rgba(255, 149, 0, 0.02);
+        }
+        
+        .requirement-header {
+          display: flex;
+          justify-content: space-between;
+          align-items: flex-start;
+          margin-bottom: $ios-spacing-sm;
+          
+          .requirement-info {
+            flex: 1;
+            
+            h6 {
+              margin: 0 0 $ios-spacing-xs 0;
+              font-size: $ios-font-size-sm;
+              font-weight: $ios-font-weight-semibold;
+              color: $ios-text-primary;
+            }
+            
+            p {
+              margin: 0 0 $ios-spacing-xs 0;
+              font-size: $ios-font-size-xs;
+              color: $ios-text-secondary;
+              line-height: 1.4;
+            }
+            
+            .requirement-tags {
+              display: flex;
+              gap: $ios-spacing-xs;
+              flex-wrap: wrap;
+              
+              .tag {
+                display: inline-block;
+                background: $ios-background-secondary;
+                color: $ios-text-secondary;
+                padding: 2px 6px;
+                border-radius: 4px;
+                font-size: $ios-font-size-xs;
+              }
+            }
+          }
+          
+          .requirement-meta {
+            display: flex;
+            flex-direction: column;
+            gap: $ios-spacing-xs;
+            align-items: flex-end;
+            
+            .priority-select {
+              padding: 4px 8px;
+              border: 1px solid $ios-border;
+              border-radius: 4px;
+              font-size: $ios-font-size-xs;
+              background: white;
+              
+              &:focus {
+                outline: none;
+                border-color: #007AFF;
+              }
+            }
+            
+            .status-badge {
+              padding: 2px 8px;
+              border-radius: 12px;
+              font-size: $ios-font-size-xs;
+              font-weight: $ios-font-weight-medium;
+              
+              &.confirmed {
+                background: #34C759;
+                color: white;
+              }
+              
+              &.rejected {
+                background: #FF3B30;
+                color: white;
+              }
+              
+              &.pending {
+                background: #FF9500;
+                color: white;
+              }
+            }
+          }
+        }
+        
+        .requirement-actions {
+          display: flex;
+          gap: $ios-spacing-sm;
+          align-items: center;
+          
+          .unread-indicator {
+            display: inline-block;
+            width: 6px;
+            height: 6px;
+            background: #FF3B30;
+            border-radius: 50%;
+            margin-left: 4px;
+          }
+        }
+        
+        .comments-section {
+          margin-top: $ios-spacing-md;
+          padding-top: $ios-spacing-md;
+          border-top: 1px solid $ios-border;
+          
+          .comments-list {
+            margin-bottom: $ios-spacing-md;
+            
+            .comment-item {
+              padding: $ios-spacing-sm;
+              border: 1px solid $ios-border;
+              border-radius: 6px;
+              margin-bottom: $ios-spacing-sm;
+              background: $ios-background-secondary;
+              
+              &.resolved {
+                opacity: 0.6;
+                background: rgba(52, 199, 89, 0.05);
+              }
+              
+              .comment-header {
+                display: flex;
+                align-items: center;
+                gap: $ios-spacing-sm;
+                margin-bottom: $ios-spacing-xs;
+                
+                .comment-author {
+                  font-size: $ios-font-size-xs;
+                  font-weight: $ios-font-weight-semibold;
+                  color: $ios-text-primary;
+                }
+                
+                .comment-role {
+                  font-size: $ios-font-size-xs;
+                  color: $ios-text-secondary;
+                  background: white;
+                  padding: 1px 4px;
+                  border-radius: 3px;
+                }
+                
+                .comment-time {
+                  font-size: $ios-font-size-xs;
+                  color: $ios-text-tertiary;
+                  margin-left: auto;
+                }
+              }
+              
+              .comment-content {
+                font-size: $ios-font-size-xs;
+                color: $ios-text-primary;
+                line-height: 1.4;
+              }
+            }
+          }
+          
+          .add-comment {
+            display: flex;
+            gap: $ios-spacing-sm;
+            align-items: flex-end;
+            
+            textarea {
+              flex: 1;
+              padding: $ios-spacing-xs;
+              border: 1px solid $ios-border;
+              border-radius: 6px;
+              font-size: $ios-font-size-xs;
+              resize: vertical;
+              min-height: 60px;
+              
+              &:focus {
+                outline: none;
+                border-color: #007AFF;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 进度管理部分
+  .progress-section {
+    .progress-overview {
+      display: grid;
+      grid-template-columns: 1fr auto;
+      gap: $ios-spacing-lg;
+      margin-bottom: $ios-spacing-lg;
+      
+      .progress-stats {
+        display: flex;
+        gap: $ios-spacing-lg;
+        
+        .stat-item {
+          text-align: center;
+          
+          .stat-number {
+            font-size: 24px;
+            font-weight: $ios-font-weight-bold;
+            color: $ios-primary;
+            margin-bottom: $ios-spacing-xs;
+          }
+          
+          .stat-label {
+            font-size: $ios-font-size-xs;
+            color: $ios-text-secondary;
+          }
+        }
+      }
+      
+      .progress-chart {
+        .chart-container {
+          position: relative;
+          
+          .progress-circle {
+            position: relative;
+            
+            .progress-text-center {
+              position: absolute;
+              top: 50%;
+              left: 50%;
+              transform: translate(-50%, -50%);
+              text-align: center;
+              
+              .progress-percentage {
+                font-size: 18px;
+                font-weight: $ios-font-weight-bold;
+                color: $ios-primary;
+              }
+              
+              .progress-label {
+                font-size: $ios-font-size-xs;
+                color: $ios-text-secondary;
+              }
+            }
+          }
+        }
+      }
+    }
+    
+    .timeline {
+      h5 {
+        margin: 0 0 $ios-spacing-sm 0;
+        font-size: $ios-font-size-xs;
+        font-weight: $ios-font-weight-semibold;
+        color: $ios-text-primary;
+      }
+      
+      .timeline-list {
+        .timeline-item {
+          display: flex;
+          align-items: center;
+          gap: $ios-spacing-sm;
+          padding: $ios-spacing-sm 0;
+          border-bottom: 1px dashed $ios-border;
+          
+          &:last-child {
+            border-bottom: none;
+          }
+          
+          .timeline-marker {
+            width: 8px;
+            height: 8px;
+            border-radius: 50%;
+            background: $ios-border;
+            flex-shrink: 0;
+          }
+          
+          &.status-confirmed .timeline-marker {
+            background: #34C759;
+          }
+          
+          &.status-rejected .timeline-marker {
+            background: #FF3B30;
+          }
+          
+          &.status-pending .timeline-marker {
+            background: #007AFF;
+          }
+          
+          .timeline-content {
+            .timeline-title {
+              font-size: $ios-font-size-xs;
+              color: $ios-text-primary;
+              font-weight: $ios-font-weight-medium;
+            }
+            
+            .timeline-status {
+              font-size: $ios-font-size-xs;
+              color: $ios-text-secondary;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 通用按钮样式
+  .btn-primary, .btn-success, .btn-ghost {
+    border: none;
+    border-radius: 6px;
+    font-size: $ios-font-size-xs;
+    font-weight: $ios-font-weight-medium;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    
+    &.btn-sm {
+      padding: 6px 12px;
+    }
+    
+    &.btn-xs {
+      padding: 4px 8px;
+    }
+    
+    &:disabled {
+      opacity: 0.5;
+      cursor: not-allowed;
+    }
+  }
+  
+  .btn-primary {
+    background: $ios-primary;
+    color: white;
+    
+    &:hover:not(:disabled) {
+      background: color.adjust($ios-primary, $lightness: -10%);
+    }
+  }
+  
+  .btn-success {
+    background: #34C759;
+    color: white;
+    
+    &:hover:not(:disabled) {
+      background: color.adjust(#34C759, $lightness: -10%);
+    }
+  }
+  
+  .btn-ghost {
+    background: transparent;
+    color: $ios-text-secondary;
+    border: 1px solid $ios-border;
+    
+    &:hover:not(:disabled) {
+      background: $ios-background-secondary;
+      color: $ios-text-primary;
+    }
+  }
+}
+
+// 进度动画
+@keyframes progressPulse {
+  0% { opacity: 0.6; }
+  50% { opacity: 1; }
+  100% { opacity: 0.6; }
+}
+
+// 全局通知样式
+.execution-notification {
+  position: fixed;
+  top: 20px;
+  right: 20px;
+  background: #4CAF50;
+  color: white;
+  padding: 16px 24px;
+  border-radius: 8px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  z-index: 1000;
+  animation: slideInRight 0.3s ease-out;
+  
+  &.error {
+    background: #f44336;
+  }
+  
+  &.warning {
+    background: #ff9800;
+  }
+}
+
+@keyframes slideInRight {
+  from {
+    transform: translateX(100%);
+    opacity: 0;
+  }
+  to {
+    transform: translateX(0);
+    opacity: 1;
+  }
+}
+
+// 紧凑型流程进度指示器
+.compact-stage-indicators {
+  display: flex;
+  align-items: center;
+  margin: 0 16px;
+  
+  .stage-chain {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+  
+  .stage-dot {
+    width: 28px;
+    height: 28px;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 12px;
+    font-weight: 600;
+    color: white;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    position: relative;
+    
+    .stage-number {
+      font-size: 11px;
+      font-weight: bold;
+    }
+    
+    // 已完成状态 - 绿色
+    &.stage-completed {
+      background: linear-gradient(135deg, #10b981, #059669);
+      box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
+      
+      &:hover {
+        transform: scale(1.1);
+        box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
+      }
+    }
+    
+    // 进行中状态 - 红色
+    &.stage-in-progress {
+      background: linear-gradient(135deg, #ef4444, #dc2626);
+      box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
+      animation: pulse-red 2s infinite;
+      
+      &:hover {
+        transform: scale(1.1);
+        box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
+      }
+    }
+    
+    // 未进行状态 - 黄色
+    &.stage-pending {
+      background: linear-gradient(135deg, #f59e0b, #d97706);
+      box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);
+      
+      &:hover {
+        transform: scale(1.1);
+        box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4);
+      }
+    }
+  }
+  
+  .stage-connector {
+    width: 20px;
+    height: 3px;
+    border-radius: 2px;
+    transition: all 0.3s ease;
+    
+    &.completed {
+      background: linear-gradient(90deg, #10b981, #059669);
+    }
+    
+    &.pending {
+      background: #e5e7eb;
+    }
+  }
+}
+
+// 进行中状态的脉冲动画
+@keyframes pulse-red {
+  0%, 100% {
+    box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
+  }
+  50% {
+    box-shadow: 0 2px 12px rgba(239, 68, 68, 0.6);
+  }
+}
+
+// 更新header-actions布局
+.header-actions {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  
+  .btn-ghost {
+    flex-shrink: 0;
+  }
+  
+  .compact-stage-indicators {
+    flex-shrink: 0;
+  }
+  
+  .progress-indicator {
+    flex-shrink: 0;
+  }
+}
+
+// 保存状态区域
+.save-section {
+  margin-top: 24px;
+  padding: 16px;
+  background: #f8fafc;
+  border-radius: 8px;
+  border: 1px solid #e2e8f0;
+  
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 16px;
+  
+  .save-status {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    
+    .save-icon {
+      font-size: 16px;
+      font-weight: bold;
+      
+      &.save-icon-saved {
+        color: #10b981;
+      }
+      
+      &.save-icon-saving {
+        color: #3b82f6;
+        animation: spin 1s linear infinite;
+      }
+      
+      &.save-icon-error {
+        color: #ef4444;
+      }
+      
+      &.save-icon-unsaved {
+        color: #f59e0b;
+      }
+    }
+    
+    .save-text {
+      font-size: 14px;
+      color: #64748b;
+    }
+  }
+  
+  .save-actions {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    
+    .btn-secondary {
+      padding: 8px 16px;
+      background: #3b82f6;
+      color: white;
+      border: none;
+      border-radius: 6px;
+      font-size: 14px;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      
+      &:hover:not(:disabled) {
+        background: #2563eb;
+        transform: translateY(-1px);
+      }
+      
+      &:disabled {
+        background: #94a3b8;
+        cursor: not-allowed;
+        transform: none;
+      }
+      
+      .loading-spinner {
+        display: inline-block;
+        width: 12px;
+        height: 12px;
+        border: 2px solid transparent;
+        border-top: 2px solid currentColor;
+        border-radius: 50%;
+        animation: spin 1s linear infinite;
+        margin-right: 8px;
+      }
+    }
+    
+    .auto-save-toggle {
+      .toggle-label {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        cursor: pointer;
+        
+        .toggle-input {
+          display: none;
+        }
+        
+        .toggle-slider {
+          width: 40px;
+          height: 20px;
+          background: #cbd5e1;
+          border-radius: 10px;
+          position: relative;
+          transition: all 0.3s ease;
+          
+          &::after {
+            content: '';
+            position: absolute;
+            top: 2px;
+            left: 2px;
+            width: 16px;
+            height: 16px;
+            background: white;
+            border-radius: 50%;
+            transition: all 0.3s ease;
+          }
+        }
+        
+        .toggle-input:checked + .toggle-slider {
+          background: #10b981;
+          
+          &::after {
+            transform: translateX(20px);
+          }
+        }
+        
+        .toggle-text {
+          font-size: 14px;
+          color: #64748b;
+        }
+      }
+    }
+  }
+}
+
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+// 需求映射测试相关样式
+.test-progress {
+  margin-bottom: 32px;
+  padding: 20px;
+  background: #f8f9fa;
+  border-radius: 8px;
+  border: 1px solid #e9ecef;
+
+  h3 {
+    margin: 0 0 16px 0;
+    font-size: 18px;
+    color: #495057;
+  }
+
+  .steps-container {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+    gap: 16px;
+  }
+
+  .step-item {
+    display: flex;
+    align-items: center;
+    padding: 12px;
+    background: white;
+    border-radius: 6px;
+    border: 2px solid #e9ecef;
+    transition: all 0.2s ease;
+
+    &.step-pending {
+      border-color: #e9ecef;
+      .step-icon { color: #6c757d; }
+    }
+
+    &.step-in-progress {
+      border-color: #ffc107;
+      background: #fff8e1;
+      .step-icon { color: #ffc107; }
+    }
+
+    &.step-completed {
+      border-color: #28a745;
+      background: #f8fff9;
+      .step-icon { color: #28a745; }
+    }
+
+    &.step-error {
+      border-color: #dc3545;
+      background: #fff5f5;
+      .step-icon { color: #dc3545; }
+    }
+
+    .step-icon {
+      font-size: 20px;
+      margin-right: 12px;
+    }
+
+    .step-info {
+      .step-name {
+        font-weight: 500;
+        font-size: 14px;
+        color: #495057;
+      }
+
+      .step-status {
+        font-size: 12px;
+        color: #6c757d;
+        margin-top: 2px;
+      }
+    }
+  }
+}
+
+.upload-section,
+.analysis-section,
+.mapping-section {
+  margin-bottom: 32px;
+  padding: 24px;
+  background: white;
+  border-radius: 8px;
+  border: 1px solid #e9ecef;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+
+  &.disabled {
+    opacity: 0.6;
+    pointer-events: none;
+  }
+
+  h3 {
+    margin: 0 0 20px 0;
+    font-size: 20px;
+    color: #495057;
+    border-bottom: 2px solid #e9ecef;
+    padding-bottom: 8px;
+  }
+}
+
+.upload-area {
+  position: relative;
+  min-height: 200px;
+
+  .upload-dropzone {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    min-height: 200px;
+    border: 2px dashed #dee2e6;
+    border-radius: 8px;
+    background: #f8f9fa;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    position: relative;
+
+    &:hover {
+      border-color: #667eea;
+      background: #f0f4ff;
+    }
+
+    .upload-icon {
+      font-size: 48px;
+      margin-bottom: 16px;
+      opacity: 0.7;
+    }
+
+    .upload-text {
+      font-size: 18px;
+      font-weight: 500;
+      color: #495057;
+      margin-bottom: 8px;
+    }
+
+    .upload-hint {
+      font-size: 14px;
+      color: #6c757d;
+    }
+
+    .file-input {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      opacity: 0;
+      cursor: pointer;
+    }
+  }
+
+  .uploaded-files {
+    h4 {
+      margin: 0 0 16px 0;
+      font-size: 16px;
+      color: #495057;
+    }
+
+    .files-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+      gap: 16px;
+    }
+
+    .file-item {
+      border: 1px solid #e9ecef;
+      border-radius: 6px;
+      overflow: hidden;
+      background: white;
+
+      .file-preview {
+        width: 100%;
+        height: 120px;
+        object-fit: cover;
+      }
+
+      .file-info {
+        padding: 8px;
+
+        .file-name {
+          font-size: 12px;
+          font-weight: 500;
+          color: #495057;
+          margin-bottom: 4px;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+
+        .file-size {
+          font-size: 11px;
+          color: #6c757d;
+        }
+      }
+    }
+  }
+
+  .loading-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(255, 255, 255, 0.9);
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    border-radius: 8px;
+  }
+}
+
+.analysis-loading,
+.mapping-loading {
+  display: flex;
+  align-items: center;
+  padding: 24px;
+  background: #f0f4ff;
+  border-radius: 6px;
+  border: 1px solid #e3f2fd;
+
+  .loading-text {
+    margin-left: 16px;
+
+    h4 {
+      margin: 0 0 8px 0;
+      font-size: 16px;
+      color: #495057;
+    }
+
+    p {
+      margin: 0;
+      font-size: 14px;
+      color: #6c757d;
+    }
+  }
+}
+
+.analysis-error,
+.mapping-error {
+  display: flex;
+  align-items: center;
+  padding: 24px;
+  background: #fff5f5;
+  border-radius: 6px;
+  border: 1px solid #ffebee;
+
+  .error-icon {
+    font-size: 24px;
+    margin-right: 16px;
+  }
+
+  .error-text {
+    flex: 1;
+
+    h4 {
+      margin: 0 0 8px 0;
+      font-size: 16px;
+      color: #dc3545;
+    }
+
+    p {
+      margin: 0 0 12px 0;
+      font-size: 14px;
+      color: #6c757d;
+    }
+
+    .retry-btn {
+      background: #dc3545;
+      color: white;
+      border: none;
+      padding: 8px 16px;
+      border-radius: 4px;
+      cursor: pointer;
+      font-size: 14px;
+      transition: background 0.2s ease;
+
+      &:hover {
+        background: #c82333;
+      }
+    }
+  }
+}
+
+.analysis-result,
+.mapping-result {
+  h4 {
+    margin: 0 0 16px 0;
+    font-size: 18px;
+    color: #28a745;
+    display: flex;
+    align-items: center;
+  }
+
+  .analysis-summary {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+    gap: 16px;
+    margin-top: 16px;
+  }
+
+  .summary-item {
+    display: flex;
+    justify-content: space-between;
+    padding: 12px;
+    background: #f8f9fa;
+    border-radius: 4px;
+
+    .label {
+      font-weight: 500;
+      color: #495057;
+    }
+
+    .value {
+      color: #6c757d;
+    }
+  }
+}
+
+.mapping-section-item {
+  margin-bottom: 24px;
+
+  h5 {
+    margin: 0 0 16px 0;
+    font-size: 16px;
+    color: #495057;
+    border-bottom: 1px solid #e9ecef;
+    padding-bottom: 8px;
+  }
+
+  .scene-info {
+    .info-row {
+      display: flex;
+      justify-content: space-between;
+      padding: 8px 0;
+      border-bottom: 1px solid #f8f9fa;
+
+      .label {
+        font-weight: 500;
+        color: #495057;
+      }
+
+      .value {
+        color: #6c757d;
+      }
+    }
+
+    .atmosphere-preview {
+      margin-top: 16px;
+      text-align: center;
+
+      .preview-image {
+        max-width: 300px;
+        max-height: 200px;
+        border-radius: 6px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+      }
+
+      .preview-label {
+        margin-top: 8px;
+        font-size: 14px;
+        color: #6c757d;
+      }
+    }
+  }
+
+  .params-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+    gap: 20px;
+  }
+
+  .param-group {
+    background: #f8f9fa;
+    padding: 16px;
+    border-radius: 6px;
+
+    h6 {
+      margin: 0 0 12px 0;
+      font-size: 14px;
+      font-weight: 600;
+      color: #495057;
+    }
+
+    .param-item {
+      display: flex;
+      justify-content: space-between;
+      padding: 6px 0;
+      border-bottom: 1px solid #e9ecef;
+
+      &:last-child {
+        border-bottom: none;
+      }
+
+      .label {
+        font-size: 13px;
+        color: #6c757d;
+      }
+
+      .value {
+        font-size: 13px;
+        font-weight: 500;
+        color: #495057;
+      }
+    }
+  }
+}
+
+.analysis-placeholder,
+.mapping-placeholder {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  min-height: 150px;
+  color: #6c757d;
+
+  .placeholder-icon {
+    font-size: 48px;
+    margin-bottom: 16px;
+    opacity: 0.7;
+  }
+
+  .placeholder-text {
+    text-align: center;
+
+    h4 {
+      margin: 0 0 8px 0;
+      font-size: 16px;
+    }
+
+    p {
+      margin: 0;
+      font-size: 14px;
+      opacity: 0.8;
+    }
+  }
+}
+
+.test-actions {
+  margin-top: 24px;
+  text-align: center;
+
+  .download-btn {
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    background: #667eea;
+    color: white;
+    border: none;
+    padding: 12px 24px;
+    border-radius: 6px;
+    cursor: pointer;
+    font-size: 14px;
+    font-weight: 500;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background: #5a6fd8;
+      transform: translateY(-1px);
+    }
+
+    svg {
+      width: 16px;
+      height: 16px;
+    }
+  }
+}
+
+.loading-spinner {
+  width: 24px;
+  height: 24px;
+  border: 2px solid #f3f3f3;
+  border-top: 2px solid #667eea;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .test-progress {
+    padding: 16px;
+
+    .steps-container {
+      grid-template-columns: 1fr;
+    }
+  }
+
+  .upload-section,
+  .analysis-section,
+  .mapping-section {
+    padding: 16px;
+  }
+
+  .params-grid {
+    grid-template-columns: 1fr;
+  }
+
+  .analysis-summary {
+    grid-template-columns: 1fr;
+  }
+}
+
+@keyframes pulse {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.5; }
+}

+ 2086 - 0
copy/requirements-confirm-card.ts

@@ -0,0 +1,2086 @@
+import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Subscription } from 'rxjs';
+import { UploadSuccessModalComponent } from '../upload-success-modal/upload-success-modal.component';
+import { GlobalPromptComponent } from '../global-prompt/global-prompt.component';
+import { ColorAnalysisService, ColorAnalysisResult } from '../../services/color-analysis.service';
+import { FullReportOverlayComponent } from '../full-report-overlay/full-report-overlay.component';
+import { CadAnalysisService, CadAnalysisResult } from '../../services/cad-analysis.service';
+import { RequirementMappingService } from '../../../services/requirement-mapping.service';
+import { RequirementMapping, SceneTemplate } from '../../../models/requirement-mapping.interface';
+
+// 素材文件接口
+interface MaterialFile {
+  id: string;
+  name: string;
+  type: 'text' | 'image' | 'cad';
+  url?: string;
+  content?: string;
+  size?: string;
+  uploadTime: Date;
+  analysis?: MaterialAnalysis;
+}
+
+// 素材解析结果接口
+interface MaterialAnalysis {
+  // 文本类解析
+  atmosphere?: { description: string; rgb?: string; colorTemp?: string };
+  residents?: { type: string; details: string };
+  scenes?: { preference: string; requirements: string };
+  keywords?: { positive: string[]; negative: string[] };
+  
+  // 图片类解析
+  mainColors?: { rgb: string; percentage: number }[];
+  colorTemperature?: string;
+  materialRatio?: { material: string; percentage: number }[];
+  spaceRatio?: number;
+  
+  // CAD类解析
+  structuralElements?: { type: string; position: string; changeable: boolean }[];
+  spaceMetrics?: { room: string; ratio: string; width: string }[];
+  flowMetrics?: { area: string; width: string; compliance: boolean }[];
+  
+  // 增强色彩分析
+  enhancedColorAnalysis?: {
+    colorWheel?: {
+      primaryHue: number;
+      saturation: number;
+      brightness: number;
+      dominantColors: Array<{ hue: number; saturation: number; brightness: number; percentage: number }>;
+      colorDistribution: string;
+    };
+    colorHarmony?: {
+      harmonyType: string;
+      harmonyScore: number;
+      complementaryColors: string[];
+      suggestions: string[];
+    };
+    colorTemperatureAnalysis?: {
+      kelvin: number;
+      warmCoolBalance: number;
+      description: string;
+      lightingRecommendations: string[];
+    };
+    colorPsychology?: {
+      primaryMood: string;
+      atmosphere: string;
+      psychologicalEffects: string[];
+      suitableSpaces: string[];
+    };
+  };
+  
+  // 形体分析
+  formAnalysis?: {
+    lineAnalysis?: {
+      dominantLines: Array<{ type: string; percentage: number; characteristics: string[] }>;
+      lineQuality: { smoothness: number; precision: number; expressiveness: number };
+      visualFlow: { direction: string; rhythm: string; continuity: number };
+    };
+    geometryAnalysis?: {
+      primaryShapes: Array<{ shape: string; percentage: number; characteristics: string[] }>;
+      complexity: string;
+      symmetry: { type: string; score: number };
+      balance: { visual: number; compositional: number };
+    };
+    proportionAnalysis?: {
+      aspectRatio: number;
+      goldenRatio: number;
+      proportionHarmony: number;
+      scaleRelationships: Array<{ element: string; scale: string; relationship: string }>;
+    };
+    overallAssessment?: {
+      formComplexity: number;
+      visualImpact: number;
+      functionalSuitability: number;
+      aestheticAppeal: number;
+    };
+  };
+  
+  // 质感分析
+  textureAnalysis?: {
+    surfaceProperties?: {
+      roughness: { level: string; value: number; description: string };
+      glossiness: { level: string; value: number; reflectivity: number };
+      transparency: { level: string; value: number };
+      porosity: { level: string; value: number };
+    };
+    tactilePredict?: {
+      temperature: { perceived: string; thermalConductivity: number };
+      hardness: { level: string; value: number };
+      flexibility: { level: string; value: number };
+      weight: { perceived: string; density: number };
+      comfort: { tactileComfort: number; ergonomicSuitability: number };
+    };
+    materialClassification?: {
+      primaryMaterial: { category: string; confidence: number; subcategory?: string };
+      secondaryMaterials: Array<{ category: string; percentage: number; confidence: number }>;
+      materialProperties: { durability: number; maintenance: string; sustainability: number; costLevel: string };
+    };
+    textureQuality?: {
+      overallQuality: number;
+      consistency: number;
+      authenticity: number;
+      visualAppeal: number;
+      functionalSuitability: number;
+      ageingCharacteristics: { wearResistance: number; patinaPotential: number; maintenanceNeeds: string[] };
+    };
+  };
+  
+  // 纹理图案分析
+  patternAnalysis?: {
+    patternRecognition?: {
+      primaryPatterns: Array<{ type: string; confidence: number; coverage: number; characteristics: string[] }>;
+      patternComplexity: { level: string; score: number; elements: number };
+      patternScale: { size: string; uniformity: number; variation: number };
+    };
+    repetitionAnalysis?: {
+      repetitionType: { primary: string; pattern: string; consistency: number };
+      spacing: { horizontal: number; vertical: number; uniformity: number; rhythm: string };
+      symmetry: { type: string; strength: number; axes: number };
+      tiling: { seamless: boolean; quality: number; edgeHandling: string };
+    };
+    textureClassification?: {
+      textureFamily: { primary: string; subcategory: string; confidence: number };
+      surfaceCharacter: { tactileQuality: string; visualDepth: number; dimensionality: string };
+      materialSuggestion: { likelyMaterials: string[]; fabricationMethod: string[]; applicationSuitability: string[] };
+    };
+    visualRhythm?: {
+      rhythmType: { primary: string; intensity: number; tempo: string };
+      movement: { direction: string; flow: number; energy: number };
+      emphasis: { focalPoints: number; contrast: number; hierarchy: number };
+      harmony: { overall: number; balance: number; unity: number };
+    };
+  };
+  
+  // 灯光分析
+  lightingAnalysis?: {
+    lightSourceIdentification?: {
+      primarySources: Array<{
+        type: string;
+        subtype: string;
+        position: { direction: string; angle: number; distance: string };
+        intensity: number;
+        confidence: number;
+      }>;
+      lightingSetup: { complexity: string; sourceCount: number; dominantSource: string; lightingStyle: string };
+    };
+    illuminationAnalysis?: {
+      brightness: { overall: number; distribution: string; dynamicRange: number; exposure: string };
+      contrast: { level: number; type: string; areas: { highlights: number; midtones: number; shadows: number } };
+      colorTemperature: { kelvin: number; warmth: string; consistency: number; mixedLighting: boolean };
+      lightQuality: { softness: number; diffusion: number; directionality: number; evenness: number };
+    };
+    shadowAnalysis?: {
+      shadowPresence: { coverage: number; intensity: number; sharpness: string; definition: number };
+      shadowCharacteristics: { direction: string; length: string; density: number; falloff: string };
+      shadowTypes: Array<{ type: 'cast' | 'form' | 'occlusion' | 'contact'; prominence: number; contribution: string }>;
+      dimensionalEffect: { depth: number; volume: number; threedimensionality: number };
+    };
+    ambientAnalysis?: {
+      ambientLevel: { strength: number; uniformity: number; contribution: number };
+      lightingMood: { primary: string; intensity: number; emotional_impact: string[] };
+      atmosphericEffects: { haze: number; glare: number; reflections: number; transparency: number };
+      spatialPerception: { depth_enhancement: number; space_definition: number; focal_guidance: number };
+    };
+  };
+}
+
+// 需求指标接口
+interface RequirementMetric {
+  id: string;
+  category: 'color' | 'space' | 'material';
+  name: string;
+  value: any;
+  unit?: string;
+  range?: { min: number; max: number };
+  description?: string;
+}
+
+// 协作评论接口
+interface CollaborationComment {
+  id: string;
+  author: string;
+  role: 'customer-service' | 'designer' | 'client';
+  content: string;
+  timestamp: Date;
+  requirementId?: string;
+  status: 'pending' | 'resolved';
+}
+
+// 需求项接口
+interface RequirementItem {
+  id: string;
+  title: string;
+  description: string;
+  priority: 'high' | 'medium' | 'low';
+  status: 'pending' | 'confirmed' | 'rejected';
+  tags?: string[];
+  metrics?: RequirementMetric[];
+  comments?: CollaborationComment[];
+  lastUpdated: Date;
+  showComments?: boolean;
+}
+
+// 上传文件接口
+interface UploadedFile {
+  id: string;
+  name: string;
+  url: string;
+  size?: number;
+  type: 'image' | 'cad' | 'text';
+  preview?: string;
+}
+
+@Component({
+  selector: 'app-requirements-confirm-card',
+  standalone: true,
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, UploadSuccessModalComponent, GlobalPromptComponent, FullReportOverlayComponent],
+  providers: [CadAnalysisService],
+  templateUrl: './requirements-confirm-card.html',
+  styleUrls: ['./requirements-confirm-card.scss'],
+  changeDetection: ChangeDetectionStrategy.Default // 确保使用默认变更检测策略
+})
+export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
+  @Input() projectId?: string;
+  @Output() requirementConfirmed = new EventEmitter<RequirementItem>();
+  @Output() progressUpdated = new EventEmitter<number>();
+  @Output() stageCompleted = new EventEmitter<{ stage: string; allStagesCompleted: boolean }>();
+  @Output() dataUpdated = new EventEmitter<any>(); // 新增:实时数据更新事件
+
+  // 表单
+  materialForm!: FormGroup;
+  commentForm!: FormGroup;
+  materialUploadForm!: FormGroup;
+
+  // 数据
+  materialFiles: MaterialFile[] = [];
+  materials: MaterialFile[] = [];
+  requirementMetrics: RequirementMetric[] = [];
+  collaborationComments: CollaborationComment[] = [];
+  requirementItems: RequirementItem[] = [];
+
+  // 状态
+  activeTab: 'materials' | 'mapping' | 'collaboration' | 'progress' = 'materials';
+  isUploading = false;
+  isAnalyzing = false;
+  dragOver = false;
+  showConsistencyWarning = false;
+  warningMessage = '';
+
+  // 自动保存相关状态
+  autoSaveEnabled = true;
+  lastSaveTime?: Date;
+  hasUnsavedChanges = false;
+  isSaving = false;
+  saveStatus: 'saved' | 'saving' | 'error' | 'unsaved' = 'saved';
+
+  // 流程状态
+  stageCompletionStatus = {
+    materialAnalysis: false,
+    requirementMapping: false,
+    collaboration: false,
+    progressReview: false
+  };
+
+  // 自动保存定时器
+  private autoSaveTimer?: any;
+
+  // 需求指标
+  colorIndicators = {
+    mainColor: { r: 255, g: 230, b: 180 },
+    colorTemperature: 2700,
+    colorRange: '暖色调',
+    saturation: 70,
+    brightness: 80,
+    contrast: 60
+  };
+
+  spaceIndicators = {
+    lineRatio: 60,
+    blankRatio: 30,
+    flowWidth: 0.9,
+    aspectRatio: 1.6,
+    ceilingHeight: 2.8,
+    lightingLevel: 300
+  };
+
+  materialIndicators = {
+    fabricRatio: 50,
+    woodRatio: 30,
+    metalRatio: 20,
+    smoothness: 7,
+    glossiness: 4,
+    texture: 6
+  };
+
+  // 指标范围配置
+  indicatorRanges = {
+    color: {
+      r: { min: 0, max: 255 },
+      g: { min: 0, max: 255 },
+      b: { min: 0, max: 255 },
+      temperature: { min: 2000, max: 6500 },
+      saturation: { min: 0, max: 100 },
+      brightness: { min: 0, max: 100 },
+      contrast: { min: 0, max: 100 }
+    },
+    space: {
+      lineRatio: { min: 0, max: 100 },
+      blankRatio: { min: 10, max: 80 },
+      flowWidth: { min: 0.6, max: 1.5 },
+      aspectRatio: { min: 1.0, max: 3.0 },
+      ceilingHeight: { min: 2.4, max: 4.0 },
+      lightingLevel: { min: 100, max: 800 }
+    },
+    material: {
+      fabricRatio: { min: 0, max: 100 },
+      woodRatio: { min: 0, max: 100 },
+      metalRatio: { min: 0, max: 100 },
+      smoothness: { min: 1, max: 10 },
+      glossiness: { min: 1, max: 10 },
+      texture: { min: 1, max: 10 }
+    }
+  };
+
+  // 预设数据
+  presetAtmospheres = [
+    { name: '温馨暖调', rgb: '255,230,180', colorTemp: '2700-3000K', materials: ['木质', '布艺'] },
+    { name: '现代冷调', rgb: '200,220,240', colorTemp: '5000-6000K', materials: ['金属', '玻璃'] },
+    { name: '自然清新', rgb: '220,240,220', colorTemp: '4000-4500K', materials: ['竹木', '石材'] }
+  ];
+
+  // 一致性检查
+  consistencyWarnings: string[] = [];
+  historyStates: any[] = [];
+
+  // 上传成功弹窗相关
+  showUploadSuccessModal = false;
+  uploadedFiles: { id: string; name: string; url: string; size?: number; type?: 'image' | 'cad' | 'text'; preview?: string }[] = [];
+  uploadType: 'image' | 'document' | 'mixed' = 'image';
+  colorAnalysisResult?: ColorAnalysisResult;
+  cadAnalysisResult?: CadAnalysisResult;
+  isAnalyzingColors = false; // 新增:色彩分析状态
+
+  // 全局提示组件状态
+  showGlobalPrompt = false;
+  promptMode: 'fullscreen' | 'corner' = 'corner';
+  promptPosition: 'top-right' | 'bottom-right' = 'bottom-right';
+  promptTitle = '上传成功!';
+  promptMessage = '';
+  // 全屏报告覆盖层
+  showFullReportOverlay = false;
+
+  // 需求映射测试相关属性
+  testSteps = [
+    { id: 'upload', name: '图片上传', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' },
+    { id: 'analysis', name: '图片分析', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' },
+    { id: 'mapping', name: '需求映射', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' },
+    { id: 'preview', name: '氛围预览', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' }
+  ];
+  
+  // 需求映射相关状态
+  isGeneratingMapping = false;
+  requirementMapping: RequirementMapping | null = null;
+  mappingError: string | null = null;
+  analysisError: string | null = null;
+  analysisResult: ColorAnalysisResult | undefined = undefined;
+  
+  // 订阅管理
+  private subscriptions: Subscription[] = [];
+
+  constructor(
+    private fb: FormBuilder,
+    private cdr: ChangeDetectorRef,
+    private colorAnalysisService: ColorAnalysisService,
+    private cadAnalysisService: CadAnalysisService,
+    private requirementMappingService: RequirementMappingService
+  ) {}
+
+  ngOnInit() {
+    this.initializeForms();
+    this.initializeRequirements();
+    this.loadPresetMetrics();
+    
+    // 启用自动保存
+    this.autoSaveEnabled = true;
+    
+    // 定期检查阶段完成状态
+    setInterval(() => {
+      this.checkStageCompletion();
+    }, 5000);
+  }
+
+  ngOnDestroy(): void {
+    // 清理自动保存定时器
+    if (this.autoSaveTimer) {
+      clearTimeout(this.autoSaveTimer);
+    }
+    
+    // 清理订阅
+    this.subscriptions.forEach(sub => sub.unsubscribe());
+    
+    // 清理对象URL
+    this.uploadedFiles.forEach(file => {
+      if (file.url.startsWith('blob:')) {
+        URL.revokeObjectURL(file.url);
+      }
+    });
+  }
+
+  private initializeForms() {
+    this.materialForm = this.fb.group({
+      textContent: ['', Validators.required],
+      atmosphereDescription: [''],
+      residentInfo: [''],
+      scenePreferences: [''],
+      title: [''],
+      content: ['']
+    });
+
+    this.commentForm = this.fb.group({
+      content: ['', Validators.required],
+      requirementId: ['']
+    });
+
+    this.materialUploadForm = this.fb.group({
+      file: [''],
+      textContent: ['', Validators.required]
+    });
+  }
+
+  private initializeRequirements() {
+    this.requirementItems = [
+      {
+        id: 'color-atmosphere',
+        title: '色彩氛围',
+        description: '整体色调和氛围营造',
+        priority: 'high',
+        status: 'pending',
+        lastUpdated: new Date()
+      },
+      {
+        id: 'space-layout',
+        title: '空间布局',
+        description: '功能分区和动线设计',
+        priority: 'high',
+        status: 'pending',
+        lastUpdated: new Date()
+      },
+      {
+        id: 'material-selection',
+        title: '材质选择',
+        description: '主要材质和质感要求',
+        priority: 'medium',
+        status: 'pending',
+        lastUpdated: new Date()
+      }
+    ];
+  }
+
+  private loadPresetMetrics() {
+    this.requirementMetrics = [
+      {
+        id: 'main-color',
+        category: 'color',
+        name: '主色调',
+        value: { r: 255, g: 230, b: 180 },
+        description: '空间主要色彩'
+      },
+      {
+        id: 'color-temp',
+        category: 'color',
+        name: '色温',
+        value: 3000,
+        unit: 'K',
+        range: { min: 2700, max: 6500 },
+        description: '照明色温范围'
+      },
+      {
+        id: 'wood-ratio',
+        category: 'material',
+        name: '木质占比',
+        value: 60,
+        unit: '%',
+        range: { min: 0, max: 100 },
+        description: '木质材料使用比例'
+      }
+    ];
+  }
+
+  // 素材上传功能
+  onFileSelected(event: Event, type: 'text' | 'image' | 'cad') {
+    const input = event.target as HTMLInputElement;
+    if (input.files) {
+      Array.from(input.files).forEach(file => {
+        this.uploadFile(file, type);
+      });
+    }
+  }
+
+  onFileDrop(event: DragEvent, type: 'text' | 'image' | 'cad') {
+    event.preventDefault();
+    this.dragOver = false;
+    
+    if (event.dataTransfer?.files) {
+      Array.from(event.dataTransfer.files).forEach(file => {
+        this.uploadFile(file, type);
+      });
+    }
+  }
+
+  onDragOver(event: DragEvent) {
+    event.preventDefault();
+    this.dragOver = true;
+  }
+
+  onDragLeave(event: DragEvent) {
+    event.preventDefault();
+    this.dragOver = false;
+  }
+
+  private uploadFile(file: File, type: 'text' | 'image' | 'cad') {
+    this.isUploading = true;
+    
+    // 模拟文件上传
+    setTimeout(() => {
+      const materialFile: MaterialFile = {
+        id: this.generateId(),
+        name: file.name,
+        type: type,
+        url: URL.createObjectURL(file),
+        size: this.formatFileSize(file.size),
+        uploadTime: new Date()
+      };
+
+      this.materialFiles.push(materialFile);
+      this.materials.push(materialFile);
+      this.isUploading = false;
+      
+      // 显示全局提示(角落模式,无遮罩,不遮挡操作)
+      this.promptTitle = '上传成功!';
+      this.promptMessage = `已上传文件:${file.name}`;
+      this.promptMode = 'corner';
+      this.promptPosition = 'bottom-right';
+      this.showGlobalPrompt = true;
+
+      // 显示上传成功弹窗
+      this.showUploadSuccessModal = true;
+      this.uploadedFiles = [{
+        id: materialFile.id,
+        name: file.name,
+        url: materialFile.url || '',
+        size: file.size,
+        type: type === 'text' ? 'text' : type === 'image' ? 'image' : 'cad'
+      }];
+      this.uploadType = type === 'text' ? 'document' : type === 'image' ? 'image' : 'mixed';
+      
+      // 自动解析
+      this.analyzeMaterial(materialFile);
+    }, 1000);
+  }
+
+  // 文本提交
+  onTextSubmit(): void {
+    if (this.materialUploadForm.valid) {
+      const formValue = this.materialUploadForm.value;
+      const textMaterial: MaterialFile = {
+        id: this.generateId(),
+        name: '文本需求',
+        type: 'text',
+        content: formValue.textContent,
+        uploadTime: new Date(),
+        size: this.formatFileSize(formValue.textContent?.length || 0)
+      };
+      
+      this.materialFiles.push(textMaterial);
+      this.materials.push(textMaterial);
+      this.analyzeMaterial(textMaterial);
+      this.materialUploadForm.reset();
+    }
+  }
+
+  // 素材解析功能
+  private analyzeMaterial(material: MaterialFile): void {
+    this.isAnalyzing = true;
+    
+    // CAD采用服务分析,其它类型保持模拟
+    if (material.type === 'cad') {
+      const fileUrl = material.url || '';
+      this.cadAnalysisService.analyzeByUrl(fileUrl, material.name).subscribe({
+        next: (result) => {
+          this.cadAnalysisResult = result;
+          const analysis: MaterialAnalysis = {
+            structuralElements: result.structuralElements,
+            spaceMetrics: result.spaceMetrics,
+            flowMetrics: result.flowMetrics
+          };
+          material.analysis = analysis;
+          this.isAnalyzing = false;
+          this.updateRequirementsFromAnalysis(analysis);
+        },
+        error: () => {
+          this.cadAnalysisService.simulateCadAnalysis(material.name).subscribe({
+            next: (mock) => {
+              this.cadAnalysisResult = mock;
+              const analysis: MaterialAnalysis = {
+                structuralElements: mock.structuralElements,
+                spaceMetrics: mock.spaceMetrics,
+                flowMetrics: mock.flowMetrics
+              };
+              material.analysis = analysis;
+              this.isAnalyzing = false;
+              this.updateRequirementsFromAnalysis(analysis);
+            },
+            error: () => {
+              this.isAnalyzing = false;
+              console.error('CAD分析失败');
+            }
+          });
+        }
+      });
+      return;
+    }
+
+    // 非CAD类型模拟
+    setTimeout(() => {
+      let analysis: MaterialAnalysis = {};
+      switch (material.type) {
+        case 'text':
+          analysis = this.analyzeTextMaterial(material);
+          break;
+        case 'image':
+          analysis = this.analyzeImageMaterial(material);
+          break;
+      }
+      material.analysis = analysis;
+      this.isAnalyzing = false;
+      this.updateRequirementsFromAnalysis(analysis);
+    }, 1200);
+  }
+
+  private analyzeTextMaterial(material: MaterialFile): MaterialAnalysis {
+    return {
+      atmosphere: {
+        description: '温馨暖调',
+        rgb: '255,230,180',
+        colorTemp: '2700-3000K'
+      },
+      residents: {
+        type: '亲子家庭',
+        details: '孩子年龄3-6岁'
+      },
+      scenes: {
+        preference: '瑜伽爱好者',
+        requirements: '需要瑜伽区收纳'
+      },
+      keywords: {
+        positive: ['科技感', '温馨', '实用'],
+        negative: ['复杂线条', '冷色调']
+      }
+    };
+  }
+
+  private analyzeImageMaterial(material: MaterialFile): MaterialAnalysis {
+    return {
+      mainColors: [
+        { rgb: '255,230,180', percentage: 45 },
+        { rgb: '200,180,160', percentage: 30 },
+        { rgb: '180,160,140', percentage: 25 }
+      ],
+      colorTemperature: '3000K',
+      materialRatio: [
+        { material: '木质', percentage: 60 },
+        { material: '布艺', percentage: 25 },
+        { material: '金属', percentage: 15 }
+      ],
+      spaceRatio: 35
+    };
+  }
+
+  private analyzeCADMaterial(material: MaterialFile): MaterialAnalysis {
+    return {
+      structuralElements: [
+        { type: '承重柱', position: '客厅中央', changeable: false },
+        { type: '门窗', position: '南墙', changeable: false }
+      ],
+      spaceMetrics: [
+        { room: '客厅', ratio: '16:9', width: '4.2m' },
+        { room: '卧室', ratio: '4:3', width: '3.6m' }
+      ],
+      flowMetrics: [
+        { area: '主通道', width: '1.2m', compliance: true },
+        { area: '次通道', width: '0.8m', compliance: false }
+      ]
+    };
+  }
+
+  private updateRequirementsFromAnalysis(analysis: MaterialAnalysis) {
+    if (analysis.atmosphere?.rgb) {
+      const colorMetric = this.requirementMetrics.find(m => m.id === 'main-color');
+      if (colorMetric) {
+        const [r, g, b] = analysis.atmosphere.rgb.split(',').map(Number);
+        colorMetric.value = { r, g, b };
+      }
+    }
+    
+    if (analysis.materialRatio) {
+      const woodRatio = analysis.materialRatio.find(m => m.material === '木质');
+      if (woodRatio) {
+        const woodMetric = this.requirementMetrics.find(m => m.id === 'wood-ratio');
+        if (woodMetric) {
+          woodMetric.value = woodRatio.percentage;
+        }
+      }
+    }
+  }
+
+  // 需求确认功能
+  confirmRequirement(requirementId: string) {
+    // 检查是否可以进行需求确认操作
+    if (!this.canProceedToNextStage('materialAnalysis')) {
+      alert('请先完成素材分析阶段的所有必要操作');
+      return;
+    }
+    
+    const requirement = this.requirementItems.find(r => r.id === requirementId);
+    if (requirement) {
+      requirement.status = 'confirmed';
+      requirement.lastUpdated = new Date();
+      this.requirementConfirmed.emit(requirement);
+      this.updateProgress();
+      this.triggerAutoSave();
+      
+      // 检查阶段完成状态
+      this.checkStageCompletion();
+    }
+  }
+
+  rejectRequirement(requirementId: string, reason?: string) {
+    // 检查是否可以进行需求拒绝操作
+    if (!this.canProceedToNextStage('materialAnalysis')) {
+      alert('请先完成素材分析阶段的所有必要操作');
+      return;
+    }
+    
+    const requirement = this.requirementItems.find(r => r.id === requirementId);
+    if (requirement) {
+      requirement.status = 'rejected';
+      requirement.lastUpdated = new Date();
+      
+      if (reason) {
+        this.addCommentToRequirement(requirementId, reason, 'system');
+      }
+      
+      this.updateProgress();
+      this.triggerAutoSave();
+      
+      // 检查阶段完成状态
+      this.checkStageCompletion();
+    }
+  }
+
+  // 协作功能
+  addComment() {
+    if (this.commentForm.valid) {
+      const comment: CollaborationComment = {
+        id: this.generateId(),
+        author: '当前用户',
+        role: 'designer',
+        content: this.commentForm.value.content,
+        timestamp: new Date(),
+        requirementId: this.commentForm.value.requirementId,
+        status: 'pending'
+      };
+      
+      this.collaborationComments.push(comment);
+      this.commentForm.reset();
+      this.triggerAutoSave();
+      
+      // 检查阶段完成状态
+      this.checkStageCompletion();
+    }
+  }
+
+  resolveComment(commentId: string) {
+    const comment = this.collaborationComments.find(c => c.id === commentId);
+    if (comment) {
+      comment.status = 'resolved';
+      this.triggerAutoSave();
+      
+      // 检查阶段完成状态
+      this.checkStageCompletion();
+    }
+  }
+
+  // 进度管理
+  private updateProgress() {
+    const totalRequirements = this.requirementItems.length;
+    const confirmedRequirements = this.requirementItems.filter(r => r.status === 'confirmed').length;
+    const progress = totalRequirements > 0 ? (confirmedRequirements / totalRequirements) * 100 : 0;
+    
+    // 实时更新进度条
+    this.updateProgressBar(progress);
+    this.progressUpdated.emit(progress);
+    
+    // 检查是否完成所有需求
+    if (progress === 100) {
+      this.onRequirementCommunicationComplete();
+    }
+  }
+
+  private updateProgressBar(progress: number): void {
+    // 更新进度条显示
+    const progressBar = document.querySelector('.progress-bar-fill') as HTMLElement;
+    if (progressBar) {
+      progressBar.style.width = `${progress}%`;
+    }
+    
+    // 更新进度文本
+    const progressText = document.querySelector('.progress-text') as HTMLElement;
+    if (progressText) {
+      progressText.textContent = `${Math.round(progress)}%`;
+    }
+    
+    // 添加进度动画效果
+    this.animateProgressUpdate(progress);
+  }
+
+  private animateProgressUpdate(progress: number): void {
+    // 添加进度更新的视觉反馈
+    const progressContainer = document.querySelector('.progress-container') as HTMLElement;
+    if (progressContainer) {
+      progressContainer.classList.add('progress-updated');
+      setTimeout(() => {
+        progressContainer.classList.remove('progress-updated');
+      }, 500);
+    }
+  }
+
+  // 需求沟通阶段完成处理
+  private onRequirementCommunicationComplete(): void {
+    console.log('需求沟通阶段完成,开始同步关键信息');
+    this.syncKeyInfoToSolutionConfirmation();
+  }
+
+  // 同步关键信息到方案确认阶段
+  private syncKeyInfoToSolutionConfirmation(): void {
+    const keyInfo = {
+      colorAtmosphere: {
+        mainColor: this.colorIndicators.mainColor,
+        colorTemperature: this.colorIndicators.colorTemperature,
+        saturation: this.colorIndicators.saturation,
+        brightness: this.colorIndicators.brightness
+      },
+      spaceStructure: {
+        lineRatio: this.spaceIndicators.lineRatio,
+        blankRatio: this.spaceIndicators.blankRatio,
+        flowWidth: this.spaceIndicators.flowWidth
+      },
+      materialWeights: {
+        fabricRatio: this.materialIndicators.fabricRatio,
+        woodRatio: this.materialIndicators.woodRatio,
+        metalRatio: this.materialIndicators.metalRatio,
+        smoothness: this.materialIndicators.smoothness
+      },
+      presetAtmosphere: this.getSelectedPresetAtmosphere()
+    };
+
+    // 触发同步事件
+    this.requirementConfirmed.emit({
+      id: 'requirement-sync',
+      title: '需求沟通完成',
+      description: '关键信息已同步至方案确认阶段',
+      priority: 'high',
+      status: 'confirmed',
+      lastUpdated: new Date(),
+      metrics: this.convertToMetrics(keyInfo)
+    });
+
+    // 启动交付执行流程
+    setTimeout(() => {
+      this.startDeliveryExecution();
+    }, 1000);
+  }
+
+  private getSelectedPresetAtmosphere(): any {
+    // 根据当前颜色指标找到最匹配的预设氛围
+    const currentRgb = `${this.colorIndicators.mainColor.r},${this.colorIndicators.mainColor.g},${this.colorIndicators.mainColor.b}`;
+    return this.presetAtmospheres.find(preset => preset.rgb === currentRgb) || this.presetAtmospheres[0];
+  }
+
+  private convertToMetrics(keyInfo: any): RequirementMetric[] {
+    return [
+      {
+        id: 'color-sync',
+        category: 'color',
+        name: '色彩氛围',
+        value: keyInfo.colorAtmosphere
+      },
+      {
+        id: 'space-sync',
+        category: 'space',
+        name: '空间结构',
+        value: keyInfo.spaceStructure
+      },
+      {
+        id: 'material-sync',
+        category: 'material',
+        name: '材质权重',
+        value: keyInfo.materialWeights
+      }
+    ];
+  }
+
+  // 启动交付执行流程
+  private startDeliveryExecution(): void {
+    console.log('启动交付执行流程');
+    
+    // 显示执行启动提示
+    this.showExecutionStartNotification();
+    
+    // 这里可以触发路由跳转或其他业务逻辑
+    // 例如:this.router.navigate(['/project', this.projectId, 'execution']);
+  }
+
+  private showExecutionStartNotification(): void {
+    // 显示执行流程启动的通知
+    const notification = document.createElement('div');
+    notification.className = 'execution-notification';
+    notification.innerHTML = `
+      <div class="notification-content">
+        <svg viewBox="0 0 24 24" fill="currentColor">
+          <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
+        </svg>
+        <span>交付执行流程已启动</span>
+      </div>
+    `;
+    
+    document.body.appendChild(notification);
+    
+    setTimeout(() => {
+      notification.remove();
+    }, 3000);
+  }
+
+  getProgressPercentage(): number {
+    if (this.requirementItems.length === 0) return 0;
+    const confirmedCount = this.requirementItems.filter(r => r.status === 'confirmed').length;
+    return Math.round((confirmedCount / this.requirementItems.length) * 100);
+  }
+
+  // 处理优先级更新
+  onPriorityChange(requirementId: string, event: Event): void {
+    const target = event.target as HTMLSelectElement;
+    this.updateRequirementPriority(requirementId, target.value as 'high' | 'medium' | 'low');
+  }
+
+  // 检查需求是否有待处理评论
+  hasUnreadComments(requirementId: string): boolean {
+    return this.getCommentsForRequirement(requirementId).some(c => c.status === 'pending');
+  }
+
+  // 处理历史状态选择
+  onHistoryStateChange(event: Event): void {
+    const target = event.target as HTMLSelectElement;
+    const index = +target.value;
+    if (index >= 0) {
+      this.restoreHistoryState(index);
+    }
+  }
+
+  // 获取指定状态的需求数量
+  getRequirementCountByStatus(status: 'confirmed' | 'pending' | 'rejected'): number {
+    return (this.requirementItems || []).filter(r => r.status === status).length;
+  }
+
+  // 获取状态百分比
+  getStatusPercentage(status: 'confirmed' | 'pending' | 'rejected'): number {
+    const total = this.requirementItems.length;
+    if (total === 0) return 0;
+    
+    const statusCount = this.getRequirementCountByStatus(status);
+    return Math.round((statusCount / total) * 100);
+  }
+
+  // 指标映射功能
+  updateMetricValue(metricId: string, value: any) {
+    const metric = this.requirementMetrics.find(m => m.id === metricId);
+    if (metric) {
+      metric.value = value;
+      this.checkConsistency();
+    }
+  }
+
+  // 滑动条数值同步方法
+  onSliderChange(key: string, value: number): void {
+    console.log(`滑动条变化: ${key} = ${value}`);
+    console.log('变化前的mainColor:', JSON.stringify(this.colorIndicators.mainColor));
+    
+    // 直接更新数据,不需要额外的updateIndicatorValue调用
+    // 因为使用了双向绑定,数据已经自动更新
+    if (key === 'r' || key === 'g' || key === 'b') {
+      // 确保RGB值在有效范围内
+      const range = this.getIndicatorRange(key);
+      if (range) {
+        const clampedValue = Math.max(range.min, Math.min(range.max, value));
+        this.colorIndicators.mainColor[key as keyof typeof this.colorIndicators.mainColor] = clampedValue;
+        console.log(`RGB值已更新: ${key} = ${clampedValue}`);
+        console.log('变化后的mainColor:', JSON.stringify(this.colorIndicators.mainColor));
+      }
+      
+      // 触发颜色指标更新
+      this.updateColorIndicator('mainColor', this.colorIndicators.mainColor);
+    } else {
+      // 处理其他颜色指标
+      this.updateIndicatorValue(key, value);
+    }
+    
+    this.triggerAutoSave(); // 触发自动保存
+    this.checkStageCompletion(); // 检查阶段完成状态
+    
+    // 发射实时数据更新事件
+    this.emitDataUpdate();
+    
+    // 强制触发变更检测
+    this.cdr.detectChanges();
+    console.log('滑动条变更检测已触发');
+  }
+
+  onInputChange(key: string, value: number): void {
+    console.log(`输入框变化: ${key} = ${value}`);
+    console.log('变化前的mainColor:', JSON.stringify(this.colorIndicators.mainColor));
+    
+    // 直接更新数据,不需要额外的updateIndicatorValue调用
+    // 因为使用了双向绑定,数据已经自动更新
+    if (key === 'r' || key === 'g' || key === 'b') {
+      // 确保RGB值在有效范围内
+      const range = this.getIndicatorRange(key);
+      if (range) {
+        const clampedValue = Math.max(range.min, Math.min(range.max, value));
+        this.colorIndicators.mainColor[key as keyof typeof this.colorIndicators.mainColor] = clampedValue;
+        console.log(`RGB值已更新: ${key} = ${clampedValue}`);
+        console.log('变化后的mainColor:', JSON.stringify(this.colorIndicators.mainColor));
+      }
+      
+      // 触发颜色指标更新
+      this.updateColorIndicator('mainColor', this.colorIndicators.mainColor);
+    } else {
+      // 处理其他颜色指标
+      this.updateIndicatorValue(key, value);
+    }
+    
+    this.triggerAutoSave();
+    
+    // 发射实时数据更新事件
+    this.emitDataUpdate();
+    
+    // 强制触发变更检测
+    this.cdr.detectChanges();
+    console.log('输入框变更检测已触发');
+  }
+
+  validateInput(key: string, value: string): void {
+    const numValue = parseFloat(value);
+    if (isNaN(numValue)) {
+      // 如果输入无效,恢复原值
+      this.restoreIndicatorValue(key);
+      return;
+    }
+    
+    const range = this.getIndicatorRange(key);
+    if (range) {
+      const clampedValue = Math.max(range.min, Math.min(range.max, numValue));
+      this.updateIndicatorValue(key, clampedValue);
+    }
+  }
+
+  private updateIndicatorValue(key: string, value: number): void {
+    console.log(`updateIndicatorValue调用: ${key} = ${value}`);
+    
+    // 更新颜色指标
+    if (key in this.colorIndicators) {
+      if (key === 'r' || key === 'g' || key === 'b') {
+        // 创建新的mainColor对象以确保变更检测
+        const oldColor = { ...this.colorIndicators.mainColor };
+        this.colorIndicators.mainColor = {
+          ...this.colorIndicators.mainColor,
+          [key]: value
+        };
+        console.log(`RGB更新: ${key}从${oldColor[key as keyof typeof oldColor]}变为${value}`, 
+                   '新mainColor:', this.colorIndicators.mainColor);
+        this.updateColorIndicator('mainColor', this.colorIndicators.mainColor);
+      } else {
+        (this.colorIndicators as any)[key] = value;
+        this.updateColorIndicator(key, value);
+      }
+      return;
+    }
+
+    // 更新空间指标
+    if (key in this.spaceIndicators) {
+      (this.spaceIndicators as any)[key] = value;
+      this.updateSpaceIndicator(key, value);
+      return;
+    }
+
+    // 更新材质指标
+    if (key in this.materialIndicators) {
+      (this.materialIndicators as any)[key] = value;
+      this.updateMaterialIndicator(key, value);
+      return;
+    }
+  }
+
+  private restoreIndicatorValue(key: string): void {
+    // 这里可以从历史状态或默认值恢复
+    console.log(`恢复指标值: ${key}`);
+  }
+
+  private getIndicatorRange(key: string): { min: number; max: number } | null {
+    // 检查颜色范围
+    if (key === 'r' || key === 'g' || key === 'b') {
+      return this.indicatorRanges.color[key as keyof typeof this.indicatorRanges.color];
+    }
+    if (key in this.indicatorRanges.color) {
+      return (this.indicatorRanges.color as any)[key];
+    }
+
+    // 检查空间范围
+    if (key in this.indicatorRanges.space) {
+      return (this.indicatorRanges.space as any)[key];
+    }
+
+    // 检查材质范围
+    if (key in this.indicatorRanges.material) {
+      return (this.indicatorRanges.material as any)[key];
+    }
+
+    return null;
+  }
+
+  // 指标更新方法
+  updateColorIndicator(type: string, value?: any): void {
+    switch (type) {
+      case 'mainColor':
+        if (value && typeof value === 'object' && 'r' in value) {
+          this.colorIndicators.mainColor = { ...value }; // 创建新对象以触发变更检测
+          console.log('mainColor更新后:', this.colorIndicators.mainColor);
+        }
+        break;
+      case 'colorTemperature':
+        // 色温更新时不需要改变颜色值,只是触发一致性检查
+        break;
+      case 'saturation':
+        // 饱和度更新时不需要改变颜色值,只是触发一致性检查
+        break;
+    }
+    
+    console.log(`更新颜色指示器 ${type}:`, value, '当前RGB:', this.getRgbString());
+    this.checkConsistency();
+    this.updatePreview();
+    // 强制触发变更检测
+    this.cdr.detectChanges();
+    console.log('变更检测已触发');
+  }
+
+  updateSpaceIndicator(key: string, value: number) {
+    if (key in this.spaceIndicators) {
+      (this.spaceIndicators as any)[key] = value;
+      this.checkConsistency();
+    }
+  }
+
+  updateMaterialIndicator(key: string, value: number) {
+    if (key in this.materialIndicators) {
+      (this.materialIndicators as any)[key] = value;
+      this.checkConsistency();
+      this.normalizeMaterialRatios();
+    }
+  }
+
+  // 材质比例归一化
+  private normalizeMaterialRatios() {
+    const total = this.materialIndicators.fabricRatio + 
+                  this.materialIndicators.woodRatio + 
+                  this.materialIndicators.metalRatio;
+    
+    if (total > 100) {
+      const scale = 100 / total;
+      this.materialIndicators.fabricRatio = Math.round(this.materialIndicators.fabricRatio * scale);
+      this.materialIndicators.woodRatio = Math.round(this.materialIndicators.woodRatio * scale);
+      this.materialIndicators.metalRatio = 100 - this.materialIndicators.fabricRatio - this.materialIndicators.woodRatio;
+    }
+  }
+
+  // 预览更新
+  private updatePreview() {
+    // 移除直接DOM操作,依赖Angular的属性绑定
+    console.log('updatePreview调用,当前RGB:', this.getRgbString());
+    // 不再直接操作DOM,让Angular的变更检测处理
+  }
+
+  // 获取RGB字符串
+  getRgbString(): string {
+    const { r, g, b } = this.colorIndicators.mainColor;
+    const rgbString = `rgb(${r}, ${g}, ${b})`;
+    console.log('getRgbString调用:', rgbString, '完整mainColor对象:', this.colorIndicators.mainColor);
+    return rgbString;
+  }
+
+  // 获取色温描述
+  getColorTemperatureDescription(): string {
+    const temp = this.colorIndicators.colorTemperature;
+    if (temp < 3000) return '暖色调';
+    if (temp < 4000) return '中性色调';
+    if (temp < 5000) return '自然色调';
+    return '冷色调';
+  }
+
+  applyPresetAtmosphere(preset: any) {
+    const rgbMatch = preset.rgb.match(/(\d+),(\d+),(\d+)/);
+    if (rgbMatch) {
+      this.colorIndicators.mainColor = {
+        r: parseInt(rgbMatch[1]),
+        g: parseInt(rgbMatch[2]),
+        b: parseInt(rgbMatch[3])
+      };
+    }
+
+    const tempMatch = preset.colorTemp.match(/(\d+)/);
+    if (tempMatch) {
+      this.colorIndicators.colorTemperature = parseInt(tempMatch[1]);
+    }
+
+    if (preset.materials.includes('木质')) {
+      this.materialIndicators.woodRatio = 60;
+      this.materialIndicators.fabricRatio = 30;
+      this.materialIndicators.metalRatio = 10;
+    } else if (preset.materials.includes('金属')) {
+      this.materialIndicators.metalRatio = 50;
+      this.materialIndicators.woodRatio = 20;
+      this.materialIndicators.fabricRatio = 30;
+    }
+
+    this.updatePreview();
+    this.checkConsistency();
+  }
+
+  // 一致性检查
+  private checkConsistency() {
+    this.consistencyWarnings = [];
+    
+    if (this.colorIndicators.colorTemperature < 3000 && 
+        this.colorIndicators.mainColor.r > 200 && 
+        this.colorIndicators.mainColor.g < 150) {
+      this.consistencyWarnings.push('暖调氛围与冷色调RGB值存在矛盾');
+    }
+    
+    if (this.materialIndicators.fabricRatio > 70 && this.colorIndicators.colorTemperature > 5000) {
+      this.consistencyWarnings.push('高布艺占比与冷色调可能不协调');
+    }
+    
+    if (this.spaceIndicators.lineRatio > 80 && this.materialIndicators.woodRatio > 60) {
+      this.consistencyWarnings.push('高直线条占比与高木质占比可能过于刚硬');
+    }
+  }
+
+  // 协作批注功能
+  addCommentToRequirement(requirementId: string, content: string, author: string = '当前用户'): void {
+    const comment: CollaborationComment = {
+      id: this.generateId(),
+      author,
+      role: 'designer',
+      content,
+      timestamp: new Date(),
+      requirementId,
+      status: 'pending'
+    };
+    
+    this.collaborationComments.push(comment);
+    this.updateProgress();
+  }
+
+  // 优先级排序功能
+  updateRequirementPriority(requirementId: string, priority: 'high' | 'medium' | 'low'): void {
+    const requirement = this.requirementItems.find(r => r.id === requirementId);
+    if (requirement) {
+      requirement.priority = priority;
+      requirement.lastUpdated = new Date();
+      this.sortRequirementsByPriority();
+    }
+  }
+
+  sortRequirementsByPriority(): void {
+    const priorityOrder = { 'high': 3, 'medium': 2, 'low': 1 };
+    this.requirementItems.sort((a, b) => {
+      return priorityOrder[b.priority] - priorityOrder[a.priority];
+    });
+  }
+
+  // 历史记录功能
+  saveCurrentState(): void {
+    const currentState = {
+      timestamp: new Date(),
+      colorIndicators: { ...this.colorIndicators },
+      spaceIndicators: { ...this.spaceIndicators },
+      materialIndicators: { ...this.materialIndicators },
+      requirementItems: this.requirementItems.map((r: RequirementItem) => ({ ...r })),
+      author: '当前用户'
+    };
+    
+    this.historyStates.push(currentState);
+    
+    if (this.historyStates.length > 10) {
+      this.historyStates.shift();
+    }
+  }
+
+  restoreHistoryState(index: number): void {
+    if (index >= 0 && index < this.historyStates.length) {
+      const state = this.historyStates[index];
+      this.colorIndicators = { ...state.colorIndicators };
+      this.spaceIndicators = { ...state.spaceIndicators };
+      this.materialIndicators = { ...state.materialIndicators };
+      this.requirementItems = state.requirementItems.map((r: RequirementItem) => ({ ...r }));
+      
+      this.updatePreview();
+      this.checkConsistency();
+    }
+  }
+
+  // 获取未解决的评论数量
+  getUnresolvedCommentsCount(): number {
+    return this.collaborationComments.filter(c => c.status === 'pending').length;
+  }
+
+  // 获取需求的评论
+  getCommentsForRequirement(requirementId: string): CollaborationComment[] {
+    return this.collaborationComments.filter(comment => comment.requirementId === requirementId) || [];
+  }
+
+  // 获取状态样式类
+  getStatusClass(status: string): string {
+    return status;
+  }
+
+  // 工具方法
+  private generateId(): string {
+    return Math.random().toString(36).substr(2, 9);
+  }
+
+  private formatFileSize(bytes: number): string {
+    if (bytes === 0) return '0 Bytes';
+    const k = 1024;
+    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+    const i = Math.floor(Math.log(bytes) / Math.log(k));
+    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+  }
+
+  // 删除素材
+  removeMaterial(materialId: string) {
+    const index = this.materialFiles.findIndex(m => m.id === materialId);
+    if (index > -1) {
+      const material = this.materialFiles[index];
+      if (material.url) {
+        URL.revokeObjectURL(material.url);
+      }
+      this.materialFiles.splice(index, 1);
+    }
+    
+    const materialsIndex = this.materials.findIndex(m => m.id === materialId);
+    if (materialsIndex > -1) {
+      this.materials.splice(materialsIndex, 1);
+    }
+  }
+
+  // 切换标签页
+  switchTab(tab: 'materials' | 'mapping' | 'collaboration' | 'progress') {
+    this.activeTab = tab;
+  }
+
+  // 获取需求状态文本
+  getRequirementStatusText(status: string): string {
+    const statusMap: { [key: string]: string } = {
+      'pending': '待确认',
+      'confirmed': '已确认',
+      'rejected': '已拒绝'
+    };
+    return statusMap[status] || status;
+  }
+
+  // 获取优先级文本
+  getPriorityText(priority: string): string {
+    const priorityMap: { [key: string]: string } = {
+      'high': '高',
+      'medium': '中',
+      'low': '低'
+    };
+    return priorityMap[priority] || priority;
+  }
+
+  // 刷新进度(保留用于手动刷新,但不再强制要求)
+  refreshProgress(): void {
+    // 重新计算进度
+    this.updateProgress();
+    
+    // 更新进度条显示
+    const progress = this.getProgressPercentage();
+    this.updateProgressBar(progress);
+    
+    // 检查阶段完成状态
+    this.checkStageCompletion();
+    
+    // 触发进度更新事件
+    this.progressUpdated.emit(progress);
+    
+    console.log('进度已手动刷新:', progress + '%');
+  }
+
+  // 自动保存功能
+  private triggerAutoSave(): void {
+    if (!this.autoSaveEnabled) return;
+    
+    this.hasUnsavedChanges = true;
+    this.saveStatus = 'unsaved';
+    
+    // 清除之前的定时器
+    if (this.autoSaveTimer) {
+      clearTimeout(this.autoSaveTimer);
+    }
+    
+    // 设置新的自动保存定时器(延迟1秒)
+    this.autoSaveTimer = setTimeout(() => {
+      this.performAutoSave();
+    }, 1000);
+  }
+
+  private async performAutoSave(): Promise<void> {
+    if (this.isSaving) return;
+    
+    this.isSaving = true;
+    this.saveStatus = 'saving';
+    
+    try {
+      // 收集所有需要保存的数据
+      const saveData = this.collectSaveData();
+      
+      // 模拟保存操作(实际项目中这里会调用API)
+      await this.saveToServer(saveData);
+      
+      this.hasUnsavedChanges = false;
+      this.saveStatus = 'saved';
+      this.lastSaveTime = new Date();
+      
+      console.log('自动保存成功:', new Date().toLocaleTimeString());
+    } catch (error) {
+      this.saveStatus = 'error';
+      console.error('自动保存失败:', error);
+    } finally {
+      this.isSaving = false;
+    }
+  }
+
+  // 手动保存
+  async manualSave(): Promise<void> {
+    if (this.isSaving) return;
+    
+    // 清除自动保存定时器
+    if (this.autoSaveTimer) {
+      clearTimeout(this.autoSaveTimer);
+    }
+    
+    await this.performAutoSave();
+  }
+
+  private collectSaveData(): any {
+    return {
+      projectId: this.projectId,
+      colorIndicators: this.colorIndicators,
+      spaceIndicators: this.spaceIndicators,
+      materialIndicators: this.materialIndicators,
+      requirementItems: this.requirementItems,
+      requirementMetrics: this.requirementMetrics,
+      materialFiles: this.materialFiles,
+      collaborationComments: this.collaborationComments,
+      stageCompletionStatus: this.stageCompletionStatus,
+      lastUpdated: new Date()
+    };
+  }
+
+  private async saveToServer(data: any): Promise<void> {
+    // 模拟API调用延迟
+    return new Promise((resolve, reject) => {
+      setTimeout(() => {
+        // 模拟90%成功率
+        if (Math.random() > 0.1) {
+          resolve();
+        } else {
+          reject(new Error('网络错误'));
+        }
+      }, 500);
+    });
+  }
+
+  // 检查阶段完成状态
+  private checkStageCompletion(): void {
+    // 检查素材分析阶段 - 需要有素材文件且都已分析
+    this.stageCompletionStatus.materialAnalysis = this.materialFiles.length > 0 && 
+      this.materialFiles.every(m => m.analysis);
+    
+    // 检查需求映射阶段 - 需要有确认的需求项,或者滑动条数据已调整且保存
+    const hasConfirmedRequirements = this.requirementItems.length > 0 && 
+      this.requirementItems.some(r => r.status === 'confirmed');
+    const hasAdjustedIndicators = this.hasIndicatorChanges();
+    this.stageCompletionStatus.requirementMapping = hasConfirmedRequirements || 
+      (hasAdjustedIndicators && this.saveStatus === 'saved');
+    
+    // 检查协作验证阶段 - 需要有协作评论或需求评论
+    this.stageCompletionStatus.collaboration = this.collaborationComments.length > 0 || 
+      this.requirementItems.some(r => r.comments && r.comments.length > 0);
+    
+    // 检查进度审查阶段 - 需要进度达到80%以上
+    this.stageCompletionStatus.progressReview = this.getProgressPercentage() >= 80;
+    
+    console.log('阶段完成状态更新:', this.stageCompletionStatus);
+    
+    // 检查是否所有阶段都已完成
+    const allStagesCompleted = Object.values(this.stageCompletionStatus).every(status => status);
+    
+    // 发射阶段完成事件
+    if (allStagesCompleted) {
+      this.stageCompleted.emit({ stage: 'requirements-communication', allStagesCompleted: true });
+    }
+  }
+
+  // 检查指示器是否有变化
+  private hasIndicatorChanges(): boolean {
+    // 检查颜色指示器是否有变化(与默认值比较)
+    const defaultColorIndicators = {
+      mainColor: { r: 255, g: 230, b: 180 },
+      colorTemperature: 2700,
+      colorRange: '暖色调',
+      saturation: 70,
+      brightness: 80,
+      contrast: 60
+    };
+    
+    const defaultSpaceIndicators = {
+      lineRatio: 60,
+      blankRatio: 30,
+      flowWidth: 0.9,
+      aspectRatio: 1.6,
+      ceilingHeight: 2.8,
+      lightingLevel: 300
+    };
+    
+    const defaultMaterialIndicators = {
+      fabricRatio: 50,
+      woodRatio: 30,
+      metalRatio: 20,
+      smoothness: 7,
+      glossiness: 4,
+      texture: 6
+    };
+    
+    // 检查颜色指示器变化
+    const colorChanged = JSON.stringify(this.colorIndicators) !== JSON.stringify(defaultColorIndicators);
+    
+    // 检查空间指示器变化
+    const spaceChanged = JSON.stringify(this.spaceIndicators) !== JSON.stringify(defaultSpaceIndicators);
+    
+    // 检查材质指示器变化
+    const materialChanged = JSON.stringify(this.materialIndicators) !== JSON.stringify(defaultMaterialIndicators);
+    
+    return colorChanged || spaceChanged || materialChanged;
+  }
+
+  // 获取阶段状态文本
+  getStageStatusText(stage: keyof typeof this.stageCompletionStatus): string {
+    if (this.stageCompletionStatus[stage]) {
+      return '已完成';
+    } else {
+      // 检查是否为未开始状态
+      const stages = ['materialAnalysis', 'requirementMapping', 'collaboration', 'progressReview'];
+      const currentIndex = stages.indexOf(stage);
+      
+      // 检查前面的阶段是否都已完成
+      let allPreviousCompleted = true;
+      for (let i = 0; i < currentIndex; i++) {
+        if (!this.stageCompletionStatus[stages[i] as keyof typeof this.stageCompletionStatus]) {
+          allPreviousCompleted = false;
+          break;
+        }
+      }
+      
+      return allPreviousCompleted ? '进行中' : '未进行';
+    }
+  }
+
+  // 获取阶段状态类名
+  getStageStatusClass(stage: keyof typeof this.stageCompletionStatus): string {
+    if (this.stageCompletionStatus[stage]) {
+      return 'stage-completed';
+    } else {
+      // 检查是否为未开始状态
+      const stages = ['materialAnalysis', 'requirementMapping', 'collaboration', 'progressReview'];
+      const currentIndex = stages.indexOf(stage);
+      
+      // 检查前面的阶段是否都已完成
+      let allPreviousCompleted = true;
+      for (let i = 0; i < currentIndex; i++) {
+        if (!this.stageCompletionStatus[stages[i] as keyof typeof this.stageCompletionStatus]) {
+          allPreviousCompleted = false;
+          break;
+        }
+      }
+      
+      return allPreviousCompleted ? 'stage-in-progress' : 'stage-pending';
+    }
+  }
+
+  // 检查是否可以进入下一阶段
+  canProceedToNextStage(currentStage: keyof typeof this.stageCompletionStatus): boolean {
+    const stages = Object.keys(this.stageCompletionStatus) as (keyof typeof this.stageCompletionStatus)[];
+    const currentIndex = stages.indexOf(currentStage);
+    
+    // 检查当前阶段之前的所有阶段是否都已完成
+    for (let i = 0; i <= currentIndex; i++) {
+      if (!this.stageCompletionStatus[stages[i]]) {
+        return false;
+      }
+    }
+    
+    return true;
+  }
+
+  // 获取保存状态文本
+  getSaveStatusText(): string {
+    switch (this.saveStatus) {
+      case 'saved': return this.lastSaveTime ? `已保存 ${this.lastSaveTime.toLocaleTimeString()}` : '已保存';
+      case 'saving': return '保存中...';
+      case 'error': return '保存失败';
+      case 'unsaved': return '有未保存的更改';
+      default: return '';
+    }
+  }
+
+  // 获取保存状态图标
+  getSaveStatusIcon(): string {
+    switch (this.saveStatus) {
+      case 'saved': return '✓';
+      case 'saving': return '⏳';
+      case 'error': return '⚠';
+      case 'unsaved': return '●';
+      default: return '';
+    }
+  }
+
+  // 上传成功弹窗相关方法
+  onModalClose(): void {
+    this.showUploadSuccessModal = false;
+    this.uploadedFiles = [];
+    this.colorAnalysisResult = undefined;
+  }
+
+  // 全局提示关闭
+  onPromptClose(): void {
+    this.showGlobalPrompt = false;
+  }
+
+  onAnalyzeColors(): void {
+    if (this.uploadedFiles.length > 0 && this.uploadType === 'image') {
+      const imageFile = this.uploadedFiles[0];
+      
+      // 设置分析状态为true
+      this.isAnalyzingColors = true;
+      
+      // 从URL获取文件数据并创建File对象
+      fetch(imageFile.url)
+        .then(response => {
+          if (!response.ok) {
+            throw new Error(`HTTP error! status: ${response.status}`);
+          }
+          return response.blob();
+        })
+        .then(blob => {
+          // 创建包含实际文件数据的File对象
+          const file = new File([blob], imageFile.name, { type: imageFile.type || 'image/jpeg' });
+          
+          // 使用模拟分析(在实际应用中应该调用真实的API)
+          this.colorAnalysisService.simulateAnalysis(file).subscribe({
+            next: (result) => {
+              this.colorAnalysisResult = result;
+              this.isAnalyzingColors = false; // 分析完成,设置状态为false
+              
+              // 可以在这里更新颜色指标
+              if (result.colors.length > 0) {
+                const mainColor = result.colors[0];
+                this.updateColorIndicator('mainColor', mainColor.rgb);
+              }
+              // 新增:分析完成后向父级发射数据更新,包含色彩分析结果
+              this.emitDataUpdate();
+            },
+            error: (error) => {
+              console.error('颜色分析失败:', error);
+              this.isAnalyzingColors = false; // 分析失败,也要重置状态
+            }
+          });
+        })
+        .catch(error => {
+          console.error('获取文件数据失败:', error);
+          this.isAnalyzingColors = false; // 获取文件失败,重置状态
+        });
+    }
+  }
+
+  onViewReport(): void {
+    console.log('onViewReport被调用,当前colorAnalysisResult:', this.colorAnalysisResult);
+    // 打开全屏报告覆盖层
+    this.showFullReportOverlay = true;
+    
+    // 确保色彩分析结果传递给父组件用于右侧展示
+    if (this.colorAnalysisResult) {
+      console.log('调用emitDataUpdate传递色彩分析结果');
+      this.emitDataUpdate();
+    } else {
+      console.log('没有色彩分析结果可传递');
+    }
+  }
+
+  onCloseFullReport(): void {
+    this.showFullReportOverlay = false;
+  }
+
+  // 新增:发射实时数据更新事件的方法
+  private emitDataUpdate(): void {
+    // 收集所有材料的详细分析数据
+    const materialAnalysisData = this.materials.map(material => ({
+      id: material.id,
+      name: material.name,
+      type: material.type,
+      analysis: material.analysis || {}
+    }));
+
+    const currentData = {
+      colorIndicators: this.colorIndicators || [],
+      spaceIndicators: this.spaceIndicators || [],
+      materialIndicators: this.materialIndicators || [],
+      requirementItems: this.requirementItems || [],
+      materials: this.materials || [],
+      collaborationComments: this.collaborationComments || '',
+      stageCompletionStatus: this.stageCompletionStatus || {},
+      // 传递色彩分析结果到父级用于右侧展示
+      colorAnalysisResult: this.colorAnalysisResult,
+      // 新增:传递完整的材料分析数据,包含色彩、形体、质感、纹理、灯光分析
+      materialAnalysisData: materialAnalysisData,
+      // 新增:传递详细的分析结果
+      detailedAnalysis: {
+        enhancedColorAnalysis: this.extractEnhancedColorAnalysis(),
+        formAnalysis: this.extractFormAnalysis(),
+        textureAnalysis: this.extractTextureAnalysis(),
+        patternAnalysis: this.extractPatternAnalysis(),
+        lightingAnalysis: this.extractLightingAnalysis()
+      }
+    };
+    
+    console.log('emitDataUpdate被调用,准备发射数据:', {
+      colorAnalysisResult: this.colorAnalysisResult,
+      materials: this.materials,
+      requirementItems: this.requirementItems,
+      materialAnalysisData: materialAnalysisData,
+      detailedAnalysis: currentData.detailedAnalysis
+    });
+    
+    this.dataUpdated.emit(currentData);
+    console.log('实时数据更新事件已发射:', currentData);
+  }
+
+  // 提取增强色彩分析数据
+  private extractEnhancedColorAnalysis(): any {
+    const colorAnalyses = this.materials
+      .filter(m => m.analysis?.enhancedColorAnalysis)
+      .map(m => m.analysis!.enhancedColorAnalysis);
+    
+    if (colorAnalyses.length === 0) return null;
+    
+    // 合并所有色彩分析数据
+    return colorAnalyses.reduce((merged, current) => ({
+      ...merged,
+      ...current
+    }), {});
+  }
+
+  // 提取形体分析数据
+  private extractFormAnalysis(): any {
+    const formAnalyses = this.materials
+      .filter(m => m.analysis?.formAnalysis)
+      .map(m => m.analysis!.formAnalysis);
+    
+    if (formAnalyses.length === 0) return null;
+    
+    return formAnalyses.reduce((merged, current) => ({
+      ...merged,
+      ...current
+    }), {});
+  }
+
+  // 提取质感分析数据
+  private extractTextureAnalysis(): any {
+    const textureAnalyses = this.materials
+      .filter(m => m.analysis?.textureAnalysis)
+      .map(m => m.analysis!.textureAnalysis);
+    
+    if (textureAnalyses.length === 0) return null;
+    
+    return textureAnalyses.reduce((merged, current) => ({
+      ...merged,
+      ...current
+    }), {});
+  }
+
+  // 提取纹理分析数据
+  private extractPatternAnalysis(): any {
+    const patternAnalyses = this.materials
+      .filter(m => m.analysis?.patternAnalysis)
+      .map(m => m.analysis!.patternAnalysis);
+    
+    if (patternAnalyses.length === 0) return null;
+    
+    return patternAnalyses.reduce((merged, current) => ({
+      ...merged,
+      ...current
+    }), {});
+  }
+
+  // 提取灯光分析数据
+  private extractLightingAnalysis(): any {
+    const lightingAnalyses = this.materials
+      .filter(m => m.analysis?.lightingAnalysis)
+      .map(m => m.analysis!.lightingAnalysis);
+    
+    if (lightingAnalyses.length === 0) return null;
+    
+    return lightingAnalyses.reduce((merged, current) => ({
+      ...merged,
+      ...current
+    }), {});
+  }
+
+  // 新增:图片与CAD文件预览方法
+  previewImage(url?: string): void {
+    if (!url) { return; }
+    try {
+      window.open(url, '_blank');
+    } catch (e) {
+      console.error('预览图片失败:', e);
+    }
+  }
+
+  previewCad(url?: string): void {
+    if (!url) { return; }
+    try {
+      window.open(url, '_blank');
+    } catch (e) {
+      console.error('预览CAD失败:', e);
+    }
+  }
+
+  // 需求映射测试相关方法
+  onFileSelected(event: Event): void {
+    const input = event.target as HTMLInputElement;
+    if (!input.files || input.files.length === 0) return;
+
+    this.updateStepStatus('upload', 'in-progress');
+    this.isUploading = true;
+
+    try {
+      const files = Array.from(input.files);
+      this.uploadedFiles = files.map(file => ({
+        id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
+        name: file.name,
+        url: URL.createObjectURL(file),
+        size: file.size,
+        type: 'image' as const,
+        preview: URL.createObjectURL(file)
+      }));
+
+      // 模拟上传延迟
+      setTimeout(() => {
+        this.isUploading = false;
+        this.updateStepStatus('upload', 'completed');
+        this.showUploadModal = true;
+        
+        // 自动开始分析
+        this.startAnalysis();
+      }, 1000);
+
+    } catch (error) {
+      console.error('文件上传失败:', error);
+      this.isUploading = false;
+      this.updateStepStatus('upload', 'error');
+    }
+  }
+
+  // 开始分析
+  startAnalysis(): void {
+    if (this.uploadedFiles.length === 0) return;
+
+    this.updateStepStatus('analysis', 'in-progress');
+    this.isAnalyzing = true;
+    this.analysisError = null;
+
+    // 使用第一个文件进行分析
+    const firstFile = this.uploadedFiles[0];
+    
+    // 添加null检查和错误处理
+    if (!firstFile) {
+      this.analysisError = '未找到有效的文件';
+      this.isAnalyzing = false;
+      this.updateStepStatus('analysis', 'error');
+      return;
+    }
+
+    try {
+      const analysisSubscription = this.colorAnalysisService.analyzeImage(firstFile).subscribe({
+        next: (result: ColorAnalysisResult) => {
+          if (result) {
+            this.analysisResult = result;
+            this.isAnalyzing = false;
+            this.updateStepStatus('analysis', 'completed');
+            
+            // 自动开始需求映射
+            this.startRequirementMapping();
+          } else {
+            this.analysisError = '分析结果为空';
+            this.isAnalyzing = false;
+            this.updateStepStatus('analysis', 'error');
+          }
+        },
+        error: (error: any) => {
+          console.error('分析失败:', error);
+          this.analysisError = '图片分析失败,请重试';
+          this.isAnalyzing = false;
+          this.updateStepStatus('analysis', 'error');
+        }
+      });
+
+      this.subscriptions.push(analysisSubscription);
+    } catch (error) {
+      console.error('启动分析失败:', error);
+      this.analysisError = '启动分析失败,请重试';
+      this.isAnalyzing = false;
+      this.updateStepStatus('analysis', 'error');
+    }
+  }
+
+  // 开始需求映射
+  startRequirementMapping(): void {
+    if (!this.analysisResult) {
+      console.warn('分析结果为空,无法开始需求映射');
+      return;
+    }
+
+    this.updateStepStatus('mapping', 'in-progress');
+    this.isGeneratingMapping = true;
+    this.mappingError = null;
+
+    try {
+      const mappingSubscription = this.requirementMappingService.generateRequirementMapping(
+        this.analysisResult,
+        SceneTemplate.LIVING_ROOM_MODERN
+      ).subscribe({
+        next: (mapping) => {
+          if (mapping) {
+            this.requirementMapping = mapping;
+            this.isGeneratingMapping = false;
+            this.updateStepStatus('mapping', 'completed');
+            this.updateStepStatus('preview', 'completed');
+            
+            console.log('=== 需求映射测试完成 ===');
+            console.log('映射结果:', this.requirementMapping);
+          } else {
+            this.mappingError = '需求映射结果为空';
+            this.isGeneratingMapping = false;
+            this.updateStepStatus('mapping', 'error');
+          }
+        },
+        error: (error) => {
+          console.error('需求映射生成失败:', error);
+          this.mappingError = '需求映射生成失败,请重试';
+          this.isGeneratingMapping = false;
+          this.updateStepStatus('mapping', 'error');
+        }
+      });
+
+      this.subscriptions.push(mappingSubscription);
+    } catch (error) {
+      console.error('启动需求映射失败:', error);
+      this.mappingError = '启动需求映射失败,请重试';
+      this.isGeneratingMapping = false;
+      this.updateStepStatus('mapping', 'error');
+    }
+  }
+
+  // 更新步骤状态
+  private updateStepStatus(stepId: string, status: 'pending' | 'in-progress' | 'completed' | 'error'): void {
+    const step = this.testSteps.find(s => s.id === stepId);
+    if (step) {
+      step.status = status;
+    } else {
+      console.warn(`未找到步骤: ${stepId}`);
+    }
+  }
+
+  // 辅助方法:获取步骤图标
+  getStepIcon(status: string): string {
+    switch (status) {
+      case 'completed': return '✅';
+      case 'in-progress': return '⏳';
+      case 'error': return '❌';
+      default: return '⭕';
+    }
+  }
+
+  // 获取步骤状态类名
+  getStepClass(status: string): string {
+    return `step-${status}`;
+  }
+
+  // 手动重试分析
+  retryAnalysis(): void {
+    this.analysisError = null;
+    this.startAnalysis();
+  }
+
+  // 手动重试映射
+  retryMapping(): void {
+    this.mappingError = null;
+    this.startRequirementMapping();
+  }
+
+  // 下载测试结果
+  downloadTestResult(): void {
+    if (!this.requirementMapping) return;
+
+    const testResult = {
+      timestamp: new Date().toISOString(),
+      uploadedFiles: this.uploadedFiles.map(f => ({
+        name: f.name,
+        size: f.size,
+        type: f.type
+      })),
+      analysisResult: this.analysisResult,
+      requirementMapping: this.requirementMapping,
+      testSteps: this.testSteps
+    };
+
+    const blob = new Blob([JSON.stringify(testResult, null, 2)], { type: 'application/json' });
+    const url = URL.createObjectURL(blob);
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = `requirement-mapping-test-${Date.now()}.json`;
+    link.click();
+    URL.revokeObjectURL(url);
+  }
+}

+ 866 - 0
copy/requirements-confirm-card1.html

@@ -0,0 +1,866 @@
+<div class="info-card requirements-confirm-card">
+  <div class="card-header">
+    <h4>确认需求</h4>
+    <div class="header-actions">
+      <button class="btn-ghost btn-sm" (click)="refreshProgress()">刷新进度</button>
+      
+      <!-- 紧凑型流程进度卡片 -->
+      <div class="compact-stage-indicators">
+        <div class="stage-chain">
+          <div class="stage-dot" [class]="getStageStatusClass('materialAnalysis')" 
+               title="素材分析 - {{ getStageStatusText('materialAnalysis') }}">
+            <span class="stage-number">1</span>
+          </div>
+          <div class="stage-connector" [class]="stageCompletionStatus.materialAnalysis ? 'completed' : 'pending'"></div>
+          
+          <div class="stage-dot" [class]="getStageStatusClass('requirementMapping')" 
+               title="需求映射 - {{ getStageStatusText('requirementMapping') }}">
+            <span class="stage-number">2</span>
+          </div>
+          <div class="stage-connector" [class]="stageCompletionStatus.requirementMapping ? 'completed' : 'pending'"></div>
+          
+          <div class="stage-dot" [class]="getStageStatusClass('collaboration')" 
+               title="协作验证 - {{ getStageStatusText('collaboration') }}">
+            <span class="stage-number">3</span>
+          </div>
+          <div class="stage-connector" [class]="stageCompletionStatus.collaboration ? 'completed' : 'pending'"></div>
+          
+          <div class="stage-dot" [class]="getStageStatusClass('progressReview')" 
+               title="进度审查 - {{ getStageStatusText('progressReview') }}">
+            <span class="stage-number">4</span>
+          </div>
+        </div>
+      </div>
+      
+      <div class="progress-indicator">
+        <div class="progress-bar">
+          <div class="progress-fill" [style.width.%]="getProgressPercentage()"></div>
+        </div>
+        <span class="progress-text">{{ getProgressPercentage() | number:'1.0-0' }}% 完成</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- 标签页导航 -->
+  <div class="tab-navigation">
+    <button 
+      class="tab-button" 
+      [class.active]="activeTab === 'materials'"
+      (click)="switchTab('materials')">
+      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+        <polyline points="14,2 14,8 20,8"></polyline>
+      </svg>
+      素材解析
+    </button>
+    <button 
+      class="tab-button" 
+      [class.active]="activeTab === 'mapping'"
+      (click)="switchTab('mapping')">
+      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+        <circle cx="12" cy="12" r="3"></circle>
+        <path d="M12 1v6m0 6v6m11-7h-6m-6 0H1"></path>
+      </svg>
+      需求映射
+    </button>
+    <button 
+      class="tab-button" 
+      [class.active]="activeTab === 'collaboration'"
+      (click)="switchTab('collaboration')">
+      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+        <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
+        <circle cx="9" cy="7" r="4"></circle>
+        <path d="M23 21v-2a4 4 0 0 0-3-3.87m-4-12a4 4 0 0 1 0 7.75"></path>
+      </svg>
+      协作验证
+    </button>
+    <button 
+      class="tab-button" 
+      [class.active]="activeTab === 'progress'"
+      (click)="switchTab('progress')">
+      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+        <polyline points="22,12 18,12 15,21 9,3 6,12 2,12"></polyline>
+      </svg>
+      进度管理
+    </button>
+  </div>
+
+  <!-- 标签页内容 -->
+  <div class="tab-content">
+    
+    <!-- 素材解析标签页 -->
+    @if (activeTab === 'materials') {
+      <div class="materials-section">
+        <!-- 文本输入区域 - 独立一行 -->
+        <div class="text-upload-section">
+          <div class="upload-item text-item">
+            <h5>文本描述</h5>
+            <form [formGroup]="materialUploadForm" (ngSubmit)="onTextSubmit()">
+              <textarea 
+                formControlName="textContent" 
+                placeholder="请描述您的装修需求,如:希望温馨的暖木色调,适合亲子家庭,需要瑜伽区收纳..."
+                rows="4">
+              </textarea>
+              <button type="submit" class="btn-primary btn-sm" [disabled]="!materialUploadForm.get('textContent')?.value">
+                解析文本
+              </button>
+            </form>
+          </div>
+        </div>
+
+        <!-- 参考图片和CAD图纸并排布局 -->
+        <div class="file-upload-grid">
+          <!-- 参考图片上传区域 -->
+          <div class="upload-item image-item">
+            <h5>参考图片</h5>
+            <div class="file-upload-zone" (click)="imageInput.click()">
+              <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
+                <circle cx="8.5" cy="8.5" r="1.5"></circle>
+                <polyline points="21,15 16,10 5,21"></polyline>
+              </svg>
+              <p>点击上传参考图片</p>
+              <span class="hint">支持多张图片,自动分析色调和材质</span>
+            </div>
+            <input #imageInput type="file" multiple accept="image/*" (change)="onFileSelected($event, 'image')" style="display: none;">
+          </div>
+
+          <!-- CAD图纸上传区域 -->
+          <div class="upload-item cad-item">
+            <h5>CAD图纸</h5>
+            <div class="file-upload-zone" (click)="cadInput.click()">
+              <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+                <polyline points="14,2 14,8 20,8"></polyline>
+                <line x1="16" y1="13" x2="8" y2="13"></line>
+                <line x1="16" y1="17" x2="8" y2="17"></line>
+              </svg>
+              <p>点击上传CAD图纸</p>
+              <span class="hint">自动识别结构和空间尺寸</span>
+            </div>
+            <input #cadInput type="file" accept=".dwg,.dxf,.pdf" (change)="onFileSelected($event, 'cad')" style="display: none;">
+          </div>
+        </div>
+
+        <!-- 已上传素材列表 -->
+        @if (materials.length > 0) {
+          <div class="materials-list">
+            <h5>已上传素材</h5>
+            <div class="material-cards">
+              @for (material of materials; track material.id) {
+                <div class="material-card" [class]="'material-' + material.type">
+                  <div class="material-header">
+                    <span class="material-type">{{ material.type === 'text' ? '文本' : material.type === 'image' ? '图片' : 'CAD' }}</span>
+                    <button class="btn-ghost btn-xs" (click)="removeMaterial(material.id)">
+                      <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                        <line x1="18" y1="6" x2="6" y2="18"></line>
+                        <line x1="6" y1="6" x2="18" y2="18"></line>
+                      </svg>
+                    </button>
+                  </div>
+                  <div class="material-name">{{ material.name }}</div>
+                  @if (material.type === 'image') {
+                    <button class="btn-ghost btn-xs" (click)="previewImage(material.url)">预览图片</button>
+                  } @else if (material.type === 'cad') {
+                    <button class="btn-ghost btn-xs" (click)="previewCad(material.url)">预览CAD</button>
+                  }
+                  @if (material.analysis) {
+                    <div class="parsed-info">
+                      @if (material.type === 'text') {
+                        <div class="parsed-tags">
+                          @if (material.analysis.atmosphere) {
+                          <div class="tag-group">
+                            <span class="tag-label">氛围:</span>
+                            <span class="tag">{{ material.analysis.atmosphere.description }}</span>
+                          </div>
+                        }
+                        </div>
+                      } @else if (material.type === 'image') {
+                        <div class="analysis-results">
+                          <!-- 基础色彩信息 -->
+                          <div class="color-info">
+                            <span class="color-temp">色温: {{ material.analysis.colorTemperature }}K</span>
+                          </div>
+                          
+                          <!-- 增强色彩分析 -->
+                          @if (material.analysis.enhancedColorAnalysis) {
+                            <div class="enhanced-analysis">
+                              <div class="analysis-section">
+                                <h6>色彩分析</h6>
+                                
+                                <!-- 色轮分析 -->
+                                @if (material.analysis.enhancedColorAnalysis.colorWheel) {
+                                  <div class="color-wheel-info">
+                                    <div class="color-wheel-icon">
+                                      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                                        <circle cx="12" cy="12" r="10"></circle>
+                                        <path d="M12 2a10 10 0 0 1 10 10"></path>
+                                        <path d="M12 2a10 10 0 0 0-10 10"></path>
+                                        <path d="M12 12l8.66-5"></path>
+                                        <path d="M12 12l-8.66-5"></path>
+                                      </svg>
+                                    </div>
+                                    <span>主色调: {{ material.analysis.enhancedColorAnalysis.colorWheel.primaryHue }}°</span>
+                                    <span>饱和度: {{ material.analysis.enhancedColorAnalysis.colorWheel.saturation }}%</span>
+                                  </div>
+                                }
+                                
+                                <!-- 色彩心理学 -->
+                                @if (material.analysis.enhancedColorAnalysis.colorPsychology) {
+                                  <div class="psychology-info">
+                                    <span class="mood-tag">{{ material.analysis.enhancedColorAnalysis.colorPsychology.primaryMood }}</span>
+                                    <span class="atmosphere-tag">{{ material.analysis.enhancedColorAnalysis.colorPsychology.atmosphere }}</span>
+                                  </div>
+                                }
+                              </div>
+                              
+                              <!-- 形体分析 -->
+                              @if (material.analysis.formAnalysis) {
+                                <div class="analysis-section">
+                                  <h6>形体分析</h6>
+                                  @if (material.analysis.formAnalysis.overallAssessment) {
+                                    <div class="form-metrics">
+                                      <div class="metric-item">
+                                        <span class="metric-label">复杂度:</span>
+                                        <div class="metric-bar">
+                                          <div class="metric-fill" [style.width.%]="material.analysis.formAnalysis.overallAssessment.formComplexity"></div>
+                                        </div>
+                                      </div>
+                                      <div class="metric-item">
+                                        <span class="metric-label">视觉冲击:</span>
+                                        <div class="metric-bar">
+                                          <div class="metric-fill" [style.width.%]="material.analysis.formAnalysis.overallAssessment.visualImpact"></div>
+                                        </div>
+                                      </div>
+                                    </div>
+                                  }
+                                </div>
+                              }
+                              
+                              <!-- 质感分析 -->
+                              @if (material.analysis.textureAnalysis) {
+                                <div class="analysis-section">
+                                  <h6>质感分析</h6>
+                                  @if (material.analysis.textureAnalysis.materialClassification) {
+                                    <div class="material-info">
+                                      <span class="material-tag">{{ material.analysis.textureAnalysis.materialClassification.primaryMaterial.category }}</span>
+                                      @if (material.analysis.textureAnalysis.surfaceProperties) {
+                                        <span class="surface-tag">{{ material.analysis.textureAnalysis.surfaceProperties.roughness.level }}</span>
+                                      }
+                                    </div>
+                                  }
+                                </div>
+                              }
+                              
+                              <!-- 纹理分析 -->
+                              @if (material.analysis.patternAnalysis) {
+                                <div class="analysis-section">
+                                  <h6>纹理分析</h6>
+                                  @if (material.analysis.patternAnalysis.patternRecognition) {
+                                    <div class="pattern-info">
+                                      @for (pattern of material.analysis.patternAnalysis.patternRecognition.primaryPatterns; track pattern.type) {
+                                        <span class="pattern-tag">{{ pattern.type }} ({{ pattern.coverage }}%)</span>
+                                      }
+                                    </div>
+                                  }
+                                </div>
+                              }
+                              
+                              <!-- 灯光分析 -->
+                              @if (material.analysis.lightingAnalysis) {
+                                <div class="analysis-section">
+                                  <h6>灯光分析</h6>
+                                  @if (material.analysis.lightingAnalysis.ambientAnalysis) {
+                                    <div class="lighting-info">
+                                      <span class="mood-tag">{{ material.analysis.lightingAnalysis.ambientAnalysis.lightingMood?.primary || '未知' }}</span>
+                                      @if (material.analysis.lightingAnalysis.illuminationAnalysis) {
+                                        <span class="brightness-tag">亮度: {{ material.analysis.lightingAnalysis.illuminationAnalysis.brightness?.overall || 0 }}%</span>
+                                      }
+                                      @if (material.analysis.lightingAnalysis.lightSourceIdentification) {
+                                        <span class="source-tag">光源: {{ material.analysis.lightingAnalysis.lightSourceIdentification.lightingSetup?.dominantSource || '未知' }}</span>
+                                      }
+                                    </div>
+                                  }
+                                </div>
+                              }
+                            </div>
+                          }
+                        </div>
+                      }
+                    </div>
+                  }
+                </div>
+              }
+            </div>
+          </div>
+        }
+      </div>
+    }
+
+    <!-- 需求映射标签页 -->
+    @if (activeTab === 'mapping') {
+      <div class="mapping-section">
+        <!-- 测试步骤进度 -->
+        <div class="test-progress">
+          <h3>需求映射进度</h3>
+          <div class="steps-container">
+            @for (step of testSteps; track step.id) {
+              <div class="step-item" [class]="getStepClass(step.status)">
+                <div class="step-icon">{{ getStepIcon(step.status) }}</div>
+                <div class="step-info">
+                  <div class="step-name">{{ step.name }}</div>
+                  <div class="step-status">
+                    @switch (step.status) {
+                      @case ('pending') { 等待中 }
+                      @case ('in-progress') { 进行中... }
+                      @case ('completed') { 已完成 }
+                      @case ('error') { 失败 }
+                    }
+                  </div>
+                </div>
+              </div>
+            }
+          </div>
+        </div>
+
+        <!-- 文件上传区域 -->
+        <div class="upload-section">
+          <h3>1. 图片上传</h3>
+          <div class="upload-area" [class.uploading]="isUploading">
+            @if (uploadedFiles.length === 0) {
+              <div class="upload-dropzone">
+                <div class="upload-icon">📁</div>
+                <div class="upload-text">选择图片文件进行测试</div>
+                <div class="upload-hint">支持 JPG、PNG 格式,可选择多张图片</div>
+                <input type="file" 
+                       accept="image/*" 
+                       multiple 
+                       (change)="onFileSelectedForMapping($event)"
+                       class="file-input">
+              </div>
+            } @else {
+              <div class="uploaded-files">
+                <h4>已上传文件 ({{ uploadedFiles.length }})</h4>
+                <div class="files-grid">
+                  @for (file of uploadedFiles; track file.id) {
+                    <div class="file-item">
+                      <img [src]="file.preview || file.url" [alt]="file.name" class="file-preview">
+                      <div class="file-info">
+                        <div class="file-name">{{ file.name }}</div>
+                        <div class="file-size">{{ (file.size! / 1024 / 1024).toFixed(2) }} MB</div>
+                      </div>
+                    </div>
+                  }
+                </div>
+              </div>
+            }
+            
+            @if (isUploading) {
+              <div class="loading-overlay">
+                <div class="loading-spinner"></div>
+                <div class="loading-text">正在上传文件...</div>
+              </div>
+            }
+          </div>
+        </div>
+
+        <!-- 分析结果区域 -->
+        <div class="analysis-section" [class.disabled]="uploadedFiles.length === 0">
+          <h3>2. 图片分析</h3>
+          
+          @if (isAnalyzing) {
+            <div class="analysis-loading">
+              <div class="loading-spinner"></div>
+              <div class="loading-text">
+                <h4>正在分析图片...</h4>
+                <p>系统正在进行色彩、纹理、形态、图案和灯光分析</p>
+              </div>
+            </div>
+          } @else if (analysisError) {
+            <div class="analysis-error">
+              <div class="error-icon">❌</div>
+              <div class="error-text">
+                <h4>分析失败</h4>
+                <p>{{ analysisError }}</p>
+                <button class="retry-btn" (click)="retryAnalysis()">重新分析</button>
+              </div>
+            </div>
+          } @else if (analysisResult) {
+            <div class="analysis-result">
+              <h4>分析完成 ✅</h4>
+              <div class="analysis-summary">
+                <div class="summary-item">
+                  <span class="label">主要颜色:</span>
+                  <span class="value">{{ analysisResult.enhancedAnalysis?.colorWheel?.colorDistribution?.length || 0 }} 种</span>
+                </div>
+                <div class="summary-item">
+                  <span class="label">材质类型:</span>
+                  <span class="value">{{ analysisResult.textureAnalysis?.materialClassification?.primary || '未识别' }}</span>
+                </div>
+                <div class="summary-item">
+                  <span class="label">灯光情绪:</span>
+                  <span class="value">{{ analysisResult.lightingAnalysis?.ambientAnalysis?.lightingMood || '未识别' }}</span>
+                </div>
+                <div class="summary-item">
+                  <span class="label">空间形态:</span>
+                  <span class="value">{{ analysisResult.formAnalysis?.spaceAnalysis?.spaceType || '未识别' }}</span>
+                </div>
+              </div>
+            </div>
+          } @else {
+            <div class="analysis-placeholder">
+              <div class="placeholder-icon">🔍</div>
+              <div class="placeholder-text">
+                <h4>等待分析</h4>
+                <p>请先上传图片,系统将自动开始分析</p>
+              </div>
+            </div>
+          }
+        </div>
+
+        <!-- 需求映射结果区域 -->
+        <div class="mapping-section" [class.disabled]="!analysisResult">
+          <h3>3. 需求映射生成</h3>
+          
+          @if (isGeneratingMapping) {
+            <div class="mapping-loading">
+              <div class="loading-spinner"></div>
+              <div class="loading-text">
+                <h4>正在生成需求映射...</h4>
+                <p>基于分析结果生成场景参数和映射关系</p>
+              </div>
+            </div>
+          } @else if (mappingError) {
+            <div class="mapping-error">
+              <div class="error-icon">❌</div>
+              <div class="error-text">
+                <h4>映射生成失败</h4>
+                <p>{{ mappingError }}</p>
+                <button class="retry-btn" (click)="retryMapping()">重新生成</button>
+              </div>
+            </div>
+          } @else if (requirementMapping) {
+            <div class="mapping-result">
+              <h4>需求映射生成完成 ✅</h4>
+              
+              <!-- 场景生成信息 -->
+              <div class="mapping-section-item">
+                <h5>场景生成</h5>
+                <div class="scene-info">
+                  <div class="info-row">
+                    <span class="label">基础场景:</span>
+                    <span class="value">{{ requirementMapping.sceneGeneration.baseScene }}</span>
+                  </div>
+                  @if (requirementMapping.sceneGeneration.atmospherePreview) {
+                    <div class="atmosphere-preview">
+                      <img [src]="requirementMapping.sceneGeneration.atmospherePreview" 
+                           alt="氛围感预览图"
+                           class="preview-image">
+                      <div class="preview-label">氛围感预览图</div>
+                    </div>
+                  }
+                </div>
+              </div>
+
+              <!-- 参数映射信息 -->
+              <div class="mapping-section-item">
+                <h5>参数映射</h5>
+                <div class="params-grid">
+                  <!-- 颜色参数 -->
+                  <div class="param-group">
+                    <h6>颜色映射</h6>
+                    <div class="color-params">
+                      <div class="param-item">
+                        <span class="label">主要颜色:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.colorParams.primaryColors.length }} 种</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">色彩和谐:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.colorParams.colorHarmony }}</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">饱和度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.colorParams.saturation }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">亮度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.colorParams.brightness }}%</span>
+                      </div>
+                    </div>
+                  </div>
+
+                  <!-- 空间参数 -->
+                  <div class="param-group">
+                    <h6>空间映射</h6>
+                    <div class="space-params">
+                      <div class="param-item">
+                        <span class="label">布局类型:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.spaceParams.layout.type }}</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">空间流线:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.spaceParams.layout.flow }}</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">家具比例:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.spaceParams.scale.furniture }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">开放度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.spaceParams.scale.openness }}%</span>
+                      </div>
+                    </div>
+                  </div>
+
+                  <!-- 材质参数 -->
+                  <div class="param-group">
+                    <h6>材质映射</h6>
+                    <div class="material-params">
+                      <div class="param-item">
+                        <span class="label">纹理缩放:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.textureScale }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">反射率:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.reflectivity }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">粗糙度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.roughness }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">金属度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.metallic }}%</span>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+              <!-- 测试结果下载 -->
+              <div class="test-actions">
+                <button class="download-btn" (click)="downloadTestResult()">
+                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
+                    <polyline points="7,10 12,15 17,10"></polyline>
+                    <line x1="12" y1="15" x2="12" y2="3"></line>
+                  </svg>
+                  下载测试结果
+                </button>
+              </div>
+            </div>
+          } @else {
+            <div class="mapping-placeholder">
+              <div class="placeholder-icon">🎯</div>
+              <div class="placeholder-text">
+                <h4>等待映射生成</h4>
+                <p>请先完成图片分析,系统将自动生成需求映射</p>
+              </div>
+            </div>
+          }
+        </div>
+      </div>
+    }
+
+    <!-- 协作验证标签页 -->
+    @if (activeTab === 'collaboration') {
+      <div class="collaboration-section">
+        <!-- 一致性预警 -->
+        @if (consistencyWarnings.length > 0) {
+          <div class="consistency-warning">
+            <svg viewBox="0 0 24 24" fill="currentColor">
+              <path d="M12 2L1 21h22L12 2zm0 3.99L19.53 19H4.47L12 5.99zM11 16h2v2h-2v-2zm0-6h2v4h-2v-4z"/>
+            </svg>
+            <div>
+              <strong>一致性预警:</strong>
+              @for (warning of consistencyWarnings; track warning) {
+                <div>{{ warning }}</div>
+              }
+            </div>
+          </div>
+        }
+
+        <!-- 需求列表 -->
+        <div class="requirements-list">
+          <div class="list-header">
+            <h5>需求项目</h5>
+            <div class="list-controls">
+              <button class="btn-ghost btn-sm" (click)="sortRequirementsByPriority()">按优先级排序</button>
+              <button class="btn-ghost btn-sm" (click)="saveCurrentState()">保存状态</button>
+            </div>
+          </div>
+
+          @for (requirement of requirementItems; track requirement.id) {
+            <div class="requirement-item" [class]="getStatusClass(requirement.status)">
+              <div class="requirement-header">
+                <div class="requirement-info">
+                  <h6>{{ requirement.title }}</h6>
+                  <p>{{ requirement.description }}</p>
+                  <div class="requirement-tags">
+                    @for (tag of requirement.tags; track tag) {
+                      <span class="tag">{{ tag }}</span>
+                    }
+                  </div>
+                </div>
+                <div class="requirement-meta">
+                  <select class="priority-select" 
+                          [value]="requirement.priority" 
+                          (change)="onPriorityChange(requirement.id, $event)">
+                    <option value="high">高优先级</option>
+                    <option value="medium">中优先级</option>
+                    <option value="low">低优先级</option>
+                  </select>
+                  <span class="status-badge" [class]="getStatusClass(requirement.status)">
+                    {{ requirement.status === 'confirmed' ? '已确认' : requirement.status === 'rejected' ? '已拒绝' : '待确认' }}
+                  </span>
+                </div>
+              </div>
+              
+              <div class="requirement-actions">
+                @if (requirement.status === 'pending') {
+                  <button class="btn-success btn-sm" (click)="confirmRequirement(requirement.id)">确认</button>
+                  <button class="btn-danger btn-sm" (click)="rejectRequirement(requirement.id, '需要进一步讨论')">拒绝</button>
+                }
+                <button class="btn-ghost btn-sm" (click)="requirement.showComments = !requirement.showComments">
+                  评论 ({{ getCommentsForRequirement(requirement.id).length }})
+                  @if (hasUnreadComments(requirement.id)) {
+                    <span class="unread-indicator"></span>
+                  }
+                </button>
+              </div>
+
+              @if (requirement.showComments) {
+                <div class="comments-section">
+                  @if (getCommentsForRequirement(requirement.id).length > 0) {
+                    <div class="comments-list">
+                      @for (comment of getCommentsForRequirement(requirement.id); track comment.id) {
+                        <div class="comment-item" [class.resolved]="comment.status === 'resolved'">
+                          <div class="comment-header">
+                            <span class="comment-author">{{ comment.author }}</span>
+                            <span class="comment-role">{{ comment.role === 'designer' ? '设计师' : comment.role === 'customer-service' ? '客服' : '客户' }}</span>
+                            <span class="comment-time">{{ comment.timestamp | date:'MM-dd HH:mm' }}</span>
+                            @if (comment.status === 'pending') {
+                              <button class="btn-ghost btn-xs" (click)="resolveComment(comment.id)">标记已解决</button>
+                            }
+                          </div>
+                          <div class="comment-content">{{ comment.content }}</div>
+                        </div>
+                      }
+                    </div>
+                  }
+                  <div class="add-comment">
+                    <textarea #commentInput placeholder="添加评论..." rows="2"></textarea>
+                    <button class="btn-primary btn-sm" (click)="addCommentToRequirement(requirement.id, commentInput.value); commentInput.value = ''">
+                      发送
+                    </button>
+                  </div>
+                </div>
+              }
+            </div>
+          }
+        </div>
+      </div>
+    }
+
+    <!-- 进度管理标签页 -->
+    @if (activeTab === 'progress') {
+      <div class="progress-section">
+        <!-- 整体进度 -->
+        <div class="overall-progress">
+          <div class="section-header">
+            <h5>整体进度</h5>
+          </div>
+          
+          <div class="progress-container">
+            <div class="progress-visual">
+              <div class="linear-progress">
+                <div class="progress-track">
+                  <div class="progress-bar-fill" [style.width.%]="getProgressPercentage()"></div>
+                </div>
+                <div class="progress-text">{{ getProgressPercentage() }}%</div>
+              </div>
+              
+              <div class="circular-progress">
+                <svg viewBox="0 0 120 120" class="progress-circle">
+                  <circle cx="60" cy="60" r="54" fill="none" stroke="#e5e7eb" stroke-width="8"></circle>
+                  <circle cx="60" cy="60" r="54" fill="none" stroke="#3b82f6" stroke-width="8"
+                    stroke-linecap="round"
+                    [attr.stroke-dasharray]="339.292"
+                    [attr.stroke-dashoffset]="339.292 - (339.292 * getProgressPercentage() / 100)"
+                    transform="rotate(-90 60 60)">
+                  </circle>
+                </svg>
+                <div class="progress-text">
+                  <div class="progress-percentage">{{ getProgressPercentage() }}%</div>
+                  <div class="progress-label">完成度</div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 进度统计 -->
+        <div class="progress-stats">
+          <div class="stat-item">
+              <div class="stat-number">{{ getRequirementCountByStatus('confirmed') }}</div>
+              <div class="stat-label">已确认</div>
+            </div>
+            <div class="stat-item">
+              <div class="stat-number">{{ getRequirementCountByStatus('pending') }}</div>
+              <div class="stat-label">待确认</div>
+            </div>
+            <div class="stat-item">
+              <div class="stat-number">{{ getRequirementCountByStatus('rejected') }}</div>
+              <div class="stat-label">已拒绝</div>
+            </div>
+          <div class="stat-item">
+            <div class="stat-number">{{ getUnresolvedCommentsCount() }}</div>
+            <div class="stat-label">待解决评论</div>
+          </div>
+        </div>
+
+        <!-- 历史状态 -->
+        <div class="history-section">
+          <div class="section-header">
+            <h5>历史状态</h5>
+            <div class="history-controls">
+              <button class="btn-ghost btn-sm" (click)="saveCurrentState()">保存当前状态</button>
+              <select class="history-select" (change)="onHistoryStateChange($event)">
+                <option value="-1">选择历史状态</option>
+                @for (state of historyStates; track $index) {
+                  <option [value]="$index">{{ state.timestamp | date:'MM-dd HH:mm' }} - {{ state.author }}</option>
+                }
+              </select>
+            </div>
+          </div>
+
+          @if (historyStates.length > 0) {
+            <div class="history-timeline">
+              @for (state of historyStates; track $index) {
+                <div class="timeline-item" (click)="restoreHistoryState($index)">
+                  <div class="timeline-marker"></div>
+                  <div class="timeline-content">
+                    <div class="timeline-header">
+                      <span class="timeline-time">{{ state.timestamp | date:'MM-dd HH:mm' }}</span>
+                      <span class="timeline-author">{{ state.author }}</span>
+                    </div>
+                    <div class="timeline-summary">
+                      状态快照:{{ state.requirementItems.length }}个需求项,
+                      {{ getRequirementCountByStatus('confirmed') }}个已确认
+                    </div>
+                  </div>
+                </div>
+              }
+            </div>
+          } @else {
+            <div class="empty-history">
+              <p>暂无历史状态记录</p>
+              <button class="btn-primary btn-sm" (click)="saveCurrentState()">保存当前状态</button>
+            </div>
+          }
+        </div>
+
+        <!-- 需求状态分布 -->
+        <div class="status-distribution">
+          <h5>需求状态分布</h5>
+          <div class="status-bars">
+            <div class="status-bar">
+              <div class="status-info">
+                <span class="status-name">已确认</span>
+                <span class="status-count">{{ getRequirementCountByStatus('confirmed') }}</span>
+              </div>
+              <div class="status-progress">
+                <div class="progress-bar confirmed" 
+                     [style.width.%]="getStatusPercentage('confirmed')"></div>
+              </div>
+            </div>
+            
+            <div class="status-bar">
+              <div class="status-info">
+                <span class="status-name">待确认</span>
+                <span class="status-count">{{ getRequirementCountByStatus('pending') }}</span>
+              </div>
+              <div class="status-progress">
+                <div class="progress-bar pending" 
+                     [style.width.%]="getStatusPercentage('pending')"></div>
+              </div>
+            </div>
+            
+            <div class="status-bar">
+              <div class="status-info">
+                <span class="status-name">已拒绝</span>
+                <span class="status-count">{{ getRequirementCountByStatus('rejected') }}</span>
+              </div>
+              <div class="status-progress">
+                <div class="progress-bar rejected" 
+                     [style.width.%]="getStatusPercentage('rejected')"></div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    }
+  </div>
+</div>
+
+<!-- 保存状态和手动保存按钮 -->
+<div class="save-section">
+  <div class="save-status">
+    <span class="save-icon" [class]="'save-icon-' + saveStatus">{{ getSaveStatusIcon() }}</span>
+    <span class="save-text">{{ getSaveStatusText() }}</span>
+  </div>
+  
+  <div class="save-actions">
+    <button class="btn-secondary" 
+            [disabled]="isSaving || !hasUnsavedChanges"
+            (click)="manualSave()">
+      @if (isSaving) {
+        <span class="loading-spinner"></span>
+        保存中...
+      } @else {
+        手动保存
+      }
+    </button>
+    
+    <div class="auto-save-toggle">
+      <label class="toggle-label">
+        <input type="checkbox" 
+               [(ngModel)]="autoSaveEnabled"
+               class="toggle-input">
+        <span class="toggle-slider"></span>
+        <span class="toggle-text">自动保存</span>
+      </label>
+    </div>
+  </div>
+</div>
+
+<!-- 上传成功弹窗 -->
+<app-upload-success-modal
+  [isVisible]="showUploadSuccessModal"
+  [uploadedFiles]="uploadedFiles"
+  [uploadType]="uploadType"
+  [analysisResult]="colorAnalysisResult"
+  [isAnalyzing]="isAnalyzingColors"
+  (closeModal)="onModalClose()"
+  (analyzeColors)="onAnalyzeColors()"
+  (viewReport)="onViewReport()">
+</app-upload-success-modal>
+
+<!-- 全局提示(支持全屏与角落两模式) -->
+<app-global-prompt
+  [visible]="showGlobalPrompt"
+  [title]="promptTitle"
+  [message]="promptMessage"
+  [mode]="promptMode"
+  [position]="promptPosition"
+  [autoDismissMs]="4000"
+  (closed)="onPromptClose()">
+</app-global-prompt>
+
+<!-- 完整报告全屏覆盖层 -->
+<app-full-report-overlay
+  [visible]="showFullReportOverlay"
+  [colorResult]="colorAnalysisResult"
+  [cadResult]="cadAnalysisResult"
+  (close)="onCloseFullReport()">
+</app-full-report-overlay>

+ 888 - 0
copy/requirements-confirm-card2.html

@@ -0,0 +1,888 @@
+<div class="info-card requirements-confirm-card">
+  <div class="card-header">
+    <h4>确认需求</h4>
+    <div class="header-actions">
+      <button class="btn-ghost btn-sm" (click)="refreshProgress()">刷新进度</button>
+      
+      <!-- 紧凑型流程进度卡片 -->
+      <div class="compact-stage-indicators">
+        <div class="stage-chain">
+          <div class="stage-dot" [class]="getStageStatusClass('materialAnalysis')" 
+               title="素材分析 - {{ getStageStatusText('materialAnalysis') }}">
+            <span class="stage-number">1</span>
+          </div>
+          <div class="stage-connector" [class]="stageCompletionStatus.materialAnalysis ? 'completed' : 'pending'"></div>
+          
+          <div class="stage-dot" [class]="getStageStatusClass('requirementMapping')" 
+               title="需求映射 - {{ getStageStatusText('requirementMapping') }}">
+            <span class="stage-number">2</span>
+          </div>
+          <div class="stage-connector" [class]="stageCompletionStatus.requirementMapping ? 'completed' : 'pending'"></div>
+          
+          <div class="stage-dot" [class]="getStageStatusClass('collaboration')" 
+               title="协作验证 - {{ getStageStatusText('collaboration') }}">
+            <span class="stage-number">3</span>
+          </div>
+          <div class="stage-connector" [class]="stageCompletionStatus.collaboration ? 'completed' : 'pending'"></div>
+          
+          <div class="stage-dot" [class]="getStageStatusClass('progressReview')" 
+               title="进度审查 - {{ getStageStatusText('progressReview') }}">
+            <span class="stage-number">4</span>
+          </div>
+        </div>
+      </div>
+      
+      <div class="progress-indicator">
+        <div class="progress-bar">
+          <div class="progress-fill" [style.width.%]="getProgressPercentage()"></div>
+        </div>
+        <span class="progress-text">{{ getProgressPercentage() | number:'1.0-0' }}% 完成</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- 标签页导航 -->
+  <div class="tab-navigation">
+    <button 
+      class="tab-button" 
+      [class.active]="activeTab === 'materials'"
+      (click)="switchTab('materials')">
+      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+        <polyline points="14,2 14,8 20,8"></polyline>
+      </svg>
+      素材解析
+    </button>
+    <button 
+      class="tab-button" 
+      [class.active]="activeTab === 'mapping'"
+      (click)="switchTab('mapping')">
+      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+        <circle cx="12" cy="12" r="3"></circle>
+        <path d="M12 1v6m0 6v6m11-7h-6m-6 0H1"></path>
+      </svg>
+      需求映射
+    </button>
+    <button 
+      class="tab-button" 
+      [class.active]="activeTab === 'collaboration'"
+      (click)="switchTab('collaboration')">
+      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+        <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
+        <circle cx="9" cy="7" r="4"></circle>
+        <path d="M23 21v-2a4 4 0 0 0-3-3.87m-4-12a4 4 0 0 1 0 7.75"></path>
+      </svg>
+      协作验证
+    </button>
+    <button 
+      class="tab-button" 
+      [class.active]="activeTab === 'progress'"
+      (click)="switchTab('progress')">
+      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+        <polyline points="22,12 18,12 15,21 9,3 6,12 2,12"></polyline>
+      </svg>
+      进度管理
+    </button>
+  </div>
+
+  <!-- 标签页内容 -->
+  <div class="tab-content">
+    
+    <!-- 素材解析标签页 -->
+    @if (activeTab === 'materials') {
+      <div class="materials-section">
+        <!-- 文本输入区域 - 独立一行 -->
+        <div class="text-upload-section">
+          <div class="upload-item text-item">
+            <h5>文本描述</h5>
+            <form [formGroup]="materialUploadForm" (ngSubmit)="onTextSubmit()">
+              <textarea 
+                formControlName="textContent" 
+                placeholder="请描述您的装修需求,如:希望温馨的暖木色调,适合亲子家庭,需要瑜伽区收纳..."
+                rows="4">
+              </textarea>
+              <button type="submit" class="btn-primary btn-sm" [disabled]="!materialUploadForm.get('textContent')?.value">
+                解析文本
+              </button>
+            </form>
+          </div>
+        </div>
+
+        <!-- 参考图片和CAD图纸并排布局 -->
+        <div class="file-upload-grid">
+          <!-- 参考图片上传区域 -->
+          <div class="upload-item image-item">
+            <h5>参考图片</h5>
+            <div class="file-upload-zone" (click)="imageInput.click()">
+              <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
+                <circle cx="8.5" cy="8.5" r="1.5"></circle>
+                <polyline points="21,15 16,10 5,21"></polyline>
+              </svg>
+              <p>点击上传参考图片</p>
+              <span class="hint">支持多张图片,自动分析色调和材质</span>
+            </div>
+            <input #imageInput type="file" multiple accept="image/*" (change)="onFileSelected($event, 'image')" style="display: none;">
+          </div>
+
+          <!-- CAD图纸上传区域 -->
+          <div class="upload-item cad-item">
+            <h5>CAD图纸</h5>
+            <div class="file-upload-zone" (click)="cadInput.click()">
+              <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+                <polyline points="14,2 14,8 20,8"></polyline>
+                <line x1="16" y1="13" x2="8" y2="13"></line>
+                <line x1="16" y1="17" x2="8" y2="17"></line>
+              </svg>
+              <p>点击上传CAD图纸</p>
+              <span class="hint">自动识别结构和空间尺寸</span>
+            </div>
+            <input #cadInput type="file" accept=".dwg,.dxf,.pdf" (change)="onFileSelected($event, 'cad')" style="display: none;">
+          </div>
+        </div>
+
+        <!-- 已上传素材列表 -->
+        @if (materials.length > 0) {
+          <div class="materials-list">
+            <h5>已上传素材</h5>
+            <div class="material-cards">
+              @for (material of materials; track material.id) {
+                <div class="material-card" [class]="'material-' + material.type">
+                  <div class="material-header">
+                    <span class="material-type">{{ material.type === 'text' ? '文本' : material.type === 'image' ? '图片' : 'CAD' }}</span>
+                    <button class="btn-ghost btn-xs" (click)="removeMaterial(material.id)">
+                      <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                        <line x1="18" y1="6" x2="6" y2="18"></line>
+                        <line x1="6" y1="6" x2="18" y2="18"></line>
+                      </svg>
+                    </button>
+                  </div>
+                  <div class="material-name">{{ material.name }}</div>
+                  @if (material.type === 'image') {
+                    <button class="btn-ghost btn-xs" (click)="previewImage(material.url)">预览图片</button>
+                  } @else if (material.type === 'cad') {
+                    <button class="btn-ghost btn-xs" (click)="previewCad(material.url)">预览CAD</button>
+                  }
+                  @if (material.analysis) {
+                    <div class="parsed-info">
+                      @if (material.type === 'text') {
+                        <div class="parsed-tags">
+                          @if (material.analysis.atmosphere) {
+                          <div class="tag-group">
+                            <span class="tag-label">氛围:</span>
+                            <span class="tag">{{ material.analysis.atmosphere.description }}</span>
+                          </div>
+                        }
+                        </div>
+                      } @else if (material.type === 'image') {
+                        <div class="analysis-results">
+                          <!-- 基础色彩信息 -->
+                          <div class="color-info">
+                            <span class="color-temp">色温: {{ material.analysis.colorTemperature }}K</span>
+                          </div>
+                          
+                          <!-- 增强色彩分析 -->
+                          @if (material.analysis.enhancedColorAnalysis) {
+                            <div class="enhanced-analysis">
+                              <div class="analysis-section">
+                                <h6>色彩分析</h6>
+                                
+                                <!-- 色轮分析 -->
+                                @if (material.analysis.enhancedColorAnalysis.colorWheel) {
+                                  <div class="color-wheel-info">
+                                    <div class="color-wheel-icon">
+                                      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                                        <circle cx="12" cy="12" r="10"></circle>
+                                        <path d="M12 2a10 10 0 0 1 10 10"></path>
+                                        <path d="M12 2a10 10 0 0 0-10 10"></path>
+                                        <path d="M12 12l8.66-5"></path>
+                                        <path d="M12 12l-8.66-5"></path>
+                                      </svg>
+                                    </div>
+                                    <span>主色调: {{ material.analysis.enhancedColorAnalysis.colorWheel.primaryHue }}°</span>
+                                    <span>饱和度: {{ material.analysis.enhancedColorAnalysis.colorWheel.saturation }}%</span>
+                                  </div>
+                                }
+                                
+                                <!-- 色彩心理学 -->
+                                @if (material.analysis.enhancedColorAnalysis.colorPsychology) {
+                                  <div class="psychology-info">
+                                    <span class="mood-tag">{{ material.analysis.enhancedColorAnalysis.colorPsychology.primaryMood }}</span>
+                                    <span class="atmosphere-tag">{{ material.analysis.enhancedColorAnalysis.colorPsychology.atmosphere }}</span>
+                                  </div>
+                                }
+                              </div>
+                              
+                              <!-- 形体分析 -->
+                              @if (material.analysis.formAnalysis) {
+                                <div class="analysis-section">
+                                  <h6>形体分析</h6>
+                                  @if (material.analysis.formAnalysis.overallAssessment) {
+                                    <div class="form-metrics">
+                                      <div class="metric-item">
+                                        <span class="metric-label">复杂度:</span>
+                                        <div class="metric-bar">
+                                          <div class="metric-fill" [style.width.%]="material.analysis.formAnalysis.overallAssessment.formComplexity"></div>
+                                        </div>
+                                      </div>
+                                      <div class="metric-item">
+                                        <span class="metric-label">视觉冲击:</span>
+                                        <div class="metric-bar">
+                                          <div class="metric-fill" [style.width.%]="material.analysis.formAnalysis.overallAssessment.visualImpact"></div>
+                                        </div>
+                                      </div>
+                                    </div>
+                                  }
+                                </div>
+                              }
+                              
+                              <!-- 质感分析 -->
+                              @if (material.analysis.textureAnalysis) {
+                                <div class="analysis-section">
+                                  <h6>质感分析</h6>
+                                  @if (material.analysis.textureAnalysis.materialClassification) {
+                                    <div class="material-info">
+                                      <span class="material-tag">{{ material.analysis.textureAnalysis.materialClassification.primaryMaterial.category }}</span>
+                                      @if (material.analysis.textureAnalysis.surfaceProperties) {
+                                        <span class="surface-tag">{{ material.analysis.textureAnalysis.surfaceProperties.roughness.level }}</span>
+                                      }
+                                    </div>
+                                  }
+                                </div>
+                              }
+                              
+                              <!-- 纹理分析 -->
+                              @if (material.analysis.patternAnalysis) {
+                                <div class="analysis-section">
+                                  <h6>纹理分析</h6>
+                                  @if (material.analysis.patternAnalysis.patternRecognition) {
+                                    <div class="pattern-info">
+                                      @for (pattern of material.analysis.patternAnalysis.patternRecognition.primaryPatterns; track pattern.type) {
+                                        <span class="pattern-tag">{{ pattern.type }} ({{ pattern.coverage }}%)</span>
+                                      }
+                                    </div>
+                                  }
+                                </div>
+                              }
+                              
+                              <!-- 灯光分析 -->
+                              @if (material.analysis.lightingAnalysis) {
+                                <div class="analysis-section">
+                                  <h6>灯光分析</h6>
+                                  @if (material.analysis.lightingAnalysis.ambientAnalysis) {
+                                    <div class="lighting-info">
+                                      <span class="mood-tag">{{ material.analysis.lightingAnalysis.ambientAnalysis.lightingMood?.primary || '未知' }}</span>
+                                      @if (material.analysis.lightingAnalysis.illuminationAnalysis) {
+                                        <span class="brightness-tag">亮度: {{ material.analysis.lightingAnalysis.illuminationAnalysis.brightness?.overall || 0 }}%</span>
+                                      }
+                                      @if (material.analysis.lightingAnalysis.lightSourceIdentification) {
+                                        <span class="source-tag">光源: {{ material.analysis.lightingAnalysis.lightSourceIdentification.lightingSetup?.dominantSource || '未知' }}</span>
+                                      }
+                                    </div>
+                                  }
+                                </div>
+                              }
+                            </div>
+                          }
+                        </div>
+                      }
+                    </div>
+                  }
+                </div>
+              }
+            </div>
+          </div>
+        }
+      </div>
+    }
+
+    <!-- 需求映射标签页 -->
+    @if (activeTab === 'mapping') {
+      <div class="mapping-section">
+        <!-- 测试步骤进度 -->
+        <div class="test-progress">
+          <h3>需求映射进度</h3>
+          <div class="progress-summary">
+            @if (uploadedFiles.length > 0) {
+              <span class="progress-badge">
+                📸 已同步 {{ uploadedFiles.length }} 张参考图片
+              </span>
+            }
+          </div>
+          <div class="steps-container">
+            @for (step of testSteps; track step.id) {
+              <div class="step-item" [class]="getStepClass(step.status)">
+                <div class="step-icon">{{ getStepIcon(step.status) }}</div>
+                <div class="step-info">
+                  <div class="step-name">{{ step.name }}</div>
+                  <div class="step-status">
+                    @switch (step.status) {
+                      @case ('pending') { 等待中 }
+                      @case ('in-progress') { 进行中... }
+                      @case ('completed') { 已完成 }
+                      @case ('error') { 失败 }
+                    }
+                  </div>
+                </div>
+              </div>
+            }
+          </div>
+        </div>
+
+        <!-- 文件上传区域 -->
+        <div class="upload-section">
+          <h3>1. 图片上传</h3>
+          <div class="upload-area" [class.uploading]="isUploading">
+            @if (uploadedFiles.length === 0) {
+              <div class="upload-dropzone">
+                <div class="upload-icon">📁</div>
+                <div class="upload-text">选择图片文件进行测试</div>
+                <div class="upload-hint">支持 JPG、PNG 格式,可选择多张图片</div>
+                <input type="file" 
+                       accept="image/*" 
+                       multiple 
+                       (change)="onFileSelectedForMapping($event)"
+                       class="file-input">
+              </div>
+            } @else {
+              <div class="uploaded-files">
+                <h4>已上传文件 ({{ uploadedFiles.length }} 张图片)</h4>
+                <div class="upload-source-info">
+                  <span class="info-tag">✨ 来自素材分析的参考图片</span>
+                </div>
+                <div class="files-grid">
+                  @for (file of uploadedFiles; track file.id) {
+                    <div class="file-item">
+                      <img [src]="file.preview || file.url" [alt]="file.name" class="file-preview">
+                      <div class="file-info">
+                        <div class="file-name">{{ file.name }}</div>
+                        <div class="file-size">{{ (file.size! / 1024 / 1024).toFixed(2) }} MB</div>
+                      </div>
+                      <button class="remove-file-btn" (click)="removeUploadedFile(file.id)" title="移除">
+                        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                          <line x1="18" y1="6" x2="6" y2="18"></line>
+                          <line x1="6" y1="6" x2="18" y2="18"></line>
+                        </svg>
+                      </button>
+                    </div>
+                  }
+                </div>
+              </div>
+            }
+            
+            @if (isUploading) {
+              <div class="loading-overlay">
+                <div class="loading-spinner"></div>
+                <div class="loading-text">正在上传文件...</div>
+              </div>
+            }
+          </div>
+        </div>
+
+        <!-- 分析结果区域 -->
+        <div class="analysis-section" [class.disabled]="uploadedFiles.length === 0">
+          <h3>2. 图片分析</h3>
+          
+          @if (isAnalyzing) {
+            <div class="analysis-loading">
+              <div class="loading-spinner"></div>
+              <div class="loading-text">
+                <h4>正在分析图片...</h4>
+                <p>
+                  @if (uploadedFiles.length > 1) {
+                    正在分析 {{ uploadedFiles.length }} 张图片,系统将合并分析结果
+                  } @else {
+                    系统正在进行色彩、纹理、形态、图案和灯光分析
+                  }
+                </p>
+              </div>
+            </div>
+          } @else if (analysisError) {
+            <div class="analysis-error">
+              <div class="error-icon">❌</div>
+              <div class="error-text">
+                <h4>分析失败</h4>
+                <p>{{ analysisError }}</p>
+                <button class="retry-btn" (click)="retryAnalysis()">重新分析</button>
+              </div>
+            </div>
+          } @else if (analysisResult) {
+            <div class="analysis-result">
+              <h4>分析完成 ✅</h4>
+              <div class="analysis-summary">
+                <div class="summary-item">
+                  <span class="label">主要颜色:</span>
+                  <span class="value">{{ analysisResult.enhancedAnalysis?.colorWheel?.colorDistribution?.length || 0 }} 种</span>
+                </div>
+                <div class="summary-item">
+                  <span class="label">材质类型:</span>
+                  <span class="value">{{ getMaterialName(analysisResult.textureAnalysis?.materialClassification?.primaryMaterial?.category) || '未识别' }}</span>
+                </div>
+                <div class="summary-item">
+                  <span class="label">灯光情绪:</span>
+                  <span class="value">{{ getLightingMoodName(analysisResult.lightingAnalysis?.ambientAnalysis?.lightingMood?.primary) || '未识别' }}</span>
+                </div>
+                <div class="summary-item">
+                  <span class="label">空间形态:</span>
+                  <span class="value">{{ analysisResult.formAnalysis?.geometryAnalysis?.complexity ? getComplexityName(analysisResult.formAnalysis.geometryAnalysis.complexity) : '未识别' }}</span>
+                </div>
+              </div>
+            </div>
+          } @else {
+            <div class="analysis-placeholder">
+              <div class="placeholder-icon">🔍</div>
+              <div class="placeholder-text">
+                <h4>等待分析</h4>
+                <p>请先上传图片,系统将自动开始分析</p>
+              </div>
+            </div>
+          }
+        </div>
+
+        <!-- 需求映射结果区域 -->
+        <div class="mapping-section" [class.disabled]="!analysisResult">
+          <h3>3. 需求映射生成</h3>
+          
+          @if (isGeneratingMapping) {
+            <div class="mapping-loading">
+              <div class="loading-spinner"></div>
+              <div class="loading-text">
+                <h4>正在生成需求映射...</h4>
+                <p>基于分析结果生成场景参数和映射关系</p>
+              </div>
+            </div>
+          } @else if (mappingError) {
+            <div class="mapping-error">
+              <div class="error-icon">❌</div>
+              <div class="error-text">
+                <h4>映射生成失败</h4>
+                <p>{{ mappingError }}</p>
+                <button class="retry-btn" (click)="retryMapping()">重新生成</button>
+              </div>
+            </div>
+          } @else if (requirementMapping) {
+            <div class="mapping-result">
+              <h4>需求映射生成完成 ✅</h4>
+              
+              <!-- 场景生成信息 -->
+              <div class="mapping-section-item">
+                <h5>场景生成</h5>
+                <div class="scene-info">
+                  <div class="info-row">
+                    <span class="label">基础场景:</span>
+                    <span class="value">{{ requirementMapping.sceneGeneration.baseScene }}</span>
+                  </div>
+                  @if (requirementMapping.sceneGeneration.atmospherePreview) {
+                    <div class="atmosphere-preview">
+                      <img [src]="requirementMapping.sceneGeneration.atmospherePreview" 
+                           alt="氛围感预览图"
+                           class="preview-image">
+                      <div class="preview-label">氛围感预览图</div>
+                    </div>
+                  }
+                </div>
+              </div>
+
+              <!-- 参数映射信息 -->
+              <div class="mapping-section-item">
+                <h5>参数映射</h5>
+                <div class="params-grid">
+                  <!-- 颜色参数 -->
+                  <div class="param-group">
+                    <h6>颜色映射</h6>
+                    <div class="color-params">
+                      <div class="param-item">
+                        <span class="label">主要颜色:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.colorParams.primaryColors.length }} 种</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">色彩和谐:</span>
+                        <span class="value">{{ getColorHarmonyName(requirementMapping.parameterMapping.colorParams.colorHarmony) }}</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">饱和度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.colorParams.saturation }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">亮度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.colorParams.brightness }}%</span>
+                      </div>
+                    </div>
+                  </div>
+
+                  <!-- 空间参数 -->
+                  <div class="param-group">
+                    <h6>空间映射</h6>
+                    <div class="space-params">
+                      <div class="param-item">
+                        <span class="label">布局类型:</span>
+                        <span class="value">{{ getLayoutTypeName(requirementMapping.parameterMapping.spaceParams.layout.type) }}</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">空间流线:</span>
+                        <span class="value">{{ getFlowTypeName(requirementMapping.parameterMapping.spaceParams.layout.flow) }}</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">家具比例:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.spaceParams.scale.furniture }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">开放度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.spaceParams.scale.openness }}%</span>
+                      </div>
+                    </div>
+                  </div>
+
+                  <!-- 材质参数 -->
+                  <div class="param-group">
+                    <h6>材质映射</h6>
+                    <div class="material-params">
+                      <div class="param-item">
+                        <span class="label">纹理缩放:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.textureScale }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">反射率:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.reflectivity }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">粗糙度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.roughness }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">金属度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.metallic }}%</span>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+              <!-- 测试结果下载 -->
+              <div class="test-actions">
+                <button class="download-btn" (click)="downloadTestResult()">
+                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
+                    <polyline points="7,10 12,15 17,10"></polyline>
+                    <line x1="12" y1="15" x2="12" y2="3"></line>
+                  </svg>
+                  下载测试结果
+                </button>
+              </div>
+            </div>
+          } @else {
+            <div class="mapping-placeholder">
+              <div class="placeholder-icon">🎯</div>
+              <div class="placeholder-text">
+                <h4>等待映射生成</h4>
+                <p>请先完成图片分析,系统将自动生成需求映射</p>
+              </div>
+            </div>
+          }
+        </div>
+      </div>
+    }
+
+    <!-- 协作验证标签页 -->
+    @if (activeTab === 'collaboration') {
+      <div class="collaboration-section">
+        <!-- 一致性预警 -->
+        @if (consistencyWarnings.length > 0) {
+          <div class="consistency-warning">
+            <svg viewBox="0 0 24 24" fill="currentColor">
+              <path d="M12 2L1 21h22L12 2zm0 3.99L19.53 19H4.47L12 5.99zM11 16h2v2h-2v-2zm0-6h2v4h-2v-4z"/>
+            </svg>
+            <div>
+              <strong>一致性预警:</strong>
+              @for (warning of consistencyWarnings; track warning) {
+                <div>{{ warning }}</div>
+              }
+            </div>
+          </div>
+        }
+
+        <!-- 需求列表 -->
+        <div class="requirements-list">
+          <div class="list-header">
+            <h5>需求项目</h5>
+            <div class="list-controls">
+              <button class="btn-ghost btn-sm" (click)="sortRequirementsByPriority()">按优先级排序</button>
+              <button class="btn-ghost btn-sm" (click)="saveCurrentState()">保存状态</button>
+            </div>
+          </div>
+
+          @for (requirement of requirementItems; track requirement.id) {
+            <div class="requirement-item" [class]="getStatusClass(requirement.status)">
+              <div class="requirement-header">
+                <div class="requirement-info">
+                  <h6>{{ requirement.title }}</h6>
+                  <p>{{ requirement.description }}</p>
+                  <div class="requirement-tags">
+                    @for (tag of requirement.tags; track tag) {
+                      <span class="tag">{{ tag }}</span>
+                    }
+                  </div>
+                </div>
+                <div class="requirement-meta">
+                  <select class="priority-select" 
+                          [value]="requirement.priority" 
+                          (change)="onPriorityChange(requirement.id, $event)">
+                    <option value="high">高优先级</option>
+                    <option value="medium">中优先级</option>
+                    <option value="low">低优先级</option>
+                  </select>
+                  <span class="status-badge" [class]="getStatusClass(requirement.status)">
+                    {{ requirement.status === 'confirmed' ? '已确认' : requirement.status === 'rejected' ? '已拒绝' : '待确认' }}
+                  </span>
+                </div>
+              </div>
+              
+              <div class="requirement-actions">
+                @if (requirement.status === 'pending') {
+                  <button class="btn-success btn-sm" (click)="confirmRequirement(requirement.id)">确认</button>
+                  <button class="btn-danger btn-sm" (click)="rejectRequirement(requirement.id, '需要进一步讨论')">拒绝</button>
+                }
+                <button class="btn-ghost btn-sm" (click)="requirement.showComments = !requirement.showComments">
+                  评论 ({{ getCommentsForRequirement(requirement.id).length }})
+                  @if (hasUnreadComments(requirement.id)) {
+                    <span class="unread-indicator"></span>
+                  }
+                </button>
+              </div>
+
+              @if (requirement.showComments) {
+                <div class="comments-section">
+                  @if (getCommentsForRequirement(requirement.id).length > 0) {
+                    <div class="comments-list">
+                      @for (comment of getCommentsForRequirement(requirement.id); track comment.id) {
+                        <div class="comment-item" [class.resolved]="comment.status === 'resolved'">
+                          <div class="comment-header">
+                            <span class="comment-author">{{ comment.author }}</span>
+                            <span class="comment-role">{{ comment.role === 'designer' ? '设计师' : comment.role === 'customer-service' ? '客服' : '客户' }}</span>
+                            <span class="comment-time">{{ comment.timestamp | date:'MM-dd HH:mm' }}</span>
+                            @if (comment.status === 'pending') {
+                              <button class="btn-ghost btn-xs" (click)="resolveComment(comment.id)">标记已解决</button>
+                            }
+                          </div>
+                          <div class="comment-content">{{ comment.content }}</div>
+                        </div>
+                      }
+                    </div>
+                  }
+                  <div class="add-comment">
+                    <textarea #commentInput placeholder="添加评论..." rows="2"></textarea>
+                    <button class="btn-primary btn-sm" (click)="addCommentToRequirement(requirement.id, commentInput.value); commentInput.value = ''">
+                      发送
+                    </button>
+                  </div>
+                </div>
+              }
+            </div>
+          }
+        </div>
+      </div>
+    }
+
+    <!-- 进度管理标签页 -->
+    @if (activeTab === 'progress') {
+      <div class="progress-section">
+        <!-- 整体进度 -->
+        <div class="overall-progress">
+          <div class="section-header">
+            <h5>整体进度</h5>
+          </div>
+          
+          <div class="progress-container">
+            <div class="progress-visual">
+              <div class="linear-progress">
+                <div class="progress-track">
+                  <div class="progress-bar-fill" [style.width.%]="getProgressPercentage()"></div>
+                </div>
+                <div class="progress-text">{{ getProgressPercentage() }}%</div>
+              </div>
+              
+              <div class="circular-progress">
+                <svg viewBox="0 0 120 120" class="progress-circle">
+                  <circle cx="60" cy="60" r="54" fill="none" stroke="#e5e7eb" stroke-width="8"></circle>
+                  <circle cx="60" cy="60" r="54" fill="none" stroke="#3b82f6" stroke-width="8"
+                    stroke-linecap="round"
+                    [attr.stroke-dasharray]="339.292"
+                    [attr.stroke-dashoffset]="339.292 - (339.292 * getProgressPercentage() / 100)"
+                    transform="rotate(-90 60 60)">
+                  </circle>
+                </svg>
+                <div class="progress-text">
+                  <div class="progress-percentage">{{ getProgressPercentage() }}%</div>
+                  <div class="progress-label">完成度</div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 进度统计 -->
+        <div class="progress-stats">
+          <div class="stat-item">
+              <div class="stat-number">{{ getRequirementCountByStatus('confirmed') }}</div>
+              <div class="stat-label">已确认</div>
+            </div>
+            <div class="stat-item">
+              <div class="stat-number">{{ getRequirementCountByStatus('pending') }}</div>
+              <div class="stat-label">待确认</div>
+            </div>
+            <div class="stat-item">
+              <div class="stat-number">{{ getRequirementCountByStatus('rejected') }}</div>
+              <div class="stat-label">已拒绝</div>
+            </div>
+          <div class="stat-item">
+            <div class="stat-number">{{ getUnresolvedCommentsCount() }}</div>
+            <div class="stat-label">待解决评论</div>
+          </div>
+        </div>
+
+        <!-- 历史状态 -->
+        <div class="history-section">
+          <div class="section-header">
+            <h5>历史状态</h5>
+            <div class="history-controls">
+              <button class="btn-ghost btn-sm" (click)="saveCurrentState()">保存当前状态</button>
+              <select class="history-select" (change)="onHistoryStateChange($event)">
+                <option value="-1">选择历史状态</option>
+                @for (state of historyStates; track $index) {
+                  <option [value]="$index">{{ state.timestamp | date:'MM-dd HH:mm' }} - {{ state.author }}</option>
+                }
+              </select>
+            </div>
+          </div>
+
+          @if (historyStates.length > 0) {
+            <div class="history-timeline">
+              @for (state of historyStates; track $index) {
+                <div class="timeline-item" (click)="restoreHistoryState($index)">
+                  <div class="timeline-marker"></div>
+                  <div class="timeline-content">
+                    <div class="timeline-header">
+                      <span class="timeline-time">{{ state.timestamp | date:'MM-dd HH:mm' }}</span>
+                      <span class="timeline-author">{{ state.author }}</span>
+                    </div>
+                    <div class="timeline-summary">
+                      状态快照:{{ state.requirementItems.length }}个需求项,
+                      {{ getRequirementCountByStatus('confirmed') }}个已确认
+                    </div>
+                  </div>
+                </div>
+              }
+            </div>
+          } @else {
+            <div class="empty-history">
+              <p>暂无历史状态记录</p>
+              <button class="btn-primary btn-sm" (click)="saveCurrentState()">保存当前状态</button>
+            </div>
+          }
+        </div>
+
+        <!-- 需求状态分布 -->
+        <div class="status-distribution">
+          <h5>需求状态分布</h5>
+          <div class="status-bars">
+            <div class="status-bar">
+              <div class="status-info">
+                <span class="status-name">已确认</span>
+                <span class="status-count">{{ getRequirementCountByStatus('confirmed') }}</span>
+              </div>
+              <div class="status-progress">
+                <div class="progress-bar confirmed" 
+                     [style.width.%]="getStatusPercentage('confirmed')"></div>
+              </div>
+            </div>
+            
+            <div class="status-bar">
+              <div class="status-info">
+                <span class="status-name">待确认</span>
+                <span class="status-count">{{ getRequirementCountByStatus('pending') }}</span>
+              </div>
+              <div class="status-progress">
+                <div class="progress-bar pending" 
+                     [style.width.%]="getStatusPercentage('pending')"></div>
+              </div>
+            </div>
+            
+            <div class="status-bar">
+              <div class="status-info">
+                <span class="status-name">已拒绝</span>
+                <span class="status-count">{{ getRequirementCountByStatus('rejected') }}</span>
+              </div>
+              <div class="status-progress">
+                <div class="progress-bar rejected" 
+                     [style.width.%]="getStatusPercentage('rejected')"></div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    }
+  </div>
+</div>
+
+<!-- 保存状态和手动保存按钮 -->
+<div class="save-section">
+  <div class="save-status">
+    <span class="save-icon" [class]="'save-icon-' + saveStatus">{{ getSaveStatusIcon() }}</span>
+    <span class="save-text">{{ getSaveStatusText() }}</span>
+  </div>
+  
+  <div class="save-actions">
+    <button class="btn-secondary" 
+            [disabled]="isSaving || !hasUnsavedChanges"
+            (click)="manualSave()">
+      @if (isSaving) {
+        <span class="loading-spinner"></span>
+        保存中...
+      } @else {
+        手动保存
+      }
+    </button>
+    
+    <div class="auto-save-toggle">
+      <label class="toggle-label">
+        <input type="checkbox" 
+               [(ngModel)]="autoSaveEnabled"
+               class="toggle-input">
+        <span class="toggle-slider"></span>
+        <span class="toggle-text">自动保存</span>
+      </label>
+    </div>
+  </div>
+</div>
+
+<!-- 上传成功弹窗 -->
+<app-upload-success-modal
+  [isVisible]="showUploadSuccessModal"
+  [uploadedFiles]="uploadedFiles"
+  [uploadType]="uploadType"
+  [analysisResult]="colorAnalysisResult"
+  [isAnalyzing]="isAnalyzingColors"
+  (closeModal)="onModalClose()"
+  (analyzeColors)="onAnalyzeColors()"
+  (viewReport)="onViewReport()">
+</app-upload-success-modal>
+
+<!-- 全局提示(支持全屏与角落两模式) -->
+<app-global-prompt
+  [visible]="showGlobalPrompt"
+  [title]="promptTitle"
+  [message]="promptMessage"
+  [mode]="promptMode"
+  [position]="promptPosition"
+  [autoDismissMs]="4000"
+  (closed)="onPromptClose()">
+</app-global-prompt>
+
+<!-- 完整报告全屏覆盖层 -->
+<app-full-report-overlay
+  [visible]="showFullReportOverlay"
+  [colorResult]="colorAnalysisResult"
+  [cadResult]="cadAnalysisResult"
+  (close)="onCloseFullReport()">
+</app-full-report-overlay>

+ 546 - 0
copy/upload-success-modal.component.html

@@ -0,0 +1,546 @@
+<!-- 上传成功弹窗 -->
+@if (isVisible) {
+  <div class="modal-backdrop" 
+       [@backdropAnimation]
+       (click)="onBackdropClick($event)">
+    
+    <div class="modal-container" 
+         [@modalAnimation]
+         [class.mobile]="isMobile"
+         [class.tablet]="isTablet"
+         (click)="$event.stopPropagation()">
+      
+      <!-- 弹窗头部 -->
+      <div class="modal-header" [@fadeInOut]>
+        <div class="header-content">
+          <div class="success-icon" [@successIconAnimation]>
+            <svg width="32" height="32" viewBox="0 0 24 24" fill="none">
+              <circle cx="12" cy="12" r="10" fill="#10B981"/>
+              <path d="m9 12 2 2 4-4" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+            </svg>
+          </div>
+          <div class="header-text">
+            <h2>上传成功!</h2>
+            <p>已成功上传 {{ uploadedFiles.length }} 个文件</p>
+          </div>
+        </div>
+        
+        <button class="close-button" 
+                (click)="onClose()"
+                [@buttonHoverAnimation]="buttonHoverState"
+                (mouseenter)="buttonHoverState = 'hover'"
+                (mouseleave)="buttonHoverState = 'normal'"
+                aria-label="关闭弹窗">
+          <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
+            <path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
+          </svg>
+        </button>
+      </div>
+
+      <!-- 弹窗内容 -->
+      <div class="modal-content">
+        <!-- 已上传文件列表 -->
+        <div class="uploaded-files" [@fileListAnimation]="uploadedFiles.length">
+          <h3>已上传文件</h3>
+          <div class="file-list">
+            @for (file of uploadedFiles; track file.id) {
+              <div class="file-item" [@slideInOut]>
+                <div class="file-icon">
+                  @if (file.type?.startsWith('image/')) {
+                    <img [src]="file.preview || '/assets/images/file-image.svg'" 
+                         [alt]="file.name"
+                         class="file-preview">
+                  } @else {
+                    <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
+                      <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" 
+                            fill="#6B7280"/>
+                      <polyline points="14,2 14,8 20,8" fill="#9CA3AF"/>
+                    </svg>
+                  }
+                </div>
+                <div class="file-info">
+                  <span class="file-name">{{ file.name }}</span>
+                  <span class="file-size">{{ formatFileSize(file.size || 0) }}</span>
+                </div>
+                <div class="file-status success">
+                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
+                    <circle cx="12" cy="12" r="10" fill="#10B981"/>
+                    <path d="m9 12 2 2 4-4" stroke="white" stroke-width="2" stroke-linecap="round"/>
+                  </svg>
+                </div>
+              </div>
+            }
+          </div>
+        </div>
+
+        <!-- 颜色分析功能区域 -->
+        @if (shouldShowColorAnalysis()) {
+          <div class="color-analysis-section">
+            <div class="section-header">
+              <h4>智能颜色分析</h4>
+              <p>基于上传的参考图片,自动提取主要色彩和材质信息</p>
+            </div>
+
+            <!-- 分析按钮或结果 -->
+            @if (!analysisResult && !isAnalyzing) {
+              <div class="analysis-action">
+                <button class="analyze-btn" 
+                        (click)="startColorAnalysis()"
+                        [disabled]="isAnalyzing">
+                  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <circle cx="12" cy="12" r="3"></circle>
+                    <path d="M12 1v6m0 6v6m11-7h-6m-6 0H1m15.5-6.5l-4.24 4.24M7.76 16.24l-4.24 4.24m0-8.48l4.24 4.24m8.48 0l4.24-4.24"></path>
+                  </svg>
+                  开始颜色分析
+                </button>
+              </div>
+            }
+
+            <!-- 分析进行中 -->
+            @if (isAnalyzing) {
+              <div class="analysis-loading">
+                <div class="loading-spinner"></div>
+                <div class="loading-text">
+                  <h5>正在分析图片颜色...</h5>
+                  <p>请稍候,系统正在提取主要色彩信息</p>
+                </div>
+              </div>
+            }
+
+            <!-- 分析结果 -->
+            @if (analysisResult && !isAnalyzing) {
+              <div class="analysis-result">
+                <div class="result-header">
+                  <h5>颜色分析结果</h5>
+                  <span class="result-count">提取到 {{ analysisResult.colors.length }} 种主要颜色</span>
+                </div>
+                
+                <div class="color-palette">
+                  @for (colorInfo of analysisResult.colors; track colorInfo.hex) {
+                    <div class="color-item">
+                      <div class="color-swatch" 
+                           [style.background-color]="colorInfo.hex"
+                           [title]="colorInfo.hex + ' (' + colorInfo.percentage + '%)'">
+                      </div>
+                      <div class="color-info">
+                        <div class="color-value">{{ colorInfo.hex }}</div>
+                        <div class="color-percentage">{{ colorInfo.percentage }}%</div>
+                      </div>
+                    </div>
+                  }
+                </div>
+
+                <!-- 增强分析结果 -->
+                @if (analysisResult.enhancedAnalysis) {
+                  <div class="enhanced-analysis">
+                    <div class="analysis-tabs">
+                      <button class="tab-btn" 
+                              [class.active]="activeTab === 'color'"
+                              (click)="activeTab = 'color'">色彩分析</button>
+                      <button class="tab-btn" 
+                              [class.active]="activeTab === 'form'"
+                              (click)="activeTab = 'form'">形体分析</button>
+                      <button class="tab-btn" 
+                              [class.active]="activeTab === 'texture'"
+                              (click)="activeTab = 'texture'">质感分析</button>
+                      <button class="tab-btn" 
+                              [class.active]="activeTab === 'pattern'"
+                              (click)="activeTab = 'pattern'">纹理分析</button>
+                      <button class="tab-btn" 
+                              [class.active]="activeTab === 'lighting'"
+                              (click)="activeTab = 'lighting'">灯光分析</button>
+                      <button class="tab-btn" 
+                              [class.active]="activeTab === 'mapping'"
+                              (click)="activeTab = 'mapping'">需求映射</button>
+                    </div>
+
+                    <div class="tab-content">
+                      <!-- 色彩分析标签页 -->
+                      @if (activeTab === 'color') {
+                        <div class="color-analysis-tab">
+                          <div class="analysis-section">
+                            <h6>色轮分析</h6>
+                            <div class="color-wheel-info">
+                              <div class="info-item">
+                                <span class="label">主色调:</span>
+                                <span class="value">{{ analysisResult.enhancedAnalysis.colorWheel.dominantHue }}°</span>
+                              </div>
+                              <div class="info-item">
+                                <span class="label">饱和度范围:</span>
+                                <span class="value">{{ (analysisResult.enhancedAnalysis.colorWheel.saturationRange?.min || 0) }}% - {{ (analysisResult.enhancedAnalysis.colorWheel.saturationRange?.max || 100) }}%</span>
+                              </div>
+                            </div>
+                          </div>
+                          
+                          <div class="analysis-section">
+                            <h6>色彩心理学</h6>
+                            <div class="psychology-info">
+                              <div class="mood-atmosphere">
+                                <span class="mood">{{ analysisResult.enhancedAnalysis.colorPsychology.mood || '无' }}</span>
+                                <span class="atmosphere">{{ analysisResult.enhancedAnalysis.colorPsychology.atmosphere || '无' }}</span>
+                              </div>
+                              <div class="suitable-spaces">
+                                @for (space of analysisResult.enhancedAnalysis.colorPsychology.suitableSpaces; track space) {
+                                  <span class="space-tag">{{ space }}</span>
+                                }
+                              </div>
+                            </div>
+                          </div>
+                        </div>
+                      }
+
+                      <!-- 形体分析标签页 -->
+                      @if (activeTab === 'form' && analysisResult.formAnalysis) {
+                        <div class="form-analysis-tab">
+                          <div class="analysis-section">
+                            <h6>线条分析</h6>
+                            <div class="form-info">
+                              <div class="info-item">
+                <span class="label">主导线条:</span>
+                <span class="value">{{ analysisResult.formAnalysis.lineAnalysis.dominantLineType || '无' }}</span>
+              </div>
+                              <div class="info-item">
+                                <span class="label">视觉流动:</span>
+                                <span class="value">{{ analysisResult.formAnalysis.lineAnalysis.visualFlow || '无' }}</span>
+                              </div>
+                            </div>
+                          </div>
+                          
+                          <div class="analysis-section">
+                            <h6>整体评估</h6>
+                            <div class="overall-info">
+                              <div class="metric">
+                                <span class="metric-label">复杂度</span>
+                                <div class="metric-bar">
+                                  <div class="metric-fill" [style.width.%]="analysisResult.formAnalysis.overallAssessment.complexity"></div>
+                                </div>
+                                <span class="metric-value">{{ analysisResult.formAnalysis.overallAssessment.complexity || 0 }}%</span>
+                              </div>
+                              <div class="metric">
+                                <span class="metric-label">视觉冲击</span>
+                                <div class="metric-bar">
+                                  <div class="metric-fill" [style.width.%]="analysisResult.formAnalysis.overallAssessment.visualImpact"></div>
+                                </div>
+                                <span class="metric-value">{{ analysisResult.formAnalysis.overallAssessment.visualImpact || 0 }}%</span>
+                              </div>
+                            </div>
+                          </div>
+                        </div>
+                      }
+
+                      <!-- 质感分析标签页 -->
+                      @if (activeTab === 'texture' && analysisResult.textureAnalysis) {
+                        <div class="texture-analysis-tab">
+                          <div class="analysis-section">
+                            <h6>表面属性</h6>
+                            <div class="texture-properties">
+                              @for (property of getTextureProperties(); track property.name) {
+                                <div class="property-item">
+                                  <span class="property-name">{{ property.name }}</span>
+                                  <div class="property-bar">
+                                    <div class="property-fill" [style.width.%]="property.value"></div>
+                                  </div>
+                                  <span class="property-value">{{ property.value }}%</span>
+                                </div>
+                              }
+                            </div>
+                          </div>
+                          
+                          <div class="analysis-section">
+                            <h6>材质分类</h6>
+                            <div class="material-tags">
+                              @for (material of analysisResult.textureAnalysis.materialClassification.primaryMaterials; track material; let i = $index) {
+                                <span class="material-tag primary">{{ material }}</span>
+                              }
+                              @for (material of analysisResult.textureAnalysis.materialClassification.secondaryMaterials; track material; let i = $index) {
+                                <span class="material-tag secondary">{{ material }}</span>
+                              }
+                            </div>
+                          </div>
+                        </div>
+                      }
+
+                      <!-- 纹理分析标签页 -->
+                      @if (activeTab === 'pattern' && analysisResult.patternAnalysis) {
+                        <div class="pattern-analysis-tab">
+                          <div class="analysis-section">
+                            <h6>图案识别</h6>
+                            <div class="pattern-info">
+                              <div class="info-item">
+                <span class="label">主要图案:</span>
+                <span class="value">{{ analysisResult.patternAnalysis.patternRecognition.primaryPatterns[0]?.type || '无' }}</span>
+              </div>
+                              <div class="info-item">
+                                <span class="label">复杂度:</span>
+                                <span class="value">{{ analysisResult.patternAnalysis.patternRecognition.patternComplexity.level || '无' }}</span>
+                              </div>
+                            </div>
+                          </div>
+                          
+                          <div class="analysis-section">
+                            <h6>视觉节奏</h6>
+                            <div class="rhythm-info">
+                              <div class="rhythm-tags">
+                                <span class="rhythm-tag">{{ analysisResult.patternAnalysis.visualRhythm.rhythmType.primary || '无' }}</span>
+                                <span class="rhythm-tag">{{ analysisResult.patternAnalysis.visualRhythm.movement.direction || '无' }}</span>
+                              </div>
+                            </div>
+                          </div>
+                        </div>
+                      }
+
+                      <!-- 灯光分析标签页 -->
+                      @if (activeTab === 'lighting' && analysisResult.lightingAnalysis) {
+                        <div class="lighting-analysis-tab">
+                          <div class="analysis-section">
+                            <h6>光源识别</h6>
+                            <div class="lighting-info">
+                              <div class="info-item">
+                <span class="label">主要光源:</span>
+                <span class="value">{{ analysisResult.lightingAnalysis.lightSourceIdentification.primarySources?.join(', ') || '无' }}</span>
+              </div>
+                              <div class="info-item">
+                                <span class="label">灯光设置:</span>
+                                <span class="value">{{ analysisResult.lightingAnalysis.lightSourceIdentification.lightingSetup || '无' }}</span>
+                              </div>
+                            </div>
+                          </div>
+                          
+                          <div class="analysis-section">
+                            <h6>环境分析</h6>
+                            <div class="ambient-info">
+                              <div class="info-item">
+                                <span class="label">环境光:</span>
+                                <span class="value">{{ analysisResult.lightingAnalysis.ambientAnalysis.ambientLight || '无' }}</span>
+                              </div>
+                              <div class="info-item">
+                                <span class="label">灯光情绪:</span>
+                                <span class="value">{{ analysisResult.lightingAnalysis.ambientAnalysis.lightingMood || '无' }}</span>
+                              </div>
+                            </div>
+                          </div>
+                        </div>
+                      }
+
+                      <!-- 需求映射标签页 -->
+                      @if (activeTab === 'mapping') {
+                        <div class="mapping-analysis-tab">
+                          @if (isGeneratingMapping) {
+                            <div class="mapping-loading">
+                              <div class="loading-spinner"></div>
+                              <div class="loading-text">
+                                <h6>正在生成需求映射...</h6>
+                                <p>基于分析结果生成场景参数和映射关系</p>
+                              </div>
+                            </div>
+                          } @else if (mappingError) {
+                            <div class="mapping-error">
+                              <div class="error-icon">⚠️</div>
+                              <div class="error-text">
+                                <h6>需求映射生成失败</h6>
+                                <p>{{ mappingError }}</p>
+                                <button class="retry-btn" (click)="regenerateRequirementMapping()">
+                                  重新生成
+                                </button>
+                              </div>
+                            </div>
+                          } @else if (requirementMapping) {
+                            <div class="mapping-result">
+                              <!-- 场景生成部分 -->
+                              <div class="analysis-section">
+                                <h6>场景生成</h6>
+                                <div class="scene-info">
+                                  <div class="info-item">
+                                    <span class="label">基础场景:</span>
+                                    <span class="value">{{ requirementMapping.sceneGeneration.baseScene }}</span>
+                                  </div>
+                                  @if (requirementMapping.sceneGeneration.atmospherePreview) {
+                                    <div class="atmosphere-preview">
+                                      <img [src]="requirementMapping.sceneGeneration.atmospherePreview" 
+                                           alt="氛围感预览图"
+                                           class="preview-image">
+                                    </div>
+                                  }
+                                </div>
+                              </div>
+
+                              <!-- 参数映射部分 -->
+                              <div class="analysis-section">
+                                <h6>参数映射</h6>
+                                
+                                <!-- 颜色参数 -->
+                                <div class="mapping-subsection">
+                                  <h6>颜色映射</h6>
+                                  <div class="color-mappings">
+                                    @for (mapping of requirementMapping.parameterMapping.colorParams.primaryColors; track mapping.originalColor) {
+                                      <div class="color-mapping-item">
+                                        <div class="color-swatch" [style.background-color]="mapping.originalColor"></div>
+                                        <span class="mapping-arrow">→</span>
+                                        <span class="target-material">{{ mapping.mappedColor }}</span>
+                                        <span class="confidence">({{ mapping.weight }}%)</span>
+                                      </div>
+                                    }
+                                  </div>
+                                </div>
+
+                                <!-- 空间参数 -->
+                                <div class="mapping-subsection">
+                                  <h6>空间映射</h6>
+                                  <div class="space-info">
+                                    <div class="info-item">
+                                      <span class="label">空间类型:</span>
+                                      <span class="value">{{ requirementMapping.parameterMapping.spaceParams.layout?.type }}</span>
+                                    </div>
+                                    <div class="info-item">
+                                      <span class="label">开放程度:</span>
+                                      <span class="value">{{ requirementMapping.parameterMapping.spaceParams.scale?.openness }}%</span>
+                                    </div>
+                                  </div>
+                                </div>
+
+                                <!-- 材质参数 -->
+                                <div class="mapping-subsection">
+                                  <h6>材质映射</h6>
+                                  <div class="material-mappings">
+                                    @for (mapping of requirementMapping.parameterMapping.materialParams.surfaceMaterials; track mapping.category) {
+                                      <div class="material-mapping-item">
+                                        <span class="source-texture">{{ mapping.category }}</span>
+                                        <span class="mapping-arrow">→</span>
+                                        <span class="target-material">{{ mapping.subtype }}</span>
+                                        <span class="properties">{{ mapping.finish }}, {{ mapping.priority }}</span>
+                                      </div>
+                                    }
+                                  </div>
+                                </div>
+                              </div>
+
+                              <!-- 重新生成按钮 -->
+                              <div class="mapping-actions">
+                                <button class="regenerate-btn" (click)="regenerateRequirementMapping()">
+                                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                                    <polyline points="23 4 23 10 17 10"></polyline>
+                                    <polyline points="1 20 1 14 7 14"></polyline>
+                                    <path d="m3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
+                                  </svg>
+                                  重新生成映射
+                                </button>
+                              </div>
+                            </div>
+                          } @else {
+                            <div class="mapping-placeholder">
+                              <div class="placeholder-icon">🎯</div>
+                              <div class="placeholder-text">
+                                <h6>需求映射未生成</h6>
+                                <p>请先完成分析,系统将自动生成需求映射</p>
+                              </div>
+                            </div>
+                          }
+                        </div>
+                      }
+                    </div>
+                  </div>
+                }
+
+                <div class="result-actions">
+                  <button class="view-report-btn" (click)="onViewReportClick()">
+                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                      <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+                      <polyline points="14,2 14,8 20,8"></polyline>
+                    </svg>
+                    查看完整报告
+                  </button>
+                </div>
+
+                <!-- 颜色描述文字区域 -->
+                <div class="color-description-section">
+                  <div class="description-header">
+                    <h6>颜色描述文字</h6>
+                    <p>适用于即梦等AI工具的颜色描述</p>
+                  </div>
+                  
+                  <div class="description-content">
+                    <div class="description-text" 
+                         [class.has-content]="generateColorDescription()"
+                         #descriptionText>
+                      {{ generateColorDescription() || '暂无颜色分析结果' }}
+                    </div>
+                    
+                    @if (generateColorDescription()) {
+                      <button class="copy-description-btn" 
+                              (click)="copyColorDescription()"
+                              [class.copied]="copySuccess"
+                              title="复制颜色描述">
+                        @if (copySuccess) {
+                          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                            <polyline points="20,6 9,17 4,12"></polyline>
+                          </svg>
+                          已复制
+                        } @else {
+                          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                            <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
+                            <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
+                          </svg>
+                          复制
+                        }
+                      </button>
+                    }
+                  </div>
+                  
+                  <div class="description-tips">
+                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                      <circle cx="12" cy="12" r="10"></circle>
+                      <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
+                      <line x1="12" y1="17" x2="12.01" y2="17"></line>
+                    </svg>
+                    <span>复制后可直接粘贴到即梦等AI工具中,用于生成对应颜色风格的室内设计</span>
+                  </div>
+                </div>
+              </div>
+            }
+
+            <!-- 分析错误 -->
+            @if (analysisError) {
+              <div class="analysis-error">
+                <div class="error-icon">
+                  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <circle cx="12" cy="12" r="10"></circle>
+                    <line x1="15" y1="9" x2="9" y2="15"></line>
+                    <line x1="9" y1="9" x2="15" y2="15"></line>
+                  </svg>
+                </div>
+                <div class="error-text">
+                  <h5>分析失败</h5>
+                  <p>{{ analysisError }}</p>
+                </div>
+                <button class="retry-btn" (click)="startColorAnalysis()">
+                  重试
+                </button>
+              </div>
+            }
+          </div>
+        }
+      </div>
+
+      <!-- 弹窗底部 -->
+      <div class="modal-footer">
+        <div class="footer-actions">
+          <button class="secondary-btn" (click)="onClose()">
+            关闭
+          </button>
+          @if (shouldShowColorAnalysis() && !analysisResult) {
+            <button class="primary-btn" 
+                    (click)="startColorAnalysis()"
+                    [disabled]="isAnalyzing">
+              @if (isAnalyzing) {
+                <div class="btn-spinner"></div>
+                分析中...
+              } @else {
+                开始分析
+              }
+            </button>
+          }
+        </div>
+      </div>
+    </div>
+  </div>
+}

+ 0 - 5
src/app/app.routes.ts

@@ -173,11 +173,6 @@ export const routes: Routes = [
     loadComponent: () => import('./test-atmosphere-preview/test-atmosphere-preview').then(m => m.TestAtmospherePreviewComponent),
     loadComponent: () => import('./test-atmosphere-preview/test-atmosphere-preview').then(m => m.TestAtmospherePreviewComponent),
     title: '氛围感预览图测试'
     title: '氛围感预览图测试'
   },
   },
-  {
-    path: 'test-requirement-mapping',
-    loadComponent: () => import('./components/test-requirement-mapping/test-requirement-mapping.component').then(m => m.TestRequirementMappingComponent),
-    title: '需求映射功能测试'
-  },
 
 
   // 默认路由重定向到登录页
   // 默认路由重定向到登录页
   { path: '', component: LoginPage, pathMatch: 'full' },
   { path: '', component: LoginPage, pathMatch: 'full' },

+ 0 - 284
src/app/components/test-requirement-mapping/test-requirement-mapping.component.html

@@ -1,284 +0,0 @@
-<div class="test-requirement-mapping">
-  <div class="test-header">
-    <h2>需求映射功能完整工作流程测试</h2>
-    <p class="test-description">
-      测试从图片上传到生成需求映射的完整流程,包括图片分析、参数映射和氛围预览生成
-    </p>
-    <button class="reset-btn" (click)="resetTest()">重置测试</button>
-  </div>
-
-  <!-- 测试步骤进度 -->
-  <div class="test-progress">
-    <h3>测试进度</h3>
-    <div class="steps-container">
-      @for (step of testSteps; track step.id) {
-        <div class="step-item" [class]="getStepClass(step.status)">
-          <div class="step-icon">{{ getStepIcon(step.status) }}</div>
-          <div class="step-info">
-            <div class="step-name">{{ step.name }}</div>
-            <div class="step-status">
-              @switch (step.status) {
-                @case ('pending') { 等待中 }
-                @case ('in-progress') { 进行中... }
-                @case ('completed') { 已完成 }
-                @case ('error') { 失败 }
-              }
-            </div>
-          </div>
-        </div>
-      }
-    </div>
-  </div>
-
-  <!-- 文件上传区域 -->
-  <div class="upload-section">
-    <h3>1. 图片上传</h3>
-    <div class="upload-area" [class.uploading]="isUploading">
-      @if (uploadedFiles.length === 0) {
-        <div class="upload-dropzone">
-          <div class="upload-icon">📁</div>
-          <div class="upload-text">选择图片文件进行测试</div>
-          <div class="upload-hint">支持 JPG、PNG 格式,可选择多张图片</div>
-          <input type="file" 
-                 accept="image/*" 
-                 multiple 
-                 (change)="onFileSelected($event)"
-                 class="file-input">
-        </div>
-      } @else {
-        <div class="uploaded-files">
-          <h4>已上传文件 ({{ uploadedFiles.length }})</h4>
-          <div class="files-grid">
-            @for (file of uploadedFiles; track file.id) {
-              <div class="file-item">
-                <img [src]="file.preview || file.url" [alt]="file.name" class="file-preview">
-                <div class="file-info">
-                  <div class="file-name">{{ file.name }}</div>
-                  <div class="file-size">{{ (file.size! / 1024 / 1024).toFixed(2) }} MB</div>
-                </div>
-              </div>
-            }
-          </div>
-        </div>
-      }
-      
-      @if (isUploading) {
-        <div class="loading-overlay">
-          <div class="loading-spinner"></div>
-          <div class="loading-text">正在上传文件...</div>
-        </div>
-      }
-    </div>
-  </div>
-
-  <!-- 分析结果区域 -->
-  <div class="analysis-section" [class.disabled]="uploadedFiles.length === 0">
-    <h3>2. 图片分析</h3>
-    
-    @if (isAnalyzing) {
-      <div class="analysis-loading">
-        <div class="loading-spinner"></div>
-        <div class="loading-text">
-          <h4>正在分析图片...</h4>
-          <p>系统正在进行色彩、纹理、形态、图案和灯光分析</p>
-        </div>
-      </div>
-    } @else if (analysisError) {
-      <div class="analysis-error">
-        <div class="error-icon">❌</div>
-        <div class="error-text">
-          <h4>分析失败</h4>
-          <p>{{ analysisError }}</p>
-          <button class="retry-btn" (click)="retryAnalysis()">重新分析</button>
-        </div>
-      </div>
-    } @else if (analysisResult) {
-      <div class="analysis-result">
-        <h4>分析完成 ✅</h4>
-        <div class="analysis-summary">
-          <div class="summary-item">
-            <span class="label">主要颜色:</span>
-            <span class="value">{{ analysisResult.enhancedAnalysis?.colorWheel?.colorDistribution?.length || 0 }} 种</span>
-          </div>
-          <div class="summary-item">
-            <span class="label">材质类型:</span>
-            <span class="value">{{ analysisResult.textureAnalysis?.materialClassification?.primary || '未识别' }}</span>
-          </div>
-          <div class="summary-item">
-            <span class="label">灯光情绪:</span>
-            <span class="value">{{ analysisResult.lightingAnalysis?.ambientAnalysis?.lightingMood || '未识别' }}</span>
-          </div>
-          <div class="summary-item">
-            <span class="label">空间形态:</span>
-            <span class="value">{{ analysisResult.formAnalysis?.spaceAnalysis?.spaceType || '未识别' }}</span>
-          </div>
-        </div>
-      </div>
-    } @else {
-      <div class="analysis-placeholder">
-        <div class="placeholder-icon">🔍</div>
-        <div class="placeholder-text">
-          <h4>等待分析</h4>
-          <p>请先上传图片,系统将自动开始分析</p>
-        </div>
-      </div>
-    }
-  </div>
-
-  <!-- 需求映射结果区域 -->
-  <div class="mapping-section" [class.disabled]="!analysisResult">
-    <h3>3. 需求映射生成</h3>
-    
-    @if (isGeneratingMapping) {
-      <div class="mapping-loading">
-        <div class="loading-spinner"></div>
-        <div class="loading-text">
-          <h4>正在生成需求映射...</h4>
-          <p>基于分析结果生成场景参数和映射关系</p>
-        </div>
-      </div>
-    } @else if (mappingError) {
-      <div class="mapping-error">
-        <div class="error-icon">❌</div>
-        <div class="error-text">
-          <h4>映射生成失败</h4>
-          <p>{{ mappingError }}</p>
-          <button class="retry-btn" (click)="retryMapping()">重新生成</button>
-        </div>
-      </div>
-    } @else if (requirementMapping) {
-      <div class="mapping-result">
-        <h4>需求映射生成完成 ✅</h4>
-        
-        <!-- 场景生成信息 -->
-        <div class="mapping-section-item">
-          <h5>场景生成</h5>
-          <div class="scene-info">
-            <div class="info-row">
-              <span class="label">基础场景:</span>
-              <span class="value">{{ requirementMapping.sceneGeneration.baseScene }}</span>
-            </div>
-            @if (requirementMapping.sceneGeneration.atmospherePreview) {
-              <div class="atmosphere-preview">
-                <img [src]="requirementMapping.sceneGeneration.atmospherePreview" 
-                     alt="氛围感预览图"
-                     class="preview-image">
-                <div class="preview-label">氛围感预览图</div>
-              </div>
-            }
-          </div>
-        </div>
-
-        <!-- 参数映射信息 -->
-        <div class="mapping-section-item">
-          <h5>参数映射</h5>
-          <div class="params-grid">
-            <!-- 颜色参数 -->
-            <div class="param-group">
-              <h6>颜色映射</h6>
-              <div class="color-params">
-                <div class="param-item">
-                  <span class="label">主要颜色:</span>
-                  <span class="value">{{ requirementMapping.parameterMapping.colorParams.primaryColors.length }} 种</span>
-                </div>
-                <div class="param-item">
-                  <span class="label">色彩和谐:</span>
-                  <span class="value">{{ requirementMapping.parameterMapping.colorParams.colorHarmony }}</span>
-                </div>
-                <div class="param-item">
-                  <span class="label">饱和度:</span>
-                  <span class="value">{{ requirementMapping.parameterMapping.colorParams.saturation }}%</span>
-                </div>
-                <div class="param-item">
-                  <span class="label">亮度:</span>
-                  <span class="value">{{ requirementMapping.parameterMapping.colorParams.brightness }}%</span>
-                </div>
-              </div>
-            </div>
-
-            <!-- 空间参数 -->
-            <div class="param-group">
-              <h6>空间映射</h6>
-              <div class="space-params">
-                <div class="param-item">
-                  <span class="label">布局类型:</span>
-                  <span class="value">{{ requirementMapping.parameterMapping.spaceParams.layout.type }}</span>
-                </div>
-                <div class="param-item">
-                  <span class="label">空间流线:</span>
-                  <span class="value">{{ requirementMapping.parameterMapping.spaceParams.layout.flow }}</span>
-                </div>
-                <div class="param-item">
-                  <span class="label">家具比例:</span>
-                  <span class="value">{{ requirementMapping.parameterMapping.spaceParams.scale.furniture }}%</span>
-                </div>
-                <div class="param-item">
-                  <span class="label">开放度:</span>
-                  <span class="value">{{ requirementMapping.parameterMapping.spaceParams.scale.openness }}%</span>
-                </div>
-              </div>
-            </div>
-
-            <!-- 材质参数 -->
-            <div class="param-group">
-              <h6>材质映射</h6>
-              <div class="material-params">
-                <div class="param-item">
-                  <span class="label">纹理缩放:</span>
-                  <span class="value">{{ requirementMapping.parameterMapping.materialParams.textureScale }}%</span>
-                </div>
-                <div class="param-item">
-                  <span class="label">反射率:</span>
-                  <span class="value">{{ requirementMapping.parameterMapping.materialParams.reflectivity }}%</span>
-                </div>
-                <div class="param-item">
-                  <span class="label">粗糙度:</span>
-                  <span class="value">{{ requirementMapping.parameterMapping.materialParams.roughness }}%</span>
-                </div>
-                <div class="param-item">
-                  <span class="label">金属度:</span>
-                  <span class="value">{{ requirementMapping.parameterMapping.materialParams.metallic }}%</span>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-
-        <!-- 测试结果下载 -->
-        <div class="test-actions">
-          <button class="download-btn" (click)="downloadTestResult()">
-            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-              <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
-              <polyline points="7,10 12,15 17,10"></polyline>
-              <line x1="12" y1="15" x2="12" y2="3"></line>
-            </svg>
-            下载测试结果
-          </button>
-        </div>
-      </div>
-    } @else {
-      <div class="mapping-placeholder">
-        <div class="placeholder-icon">🎯</div>
-        <div class="placeholder-text">
-          <h4>等待映射生成</h4>
-          <p>请先完成图片分析,系统将自动生成需求映射</p>
-        </div>
-      </div>
-    }
-  </div>
-
-  <!-- 上传成功模态框 -->
-  @if (showUploadModal) {
-    <app-upload-success-modal
-      [isVisible]="showUploadModal"
-      [uploadedFiles]="uploadedFiles"
-      [uploadType]="'image'"
-      [analysisResult]="analysisResult"
-      [isAnalyzing]="isAnalyzing"
-      (closeModal)="onCloseModal()"
-      (analyzeColors)="onAnalyzeColors($event)"
-      (viewReport)="onViewReport($event)"
-      (generateRequirementMapping)="onGenerateRequirementMapping($event)">
-    </app-upload-success-modal>
-  }
-</div>

+ 0 - 558
src/app/components/test-requirement-mapping/test-requirement-mapping.component.scss

@@ -1,558 +0,0 @@
-.test-requirement-mapping {
-  max-width: 1200px;
-  margin: 0 auto;
-  padding: 24px;
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-
-  .test-header {
-    text-align: center;
-    margin-bottom: 32px;
-    padding: 24px;
-    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-    border-radius: 12px;
-    color: white;
-
-    h2 {
-      margin: 0 0 12px 0;
-      font-size: 28px;
-      font-weight: 600;
-    }
-
-    .test-description {
-      margin: 0 0 20px 0;
-      font-size: 16px;
-      opacity: 0.9;
-      line-height: 1.5;
-    }
-
-    .reset-btn {
-      background: rgba(255, 255, 255, 0.2);
-      border: 1px solid rgba(255, 255, 255, 0.3);
-      color: white;
-      padding: 8px 16px;
-      border-radius: 6px;
-      cursor: pointer;
-      font-size: 14px;
-      transition: all 0.2s ease;
-
-      &:hover {
-        background: rgba(255, 255, 255, 0.3);
-        transform: translateY(-1px);
-      }
-    }
-  }
-
-  .test-progress {
-    margin-bottom: 32px;
-    padding: 20px;
-    background: #f8f9fa;
-    border-radius: 8px;
-    border: 1px solid #e9ecef;
-
-    h3 {
-      margin: 0 0 16px 0;
-      font-size: 18px;
-      color: #495057;
-    }
-
-    .steps-container {
-      display: grid;
-      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
-      gap: 16px;
-    }
-
-    .step-item {
-      display: flex;
-      align-items: center;
-      padding: 12px;
-      background: white;
-      border-radius: 6px;
-      border: 2px solid #e9ecef;
-      transition: all 0.2s ease;
-
-      &.step-pending {
-        border-color: #e9ecef;
-        .step-icon { color: #6c757d; }
-      }
-
-      &.step-in-progress {
-        border-color: #ffc107;
-        background: #fff8e1;
-        .step-icon { color: #ffc107; }
-      }
-
-      &.step-completed {
-        border-color: #28a745;
-        background: #f8fff9;
-        .step-icon { color: #28a745; }
-      }
-
-      &.step-error {
-        border-color: #dc3545;
-        background: #fff5f5;
-        .step-icon { color: #dc3545; }
-      }
-
-      .step-icon {
-        font-size: 20px;
-        margin-right: 12px;
-      }
-
-      .step-info {
-        .step-name {
-          font-weight: 500;
-          font-size: 14px;
-          color: #495057;
-        }
-
-        .step-status {
-          font-size: 12px;
-          color: #6c757d;
-          margin-top: 2px;
-        }
-      }
-    }
-  }
-
-  .upload-section,
-  .analysis-section,
-  .mapping-section {
-    margin-bottom: 32px;
-    padding: 24px;
-    background: white;
-    border-radius: 8px;
-    border: 1px solid #e9ecef;
-    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-
-    &.disabled {
-      opacity: 0.6;
-      pointer-events: none;
-    }
-
-    h3 {
-      margin: 0 0 20px 0;
-      font-size: 20px;
-      color: #495057;
-      border-bottom: 2px solid #e9ecef;
-      padding-bottom: 8px;
-    }
-  }
-
-  .upload-area {
-    position: relative;
-    min-height: 200px;
-
-    .upload-dropzone {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      justify-content: center;
-      min-height: 200px;
-      border: 2px dashed #dee2e6;
-      border-radius: 8px;
-      background: #f8f9fa;
-      cursor: pointer;
-      transition: all 0.2s ease;
-      position: relative;
-
-      &:hover {
-        border-color: #667eea;
-        background: #f0f4ff;
-      }
-
-      .upload-icon {
-        font-size: 48px;
-        margin-bottom: 16px;
-        opacity: 0.7;
-      }
-
-      .upload-text {
-        font-size: 18px;
-        font-weight: 500;
-        color: #495057;
-        margin-bottom: 8px;
-      }
-
-      .upload-hint {
-        font-size: 14px;
-        color: #6c757d;
-      }
-
-      .file-input {
-        position: absolute;
-        top: 0;
-        left: 0;
-        width: 100%;
-        height: 100%;
-        opacity: 0;
-        cursor: pointer;
-      }
-    }
-
-    .uploaded-files {
-      h4 {
-        margin: 0 0 16px 0;
-        font-size: 16px;
-        color: #495057;
-      }
-
-      .files-grid {
-        display: grid;
-        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
-        gap: 16px;
-      }
-
-      .file-item {
-        border: 1px solid #e9ecef;
-        border-radius: 6px;
-        overflow: hidden;
-        background: white;
-
-        .file-preview {
-          width: 100%;
-          height: 120px;
-          object-fit: cover;
-        }
-
-        .file-info {
-          padding: 8px;
-
-          .file-name {
-            font-size: 12px;
-            font-weight: 500;
-            color: #495057;
-            margin-bottom: 4px;
-            white-space: nowrap;
-            overflow: hidden;
-            text-overflow: ellipsis;
-          }
-
-          .file-size {
-            font-size: 11px;
-            color: #6c757d;
-          }
-        }
-      }
-    }
-
-    .loading-overlay {
-      position: absolute;
-      top: 0;
-      left: 0;
-      right: 0;
-      bottom: 0;
-      background: rgba(255, 255, 255, 0.9);
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      justify-content: center;
-      border-radius: 8px;
-    }
-  }
-
-  .analysis-loading,
-  .mapping-loading {
-    display: flex;
-    align-items: center;
-    padding: 24px;
-    background: #f0f4ff;
-    border-radius: 6px;
-    border: 1px solid #e3f2fd;
-
-    .loading-text {
-      margin-left: 16px;
-
-      h4 {
-        margin: 0 0 8px 0;
-        font-size: 16px;
-        color: #495057;
-      }
-
-      p {
-        margin: 0;
-        font-size: 14px;
-        color: #6c757d;
-      }
-    }
-  }
-
-  .analysis-error,
-  .mapping-error {
-    display: flex;
-    align-items: center;
-    padding: 24px;
-    background: #fff5f5;
-    border-radius: 6px;
-    border: 1px solid #ffebee;
-
-    .error-icon {
-      font-size: 24px;
-      margin-right: 16px;
-    }
-
-    .error-text {
-      flex: 1;
-
-      h4 {
-        margin: 0 0 8px 0;
-        font-size: 16px;
-        color: #dc3545;
-      }
-
-      p {
-        margin: 0 0 12px 0;
-        font-size: 14px;
-        color: #6c757d;
-      }
-
-      .retry-btn {
-        background: #dc3545;
-        color: white;
-        border: none;
-        padding: 8px 16px;
-        border-radius: 4px;
-        cursor: pointer;
-        font-size: 14px;
-        transition: background 0.2s ease;
-
-        &:hover {
-          background: #c82333;
-        }
-      }
-    }
-  }
-
-  .analysis-result,
-  .mapping-result {
-    h4 {
-      margin: 0 0 16px 0;
-      font-size: 18px;
-      color: #28a745;
-      display: flex;
-      align-items: center;
-    }
-
-    .analysis-summary {
-      display: grid;
-      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
-      gap: 16px;
-      margin-top: 16px;
-    }
-
-    .summary-item {
-      display: flex;
-      justify-content: space-between;
-      padding: 12px;
-      background: #f8f9fa;
-      border-radius: 4px;
-
-      .label {
-        font-weight: 500;
-        color: #495057;
-      }
-
-      .value {
-        color: #6c757d;
-      }
-    }
-  }
-
-  .mapping-section-item {
-    margin-bottom: 24px;
-
-    h5 {
-      margin: 0 0 16px 0;
-      font-size: 16px;
-      color: #495057;
-      border-bottom: 1px solid #e9ecef;
-      padding-bottom: 8px;
-    }
-
-    .scene-info {
-      .info-row {
-        display: flex;
-        justify-content: space-between;
-        padding: 8px 0;
-        border-bottom: 1px solid #f8f9fa;
-
-        .label {
-          font-weight: 500;
-          color: #495057;
-        }
-
-        .value {
-          color: #6c757d;
-        }
-      }
-
-      .atmosphere-preview {
-        margin-top: 16px;
-        text-align: center;
-
-        .preview-image {
-          max-width: 300px;
-          max-height: 200px;
-          border-radius: 6px;
-          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-        }
-
-        .preview-label {
-          margin-top: 8px;
-          font-size: 14px;
-          color: #6c757d;
-        }
-      }
-    }
-
-    .params-grid {
-      display: grid;
-      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
-      gap: 20px;
-    }
-
-    .param-group {
-      background: #f8f9fa;
-      padding: 16px;
-      border-radius: 6px;
-
-      h6 {
-        margin: 0 0 12px 0;
-        font-size: 14px;
-        font-weight: 600;
-        color: #495057;
-      }
-
-      .param-item {
-        display: flex;
-        justify-content: space-between;
-        padding: 6px 0;
-        border-bottom: 1px solid #e9ecef;
-
-        &:last-child {
-          border-bottom: none;
-        }
-
-        .label {
-          font-size: 13px;
-          color: #6c757d;
-        }
-
-        .value {
-          font-size: 13px;
-          font-weight: 500;
-          color: #495057;
-        }
-      }
-    }
-  }
-
-  .analysis-placeholder,
-  .mapping-placeholder {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    min-height: 150px;
-    color: #6c757d;
-
-    .placeholder-icon {
-      font-size: 48px;
-      margin-bottom: 16px;
-      opacity: 0.7;
-    }
-
-    .placeholder-text {
-      text-align: center;
-
-      h4 {
-        margin: 0 0 8px 0;
-        font-size: 16px;
-      }
-
-      p {
-        margin: 0;
-        font-size: 14px;
-        opacity: 0.8;
-      }
-    }
-  }
-
-  .test-actions {
-    margin-top: 24px;
-    text-align: center;
-
-    .download-btn {
-      display: inline-flex;
-      align-items: center;
-      gap: 8px;
-      background: #667eea;
-      color: white;
-      border: none;
-      padding: 12px 24px;
-      border-radius: 6px;
-      cursor: pointer;
-      font-size: 14px;
-      font-weight: 500;
-      transition: all 0.2s ease;
-
-      &:hover {
-        background: #5a6fd8;
-        transform: translateY(-1px);
-      }
-
-      svg {
-        width: 16px;
-        height: 16px;
-      }
-    }
-  }
-
-  .loading-spinner {
-    width: 24px;
-    height: 24px;
-    border: 2px solid #f3f3f3;
-    border-top: 2px solid #667eea;
-    border-radius: 50%;
-    animation: spin 1s linear infinite;
-  }
-
-  @keyframes spin {
-    0% { transform: rotate(0deg); }
-    100% { transform: rotate(360deg); }
-  }
-
-  // 响应式设计
-  @media (max-width: 768px) {
-    padding: 16px;
-
-    .test-header {
-      padding: 16px;
-
-      h2 {
-        font-size: 24px;
-      }
-
-      .test-description {
-        font-size: 14px;
-      }
-    }
-
-    .upload-section,
-    .analysis-section,
-    .mapping-section {
-      padding: 16px;
-    }
-
-    .steps-container {
-      grid-template-columns: 1fr;
-    }
-
-    .params-grid {
-      grid-template-columns: 1fr;
-    }
-
-    .analysis-summary {
-      grid-template-columns: 1fr;
-    }
-  }
-}

+ 0 - 299
src/app/components/test-requirement-mapping/test-requirement-mapping.component.ts

@@ -1,299 +0,0 @@
-import { Component, OnInit, OnDestroy } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { FormsModule } from '@angular/forms';
-import { Subscription } from 'rxjs';
-import { RequirementMappingService } from '../../services/requirement-mapping.service';
-import { ColorAnalysisService, ColorAnalysisResult } from '../../shared/services/color-analysis.service';
-import { RequirementMapping, SceneTemplate } from '../../models/requirement-mapping.interface';
-import { UploadSuccessModalComponent, UploadedFile } from '../../shared/components/upload-success-modal/upload-success-modal.component';
-
-@Component({
-  selector: 'app-test-requirement-mapping',
-  standalone: true,
-  imports: [CommonModule, FormsModule, UploadSuccessModalComponent],
-  templateUrl: './test-requirement-mapping.component.html',
-  styleUrls: ['./test-requirement-mapping.component.scss']
-})
-export class TestRequirementMappingComponent implements OnInit, OnDestroy {
-  // 测试状态
-  isUploading = false;
-  isAnalyzing = false;
-  isGeneratingMapping = false;
-  
-  // 上传的文件
-  uploadedFiles: UploadedFile[] = [];
-  
-  // 分析结果
-  analysisResult: ColorAnalysisResult | undefined = undefined;
-  analysisError: string | null = null;
-  
-  // 需求映射结果
-  requirementMapping: RequirementMapping | null = null;
-  mappingError: string | null = null;
-  
-  // 模态框状态
-  showUploadModal = false;
-  
-  // 测试步骤状态
-  testSteps = [
-    { id: 'upload', name: '图片上传', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' },
-    { id: 'analysis', name: '图片分析', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' },
-    { id: 'mapping', name: '需求映射', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' },
-    { id: 'preview', name: '氛围预览', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' }
-  ];
-  
-  private subscriptions: Subscription[] = [];
-
-  constructor(
-    private requirementMappingService: RequirementMappingService,
-    private colorAnalysisService: ColorAnalysisService
-  ) {}
-
-  ngOnInit(): void {
-    this.resetTest();
-  }
-
-  ngOnDestroy(): void {
-    this.subscriptions.forEach(sub => sub.unsubscribe());
-    // 清理对象URL
-    this.uploadedFiles.forEach(file => {
-      if (file.url.startsWith('blob:')) {
-        URL.revokeObjectURL(file.url);
-      }
-    });
-  }
-
-  // 重置测试
-  resetTest(): void {
-    this.isUploading = false;
-    this.isAnalyzing = false;
-    this.isGeneratingMapping = false;
-    this.uploadedFiles = [];
-    this.analysisResult = undefined;
-    this.analysisError = null;
-    this.requirementMapping = null;
-    this.mappingError = null;
-    this.showUploadModal = false;
-    
-    this.testSteps.forEach(step => {
-      step.status = 'pending';
-    });
-  }
-
-  // 文件上传处理
-  onFileSelected(event: Event): void {
-    const input = event.target as HTMLInputElement;
-    if (!input.files || input.files.length === 0) return;
-
-    this.updateStepStatus('upload', 'in-progress');
-    this.isUploading = true;
-
-    try {
-      const files = Array.from(input.files);
-      this.uploadedFiles = files.map(file => ({
-        id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
-        name: file.name,
-        url: URL.createObjectURL(file),
-        size: file.size,
-        type: 'image' as const,
-        preview: URL.createObjectURL(file)
-      }));
-
-      // 模拟上传延迟
-      setTimeout(() => {
-        this.isUploading = false;
-        this.updateStepStatus('upload', 'completed');
-        this.showUploadModal = true;
-      }, 1000);
-
-    } catch (error) {
-      console.error('文件上传失败:', error);
-      this.isUploading = false;
-      this.updateStepStatus('upload', 'error');
-    }
-  }
-
-  // 开始分析
-  startAnalysis(): void {
-    if (this.uploadedFiles.length === 0) return;
-
-    this.updateStepStatus('analysis', 'in-progress');
-    this.isAnalyzing = true;
-    this.analysisError = null;
-
-    // 使用第一个文件进行分析
-    const firstFile = this.uploadedFiles[0];
-    
-    // 添加null检查和错误处理
-    if (!firstFile) {
-      this.analysisError = '未找到有效的文件';
-      this.isAnalyzing = false;
-      this.updateStepStatus('analysis', 'error');
-      return;
-    }
-
-    try {
-      const analysisSubscription = this.colorAnalysisService.analyzeImage(firstFile).subscribe({
-        next: (result: ColorAnalysisResult) => {
-          if (result) {
-            this.analysisResult = result;
-            this.isAnalyzing = false;
-            this.updateStepStatus('analysis', 'completed');
-            
-            // 自动开始需求映射
-            this.startRequirementMapping();
-          } else {
-            this.analysisError = '分析结果为空';
-            this.isAnalyzing = false;
-            this.updateStepStatus('analysis', 'error');
-          }
-        },
-        error: (error: any) => {
-          console.error('分析失败:', error);
-          this.analysisError = '图片分析失败,请重试';
-          this.isAnalyzing = false;
-          this.updateStepStatus('analysis', 'error');
-        }
-      });
-
-      this.subscriptions.push(analysisSubscription);
-    } catch (error) {
-      console.error('启动分析失败:', error);
-      this.analysisError = '启动分析失败,请重试';
-      this.isAnalyzing = false;
-      this.updateStepStatus('analysis', 'error');
-    }
-  }
-
-  // 开始需求映射
-  startRequirementMapping(): void {
-    if (!this.analysisResult) {
-      console.warn('分析结果为空,无法开始需求映射');
-      return;
-    }
-
-    this.updateStepStatus('mapping', 'in-progress');
-    this.isGeneratingMapping = true;
-    this.mappingError = null;
-
-    try {
-      const mappingSubscription = this.requirementMappingService.generateRequirementMapping(
-        this.analysisResult,
-        SceneTemplate.LIVING_ROOM_MODERN
-      ).subscribe({
-        next: (mapping) => {
-          if (mapping) {
-            this.requirementMapping = mapping;
-            this.isGeneratingMapping = false;
-            this.updateStepStatus('mapping', 'completed');
-            this.updateStepStatus('preview', 'completed');
-            
-            console.log('=== 需求映射测试完成 ===');
-            console.log('映射结果:', this.requirementMapping);
-          } else {
-            this.mappingError = '需求映射结果为空';
-            this.isGeneratingMapping = false;
-            this.updateStepStatus('mapping', 'error');
-          }
-        },
-        error: (error) => {
-          console.error('需求映射生成失败:', error);
-          this.mappingError = '需求映射生成失败,请重试';
-          this.isGeneratingMapping = false;
-          this.updateStepStatus('mapping', 'error');
-        }
-      });
-
-      this.subscriptions.push(mappingSubscription);
-    } catch (error) {
-      console.error('启动需求映射失败:', error);
-      this.mappingError = '启动需求映射失败,请重试';
-      this.isGeneratingMapping = false;
-      this.updateStepStatus('mapping', 'error');
-    }
-  }
-
-  // 更新步骤状态
-  private updateStepStatus(stepId: string, status: 'pending' | 'in-progress' | 'completed' | 'error'): void {
-    const step = this.testSteps.find(s => s.id === stepId);
-    if (step) {
-      step.status = status;
-    } else {
-      console.warn(`未找到步骤: ${stepId}`);
-    }
-  }
-
-  // 辅助方法:获取步骤图标
-  getStepIcon(status: string): string {
-    switch (status) {
-      case 'completed': return '✅';
-      case 'in-progress': return '⏳';
-      case 'error': return '❌';
-      default: return '⭕';
-    }
-  }
-
-  // 辅助方法:从HSL值生成颜色字符串
-  getColorFromHSL(hue: number, saturation: number, brightness: number): string {
-    return `hsl(${hue}, ${saturation}%, ${brightness}%)`;
-  }
-
-  // 获取步骤状态类名
-  getStepClass(status: string): string {
-    return `step-${status}`;
-  }
-
-  // 模态框事件处理
-  onCloseModal(): void {
-    this.showUploadModal = false;
-  }
-
-  onAnalyzeColors(files: UploadedFile[]): void {
-    this.startAnalysis();
-  }
-
-  onViewReport(result: ColorAnalysisResult): void {
-    console.log('查看报告:', result);
-  }
-
-  onGenerateRequirementMapping(mapping: RequirementMapping): void {
-    console.log('需求映射生成完成:', mapping);
-  }
-
-  // 手动重试分析
-  retryAnalysis(): void {
-    this.analysisError = null;
-    this.startAnalysis();
-  }
-
-  // 手动重试映射
-  retryMapping(): void {
-    this.mappingError = null;
-    this.startRequirementMapping();
-  }
-
-  // 下载测试结果
-  downloadTestResult(): void {
-    if (!this.requirementMapping) return;
-
-    const testResult = {
-      timestamp: new Date().toISOString(),
-      uploadedFiles: this.uploadedFiles.map(f => ({
-        name: f.name,
-        size: f.size,
-        type: f.type
-      })),
-      analysisResult: this.analysisResult,
-      requirementMapping: this.requirementMapping,
-      testSteps: this.testSteps
-    };
-
-    const blob = new Blob([JSON.stringify(testResult, null, 2)], { type: 'application/json' });
-    const url = URL.createObjectURL(blob);
-    const link = document.createElement('a');
-    link.href = url;
-    link.download = `requirement-mapping-test-${Date.now()}.json`;
-    link.click();
-    URL.revokeObjectURL(url);
-  }
-}

+ 48 - 6
src/app/pages/designer/project-detail/horizontal-panel.scss

@@ -715,8 +715,9 @@
 
 
           .uploaded-images-grid {
           .uploaded-images-grid {
             display: grid;
             display: grid;
-            grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
-            gap: 12px;
+            grid-template-columns: repeat(3, 1fr);
+            gap: 8px;
+            max-width: 300px;
 
 
             .uploaded-image-item {
             .uploaded-image-item {
               position: relative;
               position: relative;
@@ -735,8 +736,9 @@
 
 
               img {
               img {
                 width: 100%;
                 width: 100%;
-                height: 80px;
+                height: 100%;
                 object-fit: cover;
                 object-fit: cover;
+                aspect-ratio: 1;
               }
               }
 
 
               .image-overlay {
               .image-overlay {
@@ -806,8 +808,9 @@
         .readonly-images {
         .readonly-images {
           .uploaded-images-grid {
           .uploaded-images-grid {
             display: grid;
             display: grid;
-            grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
-            gap: 12px;
+            grid-template-columns: repeat(3, 1fr);
+            gap: 8px;
+            max-width: 300px;
 
 
             .uploaded-image-item {
             .uploaded-image-item {
               position: relative;
               position: relative;
@@ -817,8 +820,9 @@
 
 
               img {
               img {
                 width: 100%;
                 width: 100%;
-                height: 80px;
+                height: 100%;
                 object-fit: cover;
                 object-fit: cover;
+                aspect-ratio: 1;
               }
               }
 
 
               .image-overlay {
               .image-overlay {
@@ -848,6 +852,44 @@
                 }
                 }
               }
               }
             }
             }
+            
+            // 添加更多图片按钮
+            .add-more-images-btn {
+              aspect-ratio: 1;
+              border: 2px dashed #d1d5db;
+              border-radius: 6px;
+              display: flex;
+              flex-direction: column;
+              align-items: center;
+              justify-content: center;
+              cursor: pointer;
+              transition: all 0.3s ease;
+              background: #f9fafb;
+              
+              &:hover {
+                border-color: #3b82f6;
+                background: #eff6ff;
+                transform: scale(1.05);
+              }
+              
+              .add-icon {
+                font-size: 20px;
+                font-weight: bold;
+                color: #6b7280;
+                margin-bottom: 4px;
+              }
+              
+              .add-text {
+                font-size: 10px;
+                color: #6b7280;
+                font-weight: 500;
+              }
+              
+              &:hover .add-icon,
+              &:hover .add-text {
+                color: #3b82f6;
+              }
+            }
           }
           }
 
 
           .empty-tip {
           .empty-tip {

+ 187 - 143
src/app/pages/designer/project-detail/project-detail.html

@@ -1414,7 +1414,7 @@
                     @if (stage !== '订单创建') {
                     @if (stage !== '订单创建') {
                       <div class="vertical-stage-header">
                       <div class="vertical-stage-header">
                         <span class="dot" [class.completed]="getStageStatus(stage) === 'completed'" [class.active]="getStageStatus(stage) === 'active'"></span>
                         <span class="dot" [class.completed]="getStageStatus(stage) === 'completed'" [class.active]="getStageStatus(stage) === 'active'"></span>
-                        <h3>{{ stage === '方案确认' ? '色彩分析报告' : stage }}</h3>
+                        <h3>{{ stage === '需求沟通' ? '需求映射' : (stage === '方案确认' ? '色彩分析报告' : stage) }}</h3>
                       </div>
                       </div>
                     }
                     }
                     <!-- 直接复用原阶段内容卡片:按stage匹配显示 -->
                     <!-- 直接复用原阶段内容卡片:按stage匹配显示 -->
@@ -1680,111 +1680,103 @@
                           </div>
                           </div>
                         </div>
                         </div>
                       } @else if (stage === '需求沟通') {
                       } @else if (stage === '需求沟通') {
+                        <!-- 需求沟通阶段:确认需求组件 -->
                         <app-requirements-confirm-card 
                         <app-requirements-confirm-card 
                           (requirementConfirmed)="syncRequirementKeyInfo($event)"
                           (requirementConfirmed)="syncRequirementKeyInfo($event)"
                           (progressUpdated)="syncRequirementKeyInfo($event)"
                           (progressUpdated)="syncRequirementKeyInfo($event)"
                           (stageCompleted)="onRequirementsStageCompleted($event)"
                           (stageCompleted)="onRequirementsStageCompleted($event)"
-                          (dataUpdated)="onRequirementDataUpdated($event)">
+                          (dataUpdated)="onRequirementDataUpdated($event)"
+                          (mappingDataUpdated)="onMappingDataUpdated($event)">
                         </app-requirements-confirm-card>
                         </app-requirements-confirm-card>
-                        <!-- 新增:需求确认右侧下方左右分区,滚动展示参考图片与CAD图纸 -->
-                        <div class="materials-two-pane" style="display:flex;gap:16px;margin-top:12px;">
-                          <div class="pane left" style="flex:1;">
-                            <div class="pane-header" style="font-weight:600;margin-bottom:8px;">参考图片</div>
-                            <div class="pane-body scroller" style="max-height:240px;overflow:auto;border:1px solid #eee;border-radius:8px;padding:8px;">
-                              @if (referenceImages.length > 0) {
-                                <div class="images-grid" style="display:grid;grid-template-columns:repeat(auto-fill, minmax(100px, 1fr));gap:8px;">
-                                  @for (img of referenceImages; track img.id) {
-                                    <div class="image-thumb" style="cursor:pointer;border:1px solid #f0f0f0;border-radius:6px;overflow:hidden;" (click)="previewImageFile(img.url)">
-                                      <img [src]="img.url" [alt]="img.name || '参考图'" style="width:100%;height:90px;object-fit:cover;" />
-                                    </div>
-                                  }
-                                </div>
-                              } @else {
-                                <div class="empty-tip" style="color:#888;">暂无参考图片</div>
-                              }
-                            </div>
-                          </div>
-                          <!-- 中间列:保留 CAD 文件列表(不改动原展示) -->
-                          <div class="pane middle" style="flex:1;">
-                            <div class="pane-header" style="font-weight:600;margin-bottom:8px;">CAD图纸</div>
-                            <div class="pane-body scroller" style="max-height:240px;overflow:auto;border:1px solid #eee;border-radius:8px;padding:8px;">
-                              @if (cadFiles.length > 0) {
-                                <div class="files-grid" style="display:grid;grid-template-columns:repeat(auto-fill, minmax(140px, 1fr));gap:8px;">
-                                  @for (file of cadFiles; track file.id) {
-                                    <div class="file-thumb" style="cursor:pointer;border:1px dashed #e5e5e5;border-radius:6px;padding:8px;background:#fafafa;" (click)="previewCadFile(file.url)">
-                                      <div class="file-icon" style="font-size:12px;color:#555;margin-bottom:6px;">CAD</div>
-                                      <div class="file-name" style="font-size:12px;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{{ file.name }}</div>
-                                    </div>
-                                  }
-                                </div>
-                              } @else {
-                                <div class="empty-tip" style="color:#888;">暂无CAD文件</div>
-                              }
-                            </div>
-                          </div>
-
-                          
-                        </div>
                         
                         
                       } @else if (stage === '方案确认') {
                       } @else if (stage === '方案确认') {
-                        <!-- 直接以色彩分析面板填充最右侧全部区域 -->
-                        <div class="color-analysis-panel" style="width:100%; display:flex; flex-direction:column; gap:14px;">
-                          @if (isAnalyzing) {
-                            <div class="analysis-progress" style="margin-bottom:4px;">
-                              <div class="progress-header" style="display:flex;align-items:center;justify-content:space-between;">
-                                <h5 style="margin:0;font-size:14px;">正在解析素材...</h5>
-                                <span class="progress-percentage">{{ analysisProgress.toFixed(0) }}%</span>
+                        <!-- 需求映射面板(替换原色彩分析报告区域) -->
+                        <div class="requirement-mapping-panel" style="width:100%; display:flex; flex-direction:column; gap:14px;">
+                          <h3 class="panel-title" style="margin:0 0 16px 0; font-size:18px; font-weight:700; color:#495057;">🎯 需求映射</h3>
+                          
+                          <div class="mapping-progress" style="background:#f8f9fa; border-radius:8px; padding:16px; margin-bottom:12px;">
+                            @if (mappingUploadedFiles.length > 0) {
+                              <div class="progress-badge" style="display:inline-block; padding:6px 14px; background:linear-gradient(135deg, #667eea 0%, #764ba2 100%); color:white; border-radius:14px; font-size:12px; font-weight:600; margin-bottom:12px;">
+                                📸 已同步 {{ mappingUploadedFiles.length }} 张参考图片
+                              </div>
+                            }
+                            
+                            <div class="steps-list" style="display:flex; flex-direction:column; gap:10px;">
+                              <div class="step-item" style="display:flex; align-items:center; gap:10px;">
+                                <span class="step-index" style="width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#e9ecef;color:#495057;font-size:12px;">1</span>
+                                <span class="step-title" style="flex:1;font-size:14px;">图片上传</span>
+                                <span class="step-status" style="font-size:12px;color:#666;">@if (mappingUploadedFiles.length > 0) { ✅ 完成 } @else { ⭕ 待上传 }</span>
                               </div>
                               </div>
-                              <div class="progress-bar" style="height:8px;background:#eee;border-radius:6px;overflow:hidden;">
-                                <div class="progress-fill" [style.width.%]="analysisProgress" style="height:100%;background:#007AFF;"></div>
+                              <div class="step-item" style="display:flex; align-items:center; gap:10px;">
+                                <span class="step-index" style="width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#e9ecef;color:#495057;font-size:12px;">2</span>
+                                <span class="step-title" style="flex:1;font-size:14px;">图片分析</span>
+                                <span class="step-status" style="font-size:12px;color:#666;">@if (mappingIsAnalyzing) { ⏳ 进行中 } @else if (mappingAnalysisResult) { ✅ 完成 } @else { ⭕ 待开始 }</span>
+                              </div>
+                              <div class="step-item" style="display:flex; align-items:center; gap:10px;">
+                                <span class="step-index" style="width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#e9ecef;color:#495057;font-size:12px;">3</span>
+                                <span class="step-title" style="flex:1;font-size:14px;">需求映射</span>
+                                <span class="step-status" style="font-size:12px;color:#666;">@if (mappingIsGeneratingMapping) { ⏳ 生成中 } @else if (mappingRequirementMapping) { ✅ 完成 } @else { ⭕ 待生成 }</span>
                               </div>
                               </div>
                             </div>
                             </div>
-                          }
-                          @if (colorAnalysisResult) {
-                            <div class="report-summary" style="display:flex;flex-direction:column;gap:18px;">
-                              <div class="report-images">
-                                <div class="section-title" style="font-weight:600;margin-bottom:8px;">参考图</div>
-                                <div class="images-row" style="display:flex;gap:10px;flex-wrap:wrap;">
-                                  @if (colorAnalysisResult.originalImage) {
-                                    <img [src]="colorAnalysisResult.originalImage" alt="参考图" (click)="previewImageFile(colorAnalysisResult.originalImage)" style="width:140px;height:140px;object-fit:cover;border-radius:8px;cursor:pointer;border:1px solid #eee;" />
-                                  } @else {
-                                    <div class="empty-tip" style="color:#888;">暂无参考图</div>
-                                  }
-                                </div>
+                          </div>
+                          
+                          @if (mappingAnalysisResult) {
+                            <div class="analysis-summary-panel" style="background:#f8f9fa;border-radius:8px;padding:16px;">
+                              <h4 style="margin:0 0 12px 0; font-size:15px; font-weight:600; color:#333;">图片分析摘要</h4>
+                              <div class="param-items" style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px;">
+                                <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">主色</span><span style="font-weight:500;">{{ mappingAnalysisResult.primaryColor?.hex || '未知' }}</span></div>
+                                <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">材质</span><span style="font-weight:500;">{{ getMaterialName(mappingAnalysisResult.materialType) }}</span></div>
+                                <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">灯光</span><span style="font-weight:500;">{{ getLightingMoodName(mappingAnalysisResult.lightingMood) }}</span></div>
+                                <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">对比</span><span style="font-weight:500;">{{ mappingAnalysisResult.contrast || '未知' }}</span></div>
                               </div>
                               </div>
-                              <div class="palette">
-                                <div class="section-title" style="font-weight:600;margin-bottom:8px;font-size:13px;">色板</div>
-                                <div class="palette-row" style="display:flex;gap:8px;flex-wrap:wrap;">
-                                  @for (c of colorAnalysisResult.colors; track c.hex) {
-                                    <div class="palette-item" style="display:flex;align-items:center;gap:6px;padding:6px;border:1px solid #eee;border-radius:6px;">
-                                      <div class="swatch" [style.backgroundColor]="c.hex" style="width:20px;height:20px;border-radius:4px;border:1px solid #ddd;"></div>
-                                      <div class="hex" style="font-size:12px;color:#333;">{{ c.hex }}</div>
-                                      <div class="pct" style="font-size:12px;color:#666;">{{ c.percentage }}%</div>
-                                    </div>
-                                  }
+                            </div>
+                          }
+                          
+                          @if (mappingRequirementMapping) {
+                            <div class="mapping-result-panel" style="background:#f8f9fa;border-radius:8px;padding:16px;">
+                              <h4 style="margin:0 0 12px 0; font-size:15px; font-weight:600; color:#333;">需求映射结果</h4>
+                              <div class="param-section" style="margin-bottom:12px;">
+                                <div class="section-title" style="font-weight:600;color:#555;margin-bottom:8px;font-size:13px;">色彩参数</div>
+                                <div class="param-items" style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px;">
+                                  <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">和谐度</span><span style="font-weight:500;">{{ getColorHarmonyName(mappingRequirementMapping.color.harmony) }}</span></div>
+                                  <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">色温</span><span style="font-weight:500;">{{ getTemperatureName(mappingRequirementMapping.color.temperature) }}</span></div>
                                 </div>
                                 </div>
                               </div>
                               </div>
-                              <div class="dominant-color">
-                                <div class="section-title" style="font-weight:600;margin-bottom:8px;font-size:13px;">主色</div>
-                                <div class="dominant-row" style="display:flex;align-items:center;gap:8px;">
-                                  <div class="swatch" [style.backgroundColor]="dominantColorHex || '#ccc'" style="width:28px;height:28px;border-radius:6px;border:1px solid #ddd;"></div>
-                                  <span class="hex" style="font-size:12px;color:#333;">
-                                    {{ dominantColorHex || (colorAnalysisResult.colors.length > 0 ? colorAnalysisResult.colors[0].hex : '未知') }}
-                                  </span>
+                              <div class="param-section">
+                                <div class="section-title" style="font-weight:600;color:#555;margin-bottom:8px;font-size:13px;">空间参数</div>
+                                <div class="param-items" style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px;">
+                                  <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">布局</span><span style="font-weight:500;">{{ getLayoutTypeName(mappingRequirementMapping.space.layoutType) }}</span></div>
+                                  <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">流线</span><span style="font-weight:500;">{{ getFlowTypeName(mappingRequirementMapping.space.flowType) }}</span></div>
                                 </div>
                                 </div>
                               </div>
                               </div>
                             </div>
                             </div>
-                          } @else {
-                            <div class="empty-tip" style="color:#888;">暂无色彩分析结果</div>
+                          }
+                          
+                          @if (mappingIsAnalyzing) {
+                            <div class="analyzing-indicator" style="display:flex; align-items:center; gap:10px; background:#f8f9fa; padding:12px; border-radius:8px;">
+                              <div class="spinner" style="width:16px;height:16px;border:2px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:spin 1s linear infinite;"></div>
+                              <span style="font-size:14px; color:#666;">正在解析图片...</span>
+                            </div>
+                          }
+                          
+                          @if (mappingIsGeneratingMapping) {
+                            <div class="analyzing-indicator" style="display:flex; align-items:center; gap:10px; background:#f8f9fa; padding:12px; border-radius:8px;">
+                              <div class="spinner" style="width:16px;height:16px;border:2px solid #e5e7eb;border-top-color:#10b981;border-radius:50%;animation:spin 1s linear infinite;"></div>
+                              <span style="font-size:14px; color:#666;">正在生成需求映射...</span>
+                            </div>
+                          }
+                          
+                          @if (!mappingAnalysisResult && !mappingIsAnalyzing && mappingUploadedFiles.length === 0) {
+                            <div class="empty-state" style="text-align:center; padding:32px; color:#999;">
+                              <div style="font-size:48px; margin-bottom:12px;">📊</div>
+                              <div style="font-size:14px;">在需求沟通中上传图片后,这里将实时显示需求映射结果</div>
+                            </div>
                           }
                           }
                         </div>
                         </div>
+                        
                       } @else if (stage === '建模') {
                       } @else if (stage === '建模') {
                         <!-- 建模阶段:直接显示建模相关内容 -->
                         <!-- 建模阶段:直接显示建模相关内容 -->
                         <div class="modeling-stage-panel">
                         <div class="modeling-stage-panel">
-                          <div class="stage-description">
-                            <h4>建模阶段</h4>
-                            <p>上传白模图片,进行模型差异检查</p>
-                          </div>
                           <!-- 空间列表 -->
                           <!-- 空间列表 -->
                           <div class="space-list-container">
                           <div class="space-list-container">
                             <div class="space-list-header">
                             <div class="space-list-header">
@@ -1851,15 +1843,24 @@
                                                [class.drag-over]="isDragOver">
                                                [class.drag-over]="isDragOver">
                                             @if (getSpaceImages('modeling', space.id).length === 0) {
                                             @if (getSpaceImages('modeling', space.id).length === 0) {
                                               <div class="upload-placeholder">
                                               <div class="upload-placeholder">
-                                                <div class="upload-icon">📁</div>
                                                 <div class="upload-text">点击上传或拖拽文件到此处</div>
                                                 <div class="upload-text">点击上传或拖拽文件到此处</div>
                                                 <div class="upload-hint">
                                                 <div class="upload-hint">
                                                   支持 JPG、PNG 格式,单个文件最大 10MB
                                                   支持 JPG、PNG 格式,单个文件最大 10MB
                                                 </div>
                                                 </div>
                                               </div>
                                               </div>
                                 } @else {
                                 } @else {
+                                  <!-- 确认上传按钮 -->
+                                  @if (canEditSection('delivery') && getSpaceImages('modeling', space.id).length > 0) {
+                                    <div class="confirm-upload-section">
+                                      <button class="confirm-upload-btn" 
+                                              (click)="$event.stopPropagation(); confirmStageUpload('modeling')"
+                                              [disabled]="!canConfirmStageUpload('modeling')">
+                                        <span>确认上传</span>
+                                      </button>
+                                    </div>
+                                  }
                                   <div class="uploaded-images-grid">
                                   <div class="uploaded-images-grid">
-                                                @for (img of getSpaceImages('modeling', space.id); track img.id) {
+                                    @for (img of getSpaceImages('modeling', space.id); track img.id) {
                                       <div class="uploaded-image-item" (click)="previewImage(img)">
                                       <div class="uploaded-image-item" (click)="previewImage(img)">
                                         <img [src]="img.url" [alt]="img.name" />
                                         <img [src]="img.url" [alt]="img.name" />
                                         <div class="image-overlay">
                                         <div class="image-overlay">
@@ -1867,22 +1868,29 @@
                                           <div class="image-actions">
                                           <div class="image-actions">
                                             <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
                                             <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
                                               <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                                               <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                                                            <circle cx="11" cy="11" r="8"></circle>
-                                                            <path d="m21 21-4.35-4.35"></path>
-                                              </svg>
-                                            </button>
-                                                        @if (canEditSection('delivery')) {
-                                                          <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('modeling', space.id, img.id)">
-                                              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                                                              <polyline points="3,6 5,6 21,6"></polyline>
-                                                              <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
+                                                <circle cx="11" cy="11" r="8"></circle>
+                                                <path d="m21 21-4.35-4.35"></path>
                                               </svg>
                                               </svg>
                                             </button>
                                             </button>
-                                                        }
+                                            @if (canEditSection('delivery')) {
+                                              <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('modeling', space.id, img.id)">
+                                                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                                                  <polyline points="3,6 5,6 21,6"></polyline>
+                                                  <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
+                                                </svg>
+                                              </button>
+                                            }
                                           </div>
                                           </div>
                                         </div>
                                         </div>
                                       </div>
                                       </div>
                                     }
                                     }
+                                    <!-- 添加更多图片按钮 -->
+                                    @if (canEditSection('delivery')) {
+                                      <div class="add-more-images-btn" (click)="$event.stopPropagation(); triggerSpaceFileInput('modeling', space.id)">
+                                        <div class="add-icon">+</div>
+                                        <div class="add-text">添加更多</div>
+                                      </div>
+                                    }
                                   </div>
                                   </div>
                                 }
                                 }
                               </div>
                               </div>
@@ -1932,10 +1940,6 @@
                       } @else if (stage === '软装') {
                       } @else if (stage === '软装') {
                         <!-- 软装阶段:直接显示软装相关内容 -->
                         <!-- 软装阶段:直接显示软装相关内容 -->
                         <div class="soft-decor-stage-panel">
                         <div class="soft-decor-stage-panel">
-                          <div class="stage-description">
-                            <h4>软装阶段</h4>
-                            <p>上传软装清单素材</p>
-                          </div>
                           <!-- 空间列表 -->
                           <!-- 空间列表 -->
                           <div class="space-list-container">
                           <div class="space-list-container">
                             <div class="space-list-header">
                             <div class="space-list-header">
@@ -2002,15 +2006,24 @@
                                                [class.drag-over]="isDragOver">
                                                [class.drag-over]="isDragOver">
                                             @if (getSpaceImages('softDecor', space.id).length === 0) {
                                             @if (getSpaceImages('softDecor', space.id).length === 0) {
                                               <div class="upload-placeholder">
                                               <div class="upload-placeholder">
-                                                <div class="upload-icon">📁</div>
                                                 <div class="upload-text">点击上传或拖拽文件到此处</div>
                                                 <div class="upload-text">点击上传或拖拽文件到此处</div>
                                                 <div class="upload-hint">
                                                 <div class="upload-hint">
                                                   建议 ≤1MB 的 JPG/PNG 小图
                                                   建议 ≤1MB 的 JPG/PNG 小图
                                                 </div>
                                                 </div>
                                               </div>
                                               </div>
                                 } @else {
                                 } @else {
+                                  <!-- 确认上传按钮 -->
+                                  @if (canEditSection('delivery') && getSpaceImages('softDecor', space.id).length > 0) {
+                                    <div class="confirm-upload-section">
+                                      <button class="confirm-upload-btn" 
+                                              (click)="$event.stopPropagation(); confirmStageUpload('softDecor')"
+                                              [disabled]="!canConfirmStageUpload('softDecor')">
+                                        <span>确认上传</span>
+                                      </button>
+                                    </div>
+                                  }
                                   <div class="uploaded-images-grid">
                                   <div class="uploaded-images-grid">
-                                                @for (img of getSpaceImages('softDecor', space.id); track img.id) {
+                                    @for (img of getSpaceImages('softDecor', space.id); track img.id) {
                                       <div class="uploaded-image-item" (click)="previewImage(img)">
                                       <div class="uploaded-image-item" (click)="previewImage(img)">
                                         <img [src]="img.url" [alt]="img.name" />
                                         <img [src]="img.url" [alt]="img.name" />
                                         <div class="image-overlay">
                                         <div class="image-overlay">
@@ -2018,22 +2031,29 @@
                                           <div class="image-actions">
                                           <div class="image-actions">
                                             <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
                                             <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
                                               <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                                               <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                                                            <circle cx="11" cy="11" r="8"></circle>
-                                                            <path d="m21 21-4.35-4.35"></path>
-                                              </svg>
-                                            </button>
-                                                        @if (canEditSection('delivery')) {
-                                                          <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('softDecor', space.id, img.id)">
-                                              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                                                              <polyline points="3,6 5,6 21,6"></polyline>
-                                                              <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
+                                                <circle cx="11" cy="11" r="8"></circle>
+                                                <path d="m21 21-4.35-4.35"></path>
                                               </svg>
                                               </svg>
                                             </button>
                                             </button>
-                                                        }
+                                            @if (canEditSection('delivery')) {
+                                              <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('softDecor', space.id, img.id)">
+                                                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                                                  <polyline points="3,6 5,6 21,6"></polyline>
+                                                  <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
+                                                </svg>
+                                              </button>
+                                            }
                                           </div>
                                           </div>
                                         </div>
                                         </div>
                                       </div>
                                       </div>
                                     }
                                     }
+                                    <!-- 添加更多图片按钮 -->
+                                    @if (canEditSection('delivery')) {
+                                      <div class="add-more-images-btn" (click)="$event.stopPropagation(); triggerSpaceFileInput('softDecor', space.id)">
+                                        <div class="add-icon">+</div>
+                                        <div class="add-text">添加更多</div>
+                                      </div>
+                                    }
                                   </div>
                                   </div>
                                 }
                                 }
                               </div>
                               </div>
@@ -2047,7 +2067,7 @@
                                                     <div class="image-overlay">
                                                     <div class="image-overlay">
                                                       <div class="image-name">{{ img.name }}</div>
                                                       <div class="image-name">{{ img.name }}</div>
                                 </div>
                                 </div>
-                              </div>
+                            </div>
                                         }
                                         }
                                       </div>
                                       </div>
                                     } @else {
                                     } @else {
@@ -2083,10 +2103,6 @@
                       } @else if (stage === '渲染') {
                       } @else if (stage === '渲染') {
                         <!-- 渲染阶段:直接显示渲染相关内容 -->
                         <!-- 渲染阶段:直接显示渲染相关内容 -->
                         <div class="rendering-stage-panel">
                         <div class="rendering-stage-panel">
-                          <div class="stage-description">
-                            <h4>渲染阶段</h4>
-                            <p>上传渲染大图,满足4K标准</p>
-                                  </div>
                           <!-- 空间列表 -->
                           <!-- 空间列表 -->
                           <div class="space-list-container">
                           <div class="space-list-container">
                             <div class="space-list-header">
                             <div class="space-list-header">
@@ -2153,13 +2169,22 @@
                                                [class.drag-over]="isDragOver">
                                                [class.drag-over]="isDragOver">
                                             @if (getSpaceImages('rendering', space.id).length === 0) {
                                             @if (getSpaceImages('rendering', space.id).length === 0) {
                                               <div class="upload-placeholder">
                                               <div class="upload-placeholder">
-                                                <div class="upload-icon">📁</div>
                                                 <div class="upload-text">点击上传或拖拽文件到此处</div>
                                                 <div class="upload-text">点击上传或拖拽文件到此处</div>
                                                 <div class="upload-hint">
                                                 <div class="upload-hint">
                                                   需满足4K标准(最长边 ≥ 4000px)
                                                   需满足4K标准(最长边 ≥ 4000px)
-                                      </div>
-                                    </div>
+                                                </div>
+                                              </div>
                                             } @else {
                                             } @else {
+                                              <!-- 确认上传按钮 -->
+                                              @if (canEditSection('delivery') && getSpaceImages('rendering', space.id).length > 0) {
+                                                <div class="confirm-upload-section">
+                                                  <button class="confirm-upload-btn" 
+                                                          (click)="$event.stopPropagation(); confirmStageUpload('rendering')"
+                                                          [disabled]="!canConfirmStageUpload('rendering')">
+                                                    <span>确认上传</span>
+                                                  </button>
+                                                </div>
+                                              }
                                               <div class="uploaded-images-grid">
                                               <div class="uploaded-images-grid">
                                                 @for (img of getSpaceImages('rendering', space.id); track img.id) {
                                                 @for (img of getSpaceImages('rendering', space.id); track img.id) {
                                                   <div class="uploaded-image-item" (click)="previewImage(img)">
                                                   <div class="uploaded-image-item" (click)="previewImage(img)">
@@ -2172,20 +2197,27 @@
                                                             <circle cx="11" cy="11" r="8"></circle>
                                                             <circle cx="11" cy="11" r="8"></circle>
                                                             <path d="m21 21-4.35-4.35"></path>
                                                             <path d="m21 21-4.35-4.35"></path>
                                                           </svg>
                                                           </svg>
-                                      </button>
+                                                        </button>
                                                         @if (canEditSection('delivery')) {
                                                         @if (canEditSection('delivery')) {
                                                           <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('rendering', space.id, img.id)">
                                                           <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('rendering', space.id, img.id)">
                                                             <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                                                             <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                                                               <polyline points="3,6 5,6 21,6"></polyline>
                                                               <polyline points="3,6 5,6 21,6"></polyline>
                                                               <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
                                                               <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
                                                             </svg>
                                                             </svg>
-                                      </button>
-                                  }
-                                </div>
-                          </div>
-                        </div>
-                      }
-                  </div>
+                                                          </button>
+                                                        }
+                                                      </div>
+                                                    </div>
+                                                  </div>
+                                                }
+                                                <!-- 添加更多图片按钮 -->
+                                                @if (canEditSection('delivery')) {
+                                                  <div class="add-more-images-btn" (click)="$event.stopPropagation(); triggerSpaceFileInput('rendering', space.id)">
+                                                    <div class="add-icon">+</div>
+                                                    <div class="add-text">添加更多</div>
+                                                  </div>
+                                                }
+                                              </div>
                 }
                 }
               </div>
               </div>
                                         } @else {
                                         } @else {
@@ -2234,10 +2266,6 @@
                       } @else if (stage === '后期') {
                       } @else if (stage === '后期') {
                         <!-- 后期阶段:直接显示后期相关内容 -->
                         <!-- 后期阶段:直接显示后期相关内容 -->
                         <div class="post-production-stage-panel">
                         <div class="post-production-stage-panel">
-                          <div class="stage-description">
-                            <h4>后期阶段</h4>
-                            <p>上传后期处理图片,进行最终调整</p>
-            </div>
                           <!-- 空间列表 -->
                           <!-- 空间列表 -->
                           <div class="space-list-container">
                           <div class="space-list-container">
                             <div class="space-list-header">
                             <div class="space-list-header">
@@ -2304,13 +2332,22 @@
                                                [class.drag-over]="isDragOver">
                                                [class.drag-over]="isDragOver">
                                             @if (getSpaceImages('postProduction', space.id).length === 0) {
                                             @if (getSpaceImages('postProduction', space.id).length === 0) {
                                               <div class="upload-placeholder">
                                               <div class="upload-placeholder">
-                                                <div class="upload-icon">📁</div>
                                                 <div class="upload-text">点击上传或拖拽文件到此处</div>
                                                 <div class="upload-text">点击上传或拖拽文件到此处</div>
                                                 <div class="upload-hint">
                                                 <div class="upload-hint">
                                                   支持 JPG、PNG 格式,后期处理图片
                                                   支持 JPG、PNG 格式,后期处理图片
-                </div>
-              </div>
+                                                </div>
+                                              </div>
                                             } @else {
                                             } @else {
+                                              <!-- 确认上传按钮 -->
+                                              @if (canEditSection('delivery') && getSpaceImages('postProduction', space.id).length > 0) {
+                                                <div class="confirm-upload-section">
+                                                  <button class="confirm-upload-btn" 
+                                                          (click)="$event.stopPropagation(); confirmStageUpload('postProduction')"
+                                                          [disabled]="!canConfirmStageUpload('postProduction')">
+                                                    <span>确认上传</span>
+                                                  </button>
+                                                </div>
+                                              }
                                               <div class="uploaded-images-grid">
                                               <div class="uploaded-images-grid">
                                                 @for (img of getSpaceImages('postProduction', space.id); track img.id) {
                                                 @for (img of getSpaceImages('postProduction', space.id); track img.id) {
                                                   <div class="uploaded-image-item" (click)="previewImage(img)">
                                                   <div class="uploaded-image-item" (click)="previewImage(img)">
@@ -2323,20 +2360,27 @@
                                                             <circle cx="11" cy="11" r="8"></circle>
                                                             <circle cx="11" cy="11" r="8"></circle>
                                                             <path d="m21 21-4.35-4.35"></path>
                                                             <path d="m21 21-4.35-4.35"></path>
                                                           </svg>
                                                           </svg>
-              </button>
+                                                        </button>
                                                         @if (canEditSection('delivery')) {
                                                         @if (canEditSection('delivery')) {
                                                           <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('postProduction', space.id, img.id)">
                                                           <button class="delete-btn" (click)="$event.stopPropagation(); removeSpaceImage('postProduction', space.id, img.id)">
                                                             <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                                                             <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                                                               <polyline points="3,6 5,6 21,6"></polyline>
                                                               <polyline points="3,6 5,6 21,6"></polyline>
                                                               <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
                                                               <path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
                                                             </svg>
                                                             </svg>
-              </button>
-                  }
-                </div>
+                                                          </button>
+                                                        }
+                                                      </div>
                                                     </div>
                                                     </div>
-                </div>
-              }
-            </div>
+                                                  </div>
+                                                }
+                                                <!-- 添加更多图片按钮 -->
+                                                @if (canEditSection('delivery')) {
+                                                  <div class="add-more-images-btn" (click)="$event.stopPropagation(); triggerSpaceFileInput('postProduction', space.id)">
+                                                    <div class="add-icon">+</div>
+                                                    <div class="add-text">添加更多</div>
+                                                  </div>
+                                                }
+                                              </div>
                                             }
                                             }
           </div>
           </div>
                                         } @else {
                                         } @else {

+ 94 - 10
src/app/pages/designer/project-detail/project-detail.scss

@@ -3153,17 +3153,63 @@
         line-height: 1.4;
         line-height: 1.4;
       }
       }
       
       
-      // 上传后的图片网格
+      // 确认上传按钮区域
+      .confirm-upload-section {
+        margin-bottom: 12px;
+        padding: 8px;
+        background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+        border-radius: 6px;
+        border: 1px solid #dee2e6;
+        
+        .confirm-upload-btn {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          width: 100%;
+          padding: 8px 16px;
+          background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
+          color: white;
+          border: none;
+          border-radius: 4px;
+          font-size: 13px;
+          font-weight: 600;
+          cursor: pointer;
+          transition: all 0.3s ease;
+          box-shadow: 0 2px 4px rgba(40, 167, 69, 0.2);
+          height: 32px;
+          
+          &:hover:not(:disabled) {
+            background: linear-gradient(135deg, #218838 0%, #1ea085 100%);
+            transform: translateY(-1px);
+            box-shadow: 0 4px 8px rgba(40, 167, 69, 0.3);
+          }
+          
+          &:active:not(:disabled) {
+            transform: translateY(0);
+            box-shadow: 0 2px 4px rgba(40, 167, 69, 0.2);
+          }
+          
+          &:disabled {
+            background: #6c757d;
+            cursor: not-allowed;
+            opacity: 0.6;
+            box-shadow: none;
+          }
+        }
+      }
+      
+      // 上传后的图片网格 - 九宫格布局
       .uploaded-images-grid {
       .uploaded-images-grid {
         display: grid;
         display: grid;
-        grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
-        gap: 12px;
+        grid-template-columns: repeat(3, 1fr);
+        gap: 8px;
         width: 100%;
         width: 100%;
+        max-width: 300px;
         
         
         .uploaded-image-item {
         .uploaded-image-item {
           position: relative;
           position: relative;
           aspect-ratio: 1;
           aspect-ratio: 1;
-          border-radius: 8px;
+          border-radius: 6px;
           overflow: hidden;
           overflow: hidden;
           cursor: pointer;
           cursor: pointer;
           transition: all 0.3s ease;
           transition: all 0.3s ease;
@@ -3181,7 +3227,7 @@
             width: 100%;
             width: 100%;
             height: 100%;
             height: 100%;
             object-fit: cover;
             object-fit: cover;
-            border-radius: 8px;
+            border-radius: 6px;
           }
           }
           
           
           .image-overlay {
           .image-overlay {
@@ -3210,14 +3256,14 @@
             
             
             .image-actions {
             .image-actions {
               display: flex;
               display: flex;
-              gap: 8px;
-              justify-content: center;
+              gap: 4px;
+              justify-content: flex-end;
               
               
               button {
               button {
-                width: 32px;
-                height: 32px;
+                width: 20px;
+                height: 20px;
                 border: none;
                 border: none;
-                border-radius: 6px;
+                border-radius: 50%;
                 background: rgba(255, 255, 255, 0.9);
                 background: rgba(255, 255, 255, 0.9);
                 color: #333;
                 color: #333;
                 cursor: pointer;
                 cursor: pointer;
@@ -3282,6 +3328,44 @@
             transition: color 0.3s ease;
             transition: color 0.3s ease;
           }
           }
         }
         }
+        
+        // 添加更多图片按钮
+        .add-more-images-btn {
+          aspect-ratio: 1;
+          border: 2px dashed #d1d5db;
+          border-radius: 6px;
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          justify-content: center;
+          cursor: pointer;
+          transition: all 0.3s ease;
+          background: #f9fafb;
+          
+          &:hover {
+            border-color: #3b82f6;
+            background: #eff6ff;
+            transform: scale(1.05);
+          }
+          
+          .add-icon {
+            font-size: 20px;
+            font-weight: bold;
+            color: #6b7280;
+            margin-bottom: 4px;
+          }
+          
+          .add-text {
+            font-size: 10px;
+            color: #6b7280;
+            font-weight: 500;
+          }
+          
+          &:hover .add-icon,
+          &:hover .add-text {
+            color: #3b82f6;
+          }
+        }
       }
       }
       
       
       // 隐藏的文件输入框
       // 隐藏的文件输入框

+ 272 - 69
src/app/pages/designer/project-detail/project-detail.ts

@@ -331,6 +331,14 @@ export class ProjectDetail implements OnInit, OnDestroy {
   lightingAnalysis: any = null;
   lightingAnalysis: any = null;
   materialAnalysisData: any[] = [];
   materialAnalysisData: any[] = [];
   
   
+  // 新增:需求映射数据(用于右侧面板显示)
+  mappingUploadedFiles: any[] = [];
+  mappingAnalysisResult: any = null;
+  mappingRequirementMapping: any = null;
+  mappingTestSteps: any[] = [];
+  mappingIsAnalyzing: boolean = false;
+  mappingIsGeneratingMapping: boolean = false;
+  
   // 新增:9阶段顺序(串式流程)- 包含后期阶段
   // 新增:9阶段顺序(串式流程)- 包含后期阶段
   stageOrder: ProjectStage[] = ['订单创建', '需求沟通', '方案确认', '建模', '软装', '渲染', '后期', '尾款结算', '客户评价', '投诉处理'];
   stageOrder: ProjectStage[] = ['订单创建', '需求沟通', '方案确认', '建模', '软装', '渲染', '后期', '尾款结算', '客户评价', '投诉处理'];
   // 新增:阶段展开状态(默认全部收起,当前阶段在数据加载后自动展开)
   // 新增:阶段展开状态(默认全部收起,当前阶段在数据加载后自动展开)
@@ -645,22 +653,28 @@ export class ProjectDetail implements OnInit, OnDestroy {
     // 初始化售后模块示例数据
     // 初始化售后模块示例数据
     this.initializeAftercareData();
     this.initializeAftercareData();
     
     
-    this.route.paramMap.subscribe(params => {
-      this.projectId = params.get('id') || '';
-      // 根据当前URL检测视图上下文
-      this.roleContext = this.detectRoleContextFromUrl();
-      this.loadProjectData();
-      this.loadExceptionHistories();
-      this.loadProjectMembers();
-      this.loadProjectFiles();
-      this.loadTimelineEvents();
-      
-      // 启动客户信息自动同步
-      this.startAutoSync();
+    this.route.paramMap.subscribe({
+      next: (params) => {
+        this.projectId = params.get('id') || '';
+        // 根据当前URL检测视图上下文
+        this.roleContext = this.detectRoleContextFromUrl();
+        this.loadProjectData();
+        this.loadExceptionHistories();
+        this.loadProjectMembers();
+        this.loadProjectFiles();
+        this.loadTimelineEvents();
+        
+        // 启动客户信息自动同步
+        this.startAutoSync();
+      },
+      error: (error) => {
+        console.error('路由参数订阅失败:', error);
+      }
     });
     });
 
 
     // 新增:监听查询参数,支持通过 activeTab 设置初始标签页和 currentStage 设置当前阶段
     // 新增:监听查询参数,支持通过 activeTab 设置初始标签页和 currentStage 设置当前阶段
-    this.route.queryParamMap.subscribe(qp => {
+    this.route.queryParamMap.subscribe({
+      next: (qp) => {
       const raw = qp.get('activeTab');
       const raw = qp.get('activeTab');
       const alias: Record<string, 'progress' | 'members' | 'files' | 'reference'> = {
       const alias: Record<string, 'progress' | 'members' | 'files' | 'reference'> = {
         requirements: 'progress',
         requirements: 'progress',
@@ -768,6 +782,10 @@ export class ProjectDetail implements OnInit, OnDestroy {
           this.isSyncingCustomerInfo = false;
           this.isSyncingCustomerInfo = false;
         }
         }
       }
       }
+      },
+      error: (error) => {
+        console.error('查询参数订阅失败:', error);
+      }
     });
     });
     
     
     // 添加点击事件监听器,当点击页面其他位置时关闭下拉菜单
     // 添加点击事件监听器,当点击页面其他位置时关闭下拉菜单
@@ -1118,31 +1136,42 @@ export class ProjectDetail implements OnInit, OnDestroy {
   
   
   // 加载历史反馈记录
   // 加载历史反馈记录
   loadExceptionHistories(): void {
   loadExceptionHistories(): void {
-    this.projectService.getExceptionHistories(this.projectId).subscribe(histories => {
-      this.exceptionHistories = histories;
+    this.projectService.getExceptionHistories(this.projectId).subscribe({
+      next: (histories) => {
+        this.exceptionHistories = histories;
+      },
+      error: (error) => {
+        console.error('加载异常历史失败:', error);
+      }
     });
     });
   }
   }
 
 
   loadProjectDetails(): void {
   loadProjectDetails(): void {
     console.log('=== loadProjectDetails 开始加载项目数据 ===');
     console.log('=== loadProjectDetails 开始加载项目数据 ===');
     console.log('当前项目ID:', this.projectId);
     console.log('当前项目ID:', this.projectId);
-    this.projectService.getProjectById(this.projectId).subscribe(project => {
-      console.log('获取到的项目数据:', project);
-      if (!project) {
-        console.error('未找到项目数据,项目ID:', this.projectId);
-        // 如果找不到项目,尝试使用默认项目ID
-        console.log('尝试使用默认项目ID: proj-001');
-        this.projectService.getProjectById('proj-001').subscribe(defaultProject => {
-          console.log('默认项目数据:', defaultProject);
-          if (defaultProject) {
-            this.project = defaultProject;
-            this.currentStage = defaultProject.currentStage || '';
-            console.log('使用默认项目,设置当前阶段:', this.currentStage);
-            this.setupStageExpansion(defaultProject);
-          }
-        });
-        return;
-      }
+    this.projectService.getProjectById(this.projectId).subscribe({
+      next: (project) => {
+        console.log('获取到的项目数据:', project);
+        if (!project) {
+          console.error('未找到项目数据,项目ID:', this.projectId);
+          // 如果找不到项目,尝试使用默认项目ID
+          console.log('尝试使用默认项目ID: proj-001');
+          this.projectService.getProjectById('proj-001').subscribe({
+            next: (defaultProject) => {
+              console.log('默认项目数据:', defaultProject);
+              if (defaultProject) {
+                this.project = defaultProject;
+                this.currentStage = defaultProject.currentStage || '';
+                console.log('使用默认项目,设置当前阶段:', this.currentStage);
+                this.setupStageExpansion(defaultProject);
+              }
+            },
+            error: (error) => {
+              console.error('加载默认项目失败:', error);
+            }
+          });
+          return;
+        }
       
       
       this.project = project;
       this.project = project;
       // 设置当前阶段
       // 设置当前阶段
@@ -1153,6 +1182,10 @@ export class ProjectDetail implements OnInit, OnDestroy {
       }
       }
       // 检查技能匹配度 - 已注释掉以取消弹窗警告
       // 检查技能匹配度 - 已注释掉以取消弹窗警告
       // this.checkSkillMismatch();
       // this.checkSkillMismatch();
+      },
+      error: (error) => {
+        console.error('加载项目详情失败:', error);
+      }
     });
     });
   }
   }
 
 
@@ -1210,30 +1243,42 @@ export class ProjectDetail implements OnInit, OnDestroy {
     
     
     // 模拟API加载过程
     // 模拟API加载过程
     setTimeout(() => {
     setTimeout(() => {
-      this.projectService.getRenderProgress(this.projectId).subscribe(progress => {
-        this.renderProgress = progress;
-        this.isLoadingRenderProgress = false;
-        
-        // 模拟API加载失败的情况
-        if (!progress) {
-          this.errorLoadingRenderProgress = true;
-          // 通知技术组长
+      this.projectService.getRenderProgress(this.projectId).subscribe({
+        next: (progress) => {
+          this.renderProgress = progress;
+          this.isLoadingRenderProgress = false;
+          
+          // 模拟API加载失败的情况
+          if (!progress) {
+            this.errorLoadingRenderProgress = true;
+            // 通知技术组长
           this.notifyTeamLeader('render-failed');
           this.notifyTeamLeader('render-failed');
         } else {
         } else {
           // 检查是否需要显示超时预警
           // 检查是否需要显示超时预警
           this.checkRenderTimeout();
           this.checkRenderTimeout();
         }
         }
+        },
+        error: (error) => {
+          console.error('加载渲染进度失败:', error);
+          this.isLoadingRenderProgress = false;
+          this.errorLoadingRenderProgress = true;
+        }
       });
       });
     }, 1000);
     }, 1000);
   }
   }
 
 
   loadCustomerFeedbacks(): void {
   loadCustomerFeedbacks(): void {
-    this.projectService.getCustomerFeedbacks().subscribe(feedbacks => {
-      this.feedbacks = feedbacks.filter(f => f.projectId === this.projectId);
-      // 为反馈添加分类标签
-      this.tagCustomerFeedbacks();
-      // 检查是否有需要处理的反馈并启动倒计时
-      this.checkFeedbackTimeout();
+    this.projectService.getCustomerFeedbacks().subscribe({
+      next: (feedbacks) => {
+        this.feedbacks = feedbacks.filter(f => f.projectId === this.projectId);
+        // 为反馈添加分类标签
+        this.tagCustomerFeedbacks();
+        // 检查是否有需要处理的反馈并启动倒计时
+        this.checkFeedbackTimeout();
+      },
+      error: (error) => {
+        console.error('加载客户反馈失败:', error);
+      }
     });
     });
   }
   }
 
 
@@ -1257,35 +1302,55 @@ export class ProjectDetail implements OnInit, OnDestroy {
   }
   }
 
 
   loadSettlements(): void {
   loadSettlements(): void {
-    this.projectService.getSettlements().subscribe(settlements => {
-      this.settlements = settlements.filter(s => s.projectId === this.projectId);
+    this.projectService.getSettlements().subscribe({
+      next: (settlements) => {
+        this.settlements = settlements.filter(s => s.projectId === this.projectId);
+      },
+      error: (error) => {
+        console.error('加载结算信息失败:', error);
+      }
     });
     });
   }
   }
 
 
   loadRequirementChecklist(): void {
   loadRequirementChecklist(): void {
-    this.projectService.generateRequirementChecklist(this.projectId).subscribe(checklist => {
-      this.requirementChecklist = checklist;
+    this.projectService.generateRequirementChecklist(this.projectId).subscribe({
+      next: (checklist) => {
+        this.requirementChecklist = checklist;
+      },
+      error: (error) => {
+        console.error('加载需求清单失败:', error);
+      }
     });
     });
   }
   }
 
 
   updateFeedbackStatus(feedbackId: string, status: '处理中' | '已解决'): void {
   updateFeedbackStatus(feedbackId: string, status: '处理中' | '已解决'): void {
-    this.projectService.updateFeedbackStatus(feedbackId, status).subscribe(() => {
-      this.loadCustomerFeedbacks(); // 重新加载反馈
-      // 清除倒计时
-      if (this.countdownInterval) {
-        clearInterval(this.countdownInterval);
-        this.feedbackTimeoutCountdown = 0;
+    this.projectService.updateFeedbackStatus(feedbackId, status).subscribe({
+      next: () => {
+        this.loadCustomerFeedbacks(); // 重新加载反馈
+        // 清除倒计时
+        if (this.countdownInterval) {
+          clearInterval(this.countdownInterval);
+          this.feedbackTimeoutCountdown = 0;
+        }
+      },
+      error: (error) => {
+        console.error('更新反馈状态失败:', error);
       }
       }
     });
     });
   }
   }
 
 
   updateProjectStage(stage: ProjectStage): void {
   updateProjectStage(stage: ProjectStage): void {
     if (this.project) {
     if (this.project) {
-      this.projectService.updateProjectStage(this.projectId, stage).subscribe(() => {
-        this.currentStage = stage; // 同步更新本地状态
-        this.project!.currentStage = stage; // 同步更新project对象的currentStage
-        this.loadProjectDetails(); // 重新加载项目详情
-        this.cdr.detectChanges(); // 触发变更检测以更新导航栏颜色
+      this.projectService.updateProjectStage(this.projectId, stage).subscribe({
+        next: () => {
+          this.currentStage = stage; // 同步更新本地状态
+          this.project!.currentStage = stage; // 同步更新project对象的currentStage
+          this.loadProjectDetails(); // 重新加载项目详情
+          this.cdr.detectChanges(); // 触发变更检测以更新导航栏颜色
+        },
+        error: (error) => {
+          console.error('更新项目阶段失败:', error);
+        }
       });
       });
     }
     }
   }
   }
@@ -1324,13 +1389,18 @@ export class ProjectDetail implements OnInit, OnDestroy {
     }
     }
   }
   }
   generateReminderMessage(): void {
   generateReminderMessage(): void {
-    this.projectService.generateReminderMessage('stagnation').subscribe(message => {
-      this.reminderMessage = message;
-      
-      // 3秒后自动清除提醒
-      setTimeout(() => {
-        this.reminderMessage = '';
-      }, 3000);
+    this.projectService.generateReminderMessage('stagnation').subscribe({
+      next: (message) => {
+        this.reminderMessage = message;
+        
+        // 3秒后自动清除提醒
+        setTimeout(() => {
+          this.reminderMessage = '';
+        }, 3000);
+      },
+      error: (error) => {
+        console.error('生成提醒消息失败:', error);
+      }
     });
     });
   }
   }
 
 
@@ -2971,7 +3041,24 @@ export class ProjectDetail implements OnInit, OnDestroy {
     
     
     // 同步关键信息
     // 同步关键信息
     this.syncRequirementKeyInfo(data);
     this.syncRequirementKeyInfo(data);
+  }
+
+  // 新增:接收需求映射数据更新
+  onMappingDataUpdated(mappingData: any): void {
+    console.log('🔄 收到需求映射数据更新:', mappingData);
+    
+    this.mappingUploadedFiles = mappingData.uploadedFiles || [];
+    this.mappingAnalysisResult = mappingData.analysisResult;
+    this.mappingRequirementMapping = mappingData.requirementMapping;
+    this.mappingTestSteps = mappingData.testSteps || [];
+    this.mappingIsAnalyzing = mappingData.isAnalyzing || false;
+    this.mappingIsGeneratingMapping = mappingData.isGeneratingMapping || false;
     
     
+    this.cdr.detectChanges(); // 触发界面更新
+  }
+
+  // 继续原有的onRequirementDataUpdated方法内容
+  private updateProjectInfoFromRequirementData(data: any): void {
     // 更新客户信息显示
     // 更新客户信息显示
     if (data && this.project) {
     if (data && this.project) {
       // 更新项目的客户信息
       // 更新项目的客户信息
@@ -5156,7 +5243,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
   
   
   // 触发空间文件输入
   // 触发空间文件输入
   triggerSpaceFileInput(processId: string, spaceId: string): void {
   triggerSpaceFileInput(processId: string, spaceId: string): void {
-    const inputId = `spaceFileInput_${processId}_${spaceId}`;
+    const inputId = `space-file-input-${processId}-${spaceId}`;
     const input = document.getElementById(inputId) as HTMLInputElement;
     const input = document.getElementById(inputId) as HTMLInputElement;
     if (input) {
     if (input) {
       input.click();
       input.click();
@@ -5204,6 +5291,69 @@ export class ProjectDetail implements OnInit, OnDestroy {
     return process.content[spaceId].images || [];
     return process.content[spaceId].images || [];
   }
   }
   
   
+  // 确认阶段上传并推进工作流
+  confirmStageUpload(stageId: string): void {
+    const stageMap: { [key: string]: ProjectStage } = {
+      'modeling': '建模',
+      'softDecor': '软装', 
+      'rendering': '渲染',
+      'postProduction': '后期'
+    };
+    
+    const currentStage = stageMap[stageId];
+    if (!currentStage) return;
+    
+    // 检查当前阶段是否有上传的图片
+    const process = this.deliveryProcesses.find(p => p.id === stageId);
+    if (!process) return;
+    
+    const hasImages = Object.values(process.content).some(space => 
+      space.images && space.images.length > 0
+    );
+    
+    if (!hasImages) {
+      alert('请先上传图片再确认');
+      return;
+    }
+    
+    // 推进到下一阶段
+    const currentIndex = this.stageOrder.indexOf(currentStage);
+    if (currentIndex < this.stageOrder.length - 1) {
+      const nextStage = this.stageOrder[currentIndex + 1];
+      
+      // 更新当前阶段
+      this.currentStage = nextStage;
+      if (this.project) {
+        this.project.currentStage = nextStage;
+      }
+      
+      // 展开下一阶段
+      this.expandedStages[nextStage] = true;
+      
+      // 滚动到下一阶段
+      setTimeout(() => {
+        this.scrollToStage(nextStage);
+      }, 100);
+      
+      console.log(`阶段推进: ${currentStage} -> ${nextStage}`);
+    } else {
+      // 如果是最后一个阶段,标记为完成
+      console.log(`交付执行阶段完成: ${currentStage}`);
+      alert('交付执行阶段已完成!');
+    }
+  }
+  
+  // 检查是否可以确认阶段上传
+  canConfirmStageUpload(stageId: string): boolean {
+    // 检查是否有上传的图片
+    const process = this.deliveryProcesses.find(p => p.id === stageId);
+    if (!process) return false;
+    
+    return Object.values(process.content).some(space => 
+      space.images && space.images.length > 0
+    );
+  }
+  
   // 获取空间备注
   // 获取空间备注
   getSpaceNotes(processId: string, spaceId: string): string {
   getSpaceNotes(processId: string, spaceId: string): string {
     const process = this.deliveryProcesses.find(p => p.id === processId);
     const process = this.deliveryProcesses.find(p => p.id === processId);
@@ -5259,4 +5409,57 @@ export class ProjectDetail implements OnInit, OnDestroy {
     
     
     alert(`✨ ${title}\n\n${description}\n\n点击确定关闭`);
     alert(`✨ ${title}\n\n${description}\n\n点击确定关闭`);
   }
   }
+
+  // ==================== 需求映射辅助方法 ====================
+  
+  getMaterialName(category: string | undefined): string {
+    if (!category) return '未识别';
+    const nameMap: { [key: string]: string } = {
+      'wood': '木材', 'metal': '金属', 'fabric': '织物', 'leather': '皮革',
+      'plastic': '塑料', 'glass': '玻璃', 'ceramic': '陶瓷', 'stone': '石材', 'composite': '复合材料'
+    };
+    return nameMap[category] || category;
+  }
+
+  getLightingMoodName(mood: string | undefined): string {
+    if (!mood) return '未识别';
+    const nameMap: { [key: string]: string } = {
+      'dramatic': '戏剧性', 'romantic': '浪漫', 'energetic': '活力',
+      'calm': '平静', 'mysterious': '神秘', 'cheerful': '愉悦', 'professional': '专业'
+    };
+    return nameMap[mood] || mood;
+  }
+
+  getColorHarmonyName(harmony: string | undefined): string {
+    if (!harmony) return '未知';
+    const nameMap: { [key: string]: string } = {
+      'monochromatic': '单色调和', 'analogous': '类似色调和', 'complementary': '互补色调和',
+      'triadic': '三角色调和', 'tetradic': '四角色调和', 'split-complementary': '分裂互补色调和'
+    };
+    return nameMap[harmony] || harmony;
+  }
+
+  getTemperatureName(temp: string | undefined): string {
+    if (!temp) return '未知';
+    const nameMap: { [key: string]: string } = {
+      'warm': '暖色调', 'neutral': '中性色调', 'cool': '冷色调'
+    };
+    return nameMap[temp] || temp;
+  }
+
+  getLayoutTypeName(type: string | undefined): string {
+    if (!type) return '未知';
+    const nameMap: { [key: string]: string } = {
+      'open': '开放式', 'enclosed': '封闭式', 'semi-open': '半开放式', 'multi-level': '多层次'
+    };
+    return nameMap[type] || type;
+  }
+
+  getFlowTypeName(flow: string | undefined): string {
+    if (!flow) return '未知';
+    const nameMap: { [key: string]: string } = {
+      'linear': '线性流线', 'circular': '环形流线', 'grid': '网格流线', 'organic': '有机流线'
+    };
+    return nameMap[flow] || flow;
+  }
 }
 }

+ 252 - 272
src/app/shared/components/requirements-confirm-card/requirements-confirm-card.html

@@ -300,306 +300,286 @@
     <!-- 需求映射标签页 -->
     <!-- 需求映射标签页 -->
     @if (activeTab === 'mapping') {
     @if (activeTab === 'mapping') {
       <div class="mapping-section">
       <div class="mapping-section">
-        
-        <!-- 一致性警告 -->
-        @if (showConsistencyWarning) {
-          <div class="consistency-warning">
-            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-              <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
-              <line x1="12" y1="9" x2="12" y2="13"></line>
-              <line x1="12" y1="17" x2="12.01" y2="17"></line>
-            </svg>
-            {{ warningMessage }}
+        <!-- 测试步骤进度 -->
+        <div class="test-progress">
+          <h3>需求映射进度</h3>
+          <div class="progress-summary">
+            @if (uploadedFiles.length > 0) {
+              <span class="progress-badge">
+                📸 已同步 {{ uploadedFiles.length }} 张参考图片
+              </span>
+            }
           </div>
           </div>
-        }
-
-        <div class="indicator-grid">
-          
-          <!-- 色彩指标 -->
-          <div class="indicator-group">
-            <h5>色彩氛围</h5>
-            
-            <!-- RGB滑块 -->
-            <div class="indicator-item">
-              <label>主色调 RGB</label>
-              <div class="rgb-controls">
-                <div class="rgb-slider">
-                  <span>R</span>
-                  <input 
-                    type="range" 
-                    [min]="indicatorRanges.color.r.min" 
-                    [max]="indicatorRanges.color.r.max" 
-                    [(ngModel)]="colorIndicators.mainColor.r"
-                    (ngModelChange)="onSliderChange('r', $event)">
-                  <input 
-                    type="number" 
-                    class="slider-input"
-                    [min]="indicatorRanges.color.r.min" 
-                    [max]="indicatorRanges.color.r.max" 
-                    [(ngModel)]="colorIndicators.mainColor.r"
-                    (ngModelChange)="onInputChange('r', $event)"
-                    (blur)="validateInput('r', $event.target.value)">
+          <div class="steps-container">
+            @for (step of testSteps; track step.id) {
+              <div class="step-item" [class]="getStepClass(step.status)">
+                <div class="step-icon">{{ getStepIcon(step.status) }}</div>
+                <div class="step-info">
+                  <div class="step-name">{{ step.name }}</div>
+                  <div class="step-status">
+                    @switch (step.status) {
+                      @case ('pending') { 等待中 }
+                      @case ('in-progress') { 进行中... }
+                      @case ('completed') { 已完成 }
+                      @case ('error') { 失败 }
+                    }
+                  </div>
                 </div>
                 </div>
-                <div class="rgb-slider">
-                  <span>G</span>
-                  <input 
-                    type="range" 
-                    [min]="indicatorRanges.color.g.min" 
-                    [max]="indicatorRanges.color.g.max" 
-                    [(ngModel)]="colorIndicators.mainColor.g"
-                    (ngModelChange)="onSliderChange('g', $event)">
-                  <input 
-                    type="number" 
-                    class="slider-input"
-                    [min]="indicatorRanges.color.g.min" 
-                    [max]="indicatorRanges.color.g.max" 
-                    [(ngModel)]="colorIndicators.mainColor.g"
-                    (ngModelChange)="onInputChange('g', $event)"
-                    (blur)="validateInput('g', $event.target.value)">
+              </div>
+            }
+          </div>
+        </div>
+
+        <!-- 文件上传区域 -->
+        <div class="upload-section">
+          <h3>1. 图片上传</h3>
+          <div class="upload-area" [class.uploading]="isUploading">
+            @if (uploadedFiles.length === 0) {
+              <div class="upload-dropzone">
+                <div class="upload-icon">📁</div>
+                <div class="upload-text">选择图片文件进行测试</div>
+                <div class="upload-hint">支持 JPG、PNG 格式,可选择多张图片</div>
+                <input type="file" 
+                       accept="image/*" 
+                       multiple 
+                       (change)="onFileSelectedForMapping($event)"
+                       class="file-input">
+              </div>
+            } @else {
+              <div class="uploaded-files">
+                <h4>已上传文件 ({{ uploadedFiles.length }} 张图片)</h4>
+                <div class="upload-source-info">
+                  <span class="info-tag">✨ 来自素材分析的参考图片</span>
                 </div>
                 </div>
-                <div class="rgb-slider">
-                  <span>B</span>
-                  <input 
-                    type="range" 
-                    [min]="indicatorRanges.color.b.min" 
-                    [max]="indicatorRanges.color.b.max" 
-                    [(ngModel)]="colorIndicators.mainColor.b"
-                    (ngModelChange)="onSliderChange('b', $event)">
-                  <input 
-                    type="number" 
-                    class="slider-input"
-                    [min]="indicatorRanges.color.b.min" 
-                    [max]="indicatorRanges.color.b.max" 
-                    [(ngModel)]="colorIndicators.mainColor.b"
-                    (ngModelChange)="onInputChange('b', $event)"
-                    (blur)="validateInput('b', $event.target.value)">
+                <div class="files-grid">
+                  @for (file of uploadedFiles; track file.id) {
+                    <div class="file-item">
+                      <img [src]="file.preview || file.url" [alt]="file.name" class="file-preview">
+                      <div class="file-info">
+                        <div class="file-name">{{ file.name }}</div>
+                        <div class="file-size">{{ (file.size! / 1024 / 1024).toFixed(2) }} MB</div>
+                      </div>
+                      <button class="remove-file-btn" (click)="removeUploadedFile(file.id)" title="移除">
+                        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                          <line x1="18" y1="6" x2="6" y2="18"></line>
+                          <line x1="6" y1="6" x2="18" y2="18"></line>
+                        </svg>
+                      </button>
+                    </div>
+                  }
                 </div>
                 </div>
-                <div class="color-preview" [style.background-color]="getRgbString()"></div>
               </div>
               </div>
-            </div>
+            }
             
             
-            <!-- 色温滑块 -->
-            <div class="indicator-item">
-              <label>色温 ({{ getColorTemperatureDescription() }})</label>
-              <div class="slider-container">
-                <input 
-                  type="range" 
-                  [min]="indicatorRanges.color.temperature.min" 
-                  [max]="indicatorRanges.color.temperature.max" 
-                  [value]="colorIndicators.colorTemperature"
-                  (input)="onSliderChange('colorTemperature', +$event.target.value)">
-                <input 
-                  type="number" 
-                  class="slider-input"
-                  [min]="indicatorRanges.color.temperature.min" 
-                  [max]="indicatorRanges.color.temperature.max" 
-                  [value]="colorIndicators.colorTemperature"
-                  (input)="onInputChange('colorTemperature', +$event.target.value)"
-                  (blur)="validateInput('colorTemperature', $event.target.value)">
-                <span class="unit-label">K</span>
+            @if (isUploading) {
+              <div class="loading-overlay">
+                <div class="loading-spinner"></div>
+                <div class="loading-text">正在上传文件...</div>
               </div>
               </div>
-            </div>
-
-            <!-- 饱和度和亮度 -->
-            <div class="indicator-item">
-              <label>饱和度</label>
-              <div class="slider-container">
-                <input 
-                  type="range" 
-                  [min]="indicatorRanges.color.saturation.min" 
-                  [max]="indicatorRanges.color.saturation.max" 
-                  [value]="colorIndicators.saturation"
-                  (input)="onSliderChange('saturation', +$event.target.value)">
-                <input 
-                  type="number" 
-                  class="slider-input"
-                  [min]="indicatorRanges.color.saturation.min" 
-                  [max]="indicatorRanges.color.saturation.max" 
-                  [value]="colorIndicators.saturation"
-                  (input)="onInputChange('saturation', +$event.target.value)"
-                  (blur)="validateInput('saturation', $event.target.value)">
-                <span class="unit-label">%</span>
-              </div>
-            </div>
+            }
           </div>
           </div>
+        </div>
 
 
-          <!-- 空间指标 -->
-          <div class="indicator-group">
-            <h5>空间结构</h5>
-            <div class="indicator-item">
-              <label>留白占比</label>
-              <div class="slider-container">
-                <input 
-                  type="range" 
-                  [min]="indicatorRanges.space.blankRatio.min" 
-                  [max]="indicatorRanges.space.blankRatio.max" 
-                  [value]="spaceIndicators.blankRatio"
-                  (input)="onSliderChange('blankRatio', +$event.target.value)">
-                <input 
-                  type="number" 
-                  class="slider-input"
-                  [min]="indicatorRanges.space.blankRatio.min" 
-                  [max]="indicatorRanges.space.blankRatio.max" 
-                  [value]="spaceIndicators.blankRatio"
-                  (input)="onInputChange('blankRatio', +$event.target.value)"
-                  (blur)="validateInput('blankRatio', $event.target.value)">
-                <span class="unit-label">%</span>
+        <!-- 分析结果区域 -->
+        <div class="analysis-section" [class.disabled]="uploadedFiles.length === 0">
+          <h3>2. 图片分析</h3>
+          
+          @if (isAnalyzing) {
+            <div class="analysis-loading">
+              <div class="loading-spinner"></div>
+              <div class="loading-text">
+                <h4>正在分析图片...</h4>
+                <p>
+                  @if (uploadedFiles.length > 1) {
+                    正在分析 {{ uploadedFiles.length }} 张图片,系统将合并分析结果
+                  } @else {
+                    系统正在进行色彩、纹理、形态、图案和灯光分析
+                  }
+                </p>
               </div>
               </div>
             </div>
             </div>
-            
-            <div class="indicator-item">
-              <label>直线条占比</label>
-              <div class="slider-container">
-                <input 
-                  type="range" 
-                  [min]="indicatorRanges.space.lineRatio.min" 
-                  [max]="indicatorRanges.space.lineRatio.max" 
-                  [value]="spaceIndicators.lineRatio"
-                  (input)="onSliderChange('lineRatio', +$event.target.value)">
-                <input 
-                  type="number" 
-                  class="slider-input"
-                  [min]="indicatorRanges.space.lineRatio.min" 
-                  [max]="indicatorRanges.space.lineRatio.max" 
-                  [value]="spaceIndicators.lineRatio"
-                  (input)="onInputChange('lineRatio', +$event.target.value)"
-                  (blur)="validateInput('lineRatio', $event.target.value)">
-                <span class="unit-label">%</span>
+          } @else if (analysisError) {
+            <div class="analysis-error">
+              <div class="error-icon">❌</div>
+              <div class="error-text">
+                <h4>分析失败</h4>
+                <p>{{ analysisError }}</p>
+                <button class="retry-btn" (click)="retryAnalysis()">重新分析</button>
               </div>
               </div>
             </div>
             </div>
-
-            <div class="indicator-item">
-              <label>动线宽度</label>
-              <div class="slider-container">
-                <input 
-                  type="range" 
-                  [min]="indicatorRanges.space.flowWidth.min" 
-                  [max]="indicatorRanges.space.flowWidth.max" 
-                  step="0.1"
-                  [value]="spaceIndicators.flowWidth"
-                  (input)="onSliderChange('flowWidth', +$event.target.value)">
-                <input 
-                  type="number" 
-                  class="slider-input"
-                  [min]="indicatorRanges.space.flowWidth.min" 
-                  [max]="indicatorRanges.space.flowWidth.max" 
-                  step="0.1"
-                  [value]="spaceIndicators.flowWidth"
-                  (input)="onInputChange('flowWidth', +$event.target.value)"
-                  (blur)="validateInput('flowWidth', $event.target.value)">
-                <span class="unit-label">m</span>
+          } @else if (analysisResult) {
+            <div class="analysis-result">
+              <h4>分析完成 ✅</h4>
+              <div class="analysis-summary">
+                <div class="summary-item">
+                  <span class="label">主要颜色:</span>
+                  <span class="value">{{ analysisResult.enhancedAnalysis?.colorWheel?.colorDistribution?.length || 0 }} 种</span>
+                </div>
+                <div class="summary-item">
+                  <span class="label">材质类型:</span>
+                  <span class="value">{{ getMaterialName(analysisResult.textureAnalysis?.materialClassification?.primaryMaterial?.category) || '未识别' }}</span>
+                </div>
+                <div class="summary-item">
+                  <span class="label">灯光情绪:</span>
+                  <span class="value">{{ getLightingMoodName(analysisResult.lightingAnalysis?.ambientAnalysis?.lightingMood?.primary) || '未识别' }}</span>
+                </div>
+                <div class="summary-item">
+                  <span class="label">空间形态:</span>
+                  <span class="value">{{ analysisResult.formAnalysis?.geometryAnalysis?.complexity ? getComplexityName(analysisResult.formAnalysis.geometryAnalysis.complexity) : '未识别' }}</span>
+                </div>
               </div>
               </div>
             </div>
             </div>
-          </div>
-
-          <!-- 材质权重 -->
-          <div class="indicator-group">
-            <h5>材质权重</h5>
-            <div class="indicator-item">
-              <label>布艺占比</label>
-              <div class="slider-container">
-                <input 
-                  type="range" 
-                  [min]="indicatorRanges.material.fabricRatio.min" 
-                  [max]="indicatorRanges.material.fabricRatio.max" 
-                  [value]="materialIndicators.fabricRatio"
-                  (input)="onSliderChange('fabricRatio', +$event.target.value)">
-                <input 
-                  type="number" 
-                  class="slider-input"
-                  [min]="indicatorRanges.material.fabricRatio.min" 
-                  [max]="indicatorRanges.material.fabricRatio.max" 
-                  [value]="materialIndicators.fabricRatio"
-                  (input)="onInputChange('fabricRatio', +$event.target.value)"
-                  (blur)="validateInput('fabricRatio', $event.target.value)">
-                <span class="unit-label">%</span>
+          } @else {
+            <div class="analysis-placeholder">
+              <div class="placeholder-icon">🔍</div>
+              <div class="placeholder-text">
+                <h4>等待分析</h4>
+                <p>请先上传图片,系统将自动开始分析</p>
               </div>
               </div>
             </div>
             </div>
+          }
+        </div>
 
 
-            <div class="indicator-item">
-              <label>木质占比</label>
-              <div class="slider-container">
-                <input 
-                  type="range" 
-                  [min]="indicatorRanges.material.woodRatio.min" 
-                  [max]="indicatorRanges.material.woodRatio.max" 
-                  [value]="materialIndicators.woodRatio"
-                  (input)="onSliderChange('woodRatio', +$event.target.value)">
-                <input 
-                  type="number" 
-                  class="slider-input"
-                  [min]="indicatorRanges.material.woodRatio.min" 
-                  [max]="indicatorRanges.material.woodRatio.max" 
-                  [value]="materialIndicators.woodRatio"
-                  (input)="onInputChange('woodRatio', +$event.target.value)"
-                  (blur)="validateInput('woodRatio', $event.target.value)">
-                <span class="unit-label">%</span>
+        <!-- 需求映射结果区域 -->
+        <div class="mapping-section" [class.disabled]="!analysisResult">
+          <h3>3. 需求映射生成</h3>
+          
+          @if (isGeneratingMapping) {
+            <div class="mapping-loading">
+              <div class="loading-spinner"></div>
+              <div class="loading-text">
+                <h4>正在生成需求映射...</h4>
+                <p>基于分析结果生成场景参数和映射关系</p>
               </div>
               </div>
             </div>
             </div>
-
-            <div class="indicator-item">
-              <label>金属占比</label>
-              <div class="slider-container">
-                <input 
-                  type="range" 
-                  [min]="indicatorRanges.material.metalRatio.min" 
-                  [max]="indicatorRanges.material.metalRatio.max" 
-                  [value]="materialIndicators.metalRatio"
-                  (input)="onSliderChange('metalRatio', +$event.target.value)">
-                <input 
-                  type="number" 
-                  class="slider-input"
-                  [min]="indicatorRanges.material.metalRatio.min" 
-                  [max]="indicatorRanges.material.metalRatio.max" 
-                  [value]="materialIndicators.metalRatio"
-                  (input)="onInputChange('metalRatio', +$event.target.value)"
-                  (blur)="validateInput('metalRatio', $event.target.value)">
-                <span class="unit-label">%</span>
+          } @else if (mappingError) {
+            <div class="mapping-error">
+              <div class="error-icon">❌</div>
+              <div class="error-text">
+                <h4>映射生成失败</h4>
+                <p>{{ mappingError }}</p>
+                <button class="retry-btn" (click)="retryMapping()">重新生成</button>
               </div>
               </div>
             </div>
             </div>
-
-            <div class="indicator-item">
-              <label>光滑度</label>
-              <div class="slider-container">
-                <input 
-                  type="range" 
-                  [min]="indicatorRanges.material.smoothness.min" 
-                  [max]="indicatorRanges.material.smoothness.max" 
-                  [value]="materialIndicators.smoothness"
-                  (input)="onSliderChange('smoothness', +$event.target.value)">
-                <input 
-                  type="number" 
-                  class="slider-input"
-                  [min]="indicatorRanges.material.smoothness.min" 
-                  [max]="indicatorRanges.material.smoothness.max" 
-                  [value]="materialIndicators.smoothness"
-                  (input)="onInputChange('smoothness', +$event.target.value)"
-                  (blur)="validateInput('smoothness', $event.target.value)">
-                <span class="unit-label">/10</span>
+          } @else if (requirementMapping) {
+            <div class="mapping-result">
+              <h4>需求映射生成完成 ✅</h4>
+              
+              <!-- 场景生成信息 -->
+              <div class="mapping-section-item">
+                <h5>场景生成</h5>
+                <div class="scene-info">
+                  <div class="info-row">
+                    <span class="label">基础场景:</span>
+                    <span class="value">{{ requirementMapping.sceneGeneration.baseScene }}</span>
+                  </div>
+                  @if (requirementMapping.sceneGeneration.atmospherePreview) {
+                    <div class="atmosphere-preview">
+                      <img [src]="requirementMapping.sceneGeneration.atmospherePreview" 
+                           alt="氛围感预览图"
+                           class="preview-image">
+                      <div class="preview-label">氛围感预览图</div>
+                    </div>
+                  }
+                </div>
               </div>
               </div>
-            </div>
-          </div>
 
 
-          <!-- 预设氛围 -->
-          <div class="indicator-group">
-            <h5>预设氛围</h5>
-            <div class="atmosphere-presets">
-              @for (preset of presetAtmospheres; track preset.name) {
-                <div class="preset-card" (click)="applyPresetAtmosphere(preset)">
-                  <div class="preset-color" [style.background]="'rgb(' + preset.rgb + ')'"></div>
-                  <div class="preset-info">
-                    <div class="preset-name">{{ preset.name }}</div>
-                    <div class="preset-details">{{ preset.colorTemp }}</div>
-                    <div class="preset-materials">
-                      @for (material of preset.materials; track material) {
-                        <span class="material-tag">{{ material }}</span>
-                      }
+              <!-- 参数映射信息 -->
+              <div class="mapping-section-item">
+                <h5>参数映射</h5>
+                <div class="params-grid">
+                  <!-- 颜色参数 -->
+                  <div class="param-group">
+                    <h6>颜色映射</h6>
+                    <div class="color-params">
+                      <div class="param-item">
+                        <span class="label">主要颜色:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.colorParams.primaryColors.length }} 种</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">色彩和谐:</span>
+                        <span class="value">{{ getColorHarmonyName(requirementMapping.parameterMapping.colorParams.colorHarmony) }}</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">饱和度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.colorParams.saturation }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">亮度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.colorParams.brightness }}%</span>
+                      </div>
+                    </div>
+                  </div>
+
+                  <!-- 空间参数 -->
+                  <div class="param-group">
+                    <h6>空间映射</h6>
+                    <div class="space-params">
+                      <div class="param-item">
+                        <span class="label">布局类型:</span>
+                        <span class="value">{{ getLayoutTypeName(requirementMapping.parameterMapping.spaceParams.layout.type) }}</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">空间流线:</span>
+                        <span class="value">{{ getFlowTypeName(requirementMapping.parameterMapping.spaceParams.layout.flow) }}</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">家具比例:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.spaceParams.scale.furniture }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">开放度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.spaceParams.scale.openness }}%</span>
+                      </div>
+                    </div>
+                  </div>
+
+                  <!-- 材质参数 -->
+                  <div class="param-group">
+                    <h6>材质映射</h6>
+                    <div class="material-params">
+                      <div class="param-item">
+                        <span class="label">纹理缩放:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.textureScale }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">反射率:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.reflectivity }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">粗糙度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.roughness }}%</span>
+                      </div>
+                      <div class="param-item">
+                        <span class="label">金属度:</span>
+                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.metallic }}%</span>
+                      </div>
                     </div>
                     </div>
                   </div>
                   </div>
                 </div>
                 </div>
-              }
+              </div>
+
+              <!-- 测试结果下载 -->
+              <div class="test-actions">
+                <button class="download-btn" (click)="downloadTestResult()">
+                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
+                    <polyline points="7,10 12,15 17,10"></polyline>
+                    <line x1="12" y1="15" x2="12" y2="3"></line>
+                  </svg>
+                  下载测试结果
+                </button>
+              </div>
             </div>
             </div>
-          </div>
+          } @else {
+            <div class="mapping-placeholder">
+              <div class="placeholder-icon">🎯</div>
+              <div class="placeholder-text">
+                <h4>等待映射生成</h4>
+                <p>请先完成图片分析,系统将自动生成需求映射</p>
+              </div>
+            </div>
+          }
         </div>
         </div>
       </div>
       </div>
     }
     }

+ 607 - 0
src/app/shared/components/requirements-confirm-card/requirements-confirm-card.scss

@@ -1919,7 +1919,614 @@
   }
   }
 }
 }
 
 
+// 需求映射测试相关样式
+.test-progress {
+  margin-bottom: 32px;
+  padding: 20px;
+  background: #f8f9fa;
+  border-radius: 8px;
+  border: 1px solid #e9ecef;
+
+  h3 {
+    margin: 0 0 8px 0;
+    font-size: 18px;
+    color: #495057;
+  }
+
+  .progress-summary {
+    margin-bottom: 16px;
+    
+    .progress-badge {
+      display: inline-block;
+      padding: 6px 16px;
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      color: white;
+      border-radius: 16px;
+      font-size: 13px;
+      font-weight: 600;
+      box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
+      animation: badgePulse 2s ease-in-out infinite;
+    }
+  }
+
+  .steps-container {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+    gap: 16px;
+  }
+
+  .step-item {
+    display: flex;
+    align-items: center;
+    padding: 12px;
+    background: white;
+    border-radius: 6px;
+    border: 2px solid #e9ecef;
+    transition: all 0.2s ease;
+
+    &.step-pending {
+      border-color: #e9ecef;
+      .step-icon { color: #6c757d; }
+    }
+
+    &.step-in-progress {
+      border-color: #ffc107;
+      background: #fff8e1;
+      .step-icon { color: #ffc107; }
+    }
+
+    &.step-completed {
+      border-color: #28a745;
+      background: #f8fff9;
+      .step-icon { color: #28a745; }
+    }
+
+    &.step-error {
+      border-color: #dc3545;
+      background: #fff5f5;
+      .step-icon { color: #dc3545; }
+    }
+
+    .step-icon {
+      font-size: 20px;
+      margin-right: 12px;
+    }
+
+    .step-info {
+      .step-name {
+        font-weight: 500;
+        font-size: 14px;
+        color: #495057;
+      }
+
+      .step-status {
+        font-size: 12px;
+        color: #6c757d;
+        margin-top: 2px;
+      }
+    }
+  }
+}
+
+.upload-section,
+.analysis-section,
+.mapping-section {
+  margin-bottom: 32px;
+  padding: 24px;
+  background: white;
+  border-radius: 8px;
+  border: 1px solid #e9ecef;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+
+  &.disabled {
+    opacity: 0.6;
+    pointer-events: none;
+  }
+
+  h3 {
+    margin: 0 0 20px 0;
+    font-size: 20px;
+    color: #495057;
+    border-bottom: 2px solid #e9ecef;
+    padding-bottom: 8px;
+  }
+}
+
+// 需求映射标签页固定高度和滚动
+.mapping-section {
+  max-height: calc(100vh - 200px); // 固定最大高度,根据视口高度调整
+  overflow-y: auto; // 添加垂直滚动条
+  overflow-x: hidden; // 隐藏水平滚动条
+  
+  // 美化滚动条样式
+  &::-webkit-scrollbar {
+    width: 8px;
+  }
+  
+  &::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 4px;
+  }
+  
+  &::-webkit-scrollbar-thumb {
+    background: #888;
+    border-radius: 4px;
+    
+    &:hover {
+      background: #555;
+    }
+  }
+  
+  // Firefox 滚动条样式
+  scrollbar-width: thin;
+  scrollbar-color: #888 #f1f1f1;
+}
+
+.upload-area {
+  position: relative;
+  min-height: 200px;
+
+  .upload-dropzone {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    min-height: 200px;
+    border: 2px dashed #dee2e6;
+    border-radius: 8px;
+    background: #f8f9fa;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    position: relative;
+
+    &:hover {
+      border-color: #667eea;
+      background: #f0f4ff;
+    }
+
+    .upload-icon {
+      font-size: 48px;
+      margin-bottom: 16px;
+      opacity: 0.7;
+    }
+
+    .upload-text {
+      font-size: 18px;
+      font-weight: 500;
+      color: #495057;
+      margin-bottom: 8px;
+    }
+
+    .upload-hint {
+      font-size: 14px;
+      color: #6c757d;
+    }
+
+    .file-input {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      opacity: 0;
+      cursor: pointer;
+    }
+  }
+
+  .uploaded-files {
+    h4 {
+      margin: 0 0 8px 0;
+      font-size: 16px;
+      color: #495057;
+    }
+
+    .upload-source-info {
+      margin-bottom: 12px;
+      
+      .info-tag {
+        display: inline-block;
+        padding: 4px 12px;
+        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+        color: white;
+        border-radius: 12px;
+        font-size: 12px;
+        font-weight: 500;
+      }
+    }
+
+    .files-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+      gap: 16px;
+    }
+
+    .file-item {
+      position: relative;
+      border: 1px solid #e9ecef;
+      border-radius: 6px;
+      overflow: hidden;
+      background: white;
+      transition: all 0.2s ease;
+      
+      &:hover {
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+        
+        .remove-file-btn {
+          opacity: 1;
+        }
+      }
+
+      .file-preview {
+        width: 100%;
+        height: 120px;
+        object-fit: cover;
+      }
+      
+      .remove-file-btn {
+        position: absolute;
+        top: 4px;
+        right: 4px;
+        width: 24px;
+        height: 24px;
+        border: none;
+        border-radius: 50%;
+        background: rgba(220, 53, 69, 0.9);
+        color: white;
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        opacity: 0;
+        transition: opacity 0.2s ease;
+        
+        &:hover {
+          background: rgba(220, 53, 69, 1);
+        }
+        
+        svg {
+          pointer-events: none;
+        }
+      }
+
+      .file-info {
+        padding: 8px;
+
+        .file-name {
+          font-size: 12px;
+          font-weight: 500;
+          color: #495057;
+          margin-bottom: 4px;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+
+        .file-size {
+          font-size: 11px;
+          color: #6c757d;
+        }
+      }
+    }
+  }
+
+  .loading-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(255, 255, 255, 0.9);
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    border-radius: 8px;
+  }
+}
+
+.analysis-loading,
+.mapping-loading {
+  display: flex;
+  align-items: center;
+  padding: 24px;
+  background: #f0f4ff;
+  border-radius: 6px;
+  border: 1px solid #e3f2fd;
+
+  .loading-text {
+    margin-left: 16px;
+
+    h4 {
+      margin: 0 0 8px 0;
+      font-size: 16px;
+      color: #495057;
+    }
+
+    p {
+      margin: 0;
+      font-size: 14px;
+      color: #6c757d;
+    }
+  }
+}
+
+.analysis-error,
+.mapping-error {
+  display: flex;
+  align-items: center;
+  padding: 24px;
+  background: #fff5f5;
+  border-radius: 6px;
+  border: 1px solid #ffebee;
+
+  .error-icon {
+    font-size: 24px;
+    margin-right: 16px;
+  }
+
+  .error-text {
+    flex: 1;
+
+    h4 {
+      margin: 0 0 8px 0;
+      font-size: 16px;
+      color: #dc3545;
+    }
+
+    p {
+      margin: 0 0 12px 0;
+      font-size: 14px;
+      color: #6c757d;
+    }
+
+    .retry-btn {
+      background: #dc3545;
+      color: white;
+      border: none;
+      padding: 8px 16px;
+      border-radius: 4px;
+      cursor: pointer;
+      font-size: 14px;
+      transition: background 0.2s ease;
+
+      &:hover {
+        background: #c82333;
+      }
+    }
+  }
+}
+
+.analysis-result,
+.mapping-result {
+  h4 {
+    margin: 0 0 16px 0;
+    font-size: 18px;
+    color: #28a745;
+    display: flex;
+    align-items: center;
+  }
+
+  .analysis-summary {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+    gap: 16px;
+    margin-top: 16px;
+  }
+
+  .summary-item {
+    display: flex;
+    justify-content: space-between;
+    padding: 12px;
+    background: #f8f9fa;
+    border-radius: 4px;
+
+    .label {
+      font-weight: 500;
+      color: #495057;
+    }
+
+    .value {
+      color: #6c757d;
+    }
+  }
+}
+
+.mapping-section-item {
+  margin-bottom: 24px;
+
+  h5 {
+    margin: 0 0 16px 0;
+    font-size: 16px;
+    color: #495057;
+    border-bottom: 1px solid #e9ecef;
+    padding-bottom: 8px;
+  }
+
+  .scene-info {
+    .info-row {
+      display: flex;
+      justify-content: space-between;
+      padding: 8px 0;
+      border-bottom: 1px solid #f8f9fa;
+
+      .label {
+        font-weight: 500;
+        color: #495057;
+      }
+
+      .value {
+        color: #6c757d;
+      }
+    }
+
+    .atmosphere-preview {
+      margin-top: 16px;
+      text-align: center;
+
+      .preview-image {
+        max-width: 300px;
+        max-height: 200px;
+        border-radius: 6px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+      }
+
+      .preview-label {
+        margin-top: 8px;
+        font-size: 14px;
+        color: #6c757d;
+      }
+    }
+  }
+
+  .params-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+    gap: 20px;
+  }
+
+  .param-group {
+    background: #f8f9fa;
+    padding: 16px;
+    border-radius: 6px;
+
+    h6 {
+      margin: 0 0 12px 0;
+      font-size: 14px;
+      font-weight: 600;
+      color: #495057;
+    }
+
+    .param-item {
+      display: flex;
+      justify-content: space-between;
+      padding: 6px 0;
+      border-bottom: 1px solid #e9ecef;
+
+      &:last-child {
+        border-bottom: none;
+      }
+
+      .label {
+        font-size: 13px;
+        color: #6c757d;
+      }
+
+      .value {
+        font-size: 13px;
+        font-weight: 500;
+        color: #495057;
+      }
+    }
+  }
+}
+
+.analysis-placeholder,
+.mapping-placeholder {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  min-height: 150px;
+  color: #6c757d;
+
+  .placeholder-icon {
+    font-size: 48px;
+    margin-bottom: 16px;
+    opacity: 0.7;
+  }
+
+  .placeholder-text {
+    text-align: center;
+
+    h4 {
+      margin: 0 0 8px 0;
+      font-size: 16px;
+    }
+
+    p {
+      margin: 0;
+      font-size: 14px;
+      opacity: 0.8;
+    }
+  }
+}
+
+.test-actions {
+  margin-top: 24px;
+  text-align: center;
+
+  .download-btn {
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    background: #667eea;
+    color: white;
+    border: none;
+    padding: 12px 24px;
+    border-radius: 6px;
+    cursor: pointer;
+    font-size: 14px;
+    font-weight: 500;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background: #5a6fd8;
+      transform: translateY(-1px);
+    }
+
+    svg {
+      width: 16px;
+      height: 16px;
+    }
+  }
+}
+
+.loading-spinner {
+  width: 24px;
+  height: 24px;
+  border: 2px solid #f3f3f3;
+  border-top: 2px solid #667eea;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .test-progress {
+    padding: 16px;
+
+    .steps-container {
+      grid-template-columns: 1fr;
+    }
+  }
+
+  .upload-section,
+  .analysis-section,
+  .mapping-section {
+    padding: 16px;
+  }
+
+  .params-grid {
+    grid-template-columns: 1fr;
+  }
+
+  .analysis-summary {
+    grid-template-columns: 1fr;
+  }
+}
+
 @keyframes pulse {
 @keyframes pulse {
   0%, 100% { opacity: 1; }
   0%, 100% { opacity: 1; }
   50% { opacity: 0.5; }
   50% { opacity: 0.5; }
+}
+
+@keyframes badgePulse {
+  0%, 100% {
+    box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
+    transform: scale(1);
+  }
+  50% {
+    box-shadow: 0 4px 16px rgba(102, 126, 234, 0.5);
+    transform: scale(1.02);
+  }
 }
 }

+ 585 - 9
src/app/shared/components/requirements-confirm-card/requirements-confirm-card.ts

@@ -1,11 +1,14 @@
 import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
 import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { CommonModule } from '@angular/common';
 import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Subscription } from 'rxjs';
 import { UploadSuccessModalComponent } from '../upload-success-modal/upload-success-modal.component';
 import { UploadSuccessModalComponent } from '../upload-success-modal/upload-success-modal.component';
 import { GlobalPromptComponent } from '../global-prompt/global-prompt.component';
 import { GlobalPromptComponent } from '../global-prompt/global-prompt.component';
 import { ColorAnalysisService, ColorAnalysisResult } from '../../services/color-analysis.service';
 import { ColorAnalysisService, ColorAnalysisResult } from '../../services/color-analysis.service';
 import { FullReportOverlayComponent } from '../full-report-overlay/full-report-overlay.component';
 import { FullReportOverlayComponent } from '../full-report-overlay/full-report-overlay.component';
 import { CadAnalysisService, CadAnalysisResult } from '../../services/cad-analysis.service';
 import { CadAnalysisService, CadAnalysisResult } from '../../services/cad-analysis.service';
+import { RequirementMappingService } from '../../../services/requirement-mapping.service';
+import { RequirementMapping, SceneTemplate } from '../../../models/requirement-mapping.interface';
 
 
 // 素材文件接口
 // 素材文件接口
 interface MaterialFile {
 interface MaterialFile {
@@ -219,6 +222,16 @@ interface RequirementItem {
   showComments?: boolean;
   showComments?: boolean;
 }
 }
 
 
+// 上传文件接口
+interface UploadedFile {
+  id: string;
+  name: string;
+  url: string;
+  size?: number;
+  type: 'image' | 'cad' | 'text';
+  preview?: string;
+}
+
 @Component({
 @Component({
   selector: 'app-requirements-confirm-card',
   selector: 'app-requirements-confirm-card',
   standalone: true,
   standalone: true,
@@ -234,6 +247,14 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
   @Output() progressUpdated = new EventEmitter<number>();
   @Output() progressUpdated = new EventEmitter<number>();
   @Output() stageCompleted = new EventEmitter<{ stage: string; allStagesCompleted: boolean }>();
   @Output() stageCompleted = new EventEmitter<{ stage: string; allStagesCompleted: boolean }>();
   @Output() dataUpdated = new EventEmitter<any>(); // 新增:实时数据更新事件
   @Output() dataUpdated = new EventEmitter<any>(); // 新增:实时数据更新事件
+  @Output() mappingDataUpdated = new EventEmitter<{
+    uploadedFiles: any[];
+    analysisResult: any;
+    requirementMapping: any;
+    testSteps: any[];
+    isAnalyzing: boolean;
+    isGeneratingMapping: boolean;
+  }>(); // 新增:需求映射数据更新事件
 
 
   // 表单
   // 表单
   materialForm!: FormGroup;
   materialForm!: FormGroup;
@@ -358,11 +379,33 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
   // 全屏报告覆盖层
   // 全屏报告覆盖层
   showFullReportOverlay = false;
   showFullReportOverlay = false;
 
 
+  // 需求映射测试相关属性
+  testSteps = [
+    { id: 'upload', name: '图片上传', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' },
+    { id: 'analysis', name: '图片分析', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' },
+    { id: 'mapping', name: '需求映射', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' },
+    { id: 'preview', name: '氛围预览', status: 'pending' as 'pending' | 'in-progress' | 'completed' | 'error' }
+  ];
+  
+  // 需求映射相关状态
+  isGeneratingMapping = false;
+  requirementMapping: RequirementMapping | null = null;
+  mappingError: string | null = null;
+  analysisError: string | null = null;
+  analysisResult: ColorAnalysisResult | undefined = undefined;
+  
+  // 上传模态框状态
+  showUploadModal = false;
+  
+  // 订阅管理
+  private subscriptions: Subscription[] = [];
+
   constructor(
   constructor(
     private fb: FormBuilder,
     private fb: FormBuilder,
     private cdr: ChangeDetectorRef,
     private cdr: ChangeDetectorRef,
     private colorAnalysisService: ColorAnalysisService,
     private colorAnalysisService: ColorAnalysisService,
-    private cadAnalysisService: CadAnalysisService
+    private cadAnalysisService: CadAnalysisService,
+    private requirementMappingService: RequirementMappingService
   ) {}
   ) {}
 
 
   ngOnInit() {
   ngOnInit() {
@@ -384,6 +427,16 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
     if (this.autoSaveTimer) {
     if (this.autoSaveTimer) {
       clearTimeout(this.autoSaveTimer);
       clearTimeout(this.autoSaveTimer);
     }
     }
+    
+    // 清理订阅
+    this.subscriptions.forEach(sub => sub.unsubscribe());
+    
+    // 清理对象URL
+    this.uploadedFiles.forEach(file => {
+      if (file.url.startsWith('blob:')) {
+        URL.revokeObjectURL(file.url);
+      }
+    });
   }
   }
 
 
   private initializeForms() {
   private initializeForms() {
@@ -524,16 +577,41 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
 
 
       // 显示上传成功弹窗
       // 显示上传成功弹窗
       this.showUploadSuccessModal = true;
       this.showUploadSuccessModal = true;
-      this.uploadedFiles = [{
-        id: materialFile.id,
-        name: file.name,
-        url: materialFile.url || '',
-        size: file.size,
-        type: type === 'text' ? 'text' : type === 'image' ? 'image' : 'cad'
-      }];
+      
+      // 如果是图片类型,添加到需求映射界面的uploadedFiles数组(支持多图片)
+      if (type === 'image') {
+        // 检查是否已存在
+        const existingIndex = this.uploadedFiles.findIndex(f => f.id === materialFile.id);
+        if (existingIndex === -1) {
+          const uploadedFile = {
+            id: materialFile.id,
+            name: file.name,
+            url: materialFile.url || '',
+            size: file.size,
+            type: 'image' as const,
+            preview: materialFile.url || ''
+          };
+          this.uploadedFiles.push(uploadedFile);
+          
+          console.log('✅ 图片已同步到需求映射,当前共', this.uploadedFiles.length, '张');
+          
+          // 🔥 新增:立即触发需求映射界面的实时分析
+          this.triggerRealtimeAnalysis(uploadedFile);
+        }
+      } else {
+        // 非图片类型,保持原有逻辑
+        this.uploadedFiles = [{
+          id: materialFile.id,
+          name: file.name,
+          url: materialFile.url || '',
+          size: file.size,
+          type: type === 'text' ? 'text' : type === 'cad' ? 'cad' : 'image'
+        }];
+      }
+      
       this.uploadType = type === 'text' ? 'document' : type === 'image' ? 'image' : 'mixed';
       this.uploadType = type === 'text' ? 'document' : type === 'image' ? 'image' : 'mixed';
       
       
-      // 自动解析
+      // 自动解析(素材分析用)
       this.analyzeMaterial(materialFile);
       this.analyzeMaterial(materialFile);
     }, 1000);
     }, 1000);
   }
   }
@@ -1843,4 +1921,502 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
       console.error('预览CAD失败:', e);
       console.error('预览CAD失败:', e);
     }
     }
   }
   }
+
+  // 需求映射测试相关方法
+  onFileSelectedForMapping(event: Event): void {
+    const input = event.target as HTMLInputElement;
+    if (!input.files || input.files.length === 0) return;
+
+    this.updateStepStatus('upload', 'in-progress');
+    this.isUploading = true;
+
+    try {
+      const files = Array.from(input.files);
+      this.uploadedFiles = files.map(file => ({
+        id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
+        name: file.name,
+        url: URL.createObjectURL(file),
+        size: file.size,
+        type: 'image' as const,
+        preview: URL.createObjectURL(file)
+      }));
+
+      // 模拟上传延迟
+      setTimeout(() => {
+        this.isUploading = false;
+        this.updateStepStatus('upload', 'completed');
+        this.showUploadModal = true;
+        
+        // 自动开始分析
+        this.startAnalysis();
+      }, 1000);
+
+    } catch (error) {
+      console.error('文件上传失败:', error);
+      this.isUploading = false;
+      this.updateStepStatus('upload', 'error');
+    }
+  }
+
+  // 开始分析 - 支持多图片分析
+  startAnalysis(): void {
+    if (this.uploadedFiles.length === 0) return;
+
+    this.updateStepStatus('analysis', 'in-progress');
+    this.isAnalyzing = true;
+    this.analysisError = null;
+
+    // 过滤出所有图片文件
+    const imageFiles = this.uploadedFiles.filter(f => f.type === 'image');
+    
+    if (imageFiles.length === 0) {
+      this.analysisError = '未找到有效的图片文件';
+      this.isAnalyzing = false;
+      this.updateStepStatus('analysis', 'error');
+      return;
+    }
+
+    try {
+      // 如果只有一张图片,使用单图分析
+      if (imageFiles.length === 1) {
+        const analysisSubscription = this.colorAnalysisService.analyzeImage(imageFiles[0]).subscribe({
+          next: (result: ColorAnalysisResult) => {
+            if (result) {
+              this.analysisResult = result;
+              this.isAnalyzing = false;
+              this.updateStepStatus('analysis', 'completed');
+              
+              // 自动开始需求映射
+              this.startRequirementMapping();
+            } else {
+              this.analysisError = '分析结果为空';
+              this.isAnalyzing = false;
+              this.updateStepStatus('analysis', 'error');
+            }
+          },
+          error: (error: any) => {
+            console.error('分析失败:', error);
+            this.analysisError = '图片分析失败,请重试';
+            this.isAnalyzing = false;
+            this.updateStepStatus('analysis', 'error');
+          }
+        });
+        this.subscriptions.push(analysisSubscription);
+      } else {
+        // 多图片分析:分析所有图片并合并结果
+        this.analyzeBatchImages(imageFiles);
+      }
+    } catch (error) {
+      console.error('启动分析失败:', error);
+      this.analysisError = '启动分析失败,请重试';
+      this.isAnalyzing = false;
+      this.updateStepStatus('analysis', 'error');
+    }
+  }
+
+  // 批量图片分析
+  private analyzeBatchImages(imageFiles: any[]): void {
+    console.log(`开始批量分析 ${imageFiles.length} 张图片...`);
+    
+    let completedCount = 0;
+    const allResults: ColorAnalysisResult[] = [];
+    
+    imageFiles.forEach((file, index) => {
+      const analysisSubscription = this.colorAnalysisService.analyzeImage(file).subscribe({
+        next: (result: ColorAnalysisResult) => {
+          allResults.push(result);
+          completedCount++;
+          
+          console.log(`图片 ${index + 1}/${imageFiles.length} 分析完成`);
+          
+          // 所有图片分析完成后,合并结果
+          if (completedCount === imageFiles.length) {
+            this.analysisResult = this.mergeAnalysisResults(allResults);
+            this.isAnalyzing = false;
+            this.updateStepStatus('analysis', 'completed');
+            
+            console.log('批量分析完成,合并结果:', this.analysisResult);
+            
+            // 自动开始需求映射
+            this.startRequirementMapping();
+          }
+        },
+        error: (error: any) => {
+          console.error(`图片 ${index + 1} 分析失败:`, error);
+          completedCount++;
+          
+          // 即使部分失败,也尝试合并已成功的结果
+          if (completedCount === imageFiles.length) {
+            if (allResults.length > 0) {
+              this.analysisResult = this.mergeAnalysisResults(allResults);
+              this.isAnalyzing = false;
+              this.updateStepStatus('analysis', 'completed');
+              this.startRequirementMapping();
+            } else {
+              this.analysisError = '所有图片分析失败,请重试';
+              this.isAnalyzing = false;
+              this.updateStepStatus('analysis', 'error');
+            }
+          }
+        }
+      });
+      
+      this.subscriptions.push(analysisSubscription);
+    });
+  }
+
+  // 合并多个分析结果
+  private mergeAnalysisResults(results: ColorAnalysisResult[]): ColorAnalysisResult {
+    if (results.length === 0) {
+      throw new Error('没有有效的分析结果');
+    }
+    
+    if (results.length === 1) {
+      return results[0];
+    }
+    
+    console.log('合并多个分析结果:', results.length);
+    
+    // 合并颜色信息
+    const allColors: any[] = [];
+    results.forEach(result => {
+      result.colors.forEach(color => {
+        const existing = allColors.find(c => c.hex === color.hex);
+        if (existing) {
+          existing.percentage += color.percentage / results.length;
+        } else {
+          allColors.push({
+            ...color,
+            percentage: color.percentage / results.length
+          });
+        }
+      });
+    });
+    
+    // 按百分比排序并取前7种颜色
+    allColors.sort((a, b) => b.percentage - a.percentage);
+    const mergedColors = allColors.slice(0, 7);
+    
+    // 重新计算百分比使总和为100%
+    const totalPercentage = mergedColors.reduce((sum, c) => sum + c.percentage, 0);
+    mergedColors.forEach(c => {
+      c.percentage = (c.percentage / totalPercentage) * 100;
+    });
+    
+    // 合并增强分析数据(取平均值或第一个)
+    const baseResult = results[0];
+    
+    return {
+      colors: mergedColors,
+      originalImage: results[0].originalImage,
+      mosaicImage: results[0].mosaicImage,
+      reportPath: results[0].reportPath,
+      enhancedAnalysis: baseResult.enhancedAnalysis,
+      formAnalysis: baseResult.formAnalysis,
+      textureAnalysis: baseResult.textureAnalysis,
+      patternAnalysis: baseResult.patternAnalysis,
+      lightingAnalysis: baseResult.lightingAnalysis
+    };
+  }
+
+  // 开始需求映射
+  startRequirementMapping(): void {
+    if (!this.analysisResult) {
+      console.warn('分析结果为空,无法开始需求映射');
+      return;
+    }
+
+    this.updateStepStatus('mapping', 'in-progress');
+    this.isGeneratingMapping = true;
+    this.mappingError = null;
+
+    try {
+      const mappingSubscription = this.requirementMappingService.generateRequirementMapping(
+        this.analysisResult,
+        SceneTemplate.LIVING_ROOM_MODERN
+      ).subscribe({
+        next: (mapping) => {
+          if (mapping) {
+            this.requirementMapping = mapping;
+            this.isGeneratingMapping = false;
+            this.updateStepStatus('mapping', 'completed');
+            this.updateStepStatus('preview', 'completed');
+            
+            console.log('=== 需求映射测试完成 ===');
+            console.log('映射结果:', this.requirementMapping);
+            
+            // 发送映射数据更新到父组件
+            this.emitMappingDataUpdate();
+          } else {
+            this.mappingError = '需求映射结果为空';
+            this.isGeneratingMapping = false;
+            this.updateStepStatus('mapping', 'error');
+          }
+        },
+        error: (error) => {
+          console.error('需求映射生成失败:', error);
+          this.mappingError = '需求映射生成失败,请重试';
+          this.isGeneratingMapping = false;
+          this.updateStepStatus('mapping', 'error');
+        }
+      });
+
+      this.subscriptions.push(mappingSubscription);
+    } catch (error) {
+      console.error('启动需求映射失败:', error);
+      this.mappingError = '启动需求映射失败,请重试';
+      this.isGeneratingMapping = false;
+      this.updateStepStatus('mapping', 'error');
+    }
+  }
+
+  // 更新步骤状态
+  private updateStepStatus(stepId: string, status: 'pending' | 'in-progress' | 'completed' | 'error'): void {
+    const step = this.testSteps.find(s => s.id === stepId);
+    if (step) {
+      step.status = status;
+    } else {
+      console.warn(`未找到步骤: ${stepId}`);
+    }
+  }
+
+  // 辅助方法:获取步骤图标
+  getStepIcon(status: string): string {
+    switch (status) {
+      case 'completed': return '✅';
+      case 'in-progress': return '⏳';
+      case 'error': return '❌';
+      default: return '⭕';
+    }
+  }
+
+  // 获取步骤状态类名
+  getStepClass(status: string): string {
+    return `step-${status}`;
+  }
+
+  // 手动重试分析
+  retryAnalysis(): void {
+    this.analysisError = null;
+    this.startAnalysis();
+  }
+
+  // 手动重试映射
+  retryMapping(): void {
+    this.mappingError = null;
+    this.startRequirementMapping();
+  }
+
+  // 下载测试结果
+  downloadTestResult(): void {
+    if (!this.requirementMapping) return;
+
+    const testResult = {
+      timestamp: new Date().toISOString(),
+      uploadedFiles: this.uploadedFiles.map(f => ({
+        name: f.name,
+        size: f.size,
+        type: f.type
+      })),
+      analysisResult: this.analysisResult,
+      requirementMapping: this.requirementMapping,
+      testSteps: this.testSteps
+    };
+
+    const blob = new Blob([JSON.stringify(testResult, null, 2)], { type: 'application/json' });
+    const url = URL.createObjectURL(blob);
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = `requirement-mapping-test-${Date.now()}.json`;
+    link.click();
+    URL.revokeObjectURL(url);
+  }
+
+  // 辅助方法:获取材质名称的中文翻译
+  getMaterialName(category: string | undefined): string {
+    if (!category) return '未识别';
+    const nameMap: { [key: string]: string } = {
+      'wood': '木材',
+      'metal': '金属',
+      'fabric': '织物',
+      'leather': '皮革',
+      'plastic': '塑料',
+      'glass': '玻璃',
+      'ceramic': '陶瓷',
+      'stone': '石材',
+      'composite': '复合材料'
+    };
+    return nameMap[category] || category;
+  }
+
+  // 获取灯光情绪名称的中文翻译
+  getLightingMoodName(mood: string | undefined): string {
+    if (!mood) return '未识别';
+    const nameMap: { [key: string]: string } = {
+      'dramatic': '戏剧性',
+      'romantic': '浪漫',
+      'energetic': '活力',
+      'calm': '平静',
+      'mysterious': '神秘',
+      'cheerful': '愉悦',
+      'professional': '专业'
+    };
+    return nameMap[mood] || mood;
+  }
+
+  // 获取复杂度名称的中文翻译
+  getComplexityName(level: string): string {
+    const nameMap: { [key: string]: string } = {
+      'simple': '简单',
+      'moderate': '中等',
+      'complex': '复杂',
+      'very-complex': '非常复杂'
+    };
+    return nameMap[level] || level;
+  }
+
+  // 获取色彩和谐类型的中文翻译
+  getColorHarmonyName(harmony: string): string {
+    const nameMap: { [key: string]: string } = {
+      'monochromatic': '单色调和',
+      'analogous': '类似色调和',
+      'complementary': '互补色调和',
+      'triadic': '三角色调和',
+      'tetradic': '四角色调和',
+      'split-complementary': '分裂互补色调和'
+    };
+    return nameMap[harmony] || harmony;
+  }
+
+  // 获取空间布局类型的中文翻译
+  getLayoutTypeName(type: string): string {
+    const nameMap: { [key: string]: string } = {
+      'open': '开放式',
+      'enclosed': '封闭式',
+      'semi-open': '半开放式',
+      'multi-level': '多层次'
+    };
+    return nameMap[type] || type;
+  }
+
+  // 获取空间流线类型的中文翻译
+  getFlowTypeName(flow: string): string {
+    const nameMap: { [key: string]: string } = {
+      'linear': '线性流线',
+      'circular': '环形流线',
+      'grid': '网格流线',
+      'organic': '有机流线'
+    };
+    return nameMap[flow] || flow;
+  }
+
+  // 移除需求映射界面中已上传的文件
+  removeUploadedFile(fileId: string): void {
+    const index = this.uploadedFiles.findIndex(f => f.id === fileId);
+    if (index !== -1) {
+      const file = this.uploadedFiles[index];
+      // 清理对象URL
+      if (file.url && file.url.startsWith('blob:')) {
+        URL.revokeObjectURL(file.url);
+      }
+      if (file.preview && file.preview.startsWith('blob:')) {
+        URL.revokeObjectURL(file.preview);
+      }
+      
+      this.uploadedFiles.splice(index, 1);
+      
+      // 如果没有文件了,重置分析状态
+      if (this.uploadedFiles.length === 0) {
+        this.analysisResult = undefined;
+        this.requirementMapping = null;
+        this.updateStepStatus('upload', 'pending');
+        this.updateStepStatus('analysis', 'pending');
+        this.updateStepStatus('mapping', 'pending');
+        this.updateStepStatus('preview', 'pending');
+      } else {
+        // 还有其他图片,重新分析剩余图片
+        this.restartAnalysisWithRemainingImages();
+      }
+    }
+  }
+
+  // 实时触发分析(每上传一张图片就分析一张)
+  private triggerRealtimeAnalysis(newFile: any): void {
+    console.log('🔄 触发实时分析:', newFile.name);
+    
+    // 第一张图片:初始化分析状态
+    if (this.uploadedFiles.length === 1) {
+      this.updateStepStatus('upload', 'completed');
+      this.updateStepStatus('analysis', 'in-progress');
+      this.isAnalyzing = true;
+    }
+    
+    // 分析新上传的图片
+    this.colorAnalysisService.analyzeImage(newFile).subscribe({
+      next: (newResult: ColorAnalysisResult) => {
+        console.log('📊 新图片分析完成:', newFile.name);
+        
+        // 如果是第一张图片,直接设置结果
+        if (!this.analysisResult) {
+          this.analysisResult = newResult;
+          console.log('✅ 设置第一张图片的分析结果');
+        } else {
+          // 如果已有结果,合并新结果
+          this.analysisResult = this.mergeAnalysisResults([this.analysisResult, newResult]);
+          console.log('🔗 合并新图片的分析结果,当前共', this.uploadedFiles.length, '张');
+        }
+        
+        // 检查是否所有图片都已分析完成
+        // 这里简化处理:每次上传都会触发分析,所以当前图片数量 = 已分析数量
+        this.isAnalyzing = false;
+        this.updateStepStatus('analysis', 'completed');
+        
+        // 🔥 自动触发需求映射生成
+        this.startRequirementMapping();
+        
+        // 发送映射数据更新事件给父组件
+        this.emitMappingDataUpdate();
+        
+        this.cdr.detectChanges(); // 触发界面更新
+      },
+      error: (error: any) => {
+        console.error('实时分析失败:', error);
+        // 不设置为error状态,因为可能还有其他图片在分析
+        this.isAnalyzing = false;
+      }
+    });
+  }
+
+  // 重新分析剩余图片
+  private restartAnalysisWithRemainingImages(): void {
+    console.log('🔄 重新分析剩余图片,共', this.uploadedFiles.length, '张');
+    
+    this.updateStepStatus('analysis', 'in-progress');
+    this.isAnalyzing = true;
+    this.analysisResult = undefined;
+    
+    // 重新分析所有剩余图片
+    const imageFiles = this.uploadedFiles.filter(f => f.type === 'image');
+    
+    if (imageFiles.length === 0) {
+      this.isAnalyzing = false;
+      this.updateStepStatus('analysis', 'pending');
+      return;
+    }
+    
+    // 使用批量分析
+    this.analyzeBatchImages(imageFiles);
+  }
+
+  // 发送映射数据更新到父组件
+  private emitMappingDataUpdate(): void {
+    this.mappingDataUpdated.emit({
+      uploadedFiles: this.uploadedFiles,
+      analysisResult: this.analysisResult,
+      requirementMapping: this.requirementMapping,
+      testSteps: this.testSteps,
+      isAnalyzing: this.isAnalyzing,
+      isGeneratingMapping: this.isGeneratingMapping
+    });
+  }
 }
 }

+ 135 - 33
src/app/shared/components/upload-success-modal/upload-success-modal.component.html

@@ -160,6 +160,51 @@
                         <div class="color-analysis-tab">
                         <div class="color-analysis-tab">
                           <div class="analysis-section">
                           <div class="analysis-section">
                             <h6>色轮分析</h6>
                             <h6>色轮分析</h6>
+                            
+                            <!-- 色轮可视化 -->
+                            <div class="color-wheel-visualization">
+                              <svg width="200" height="200" viewBox="0 0 200 200" class="color-wheel-svg">
+                                <!-- 背景色轮 -->
+                                <defs>
+                                  <linearGradient id="colorWheelGradient" x1="0%" y1="0%" x2="100%" y2="0%">
+                                    <stop offset="0%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
+                                    <stop offset="16.67%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
+                                    <stop offset="33.33%" style="stop-color:rgb(0,255,0);stop-opacity:1" />
+                                    <stop offset="50%" style="stop-color:rgb(0,255,255);stop-opacity:1" />
+                                    <stop offset="66.67%" style="stop-color:rgb(0,0,255);stop-opacity:1" />
+                                    <stop offset="83.33%" style="stop-color:rgb(255,0,255);stop-opacity:1" />
+                                    <stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
+                                  </linearGradient>
+                                </defs>
+                                
+                                <!-- 色轮圆环 -->
+                                <circle cx="100" cy="100" r="80" fill="none" stroke="url(#colorWheelGradient)" stroke-width="30" opacity="0.3"/>
+                                
+                                <!-- 主色调指示器 -->
+                                <line 
+                                  x1="100" 
+                                  y1="100" 
+                                  [attr.x2]="100 + 70 * Math.cos((analysisResult.enhancedAnalysis.colorWheel.dominantHue - 90) * Math.PI / 180)"
+                                  [attr.y2]="100 + 70 * Math.sin((analysisResult.enhancedAnalysis.colorWheel.dominantHue - 90) * Math.PI / 180)"
+                                  stroke="#333" 
+                                  stroke-width="3"
+                                  stroke-linecap="round"/>
+                                
+                                <!-- 中心圆点 -->
+                                <circle cx="100" cy="100" r="5" fill="#333"/>
+                                
+                                <!-- 色彩分布点 -->
+                                @for (color of analysisResult.enhancedAnalysis.colorWheel.colorDistribution; track color.hue) {
+                                  <circle 
+                                    [attr.cx]="100 + (60 + color.saturation * 0.2) * Math.cos((color.hue - 90) * Math.PI / 180)"
+                                    [attr.cy]="100 + (60 + color.saturation * 0.2) * Math.sin((color.hue - 90) * Math.PI / 180)"
+                                    [attr.r]="3 + color.percentage * 0.1"
+                                    [attr.fill]="'hsl(' + color.hue + ',' + color.saturation + '%,' + color.brightness + '%)'"
+                                    opacity="0.8"/>
+                                }
+                              </svg>
+                            </div>
+                            
                             <div class="color-wheel-info">
                             <div class="color-wheel-info">
                               <div class="info-item">
                               <div class="info-item">
                                 <span class="label">主色调:</span>
                                 <span class="label">主色调:</span>
@@ -196,32 +241,42 @@
                             <h6>线条分析</h6>
                             <h6>线条分析</h6>
                             <div class="form-info">
                             <div class="form-info">
                               <div class="info-item">
                               <div class="info-item">
-                <span class="label">主导线条:</span>
-                <span class="value">{{ analysisResult.formAnalysis.lineAnalysis.dominantLineType || '无' }}</span>
-              </div>
+                                <span class="label">主导线条:</span>
+                                <span class="value">
+                                  @switch (analysisResult.formAnalysis.lineAnalysis.dominantLineType) {
+                                    @case ('straight') { 直线型 }
+                                    @case ('curved') { 曲线型 }
+                                    @case ('mixed') { 混合型 }
+                                    @default { 未知 }
+                                  }
+                                </span>
+                              </div>
                               <div class="info-item">
                               <div class="info-item">
                                 <span class="label">视觉流动:</span>
                                 <span class="label">视觉流动:</span>
-                                <span class="value">{{ analysisResult.formAnalysis.lineAnalysis.visualFlow || '无' }}</span>
+                                <span class="value">
+                                  连续性 {{ analysisResult.formAnalysis.lineAnalysis.lineQuality.continuity || 0 }}%, 
+                                  流畅度 {{ analysisResult.formAnalysis.lineAnalysis.lineQuality.smoothness || 0 }}%
+                                </span>
                               </div>
                               </div>
                             </div>
                             </div>
                           </div>
                           </div>
                           
                           
                           <div class="analysis-section">
                           <div class="analysis-section">
-                            <h6>整体评估</h6>
+                            <h6>几何形状</h6>
                             <div class="overall-info">
                             <div class="overall-info">
                               <div class="metric">
                               <div class="metric">
                                 <span class="metric-label">复杂度</span>
                                 <span class="metric-label">复杂度</span>
                                 <div class="metric-bar">
                                 <div class="metric-bar">
-                                  <div class="metric-fill" [style.width.%]="analysisResult.formAnalysis.overallAssessment.complexity"></div>
+                                  <div class="metric-fill" [style.width.%]="getComplexityScore(analysisResult.formAnalysis.geometryAnalysis.complexity)"></div>
                                 </div>
                                 </div>
-                                <span class="metric-value">{{ analysisResult.formAnalysis.overallAssessment.complexity || 0 }}%</span>
+                                <span class="metric-value">{{ analysisResult.formAnalysis.geometryAnalysis.complexity || '未知' }}</span>
                               </div>
                               </div>
                               <div class="metric">
                               <div class="metric">
-                                <span class="metric-label">视觉冲击</span>
+                                <span class="metric-label">视觉平衡</span>
                                 <div class="metric-bar">
                                 <div class="metric-bar">
-                                  <div class="metric-fill" [style.width.%]="analysisResult.formAnalysis.overallAssessment.visualImpact"></div>
+                                  <div class="metric-fill" [style.width.%]="analysisResult.formAnalysis.geometryAnalysis.balance.visual"></div>
                                 </div>
                                 </div>
-                                <span class="metric-value">{{ analysisResult.formAnalysis.overallAssessment.visualImpact || 0 }}%</span>
+                                <span class="metric-value">{{ analysisResult.formAnalysis.geometryAnalysis.balance.visual || 0 }}%</span>
                               </div>
                               </div>
                             </div>
                             </div>
                           </div>
                           </div>
@@ -249,11 +304,16 @@
                           <div class="analysis-section">
                           <div class="analysis-section">
                             <h6>材质分类</h6>
                             <h6>材质分类</h6>
                             <div class="material-tags">
                             <div class="material-tags">
-                              @for (material of analysisResult.textureAnalysis.materialClassification.primaryMaterials; track material; let i = $index) {
-                                <span class="material-tag primary">{{ material }}</span>
-                              }
-                              @for (material of analysisResult.textureAnalysis.materialClassification.secondaryMaterials; track material; let i = $index) {
-                                <span class="material-tag secondary">{{ material }}</span>
+                              <span class="material-tag primary">
+                                {{ getMaterialName(analysisResult.textureAnalysis.materialClassification.primaryMaterial.category) }}
+                                @if (analysisResult.textureAnalysis.materialClassification.primaryMaterial.subcategory) {
+                                  ({{ analysisResult.textureAnalysis.materialClassification.primaryMaterial.subcategory }})
+                                }
+                              </span>
+                              @for (material of analysisResult.textureAnalysis.materialClassification.secondaryMaterials; track material.category; let i = $index) {
+                                <span class="material-tag secondary">
+                                  {{ getMaterialName(material.category) }} ({{ material.percentage }}%)
+                                </span>
                               }
                               }
                             </div>
                             </div>
                           </div>
                           </div>
@@ -267,12 +327,12 @@
                             <h6>图案识别</h6>
                             <h6>图案识别</h6>
                             <div class="pattern-info">
                             <div class="pattern-info">
                               <div class="info-item">
                               <div class="info-item">
-                <span class="label">主要图案:</span>
-                <span class="value">{{ analysisResult.patternAnalysis.patternRecognition.primaryPatterns[0]?.type || '无' }}</span>
-              </div>
+                                <span class="label">主要图案:</span>
+                                <span class="value">{{ getPatternTypeName(analysisResult.patternAnalysis.patternRecognition.primaryPatterns[0]?.type) || '无' }}</span>
+                              </div>
                               <div class="info-item">
                               <div class="info-item">
                                 <span class="label">复杂度:</span>
                                 <span class="label">复杂度:</span>
-                                <span class="value">{{ analysisResult.patternAnalysis.patternRecognition.patternComplexity.level || '无' }}</span>
+                                <span class="value">{{ getComplexityName(analysisResult.patternAnalysis.patternRecognition.patternComplexity.level) || '无' }}</span>
                               </div>
                               </div>
                             </div>
                             </div>
                           </div>
                           </div>
@@ -281,8 +341,8 @@
                             <h6>视觉节奏</h6>
                             <h6>视觉节奏</h6>
                             <div class="rhythm-info">
                             <div class="rhythm-info">
                               <div class="rhythm-tags">
                               <div class="rhythm-tags">
-                                <span class="rhythm-tag">{{ analysisResult.patternAnalysis.visualRhythm.rhythmType.primary || '无' }}</span>
-                                <span class="rhythm-tag">{{ analysisResult.patternAnalysis.visualRhythm.movement.direction || '无' }}</span>
+                                <span class="rhythm-tag">{{ getRhythmTypeName(analysisResult.patternAnalysis.visualRhythm.rhythmType.primary) || '无' }}</span>
+                                <span class="rhythm-tag">{{ getDirectionName(analysisResult.patternAnalysis.visualRhythm.movement.direction) || '无' }}</span>
                               </div>
                               </div>
                             </div>
                             </div>
                           </div>
                           </div>
@@ -296,12 +356,23 @@
                             <h6>光源识别</h6>
                             <h6>光源识别</h6>
                             <div class="lighting-info">
                             <div class="lighting-info">
                               <div class="info-item">
                               <div class="info-item">
-                <span class="label">主要光源:</span>
-                <span class="value">{{ analysisResult.lightingAnalysis.lightSourceIdentification.primarySources?.join(', ') || '无' }}</span>
-              </div>
+                                <span class="label">主要光源:</span>
+                                <span class="value">
+                                  @if (analysisResult.lightingAnalysis.lightSourceIdentification.primarySources && analysisResult.lightingAnalysis.lightSourceIdentification.primarySources.length > 0) {
+                                    @for (source of analysisResult.lightingAnalysis.lightSourceIdentification.primarySources; track source.type; let last = $last) {
+                                      {{ getLightSourceName(source.type, source.subtype) }}@if (!last) {, }
+                                    }
+                                  } @else {
+                                    无
+                                  }
+                                </span>
+                              </div>
                               <div class="info-item">
                               <div class="info-item">
                                 <span class="label">灯光设置:</span>
                                 <span class="label">灯光设置:</span>
-                                <span class="value">{{ analysisResult.lightingAnalysis.lightSourceIdentification.lightingSetup || '无' }}</span>
+                                <span class="value">
+                                  {{ getLightingStyleName(analysisResult.lightingAnalysis.lightSourceIdentification.lightingSetup.lightingStyle) || '无' }}
+                                  ({{ analysisResult.lightingAnalysis.lightSourceIdentification.lightingSetup.sourceCount || 0 }} 个光源)
+                                </span>
                               </div>
                               </div>
                             </div>
                             </div>
                           </div>
                           </div>
@@ -311,11 +382,14 @@
                             <div class="ambient-info">
                             <div class="ambient-info">
                               <div class="info-item">
                               <div class="info-item">
                                 <span class="label">环境光:</span>
                                 <span class="label">环境光:</span>
-                                <span class="value">{{ analysisResult.lightingAnalysis.ambientAnalysis.ambientLight || '无' }}</span>
+                                <span class="value">
+                                  强度 {{ analysisResult.lightingAnalysis.ambientAnalysis.ambientLevel.strength || 0 }}%, 
+                                  均匀度 {{ analysisResult.lightingAnalysis.ambientAnalysis.ambientLevel.uniformity || 0 }}%
+                                </span>
                               </div>
                               </div>
                               <div class="info-item">
                               <div class="info-item">
                                 <span class="label">灯光情绪:</span>
                                 <span class="label">灯光情绪:</span>
-                                <span class="value">{{ analysisResult.lightingAnalysis.ambientAnalysis.lightingMood || '无' }}</span>
+                                <span class="value">{{ getLightingMoodName(analysisResult.lightingAnalysis.ambientAnalysis.lightingMood.primary) || '无' }}</span>
                               </div>
                               </div>
                             </div>
                             </div>
                           </div>
                           </div>
@@ -376,11 +450,22 @@
                                       <div class="color-mapping-item">
                                       <div class="color-mapping-item">
                                         <div class="color-swatch" [style.background-color]="mapping.originalColor"></div>
                                         <div class="color-swatch" [style.background-color]="mapping.originalColor"></div>
                                         <span class="mapping-arrow">→</span>
                                         <span class="mapping-arrow">→</span>
-                                        <span class="target-material">{{ mapping.mappedColor }}</span>
-                                        <span class="confidence">({{ mapping.weight }}%)</span>
+                                        <div class="color-swatch" [style.background-color]="mapping.mappedColor"></div>
+                                        <span class="usage-tag">{{ getColorUsageName(mapping.usage) }}</span>
+                                        <span class="confidence">(权重 {{ mapping.weight }}%)</span>
                                       </div>
                                       </div>
                                     }
                                     }
                                   </div>
                                   </div>
+                                  <div class="color-params-info">
+                                    <div class="param-item">
+                                      <span class="label">色彩和谐:</span>
+                                      <span class="value">{{ getColorHarmonyName(requirementMapping.parameterMapping.colorParams.colorHarmony) }}</span>
+                                    </div>
+                                    <div class="param-item">
+                                      <span class="label">色温:</span>
+                                      <span class="value">{{ getTemperatureName(requirementMapping.parameterMapping.colorParams.temperature) }}</span>
+                                    </div>
+                                  </div>
                                 </div>
                                 </div>
 
 
                                 <!-- 空间参数 -->
                                 <!-- 空间参数 -->
@@ -389,11 +474,24 @@
                                   <div class="space-info">
                                   <div class="space-info">
                                     <div class="info-item">
                                     <div class="info-item">
                                       <span class="label">空间类型:</span>
                                       <span class="label">空间类型:</span>
-                                      <span class="value">{{ requirementMapping.parameterMapping.spaceParams.layout?.type }}</span>
+                                      <span class="value">{{ getLayoutTypeName(requirementMapping.parameterMapping.spaceParams.layout.type) }}</span>
+                                    </div>
+                                    <div class="info-item">
+                                      <span class="label">空间流线:</span>
+                                      <span class="value">{{ getFlowTypeName(requirementMapping.parameterMapping.spaceParams.layout.flow) }}</span>
                                     </div>
                                     </div>
                                     <div class="info-item">
                                     <div class="info-item">
                                       <span class="label">开放程度:</span>
                                       <span class="label">开放程度:</span>
-                                      <span class="value">{{ requirementMapping.parameterMapping.spaceParams.scale?.openness }}%</span>
+                                      <span class="value">{{ requirementMapping.parameterMapping.spaceParams.scale.openness }}%</span>
+                                    </div>
+                                    <div class="info-item">
+                                      <span class="label">尺寸:</span>
+                                      <span class="value">
+                                        {{ requirementMapping.parameterMapping.spaceParams.dimensions.width }} × 
+                                        {{ requirementMapping.parameterMapping.spaceParams.dimensions.height }} × 
+                                        {{ requirementMapping.parameterMapping.spaceParams.dimensions.depth }} 
+                                        {{ getDimensionUnitName(requirementMapping.parameterMapping.spaceParams.dimensions.unit) }}
+                                      </span>
                                     </div>
                                     </div>
                                   </div>
                                   </div>
                                 </div>
                                 </div>
@@ -404,10 +502,14 @@
                                   <div class="material-mappings">
                                   <div class="material-mappings">
                                     @for (mapping of requirementMapping.parameterMapping.materialParams.surfaceMaterials; track mapping.category) {
                                     @for (mapping of requirementMapping.parameterMapping.materialParams.surfaceMaterials; track mapping.category) {
                                       <div class="material-mapping-item">
                                       <div class="material-mapping-item">
-                                        <span class="source-texture">{{ mapping.category }}</span>
+                                        <span class="source-texture">{{ getMaterialName(mapping.category) }}</span>
                                         <span class="mapping-arrow">→</span>
                                         <span class="mapping-arrow">→</span>
                                         <span class="target-material">{{ mapping.subtype }}</span>
                                         <span class="target-material">{{ mapping.subtype }}</span>
-                                        <span class="properties">{{ mapping.finish }}, {{ mapping.priority }}</span>
+                                        <span class="properties">
+                                          {{ getFinishName(mapping.finish) }}, 
+                                          {{ getPriorityName(mapping.priority) }}
+                                        </span>
+                                        <span class="coverage">({{ mapping.coverage }}%)</span>
                                       </div>
                                       </div>
                                     }
                                     }
                                   </div>
                                   </div>

+ 15 - 0
src/app/shared/components/upload-success-modal/upload-success-modal.component.scss

@@ -126,6 +126,21 @@ $animation-easing: cubic-bezier(0.25, 0.8, 0.25, 1);
 
 
     // 色彩分析标签页样式
     // 色彩分析标签页样式
     .color-analysis-tab {
     .color-analysis-tab {
+      // 色轮可视化样式
+      .color-wheel-visualization {
+        display: flex;
+        justify-content: center;
+        margin: 20px 0;
+        
+        .color-wheel-svg {
+          filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.1));
+          
+          circle, line {
+            transition: all 0.3s ease;
+          }
+        }
+      }
+      
       .mood-atmosphere {
       .mood-atmosphere {
         display: flex;
         display: flex;
         gap: 12px;
         gap: 12px;

+ 218 - 1
src/app/shared/components/upload-success-modal/upload-success-modal.component.ts

@@ -69,6 +69,9 @@ export class UploadSuccessModalComponent implements OnInit, OnDestroy {
   animationState = 'idle';
   animationState = 'idle';
   buttonHoverState = 'normal';
   buttonHoverState = 'normal';
   copySuccess = false; // 复制成功状态
   copySuccess = false; // 复制成功状态
+  
+  // 暴露Math对象给模板使用
+  Math = Math;
 
 
   private progressSubscription?: Subscription;
   private progressSubscription?: Subscription;
   private resizeSubscription?: Subscription;
   private resizeSubscription?: Subscription;
@@ -1179,7 +1182,221 @@ interface RequirementMapping {
       { name: '粗糙度', value: properties.roughness?.value || 0 },
       { name: '粗糙度', value: properties.roughness?.value || 0 },
       { name: '光泽度', value: properties.glossiness?.value || 0 },
       { name: '光泽度', value: properties.glossiness?.value || 0 },
       { name: '透明度', value: properties.transparency?.value || 0 },
       { name: '透明度', value: properties.transparency?.value || 0 },
-      { name: '反射率', value: properties.reflectivity?.value || 0 }
+      { name: '反射率', value: properties.glossiness?.reflectivity || 0 }
     ];
     ];
   }
   }
+
+  // 获取复杂度分数(用于进度条显示)
+  getComplexityScore(complexity: string): number {
+    const scoreMap: { [key: string]: number } = {
+      'simple': 33,
+      'moderate': 66,
+      'complex': 90,
+      'very-complex': 100
+    };
+    return scoreMap[complexity] || 0;
+  }
+
+  // 获取材质名称的中文翻译
+  getMaterialName(category: string): string {
+    const nameMap: { [key: string]: string } = {
+      'wood': '木材',
+      'metal': '金属',
+      'fabric': '织物',
+      'leather': '皮革',
+      'plastic': '塑料',
+      'glass': '玻璃',
+      'ceramic': '陶瓷',
+      'stone': '石材',
+      'composite': '复合材料'
+    };
+    return nameMap[category] || category;
+  }
+
+  // 获取图案类型名称的中文翻译
+  getPatternTypeName(type: string): string {
+    const nameMap: { [key: string]: string } = {
+      'geometric': '几何图案',
+      'organic': '有机图案',
+      'abstract': '抽象图案',
+      'floral': '花卉图案',
+      'striped': '条纹图案',
+      'checkered': '格子图案',
+      'dotted': '圆点图案',
+      'textured': '纹理图案'
+    };
+    return nameMap[type] || type;
+  }
+
+  // 获取复杂度名称的中文翻译
+  getComplexityName(level: string): string {
+    const nameMap: { [key: string]: string } = {
+      'simple': '简单',
+      'moderate': '中等',
+      'complex': '复杂',
+      'very-complex': '非常复杂'
+    };
+    return nameMap[level] || level;
+  }
+
+  // 获取节奏类型名称的中文翻译
+  getRhythmTypeName(type: string): string {
+    const nameMap: { [key: string]: string } = {
+      'regular': '规律节奏',
+      'alternating': '交替节奏',
+      'flowing': '流动节奏',
+      'progressive': '渐进节奏',
+      'random': '随机节奏'
+    };
+    return nameMap[type] || type;
+  }
+
+  // 获取方向名称的中文翻译
+  getDirectionName(direction: string): string {
+    const nameMap: { [key: string]: string } = {
+      'horizontal': '水平方向',
+      'vertical': '垂直方向',
+      'diagonal': '对角方向',
+      'radial': '径向',
+      'multi-directional': '多方向'
+    };
+    return nameMap[direction] || direction;
+  }
+
+  // 获取光源名称的中文翻译
+  getLightSourceName(type: string, subtype: string): string {
+    const typeMap: { [key: string]: string } = {
+      'natural': '自然光',
+      'artificial': '人工光',
+      'mixed': '混合光'
+    };
+    
+    const subtypeMap: { [key: string]: string } = {
+      'sunlight': '日光',
+      'LED': 'LED灯',
+      'fluorescent': '荧光灯',
+      'incandescent': '白炽灯',
+      'candle': '烛光'
+    };
+    
+    const typeName = typeMap[type] || type;
+    const subtypeName = subtypeMap[subtype] || subtype;
+    
+    return subtype ? `${typeName}(${subtypeName})` : typeName;
+  }
+
+  // 获取灯光风格名称的中文翻译
+  getLightingStyleName(style: string): string {
+    const nameMap: { [key: string]: string } = {
+      'dramatic': '戏剧性',
+      'soft': '柔和',
+      'even': '均匀',
+      'accent': '重点照明',
+      'task': '任务照明',
+      'ambient': '环境照明'
+    };
+    return nameMap[style] || style;
+  }
+
+  // 获取灯光情绪名称的中文翻译
+  getLightingMoodName(mood: string): string {
+    const nameMap: { [key: string]: string } = {
+      'dramatic': '戏剧性',
+      'romantic': '浪漫',
+      'energetic': '活力',
+      'calm': '平静',
+      'mysterious': '神秘',
+      'cheerful': '愉悦',
+      'professional': '专业'
+    };
+    return nameMap[mood] || mood;
+  }
+
+  // 获取颜色用途名称的中文翻译
+  getColorUsageName(usage: string): string {
+    const nameMap: { [key: string]: string } = {
+      'primary': '主色',
+      'secondary': '辅色',
+      'accent': '点缀色',
+      'background': '背景色'
+    };
+    return nameMap[usage] || usage;
+  }
+
+  // 获取色彩和谐类型的中文翻译
+  getColorHarmonyName(harmony: string): string {
+    const nameMap: { [key: string]: string } = {
+      'monochromatic': '单色调和',
+      'analogous': '类似色调和',
+      'complementary': '互补色调和',
+      'triadic': '三角色调和',
+      'tetradic': '四角色调和',
+      'split-complementary': '分裂互补色调和'
+    };
+    return nameMap[harmony] || harmony;
+  }
+
+  // 获取色温名称的中文翻译
+  getTemperatureName(temp: string): string {
+    const nameMap: { [key: string]: string } = {
+      'warm': '暖色调',
+      'neutral': '中性色调',
+      'cool': '冷色调'
+    };
+    return nameMap[temp] || temp;
+  }
+
+  // 获取空间布局类型的中文翻译
+  getLayoutTypeName(type: string): string {
+    const nameMap: { [key: string]: string } = {
+      'open': '开放式',
+      'enclosed': '封闭式',
+      'semi-open': '半开放式',
+      'multi-level': '多层次'
+    };
+    return nameMap[type] || type;
+  }
+
+  // 获取空间流线类型的中文翻译
+  getFlowTypeName(flow: string): string {
+    const nameMap: { [key: string]: string } = {
+      'linear': '线性流线',
+      'circular': '环形流线',
+      'grid': '网格流线',
+      'organic': '有机流线'
+    };
+    return nameMap[flow] || flow;
+  }
+
+  // 获取尺寸单位的中文翻译
+  getDimensionUnitName(unit: string): string {
+    const nameMap: { [key: string]: string } = {
+      'meter': '米',
+      'feet': '英尺'
+    };
+    return nameMap[unit] || unit;
+  }
+
+  // 获取表面处理名称的中文翻译
+  getFinishName(finish: string): string {
+    const nameMap: { [key: string]: string } = {
+      'matte': '哑光',
+      'satin': '缎面',
+      'gloss': '光面',
+      'textured': '纹理',
+      'brushed': '拉丝',
+      'polished': '抛光'
+    };
+    return nameMap[finish] || finish;
+  }
+
+  // 获取优先级名称的中文翻译
+  getPriorityName(priority: string): string {
+    const nameMap: { [key: string]: string } = {
+      'primary': '主要',
+      'secondary': '次要',
+      'accent': '点缀'
+    };
+    return nameMap[priority] || priority;
+  }
 }
 }

+ 46 - 26
src/app/shared/services/color-analysis.service.ts

@@ -167,34 +167,54 @@ export class ColorAnalysisService {
     
     
     // 创建一个File对象用于模拟分析
     // 创建一个File对象用于模拟分析
     return new Observable(observer => {
     return new Observable(observer => {
-      // 如果有预览URL,尝试获取文件
-      if (file.preview || file.url) {
-        fetch(file.preview || file.url)
-          .then(response => response.blob())
-          .then(blob => {
-            const mockFile = new File([blob], file.name, { type: file.type || 'image/jpeg' });
-            this.simulateAnalysis(mockFile).subscribe({
-              next: (result) => observer.next(result),
-              error: (error) => observer.error(error),
-              complete: () => observer.complete()
-            });
-          })
-          .catch(error => {
-            console.warn('无法获取文件内容,使用默认模拟数据:', error);
-            // 如果无法获取文件,直接返回模拟结果
-            this.getDefaultMockResult(file).subscribe({
-              next: (result) => observer.next(result),
-              error: (error) => observer.error(error),
-              complete: () => observer.complete()
+      try {
+        // 如果有预览URL,尝试获取文件
+        if (file.preview || file.url) {
+          fetch(file.preview || file.url)
+            .then(response => response.blob())
+            .then(blob => {
+              const mockFile = new File([blob], file.name, { type: file.type || 'image/jpeg' });
+              this.simulateAnalysis(mockFile).subscribe({
+                next: (result) => {
+                  observer.next(result);
+                  observer.complete();
+                },
+                error: (error) => {
+                  console.error('模拟分析失败:', error);
+                  observer.error(error);
+                }
+              });
+            })
+            .catch(error => {
+              console.warn('无法获取文件内容,使用默认模拟数据:', error);
+              // 如果无法获取文件,直接返回模拟结果
+              this.getDefaultMockResult(file).subscribe({
+                next: (result) => {
+                  observer.next(result);
+                  observer.complete();
+                },
+                error: (error) => {
+                  console.error('获取默认模拟数据失败:', error);
+                  observer.error(error);
+                }
+              });
             });
             });
+        } else {
+          // 没有文件URL,直接返回模拟结果
+          this.getDefaultMockResult(file).subscribe({
+            next: (result) => {
+              observer.next(result);
+              observer.complete();
+            },
+            error: (error) => {
+              console.error('获取模拟结果失败:', error);
+              observer.error(error);
+            }
           });
           });
-      } else {
-        // 没有文件URL,直接返回模拟结果
-        this.getDefaultMockResult(file).subscribe({
-          next: (result) => observer.next(result),
-          error: (error) => observer.error(error),
-          complete: () => observer.complete()
-        });
+        }
+      } catch (error) {
+        console.error('analyzeImage方法异常:', error);
+        observer.error(error);
       }
       }
     });
     });
   }
   }