Bläddra i källkod

refactor(requirements-confirm-card): 移除需求映射测试组件及相关文件

- 删除需求映射测试组件及其模板、样式和逻辑文件,简化代码结构。
- 更新路由配置,移除与需求映射相关的路径。
- 此次清理旨在提升项目的可维护性和代码整洁性。
0235711 1 månad sedan
förälder
incheckning
8561f6ad50

+ 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);
+  }
+}

+ 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),
     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' },

+ 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 {
             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 {
               position: relative;
@@ -735,8 +736,9 @@
 
               img {
                 width: 100%;
-                height: 80px;
+                height: 100%;
                 object-fit: cover;
+                aspect-ratio: 1;
               }
 
               .image-overlay {
@@ -806,8 +808,9 @@
         .readonly-images {
           .uploaded-images-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 {
               position: relative;
@@ -817,8 +820,9 @@
 
               img {
                 width: 100%;
-                height: 80px;
+                height: 100%;
                 object-fit: cover;
+                aspect-ratio: 1;
               }
 
               .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 {

+ 69 - 41
src/app/pages/designer/project-detail/project-detail.html

@@ -1833,14 +1833,14 @@
                                   @if (canEditSection('delivery') && getSpaceImages('modeling', space.id).length > 0) {
                                     <div class="confirm-upload-section">
                                       <button class="confirm-upload-btn" 
-                                              (click)="confirmStageUpload('modeling')"
+                                              (click)="$event.stopPropagation(); confirmStageUpload('modeling')"
                                               [disabled]="!canConfirmStageUpload('modeling')">
                                         <span>确认上传</span>
                                       </button>
                                     </div>
                                   }
                                   <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)">
                                         <img [src]="img.url" [alt]="img.name" />
                                         <div class="image-overlay">
@@ -1848,22 +1848,29 @@
                                           <div class="image-actions">
                                             <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
                                               <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>
                                             </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>
                                     }
+                                    <!-- 添加更多图片按钮 -->
+                                    @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>
@@ -1989,14 +1996,14 @@
                                   @if (canEditSection('delivery') && getSpaceImages('softDecor', space.id).length > 0) {
                                     <div class="confirm-upload-section">
                                       <button class="confirm-upload-btn" 
-                                              (click)="confirmStageUpload('softDecor')"
+                                              (click)="$event.stopPropagation(); confirmStageUpload('softDecor')"
                                               [disabled]="!canConfirmStageUpload('softDecor')">
                                         <span>确认上传</span>
                                       </button>
                                     </div>
                                   }
                                   <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)">
                                         <img [src]="img.url" [alt]="img.name" />
                                         <div class="image-overlay">
@@ -2004,22 +2011,29 @@
                                           <div class="image-actions">
                                             <button class="preview-btn" (click)="$event.stopPropagation(); previewImage(img)">
                                               <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>
                                             </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>
                                     }
+                                    <!-- 添加更多图片按钮 -->
+                                    @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>
@@ -2145,7 +2159,7 @@
                                               @if (canEditSection('delivery') && getSpaceImages('rendering', space.id).length > 0) {
                                                 <div class="confirm-upload-section">
                                                   <button class="confirm-upload-btn" 
-                                                          (click)="confirmStageUpload('rendering')"
+                                                          (click)="$event.stopPropagation(); confirmStageUpload('rendering')"
                                                           [disabled]="!canConfirmStageUpload('rendering')">
                                                     <span>确认上传</span>
                                                   </button>
@@ -2163,20 +2177,27 @@
                                                             <circle cx="11" cy="11" r="8"></circle>
                                                             <path d="m21 21-4.35-4.35"></path>
                                                           </svg>
-                                      </button>
+                                                        </button>
                                                         @if (canEditSection('delivery')) {
                                                           <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">
                                                               <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>
+                                                          </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>
                                         } @else {
@@ -2301,7 +2322,7 @@
                                               @if (canEditSection('delivery') && getSpaceImages('postProduction', space.id).length > 0) {
                                                 <div class="confirm-upload-section">
                                                   <button class="confirm-upload-btn" 
-                                                          (click)="confirmStageUpload('postProduction')"
+                                                          (click)="$event.stopPropagation(); confirmStageUpload('postProduction')"
                                                           [disabled]="!canConfirmStageUpload('postProduction')">
                                                     <span>确认上传</span>
                                                   </button>
@@ -2319,20 +2340,27 @@
                                                             <circle cx="11" cy="11" r="8"></circle>
                                                             <path d="m21 21-4.35-4.35"></path>
                                                           </svg>
-              </button>
+                                                        </button>
                                                         @if (canEditSection('delivery')) {
                                                           <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">
                                                               <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>
+                                                          </button>
+                                                        }
+                                                      </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>
                                         } @else {

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

@@ -3198,17 +3198,18 @@
         }
       }
       
-      // 上传后的图片网格
+      // 上传后的图片网格 - 九宫格布局
       .uploaded-images-grid {
         display: grid;
-        grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
-        gap: 12px;
+        grid-template-columns: repeat(3, 1fr);
+        gap: 8px;
         width: 100%;
+        max-width: 300px;
         
         .uploaded-image-item {
           position: relative;
           aspect-ratio: 1;
-          border-radius: 8px;
+          border-radius: 6px;
           overflow: hidden;
           cursor: pointer;
           transition: all 0.3s ease;
@@ -3226,7 +3227,7 @@
             width: 100%;
             height: 100%;
             object-fit: cover;
-            border-radius: 8px;
+            border-radius: 6px;
           }
           
           .image-overlay {
@@ -3255,14 +3256,14 @@
             
             .image-actions {
               display: flex;
-              gap: 8px;
-              justify-content: center;
+              gap: 4px;
+              justify-content: flex-end;
               
               button {
-                width: 32px;
-                height: 32px;
+                width: 20px;
+                height: 20px;
                 border: none;
-                border-radius: 6px;
+                border-radius: 50%;
                 background: rgba(255, 255, 255, 0.9);
                 color: #333;
                 cursor: pointer;
@@ -3327,6 +3328,44 @@
             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;
+          }
+        }
       }
       
       // 隐藏的文件输入框

+ 231 - 273
src/app/shared/components/requirements-confirm-card/requirements-confirm-card.html

@@ -300,306 +300,264 @@
     <!-- 需求映射标签页 -->
     @if (activeTab === 'mapping') {
       <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="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="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>
-                <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 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="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 class="color-preview" [style.background-color]="getRgbString()"></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>
-              </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>
+            @if (isUploading) {
+              <div class="loading-overlay">
+                <div class="loading-spinner"></div>
+                <div class="loading-text">正在上传文件...</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>系统正在进行色彩、纹理、形态、图案和灯光分析</p>
               </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 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">{{ 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>
-          </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 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 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 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 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">{{ 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>
-          </div>
+          } @else {
+            <div class="mapping-placeholder">
+              <div class="placeholder-icon">🎯</div>
+              <div class="placeholder-text">
+                <h4>等待映射生成</h4>
+                <p>请先完成图片分析,系统将自动生成需求映射</p>
+              </div>
+            </div>
+          }
         </div>
       </div>
     }

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

@@ -1919,6 +1919,536 @@
   }
 }
 
+// 需求映射测试相关样式
+.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;
+  }
+}
+
+// 需求映射标签页固定高度和滚动
+.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 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; }

+ 244 - 1
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 { 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 {
@@ -219,6 +222,16 @@ interface RequirementItem {
   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,
@@ -358,11 +371,33 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
   // 全屏报告覆盖层
   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(
     private fb: FormBuilder,
     private cdr: ChangeDetectorRef,
     private colorAnalysisService: ColorAnalysisService,
-    private cadAnalysisService: CadAnalysisService
+    private cadAnalysisService: CadAnalysisService,
+    private requirementMappingService: RequirementMappingService
   ) {}
 
   ngOnInit() {
@@ -384,6 +419,16 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
     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() {
@@ -1843,4 +1888,202 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
       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 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);
+  }
 }

+ 101 - 27
src/app/shared/components/upload-success-modal/upload-success-modal.component.html

@@ -160,6 +160,51 @@
                         <div class="color-analysis-tab">
                           <div class="analysis-section">
                             <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="info-item">
                                 <span class="label">主色调:</span>
@@ -196,32 +241,42 @@
                             <h6>线条分析</h6>
                             <div class="form-info">
                               <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">
                                 <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 class="analysis-section">
-                            <h6>整体评估</h6>
+                            <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 class="metric-fill" [style.width.%]="getComplexityScore(analysisResult.formAnalysis.geometryAnalysis.complexity)"></div>
                                 </div>
-                                <span class="metric-value">{{ analysisResult.formAnalysis.overallAssessment.complexity || 0 }}%</span>
+                                <span class="metric-value">{{ analysisResult.formAnalysis.geometryAnalysis.complexity || '未知' }}</span>
                               </div>
                               <div class="metric">
-                                <span class="metric-label">视觉冲击</span>
+                                <span class="metric-label">视觉平衡</span>
                                 <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>
-                                <span class="metric-value">{{ analysisResult.formAnalysis.overallAssessment.visualImpact || 0 }}%</span>
+                                <span class="metric-value">{{ analysisResult.formAnalysis.geometryAnalysis.balance.visual || 0 }}%</span>
                               </div>
                             </div>
                           </div>
@@ -249,11 +304,16 @@
                           <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>
+                              <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>
@@ -267,12 +327,12 @@
                             <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>
+                                <span class="label">主要图案:</span>
+                                <span class="value">{{ getPatternTypeName(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>
+                                <span class="value">{{ getComplexityName(analysisResult.patternAnalysis.patternRecognition.patternComplexity.level) || '无' }}</span>
                               </div>
                             </div>
                           </div>
@@ -281,8 +341,8 @@
                             <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>
+                                <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>
@@ -296,12 +356,23 @@
                             <h6>光源识别</h6>
                             <div class="lighting-info">
                               <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">
                                 <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>
@@ -311,11 +382,14 @@
                             <div class="ambient-info">
                               <div class="info-item">
                                 <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 class="info-item">
                                 <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>

+ 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-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 {
         display: flex;
         gap: 12px;

+ 130 - 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';
   buttonHoverState = 'normal';
   copySuccess = false; // 复制成功状态
+  
+  // 暴露Math对象给模板使用
+  Math = Math;
 
   private progressSubscription?: Subscription;
   private resizeSubscription?: Subscription;
@@ -1179,7 +1182,133 @@ interface RequirementMapping {
       { name: '粗糙度', value: properties.roughness?.value || 0 },
       { name: '光泽度', value: properties.glossiness?.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;
+  }
 }