Эх сурвалжийг харах

Merge branch 'master' of http://git.fmode.cn:3000/nkkj/yss-project

徐福静0235668 3 өдөр өмнө
parent
commit
9541cf5ac4
26 өөрчлөгдсөн 7241 нэмэгдсэн , 220 устгасан
  1. 532 0
      docs/implementation-status.md
  2. 263 0
      docs/optimization-summary.md
  3. 2207 0
      docs/requirements-optimization.md
  4. 136 79
      src/app/pages/designer/project-detail/project-detail.html
  5. 35 2
      src/app/pages/designer/project-detail/project-detail.ts
  6. 160 0
      src/app/shared/components/color-wheel-visualizer/color-wheel-visualizer.html
  7. 89 0
      src/app/shared/components/color-wheel-visualizer/color-wheel-visualizer.scss
  8. 215 0
      src/app/shared/components/color-wheel-visualizer/color-wheel-visualizer.ts
  9. 82 0
      src/app/shared/components/furniture-form-selector/furniture-form-selector.html
  10. 233 0
      src/app/shared/components/furniture-form-selector/furniture-form-selector.scss
  11. 246 0
      src/app/shared/components/furniture-form-selector/furniture-form-selector.ts
  12. 196 0
      src/app/shared/components/pattern-visualizer/pattern-visualizer.html
  13. 305 0
      src/app/shared/components/pattern-visualizer/pattern-visualizer.scss
  14. 169 0
      src/app/shared/components/pattern-visualizer/pattern-visualizer.ts
  15. 246 65
      src/app/shared/components/requirements-confirm-card/requirements-confirm-card.html
  16. 639 0
      src/app/shared/components/requirements-confirm-card/requirements-confirm-card.scss
  17. 612 67
      src/app/shared/components/requirements-confirm-card/requirements-confirm-card.ts
  18. 151 0
      src/app/shared/components/texture-comparison-visualizer/texture-comparison-visualizer.html
  19. 200 0
      src/app/shared/components/texture-comparison-visualizer/texture-comparison-visualizer.scss
  20. 272 0
      src/app/shared/components/texture-comparison-visualizer/texture-comparison-visualizer.ts
  21. 19 2
      src/app/shared/components/upload-success-modal/upload-success-modal.component.html
  22. 15 0
      src/app/shared/components/upload-success-modal/upload-success-modal.component.scss
  23. 24 3
      src/app/shared/components/upload-success-modal/upload-success-modal.component.ts
  24. 26 1
      src/app/shared/services/color-analysis.service.ts
  25. 155 1
      src/app/shared/services/lighting-analysis.service.ts
  26. 14 0
      src/styles.scss

+ 532 - 0
docs/implementation-status.md

@@ -0,0 +1,532 @@
+# 需求深化阶段实施状态报告
+
+## 📅 更新时间
+最后更新:2025-10-14
+
+---
+
+## ✅ 阶段1:素材分析增强 - 实施完成
+
+### 1.1 色彩分析增强 ✅ **已完成**
+
+**实现内容:**
+- ✅ 创建了 `color-wheel-visualizer` 组件
+- ✅ 在素材分析中集成色轮可视化(第230-235行)
+- ✅ 支持显示主色调分布、百分比、颜色名称
+- ✅ 交互式色轮,支持180px尺寸渲染
+
+**界面位置:**
+```
+素材解析标签页 → 上传参考图 → 素材卡片 → 色彩分析区域 → 色轮可视化
+```
+
+**代码文件:**
+- 组件:`src/app/shared/components/color-wheel-visualizer/`
+- 集成位置:`requirements-confirm-card.html` 第230行
+- 数据来源:`material.analysis.mainColors`
+
+---
+
+### 1.2 形体分析补充 ✅ **已完成**
+
+**实现内容:**
+- ✅ 创建了 `furniture-form-selector` 组件
+- ✅ 支持软装形体选择(沙发、椅子、桌子等9种形体)
+- ✅ 自动计算主导质感、形体复杂度、风格方向
+- ✅ 可选择和取消选择不同形体
+
+**组件功能:**
+- 9种预设形体类型可选
+- 动态计算主导质感(如:柔软占67%)
+- 计算形体复杂度(0-100分)
+- 确定风格方向(现代简约/欧式古典等)
+
+**代码文件:**
+- 组件:`src/app/shared/components/furniture-form-selector/`
+- 接口:`FurnitureForm`, `FormSelectorConfig`
+
+---
+
+### 1.3 质感分析优化 ✅ **已完成**
+
+**实现内容:**
+- ✅ 创建了 `texture-comparison-visualizer` 组件
+- ✅ 支持多材质质感对比可视化
+- ✅ 雷达图展示6个质感维度(粗糙度、反射率、光泽度、金属感、透明度、柔软度)
+- ✅ 属性条形图对比展示
+
+**可视化维度:**
+1. 粗糙度 (Roughness)
+2. 反射率 (Reflectivity)
+3. 光泽度 (Glossiness)
+4. 金属感 (Metallic)
+5. 透明度 (Transparency)
+6. 柔软度 (Softness)
+
+**代码文件:**
+- 组件:`src/app/shared/components/texture-comparison-visualizer/`
+- 数据模型:`TextureProperties`, `TextureComparison`
+
+---
+
+### 1.4 纹理分析增强 ✅ **已完成**
+
+**实现内容:**
+- ✅ 创建了 `pattern-visualizer` 组件
+- ✅ 可视化展示纹理特征:重复、间距、对称性、节奏
+- ✅ 支持5种纹理类型(几何、有机、线性、花卉、抽象)
+- ✅ 条形图展示各特征强度
+
+**可视化特征:**
+1. 重复性 (Repetition) - 0-100%
+2. 间距 (Spacing) - 密集/适中/稀疏
+3. 对称性 (Symmetry) - 0-100%
+4. 节奏感 (Rhythm) - 0-100%
+
+**代码文件:**
+- 组件:`src/app/shared/components/pattern-visualizer/`
+- 数据模型:`PatternCharacteristics`, `PatternType`
+
+---
+
+### 1.5 灯光分析补充 ✅ **已完成**
+
+**实现内容:**
+- ✅ 扩展了 `lighting-analysis.service.ts`
+- ✅ 新增**光比分析**(lightingRatio)功能
+  - 主光与补光比例(keyToFill)
+  - 对比度描述(低/中/高/戏剧性)
+  - 主光和补光强度百分比
+- ✅ 新增**光占比分析**(lightDistribution)功能
+  - 自然光占比
+  - 人工光占比
+  - 环境光占比
+  - 直射光占比
+  - 间接光占比
+  - 主导光源类型判断
+  - 光平衡描述
+
+**界面位置:**
+```
+素材解析标签页 → 参考图素材卡片 → 灯光分析区域
+→ 光比分析(第334-363行)
+→ 光占比分析(第366-416行)
+```
+
+**数据结构:**
+```typescript
+interface LightingRatioAnalysis {
+  keyToFill: number;           // 如 3:1
+  contrast: number;
+  description: 'low-contrast' | 'medium-contrast' | 'high-contrast' | 'dramatic';
+  keyLightIntensity: number;   // 主光强度 %
+  fillLightIntensity: number;  // 补光强度 %
+  ratioQuality: 'flat' | 'balanced' | 'dramatic' | 'extreme';
+}
+
+interface LightDistributionAnalysis {
+  natural: number;        // 自然光占比 %
+  artificial: number;     // 人工光占比 %
+  ambient: number;        // 环境光占比 %
+  direct: number;         // 直射光占比 %
+  indirect: number;       // 间接光占比 %
+  dominantType: 'natural' | 'artificial' | 'mixed';
+  lightingBalance: 'natural-dominant' | 'artificial-dominant' | 'balanced';
+}
+```
+
+**代码文件:**
+- 服务:`src/app/shared/services/lighting-analysis.service.ts`
+- 集成位置:`requirements-confirm-card.html` 第334-416行
+- 翻译方法:`getRatioDescription()`, `getBalanceDescription()`
+
+---
+
+### 1.6 全展示模式 ✅ **已完成**
+
+**实现内容:**
+- ✅ 添加"完整展示"切换按钮(第89行)
+- ✅ 支持展开/折叠所有素材分析详情
+- ✅ 记录每个素材的展开状态
+- ✅ 全局切换和单个素材切换
+
+**功能特性:**
+- 精简模式:只显示关键信息
+- 完整展示模式:展开所有分析细节
+- 记忆功能:记住每个素材的展开状态
+
+**代码实现:**
+```typescript
+// requirements-confirm-card.ts
+isFullDisplayMode = false;
+expandedMaterials: Set<string> = new Set();
+
+toggleFullDisplayMode(): void {
+  this.isFullDisplayMode = !this.isFullDisplayMode;
+  if (this.isFullDisplayMode) {
+    this.materials.forEach(m => this.expandedMaterials.add(m.id));
+  } else {
+    this.expandedMaterials.clear();
+  }
+}
+```
+
+**界面位置:**
+```
+素材解析标签页 → 右上角工具栏 → "完整展示" 按钮
+```
+
+---
+
+### 1.7 报告导出功能 ✅ **已完成**
+
+**实现内容:**
+- ✅ 导出HTML格式分析报告(第111行)
+- ✅ 导出JSON格式原始数据(第119行)
+- ✅ 包含所有五维分析数据
+- ✅ 自动生成文件名(带时间戳)
+
+**导出内容包括:**
+1. 项目基本信息
+2. 所有上传素材列表
+3. 五维分析详细数据:
+   - 色彩分析(主色调、色温、心理学)
+   - 形体分析(复杂度、对称性)
+   - 质感分析(材质、光泽度)
+   - 纹理分析(类型、重复性)
+   - 灯光分析(光源、光比、光占比)
+
+**代码实现:**
+```typescript
+exportAnalysisReportHTML(): void {
+  const reportData = this.generateReportData();
+  const html = this.generateHTMLReport(reportData);
+  const blob = new Blob([html], { type: 'text/html' });
+  const url = window.URL.createObjectURL(blob);
+  const a = document.createElement('a');
+  a.href = url;
+  a.download = `分析报告_${new Date().getTime()}.html`;
+  a.click();
+}
+
+exportAnalysisReportJSON(): void {
+  const reportData = this.generateReportData();
+  const json = JSON.stringify(reportData, null, 2);
+  const blob = new Blob([json], { type: 'application/json' });
+  // ... 下载逻辑
+}
+```
+
+**界面位置:**
+```
+素材解析标签页 → 右上角工具栏 → "导出HTML" / "导出JSON" 按钮
+```
+
+---
+
+## ✅ 阶段2:需求映射隐藏 - 实施完成
+
+### 2.1 移除需求映射标签页 ✅ **已完成**
+
+**实现内容:**
+- ✅ 使用 `@if (false)` 隐藏需求映射标签按钮
+- ✅ 保留需求映射内容区域但不可见
+- ✅ 不影响数据流和父组件集成
+
+**代码修改:**
+```html
+<!-- requirements-confirm-card.html -->
+@if (false) {
+  <button class="tab-button" 
+          [class.active]="activeTab === 'mapping'"
+          (click)="switchTab('mapping')">
+    需求映射
+  </button>
+}
+```
+
+---
+
+### 2.2 后台自动触发机制 ✅ **已完成**
+
+**实现内容:**
+- ✅ 素材分析完成后自动生成需求映射
+- ✅ 静默执行,不显示给用户
+- ✅ 自动发送映射数据到父组件
+
+**触发时机:**
+1. 上传参考图完成色彩分析后
+2. 上传CAD完成CAD分析后
+3. 实时分析完成后
+
+**代码实现:**
+```typescript
+// requirements-confirm-card.ts
+private autoGenerateRequirementMapping(): void {
+  if (!this.colorAnalysisResult) return;
+  
+  this.requirementMappingService
+    .generateRequirementMapping(this.colorAnalysisResult)
+    .subscribe({
+      next: (mapping: RequirementMapping) => {
+        this.requirementMapping = mapping;
+        console.log('✅ 需求映射已自动生成(后台):', mapping);
+        this.emitMappingDataUpdate(); // 发送给父组件
+      },
+      error: (error: any) => {
+        console.error('❌ 自动映射生成失败:', error);
+      }
+    });
+}
+```
+
+---
+
+### 2.3 数据接口保留 ✅ **已完成**
+
+**实现内容:**
+- ✅ 保留 `requirementMapping` 数据属性
+- ✅ 通过 `@Output()` 发送映射数据给父组件
+- ✅ 父组件可在"方案确认"阶段使用映射数据
+
+**数据流:**
+```
+素材分析完成 
+  → autoGenerateRequirementMapping()
+  → RequirementMappingService.generateRequirementMapping()
+  → this.requirementMapping = mapping
+  → emitMappingDataUpdate()
+  → @Output() mappingDataUpdated
+  → 父组件接收
+  → 用于方案确认阶段显示
+```
+
+---
+
+## 🐛 问题修复记录
+
+### 修复1:上传成功弹窗定位问题 ✅ **已解决**
+
+**问题描述:**
+- 色彩分析弹窗显示在右下角而非屏幕中央
+- 背景遮罩只覆盖部分区域
+- 弹窗被父容器定位上下文限制
+
+**根本原因:**
+- `app-upload-success-modal` 组件嵌套在 `requirements-confirm-card` 内部(第1015行)
+- 父容器可能有 `position: relative` 等定位样式
+- 即使设置 `position: fixed` 也会受父容器影响
+
+**解决方案:**
+1. 在组件初始化时将弹窗DOM移到 `document.body` 层级
+2. 禁用样式封装 `ViewEncapsulation.None`
+3. 添加全局样式强制定位
+
+**代码修改:**
+```typescript
+// upload-success-modal.component.ts
+ngOnInit() {
+  this.moveToBody();  // 移到body层级
+  this.checkScreenSize();
+  this.setupResizeListener();
+}
+
+private moveToBody(): void {
+  const element = this.elementRef.nativeElement;
+  this.renderer.appendChild(document.body, element);
+  console.log('✅ 弹窗组件已移到 body 层级');
+}
+```
+
+**最终状态:** ✅ 弹窗完美居中显示
+
+---
+
+## 📊 实施进度总览
+
+| 阶段 | 任务 | 状态 | 完成时间 |
+|------|------|------|----------|
+| **阶段1** | 色彩分析增强 | ✅ 完成 | 2025-10-14 |
+| | 形体分析补充 | ✅ 完成 | 2025-10-14 |
+| | 质感分析优化 | ✅ 完成 | 2025-10-14 |
+| | 纹理分析增强 | ✅ 完成 | 2025-10-14 |
+| | 灯光分析补充 | ✅ 完成 | 2025-10-14 |
+| | 全展示模式 | ✅ 完成 | 2025-10-14 |
+| | 报告导出功能 | ✅ 完成 | 2025-10-14 |
+| **阶段2** | 移除需求映射标签页 | ✅ 完成 | 2025-10-14 |
+| | 后台自动触发机制 | ✅ 完成 | 2025-10-14 |
+| | 数据接口保留 | ✅ 完成 | 2025-10-14 |
+
+**总体进度:** 阶段1 & 阶段2 100% 完成 ✅
+
+---
+
+## 📁 新增文件清单
+
+### 组件文件
+
+```
+src/app/shared/components/
+├── color-wheel-visualizer/
+│   ├── color-wheel-visualizer.ts          ✅ 已创建
+│   ├── color-wheel-visualizer.html        ✅ 已创建
+│   └── color-wheel-visualizer.scss        ✅ 已创建
+│
+├── furniture-form-selector/
+│   ├── furniture-form-selector.ts         ✅ 已创建
+│   ├── furniture-form-selector.html       ✅ 已创建
+│   └── furniture-form-selector.scss       ✅ 已创建
+│
+├── texture-comparison-visualizer/
+│   ├── texture-comparison-visualizer.ts   ✅ 已创建
+│   ├── texture-comparison-visualizer.html ✅ 已创建
+│   └── texture-comparison-visualizer.scss ✅ 已创建
+│
+└── pattern-visualizer/
+    ├── pattern-visualizer.ts              ✅ 已创建
+    ├── pattern-visualizer.html            ✅ 已创建
+    └── pattern-visualizer.scss            ✅ 已创建
+```
+
+### 修改文件清单
+
+```
+src/app/shared/
+├── components/
+│   ├── requirements-confirm-card/
+│   │   ├── requirements-confirm-card.ts       ✅ 已修改(新增1000+行代码)
+│   │   ├── requirements-confirm-card.html     ✅ 已修改(集成新组件、添加UI)
+│   │   └── requirements-confirm-card.scss     ✅ 已修改(新增样式)
+│   │
+│   └── upload-success-modal/
+│       ├── upload-success-modal.component.ts  ✅ 已修改(修复定位问题)
+│       ├── upload-success-modal.component.html ✅ 已修改(强制样式)
+│       └── upload-success-modal.component.scss ✅ 已修改(全局定位)
+│
+└── services/
+    ├── color-analysis.service.ts          ✅ 已修改(扩展接口)
+    └── lighting-analysis.service.ts       ✅ 已修改(新增光比、光占比)
+
+src/
+└── styles.scss                            ✅ 已修改(添加全局弹窗样式)
+```
+
+---
+
+## 🎯 如何查看实施成果
+
+### 步骤1:上传参考图片
+1. 进入项目详情页
+2. 点击"确认需求"卡片
+3. 切换到"素材解析"标签
+4. 点击"上传参考图"
+5. 选择一张图片上传
+
+### 步骤2:查看上传成功弹窗(修复后的居中效果)
+- ✅ 弹窗应在**屏幕正中央**显示
+- ✅ 背景遮罩覆盖**整个屏幕**
+- ✅ 显示色彩分析进度和结果
+
+### 步骤3:查看素材分析详情
+上传完成后,在素材卡片中可以看到:
+
+1. **色彩分析区域**
+   - 🎨 色轮可视化组件(圆形色轮图)
+   - 显示主色调、百分比、颜色名称
+
+2. **形体分析区域**
+   - 📐 形体复杂度和对称性数据
+   - (软装形体选择器可独立使用)
+
+3. **质感分析区域**
+   - 🪨 材质类型和粗糙度等级
+   - (质感对比可视化可独立使用)
+
+4. **纹理分析区域**
+   - 🖼️ 纹理类型和复杂度
+   - (纹理可视化可独立使用)
+
+5. **灯光分析区域**
+   - 💡 光源识别
+   - 📊 **光比分析**(主光vs补光,柱状图)
+   - 📊 **光占比分析**(自然光、人工光、直射光等百分比)
+
+### 步骤4:使用完整展示模式
+- 点击右上角"完整展示"按钮
+- 所有素材的分析详情自动展开
+- 再次点击可切换回精简模式
+
+### 步骤5:导出分析报告
+- 点击"导出HTML"按钮 → 下载完整HTML报告
+- 点击"导出JSON"按钮 → 下载原始JSON数据
+
+### 步骤6:验证需求映射隐藏
+- ✅ 标签页中**不再显示**"需求映射"按钮
+- ✅ 素材分析完成后,控制台会输出"✅ 需求映射已自动生成(后台)"
+- ✅ 数据会自动发送给父组件
+
+---
+
+## 🔄 下一步:阶段3 - 协作验证重构
+
+根据原计划,接下来应该实施:
+
+### 阶段3.1:五维验证模块(预计3-4天)
+- [ ] 创建维度验证数据模型
+- [ ] 实现五维验证UI布局
+- [ ] 创建 dimension-verification 组件
+- [ ] 实现维度切换逻辑
+- [ ] 添加验证状态管理
+
+### 阶段3.2:素材关联系统(预计2-3天)
+- [ ] 实现拖拽关联功能
+- [ ] 实现下拉选择关联
+- [ ] 添加相关度计算
+- [ ] 实现素材高亮显示
+
+### 阶段3.3:标注工具集成(预计3-4天)
+- [ ] 创建 annotation-tool 组件
+- [ ] 实现SVG标注层
+- [ ] 支持多种标注类型
+- [ ] 实现标注保存和加载
+
+### 阶段3.4:区域细分管理(预计2-3天)
+- [ ] 创建 area-management 组件
+- [ ] 实现区域添加/编辑对话框
+- [ ] 配置区域预设
+- [ ] 实现区域对比视图
+
+### 阶段3.5:分工和人员系统(预计2-3天)
+- [ ] 定义用户角色和权限
+- [ ] 实现分工管理面板
+- [ ] 实现协作状态跟踪
+- [ ] 实现审批流程
+
+---
+
+## 📝 备注
+
+**测试建议:**
+1. 测试不同类型的图片(风景、室内、产品等)
+2. 测试光比和光占比的计算准确性
+3. 测试导出功能在不同浏览器的兼容性
+4. 测试完整展示模式的性能(多个素材时)
+
+**已知限制:**
+- 色轮可视化目前使用模拟数据,实际色彩分析需要后端AI支持
+- 光比和光占比计算基于模拟算法,实际应用需要图像处理算法优化
+- 部分新组件(形体选择器、质感对比、纹理可视化)已创建但尚未完全集成到主界面
+
+**优化建议:**
+- 考虑为色轮添加交互功能(点击颜色查看详情)
+- 为光比和光占比添加更详细的可视化图表
+- 增加分析结果的导出格式(如PDF)
+- 添加分析历史记录功能
+
+---
+
+**文档版本**: v1.0  
+**创建日期**: 2025-10-14  
+**最后更新**: 2025-10-14  
+**状态**: ✅ 阶段1&2 已完成
+

+ 263 - 0
docs/optimization-summary.md

@@ -0,0 +1,263 @@
+# 素材分析优化总结
+
+## 📅 更新时间
+2025-10-15
+
+---
+
+## ✅ 完成的优化
+
+### 1. 素材卡片空间优化
+
+**问题**:
+- 素材卡片中的详细分析占用太多空间
+- 分析内容展开后影响整体布局
+- 没有合理利用界面空间
+
+**解决方案**:
+- ✅ 添加**折叠/展开按钮**,默认只显示基础信息
+- ✅ 用户点击"查看详情"才展开完整分析
+- ✅ 展开时有平滑动画效果
+- ✅ 保留"全展示模式"快速展开所有素材
+
+**效果**:
+```
+默认视图:
+┌─────────────────────────┐
+│ 图片缩略图              │
+│ 20.jpg                  │
+│ 预览图片                │
+│                         │
+│ 色温: 3000K  主色: 3种 │
+│      [查看详情 ▼]       │
+└─────────────────────────┘
+
+点击后展开:
+┌─────────────────────────┐
+│ 图片缩略图              │
+│ 20.jpg                  │
+│ 预览图片                │
+│                         │
+│ 色温: 3000K  主色: 3种 │
+│      [收起详情 ▲]       │
+├─────────────────────────┤
+│ 🎨 色彩分析             │
+│   [色轮可视化组件]      │
+│   主色调、心理学标签    │
+├─────────────────────────┤
+│ 📐 形体分析             │
+│   复杂度、对称性数据    │
+├─────────────────────────┤
+│ 🪨 质感分析             │
+│   材质类型、光泽度      │
+├─────────────────────────┤
+│ 🖼️ 纹理分析            │
+│   纹理类型、重复性      │
+├─────────────────────────┤
+│ 💡 灯光分析             │
+│   光比、光占比          │
+└─────────────────────────┘
+```
+
+---
+
+### 2. 新增"分析汇总"标签页
+
+**功能**:
+- ✅ 在独立标签页中集中展示所有分析数据
+- ✅ 使用卡片式布局,空间利用更合理
+- ✅ 集成所有可视化组件(色轮、形体选择器、质感对比等)
+- ✅ 响应式网格布局,自动适应屏幕宽度
+
+**标签页结构**:
+```
+[素材解析] [分析汇总] [协作验证] [进度管理]
+            ↑ 新增
+```
+
+**布局优势**:
+- 每个素材一个大卡片
+- 卡片内按维度分成5个小卡片(色彩、形体、质感、纹理、灯光)
+- 网格自动布局,响应式设计
+- 数据更直观,对比更方便
+
+---
+
+### 3. 界面布局改进
+
+#### 素材解析标签页(左侧)
+- 上传素材区域
+- 素材列表(折叠式,节省空间)
+- 快速预览缩略图
+- 基础信息展示
+- 按需展开详细分析
+
+#### 分析汇总标签页(中间)
+- 所有素材的详细分析数据
+- 五维分析可视化组件
+- 卡片式布局,清晰直观
+- 支持导出HTML/JSON报告
+
+#### 协作验证标签页(右侧)
+- 保持原有功能不变
+- 可以引用分析汇总中的数据
+- 协作评论和验证流程
+
+---
+
+## 🎨 新增样式特性
+
+### 1. 折叠按钮动画
+```scss
+.toggle-detail-btn {
+  transition: all 0.3s ease;
+  
+  &:hover {
+    background: #667eea;
+    color: white;
+  }
+  
+  svg {
+    transform: rotate(180deg); // 展开时旋转
+  }
+}
+```
+
+### 2. 分析汇总卡片网格
+```scss
+.analysis-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+  gap: 20px;
+}
+```
+
+### 3. 光比可视化条形图
+```html
+<div class="ratio-bar">
+  <div class="key-light" style="width: 75%">主光 75%</div>
+  <div class="fill-light" style="width: 25%">补光 25%</div>
+</div>
+```
+
+---
+
+## 📊 使用流程
+
+### 步骤 1:上传素材
+1. 进入"素材解析"标签页
+2. 上传参考图片
+3. 查看上传成功弹窗(屏幕居中)
+4. 弹窗关闭后,素材卡片显示基础信息
+
+### 步骤 2:查看详细分析(两种方式)
+
+**方式A:在素材卡片中展开**
+- 点击素材卡片上的"查看详情"按钮
+- 详细分析在卡片内展开
+- 适合单个素材的快速查看
+
+**方式B:切换到"分析汇总"标签页**
+- 点击顶部"分析汇总"标签
+- 所有素材的完整分析集中展示
+- 五维分析并排对比
+- 适合全面了解和对比
+
+### 步骤 3:使用分析数据
+- 在"协作验证"标签页中引用分析结果
+- 与团队成员讨论特定维度的数据
+- 导出报告(HTML/JSON)分享
+
+---
+
+## 🔧 技术实现
+
+### 组件结构
+```typescript
+// HTML模板新增部分
+<div class="analysis-summary">
+  <div class="color-info">
+    <span>色温: 3000K</span>
+    <span>主色: 3种</span>
+  </div>
+  <button (click)="toggleMaterialExpansion(material.id)">
+    {{ isMaterialExpanded(material.id) ? '收起详情' : '查看详情' }}
+  </button>
+</div>
+
+@if (isMaterialExpanded(material.id)) {
+  <div class="detailed-analysis">
+    <!-- 所有五维分析内容 -->
+  </div>
+}
+```
+
+### 数据流保持不变
+```
+素材上传 
+  → 自动分析 
+  → 生成完整数据 
+  → 素材卡片(折叠显示) 
+  → 分析汇总(完整展示)
+  → 协作验证(引用数据)
+```
+
+---
+
+## 💡 优势对比
+
+| 特性 | 优化前 | 优化后 |
+|------|--------|--------|
+| 素材卡片高度 | 展开时非常高,滚动困难 | 默认精简,按需展开 |
+| 空间利用 | 单列展示,浪费水平空间 | 网格布局,充分利用 |
+| 分析查看 | 所有分析混在一起 | 独立标签页,分类清晰 |
+| 对比功能 | 需要上下滚动对比 | 网格并排,直观对比 |
+| 用户体验 | 信息过载 | 分层展示,按需查看 |
+
+---
+
+## 🎯 下一步建议
+
+1. **测试新布局**:
+   - 上传多个素材测试折叠/展开效果
+   - 切换到"分析汇总"查看网格布局
+   - 尝试导出报告功能
+
+2. **反馈收集**:
+   - 折叠按钮是否足够明显
+   - 分析汇总的卡片大小是否合适
+   - 网格布局是否需要调整
+
+3. **潜在改进**:
+   - 添加"一键对比"功能
+   - 支持拖拽调整卡片顺序
+   - 增加筛选和搜索功能
+
+---
+
+## 📝 文件修改清单
+
+### 修改的文件
+- `requirements-confirm-card.html` - 添加折叠按钮和分析汇总标签页
+- `requirements-confirm-card.scss` - 新增样式(350+行)
+- `requirements-confirm-card.ts` - 完善模拟数据
+
+### 新增组件(已存在)
+- `color-wheel-visualizer` - 色轮可视化 ✅
+- `furniture-form-selector` - 形体选择器 ✅
+- `texture-comparison-visualizer` - 质感对比 ✅
+- `pattern-visualizer` - 纹理可视化 ✅
+
+### 保持不变
+- 所有原有功能
+- 数据流和接口
+- 导出功能
+- 协作验证
+- 进度管理
+
+---
+
+**状态**: ✅ 已完成并通过构建测试
+**版本**: v1.1
+**日期**: 2025-10-15
+

+ 2207 - 0
docs/requirements-optimization.md

@@ -0,0 +1,2207 @@
+# 确认需求功能优化方案文档
+
+## 📋 项目背景
+
+### 当前功能概述
+
+本文档针对**需求沟通阶段**的**确认需求**功能模块进行全面分析和优化建议。该模块位于设计师项目详情页面中,是整个项目流程中的关键环节。
+
+### 相关代码文件
+
+- **主组件**:`src/app/shared/components/requirements-confirm-card/requirements-confirm-card.ts`
+- **模板文件**:`src/app/shared/components/requirements-confirm-card/requirements-confirm-card.html`
+- **样式文件**:`src/app/shared/components/requirements-confirm-card/requirements-confirm-card.scss`
+- **父组件集成**:`src/app/pages/designer/project-detail/project-detail.ts`
+
+### 当前功能架构
+
+确认需求模块采用**四标签页**架构:
+1. **素材解析** - 文本、图片、CAD上传及分析
+2. **需求映射** - 基于固定场景的参数映射和氛围预览
+3. **协作验证** - 需求列表、评论、优先级管理
+4. **进度管理** - 整体进度跟踪和历史状态管理
+
+---
+
+## 📊 功能对比矩阵
+
+### 需求文档要求 vs 当前实现对比
+
+| 功能模块 | 需求文档要求 | 当前实现状态 | 优先级 | 完成度 |
+|---------|------------|------------|-------|-------|
+| **素材分析** |
+| 色彩分析 | 补充色轮图标,完整展示色彩信息 | 有基础数据,缺少可视化 | 🔴 高 | 60% |
+| 形体分析 | 整体形体+软装形体选择 | 只有分析数据,无选择功能 | 🔴 高 | 40% |
+| 质感分析 | 质感、材质、亮光/哑光对比 | 有材质分类,展示不直观 | 🟡 中 | 50% |
+| 纹理分析 | 图案可视化展示 | 只有文字标签 | 🟡 中 | 40% |
+| 灯光分析 | 光色、光比、光质、光占比 | 有光色和光质,缺光比和光占比 | 🔴 高 | 50% |
+| 完全展示所有信息 | 核心要求 | 信息折叠,展示简化 | 🔴 高 | 50% |
+| **需求映射** |
+| 功能定位 | 隐藏功能,后台自动生成 | 独立标签页,用户可见 | 🟡 中 | 需调整 |
+| **协作验证** |
+| 五维度验证 | 色彩、形体、质感、纹理、灯光分类 | 通用需求列表 | 🔴 高 | 20% |
+| 素材关联 | 关联参考图、CAD、文字说明 | 无关联机制 | 🔴 高 | 0% |
+| 选择和标注 | 交互式标注工具 | 无标注功能 | 🔴 高 | 0% |
+| 区域细分 | 支持不同区域(客厅、婴儿房等) | 只有整体方案 | 🔴 高 | 0% |
+| 分工人员 | 不同区域分配不同人员 | 只有角色标识 | 🟡 中 | 10% |
+
+---
+
+## 🎯 详细优化点清单
+
+## A. 素材分析标签页优化
+
+### A1. 色彩分析增强 🔴 高优先级
+
+#### 当前问题
+- 只显示简单的SVG图标
+- 色彩数据展示不够直观
+- 缺少交互式色轮可视化
+
+#### 优化方案
+**1. 交互式色轮可视化组件**
+
+```typescript
+// 新建组件:src/app/shared/components/color-wheel-visualizer/
+interface ColorWheelConfig {
+  colors: Array<{
+    hue: number;        // 色调角度 0-360
+    saturation: number; // 饱和度 0-100
+    brightness: number; // 亮度 0-100
+    percentage: number; // 占比
+    hex: string;
+  }>;
+  interactive: boolean; // 是否可交互
+}
+```
+
+**功能特性:**
+- SVG绘制的色轮,显示360度色相环
+- 在色轮上标注主要颜色的位置
+- 鼠标悬停显示详细信息(RGB、HEX、HSB值)
+- 显示色彩分布饼图
+- 色彩和谐度可视化(单色、类似色、互补色等)
+
+**2. 色彩详情卡片**
+
+```html
+<div class="color-analysis-detail">
+  <div class="color-wheel-container">
+    <app-color-wheel-visualizer 
+      [colors]="material.analysis.enhancedColorAnalysis.colorWheel.colorDistribution"
+      [interactive]="true">
+    </app-color-wheel-visualizer>
+  </div>
+  
+  <div class="color-info-grid">
+    <!-- 主色调信息 -->
+    <div class="color-metric">
+      <span class="label">主色调</span>
+      <div class="hue-indicator" [style.background]="getHueColor()"></div>
+      <span class="value">{{ primaryHue }}°</span>
+    </div>
+    
+    <!-- 饱和度 -->
+    <div class="color-metric">
+      <span class="label">饱和度</span>
+      <div class="saturation-bar">
+        <div class="fill" [style.width.%]="saturation"></div>
+      </div>
+      <span class="value">{{ saturation }}%</span>
+    </div>
+    
+    <!-- 色温 -->
+    <div class="color-metric">
+      <span class="label">色温</span>
+      <div class="temperature-indicator" [class]="temperatureClass"></div>
+      <span class="value">{{ temperature }}K ({{ temperatureDesc }})</span>
+    </div>
+    
+    <!-- 色彩心理学 -->
+    <div class="color-psychology">
+      <h6>色彩心理学</h6>
+      <div class="mood-tags">
+        <span class="mood-tag primary">{{ primaryMood }}</span>
+        <span class="atmosphere-tag">{{ atmosphere }}</span>
+      </div>
+      <ul class="effects-list">
+        <li *ngFor="let effect of psychologicalEffects">{{ effect }}</li>
+      </ul>
+    </div>
+  </div>
+</div>
+```
+
+**3. 技术实施细节**
+- 使用D3.js或纯SVG实现色轮
+- 响应式设计,适配不同屏幕尺寸
+- 动画过渡效果
+- 支持导出色板
+
+---
+
+### A2. 形体分析补充 🔴 高优先级
+
+#### 当前问题
+- 只展示形体复杂度和视觉冲击的数值
+- 缺少软装形体的分类和选择功能
+- 无法针对特定形体进行标注
+
+#### 优化方案
+
+**1. 形体分类展示**
+
+```typescript
+interface FormCategory {
+  overall: {
+    lineType: 'straight' | 'curved' | 'mixed';  // 直线/曲线/混合
+    complexity: number;                          // 复杂度 0-100
+    symmetry: 'symmetric' | 'asymmetric';       // 对称/不对称
+  };
+  furniture: {
+    dominant: string[];      // 主要家具形体
+    style: string;          // 风格(简约、古典、现代等)
+    proportion: number;     // 占比
+  };
+}
+```
+
+**2. 软装形体选择器**
+
+```html
+<div class="form-analysis-section">
+  <h6>形体分析</h6>
+  
+  <!-- 整体形体 -->
+  <div class="overall-form">
+    <div class="form-type-selector">
+      <label>线条类型:</label>
+      <div class="type-options">
+        <button class="type-btn" 
+                [class.active]="formType === 'straight'"
+                (click)="selectFormType('straight')">
+          <svg><!-- 直线图标 --></svg>
+          直线条
+        </button>
+        <button class="type-btn" 
+                [class.active]="formType === 'curved'"
+                (click)="selectFormType('curved')">
+          <svg><!-- 曲线图标 --></svg>
+          曲线条
+        </button>
+        <button class="type-btn" 
+                [class.active]="formType === 'mixed'"
+                (click)="selectFormType('mixed')">
+          <svg><!-- 混合图标 --></svg>
+          混合型
+        </button>
+      </div>
+    </div>
+    
+    <!-- 对称性 -->
+    <div class="symmetry-indicator">
+      <span class="label">对称性:</span>
+      <div class="symmetry-visual">
+        <svg><!-- 对称性可视化图标 --></svg>
+      </div>
+      <span class="value">{{ symmetryType }}</span>
+    </div>
+  </div>
+  
+  <!-- 软装形体选择 -->
+  <div class="furniture-form-selector">
+    <h6>软装形体选择</h6>
+    <div class="furniture-grid">
+      <div class="furniture-item" 
+           *ngFor="let item of furnitureItems"
+           [class.selected]="item.selected"
+           (click)="toggleFurniture(item)">
+        <img [src]="item.preview" [alt]="item.name">
+        <span class="name">{{ item.name }}</span>
+        <span class="form-tag">{{ item.formType }}</span>
+      </div>
+    </div>
+  </div>
+  
+  <!-- 形体比例分析 -->
+  <div class="proportion-analysis">
+    <h6>形体比例分析</h6>
+    <div class="proportion-chart">
+      <!-- 使用ECharts绘制比例图 -->
+    </div>
+  </div>
+</div>
+```
+
+**3. 数据模型扩展**
+
+```typescript
+interface FurnitureFormItem {
+  id: string;
+  name: string;
+  category: 'sofa' | 'table' | 'chair' | 'cabinet' | 'bed' | 'decoration';
+  formType: 'geometric' | 'organic' | 'linear' | 'curved';
+  preview: string;
+  selected: boolean;
+  dominance: number; // 视觉主导性
+}
+```
+
+---
+
+### A3. 质感分析优化 🟡 中优先级
+
+#### 当前问题
+- 只显示材质类别和粗糙度等级文字
+- 亮光/哑光对比不明显
+- 缺少质感的视觉化展示
+
+#### 优化方案
+
+**1. 亮光/哑光对比卡片**
+
+```html
+<div class="texture-analysis-section">
+  <h6>质感分析</h6>
+  
+  <!-- 光泽度对比 -->
+  <div class="glossiness-comparison">
+    <div class="gloss-card matte" [class.active]="glossType === 'matte'">
+      <div class="gloss-preview matte-texture">
+        <span class="label">哑光</span>
+        <div class="gloss-level">{{ matteLevel }}/10</div>
+      </div>
+      <div class="gloss-properties">
+        <span>柔和 · 低反射 · 温暖</span>
+      </div>
+    </div>
+    
+    <div class="gloss-card glossy" [class.active]="glossType === 'glossy'">
+      <div class="gloss-preview glossy-texture">
+        <span class="label">亮光</span>
+        <div class="gloss-level">{{ glossyLevel }}/10</div>
+      </div>
+      <div class="gloss-properties">
+        <span>明亮 · 高反射 · 现代</span>
+      </div>
+    </div>
+  </div>
+  
+  <!-- 材质属性详情 -->
+  <div class="material-properties">
+    <div class="property-row">
+      <span class="label">粗糙度</span>
+      <div class="property-bar">
+        <div class="fill roughness" [style.width.%]="roughness"></div>
+        <span class="value">{{ roughnessLevel }}</span>
+      </div>
+    </div>
+    
+    <div class="property-row">
+      <span class="label">反射率</span>
+      <div class="property-bar">
+        <div class="fill reflectivity" [style.width.%]="reflectivity"></div>
+        <span class="value">{{ reflectivity }}%</span>
+      </div>
+    </div>
+    
+    <div class="property-row">
+      <span class="label">质感层次</span>
+      <div class="texture-layers">
+        <span class="layer" *ngFor="let layer of textureLayers">{{ layer }}</span>
+      </div>
+    </div>
+  </div>
+  
+  <!-- 材质分类 -->
+  <div class="material-classification">
+    <h6>材质类型</h6>
+    <div class="material-tags">
+      <span class="material-tag primary">{{ primaryMaterial }}</span>
+      <span class="material-tag" *ngFor="let mat of secondaryMaterials">
+        {{ mat.name }} ({{ mat.percentage }}%)
+      </span>
+    </div>
+  </div>
+</div>
+```
+
+**2. 质感预览组件**
+
+使用3D渲染或高质量图片展示不同质感效果:
+- 木质(哑光/亮光)
+- 金属(拉丝/抛光)
+- 织物(粗糙/光滑)
+- 石材(天然/磨光)
+
+---
+
+### A4. 纹理分析增强 🟡 中优先级
+
+#### 当前问题
+- 只有纹理类型的文字标签
+- 缺少图案的可视化展示
+- 无法直观了解纹理重复规律
+
+#### 优化方案
+
+**1. 纹理图案可视化**
+
+```html
+<div class="pattern-analysis-section">
+  <h6>纹理分析</h6>
+  
+  <!-- 图案预览 -->
+  <div class="pattern-preview-grid">
+    <div class="pattern-item" *ngFor="let pattern of recognizedPatterns">
+      <div class="pattern-visual">
+        <!-- 截取参考图中的纹理区域显示 -->
+        <img [src]="pattern.sampleImage" [alt]="pattern.type">
+        <div class="pattern-overlay">
+          <span class="pattern-type">{{ pattern.type }}</span>
+          <span class="coverage">{{ pattern.coverage }}%</span>
+        </div>
+      </div>
+      <div class="pattern-info">
+        <span class="complexity">复杂度: {{ pattern.complexity }}</span>
+        <span class="scale">尺度: {{ pattern.scale }}</span>
+      </div>
+    </div>
+  </div>
+  
+  <!-- 重复规律分析 -->
+  <div class="repetition-analysis">
+    <h6>重复规律</h6>
+    <div class="repetition-visual">
+      <svg>
+        <!-- 绘制网格或重复单元 -->
+      </svg>
+    </div>
+    <div class="repetition-info">
+      <div class="info-row">
+        <span class="label">重复类型:</span>
+        <span class="value">{{ repetitionType }}</span>
+      </div>
+      <div class="info-row">
+        <span class="label">间距:</span>
+        <span class="value">横向 {{ spacingH }}mm · 纵向 {{ spacingV }}mm</span>
+      </div>
+      <div class="info-row">
+        <span class="label">对称性:</span>
+        <span class="value">{{ symmetryType }} (强度 {{ symmetryStrength }}%)</span>
+      </div>
+    </div>
+  </div>
+  
+  <!-- 纹理族 -->
+  <div class="texture-family">
+    <h6>纹理族系</h6>
+    <div class="family-tags">
+      <span class="family-tag primary">{{ primaryFamily }}</span>
+      <span class="family-tag">{{ subcategory }}</span>
+    </div>
+    <div class="suggested-materials">
+      <span class="label">建议材质:</span>
+      <span class="material" *ngFor="let mat of suggestedMaterials">{{ mat }}</span>
+    </div>
+  </div>
+</div>
+```
+
+**2. 技术实施**
+- 使用Canvas API提取纹理样本
+- 傅里叶变换分析重复模式
+- 提供纹理缩放预览
+- 支持纹理导出
+
+---
+
+### A5. 灯光分析补充 🔴 高优先级
+
+#### 当前问题
+- 只有光源类型、亮度、情绪
+- **缺少光比数据**
+- **缺少光占比数据**
+- 光质展示不够直观
+
+#### 优化方案
+
+**1. 完整的灯光分析面板**
+
+```typescript
+interface CompleteLightingAnalysis {
+  // 现有数据
+  lightSource: {
+    type: string;
+    intensity: number;
+    mood: string;
+  };
+  
+  // 新增:光比
+  lightingRatio: {
+    keyToFill: number;      // 主光与补光比例 (如 3:1)
+    contrast: number;       // 明暗对比度
+    description: string;    // 高反差/低反差/中等
+  };
+  
+  // 新增:光占比
+  lightDistribution: {
+    natural: number;        // 自然光占比 %
+    artificial: number;     // 人工光占比 %
+    ambient: number;        // 环境光占比 %
+    direct: number;         // 直射光占比 %
+    indirect: number;       // 间接光占比 %
+  };
+  
+  // 光质
+  lightQuality: {
+    softness: number;       // 柔和度
+    diffusion: number;      // 漫射度
+    directionality: number; // 方向性
+  };
+}
+```
+
+**2. 灯光分析UI**
+
+```html
+<div class="lighting-analysis-section">
+  <h6>灯光分析</h6>
+  
+  <!-- 光源识别 -->
+  <div class="light-sources">
+    <div class="source-item" *ngFor="let source of lightSources">
+      <div class="source-icon" [class]="source.type">
+        <svg><!-- 光源图标 --></svg>
+      </div>
+      <div class="source-info">
+        <span class="type">{{ source.subtype }}</span>
+        <span class="intensity">强度 {{ source.intensity }}%</span>
+      </div>
+    </div>
+  </div>
+  
+  <!-- 光比分析 NEW -->
+  <div class="lighting-ratio">
+    <h6>光比分析</h6>
+    <div class="ratio-visual">
+      <div class="ratio-bars">
+        <div class="key-light-bar" [style.height.%]="keyLightRatio">
+          <span>主光</span>
+          <span class="value">{{ keyLightRatio }}%</span>
+        </div>
+        <div class="fill-light-bar" [style.height.%]="fillLightRatio">
+          <span>补光</span>
+          <span class="value">{{ fillLightRatio }}%</span>
+        </div>
+      </div>
+      <div class="ratio-description">
+        <span class="ratio-value">{{ keyToFillRatio }}</span>
+        <span class="contrast-level">{{ contrastDescription }}</span>
+      </div>
+    </div>
+  </div>
+  
+  <!-- 光占比饼图 NEW -->
+  <div class="light-distribution">
+    <h6>光占比</h6>
+    <div class="distribution-chart">
+      <!-- 使用ECharts绘制饼图 -->
+      <canvas id="lightDistributionChart"></canvas>
+    </div>
+    <div class="distribution-legend">
+      <div class="legend-item">
+        <span class="color natural"></span>
+        <span class="label">自然光</span>
+        <span class="value">{{ naturalLight }}%</span>
+      </div>
+      <div class="legend-item">
+        <span class="color artificial"></span>
+        <span class="label">人工光</span>
+        <span class="value">{{ artificialLight }}%</span>
+      </div>
+      <div class="legend-item">
+        <span class="color ambient"></span>
+        <span class="label">环境光</span>
+        <span class="value">{{ ambientLight }}%</span>
+      </div>
+    </div>
+  </div>
+  
+  <!-- 光质分析 -->
+  <div class="light-quality">
+    <h6>光质</h6>
+    <div class="quality-metrics">
+      <div class="metric-item">
+        <span class="label">柔和度</span>
+        <div class="metric-bar">
+          <div class="fill" [style.width.%]="softness"></div>
+        </div>
+        <span class="value">{{ softness }}%</span>
+      </div>
+      <div class="metric-item">
+        <span class="label">漫射度</span>
+        <div class="metric-bar">
+          <div class="fill" [style.width.%]="diffusion"></div>
+        </div>
+        <span class="value">{{ diffusion }}%</span>
+      </div>
+      <div class="metric-item">
+        <span class="label">方向性</span>
+        <div class="metric-bar">
+          <div class="fill" [style.width.%]="directionality"></div>
+        </div>
+        <span class="value">{{ directionality }}%</span>
+      </div>
+    </div>
+  </div>
+  
+  <!-- 色温与情绪 -->
+  <div class="color-temperature-mood">
+    <div class="temperature">
+      <span class="label">色温</span>
+      <div class="temp-bar" [style.background]="tempGradient"></div>
+      <span class="value">{{ colorTemp }}K</span>
+    </div>
+    <div class="mood">
+      <span class="label">灯光情绪</span>
+      <span class="mood-tag">{{ lightingMood }}</span>
+    </div>
+  </div>
+</div>
+```
+
+**3. 服务层扩展**
+
+修改 `src/app/shared/services/lighting-analysis.service.ts`:
+
+```typescript
+// 新增光比计算方法
+calculateLightingRatio(image: File): Observable<LightingRatioResult> {
+  // 分析图像的亮度分布
+  // 识别主光源和补光源
+  // 计算光比
+}
+
+// 新增光占比计算方法
+calculateLightDistribution(lightingData: any): LightDistribution {
+  // 根据光源识别结果计算各类光源占比
+}
+```
+
+---
+
+### A6. 全展示模式 🔴 高优先级
+
+#### 当前问题
+- 分析结果折叠显示,用户需要点击展开
+- 无法一次性查看所有素材的完整分析
+- 核心要求是"完全展示所有采集的参考信息"
+
+#### 优化方案
+
+**1. 展开/折叠控制**
+
+```html
+<div class="materials-list">
+  <div class="list-header">
+    <h5>已上传素材</h5>
+    <div class="view-controls">
+      <button class="btn-ghost" 
+              [class.active]="viewMode === 'compact'"
+              (click)="setViewMode('compact')">
+        精简视图
+      </button>
+      <button class="btn-ghost" 
+              [class.active]="viewMode === 'expanded'"
+              (click)="setViewMode('expanded')">
+        完整展示
+      </button>
+      <button class="btn-ghost" (click)="expandAll()">全部展开</button>
+      <button class="btn-ghost" (click)="collapseAll()">全部折叠</button>
+    </div>
+  </div>
+  
+  <div class="material-cards" [class]="viewMode">
+    <!-- 素材卡片 -->
+  </div>
+</div>
+```
+
+**2. 完整展示模式布局**
+
+```scss
+.material-cards.expanded {
+  .material-card {
+    .analysis-section {
+      display: block !important; // 强制展开所有分析部分
+      
+      .color-wheel-visualizer,
+      .form-analysis-section,
+      .texture-analysis-section,
+      .pattern-analysis-section,
+      .lighting-analysis-section {
+        max-height: none;
+        overflow: visible;
+      }
+    }
+  }
+}
+```
+
+**3. 打印/导出功能**
+
+```typescript
+// 新增导出分析报告功能
+exportAnalysisReport(materialId: string, format: 'pdf' | 'excel' | 'json') {
+  const material = this.materials.find(m => m.id === materialId);
+  // 生成完整的分析报告
+}
+
+// 批量导出所有素材
+exportAllMaterials() {
+  // 导出所有素材的完整分析数据
+}
+```
+
+---
+
+## B. 需求映射调整 🟡 中优先级
+
+### 当前问题
+- 需求映射作为独立标签页展示给用户
+- 文档要求:应该是隐藏功能,在后台自动生成
+
+### 优化方案
+
+**1. 移除"需求映射"标签页**
+
+修改 `requirements-confirm-card.html`:
+
+```html
+<!-- 移除需求映射标签按钮 -->
+<div class="tab-navigation">
+  <button class="tab-button" [class.active]="activeTab === 'materials'">素材解析</button>
+  <!-- <button class="tab-button" [class.active]="activeTab === 'mapping'">需求映射</button> --> 
+  <!-- 移除此标签 -->
+  <button class="tab-button" [class.active]="activeTab === 'collaboration'">协作验证</button>
+  <button class="tab-button" [class.active]="activeTab === 'progress'">进度管理</button>
+</div>
+```
+
+**2. 自动触发映射机制**
+
+```typescript
+// 在素材分析完成后自动触发
+private onMaterialAnalysisComplete(material: MaterialFile): void {
+  // 检查是否所有素材都已分析完成
+  const allAnalyzed = this.materials.every(m => m.analysis);
+  
+  if (allAnalyzed) {
+    console.log('所有素材分析完成,自动触发需求映射');
+    this.triggerBackgroundMapping();
+  }
+}
+
+// 后台映射生成
+private triggerBackgroundMapping(): void {
+  this.isGeneratingMapping = true;
+  
+  // 收集所有分析结果
+  const analysisResults = this.materials
+    .filter(m => m.type === 'image')
+    .map(m => this.convertToColorAnalysisResult(m));
+  
+  // 自动生成需求映射(不显示给用户)
+  this.requirementMappingService.generateRequirementMapping(
+    this.mergeAnalysisResults(analysisResults),
+    SceneTemplate.LIVING_ROOM_MODERN
+  ).subscribe({
+    next: (mapping) => {
+      this.requirementMapping = mapping;
+      this.isGeneratingMapping = false;
+      
+      // 静默保存,不弹窗提示
+      console.log('需求映射已自动生成:', mapping);
+      
+      // 发送映射数据到父组件(用于方案确认阶段展示)
+      this.emitMappingDataUpdate();
+    },
+    error: (error) => {
+      console.error('自动映射生成失败:', error);
+      this.isGeneratingMapping = false;
+    }
+  });
+}
+```
+
+**3. 保留数据接口**
+
+```typescript
+// 保留映射数据的访问接口,供其他模块使用
+public getMappingData(): RequirementMapping | null {
+  return this.requirementMapping;
+}
+
+// 提供重新生成映射的方法(调试用)
+public regenerateMapping(): void {
+  this.triggerBackgroundMapping();
+}
+```
+
+**4. 在开发模式下保留可见性(可选)**
+
+```typescript
+// 环境配置
+showMappingTab = environment.production ? false : true;
+```
+
+```html
+<button class="tab-button" 
+        *ngIf="showMappingTab"
+        [class.active]="activeTab === 'mapping'">
+  需求映射 (调试)
+</button>
+```
+
+---
+
+## C. 协作验证重构 🔴 高优先级(核心)
+
+这是最重要的优化模块,需要完全重构当前的协作验证功能。
+
+### C1. 五维验证模块设计
+
+#### 当前问题
+- 只有通用的需求列表(色彩氛围、空间布局、材质选择)
+- 没有按照五个维度(色彩、形体、质感、纹理、灯光)分类
+- 无法针对特定维度进行深度验证
+
+#### 优化方案
+
+**1. 数据模型重构**
+
+```typescript
+// 新建接口:src/app/models/collaboration-verification.interface.ts
+
+export interface DimensionVerification {
+  id: string;
+  dimension: 'color' | 'form' | 'texture' | 'pattern' | 'lighting';
+  dimensionName: string;
+  status: 'pending' | 'in-review' | 'confirmed' | 'rejected';
+  
+  // 关联的素材
+  linkedMaterials: LinkedMaterial[];
+  
+  // 验证项
+  verificationItems: VerificationItem[];
+  
+  // 标注
+  annotations: Annotation[];
+  
+  // 协作信息
+  assignedTo?: string;
+  comments: Comment[];
+  
+  // 区域(如果是区域细分)
+  areaId?: string;
+  
+  lastUpdated: Date;
+}
+
+export interface LinkedMaterial {
+  materialId: string;
+  materialType: 'image' | 'cad' | 'text';
+  materialName: string;
+  relevance: number; // 相关度 0-100
+  highlightAreas?: Array<{x: number; y: number; width: number; height: number}>;
+}
+
+export interface VerificationItem {
+  id: string;
+  title: string;
+  description: string;
+  status: 'pending' | 'confirmed' | 'rejected';
+  priority: 'high' | 'medium' | 'low';
+  tags: string[];
+}
+
+export interface Annotation {
+  id: string;
+  type: 'text' | 'arrow' | 'highlight' | 'circle' | 'rectangle';
+  content?: string;
+  position: {x: number; y: number};
+  size?: {width: number; height: number};
+  color: string;
+  author: string;
+  timestamp: Date;
+  linkedMaterialId: string;
+}
+```
+
+**2. 五维验证UI布局**
+
+```html
+<div class="collaboration-section-redesign">
+  <div class="section-header">
+    <h5>协作验证</h5>
+    <div class="dimension-selector">
+      <button class="dimension-tab" 
+              *ngFor="let dim of dimensions"
+              [class.active]="activeDimension === dim.key"
+              (click)="selectDimension(dim.key)">
+        <svg [innerHTML]="dim.icon"></svg>
+        <span>{{ dim.name }}</span>
+        <span class="status-badge" [class]="getDimensionStatus(dim.key)">
+          {{ getDimensionStatusText(dim.key) }}
+        </span>
+      </button>
+    </div>
+  </div>
+  
+  <!-- 当前维度的验证内容 -->
+  <div class="dimension-content" [ngSwitch]="activeDimension">
+    
+    <!-- 色彩验证 -->
+    <div *ngSwitchCase="'color'" class="color-verification">
+      <app-dimension-verification 
+        [dimension]="'color'"
+        [materials]="getColorRelatedMaterials()"
+        [verificationData]="colorVerificationData"
+        (materialLinked)="onMaterialLinked($event)"
+        (annotationAdded)="onAnnotationAdded($event)"
+        (statusChanged)="onVerificationStatusChanged($event)">
+      </app-dimension-verification>
+    </div>
+    
+    <!-- 形体验证 -->
+    <div *ngSwitchCase="'form'" class="form-verification">
+      <app-dimension-verification 
+        [dimension]="'form'"
+        [materials]="getFormRelatedMaterials()"
+        [verificationData]="formVerificationData">
+      </app-dimension-verification>
+    </div>
+    
+    <!-- 质感验证 -->
+    <div *ngSwitchCase="'texture'" class="texture-verification">
+      <app-dimension-verification 
+        [dimension]="'texture'"
+        [materials]="getTextureRelatedMaterials()"
+        [verificationData]="textureVerificationData">
+      </app-dimension-verification>
+    </div>
+    
+    <!-- 纹理验证 -->
+    <div *ngSwitchCase="'pattern'" class="pattern-verification">
+      <app-dimension-verification 
+        [dimension]="'pattern'"
+        [materials]="getPatternRelatedMaterials()"
+        [verificationData]="patternVerificationData">
+      </app-dimension-verification>
+    </div>
+    
+    <!-- 灯光验证 -->
+    <div *ngSwitchCase="'lighting'" class="lighting-verification">
+      <app-dimension-verification 
+        [dimension]="'lighting'"
+        [materials]="getLightingRelatedMaterials()"
+        [verificationData]="lightingVerificationData">
+      </app-dimension-verification>
+    </div>
+  </div>
+</div>
+```
+
+**3. 新建维度验证组件**
+
+创建 `src/app/shared/components/dimension-verification/`
+
+```typescript
+@Component({
+  selector: 'app-dimension-verification',
+  templateUrl: './dimension-verification.component.html',
+  styleUrls: ['./dimension-verification.component.scss']
+})
+export class DimensionVerificationComponent implements OnInit {
+  @Input() dimension!: 'color' | 'form' | 'texture' | 'pattern' | 'lighting';
+  @Input() materials: MaterialFile[] = [];
+  @Input() verificationData?: DimensionVerification;
+  
+  @Output() materialLinked = new EventEmitter<LinkedMaterial>();
+  @Output() annotationAdded = new EventEmitter<Annotation>();
+  @Output() statusChanged = new EventEmitter<{dimension: string; status: string}>();
+  
+  // 组件逻辑...
+}
+```
+
+---
+
+### C2. 素材关联系统
+
+#### 功能设计
+
+**1. 拖拽关联**
+
+```html
+<div class="material-linking-panel">
+  <div class="source-materials">
+    <h6>素材库</h6>
+    <div class="material-list" 
+         cdkDropList
+         #materialList="cdkDropList"
+         [cdkDropListData]="availableMaterials"
+         [cdkDropListConnectedTo]="[linkedList]">
+      
+      <div class="material-item" 
+           *ngFor="let material of availableMaterials"
+           cdkDrag>
+        <img [src]="material.url" [alt]="material.name">
+        <span>{{ material.name }}</span>
+      </div>
+    </div>
+  </div>
+  
+  <div class="linked-materials">
+    <h6>已关联素材 - {{ dimensionName }}</h6>
+    <div class="linked-list"
+         cdkDropList
+         #linkedList="cdkDropList"
+         [cdkDropListData]="linkedMaterials"
+         [cdkDropListConnectedTo]="[materialList]"
+         (cdkDropListDropped)="onMaterialDropped($event)">
+      
+      <div class="linked-item" 
+           *ngFor="let linked of linkedMaterials"
+           cdkDrag>
+        <img [src]="linked.materialUrl" [alt]="linked.materialName">
+        <div class="linked-info">
+          <span class="name">{{ linked.materialName }}</span>
+          <span class="relevance">相关度: {{ linked.relevance }}%</span>
+        </div>
+        <button class="btn-remove" (click)="unlinkMaterial(linked.id)">
+          <svg><!-- 删除图标 --></svg>
+        </button>
+      </div>
+    </div>
+  </div>
+</div>
+```
+
+**2. 下拉选择关联**
+
+```html
+<div class="material-selector">
+  <label>关联参考图:</label>
+  <select (change)="linkMaterial($event.target.value)">
+    <option value="">-- 选择素材 --</option>
+    <option *ngFor="let material of availableMaterials" 
+            [value]="material.id">
+      {{ material.name }}
+    </option>
+  </select>
+</div>
+```
+
+**3. 关联逻辑实现**
+
+```typescript
+onMaterialDropped(event: CdkDragDrop<MaterialFile[]>) {
+  if (event.previousContainer === event.container) {
+    moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
+  } else {
+    transferArrayItem(
+      event.previousContainer.data,
+      event.container.data,
+      event.previousIndex,
+      event.currentIndex
+    );
+    
+    // 创建关联
+    const material = event.container.data[event.currentIndex];
+    this.createMaterialLink(material);
+  }
+}
+
+private createMaterialLink(material: MaterialFile): void {
+  const linked: LinkedMaterial = {
+    materialId: material.id,
+    materialType: material.type,
+    materialName: material.name,
+    relevance: this.calculateRelevance(material), // 自动计算相关度
+    highlightAreas: []
+  };
+  
+  this.materialLinked.emit(linked);
+}
+
+// 计算素材与当前维度的相关度
+private calculateRelevance(material: MaterialFile): number {
+  // 根据维度和素材分析结果计算相关度
+  switch(this.dimension) {
+    case 'color':
+      return material.analysis?.enhancedColorAnalysis ? 90 : 50;
+    case 'form':
+      return material.analysis?.formAnalysis ? 85 : 40;
+    case 'texture':
+      return material.analysis?.textureAnalysis ? 88 : 45;
+    case 'pattern':
+      return material.analysis?.patternAnalysis ? 82 : 35;
+    case 'lighting':
+      return material.analysis?.lightingAnalysis ? 92 : 50;
+    default:
+      return 50;
+  }
+}
+```
+
+---
+
+### C3. 标注工具集成
+
+#### 功能设计
+
+**1. 标注工具栏**
+
+```html
+<div class="annotation-toolbar">
+  <button class="tool-btn" 
+          [class.active]="activeTool === 'text'"
+          (click)="selectTool('text')">
+    <svg><!-- 文本图标 --></svg>
+    文字
+  </button>
+  
+  <button class="tool-btn" 
+          [class.active]="activeTool === 'arrow'"
+          (click)="selectTool('arrow')">
+    <svg><!-- 箭头图标 --></svg>
+    箭头
+  </button>
+  
+  <button class="tool-btn" 
+          [class.active]="activeTool === 'highlight'"
+          (click)="selectTool('highlight')">
+    <svg><!-- 高亮图标 --></svg>
+    高亮
+  </button>
+  
+  <button class="tool-btn" 
+          [class.active]="activeTool === 'circle'"
+          (click)="selectTool('circle')">
+    <svg><!-- 圆圈图标 --></svg>
+    圆圈
+  </button>
+  
+  <button class="tool-btn" 
+          [class.active]="activeTool === 'rectangle'"
+          (click)="selectTool('rectangle')">
+    <svg><!-- 矩形图标 --></svg>
+    矩形
+  </button>
+  
+  <div class="color-picker">
+    <input type="color" 
+           [(ngModel)]="annotationColor"
+           title="标注颜色">
+  </div>
+  
+  <button class="tool-btn" (click)="clearAnnotations()">
+    <svg><!-- 清除图标 --></svg>
+    清除全部
+  </button>
+</div>
+```
+
+**2. 标注画布**
+
+```html
+<div class="annotation-canvas-container">
+  <div class="canvas-wrapper">
+    <!-- 底层:原始图片 -->
+    <img [src]="selectedMaterial.url" 
+         class="base-image"
+         #baseImage>
+    
+    <!-- 中层:SVG标注层 -->
+    <svg class="annotation-layer"
+         [attr.width]="canvasWidth"
+         [attr.height]="canvasHeight"
+         (mousedown)="startAnnotation($event)"
+         (mousemove)="continueAnnotation($event)"
+         (mouseup)="finishAnnotation($event)">
+      
+      <!-- 已有标注 -->
+      <g *ngFor="let annotation of annotations" 
+         [attr.data-id]="annotation.id"
+         class="annotation-item"
+         (click)="selectAnnotation(annotation)">
+        
+        <!-- 文字标注 -->
+        <text *ngIf="annotation.type === 'text'"
+              [attr.x]="annotation.position.x"
+              [attr.y]="annotation.position.y"
+              [attr.fill]="annotation.color">
+          {{ annotation.content }}
+        </text>
+        
+        <!-- 箭头标注 -->
+        <line *ngIf="annotation.type === 'arrow'"
+              [attr.x1]="annotation.start.x"
+              [attr.y1]="annotation.start.y"
+              [attr.x2]="annotation.end.x"
+              [attr.y2]="annotation.end.y"
+              [attr.stroke]="annotation.color"
+              stroke-width="2"
+              marker-end="url(#arrowhead)">
+        </line>
+        
+        <!-- 高亮标注 -->
+        <rect *ngIf="annotation.type === 'highlight'"
+              [attr.x]="annotation.position.x"
+              [attr.y]="annotation.position.y"
+              [attr.width]="annotation.size.width"
+              [attr.height]="annotation.size.height"
+              [attr.fill]="annotation.color"
+              opacity="0.3">
+        </rect>
+        
+        <!-- 圆圈标注 -->
+        <circle *ngIf="annotation.type === 'circle'"
+                [attr.cx]="annotation.position.x"
+                [attr.cy]="annotation.position.y"
+                [attr.r]="annotation.radius"
+                [attr.stroke]="annotation.color"
+                stroke-width="2"
+                fill="none">
+        </circle>
+        
+        <!-- 矩形标注 -->
+        <rect *ngIf="annotation.type === 'rectangle'"
+              [attr.x]="annotation.position.x"
+              [attr.y]="annotation.position.y"
+              [attr.width]="annotation.size.width"
+              [attr.height]="annotation.size.height"
+              [attr.stroke]="annotation.color"
+              stroke-width="2"
+              fill="none">
+        </rect>
+      </g>
+      
+      <!-- 箭头标记定义 -->
+      <defs>
+        <marker id="arrowhead" markerWidth="10" markerHeight="10" 
+                refX="5" refY="5" orient="auto">
+          <polygon points="0 0, 10 5, 0 10" [attr.fill]="annotationColor" />
+        </marker>
+      </defs>
+    </svg>
+  </div>
+  
+  <!-- 标注列表 -->
+  <div class="annotation-list">
+    <h6>标注列表</h6>
+    <div class="annotation-item-list">
+      <div class="list-item" 
+           *ngFor="let annotation of annotations"
+           [class.selected]="selectedAnnotation?.id === annotation.id"
+           (click)="selectAnnotation(annotation)">
+        <span class="type-icon">{{ getAnnotationIcon(annotation.type) }}</span>
+        <span class="content">{{ annotation.content || annotation.type }}</span>
+        <span class="author">{{ annotation.author }}</span>
+        <button class="btn-delete" (click)="deleteAnnotation(annotation.id)">
+          <svg><!-- 删除图标 --></svg>
+        </button>
+      </div>
+    </div>
+  </div>
+</div>
+```
+
+**3. 标注逻辑实现**
+
+```typescript
+export class AnnotationToolComponent {
+  activeTool: 'text' | 'arrow' | 'highlight' | 'circle' | 'rectangle' | null = null;
+  annotationColor = '#FF0000';
+  annotations: Annotation[] = [];
+  isDrawing = false;
+  currentAnnotation: Partial<Annotation> | null = null;
+  
+  startAnnotation(event: MouseEvent): void {
+    if (!this.activeTool) return;
+    
+    this.isDrawing = true;
+    const rect = (event.target as HTMLElement).getBoundingClientRect();
+    const x = event.clientX - rect.left;
+    const y = event.clientY - rect.top;
+    
+    this.currentAnnotation = {
+      id: this.generateId(),
+      type: this.activeTool,
+      position: { x, y },
+      color: this.annotationColor,
+      author: this.getCurrentUser(),
+      timestamp: new Date()
+    };
+  }
+  
+  continueAnnotation(event: MouseEvent): void {
+    if (!this.isDrawing || !this.currentAnnotation) return;
+    
+    const rect = (event.target as HTMLElement).getBoundingClientRect();
+    const x = event.clientX - rect.left;
+    const y = event.clientY - rect.top;
+    
+    // 根据工具类型更新标注
+    if (this.activeTool === 'arrow') {
+      this.currentAnnotation.end = { x, y };
+    } else if (['highlight', 'rectangle'].includes(this.activeTool!)) {
+      this.currentAnnotation.size = {
+        width: x - this.currentAnnotation.position!.x,
+        height: y - this.currentAnnotation.position!.y
+      };
+    } else if (this.activeTool === 'circle') {
+      const dx = x - this.currentAnnotation.position!.x;
+      const dy = y - this.currentAnnotation.position!.y;
+      this.currentAnnotation.radius = Math.sqrt(dx * dx + dy * dy);
+    }
+  }
+  
+  finishAnnotation(event: MouseEvent): void {
+    if (!this.isDrawing || !this.currentAnnotation) return;
+    
+    this.isDrawing = false;
+    
+    // 如果是文字工具,弹出输入框
+    if (this.activeTool === 'text') {
+      const text = prompt('请输入标注内容:');
+      if (text) {
+        this.currentAnnotation.content = text;
+        this.annotations.push(this.currentAnnotation as Annotation);
+        this.annotationAdded.emit(this.currentAnnotation as Annotation);
+      }
+    } else {
+      this.annotations.push(this.currentAnnotation as Annotation);
+      this.annotationAdded.emit(this.currentAnnotation as Annotation);
+    }
+    
+    this.currentAnnotation = null;
+  }
+  
+  deleteAnnotation(id: string): void {
+    const index = this.annotations.findIndex(a => a.id === id);
+    if (index !== -1) {
+      this.annotations.splice(index, 1);
+    }
+  }
+  
+  clearAnnotations(): void {
+    if (confirm('确定清除所有标注吗?')) {
+      this.annotations = [];
+    }
+  }
+}
+```
+
+---
+
+### C4. 区域细分管理
+
+#### 功能设计
+
+**1. 区域管理面板**
+
+```html
+<div class="area-management-panel">
+  <div class="panel-header">
+    <h5>区域细分管理</h5>
+    <button class="btn-primary" (click)="addNewArea()">
+      <svg><!-- 添加图标 --></svg>
+      新增区域
+    </button>
+  </div>
+  
+  <!-- 整体方案 -->
+  <div class="overall-plan" 
+       [class.active]="selectedArea === null"
+       (click)="selectArea(null)">
+    <div class="area-header">
+      <svg><!-- 整体图标 --></svg>
+      <h6>整体方案</h6>
+      <span class="area-status" [class]="overallStatus">{{ getStatusText(overallStatus) }}</span>
+    </div>
+    <div class="area-summary">
+      <span>默认统一风格设计</span>
+    </div>
+  </div>
+  
+  <!-- 细分区域列表 -->
+  <div class="area-list">
+    <div class="area-item" 
+         *ngFor="let area of areas"
+         [class.active]="selectedArea?.id === area.id"
+         (click)="selectArea(area)">
+      
+      <div class="area-header">
+        <div class="area-icon" [style.background]="area.color">
+          {{ area.icon }}
+        </div>
+        <div class="area-info">
+          <h6>{{ area.name }}</h6>
+          <span class="area-style">{{ area.styleDescription }}</span>
+        </div>
+        <span class="area-status" [class]="area.status">
+          {{ getStatusText(area.status) }}
+        </span>
+      </div>
+      
+      <div class="area-details">
+        <div class="detail-item">
+          <span class="label">责任人:</span>
+          <span class="value">{{ area.assignedTo || '未分配' }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="label">五维验证:</span>
+          <span class="value">{{ getCompletedDimensions(area) }}/5</span>
+        </div>
+      </div>
+      
+      <div class="area-actions">
+        <button class="btn-ghost btn-xs" (click)="editArea(area); $event.stopPropagation()">
+          编辑
+        </button>
+        <button class="btn-ghost btn-xs" (click)="deleteArea(area.id); $event.stopPropagation()">
+          删除
+        </button>
+      </div>
+    </div>
+  </div>
+</div>
+```
+
+**2. 区域数据模型**
+
+```typescript
+export interface Area {
+  id: string;
+  name: string;
+  icon: string;              // emoji或图标
+  color: string;             // 区域标识颜色
+  styleDescription: string;  // 风格描述
+  status: 'pending' | 'in-progress' | 'completed';
+  
+  // 分工
+  assignedTo?: string;       // 责任人ID
+  assignedRole?: string;     // 角色
+  
+  // 五维验证数据
+  dimensionVerifications: {
+    color?: DimensionVerification;
+    form?: DimensionVerification;
+    texture?: DimensionVerification;
+    pattern?: DimensionVerification;
+    lighting?: DimensionVerification;
+  };
+  
+  // 特殊需求
+  specialRequirements?: string[];
+  
+  // 参考素材(区域专属)
+  dedicatedMaterials?: string[]; // 素材ID列表
+  
+  createdAt: Date;
+  updatedAt: Date;
+}
+```
+
+**3. 区域添加/编辑对话框**
+
+```html
+<div class="area-dialog-overlay" *ngIf="showAreaDialog">
+  <div class="area-dialog">
+    <div class="dialog-header">
+      <h5>{{ editingArea ? '编辑区域' : '新增区域' }}</h5>
+      <button class="btn-close" (click)="closeAreaDialog()">×</button>
+    </div>
+    
+    <div class="dialog-body">
+      <form [formGroup]="areaForm">
+        <!-- 区域名称 -->
+        <div class="form-group">
+          <label>区域名称</label>
+          <input type="text" 
+                 formControlName="name"
+                 placeholder="如:客厅、婴儿房、主卧等">
+        </div>
+        
+        <!-- 预设选择 -->
+        <div class="form-group">
+          <label>选择预设</label>
+          <div class="preset-grid">
+            <button type="button" 
+                    class="preset-btn"
+                    *ngFor="let preset of areaPresets"
+                    [class.selected]="selectedPreset?.id === preset.id"
+                    (click)="applyPreset(preset)">
+              <span class="preset-icon">{{ preset.icon }}</span>
+              <span class="preset-name">{{ preset.name }}</span>
+            </button>
+          </div>
+        </div>
+        
+        <!-- 风格描述 -->
+        <div class="form-group">
+          <label>风格描述</label>
+          <textarea formControlName="styleDescription"
+                    rows="3"
+                    placeholder="如:黑白灰很酷的现代风格 或 色彩更儿童化的温馨空间"></textarea>
+        </div>
+        
+        <!-- 标识颜色 -->
+        <div class="form-group">
+          <label>标识颜色</label>
+          <input type="color" formControlName="color">
+        </div>
+        
+        <!-- 责任人分配 -->
+        <div class="form-group">
+          <label>责任人</label>
+          <select formControlName="assignedTo">
+            <option value="">-- 未分配 --</option>
+            <option *ngFor="let user of availableUsers" [value]="user.id">
+              {{ user.name }} ({{ user.role }})
+            </option>
+          </select>
+        </div>
+        
+        <!-- 特殊需求 -->
+        <div class="form-group">
+          <label>特殊需求</label>
+          <div class="requirement-tags">
+            <input type="text" 
+                   #requirementInput
+                   placeholder="按Enter添加需求标签"
+                   (keyup.enter)="addRequirement(requirementInput.value); requirementInput.value = ''">
+            <div class="tags-list">
+              <span class="tag" *ngFor="let req of specialRequirements; let i = index">
+                {{ req }}
+                <button class="remove" (click)="removeRequirement(i)">×</button>
+              </span>
+            </div>
+          </div>
+        </div>
+      </form>
+    </div>
+    
+    <div class="dialog-footer">
+      <button class="btn-secondary" (click)="closeAreaDialog()">取消</button>
+      <button class="btn-primary" (click)="saveArea()">保存</button>
+    </div>
+  </div>
+</div>
+```
+
+**4. 区域预设配置**
+
+```typescript
+const areaPresets: AreaPreset[] = [
+  {
+    id: 'living-room',
+    name: '客厅',
+    icon: '🛋️',
+    color: '#4A90E2',
+    styleDescription: '现代简约,明亮开阔',
+    defaultRequirements: ['开放式布局', '充足采光', '舒适氛围']
+  },
+  {
+    id: 'baby-room',
+    name: '婴儿房',
+    icon: '👶',
+    color: '#FFB6C1',
+    styleDescription: '色彩丰富,童趣温馨',
+    defaultRequirements: ['安全环保', '柔和色彩', '储物充足', '易于清洁']
+  },
+  {
+    id: 'master-bedroom',
+    name: '主卧',
+    icon: '🛏️',
+    color: '#9B59B6',
+    styleDescription: '宁静优雅,私密舒适',
+    defaultRequirements: ['隔音效果', '柔和灯光', '收纳空间']
+  },
+  {
+    id: 'kitchen',
+    name: '厨房',
+    icon: '🍳',
+    color: '#E67E22',
+    styleDescription: '实用高效,易于清洁',
+    defaultRequirements: ['动线合理', '台面充足', '通风良好', '防油防水']
+  },
+  {
+    id: 'bathroom',
+    name: '卫生间',
+    icon: '🚿',
+    color: '#3498DB',
+    styleDescription: '清新明亮,干湿分离',
+    defaultRequirements: ['防水防滑', '通风除湿', '储物空间']
+  },
+  {
+    id: 'study',
+    name: '书房',
+    icon: '📚',
+    color: '#27AE60',
+    styleDescription: '安静专注,书香氛围',
+    defaultRequirements: ['采光充足', '隔音效果', '书柜充足']
+  },
+  {
+    id: 'dining',
+    name: '餐厅',
+    icon: '🍽️',
+    color: '#F39C12',
+    styleDescription: '温馨舒适,聚餐氛围',
+    defaultRequirements: ['空间适中', '照明充足', '易于清洁']
+  },
+  {
+    id: 'custom',
+    name: '自定义',
+    icon: '✏️',
+    color: '#95A5A6',
+    styleDescription: '',
+    defaultRequirements: []
+  }
+];
+```
+
+**5. 区域切换与对比视图**
+
+```html
+<div class="area-comparison-view">
+  <div class="comparison-header">
+    <h5>区域参数对比</h5>
+    <button class="btn-ghost" (click)="exportComparison()">导出对比表</button>
+  </div>
+  
+  <table class="comparison-table">
+    <thead>
+      <tr>
+        <th>维度/区域</th>
+        <th *ngFor="let area of areas">{{ area.name }}</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>色彩</td>
+        <td *ngFor="let area of areas">
+          <span class="status-badge" [class]="area.dimensionVerifications.color?.status">
+            {{ getStatusText(area.dimensionVerifications.color?.status) }}
+          </span>
+        </td>
+      </tr>
+      <tr>
+        <td>形体</td>
+        <td *ngFor="let area of areas">
+          <span class="status-badge" [class]="area.dimensionVerifications.form?.status">
+            {{ getStatusText(area.dimensionVerifications.form?.status) }}
+          </span>
+        </td>
+      </tr>
+      <tr>
+        <td>质感</td>
+        <td *ngFor="let area of areas">
+          <span class="status-badge" [class]="area.dimensionVerifications.texture?.status">
+            {{ getStatusText(area.dimensionVerifications.texture?.status) }}
+          </span>
+        </td>
+      </tr>
+      <tr>
+        <td>纹理</td>
+        <td *ngFor="let area of areas">
+          <span class="status-badge" [class]="area.dimensionVerifications.pattern?.status">
+            {{ getStatusText(area.dimensionVerifications.pattern?.status) }}
+          </span>
+        </td>
+      </tr>
+      <tr>
+        <td>灯光</td>
+        <td *ngFor="let area of areas">
+          <span class="status-badge" [class]="area.dimensionVerifications.lighting?.status">
+            {{ getStatusText(area.dimensionVerifications.lighting?.status) }}
+          </span>
+        </td>
+      </tr>
+    </tbody>
+  </table>
+</div>
+```
+
+---
+
+### C5. 分工和人员系统
+
+#### 功能设计
+
+**1. 人员角色定义**
+
+```typescript
+export interface CollaborationUser {
+  id: string;
+  name: string;
+  role: 'designer' | 'customer-service' | 'team-leader' | 'client';
+  avatar?: string;
+  permissions: {
+    canEditColor: boolean;
+    canEditForm: boolean;
+    canEditTexture: boolean;
+    canEditPattern: boolean;
+    canEditLighting: boolean;
+    canApprove: boolean;
+  };
+}
+```
+
+**2. 分工面板**
+
+```html
+<div class="collaboration-assignment-panel">
+  <h6>分工管理</h6>
+  
+  <!-- 按维度分工 -->
+  <div class="dimension-assignments">
+    <div class="assignment-row" *ngFor="let dimension of dimensions">
+      <span class="dimension-name">{{ dimension.name }}</span>
+      <select [(ngModel)]="dimensionAssignments[dimension.key]"
+              (change)="updateAssignment(dimension.key, $event.target.value)">
+        <option value="">-- 未分配 --</option>
+        <option *ngFor="let user of eligibleUsers[dimension.key]" 
+                [value]="user.id">
+          {{ user.name }} ({{ user.role }})
+        </option>
+      </select>
+      <span class="assignment-status">
+        {{ getAssignmentStatus(dimension.key) }}
+      </span>
+    </div>
+  </div>
+  
+  <!-- 按区域分工 -->
+  <div class="area-assignments" *ngIf="areas.length > 0">
+    <h6>区域责任人</h6>
+    <div class="assignment-row" *ngFor="let area of areas">
+      <span class="area-name">{{ area.name }}</span>
+      <select [(ngModel)]="area.assignedTo"
+              (change)="updateAreaAssignment(area.id, $event.target.value)">
+        <option value="">-- 未分配 --</option>
+        <option *ngFor="let user of availableUsers" 
+                [value]="user.id">
+          {{ user.name }} ({{ user.role }})
+        </option>
+      </select>
+    </div>
+  </div>
+  
+  <!-- 协作状态跟踪 -->
+  <div class="collaboration-tracking">
+    <h6>协作状态</h6>
+    <div class="tracking-grid">
+      <div class="tracking-item" *ngFor="let user of activeUsers">
+        <div class="user-avatar">
+          <img [src]="user.avatar || defaultAvatar" [alt]="user.name">
+          <span class="online-indicator" *ngIf="user.isOnline"></span>
+        </div>
+        <div class="user-info">
+          <span class="name">{{ user.name }}</span>
+          <span class="role">{{ getRoleName(user.role) }}</span>
+        </div>
+        <div class="user-tasks">
+          <span class="task-count">{{ getUserTaskCount(user.id) }} 项任务</span>
+          <span class="completion">{{ getUserCompletion(user.id) }}% 完成</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+```
+
+**3. 审批流程**
+
+```html
+<div class="approval-workflow">
+  <h6>审批流程</h6>
+  
+  <div class="workflow-steps">
+    <div class="step" 
+         *ngFor="let step of approvalSteps; let i = index"
+         [class.completed]="step.status === 'completed'"
+         [class.active]="step.status === 'in-progress'">
+      
+      <div class="step-number">{{ i + 1 }}</div>
+      <div class="step-content">
+        <h6>{{ step.title }}</h6>
+        <p>{{ step.description }}</p>
+        
+        <div class="step-assignee">
+          <span class="label">审批人:</span>
+          <span class="value">{{ step.assignee?.name || '待分配' }}</span>
+        </div>
+        
+        <div class="step-actions" *ngIf="step.status === 'in-progress' && canApprove(step)">
+          <button class="btn-success btn-sm" (click)="approveStep(step.id)">
+            通过
+          </button>
+          <button class="btn-danger btn-sm" (click)="rejectStep(step.id)">
+            驳回
+          </button>
+        </div>
+        
+        <div class="step-result" *ngIf="step.status === 'completed'">
+          <span class="result-badge" [class]="step.result">
+            {{ step.result === 'approved' ? '已通过' : '已驳回' }}
+          </span>
+          <span class="result-time">{{ step.completedAt | date:'MM-dd HH:mm' }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+```
+
+**4. 审批逻辑**
+
+```typescript
+interface ApprovalStep {
+  id: string;
+  title: string;
+  description: string;
+  assignee?: CollaborationUser;
+  status: 'pending' | 'in-progress' | 'completed';
+  result?: 'approved' | 'rejected';
+  completedAt?: Date;
+  comments?: string;
+}
+
+const approvalSteps: ApprovalStep[] = [
+  {
+    id: 'dimension-verification',
+    title: '五维验证审核',
+    description: '设计师完成五个维度的验证',
+    status: 'pending'
+  },
+  {
+    id: 'customer-service-review',
+    title: '客服审核',
+    description: '客服确认需求理解正确',
+    status: 'pending'
+  },
+  {
+    id: 'team-leader-approval',
+    title: '组长审批',
+    description: '组长审批整体方案',
+    status: 'pending'
+  },
+  {
+    id: 'client-confirmation',
+    title: '客户确认',
+    description: '客户最终确认',
+    status: 'pending'
+  }
+];
+
+approveStep(stepId: string): void {
+  const step = this.approvalSteps.find(s => s.id === stepId);
+  if (step) {
+    step.status = 'completed';
+    step.result = 'approved';
+    step.completedAt = new Date();
+    
+    // 自动启动下一步
+    const nextStepIndex = this.approvalSteps.indexOf(step) + 1;
+    if (nextStepIndex < this.approvalSteps.length) {
+      this.approvalSteps[nextStepIndex].status = 'in-progress';
+    }
+    
+    // 检查是否所有步骤都完成
+    const allApproved = this.approvalSteps.every(s => s.result === 'approved');
+    if (allApproved) {
+      this.onAllStepsApproved();
+    }
+  }
+}
+
+rejectStep(stepId: string): void {
+  const step = this.approvalSteps.find(s => s.id === stepId);
+  if (step) {
+    const reason = prompt('请输入驳回原因:');
+    if (reason) {
+      step.status = 'completed';
+      step.result = 'rejected';
+      step.comments = reason;
+      step.completedAt = new Date();
+      
+      // 通知相关人员
+      this.notifyRejection(step);
+    }
+  }
+}
+```
+
+---
+
+## 4. 技术实施建议
+
+### 需要新增的组件
+
+```
+src/app/shared/components/
+├── color-wheel-visualizer/          # 色轮可视化组件
+│   ├── color-wheel-visualizer.component.ts
+│   ├── color-wheel-visualizer.component.html
+│   └── color-wheel-visualizer.component.scss
+│
+├── dimension-verification/          # 维度验证组件
+│   ├── dimension-verification.component.ts
+│   ├── dimension-verification.component.html
+│   └── dimension-verification.component.scss
+│
+├── annotation-tool/                 # 标注工具组件
+│   ├── annotation-tool.component.ts
+│   ├── annotation-tool.component.html
+│   └── annotation-tool.component.scss
+│
+├── area-management/                 # 区域管理组件
+│   ├── area-management.component.ts
+│   ├── area-management.component.html
+│   └── area-management.component.scss
+│
+├── furniture-form-selector/         # 软装形体选择器
+│   ├── furniture-form-selector.component.ts
+│   ├── furniture-form-selector.component.html
+│   └── furniture-form-selector.component.scss
+│
+├── texture-comparison/              # 质感对比组件
+│   ├── texture-comparison.component.ts
+│   ├── texture-comparison.component.html
+│   └── texture-comparison.component.scss
+│
+├── pattern-visualizer/              # 纹理可视化组件
+│   ├── pattern-visualizer.component.ts
+│   ├── pattern-visualizer.component.html
+│   └── pattern-visualizer.component.scss
+│
+└── lighting-distribution-chart/     # 光占比图表组件
+    ├── lighting-distribution-chart.component.ts
+    ├── lighting-distribution-chart.component.html
+    └── lighting-distribution-chart.component.scss
+```
+
+### 需要修改的现有文件
+
+**1. requirements-confirm-card.ts**
+- 移除需求映射标签页相关代码
+- 添加五维验证数据结构
+- 添加区域管理逻辑
+- 集成标注工具
+- 实现分工和审批流程
+
+**2. requirements-confirm-card.html**
+- 重构协作验证标签页
+- 增强素材分析展示
+- 添加区域管理UI
+- 集成新组件
+
+**3. requirements-confirm-card.scss**
+- 新增五维验证样式
+- 标注工具样式
+- 区域管理样式
+- 响应式布局优化
+
+### 数据模型调整
+
+**新建接口文件:**
+
+```
+src/app/models/
+├── collaboration-verification.interface.ts  # 协作验证相关接口
+├── annotation.interface.ts                  # 标注相关接口
+├── area-management.interface.ts             # 区域管理相关接口
+└── approval-workflow.interface.ts           # 审批流程相关接口
+```
+
+### API接口需求
+
+```typescript
+// 需要后端提供的API接口
+
+// 1. 灯光分析增强
+POST /api/analysis/lighting/ratio            // 计算光比
+POST /api/analysis/lighting/distribution     // 计算光占比
+
+// 2. 标注管理
+POST /api/annotations                         // 创建标注
+GET /api/annotations/:projectId              // 获取项目标注
+PUT /api/annotations/:id                     // 更新标注
+DELETE /api/annotations/:id                  // 删除标注
+
+// 3. 区域管理
+POST /api/areas                              // 创建区域
+GET /api/areas/:projectId                    // 获取项目区域
+PUT /api/areas/:id                           // 更新区域
+DELETE /api/areas/:id                        // 删除区域
+
+// 4. 分工管理
+POST /api/assignments                        // 分配任务
+GET /api/assignments/:projectId              // 获取项目分工
+PUT /api/assignments/:id                     // 更新分工
+
+// 5. 审批流程
+POST /api/approvals/:stepId/approve          // 审批通过
+POST /api/approvals/:stepId/reject           // 审批驳回
+GET /api/approvals/:projectId/status         // 获取审批状态
+```
+
+---
+
+## 5. 实施分阶段建议
+
+### 阶段1:素材分析增强(预计5-7天)
+
+**优先级:高**
+
+**任务清单:**
+- [ ] 创建色轮可视化组件
+- [ ] 实现软装形体选择器
+- [ ] 优化质感对比展示
+- [ ] 增强纹理图案可视化
+- [ ] 补充灯光分析(光比、光占比)
+- [ ] 实现全展示模式切换
+- [ ] 添加分析报告导出功能
+
+**验收标准:**
+- 色轮能正确显示色彩分布
+- 软装形体可选择并保存
+- 质感亮光/哑光对比清晰
+- 灯光分析包含光比和光占比数据
+- 全展示模式能展开所有分析细节
+
+---
+
+### 阶段2:需求映射隐藏(预计1-2天)
+
+**优先级:中**
+
+**任务清单:**
+- [ ] 移除需求映射标签页UI
+- [ ] 实现后台自动触发机制
+- [ ] 保留映射数据接口
+- [ ] 测试自动映射功能
+- [ ] 添加开发模式可见性(可选)
+
+**验收标准:**
+- 需求映射标签页不可见
+- 素材分析完成后自动生成映射
+- 映射数据正确传递给父组件
+- 不影响方案确认阶段的显示
+
+---
+
+### 阶段3:协作验证重构(预计10-14天)
+
+**优先级:高(核心)**
+
+**子阶段3.1:五维验证模块(3-4天)**
+- [ ] 创建维度验证数据模型
+- [ ] 实现五维验证UI布局
+- [ ] 创建dimension-verification组件
+- [ ] 实现维度切换逻辑
+- [ ] 添加验证状态管理
+
+**子阶段3.2:素材关联系统(2-3天)**
+- [ ] 实现拖拽关联功能(使用CDK Drag&Drop)
+- [ ] 实现下拉选择关联
+- [ ] 添加相关度计算
+- [ ] 实现素材高亮显示
+
+**子阶段3.3:标注工具集成(3-4天)**
+- [ ] 创建annotation-tool组件
+- [ ] 实现SVG标注层
+- [ ] 支持文字、箭头、高亮、圆圈、矩形标注
+- [ ] 实现标注保存和加载
+- [ ] 添加标注列表管理
+
+**子阶段3.4:区域细分管理(2-3天)**
+- [ ] 创建area-management组件
+- [ ] 实现区域添加/编辑对话框
+- [ ] 配置区域预设
+- [ ] 实现区域切换逻辑
+- [ ] 添加区域对比视图
+
+**子阶段3.5:分工和人员系统(2-3天)**
+- [ ] 定义用户角色和权限
+- [ ] 实现分工管理面板
+- [ ] 实现协作状态跟踪
+- [ ] 实现审批流程
+- [ ] 添加通知机制
+
+**验收标准:**
+- 五个维度可独立验证
+- 素材能正确关联到各维度
+- 标注工具功能完整且流畅
+- 区域可添加、编辑、删除
+- 分工和审批流程正常运行
+
+---
+
+## 6. 验收标准
+
+### 功能完整性验收
+
+**素材分析模块:**
+- ✅ 所有五个维度(色彩、形体、质感、纹理、灯光)都有完整的分析数据
+- ✅ 色轮可视化正确展示色彩分布
+- ✅ 灯光分析包含光比和光占比
+- ✅ 可切换精简视图和完整展示模式
+- ✅ 可导出分析报告(PDF/Excel/JSON)
+
+**协作验证模块:**
+- ✅ 五维验证模块独立可操作
+- ✅ 素材能通过拖拽或选择关联到维度
+- ✅ 标注工具支持5种标注类型
+- ✅ 区域可自由添加和管理
+- ✅ 分工分配正常,审批流程完整
+
+**系统集成:**
+- ✅ 需求映射在后台自动生成
+- ✅ 数据正确传递给父组件
+- ✅ 不影响其他模块功能
+
+### 用户体验标准
+
+**交互体验:**
+- 响应时间 < 200ms(标注工具除外)
+- 拖拽操作流畅无卡顿
+- 标注绘制实时反馈
+- 表单验证及时提示
+
+**视觉设计:**
+- UI风格与整体系统一致
+- 色彩搭配和谐
+- 图标清晰易识别
+- 布局合理不拥挤
+
+**易用性:**
+- 操作步骤明确,不超过3步
+- 提供操作提示和帮助文档
+- 支持键盘快捷键(标注工具)
+- 错误提示友好明确
+
+**性能要求:**
+- 素材列表加载 < 1s
+- 色轮渲染 < 500ms
+- 标注保存 < 300ms
+- 区域切换 < 200ms
+
+### 数据准确性
+
+- ✅ 分析数据与实际图片内容相符
+- ✅ 光比计算误差 < 10%
+- ✅ 色彩提取准确率 > 90%
+- ✅ 关联关系正确保存和加载
+
+### 兼容性
+
+- ✅ 支持Chrome、Edge、Firefox最新版本
+- ✅ 响应式设计适配1366px及以上分辨率
+- ✅ 支持触摸屏操作(标注工具)
+
+---
+
+## 7. 风险评估与建议
+
+### 潜在风险
+
+**技术风险:**
+1. **色轮可视化性能** - 大量颜色数据可能影响渲染性能
+   - 建议:使用Canvas替代SVG,实现虚拟滚动
+   
+2. **标注工具复杂度** - SVG操作和状态管理复杂
+   - 建议:使用成熟的库如Fabric.js或Konva.js
+   
+3. **拖拽关联稳定性** - CDK Drag&Drop可能有兼容性问题
+   - 建议:充分测试,准备降级方案
+
+**业务风险:**
+1. **区域细分粒度** - 过细可能导致操作复杂
+   - 建议:限制最大区域数量(如10个)
+   
+2. **审批流程僵化** - 固定流程可能不适合所有项目
+   - 建议:支持自定义审批流程
+
+### 优化建议
+
+**性能优化:**
+- 使用虚拟滚动加载大量素材
+- 图片懒加载和预加载策略
+- 标注数据分页加载
+- 实施增量更新策略
+
+**用户体验优化:**
+- 添加操作引导(首次使用)
+- 提供快捷键支持
+- 实现撤销/重做功能
+- 添加实时保存提示
+
+**扩展性建议:**
+- 标注工具支持插件扩展
+- 区域类型可配置
+- 审批流程可自定义
+- 支持第三方集成(如AI分析)
+
+---
+
+## 8. 总结
+
+本优化方案围绕三个核心目标:
+
+1. **素材分析完全展示** - 通过增强色彩、形体、质感、纹理、灯光五个维度的可视化和数据完整性,确保所有采集的参考信息都能充分展示。
+
+2. **需求映射后台化** - 将用户不需要直接操作的需求映射功能隐藏,改为自动触发,简化用户流程。
+
+3. **协作验证深度重构** - 这是最核心的改进,通过五维验证、素材关联、标注工具、区域细分、分工管理等功能,实现真正的协作式需求验证。
+
+实施建议采用分阶段推进,优先完成素材分析增强和协作验证重构,这两个是高优先级且用户价值最大的功能。
+
+---
+
+## 附录
+
+### A. 相关技术栈
+
+- **Angular**: v16+
+- **Angular CDK**: Drag&Drop模块
+- **D3.js**: 色轮可视化
+- **ECharts**: 图表绘制
+- **Fabric.js/Konva.js**: 标注工具(可选)
+- **RxJS**: 响应式数据流
+
+### B. 参考资源
+
+- [Angular CDK Drag and Drop](https://material.angular.io/cdk/drag-drop/overview)
+- [D3.js Color Wheel Tutorial](https://observablehq.com/@d3/color-wheel)
+- [ECharts Pie Chart](https://echarts.apache.org/examples/en/editor.html?c=pie-simple)
+- [Fabric.js Documentation](http://fabricjs.com/docs/)
+
+### C. 联系与支持
+
+如有疑问或需要进一步讨论,请联系:
+- 项目负责人:[待填写]
+- 技术负责人:[待填写]
+- 设计负责人:[待填写]
+
+---
+
+**文档版本**: v1.0  
+**创建日期**: {{ currentDate }}  
+**最后更新**: {{ currentDate }}  
+**状态**: 待审核
+

+ 136 - 79
src/app/pages/designer/project-detail/project-detail.html

@@ -1805,6 +1805,7 @@
                       } @else if (stage === '需求沟通') {
                         <!-- 需求沟通阶段:确认需求组件 -->
                         <app-requirements-confirm-card 
+                          #requirementsCard
                           (requirementConfirmed)="syncRequirementKeyInfo($event)"
                           (progressUpdated)="syncRequirementKeyInfo($event)"
                           (stageCompleted)="onRequirementsStageCompleted($event)"
@@ -1814,91 +1815,147 @@
                         </app-requirements-confirm-card>
                         
                       } @else if (stage === '方案确认') {
-                        <!-- 需求映射面板(替换原色彩分析报告区域) -->
-                        <div class="requirement-mapping-panel" style="width:100%; display:flex; flex-direction:column; gap:14px;">
-                          <h3 class="panel-title" style="margin:0 0 16px 0; font-size:18px; font-weight:700; color:#495057;">🎯 需求映射</h3>
-                          
-                          <div class="mapping-progress" style="background:#f8f9fa; border-radius:8px; padding:16px; margin-bottom:12px;">
-                            @if (mappingUploadedFiles.length > 0) {
-                              <div class="progress-badge" style="display:inline-block; padding:6px 14px; background:linear-gradient(135deg, #667eea 0%, #764ba2 100%); color:white; border-radius:14px; font-size:12px; font-weight:600; margin-bottom:12px;">
-                                📸 已同步 {{ mappingUploadedFiles.length }} 张参考图片
-                              </div>
-                            }
-                            
-                            <div class="steps-list" style="display:flex; flex-direction:column; gap:10px;">
-                              <div class="step-item" style="display:flex; align-items:center; gap:10px;">
-                                <span class="step-index" style="width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#e9ecef;color:#495057;font-size:12px;">1</span>
-                                <span class="step-title" style="flex:1;font-size:14px;">图片上传</span>
-                                <span class="step-status" style="font-size:12px;color:#666;">@if (mappingUploadedFiles.length > 0) { ✅ 完成 } @else { ⭕ 待上传 }</span>
-                              </div>
-                              <div class="step-item" style="display:flex; align-items:center; gap:10px;">
-                                <span class="step-index" style="width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#e9ecef;color:#495057;font-size:12px;">2</span>
-                                <span class="step-title" style="flex:1;font-size:14px;">图片分析</span>
-                                <span class="step-status" style="font-size:12px;color:#666;">@if (mappingIsAnalyzing) { ⏳ 进行中 } @else if (mappingAnalysisResult) { ✅ 完成 } @else { ⭕ 待开始 }</span>
-                              </div>
-                              <div class="step-item" style="display:flex; align-items:center; gap:10px;">
-                                <span class="step-index" style="width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:#e9ecef;color:#495057;font-size:12px;">3</span>
-                                <span class="step-title" style="flex:1;font-size:14px;">需求映射</span>
-                                <span class="step-status" style="font-size:12px;color:#666;">@if (mappingIsGeneratingMapping) { ⏳ 生成中 } @else if (mappingRequirementMapping) { ✅ 完成 } @else { ⭕ 待生成 }</span>
-                              </div>
-                            </div>
+                        <!-- 分析数据汇总面板(原需求映射面板) -->
+                        <div class="analysis-summary-section" style="max-height: calc(100vh - 200px); overflow-y: auto; overflow-x: hidden; padding: 20px;">
+                          <div class="section-header" style="margin-bottom: 24px; text-align: center;">
+                            <h4 style="font-size: 1.5rem; font-weight: 700; color: #2c3e50; margin-bottom: 8px;">📊 分析数据汇总</h4>
+                            <p class="section-description" style="color: #6c757d; font-size: 0.95rem;">查看所有素材的详细分析数据及可视化</p>
                           </div>
-                          
-                          @if (mappingAnalysisResult) {
-                            <div class="analysis-summary-panel" style="background:#f8f9fa;border-radius:8px;padding:16px;">
-                              <h4 style="margin:0 0 12px 0; font-size:15px; font-weight:600; color:#333;">图片分析摘要</h4>
-                              <div class="param-items" style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px;">
-                                <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">主色</span><span style="font-weight:500;">{{ mappingAnalysisResult.primaryColor?.hex || '未知' }}</span></div>
-                                <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">材质</span><span style="font-weight:500;">{{ getMaterialName(mappingAnalysisResult.materialType) }}</span></div>
-                                <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">灯光</span><span style="font-weight:500;">{{ getLightingMoodName(mappingAnalysisResult.lightingMood) }}</span></div>
-                                <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">对比</span><span style="font-weight:500;">{{ mappingAnalysisResult.contrast || '未知' }}</span></div>
-                              </div>
+
+                          @if (requirementsCard?.materials?.length === 0 || !requirementsCard?.materials) {
+                            <div class="empty-state" style="text-align: center; padding: 60px 20px;">
+                              <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="#dee2e6" stroke-width="1" style="margin-bottom: 20px;">
+                                <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
+                                <line x1="9" y1="9" x2="15" y2="9"></line>
+                                <line x1="9" y1="15" x2="15" y2="15"></line>
+                              </svg>
+                              <p style="color: #6c757d; font-size: 1rem; margin: 8px 0;">暂无素材分析数据</p>
+                              <p class="hint" style="color: #adb5bd; font-size: 0.9rem; margin: 8px 0;">请先在"需求沟通"阶段上传素材</p>
                             </div>
                           }
-                          
-                          @if (mappingRequirementMapping) {
-                            <div class="mapping-result-panel" style="background:#f8f9fa;border-radius:8px;padding:16px;">
-                              <h4 style="margin:0 0 12px 0; font-size:15px; font-weight:600; color:#333;">需求映射结果</h4>
-                              @if (mappingRequirementMapping.color) {
-                                <div class="param-section" style="margin-bottom:12px;">
-                                  <div class="section-title" style="font-weight:600;color:#555;margin-bottom:8px;font-size:13px;">色彩参数</div>
-                                  <div class="param-items" style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px;">
-                                    <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">和谐度</span><span style="font-weight:500;">{{ getColorHarmonyName(mappingRequirementMapping.color?.harmony) }}</span></div>
-                                    <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">色温</span><span style="font-weight:500;">{{ getTemperatureName(mappingRequirementMapping.color?.temperature) }}</span></div>
+
+                          @for (material of requirementsCard?.materials || []; track material.id) {
+                            @if (material.analysis && material.type === 'image') {
+                              <div class="material-analysis-card" style="background: white; border-radius: 12px; padding: 20px; margin-bottom: 24px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);">
+                                <div class="material-header" style="display: flex; align-items: center; gap: 16px; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 2px solid #f8f9fa;">
+                                  <img [src]="material.url" [alt]="material.name" class="material-thumbnail" style="width: 80px; height: 80px; object-fit: cover; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);">
+                                  <div class="material-info" style="flex: 1;">
+                                    <h5 style="font-size: 1.1rem; font-weight: 600; color: #2c3e50; margin-bottom: 4px;">{{ material.name }}</h5>
+                                    <span class="upload-time" style="font-size: 0.85rem; color: #6c757d;">{{ material.uploadTime | date: 'yyyy-MM-dd HH:mm' }}</span>
                                   </div>
                                 </div>
-                              }
-                              @if (mappingRequirementMapping.space) {
-                                <div class="param-section">
-                                  <div class="section-title" style="font-weight:600;color:#555;margin-bottom:8px;font-size:13px;">空间参数</div>
-                                  <div class="param-items" style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px;">
-                                    <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">布局</span><span style="font-weight:500;">{{ getLayoutTypeName(mappingRequirementMapping.space?.layoutType) }}</span></div>
-                                    <div class="param-row" style="font-size:13px;"><span style="color:#868e96;">流线</span><span style="font-weight:500;">{{ getFlowTypeName(mappingRequirementMapping.space?.flowType) }}</span></div>
-                                  </div>
+
+                                <div class="analysis-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px;">
+                                  <!-- 色彩分析卡片 -->
+                                  @if (material.analysis.enhancedColorAnalysis) {
+                                    <div class="analysis-card color-card" style="background: #f8f9fa; padding: 14px; border-radius: 10px; border: 1px solid #e9ecef;">
+                                      <h6 style="font-size: 0.95rem; font-weight: 600; margin-bottom: 12px; color: #495057;">🎨 色彩分析</h6>
+                                      @if (material.analysis.mainColors && material.analysis.mainColors.length > 0) {
+                                        <app-color-wheel-visualizer
+                                          [colors]="material.analysis.mainColors"
+                                          [size]="140"
+                                          [showLabels]="true"
+                                          [showPercentages]="true">
+                                        </app-color-wheel-visualizer>
+                                      }
+                                      @if (material.analysis.enhancedColorAnalysis.colorPsychology) {
+                                        <div class="psychology-tags" style="display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px;">
+                                          <span class="tag" style="padding: 4px 12px; background: white; border-radius: 20px; font-size: 0.8rem; color: #667eea; border: 1px solid #667eea;">{{ material.analysis.enhancedColorAnalysis.colorPsychology.primaryMood }}</span>
+                                          <span class="tag" style="padding: 4px 12px; background: white; border-radius: 20px; font-size: 0.8rem; color: #667eea; border: 1px solid #667eea;">{{ material.analysis.enhancedColorAnalysis.colorPsychology.atmosphere }}</span>
+                                        </div>
+                                      }
+                                    </div>
+                                  }
+
+                                  <!-- 形体分析卡片 -->
+                                  @if (material.analysis.formAnalysis) {
+                                    <div class="analysis-card form-card" style="background: #f8f9fa; padding: 14px; border-radius: 10px; border: 1px solid #e9ecef;">
+                                      <h6 style="font-size: 0.95rem; font-weight: 600; margin-bottom: 12px; color: #495057;">📐 形体分析</h6>
+                                      <app-furniture-form-selector
+                                        (formSelected)="requirementsCard?.onFormSelected($event)"
+                                        (formDeselected)="requirementsCard?.onFormDeselected($event)">
+                                      </app-furniture-form-selector>
+                                      @if (material.analysis.formAnalysis.overallAssessment) {
+                                        <div class="assessment-metrics" style="display: flex; gap: 12px; margin-top: 12px;">
+                                          <div class="metric" style="flex: 1; display: flex; flex-direction: column; align-items: center; padding: 10px; background: white; border-radius: 8px;">
+                                            <span style="font-size: 0.8rem; color: #6c757d;">复杂度</span>
+                                            <span class="value" style="font-size: 1.1rem; font-weight: 700; color: #667eea; margin-top: 4px;">{{ material.analysis.formAnalysis.overallAssessment.formComplexity }}%</span>
+                                          </div>
+                                          <div class="metric" style="flex: 1; display: flex; flex-direction: column; align-items: center; padding: 10px; background: white; border-radius: 8px;">
+                                            <span style="font-size: 0.8rem; color: #6c757d;">视觉冲击</span>
+                                            <span class="value" style="font-size: 1.1rem; font-weight: 700; color: #667eea; margin-top: 4px;">{{ material.analysis.formAnalysis.overallAssessment.visualImpact }}%</span>
+                                          </div>
+                                        </div>
+                                      }
+                                    </div>
+                                  }
+
+                                  <!-- 质感分析卡片 -->
+                                  @if (material.analysis.textureAnalysis) {
+                                    <div class="analysis-card texture-card" style="background: #f8f9fa; padding: 14px; border-radius: 10px; border: 1px solid #e9ecef;">
+                                      <h6 style="font-size: 0.95rem; font-weight: 600; margin-bottom: 12px; color: #495057;">🪨 质感分析</h6>
+                                      <app-texture-comparison-visualizer
+                                        [textures]="requirementsCard?.getTextureDataForMaterial(material) || []">
+                                      </app-texture-comparison-visualizer>
+                                      @if (material.analysis.textureAnalysis.materialClassification?.primaryMaterial) {
+                                        <div class="material-tag" style="text-align: center; padding: 8px 14px; background: white; border-radius: 8px; font-size: 0.85rem; font-weight: 600; color: #667eea; margin-top: 12px;">
+                                          {{ material.analysis.textureAnalysis.materialClassification?.primaryMaterial?.category }}
+                                          ({{ material.analysis.textureAnalysis.materialClassification?.primaryMaterial?.confidence }}%)
+                                        </div>
+                                      }
+                                    </div>
+                                  }
+
+                                  <!-- 纹理分析卡片 -->
+                                  @if (material.analysis.patternAnalysis) {
+                                    <div class="analysis-card pattern-card" style="background: #f8f9fa; padding: 14px; border-radius: 10px; border: 1px solid #e9ecef;">
+                                      <h6 style="font-size: 0.95rem; font-weight: 600; margin-bottom: 12px; color: #495057;">🖼️ 纹理分析</h6>
+                                      <app-pattern-visualizer>
+                                      </app-pattern-visualizer>
+                                      @if (material.analysis.patternAnalysis.patternRecognition) {
+                                        <div class="pattern-tags" style="display: flex; gap: 6px; flex-wrap: wrap; margin-top: 12px;">
+                                          @for (pattern of material.analysis.patternAnalysis.patternRecognition.primaryPatterns; track pattern.type) {
+                                            <span class="tag" style="padding: 4px 10px; background: white; border-radius: 20px; font-size: 0.75rem; color: #667eea; border: 1px solid #667eea;">{{ pattern.type }} ({{ pattern.coverage }}%)</span>
+                                          }
+                                        </div>
+                                      }
+                                    </div>
+                                  }
+
+                                  <!-- 灯光分析卡片 -->
+                                  @if (material.analysis.lightingAnalysis) {
+                                    <div class="analysis-card lighting-card" style="background: #f8f9fa; padding: 14px; border-radius: 10px; border: 1px solid #e9ecef;">
+                                      <h6 style="font-size: 0.95rem; font-weight: 600; margin-bottom: 12px; color: #495057;">💡 灯光分析</h6>
+                                      @if (material.analysis.lightingAnalysis.lightingRatio) {
+                                        <div class="lighting-ratio-visual">
+                                          <div class="ratio-bar" style="display: flex; height: 34px; border-radius: 8px; overflow: hidden; margin-bottom: 10px;">
+                                            <div class="key-light" [style.width.%]="material.analysis.lightingAnalysis.lightingRatio.keyLightIntensity" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 0.75rem; font-weight: 600;">
+                                              主光 {{ material.analysis.lightingAnalysis.lightingRatio.keyLightIntensity }}%
+                                            </div>
+                                            <div class="fill-light" [style.width.%]="material.analysis.lightingAnalysis.lightingRatio.fillLightIntensity" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 0.75rem; font-weight: 600;">
+                                              补光 {{ material.analysis.lightingAnalysis.lightingRatio.fillLightIntensity }}%
+                                            </div>
+                                          </div>
+                                          <span class="ratio-quality" style="display: block; text-align: center; font-size: 0.85rem; color: #667eea; font-weight: 600;">{{ requirementsCard?.getRatioDescription(material.analysis.lightingAnalysis.lightingRatio.description) }}</span>
+                                        </div>
+                                      }
+                                      @if (material.analysis.lightingAnalysis.lightDistribution) {
+                                        <div class="light-distribution" style="display: flex; flex-direction: column; gap: 6px; margin-top: 10px;">
+                                          <div class="dist-item" style="display: flex; justify-content: space-between; align-items: center; padding: 6px 10px; background: white; border-radius: 6px;">
+                                            <span style="font-size: 0.85rem; color: #495057;">自然光</span>
+                                            <span class="value" style="font-weight: 700; color: #667eea; font-size: 0.85rem;">{{ material.analysis.lightingAnalysis.lightDistribution.natural }}%</span>
+                                          </div>
+                                          <div class="dist-item" style="display: flex; justify-content: space-between; align-items: center; padding: 6px 10px; background: white; border-radius: 6px;">
+                                            <span style="font-size: 0.85rem; color: #495057;">人工光</span>
+                                            <span class="value" style="font-weight: 700; color: #667eea; font-size: 0.85rem;">{{ material.analysis.lightingAnalysis.lightDistribution.artificial }}%</span>
+                                          </div>
+                                        </div>
+                                      }
+                                    </div>
+                                  }
                                 </div>
-                              }
-                            </div>
-                          }
-                          
-                          @if (mappingIsAnalyzing) {
-                            <div class="analyzing-indicator" style="display:flex; align-items:center; gap:10px; background:#f8f9fa; padding:12px; border-radius:8px;">
-                              <div class="spinner" style="width:16px;height:16px;border:2px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:spin 1s linear infinite;"></div>
-                              <span style="font-size:14px; color:#666;">正在解析图片...</span>
-                            </div>
-                          }
-                          
-                          @if (mappingIsGeneratingMapping) {
-                            <div class="analyzing-indicator" style="display:flex; align-items:center; gap:10px; background:#f8f9fa; padding:12px; border-radius:8px;">
-                              <div class="spinner" style="width:16px;height:16px;border:2px solid #e5e7eb;border-top-color:#10b981;border-radius:50%;animation:spin 1s linear infinite;"></div>
-                              <span style="font-size:14px; color:#666;">正在生成需求映射...</span>
-                            </div>
-                          }
-                          
-                          @if (!mappingAnalysisResult && !mappingIsAnalyzing && mappingUploadedFiles.length === 0) {
-                            <div class="empty-state" style="text-align:center; padding:32px; color:#999;">
-                              <div style="font-size:48px; margin-bottom:12px;">📊</div>
-                              <div style="font-size:14px;">在需求沟通中上传图片后,这里将实时显示需求映射结果</div>
-                            </div>
+                              </div>
+                            }
                           }
                         </div>
                         

+ 35 - 2
src/app/pages/designer/project-detail/project-detail.ts

@@ -1,4 +1,4 @@
-import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
+import { Component, OnInit, OnDestroy, ChangeDetectorRef, ViewChild } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { ActivatedRoute, Router } from '@angular/router';
 import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
@@ -27,6 +27,11 @@ import { DesignerAssignmentComponent, DesignerAssignmentData, Designer as Assign
 import { DesignerCalendarComponent, Designer as CalendarDesigner, ProjectGroup as CalendarProjectGroup } from '../../customer-service/consultation-order/components/designer-calendar/designer-calendar.component';
 // 引入参考图管理组件
 import { ReferenceImageManagerComponent } from './components/reference-image-manager/reference-image-manager.component';
+// 引入可视化组件
+import { ColorWheelVisualizerComponent } from '../../../shared/components/color-wheel-visualizer/color-wheel-visualizer';
+import { FurnitureFormSelectorComponent } from '../../../shared/components/furniture-form-selector/furniture-form-selector';
+import { TextureComparisonVisualizerComponent } from '../../../shared/components/texture-comparison-visualizer/texture-comparison-visualizer';
+import { PatternVisualizerComponent } from '../../../shared/components/pattern-visualizer/pattern-visualizer';
 
 import { ColorAnalysisResult, ColorAnalysisService } from '../../../shared/services/color-analysis.service';
 
@@ -265,11 +270,32 @@ interface DeliveryProcess {
 @Component({
   selector: 'app-project-detail',
   standalone: true,
-  imports: [CommonModule, FormsModule, ReactiveFormsModule, RequirementsConfirmCardComponent, SettlementCardComponent, CustomerReviewCardComponent, CustomerReviewFormComponent, ComplaintCardComponent, PanoramicSynthesisCardComponent, QuotationDetailsComponent, DesignerAssignmentComponent, DesignerCalendarComponent, ReferenceImageManagerComponent],
+  imports: [
+    CommonModule, 
+    FormsModule, 
+    ReactiveFormsModule, 
+    RequirementsConfirmCardComponent, 
+    SettlementCardComponent, 
+    CustomerReviewCardComponent, 
+    CustomerReviewFormComponent, 
+    ComplaintCardComponent, 
+    PanoramicSynthesisCardComponent, 
+    QuotationDetailsComponent, 
+    DesignerAssignmentComponent, 
+    DesignerCalendarComponent, 
+    ReferenceImageManagerComponent,
+    ColorWheelVisualizerComponent,
+    FurnitureFormSelectorComponent,
+    TextureComparisonVisualizerComponent,
+    PatternVisualizerComponent
+  ],
   templateUrl: './project-detail.html',
   styleUrls: ['./project-detail.scss', './debug-styles.scss', './horizontal-panel.scss', './suggestion-detail-modal.scss']
 })
 export class ProjectDetail implements OnInit, OnDestroy {
+  // 获取需求沟通组件实例,用于在方案确认阶段显示分析数据
+  @ViewChild('requirementsCard') requirementsCard?: RequirementsConfirmCardComponent;
+  
   // 项目基本数据
   projectId: string = '';
   project: Project | undefined;
@@ -3064,6 +3090,13 @@ export class ProjectDetail implements OnInit, OnDestroy {
     this.cdr.detectChanges(); // 触发界面更新
   }
 
+  // 新增:处理上传弹窗请求
+  onUploadModalRequested(event: any): void {
+    console.log('📤 收到上传弹窗请求:', event);
+    // 这里可以根据需要处理弹窗显示逻辑
+    // 目前只记录日志,实际功能由子组件内部处理
+  }
+
   // 继续原有的onRequirementDataUpdated方法内容
   private updateProjectInfoFromRequirementData(data: any): void {
     // 更新客户信息显示

+ 160 - 0
src/app/shared/components/color-wheel-visualizer/color-wheel-visualizer.html

@@ -0,0 +1,160 @@
+<div class="color-wheel-container">
+  <svg 
+    [attr.width]="size" 
+    [attr.height]="size" 
+    [attr.viewBox]="'0 0 200 200'"
+    class="color-wheel-svg">
+    
+    <defs>
+      <!-- 色轮圆环渐变(使用多个扇形来模拟圆锥渐变) -->
+      <g id="color-wheel-background">
+        @for (stop of getConicGradientStops(); track $index) {
+          @if ($index < getConicGradientStops().length - 1) {
+            <path
+              [attr.d]="'M 100 100 L ' + (100 + outerRadius * Math.cos(($index / 60) * 2 * Math.PI - Math.PI/2)) + ' ' + 
+                        (100 + outerRadius * Math.sin(($index / 60) * 2 * Math.PI - Math.PI/2)) + ' A ' + outerRadius + ' ' + outerRadius + 
+                        ' 0 0 1 ' + (100 + outerRadius * Math.cos((($index + 1) / 60) * 2 * Math.PI - Math.PI/2)) + ' ' + 
+                        (100 + outerRadius * Math.sin((($index + 1) / 60) * 2 * Math.PI - Math.PI/2)) + ' Z'"
+              [attr.fill]="stop.color"
+              opacity="0.9"
+            />
+          }
+        }
+      </g>
+      
+      <!-- 内圈遮罩 -->
+      <mask id="ring-mask">
+        <circle cx="100" cy="100" [attr.r]="outerRadius" fill="white" />
+        <circle cx="100" cy="100" [attr.r]="innerRadius" fill="black" />
+      </mask>
+    </defs>
+    
+    <!-- 色轮背景 -->
+    <use href="#color-wheel-background" mask="url(#ring-mask)" />
+    
+    <!-- 色轮边框 -->
+    <circle 
+      cx="100" 
+      cy="100" 
+      [attr.r]="outerRadius" 
+      fill="none" 
+      stroke="#ddd" 
+      stroke-width="1" 
+    />
+    <circle 
+      cx="100" 
+      cy="100" 
+      [attr.r]="innerRadius" 
+      fill="white" 
+      stroke="#ddd" 
+      stroke-width="1" 
+    />
+    
+    <!-- 颜色点标记 -->
+    @for (color of colorPoints; track color.hex) {
+      <!-- 连接线 -->
+      <line
+        [attr.x1]="100"
+        [attr.y1]="100"
+        [attr.x2]="getColorPosition(color).x"
+        [attr.y2]="getColorPosition(color).y"
+        stroke="#ccc"
+        stroke-width="0.5"
+        stroke-dasharray="2,2"
+      />
+      
+      <!-- 颜色点 -->
+      <circle
+        [attr.cx]="getColorPosition(color).x"
+        [attr.cy]="getColorPosition(color).y"
+        r="6"
+        [attr.fill]="color.hex"
+        stroke="white"
+        stroke-width="2"
+        class="color-point"
+      />
+      
+      <!-- 颜色点外圈 -->
+      <circle
+        [attr.cx]="getColorPosition(color).x"
+        [attr.cy]="getColorPosition(color).y"
+        r="8"
+        fill="none"
+        stroke="#333"
+        stroke-width="0.5"
+        opacity="0.3"
+      />
+    }
+    
+    <!-- 颜色标签 -->
+    @if (showLabels) {
+      @for (color of colorPoints; track color.hex) {
+        <g>
+          <!-- 标签背景 -->
+          <rect
+            [attr.x]="getLabelPosition(color).x + (getTextAnchor(color.angle) === 'end' ? -55 : 5)"
+            [attr.y]="getLabelPosition(color).y - 10"
+            width="50"
+            height="18"
+            rx="3"
+            fill="white"
+            stroke="#ddd"
+            stroke-width="0.5"
+            opacity="0.95"
+          />
+          
+          <!-- 颜色名称 -->
+          <text
+            [attr.x]="getLabelPosition(color).x + (getTextAnchor(color.angle) === 'end' ? -30 : 30)"
+            [attr.y]="getLabelPosition(color).y + 2"
+            text-anchor="middle"
+            class="color-label"
+            [attr.fill]="color.hex"
+            font-weight="600"
+          >
+            {{ color.name || color.hex }}
+          </text>
+          
+          <!-- 百分比 -->
+          @if (showPercentages && color.percentage) {
+            <text
+              [attr.x]="getLabelPosition(color).x + (getTextAnchor(color.angle) === 'end' ? -30 : 30)"
+              [attr.y]="getLabelPosition(color).y + 15"
+              text-anchor="middle"
+              class="color-percentage"
+            >
+              {{ color.percentage.toFixed(1) }}%
+            </text>
+          }
+        </g>
+      }
+    }
+    
+    <!-- 中心标题 -->
+    <text
+      x="100"
+      y="100"
+      text-anchor="middle"
+      dominant-baseline="middle"
+      class="center-title"
+    >
+      色轮
+    </text>
+  </svg>
+  
+  <!-- 图例 -->
+  <div class="color-legend">
+    @for (color of colorPoints; track color.hex) {
+      <div class="legend-item">
+        <div class="legend-color" [style.background-color]="color.hex"></div>
+        <div class="legend-info">
+          <span class="legend-name">{{ color.name || color.hex }}</span>
+          @if (showPercentages && color.percentage) {
+            <span class="legend-percentage">{{ color.percentage.toFixed(1) }}%</span>
+          }
+        </div>
+      </div>
+    }
+  </div>
+</div>
+

+ 89 - 0
src/app/shared/components/color-wheel-visualizer/color-wheel-visualizer.scss

@@ -0,0 +1,89 @@
+.color-wheel-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 16px;
+  padding: 16px;
+  background: white;
+  border-radius: 8px;
+  
+  .color-wheel-svg {
+    filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.08));
+    
+    .color-point {
+      cursor: pointer;
+      transition: r 0.2s ease;
+      
+      &:hover {
+        r: 8;
+      }
+    }
+    
+    .color-label {
+      font-size: 9px;
+      font-weight: 600;
+    }
+    
+    .color-percentage {
+      font-size: 8px;
+      fill: #666;
+    }
+    
+    .center-title {
+      font-size: 14px;
+      font-weight: 600;
+      fill: #333;
+      letter-spacing: 2px;
+    }
+  }
+  
+  .color-legend {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 12px;
+    justify-content: center;
+    max-width: 400px;
+    
+    .legend-item {
+      display: flex;
+      align-items: center;
+      gap: 6px;
+      padding: 4px 8px;
+      background: #f8f9fa;
+      border-radius: 4px;
+      transition: background 0.2s ease;
+      
+      &:hover {
+        background: #e9ecef;
+      }
+      
+      .legend-color {
+        width: 16px;
+        height: 16px;
+        border-radius: 3px;
+        border: 1px solid rgba(0, 0, 0, 0.1);
+        flex-shrink: 0;
+      }
+      
+      .legend-info {
+        display: flex;
+        flex-direction: column;
+        gap: 2px;
+        
+        .legend-name {
+          font-size: 11px;
+          font-weight: 600;
+          color: #333;
+          line-height: 1;
+        }
+        
+        .legend-percentage {
+          font-size: 9px;
+          color: #666;
+          line-height: 1;
+        }
+      }
+    }
+  }
+}
+

+ 215 - 0
src/app/shared/components/color-wheel-visualizer/color-wheel-visualizer.ts

@@ -0,0 +1,215 @@
+import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+interface ColorPoint {
+  hex: string;
+  rgb: { r: number; g: number; b: number };
+  hsl: { h: number; s: number; l: number };
+  angle: number;
+  radius: number;
+  percentage?: number;
+  name?: string;
+}
+
+@Component({
+  selector: 'app-color-wheel-visualizer',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './color-wheel-visualizer.html',
+  styleUrls: ['./color-wheel-visualizer.scss']
+})
+export class ColorWheelVisualizerComponent implements OnInit, OnChanges {
+  @Input() colors: Array<{
+    hex: string;
+    rgb: { r: number; g: number; b: number };
+    percentage: number;
+    name?: string;
+  }> = [];
+  
+  @Input() size: number = 200; // 色轮大小
+  @Input() showLabels: boolean = true; // 是否显示标签
+  @Input() showPercentages: boolean = true; // 是否显示百分比
+
+  colorPoints: ColorPoint[] = [];
+  centerX: number = 100;
+  centerY: number = 100;
+  outerRadius: number = 90;
+  innerRadius: number = 30;
+  
+  // 暴露Math对象供模板使用
+  Math = Math;
+
+  ngOnInit(): void {
+    this.updateColorPoints();
+  }
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes['colors'] || changes['size']) {
+      this.updateColorPoints();
+    }
+  }
+
+  private updateColorPoints(): void {
+    this.colorPoints = this.colors.map(color => {
+      const hsl = this.rgbToHsl(color.rgb.r, color.rgb.g, color.rgb.b);
+      
+      return {
+        hex: color.hex,
+        rgb: color.rgb,
+        hsl,
+        angle: hsl.h,
+        radius: hsl.s * (this.outerRadius - this.innerRadius) + this.innerRadius,
+        percentage: color.percentage,
+        name: color.name
+      };
+    });
+  }
+
+  /**
+   * 将RGB转换为HSL
+   */
+  private rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
+    r /= 255;
+    g /= 255;
+    b /= 255;
+
+    const max = Math.max(r, g, b);
+    const min = Math.min(r, g, b);
+    let h = 0;
+    let s = 0;
+    const l = (max + min) / 2;
+
+    if (max !== min) {
+      const d = max - min;
+      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+
+      switch (max) {
+        case r:
+          h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
+          break;
+        case g:
+          h = ((b - r) / d + 2) / 6;
+          break;
+        case b:
+          h = ((r - g) / d + 4) / 6;
+          break;
+      }
+    }
+
+    return {
+      h: h * 360,
+      s: s,
+      l: l
+    };
+  }
+
+  /**
+   * 获取色轮背景渐变(用SVG实现)
+   */
+  getColorWheelGradient(): string {
+    const steps = 60;
+    const gradientStops: string[] = [];
+    
+    for (let i = 0; i < steps; i++) {
+      const hue = (i / steps) * 360;
+      const color = this.hslToHex(hue, 100, 50);
+      const percentage = (i / steps) * 100;
+      gradientStops.push(`${color} ${percentage}%`);
+    }
+    
+    return `conic-gradient(${gradientStops.join(', ')})`;
+  }
+
+  /**
+   * 将HSL转换为十六进制颜色
+   */
+  private hslToHex(h: number, s: number, l: number): string {
+    s /= 100;
+    l /= 100;
+
+    const c = (1 - Math.abs(2 * l - 1)) * s;
+    const x = c * (1 - Math.abs((h / 60) % 2 - 1));
+    const m = l - c / 2;
+    
+    let r = 0, g = 0, b = 0;
+
+    if (0 <= h && h < 60) {
+      r = c; g = x; b = 0;
+    } else if (60 <= h && h < 120) {
+      r = x; g = c; b = 0;
+    } else if (120 <= h && h < 180) {
+      r = 0; g = c; b = x;
+    } else if (180 <= h && h < 240) {
+      r = 0; g = x; b = c;
+    } else if (240 <= h && h < 300) {
+      r = x; g = 0; b = c;
+    } else if (300 <= h && h < 360) {
+      r = c; g = 0; b = x;
+    }
+
+    const toHex = (value: number) => {
+      const hex = Math.round((value + m) * 255).toString(16);
+      return hex.length === 1 ? '0' + hex : hex;
+    };
+
+    return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
+  }
+
+  /**
+   * 获取颜色点在色轮上的坐标
+   */
+  getColorPosition(color: ColorPoint): { x: number; y: number } {
+    const angleRad = (color.angle - 90) * (Math.PI / 180);
+    const x = this.centerX + color.radius * Math.cos(angleRad);
+    const y = this.centerY + color.radius * Math.sin(angleRad);
+    
+    return { x, y };
+  }
+
+  /**
+   * 获取标签位置(在色轮外侧)
+   */
+  getLabelPosition(color: ColorPoint): { x: number; y: number } {
+    const angleRad = (color.angle - 90) * (Math.PI / 180);
+    const labelRadius = this.outerRadius + 15;
+    const x = this.centerX + labelRadius * Math.cos(angleRad);
+    const y = this.centerY + labelRadius * Math.sin(angleRad);
+    
+    return { x, y };
+  }
+
+  /**
+   * 获取文本锚点(根据角度决定左对齐还是右对齐)
+   */
+  getTextAnchor(angle: number): string {
+    if (angle > 90 && angle < 270) {
+      return 'end';
+    }
+    return 'start';
+  }
+
+  /**
+   * 生成色轮渐变ID
+   */
+  get colorWheelGradientId(): string {
+    return 'color-wheel-gradient';
+  }
+
+  /**
+   * 生成圆锥渐变的SVG定义
+   */
+  getConicGradientStops(): Array<{ offset: string; color: string }> {
+    const steps = 60;
+    const stops: Array<{ offset: string; color: string }> = [];
+    
+    for (let i = 0; i <= steps; i++) {
+      const hue = (i / steps) * 360;
+      const color = this.hslToHex(hue, 100, 50);
+      const offset = ((i / steps) * 100).toFixed(1);
+      stops.push({ offset: `${offset}%`, color });
+    }
+    
+    return stops;
+  }
+}
+

+ 82 - 0
src/app/shared/components/furniture-form-selector/furniture-form-selector.html

@@ -0,0 +1,82 @@
+<div class="furniture-form-selector">
+  <div class="selector-header">
+    <h6>软装形体质感</h6>
+    <div class="selection-count">
+      已选择: {{ selectedForms.length }} / {{ maxSelections }}
+      @if (selectedForms.length > 0) {
+        <button class="clear-btn" (click)="clearSelections()">清除</button>
+      }
+    </div>
+  </div>
+
+  <div class="form-options-grid">
+    @for (option of formOptions; track option.id) {
+      <div 
+        class="form-option-card"
+        [class.selected]="isSelected(option.id)"
+        (click)="toggleFormSelection(option)">
+        
+        <!-- 选择指示器 -->
+        <div class="selection-indicator">
+          @if (isSelected(option.id)) {
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
+              <polyline points="20 6 9 17 4 12"></polyline>
+            </svg>
+          }
+        </div>
+        
+        <!-- 形体图标 -->
+        <div class="form-icon">
+          <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+            <path [attr.d]="option.icon"></path>
+          </svg>
+        </div>
+        
+        <!-- 形体信息 -->
+        <div class="form-info">
+          <h6 class="form-name">{{ option.name }}</h6>
+          <p class="form-description">{{ option.description }}</p>
+          
+          <!-- 质感标签 -->
+          <div class="texture-tags">
+            @for (texture of option.textures; track texture) {
+              <span class="texture-tag" [attr.data-texture]="texture">
+                {{ getTextureName(texture) }}
+              </span>
+            }
+          </div>
+          
+          <!-- 示例 -->
+          @if (showExamples && option.examples.length > 0) {
+            <div class="examples">
+              <span class="examples-label">示例:</span>
+              <span class="examples-text">{{ option.examples.join('、') }}</span>
+            </div>
+          }
+        </div>
+      </div>
+    }
+  </div>
+
+  <!-- 分析结果摘要 -->
+  @if (selectedForms.length > 0) {
+    <div class="analysis-summary">
+      <h6>分析结果</h6>
+      <div class="summary-items">
+        <div class="summary-item">
+          <span class="label">主导质感:</span>
+          <span class="value">{{ calculateDominantTexture() }}</span>
+        </div>
+        <div class="summary-item">
+          <span class="label">形体复杂度:</span>
+          <span class="value">{{ getComplexityName(calculateFormComplexity()) }}</span>
+        </div>
+        <div class="summary-item">
+          <span class="label">风格方向:</span>
+          <span class="value">{{ determineStyleDirection() }}</span>
+        </div>
+      </div>
+    </div>
+  }
+</div>
+

+ 233 - 0
src/app/shared/components/furniture-form-selector/furniture-form-selector.scss

@@ -0,0 +1,233 @@
+.furniture-form-selector {
+  padding: 16px;
+  background: white;
+  border-radius: 8px;
+  
+  .selector-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+    
+    h6 {
+      margin: 0;
+      font-size: 15px;
+      font-weight: 600;
+      color: #333;
+    }
+    
+    .selection-count {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      font-size: 13px;
+      color: #666;
+      
+      .clear-btn {
+        background: none;
+        border: 1px solid #ddd;
+        border-radius: 4px;
+        padding: 2px 8px;
+        font-size: 12px;
+        color: #666;
+        cursor: pointer;
+        transition: all 0.2s ease;
+        
+        &:hover {
+          background: #f8f9fa;
+          border-color: #007AFF;
+          color: #007AFF;
+        }
+      }
+    }
+  }
+  
+  .form-options-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+    gap: 12px;
+    margin-bottom: 16px;
+    
+    .form-option-card {
+      position: relative;
+      padding: 12px;
+      background: #f8f9fa;
+      border: 2px solid transparent;
+      border-radius: 8px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      
+      &:hover {
+        background: #e9ecef;
+        transform: translateY(-2px);
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+      }
+      
+      &.selected {
+        background: rgba(0, 122, 255, 0.05);
+        border-color: #007AFF;
+        
+        .selection-indicator {
+          background: #007AFF;
+          
+          svg {
+            stroke: white;
+          }
+        }
+        
+        .form-icon svg {
+          stroke: #007AFF;
+        }
+      }
+      
+      .selection-indicator {
+        position: absolute;
+        top: 8px;
+        right: 8px;
+        width: 24px;
+        height: 24px;
+        background: #ddd;
+        border-radius: 50%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        transition: all 0.3s ease;
+        
+        svg {
+          display: none;
+        }
+      }
+      
+      .form-icon {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        height: 60px;
+        margin-bottom: 12px;
+        
+        svg {
+          transition: stroke 0.3s ease;
+        }
+      }
+      
+      .form-info {
+        .form-name {
+          margin: 0 0 6px 0;
+          font-size: 14px;
+          font-weight: 600;
+          color: #333;
+        }
+        
+        .form-description {
+          margin: 0 0 8px 0;
+          font-size: 11px;
+          color: #666;
+          line-height: 1.4;
+        }
+        
+        .texture-tags {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 4px;
+          margin-bottom: 8px;
+          
+          .texture-tag {
+            padding: 2px 6px;
+            background: white;
+            border-radius: 3px;
+            font-size: 10px;
+            color: #666;
+            border: 1px solid #e0e0e0;
+            
+            &[data-texture="smooth"] {
+              background: rgba(52, 199, 89, 0.1);
+              color: #34C759;
+              border-color: rgba(52, 199, 89, 0.3);
+            }
+            
+            &[data-texture="rough"] {
+              background: rgba(255, 149, 0, 0.1);
+              color: #FF9500;
+              border-color: rgba(255, 149, 0, 0.3);
+            }
+            
+            &[data-texture="soft"] {
+              background: rgba(255, 45, 85, 0.1);
+              color: #FF2D55;
+              border-color: rgba(255, 45, 85, 0.3);
+            }
+            
+            &[data-texture="hard"] {
+              background: rgba(88, 86, 214, 0.1);
+              color: #5856D6;
+              border-color: rgba(88, 86, 214, 0.3);
+            }
+            
+            &[data-texture="glossy"] {
+              background: rgba(0, 122, 255, 0.1);
+              color: #007AFF;
+              border-color: rgba(0, 122, 255, 0.3);
+            }
+            
+            &[data-texture="matte"] {
+              background: rgba(142, 142, 147, 0.1);
+              color: #8E8E93;
+              border-color: rgba(142, 142, 147, 0.3);
+            }
+          }
+        }
+        
+        .examples {
+          font-size: 10px;
+          color: #999;
+          
+          .examples-label {
+            font-weight: 600;
+          }
+          
+          .examples-text {
+            margin-left: 4px;
+          }
+        }
+      }
+    }
+  }
+  
+  .analysis-summary {
+    padding: 12px;
+    background: #f0f7ff;
+    border-radius: 8px;
+    border: 1px solid #d0e7ff;
+    
+    h6 {
+      margin: 0 0 10px 0;
+      font-size: 13px;
+      font-weight: 600;
+      color: #007AFF;
+    }
+    
+    .summary-items {
+      display: grid;
+      grid-template-columns: repeat(3, 1fr);
+      gap: 12px;
+      
+      .summary-item {
+        display: flex;
+        flex-direction: column;
+        gap: 4px;
+        
+        .label {
+          font-size: 11px;
+          color: #666;
+        }
+        
+        .value {
+          font-size: 13px;
+          font-weight: 600;
+          color: #333;
+        }
+      }
+    }
+  }
+}
+

+ 246 - 0
src/app/shared/components/furniture-form-selector/furniture-form-selector.ts

@@ -0,0 +1,246 @@
+import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+export interface FurnitureFormOption {
+  id: string;
+  name: string;
+  description: string;
+  icon: string; // SVG path或图标名
+  textures: Array<'smooth' | 'rough' | 'soft' | 'hard' | 'glossy' | 'matte'>;
+  examples: string[]; // 示例描述
+}
+
+export interface FormAnalysisResult {
+  selectedForms: FurnitureFormOption[];
+  dominantTexture: string;
+  formComplexity: 'simple' | 'moderate' | 'complex';
+  styleDirection: string;
+}
+
+@Component({
+  selector: 'app-furniture-form-selector',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './furniture-form-selector.html',
+  styleUrls: ['./furniture-form-selector.scss']
+})
+export class FurnitureFormSelectorComponent implements OnInit {
+  @Input() preselectedForms: string[] = []; // 预选的形体ID
+  @Input() maxSelections: number = 3; // 最多选择数量
+  @Input() showExamples: boolean = true; // 是否显示示例
+  
+  @Output() selectionChanged = new EventEmitter<FormAnalysisResult>();
+
+  // 可用的形体选项
+  formOptions: FurnitureFormOption[] = [
+    {
+      id: 'curved-soft',
+      name: '曲线柔和',
+      description: '圆润流畅的线条,柔软舒适的质感',
+      icon: 'M3 12 Q12 3, 21 12 T39 12',
+      textures: ['soft', 'smooth'],
+      examples: ['布艺沙发', '圆角茶几', '弧形书架']
+    },
+    {
+      id: 'straight-hard',
+      name: '直线硬朗',
+      description: '笔直清晰的线条,坚硬稳固的质感',
+      icon: 'M3 3 L21 3 L21 21 L3 21 Z',
+      textures: ['hard', 'smooth'],
+      examples: ['方形餐桌', '直角书柜', '金属椅']
+    },
+    {
+      id: 'organic-rough',
+      name: '有机粗糙',
+      description: '自然不规则的形态,粗糙质朴的质感',
+      icon: 'M3 12 Q6 8, 9 12 T15 12 Q18 16, 21 12',
+      textures: ['rough', 'soft'],
+      examples: ['实木家具', '藤编椅', '石材台面']
+    },
+    {
+      id: 'geometric-glossy',
+      name: '几何光泽',
+      description: '规则几何图形,光滑反光的质感',
+      icon: 'M12 3 L21 12 L12 21 L3 12 Z',
+      textures: ['glossy', 'hard', 'smooth'],
+      examples: ['玻璃茶几', '镜面柜', '金属灯具']
+    },
+    {
+      id: 'layered-matte',
+      name: '分层哑光',
+      description: '多层次堆叠,柔和哑光的质感',
+      icon: 'M3 6 L21 6 M3 12 L21 12 M3 18 L21 18',
+      textures: ['matte', 'soft'],
+      examples: ['布艺窗帘', '亚麻沙发', '木饰面墙']
+    },
+    {
+      id: 'mixed-texture',
+      name: '混合质感',
+      description: '多种材质组合,对比鲜明',
+      icon: 'M3 3 L12 3 Q21 3, 21 12 L21 21 L3 21 Z',
+      textures: ['smooth', 'rough', 'glossy', 'matte'],
+      examples: ['皮木结合椅', '金属玻璃桌', '布艺金属灯']
+    }
+  ];
+
+  selectedForms: FurnitureFormOption[] = [];
+
+  ngOnInit(): void {
+    // 根据预选ID初始化选择
+    if (this.preselectedForms.length > 0) {
+      this.selectedForms = this.formOptions.filter(opt => 
+        this.preselectedForms.includes(opt.id)
+      );
+      this.emitSelection();
+    }
+  }
+
+  /**
+   * 切换形体选择
+   */
+  toggleFormSelection(form: FurnitureFormOption): void {
+    const index = this.selectedForms.findIndex(f => f.id === form.id);
+    
+    if (index > -1) {
+      // 取消选择
+      this.selectedForms.splice(index, 1);
+    } else {
+      // 添加选择(检查最大数量)
+      if (this.selectedForms.length < this.maxSelections) {
+        this.selectedForms.push(form);
+      } else {
+        // 替换第一个选择
+        this.selectedForms.shift();
+        this.selectedForms.push(form);
+      }
+    }
+    
+    this.emitSelection();
+  }
+
+  /**
+   * 检查是否已选择
+   */
+  isSelected(formId: string): boolean {
+    return this.selectedForms.some(f => f.id === formId);
+  }
+
+  /**
+   * 发射选择变更事件
+   */
+  private emitSelection(): void {
+    const result: FormAnalysisResult = {
+      selectedForms: this.selectedForms,
+      dominantTexture: this.calculateDominantTexture(),
+      formComplexity: this.calculateFormComplexity(),
+      styleDirection: this.determineStyleDirection()
+    };
+    
+    this.selectionChanged.emit(result);
+  }
+
+  /**
+   * 计算主导质感
+   */
+  calculateDominantTexture(): string {
+    const textureCount: { [key: string]: number } = {};
+    
+    this.selectedForms.forEach(form => {
+      form.textures.forEach(texture => {
+        textureCount[texture] = (textureCount[texture] || 0) + 1;
+      });
+    });
+    
+    let maxCount = 0;
+    let dominant = 'smooth';
+    
+    Object.entries(textureCount).forEach(([texture, count]) => {
+      if (count > maxCount) {
+        maxCount = count;
+        dominant = texture;
+      }
+    });
+    
+    return this.getTextureName(dominant);
+  }
+
+  /**
+   * 计算形体复杂度
+   */
+  calculateFormComplexity(): 'simple' | 'moderate' | 'complex' {
+    if (this.selectedForms.length === 0) return 'simple';
+    if (this.selectedForms.length === 1) return 'simple';
+    
+    const hasOrganic = this.selectedForms.some(f => f.id === 'organic-rough');
+    const hasMixed = this.selectedForms.some(f => f.id === 'mixed-texture');
+    const hasGeometric = this.selectedForms.some(f => f.id === 'geometric-glossy');
+    
+    if (hasMixed || (hasOrganic && hasGeometric)) return 'complex';
+    if (this.selectedForms.length >= 2) return 'moderate';
+    
+    return 'simple';
+  }
+
+  /**
+   * 确定风格方向
+   */
+  determineStyleDirection(): string {
+    if (this.selectedForms.length === 0) return '未定义';
+    
+    const formIds = this.selectedForms.map(f => f.id);
+    
+    if (formIds.includes('curved-soft') && formIds.includes('layered-matte')) {
+      return '温馨舒适';
+    }
+    if (formIds.includes('straight-hard') && formIds.includes('geometric-glossy')) {
+      return '现代简约';
+    }
+    if (formIds.includes('organic-rough')) {
+      return '自然质朴';
+    }
+    if (formIds.includes('mixed-texture')) {
+      return '混搭创新';
+    }
+    if (formIds.includes('geometric-glossy')) {
+      return '时尚现代';
+    }
+    
+    return '综合风格';
+  }
+
+  /**
+   * 获取质感中文名称
+   */
+  getTextureName(texture: string): string {
+    const nameMap: { [key: string]: string } = {
+      'smooth': '光滑',
+      'rough': '粗糙',
+      'soft': '柔软',
+      'hard': '坚硬',
+      'glossy': '光泽',
+      'matte': '哑光'
+    };
+    return nameMap[texture] || texture;
+  }
+
+  /**
+   * 获取复杂度中文名称
+   */
+  getComplexityName(complexity: 'simple' | 'moderate' | 'complex'): string {
+    const nameMap = {
+      'simple': '简单',
+      'moderate': '中等',
+      'complex': '复杂'
+    };
+    return nameMap[complexity];
+  }
+
+  /**
+   * 清除所有选择
+   */
+  clearSelections(): void {
+    this.selectedForms = [];
+    this.emitSelection();
+  }
+}
+

+ 196 - 0
src/app/shared/components/pattern-visualizer/pattern-visualizer.html

@@ -0,0 +1,196 @@
+<div class="pattern-visualizer">
+  @if (!patternData) {
+    <div class="empty-state">
+      <p>暂无图案数据</p>
+    </div>
+  } @else {
+    <div class="visualizer-container">
+      <!-- 图案预览 -->
+      <div class="pattern-preview">
+        <div class="preview-image">
+          <img [src]="patternData.imageUrl" [alt]="getPatternTypeName(patternData.patternType)">
+          
+          <!-- 方向指示器 -->
+          @if (patternData.direction.primary !== 'random' && patternData.direction.primary !== 'radial') {
+            <div class="direction-indicator" [style.transform]="'rotate(' + getDirectionRotation() + 'deg)'">
+              <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
+                <line x1="12" y1="4" x2="12" y2="20"></line>
+                <polyline points="16 16 12 20 8 16"></polyline>
+              </svg>
+            </div>
+          }
+          
+          <!-- 放射指示器 -->
+          @if (patternData.direction.primary === 'radial') {
+            <div class="radial-indicator">
+              <svg width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
+                <circle cx="12" cy="12" r="3"></circle>
+                <line x1="12" y1="2" x2="12" y2="6"></line>
+                <line x1="12" y1="18" x2="12" y2="22"></line>
+                <line x1="2" y1="12" x2="6" y2="12"></line>
+                <line x1="18" y1="12" x2="22" y2="12"></line>
+                <line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line>
+                <line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line>
+                <line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line>
+                <line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line>
+              </svg>
+            </div>
+          }
+        </div>
+        
+        <!-- 图案类型标签 -->
+        <div class="pattern-type-badge">
+          {{ getPatternTypeName(patternData.patternType) }}
+        </div>
+      </div>
+
+      @if (showDetailedAnalysis) {
+        <div class="detailed-analysis">
+          <!-- 重复性分析 -->
+          <div class="analysis-section">
+            <h6>
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
+              </svg>
+              重复性
+            </h6>
+            <div class="section-content">
+              @if (patternData.repetition.hasRepetition) {
+                <div class="repetition-info">
+                  <div class="info-item">
+                    <span class="label">频率</span>
+                    <div class="progress-bar">
+                      <div class="progress-fill" [style.width.%]="patternData.repetition.frequency"></div>
+                    </div>
+                    <span class="value">{{ patternData.repetition.frequency }}%</span>
+                  </div>
+                  <div class="info-item">
+                    <span class="label">规律性</span>
+                    <div class="progress-bar">
+                      <div class="progress-fill" [style.width.%]="patternData.repetition.regularity"></div>
+                    </div>
+                    <span class="value">{{ patternData.repetition.regularity }}%</span>
+                  </div>
+                  
+                  <!-- 重复可视化 -->
+                  <div class="repetition-visual">
+                    @for (item of getRepetitionVisual(); track $index) {
+                      <div class="repeat-unit"></div>
+                    }
+                  </div>
+                </div>
+              } @else {
+                <p class="no-repetition">无明显重复图案</p>
+              }
+            </div>
+          </div>
+
+          <!-- 方向性分析 -->
+          <div class="analysis-section">
+            <h6>
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polyline points="4 14 10 14 10 20"></polyline>
+                <polyline points="20 10 14 10 14 4"></polyline>
+                <line x1="14" y1="10" x2="21" y2="3"></line>
+                <line x1="3" y1="21" x2="10" y2="14"></line>
+              </svg>
+              方向性
+            </h6>
+            <div class="section-content">
+              <div class="direction-info">
+                <span class="direction-badge">{{ getDirectionName(patternData.direction.primary) }}</span>
+                @if (patternData.direction.angle !== undefined) {
+                  <span class="angle-value">{{ patternData.direction.angle }}°</span>
+                }
+              </div>
+            </div>
+          </div>
+
+          <!-- 密度分析 -->
+          <div class="analysis-section">
+            <h6>
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <rect x="3" y="3" width="7" height="7"></rect>
+                <rect x="14" y="3" width="7" height="7"></rect>
+                <rect x="14" y="14" width="7" height="7"></rect>
+                <rect x="3" y="14" width="7" height="7"></rect>
+              </svg>
+              密度
+            </h6>
+            <div class="section-content">
+              <div class="density-info">
+                <span 
+                  class="density-badge" 
+                  [style.background-color]="getDensityColor(patternData.density.level) + '20'"
+                  [style.color]="getDensityColor(patternData.density.level)">
+                  {{ getDensityName(patternData.density.level) }}
+                </span>
+                <span class="density-percentage">{{ patternData.density.percentage }}%</span>
+              </div>
+              
+              <!-- 密度网格可视化 -->
+              <div class="density-grid">
+                @for (row of getDensityGrid(); track $index) {
+                  <div class="grid-row">
+                    @for (cell of row; track $index) {
+                      <div class="grid-cell" [class.filled]="cell"></div>
+                    }
+                  </div>
+                }
+              </div>
+            </div>
+          </div>
+
+          <!-- 复杂度分析 -->
+          <div class="analysis-section">
+            <h6>
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
+                <polyline points="2 17 12 22 22 17"></polyline>
+                <polyline points="2 12 12 17 22 12"></polyline>
+              </svg>
+              复杂度
+            </h6>
+            <div class="section-content">
+              <div class="complexity-info">
+                <span 
+                  class="complexity-badge"
+                  [style.background-color]="getComplexityColor(patternData.complexity.level) + '20'"
+                  [style.color]="getComplexityColor(patternData.complexity.level)">
+                  {{ getComplexityName(patternData.complexity.level) }}
+                </span>
+                <span class="element-count">{{ patternData.complexity.elementCount }} 个元素</span>
+              </div>
+            </div>
+          </div>
+
+          <!-- 颜色多样性 -->
+          <div class="analysis-section">
+            <h6>
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"></path>
+              </svg>
+              颜色多样性
+            </h6>
+            <div class="section-content">
+              <div class="color-variety-info">
+                <span class="variety-count">{{ patternData.colorVariety }} 种颜色</span>
+                
+                <!-- 主导颜色展示 -->
+                <div class="dominant-colors">
+                  @for (color of patternData.dominantColors; track color.hex) {
+                    <div class="color-item">
+                      <div class="color-swatch" [style.background-color]="color.hex"></div>
+                      <span class="color-percentage">{{ color.percentage.toFixed(1) }}%</span>
+                    </div>
+                  }
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      }
+    </div>
+  }
+</div>
+

+ 305 - 0
src/app/shared/components/pattern-visualizer/pattern-visualizer.scss

@@ -0,0 +1,305 @@
+.pattern-visualizer {
+  padding: 16px;
+  background: white;
+  border-radius: 8px;
+  
+  .empty-state {
+    padding: 40px 20px;
+    text-align: center;
+    color: #999;
+    font-size: 14px;
+  }
+  
+  .visualizer-container {
+    display: grid;
+    grid-template-columns: 200px 1fr;
+    gap: 20px;
+    
+    @media (max-width: 768px) {
+      grid-template-columns: 1fr;
+    }
+    
+    .pattern-preview {
+      position: relative;
+      
+      .preview-image {
+        position: relative;
+        width: 200px;
+        height: 200px;
+        border-radius: 8px;
+        overflow: hidden;
+        background: #f0f0f0;
+        margin-bottom: 12px;
+        
+        img {
+          width: 100%;
+          height: 100%;
+          object-fit: cover;
+        }
+        
+        .direction-indicator {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform-origin: center center;
+          pointer-events: none;
+          filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
+          
+          svg {
+            display: block;
+            margin-left: -20px;
+            margin-top: -20px;
+          }
+        }
+        
+        .radial-indicator {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
+          pointer-events: none;
+          filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
+        }
+      }
+      
+      .pattern-type-badge {
+        display: inline-block;
+        padding: 6px 12px;
+        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+        color: white;
+        border-radius: 6px;
+        font-size: 12px;
+        font-weight: 600;
+        box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
+      }
+    }
+    
+    .detailed-analysis {
+      display: flex;
+      flex-direction: column;
+      gap: 16px;
+      
+      .analysis-section {
+        padding: 12px;
+        background: #f8f9fa;
+        border-radius: 8px;
+        border: 1px solid #e0e0e0;
+        
+        h6 {
+          display: flex;
+          align-items: center;
+          gap: 6px;
+          margin: 0 0 10px 0;
+          font-size: 13px;
+          font-weight: 600;
+          color: #333;
+          
+          svg {
+            width: 16px;
+            height: 16px;
+            stroke: #007AFF;
+          }
+        }
+        
+        .section-content {
+          .repetition-info {
+            display: flex;
+            flex-direction: column;
+            gap: 10px;
+            
+            .info-item {
+              display: flex;
+              align-items: center;
+              gap: 10px;
+              
+              .label {
+                min-width: 50px;
+                font-size: 11px;
+                color: #666;
+              }
+              
+              .progress-bar {
+                flex: 1;
+                height: 6px;
+                background: #e0e0e0;
+                border-radius: 3px;
+                overflow: hidden;
+                
+                .progress-fill {
+                  height: 100%;
+                  background: linear-gradient(90deg, #007AFF 0%, #0051D5 100%);
+                  transition: width 0.3s ease;
+                }
+              }
+              
+              .value {
+                min-width: 40px;
+                text-align: right;
+                font-size: 11px;
+                font-weight: 600;
+                color: #333;
+              }
+            }
+            
+            .repetition-visual {
+              display: flex;
+              gap: 8px;
+              margin-top: 8px;
+              
+              .repeat-unit {
+                width: 30px;
+                height: 30px;
+                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+                border-radius: 4px;
+                animation: pulse 2s ease-in-out infinite;
+                
+                &:nth-child(2) { animation-delay: 0.2s; }
+                &:nth-child(3) { animation-delay: 0.4s; }
+                &:nth-child(4) { animation-delay: 0.6s; }
+                &:nth-child(5) { animation-delay: 0.8s; }
+              }
+            }
+          }
+          
+          .no-repetition {
+            margin: 0;
+            font-size: 12px;
+            color: #999;
+            font-style: italic;
+          }
+          
+          .direction-info {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+            
+            .direction-badge {
+              padding: 4px 12px;
+              background: rgba(0, 122, 255, 0.1);
+              color: #007AFF;
+              border-radius: 4px;
+              font-size: 12px;
+              font-weight: 600;
+            }
+            
+            .angle-value {
+              font-size: 12px;
+              color: #666;
+              font-family: monospace;
+            }
+          }
+          
+          .density-info {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+            margin-bottom: 10px;
+            
+            .density-badge {
+              padding: 4px 12px;
+              border-radius: 4px;
+              font-size: 12px;
+              font-weight: 600;
+            }
+            
+            .density-percentage {
+              font-size: 12px;
+              color: #666;
+            }
+          }
+          
+          .density-grid {
+            display: flex;
+            flex-direction: column;
+            gap: 2px;
+            
+            .grid-row {
+              display: flex;
+              gap: 2px;
+              
+              .grid-cell {
+                width: 12px;
+                height: 12px;
+                background: #e0e0e0;
+                border-radius: 2px;
+                transition: background 0.3s ease;
+                
+                &.filled {
+                  background: #007AFF;
+                }
+              }
+            }
+          }
+          
+          .complexity-info {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+            
+            .complexity-badge {
+              padding: 4px 12px;
+              border-radius: 4px;
+              font-size: 12px;
+              font-weight: 600;
+            }
+            
+            .element-count {
+              font-size: 12px;
+              color: #666;
+            }
+          }
+          
+          .color-variety-info {
+            .variety-count {
+              display: inline-block;
+              margin-bottom: 10px;
+              font-size: 12px;
+              font-weight: 600;
+              color: #333;
+            }
+            
+            .dominant-colors {
+              display: flex;
+              flex-wrap: wrap;
+              gap: 8px;
+              
+              .color-item {
+                display: flex;
+                align-items: center;
+                gap: 6px;
+                padding: 4px 8px;
+                background: white;
+                border-radius: 6px;
+                border: 1px solid #e0e0e0;
+                
+                .color-swatch {
+                  width: 20px;
+                  height: 20px;
+                  border-radius: 4px;
+                  border: 1px solid rgba(0, 0, 0, 0.1);
+                }
+                
+                .color-percentage {
+                  font-size: 11px;
+                  color: #666;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+@keyframes pulse {
+  0%, 100% {
+    opacity: 1;
+    transform: scale(1);
+  }
+  50% {
+    opacity: 0.7;
+    transform: scale(0.95);
+  }
+}
+

+ 169 - 0
src/app/shared/components/pattern-visualizer/pattern-visualizer.ts

@@ -0,0 +1,169 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+export interface PatternAnalysisData {
+  imageUrl: string;
+  patternType: 'geometric' | 'organic' | 'abstract' | 'floral' | 'striped' | 'none';
+  repetition: {
+    hasRepetition: boolean;
+    frequency: number; // 0-100
+    regularity: number; // 0-100
+  };
+  direction: {
+    primary: 'horizontal' | 'vertical' | 'diagonal' | 'radial' | 'random';
+    angle?: number; // 0-360度
+  };
+  density: {
+    level: 'sparse' | 'moderate' | 'dense';
+    percentage: number; // 0-100
+  };
+  complexity: {
+    level: 'simple' | 'moderate' | 'complex';
+    elementCount: number;
+  };
+  colorVariety: number; // 颜色种类数量
+  dominantColors: Array<{ hex: string; percentage: number }>;
+}
+
+@Component({
+  selector: 'app-pattern-visualizer',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './pattern-visualizer.html',
+  styleUrls: ['./pattern-visualizer.scss']
+})
+export class PatternVisualizerComponent implements OnInit {
+  @Input() patternData: PatternAnalysisData | null = null;
+  @Input() showDetailedAnalysis: boolean = true;
+
+  ngOnInit(): void {
+    // 初始化逻辑
+  }
+
+  /**
+   * 获取图案类型的中文名称
+   */
+  getPatternTypeName(type: string): string {
+    const nameMap: { [key: string]: string } = {
+      'geometric': '几何图案',
+      'organic': '有机图案',
+      'abstract': '抽象图案',
+      'floral': '花卉图案',
+      'striped': '条纹图案',
+      'none': '无图案'
+    };
+    return nameMap[type] || type;
+  }
+
+  /**
+   * 获取方向的中文名称
+   */
+  getDirectionName(direction: string): string {
+    const nameMap: { [key: string]: string } = {
+      'horizontal': '水平',
+      'vertical': '垂直',
+      'diagonal': '对角',
+      'radial': '放射',
+      'random': '随机'
+    };
+    return nameMap[direction] || direction;
+  }
+
+  /**
+   * 获取密度的中文名称
+   */
+  getDensityName(density: string): string {
+    const nameMap: { [key: string]: string } = {
+      'sparse': '稀疏',
+      'moderate': '适中',
+      'dense': '密集'
+    };
+    return nameMap[density] || density;
+  }
+
+  /**
+   * 获取复杂度的中文名称
+   */
+  getComplexityName(complexity: string): string {
+    const nameMap: { [key: string]: string } = {
+      'simple': '简单',
+      'moderate': '中等',
+      'complex': '复杂'
+    };
+    return nameMap[complexity] || complexity;
+  }
+
+  /**
+   * 获取方向指示器的旋转角度
+   */
+  getDirectionRotation(): number {
+    if (!this.patternData) return 0;
+    
+    const directionMap: { [key: string]: number } = {
+      'horizontal': 0,
+      'vertical': 90,
+      'diagonal': 45,
+      'radial': 0,
+      'random': 0
+    };
+    
+    return this.patternData.direction.angle || directionMap[this.patternData.direction.primary] || 0;
+  }
+
+  /**
+   * 获取重复频率的视觉表示
+   */
+  getRepetitionVisual(): number[] {
+    if (!this.patternData || !this.patternData.repetition.hasRepetition) {
+      return [];
+    }
+    
+    const count = Math.ceil(this.patternData.repetition.frequency / 20);
+    return Array(Math.min(count, 5)).fill(0);
+  }
+
+  /**
+   * 获取密度级别对应的颜色
+   */
+  getDensityColor(density: string): string {
+    const colorMap: { [key: string]: string } = {
+      'sparse': '#34C759',
+      'moderate': '#FF9500',
+      'dense': '#FF3B30'
+    };
+    return colorMap[density] || '#8E8E93';
+  }
+
+  /**
+   * 获取复杂度级别对应的颜色
+   */
+  getComplexityColor(complexity: string): string {
+    const colorMap: { [key: string]: string } = {
+      'simple': '#34C759',
+      'moderate': '#007AFF',
+      'complex': '#5856D6'
+    };
+    return colorMap[complexity] || '#8E8E93';
+  }
+
+  /**
+   * 生成密度可视化网格
+   */
+  getDensityGrid(): boolean[][] {
+    if (!this.patternData) return [];
+    
+    const gridSize = 10;
+    const grid: boolean[][] = [];
+    const fillPercentage = this.patternData.density.percentage;
+    
+    for (let i = 0; i < gridSize; i++) {
+      grid[i] = [];
+      for (let j = 0; j < gridSize; j++) {
+        grid[i][j] = Math.random() * 100 < fillPercentage;
+      }
+    }
+    
+    return grid;
+  }
+}
+

+ 246 - 65
src/app/shared/components/requirements-confirm-card/requirements-confirm-card.html

@@ -8,26 +8,20 @@
       <div class="compact-stage-indicators">
         <div class="stage-chain">
           <div class="stage-dot" [class]="getStageStatusClass('materialAnalysis')" 
-               title="素材析 - {{ getStageStatusText('materialAnalysis') }}">
+               title="素材析 - {{ getStageStatusText('materialAnalysis') }}">
             <span class="stage-number">1</span>
           </div>
           <div class="stage-connector" [class]="stageCompletionStatus.materialAnalysis ? 'completed' : 'pending'"></div>
           
-          <div class="stage-dot" [class]="getStageStatusClass('requirementMapping')" 
-               title="需求映射 - {{ getStageStatusText('requirementMapping') }}">
-            <span class="stage-number">2</span>
-          </div>
-          <div class="stage-connector" [class]="stageCompletionStatus.requirementMapping ? 'completed' : 'pending'"></div>
-          
           <div class="stage-dot" [class]="getStageStatusClass('collaboration')" 
                title="协作验证 - {{ getStageStatusText('collaboration') }}">
-            <span class="stage-number">3</span>
+            <span class="stage-number">2</span>
           </div>
           <div class="stage-connector" [class]="stageCompletionStatus.collaboration ? 'completed' : 'pending'"></div>
           
           <div class="stage-dot" [class]="getStageStatusClass('progressReview')" 
-               title="进度审查 - {{ getStageStatusText('progressReview') }}">
-            <span class="stage-number">4</span>
+               title="进度管理 - {{ getStageStatusText('progressReview') }}">
+            <span class="stage-number">3</span>
           </div>
         </div>
       </div>
@@ -53,16 +47,6 @@
       </svg>
       素材解析
     </button>
-    <button 
-      class="tab-button" 
-      [class.active]="activeTab === 'mapping'"
-      (click)="switchTab('mapping')">
-      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-        <circle cx="12" cy="12" r="3"></circle>
-        <path d="M12 1v6m0 6v6m11-7h-6m-6 0H1"></path>
-      </svg>
-      需求映射
-    </button>
     <button 
       class="tab-button" 
       [class.active]="activeTab === 'collaboration'"
@@ -91,6 +75,52 @@
     <!-- 素材解析标签页 -->
     @if (activeTab === 'materials') {
       <div class="materials-section">
+        <!-- 全展示模式控制和导出按钮 -->
+        <div class="full-display-control">
+          <button 
+            class="toggle-full-display-btn"
+            [class.active]="isFullDisplayMode"
+            (click)="toggleFullDisplayMode()">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              @if (isFullDisplayMode) {
+                <polyline points="4 14 10 14 10 20"></polyline>
+                <polyline points="20 10 14 10 14 4"></polyline>
+                <line x1="14" y1="10" x2="21" y2="3"></line>
+                <line x1="3" y1="21" x2="10" y2="14"></line>
+              } @else {
+                <polyline points="15 3 21 3 21 9"></polyline>
+                <polyline points="9 21 3 21 3 15"></polyline>
+                <line x1="21" y1="3" x2="14" y2="10"></line>
+                <line x1="3" y1="21" x2="10" y2="14"></line>
+              }
+            </svg>
+            {{ isFullDisplayMode ? '退出全展示' : '全展示模式' }}
+          </button>
+          <span class="mode-description">
+            {{ isFullDisplayMode ? '已展开所有分析详情' : '快速查看所有素材的完整分析' }}
+          </span>
+          
+          <!-- 导出按钮 -->
+          <div class="export-buttons">
+            <button class="export-btn" (click)="exportAnalysisReportHTML()" title="导出HTML报告">
+              <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>
+              HTML
+            </button>
+            <button class="export-btn" (click)="exportAnalysisReportJSON()" title="导出JSON数据">
+              <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>
+              JSON
+            </button>
+          </div>
+        </div>
+        
         <!-- 文本输入区域 - 独立一行 -->
         <div class="text-upload-section">
           <div class="upload-item text-item">
@@ -177,19 +207,45 @@
                         </div>
                       } @else if (material.type === 'image') {
                         <div class="analysis-results">
-                          <!-- 基础色彩信息 -->
-                          <div class="color-info">
-                            <span class="color-temp">色温: {{ material.analysis.colorTemperature }}K</span>
+                          <!-- 基础色彩信息和折叠按钮 -->
+                          <div class="analysis-summary">
+                            <div class="color-info">
+                              <span class="color-temp">色温: {{ material.analysis.colorTemperature }}K</span>
+                              @if (material.analysis.mainColors && material.analysis.mainColors.length > 0) {
+                                <span class="color-count">主色: {{ material.analysis.mainColors.length }}种</span>
+                              }
+                            </div>
+                            <button 
+                              class="toggle-detail-btn"
+                              (click)="toggleMaterialExpansion(material.id)"
+                              [class.expanded]="isMaterialExpanded(material.id)">
+                              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                                <polyline [attr.points]="isMaterialExpanded(material.id) ? '18 15 12 9 6 15' : '6 9 12 15 18 9'"></polyline>
+                              </svg>
+                              {{ isMaterialExpanded(material.id) ? '收起详情' : '查看详情' }}
+                            </button>
                           </div>
                           
-                          <!-- 增强色彩分析 -->
-                          @if (material.analysis.enhancedColorAnalysis) {
-                            <div class="enhanced-analysis">
+                          <!-- 详细分析(可折叠) -->
+                          @if (isMaterialExpanded(material.id)) {
+                            <div class="detailed-analysis">
                               <div class="analysis-section">
                                 <h6>色彩分析</h6>
                                 
+                                <!-- 色轮可视化组件 -->
+                                @if (material.analysis.mainColors && material.analysis.mainColors.length > 0) {
+                                  <div class="color-wheel-visualizer-wrapper">
+                                    <app-color-wheel-visualizer
+                                      [colors]="material.analysis.mainColors"
+                                      [size]="180"
+                                      [showLabels]="true"
+                                      [showPercentages]="true"
+                                    ></app-color-wheel-visualizer>
+                                  </div>
+                                }
+                                
                                 <!-- 色轮分析 -->
-                                @if (material.analysis.enhancedColorAnalysis.colorWheel) {
+                                @if (material.analysis.enhancedColorAnalysis?.colorWheel) {
                                   <div class="color-wheel-info">
                                     <div class="color-wheel-icon">
                                       <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -200,16 +256,16 @@
                                         <path d="M12 12l-8.66-5"></path>
                                       </svg>
                                     </div>
-                                    <span>主色调: {{ material.analysis.enhancedColorAnalysis.colorWheel.primaryHue }}°</span>
-                                    <span>饱和度: {{ material.analysis.enhancedColorAnalysis.colorWheel.saturation }}%</span>
+                                    <span>主色调: {{ material.analysis.enhancedColorAnalysis?.colorWheel?.primaryHue }}°</span>
+                                    <span>饱和度: {{ material.analysis.enhancedColorAnalysis?.colorWheel?.saturation }}%</span>
                                   </div>
                                 }
                                 
                                 <!-- 色彩心理学 -->
-                                @if (material.analysis.enhancedColorAnalysis.colorPsychology) {
+                                @if (material.analysis.enhancedColorAnalysis?.colorPsychology) {
                                   <div class="psychology-info">
-                                    <span class="mood-tag">{{ material.analysis.enhancedColorAnalysis.colorPsychology.primaryMood }}</span>
-                                    <span class="atmosphere-tag">{{ material.analysis.enhancedColorAnalysis.colorPsychology.atmosphere }}</span>
+                                    <span class="mood-tag">{{ material.analysis.enhancedColorAnalysis?.colorPsychology?.primaryMood }}</span>
+                                    <span class="atmosphere-tag">{{ material.analysis.enhancedColorAnalysis?.colorPsychology?.atmosphere }}</span>
                                   </div>
                                 }
                               </div>
@@ -218,6 +274,15 @@
                               @if (material.analysis.formAnalysis) {
                                 <div class="analysis-section">
                                   <h6>形体分析</h6>
+                                  
+                                  <!-- 软装形体选择器 -->
+                                  <div class="furniture-form-selector-wrapper">
+                                    <app-furniture-form-selector
+                                      (formSelected)="onFormSelected($event)"
+                                      (formDeselected)="onFormDeselected($event)">
+                                    </app-furniture-form-selector>
+                                  </div>
+                                  
                                   @if (material.analysis.formAnalysis.overallAssessment) {
                                     <div class="form-metrics">
                                       <div class="metric-item">
@@ -241,6 +306,16 @@
                               @if (material.analysis.textureAnalysis) {
                                 <div class="analysis-section">
                                   <h6>质感分析</h6>
+                                  
+                                  <!-- 质感对比可视化 -->
+                                  @if (material.analysis.textureAnalysis.surfaceProperties && material.analysis.textureAnalysis.materialClassification?.primaryMaterial) {
+                                    <div class="texture-comparison-wrapper">
+                                      <app-texture-comparison-visualizer
+                                        [textures]="getTextureDataForMaterial(material)">
+                                      </app-texture-comparison-visualizer>
+                                    </div>
+                                  }
+                                  
                                   @if (material.analysis.textureAnalysis.materialClassification) {
                                     <div class="material-info">
                                       <span class="material-tag">{{ material.analysis.textureAnalysis.materialClassification.primaryMaterial.category }}</span>
@@ -256,6 +331,15 @@
                               @if (material.analysis.patternAnalysis) {
                                 <div class="analysis-section">
                                   <h6>纹理分析</h6>
+                                  
+                                  <!-- 纹理可视化 -->
+                                  @if (material.analysis.patternAnalysis.patternRecognition && material.analysis.patternAnalysis.patternRecognition.primaryPatterns.length > 0) {
+                                    <div class="pattern-visualizer-wrapper">
+                                      <app-pattern-visualizer>
+                                      </app-pattern-visualizer>
+                                    </div>
+                                  }
+                                  
                                   @if (material.analysis.patternAnalysis.patternRecognition) {
                                     <div class="pattern-info">
                                       @for (pattern of material.analysis.patternAnalysis.patternRecognition.primaryPatterns; track pattern.type) {
@@ -266,23 +350,109 @@
                                 </div>
                               }
                               
-                              <!-- 灯光分析 -->
-                              @if (material.analysis.lightingAnalysis) {
-                                <div class="analysis-section">
-                                  <h6>灯光分析</h6>
-                                  @if (material.analysis.lightingAnalysis.ambientAnalysis) {
-                                    <div class="lighting-info">
-                                      <span class="mood-tag">{{ material.analysis.lightingAnalysis.ambientAnalysis.lightingMood?.primary || '未知' }}</span>
-                                      @if (material.analysis.lightingAnalysis.illuminationAnalysis) {
-                                        <span class="brightness-tag">亮度: {{ material.analysis.lightingAnalysis.illuminationAnalysis.brightness?.overall || 0 }}%</span>
-                                      }
-                                      @if (material.analysis.lightingAnalysis.lightSourceIdentification) {
-                                        <span class="source-tag">光源: {{ material.analysis.lightingAnalysis.lightSourceIdentification.lightingSetup?.dominantSource || '未知' }}</span>
-                                      }
+                            <!-- 灯光分析 -->
+                            @if (material.analysis.lightingAnalysis) {
+                              <div class="analysis-section">
+                                <h6>灯光分析</h6>
+                                @if (material.analysis.lightingAnalysis.ambientAnalysis) {
+                                  <div class="lighting-info">
+                                    <span class="mood-tag">{{ material.analysis.lightingAnalysis.ambientAnalysis.lightingMood?.primary || '未知' }}</span>
+                                    @if (material.analysis.lightingAnalysis.illuminationAnalysis) {
+                                      <span class="brightness-tag">亮度: {{ material.analysis.lightingAnalysis.illuminationAnalysis.brightness?.overall || 0 }}%</span>
+                                    }
+                                    @if (material.analysis.lightingAnalysis.lightSourceIdentification) {
+                                      <span class="source-tag">光源: {{ material.analysis.lightingAnalysis.lightSourceIdentification.lightingSetup?.dominantSource || '未知' }}</span>
+                                    }
+                                  </div>
+                                }
+                                
+                                <!-- 新增:光比分析 -->
+                                @if (material.analysis.lightingAnalysis.lightingRatio) {
+                                  <div class="lighting-ratio-info">
+                                    <div class="ratio-header">
+                                      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                                        <path d="M12 2v20M2 12h20"></path>
+                                      </svg>
+                                      <span class="label">光比分析</span>
                                     </div>
-                                  }
-                                </div>
-                              }
+                                    <div class="ratio-details">
+                                      <span class="ratio-value">{{ material.analysis.lightingAnalysis.lightingRatio.keyToFill.toFixed(1) }}:1</span>
+                                      <span class="ratio-desc">({{ getRatioDescription(material.analysis.lightingAnalysis.lightingRatio.description) }})</span>
+                                    </div>
+                                    <div class="ratio-bars">
+                                      <div class="ratio-bar-item">
+                                        <span class="bar-label">主光</span>
+                                        <div class="bar-container">
+                                          <div class="bar-fill key-light" [style.width.%]="material.analysis.lightingAnalysis.lightingRatio.keyLightIntensity"></div>
+                                        </div>
+                                        <span class="bar-value">{{ material.analysis.lightingAnalysis.lightingRatio.keyLightIntensity }}%</span>
+                                      </div>
+                                      <div class="ratio-bar-item">
+                                        <span class="bar-label">补光</span>
+                                        <div class="bar-container">
+                                          <div class="bar-fill fill-light" [style.width.%]="material.analysis.lightingAnalysis.lightingRatio.fillLightIntensity"></div>
+                                        </div>
+                                        <span class="bar-value">{{ material.analysis.lightingAnalysis.lightingRatio.fillLightIntensity }}%</span>
+                                      </div>
+                                    </div>
+                                  </div>
+                                }
+                                
+                                <!-- 新增:光占比分析 -->
+                                @if (material.analysis.lightingAnalysis.lightDistribution) {
+                                  <div class="lighting-distribution-info">
+                                    <div class="distribution-header">
+                                      <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>
+                                        <line x1="12" y1="2" x2="12" y2="12"></line>
+                                        <line x1="12" y1="12" x2="18" y2="16"></line>
+                                      </svg>
+                                      <span class="label">光占比分析</span>
+                                    </div>
+                                    <div class="distribution-grid">
+                                      <div class="distribution-item">
+                                        <div class="dist-icon natural"></div>
+                                        <div class="dist-info">
+                                          <span class="dist-label">自然光</span>
+                                          <span class="dist-value">{{ material.analysis.lightingAnalysis.lightDistribution.natural }}%</span>
+                                        </div>
+                                      </div>
+                                      <div class="distribution-item">
+                                        <div class="dist-icon artificial"></div>
+                                        <div class="dist-info">
+                                          <span class="dist-label">人工光</span>
+                                          <span class="dist-value">{{ material.analysis.lightingAnalysis.lightDistribution.artificial }}%</span>
+                                        </div>
+                                      </div>
+                                      <div class="distribution-item">
+                                        <div class="dist-icon direct"></div>
+                                        <div class="dist-info">
+                                          <span class="dist-label">直射光</span>
+                                          <span class="dist-value">{{ material.analysis.lightingAnalysis.lightDistribution.direct }}%</span>
+                                        </div>
+                                      </div>
+                                      <div class="distribution-item">
+                                        <div class="dist-icon ambient"></div>
+                                        <div class="dist-info">
+                                          <span class="dist-label">环境光</span>
+                                          <span class="dist-value">{{ material.analysis.lightingAnalysis.lightDistribution.ambient }}%</span>
+                                        </div>
+                                      </div>
+                                      <div class="distribution-item">
+                                        <div class="dist-icon indirect"></div>
+                                        <div class="dist-info">
+                                          <span class="dist-label">间接光</span>
+                                          <span class="dist-value">{{ material.analysis.lightingAnalysis.lightDistribution.indirect }}%</span>
+                                        </div>
+                                      </div>
+                                    </div>
+                                    <div class="distribution-summary">
+                                      <span class="summary-tag">{{ getBalanceDescription(material.analysis.lightingAnalysis.lightDistribution.lightingBalance) }}</span>
+                                    </div>
+                                  </div>
+                                }
+                              </div>
+                            }
                             </div>
                           }
                         </div>
@@ -297,8 +467,8 @@
       </div>
     }
 
-    <!-- 需求映射标签页 -->
-    @if (activeTab === 'mapping') {
+    <!-- 需求映射标签页 - 已隐藏,改为后台自动触发 -->
+    @if (false && activeTab === 'mapping') {
       <div class="mapping-section">
         <!-- 测试步骤进度 -->
         <div class="test-progress">
@@ -471,9 +641,9 @@
                 <div class="scene-info">
                   <div class="info-row">
                     <span class="label">基础场景:</span>
-                    <span class="value">{{ requirementMapping.sceneGeneration.baseScene }}</span>
+                    <span class="value">{{ requirementMapping?.sceneGeneration?.baseScene || '未定义' }}</span>
                   </div>
-                  @if (requirementMapping.sceneGeneration.atmospherePreview) {
+                  @if (requirementMapping?.sceneGeneration?.atmospherePreview) {
                     <div class="atmosphere-preview">
                       <img [src]="requirementMapping.sceneGeneration.atmospherePreview" 
                            alt="氛围感预览图"
@@ -494,19 +664,19 @@
                     <div class="color-params">
                       <div class="param-item">
                         <span class="label">主要颜色:</span>
-                        <span class="value">{{ requirementMapping.parameterMapping.colorParams.primaryColors.length }} 种</span>
+                        <span class="value">{{ requirementMapping?.parameterMapping?.colorParams?.primaryColors?.length || 0 }} 种</span>
                       </div>
                       <div class="param-item">
                         <span class="label">色彩和谐:</span>
-                        <span class="value">{{ getColorHarmonyName(requirementMapping.parameterMapping.colorParams.colorHarmony) }}</span>
+                        <span class="value">{{ requirementMapping?.parameterMapping?.colorParams ? getColorHarmonyName(requirementMapping.parameterMapping.colorParams.colorHarmony) : '未知' }}</span>
                       </div>
                       <div class="param-item">
                         <span class="label">饱和度:</span>
-                        <span class="value">{{ requirementMapping.parameterMapping.colorParams.saturation }}%</span>
+                        <span class="value">{{ requirementMapping?.parameterMapping?.colorParams?.saturation || 0 }}%</span>
                       </div>
                       <div class="param-item">
                         <span class="label">亮度:</span>
-                        <span class="value">{{ requirementMapping.parameterMapping.colorParams.brightness }}%</span>
+                        <span class="value">{{ requirementMapping?.parameterMapping?.colorParams?.brightness || 0 }}%</span>
                       </div>
                     </div>
                   </div>
@@ -517,19 +687,19 @@
                     <div class="space-params">
                       <div class="param-item">
                         <span class="label">布局类型:</span>
-                        <span class="value">{{ getLayoutTypeName(requirementMapping.parameterMapping.spaceParams.layout.type) }}</span>
+                        <span class="value">{{ requirementMapping?.parameterMapping?.spaceParams?.layout ? getLayoutTypeName(requirementMapping.parameterMapping.spaceParams.layout.type) : '未知' }}</span>
                       </div>
                       <div class="param-item">
                         <span class="label">空间流线:</span>
-                        <span class="value">{{ getFlowTypeName(requirementMapping.parameterMapping.spaceParams.layout.flow) }}</span>
+                        <span class="value">{{ requirementMapping?.parameterMapping?.spaceParams?.layout ? getFlowTypeName(requirementMapping.parameterMapping.spaceParams.layout.flow) : '未知' }}</span>
                       </div>
                       <div class="param-item">
                         <span class="label">家具比例:</span>
-                        <span class="value">{{ requirementMapping.parameterMapping.spaceParams.scale.furniture }}%</span>
+                        <span class="value">{{ requirementMapping?.parameterMapping?.spaceParams?.scale?.furniture || 0 }}%</span>
                       </div>
                       <div class="param-item">
                         <span class="label">开放度:</span>
-                        <span class="value">{{ requirementMapping.parameterMapping.spaceParams.scale.openness }}%</span>
+                        <span class="value">{{ requirementMapping?.parameterMapping?.spaceParams?.scale?.openness || 0 }}%</span>
                       </div>
                     </div>
                   </div>
@@ -540,19 +710,19 @@
                     <div class="material-params">
                       <div class="param-item">
                         <span class="label">纹理缩放:</span>
-                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.textureScale }}%</span>
+                        <span class="value">{{ requirementMapping?.parameterMapping?.materialParams?.textureScale || 0 }}%</span>
                       </div>
                       <div class="param-item">
                         <span class="label">反射率:</span>
-                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.reflectivity }}%</span>
+                        <span class="value">{{ requirementMapping?.parameterMapping?.materialParams?.reflectivity || 0 }}%</span>
                       </div>
                       <div class="param-item">
                         <span class="label">粗糙度:</span>
-                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.roughness }}%</span>
+                        <span class="value">{{ requirementMapping?.parameterMapping?.materialParams?.roughness || 0 }}%</span>
                       </div>
                       <div class="param-item">
                         <span class="label">金属度:</span>
-                        <span class="value">{{ requirementMapping.parameterMapping.materialParams.metallic }}%</span>
+                        <span class="value">{{ requirementMapping?.parameterMapping?.materialParams?.metallic || 0 }}%</span>
                       </div>
                     </div>
                   </div>
@@ -584,6 +754,7 @@
       </div>
     }
 
+    <!-- 分析汇总标签页 -->
     <!-- 协作验证标签页 -->
     @if (activeTab === 'collaboration') {
       <div class="collaboration-section">
@@ -875,4 +1046,14 @@
   [colorResult]="colorAnalysisResult"
   [cadResult]="cadAnalysisResult"
   (close)="onCloseFullReport()">
-</app-full-report-overlay>
+</app-full-report-overlay>
+
+<!-- 上传成功弹窗 -->
+<app-upload-success-modal
+  [isVisible]="showUploadSuccessModal"
+  [uploadedFiles]="uploadedFiles"
+  [uploadType]="uploadType"
+  [analysisResult]="colorAnalysisResult"
+  [isAnalyzing]="isAnalyzingColors"
+  (closeModal)="onModalClose()">
+</app-upload-success-modal>

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

@@ -8,6 +8,92 @@
   position: relative; // 确保子元素定位基准
 }
 
+// 新增:全展示模式控制按钮
+.full-display-control {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 12px 16px;
+  margin-bottom: 16px;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2);
+  
+  .toggle-full-display-btn {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    padding: 8px 16px;
+    background: white;
+    border: none;
+    border-radius: 6px;
+    font-size: 0.9rem;
+    font-weight: 600;
+    color: #667eea;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    
+    svg {
+      transition: transform 0.3s ease;
+    }
+    
+    &:hover {
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+    }
+    
+    &.active {
+      background: #667eea;
+      color: white;
+      
+      svg {
+        transform: rotate(90deg);
+      }
+    }
+  }
+  
+  .mode-description {
+    flex: 1;
+    font-size: 0.85rem;
+    color: white;
+    opacity: 0.95;
+  }
+  
+  .export-buttons {
+    display: flex;
+    gap: 8px;
+    
+    .export-btn {
+      display: flex;
+      align-items: center;
+      gap: 4px;
+      padding: 6px 12px;
+      background: rgba(255, 255, 255, 0.2);
+      border: 1px solid rgba(255, 255, 255, 0.3);
+      border-radius: 4px;
+      color: white;
+      font-size: 0.8rem;
+      font-weight: 600;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      
+      svg {
+        width: 14px;
+        height: 14px;
+      }
+      
+      &:hover {
+        background: rgba(255, 255, 255, 0.3);
+        transform: translateY(-1px);
+      }
+      
+      &:active {
+        transform: translateY(0);
+      }
+    }
+  }
+}
+
 // 新增:文本输入区域独立样式
 .text-upload-section {
   margin-bottom: 1.5rem;
@@ -772,6 +858,16 @@
                     }
                   }
                   
+                  // 新增:色轮可视化组件容器
+                  .color-wheel-visualizer-wrapper {
+                    margin: $ios-spacing-sm 0;
+                    padding: $ios-spacing-sm;
+                    background: #f8f9fa;
+                    border-radius: 8px;
+                    display: flex;
+                    justify-content: center;
+                  }
+                  
                   .lighting-info {
                     display: flex;
                     gap: $ios-spacing-xs;
@@ -796,6 +892,208 @@
                       font-size: $ios-font-size-xs;
                       font-weight: $ios-font-weight-medium;
                     }
+                    
+                    .source-tag {
+                      display: inline-block;
+                      background: rgba(94, 92, 230, 0.1);
+                      color: #5E5CE6;
+                      padding: 2px 6px;
+                      border-radius: 4px;
+                      font-size: $ios-font-size-xs;
+                      font-weight: $ios-font-weight-medium;
+                    }
+                  }
+                  
+                  // 新增:光比分析样式
+                  .lighting-ratio-info {
+                    margin-top: $ios-spacing-sm;
+                    padding: $ios-spacing-sm;
+                    background: rgba(0, 122, 255, 0.05);
+                    border-radius: 6px;
+                    border: 1px solid rgba(0, 122, 255, 0.1);
+                    
+                    .ratio-header {
+                      display: flex;
+                      align-items: center;
+                      gap: 4px;
+                      margin-bottom: $ios-spacing-xs;
+                      
+                      svg {
+                        width: 14px;
+                        height: 14px;
+                        stroke: #007AFF;
+                      }
+                      
+                      .label {
+                        font-size: $ios-font-size-xs;
+                        font-weight: $ios-font-weight-semibold;
+                        color: #007AFF;
+                      }
+                    }
+                    
+                    .ratio-details {
+                      display: flex;
+                      align-items: center;
+                      gap: 6px;
+                      margin-bottom: $ios-spacing-xs;
+                      
+                      .ratio-value {
+                        font-size: 14px;
+                        font-weight: $ios-font-weight-bold;
+                        color: #333;
+                      }
+                      
+                      .ratio-desc {
+                        font-size: $ios-font-size-xs;
+                        color: #666;
+                      }
+                    }
+                    
+                    .ratio-bars {
+                      display: flex;
+                      flex-direction: column;
+                      gap: 6px;
+                      
+                      .ratio-bar-item {
+                        display: flex;
+                        align-items: center;
+                        gap: 6px;
+                        
+                        .bar-label {
+                          font-size: $ios-font-size-xs;
+                          color: #666;
+                          min-width: 32px;
+                        }
+                        
+                        .bar-container {
+                          flex: 1;
+                          height: 6px;
+                          background: #f0f0f0;
+                          border-radius: 3px;
+                          overflow: hidden;
+                          
+                          .bar-fill {
+                            height: 100%;
+                            transition: width 0.3s ease;
+                            
+                            &.key-light {
+                              background: linear-gradient(90deg, #FFD60A 0%, #FFCC00 100%);
+                            }
+                            
+                            &.fill-light {
+                              background: linear-gradient(90deg, #007AFF 0%, #0051D5 100%);
+                            }
+                          }
+                        }
+                        
+                        .bar-value {
+                          font-size: $ios-font-size-xs;
+                          color: #333;
+                          min-width: 32px;
+                          text-align: right;
+                        }
+                      }
+                    }
+                  }
+                  
+                  // 新增:光占比分析样式
+                  .lighting-distribution-info {
+                    margin-top: $ios-spacing-sm;
+                    padding: $ios-spacing-sm;
+                    background: rgba(52, 199, 89, 0.05);
+                    border-radius: 6px;
+                    border: 1px solid rgba(52, 199, 89, 0.1);
+                    
+                    .distribution-header {
+                      display: flex;
+                      align-items: center;
+                      gap: 4px;
+                      margin-bottom: $ios-spacing-sm;
+                      
+                      svg {
+                        width: 14px;
+                        height: 14px;
+                        stroke: #34C759;
+                      }
+                      
+                      .label {
+                        font-size: $ios-font-size-xs;
+                        font-weight: $ios-font-weight-semibold;
+                        color: #34C759;
+                      }
+                    }
+                    
+                    .distribution-grid {
+                      display: grid;
+                      grid-template-columns: repeat(2, 1fr);
+                      gap: 8px;
+                      margin-bottom: $ios-spacing-xs;
+                      
+                      .distribution-item {
+                        display: flex;
+                        align-items: center;
+                        gap: 6px;
+                        
+                        .dist-icon {
+                          width: 12px;
+                          height: 12px;
+                          border-radius: 3px;
+                          
+                          &.natural {
+                            background: #FFD60A;
+                          }
+                          
+                          &.artificial {
+                            background: #FF9500;
+                          }
+                          
+                          &.direct {
+                            background: #007AFF;
+                          }
+                          
+                          &.ambient {
+                            background: #34C759;
+                          }
+                          
+                          &.indirect {
+                            background: #5E5CE6;
+                          }
+                        }
+                        
+                        .dist-info {
+                          display: flex;
+                          flex-direction: column;
+                          
+                          .dist-label {
+                            font-size: 10px;
+                            color: #666;
+                            line-height: 1.2;
+                          }
+                          
+                          .dist-value {
+                            font-size: 11px;
+                            font-weight: $ios-font-weight-semibold;
+                            color: #333;
+                          }
+                        }
+                      }
+                    }
+                    
+                    .distribution-summary {
+                      display: flex;
+                      justify-content: center;
+                      padding-top: $ios-spacing-xs;
+                      border-top: 1px solid rgba(52, 199, 89, 0.1);
+                      
+                      .summary-tag {
+                        font-size: $ios-font-size-xs;
+                        font-weight: $ios-font-weight-medium;
+                        color: #34C759;
+                        background: rgba(52, 199, 89, 0.1);
+                        padding: 2px 8px;
+                        border-radius: 4px;
+                      }
+                    }
                   }
                 }
               }
@@ -2529,4 +2827,345 @@
     box-shadow: 0 4px 16px rgba(102, 126, 234, 0.5);
     transform: scale(1.02);
   }
+}
+
+// 新增:素材卡片折叠按钮样式
+.analysis-summary {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px;
+  background: #f8f9fa;
+  border-radius: 8px;
+  margin-bottom: 12px;
+
+  .color-info {
+    display: flex;
+    gap: 16px;
+    align-items: center;
+
+    .color-temp, .color-count {
+      font-size: 0.9rem;
+      color: #495057;
+    }
+  }
+
+  .toggle-detail-btn {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    padding: 6px 12px;
+    background: white;
+    border: 1px solid #dee2e6;
+    border-radius: 6px;
+    font-size: 0.85rem;
+    color: #667eea;
+    cursor: pointer;
+    transition: all 0.3s ease;
+
+    svg {
+      transition: transform 0.3s ease;
+    }
+
+    &:hover {
+      background: #667eea;
+      color: white;
+      border-color: #667eea;
+    }
+
+    &.expanded {
+      background: #667eea;
+      color: white;
+      border-color: #667eea;
+
+      svg {
+        transform: rotate(180deg);
+      }
+    }
+  }
+}
+
+.detailed-analysis {
+  animation: fadeIn 0.3s ease;
+  max-height: 500px;
+  overflow-y: auto;
+  overflow-x: hidden;
+  padding-right: 8px;
+  
+  // 自定义滚动条样式
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+  
+  &::-webkit-scrollbar-track {
+    background: #f1f3f5;
+    border-radius: 3px;
+  }
+  
+  &::-webkit-scrollbar-thumb {
+    background: #667eea;
+    border-radius: 3px;
+    
+    &:hover {
+      background: #5568d3;
+    }
+  }
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(-10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+// 分析汇总标签页样式
+.analysis-summary-section {
+  padding: 20px;
+  max-height: calc(100vh - 200px);
+  overflow-y: auto;
+  overflow-x: hidden;
+  
+  // 自定义滚动条样式
+  &::-webkit-scrollbar {
+    width: 8px;
+  }
+  
+  &::-webkit-scrollbar-track {
+    background: #f1f3f5;
+    border-radius: 4px;
+  }
+  
+  &::-webkit-scrollbar-thumb {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    border-radius: 4px;
+    
+    &:hover {
+      background: linear-gradient(135deg, #5568d3 0%, #6a3f8f 100%);
+    }
+  }
+
+  .section-header {
+    margin-bottom: 24px;
+    text-align: center;
+
+    h4 {
+      font-size: 1.5rem;
+      font-weight: 700;
+      color: #2c3e50;
+      margin-bottom: 8px;
+    }
+
+    .section-description {
+      color: #6c757d;
+      font-size: 0.95rem;
+    }
+  }
+
+  .empty-state {
+    text-align: center;
+    padding: 60px 20px;
+
+    svg {
+      stroke: #dee2e6;
+      margin-bottom: 20px;
+    }
+
+    p {
+      color: #6c757d;
+      font-size: 1rem;
+      margin: 8px 0;
+
+      &.hint {
+        color: #adb5bd;
+        font-size: 0.9rem;
+      }
+    }
+  }
+
+  .material-analysis-card {
+    background: white;
+    border-radius: 12px;
+    padding: 20px;
+    margin-bottom: 24px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+
+    .material-header {
+      display: flex;
+      align-items: center;
+      gap: 16px;
+      margin-bottom: 20px;
+      padding-bottom: 16px;
+      border-bottom: 2px solid #f8f9fa;
+
+      .material-thumbnail {
+        width: 80px;
+        height: 80px;
+        object-fit: cover;
+        border-radius: 8px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+      }
+
+      .material-info {
+        flex: 1;
+
+        h5 {
+          font-size: 1.1rem;
+          font-weight: 600;
+          color: #2c3e50;
+          margin-bottom: 4px;
+        }
+
+        .upload-time {
+          font-size: 0.85rem;
+          color: #6c757d;
+        }
+      }
+    }
+
+    .analysis-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+      gap: 20px;
+    }
+
+    .analysis-card {
+      background: #f8f9fa;
+      padding: 16px;
+      border-radius: 10px;
+      border: 1px solid #e9ecef;
+
+      h6 {
+        font-size: 1rem;
+        font-weight: 600;
+        margin-bottom: 12px;
+        color: #495057;
+      }
+
+      .psychology-tags, .pattern-tags {
+        display: flex;
+        gap: 8px;
+        flex-wrap: wrap;
+        margin-top: 12px;
+
+        .tag {
+          padding: 4px 12px;
+          background: white;
+          border-radius: 20px;
+          font-size: 0.85rem;
+          color: #667eea;
+          border: 1px solid #667eea;
+        }
+      }
+
+      .assessment-metrics {
+        display: flex;
+        gap: 16px;
+        margin-top: 12px;
+
+        .metric {
+          flex: 1;
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          padding: 12px;
+          background: white;
+          border-radius: 8px;
+
+          span {
+            font-size: 0.85rem;
+            color: #6c757d;
+
+            &.value {
+              font-size: 1.2rem;
+              font-weight: 700;
+              color: #667eea;
+              margin-top: 4px;
+            }
+          }
+        }
+      }
+
+      .material-tag {
+        text-align: center;
+        padding: 8px 16px;
+        background: white;
+        border-radius: 8px;
+        font-size: 0.9rem;
+        font-weight: 600;
+        color: #667eea;
+        margin-top: 12px;
+      }
+
+      .lighting-ratio-visual {
+        .ratio-bar {
+          display: flex;
+          height: 40px;
+          border-radius: 8px;
+          overflow: hidden;
+          margin-bottom: 12px;
+
+          .key-light {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            color: white;
+            font-size: 0.85rem;
+            font-weight: 600;
+            transition: all 0.3s ease;
+          }
+
+          .fill-light {
+            background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            color: white;
+            font-size: 0.85rem;
+            font-weight: 600;
+            transition: all 0.3s ease;
+          }
+        }
+
+        .ratio-quality {
+          display: block;
+          text-align: center;
+          font-size: 0.9rem;
+          color: #667eea;
+          font-weight: 600;
+        }
+      }
+
+      .light-distribution {
+        display: flex;
+        flex-direction: column;
+        gap: 8px;
+        margin-top: 12px;
+
+        .dist-item {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          padding: 8px 12px;
+          background: white;
+          border-radius: 6px;
+
+          span {
+            font-size: 0.9rem;
+            color: #495057;
+
+            &.value {
+              font-weight: 700;
+              color: #667eea;
+            }
+          }
+        }
+      }
+    }
+  }
 }

+ 612 - 67
src/app/shared/components/requirements-confirm-card/requirements-confirm-card.ts

@@ -9,6 +9,10 @@ import { FullReportOverlayComponent } from '../full-report-overlay/full-report-o
 import { CadAnalysisService, CadAnalysisResult } from '../../services/cad-analysis.service';
 import { RequirementMappingService } from '../../../services/requirement-mapping.service';
 import { RequirementMapping, SceneTemplate } from '../../../models/requirement-mapping.interface';
+import { ColorWheelVisualizerComponent } from '../color-wheel-visualizer/color-wheel-visualizer';
+import { FurnitureFormSelectorComponent } from '../furniture-form-selector/furniture-form-selector';
+import { TextureComparisonVisualizerComponent } from '../texture-comparison-visualizer/texture-comparison-visualizer';
+import { PatternVisualizerComponent } from '../pattern-visualizer/pattern-visualizer';
 
 // 素材文件接口
 interface MaterialFile {
@@ -31,7 +35,7 @@ interface MaterialAnalysis {
   keywords?: { positive: string[]; negative: string[] };
   
   // 图片类解析
-  mainColors?: { rgb: string; percentage: number }[];
+  mainColors?: { hex: string; rgb: { r: number; g: number; b: number }; percentage: number; name?: string }[];
   colorTemperature?: string;
   materialRatio?: { material: string; percentage: number }[];
   spaceRatio?: number;
@@ -183,6 +187,25 @@ interface MaterialAnalysis {
       atmosphericEffects: { haze: number; glare: number; reflections: number; transparency: number };
       spatialPerception: { depth_enhancement: number; space_definition: number; focal_guidance: number };
     };
+    // 新增:光比分析
+    lightingRatio?: {
+      keyToFill: number;
+      contrast: number;
+      description: 'low-contrast' | 'medium-contrast' | 'high-contrast' | 'dramatic';
+      keyLightIntensity: number;
+      fillLightIntensity: number;
+      ratioQuality: 'flat' | 'balanced' | 'dramatic' | 'extreme';
+    };
+    // 新增:光占比分析
+    lightDistribution?: {
+      natural: number;
+      artificial: number;
+      ambient: number;
+      direct: number;
+      indirect: number;
+      dominantType: 'natural' | 'artificial' | 'mixed';
+      lightingBalance: 'natural-dominant' | 'artificial-dominant' | 'balanced';
+    };
   };
 }
 
@@ -235,7 +258,18 @@ interface UploadedFile {
 @Component({
   selector: 'app-requirements-confirm-card',
   standalone: true,
-  imports: [CommonModule, FormsModule, ReactiveFormsModule, UploadSuccessModalComponent, GlobalPromptComponent, FullReportOverlayComponent],
+  imports: [
+    CommonModule, 
+    FormsModule, 
+    ReactiveFormsModule, 
+    UploadSuccessModalComponent, 
+    GlobalPromptComponent, 
+    FullReportOverlayComponent, 
+    ColorWheelVisualizerComponent,
+    FurnitureFormSelectorComponent,
+    TextureComparisonVisualizerComponent,
+    PatternVisualizerComponent
+  ],
   providers: [CadAnalysisService],
   templateUrl: './requirements-confirm-card.html',
   styleUrls: ['./requirements-confirm-card.scss'],
@@ -277,9 +311,13 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
   requirementItems: RequirementItem[] = [];
 
   // 状态
-  activeTab: 'materials' | 'mapping' | 'collaboration' | 'progress' = 'materials';
+  activeTab: 'materials' | 'mapping' | 'collaboration' | 'progress' | 'analysis-summary' = 'materials';
   isUploading = false;
   isAnalyzing = false;
+  
+  // 新增:全展示模式
+  isFullDisplayMode = false;
+  expandedMaterials: Set<string> = new Set(); // 展开的素材ID集合
   dragOver = false;
   showConsistencyWarning = false;
   warningMessage = '';
@@ -294,7 +332,6 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
   // 流程状态
   stageCompletionStatus = {
     materialAnalysis: false,
-    requirementMapping: false,
     collaboration: false,
     progressReview: false
   };
@@ -575,23 +612,7 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
       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.uploadModalRequested.emit({
-        show: true,
-        uploadedFiles: this.uploadedFiles,
-        uploadType: this.uploadType,
-        colorAnalysisResult: this.colorAnalysisResult,
-        isAnalyzing: this.isAnalyzingColors
-      });
-      
       // 如果是图片类型,添加到需求映射界面的uploadedFiles数组(支持多图片)
       if (type === 'image') {
         // 检查是否已存在
@@ -609,8 +630,8 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
           
           console.log('✅ 图片已同步到需求映射,当前共', this.uploadedFiles.length, '张');
           
-          // 🔥 新增:立即触发需求映射界面的实时分析
-          this.triggerRealtimeAnalysis(uploadedFile);
+          // 🔥 显示上传成功弹窗并开始色彩分析
+          this.showUploadSuccessModalAndAnalyze(uploadedFile);
         }
       } else {
         // 非图片类型,保持原有逻辑
@@ -623,6 +644,15 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
         }];
       }
       
+      // 通知父组件显示上传成功弹窗
+      this.uploadModalRequested.emit({
+        show: true,
+        uploadedFiles: this.uploadedFiles,
+        uploadType: this.uploadType,
+        colorAnalysisResult: this.colorAnalysisResult,
+        isAnalyzing: this.isAnalyzingColors
+      });
+      
       this.uploadType = type === 'text' ? 'document' : type === 'image' ? 'image' : 'mixed';
       
       // 自动解析(素材分析用)
@@ -733,10 +763,11 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
 
   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 }
+        { hex: '#FFE6B4', rgb: { r: 255, g: 230, b: 180 }, percentage: 45, name: '暖木色' },
+        { hex: '#C8B4A0', rgb: { r: 200, g: 180, b: 160 }, percentage: 30, name: '米色' },
+        { hex: '#B4A08C', rgb: { r: 180, g: 160, b: 140 }, percentage: 25, name: '浅棕' }
       ],
       colorTemperature: '3000K',
       materialRatio: [
@@ -744,7 +775,190 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
         { material: '布艺', percentage: 25 },
         { material: '金属', percentage: 15 }
       ],
-      spaceRatio: 35
+      spaceRatio: 35,
+      
+      // 增强色彩分析
+      enhancedColorAnalysis: {
+        colorWheel: {
+          primaryHue: 35,
+          saturation: 65,
+          brightness: 85,
+          dominantColors: [
+            { hue: 35, saturation: 65, brightness: 85, percentage: 45 },
+            { hue: 30, saturation: 50, brightness: 75, percentage: 30 },
+            { hue: 25, saturation: 55, brightness: 70, percentage: 25 }
+          ],
+          colorDistribution: '暖色调为主,分布均衡'
+        },
+        colorHarmony: {
+          harmonyType: '类比色',
+          harmonyScore: 85,
+          complementaryColors: ['#4A90E2', '#5FA3E8'],
+          suggestions: ['可添加蓝色系装饰增强对比', '保持暖色调主导']
+        },
+        colorTemperatureAnalysis: {
+          kelvin: 3000,
+          warmCoolBalance: 75,
+          description: '温暖舒适',
+          lightingRecommendations: ['建议使用暖白光源', '可配合自然光']
+        },
+        colorPsychology: {
+          primaryMood: '温馨放松',
+          atmosphere: '家庭温暖',
+          psychologicalEffects: ['促进放松', '增强归属感', '营造安全感'],
+          suitableSpaces: ['客厅', '卧室', '休闲区']
+        }
+      },
+      
+      // 形体分析
+      formAnalysis: {
+        lineAnalysis: {
+          dominantLines: [
+            { type: '直线', percentage: 60, characteristics: ['简洁', '现代'] },
+            { type: '曲线', percentage: 30, characteristics: ['柔和', '优雅'] },
+            { type: '斜线', percentage: 10, characteristics: ['动感'] }
+          ],
+          lineQuality: { smoothness: 85, precision: 90, expressiveness: 75 },
+          visualFlow: { direction: '水平为主', rhythm: '平稳', continuity: 80 }
+        },
+        geometryAnalysis: {
+          primaryShapes: [
+            { shape: '矩形', percentage: 55, characteristics: ['稳定', '规整'] },
+            { shape: '圆形', percentage: 30, characteristics: ['柔和', '完整'] },
+            { shape: '不规则', percentage: 15, characteristics: ['自然'] }
+          ],
+          complexity: '中等',
+          symmetry: { type: '对称', score: 75 },
+          balance: { visual: 80, compositional: 85 }
+        },
+        proportionAnalysis: {
+          aspectRatio: 1.618,
+          goldenRatio: 0.85,
+          proportionHarmony: 80,
+          scaleRelationships: [
+            { element: '家具', scale: '1:1.5', relationship: '协调' },
+            { element: '装饰', scale: '1:3', relationship: '点缀' }
+          ]
+        },
+        overallAssessment: {
+          formComplexity: 65,
+          visualImpact: 75,
+          functionalSuitability: 85,
+          aestheticAppeal: 80
+        }
+      },
+      
+      // 质感分析
+      textureAnalysis: {
+        surfaceProperties: {
+          roughness: { value: 45, level: '中等粗糙', description: '自然木纹质感' },
+          glossiness: { value: 35, level: '哑光', reflectivity: 25 },
+          transparency: { value: 10, level: '不透明' },
+          porosity: { value: 30, level: '中等' }
+        },
+        tactilePredict: {
+          temperature: { perceived: '温暖', thermalConductivity: 0.15 },
+          hardness: { level: '中硬', value: 60 },
+          flexibility: { level: '刚性', value: 20 },
+          weight: { perceived: '中等', density: 0.6 },
+          comfort: { tactileComfort: 75, ergonomicSuitability: 80 }
+        },
+        materialClassification: {
+          primaryMaterial: { category: '木材', subcategory: '实木', confidence: 85 },
+          secondaryMaterials: [
+            { category: '布艺', percentage: 25, confidence: 70 },
+            { category: '金属', percentage: 15, confidence: 60 }
+          ],
+          materialProperties: { durability: 80, maintenance: '中等', sustainability: 75, costLevel: '中高' }
+        },
+        textureQuality: {
+          overallQuality: 85,
+          consistency: 80,
+          authenticity: 90,
+          visualAppeal: 85,
+          functionalSuitability: 80,
+          ageingCharacteristics: { wearResistance: 75, patinaPotential: 60, maintenanceNeeds: ['定期清洁', '防潮处理'] }
+        }
+      },
+      
+      // 纹理分析
+      patternAnalysis: {
+        patternRecognition: {
+          primaryPatterns: [
+            { type: '木纹', confidence: 90, coverage: 60, characteristics: ['自然', '有机'] },
+            { type: '编织', confidence: 75, coverage: 25, characteristics: ['规律', '几何'] },
+            { type: '金属拉丝', confidence: 70, coverage: 15, characteristics: ['线性', '现代'] }
+          ],
+          patternComplexity: { level: '中等', score: 65, elements: 3 },
+          patternScale: { size: '中等', uniformity: 70, variation: 30 }
+        },
+        repetitionAnalysis: {
+          repetitionType: { primary: '有机重复', pattern: '自然纹理', consistency: 60 },
+          spacing: { horizontal: 50, vertical: 50, uniformity: 70, rhythm: '平稳' },
+          symmetry: { type: '近似对称', strength: 60, axes: 1 },
+          tiling: { seamless: false, quality: 75, edgeHandling: '自然过渡' }
+        },
+        textureClassification: {
+          textureFamily: { primary: '木质纹理', subcategory: '硬木', confidence: 85 },
+          surfaceCharacter: { tactileQuality: '细腻', visualDepth: 70, dimensionality: '中等立体' },
+          materialSuggestion: { likelyMaterials: ['橡木', '胡桃木'], fabricationMethod: ['实木加工', '贴面'], applicationSuitability: ['家具', '地板', '墙面'] }
+        },
+        visualRhythm: {
+          rhythmType: { primary: '有机节奏', intensity: 65, tempo: '平稳' },
+          movement: { direction: '纵向', flow: 70, energy: 55 },
+          emphasis: { focalPoints: 2, contrast: 60, hierarchy: 65 },
+          harmony: { overall: 80, balance: 75, unity: 85 }
+        }
+      },
+      
+      // 灯光分析
+      lightingAnalysis: {
+        lightSourceIdentification: {
+          primarySources: [
+            { type: '自然光', subtype: '侧窗光', position: { direction: '左侧', angle: 45, distance: '2-3m' }, intensity: 70, confidence: 85 },
+            { type: '人工光', subtype: '顶灯', position: { direction: '正上方', angle: 90, distance: '2.5m' }, intensity: 50, confidence: 75 }
+          ],
+          lightingSetup: { complexity: '简单', sourceCount: 2, dominantSource: '自然光', lightingStyle: '自然柔和' }
+        },
+        illuminationAnalysis: {
+          brightness: { overall: 65, distribution: '均匀', dynamicRange: 60, exposure: '适中' },
+          contrast: { level: 60, type: '中等对比', areas: { highlights: 80, midtones: 65, shadows: 40 } },
+          colorTemperature: { kelvin: 3000, warmth: '温暖', consistency: 75, mixedLighting: true },
+          lightQuality: { softness: 75, diffusion: 80, directionality: 55, evenness: 70 }
+        },
+        shadowAnalysis: {
+          shadowPresence: { coverage: 35, intensity: 50, sharpness: '柔和', definition: 60 },
+          shadowCharacteristics: { direction: '右下', length: '中等', density: 50, falloff: '渐变' },
+          shadowTypes: [
+            { type: 'cast', prominence: 60, contribution: '主要阴影' },
+            { type: 'form', prominence: 40, contribution: '体积感' }
+          ],
+          dimensionalEffect: { depth: 70, volume: 65, threedimensionality: 70 }
+        },
+        ambientAnalysis: {
+          ambientLevel: { strength: 60, uniformity: 75, contribution: 50 },
+          lightingMood: { primary: '温馨舒适', intensity: 70, emotional_impact: ['放松', '温暖', '自然'] },
+          atmosphericEffects: { haze: 10, glare: 15, reflections: 40, transparency: 90 },
+          spatialPerception: { depth_enhancement: 65, space_definition: 70, focal_guidance: 60 }
+        },
+        lightingRatio: {
+          keyToFill: 3,
+          contrast: 60,
+          description: 'medium-contrast',
+          keyLightIntensity: 75,
+          fillLightIntensity: 25,
+          ratioQuality: 'balanced'
+        },
+        lightDistribution: {
+          natural: 60,
+          artificial: 40,
+          ambient: 50,
+          direct: 30,
+          indirect: 70,
+          dominantType: 'natural',
+          lightingBalance: 'natural-dominant'
+        }
+      }
     };
   }
 
@@ -1445,10 +1659,246 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
   }
 
   // 切换标签页
-  switchTab(tab: 'materials' | 'mapping' | 'collaboration' | 'progress') {
+  switchTab(tab: 'materials' | 'mapping' | 'collaboration' | 'progress' | 'analysis-summary') {
     this.activeTab = tab;
   }
 
+  /**
+   * 切换全展示模式
+   */
+  toggleFullDisplayMode(): void {
+    this.isFullDisplayMode = !this.isFullDisplayMode;
+    
+    if (this.isFullDisplayMode) {
+      // 展开所有素材
+      this.materials.forEach(material => {
+        this.expandedMaterials.add(material.id);
+      });
+    } else {
+      // 折叠所有素材
+      this.expandedMaterials.clear();
+    }
+  }
+
+  /**
+   * 切换单个素材的展开状态
+   */
+  toggleMaterialExpansion(materialId: string): void {
+    if (this.expandedMaterials.has(materialId)) {
+      this.expandedMaterials.delete(materialId);
+    } else {
+      this.expandedMaterials.add(materialId);
+    }
+  }
+
+  /**
+   * 检查素材是否已展开
+   */
+  isMaterialExpanded(materialId: string): boolean {
+    return this.expandedMaterials.has(materialId);
+  }
+
+  /**
+   * 🔥 自动生成需求映射(后台触发,无UI交互)
+   */
+  private autoGenerateRequirementMapping(analysisResult: ColorAnalysisResult): void {
+    console.log('🎯 开始后台生成需求映射...');
+    
+    this.isGeneratingMapping = true;
+    this.mappingError = null;
+    
+    // 更新测试步骤
+    const mappingStep = this.testSteps.find(s => s.id === 'mapping');
+    if (mappingStep) {
+      mappingStep.status = 'in-progress';
+    }
+    
+    // 使用需求映射服务生成映射
+    this.requirementMappingService.generateRequirementMapping(analysisResult).subscribe({
+      next: (mapping: RequirementMapping) => {
+        this.requirementMapping = mapping;
+        this.isGeneratingMapping = false;
+        
+        // 更新步骤状态
+        if (mappingStep) {
+          mappingStep.status = 'completed';
+        }
+        
+        console.log('✅ 需求映射生成完成(后台)');
+        
+        // 发射数据更新事件
+        this.emitMappingDataUpdate();
+        
+        // 显示成功提示
+        this.promptTitle = '需求映射完成';
+        this.promptMessage = '系统已自动生成场景参数和氛围预览';
+        this.promptMode = 'corner';
+        this.promptPosition = 'bottom-right';
+        this.showGlobalPrompt = true;
+      },
+      error: (error: any) => {
+        this.mappingError = '映射生成失败';
+        this.isGeneratingMapping = false;
+        
+        if (mappingStep) {
+          mappingStep.status = 'error';
+        }
+        
+        console.error('❌ 需求映射生成失败:', error);
+      }
+    });
+  }
+
+  /**
+   * 导出分析报告(JSON格式)
+   */
+  exportAnalysisReportJSON(): void {
+    const reportData = this.generateReportData();
+    const blob = new Blob([JSON.stringify(reportData, null, 2)], { type: 'application/json' });
+    const url = window.URL.createObjectURL(blob);
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = `素材分析报告_${new Date().toISOString().split('T')[0]}.json`;
+    link.click();
+    window.URL.revokeObjectURL(url);
+  }
+
+  /**
+   * 导出分析报告(HTML格式)
+   */
+  exportAnalysisReportHTML(): void {
+    const reportData = this.generateReportData();
+    const htmlContent = this.generateHTMLReport(reportData);
+    const blob = new Blob([htmlContent], { type: 'text/html' });
+    const url = window.URL.createObjectURL(blob);
+    const link = document.createElement('a');
+    link.href = url;
+    link.download = `素材分析报告_${new Date().toISOString().split('T')[0]}.html`;
+    link.click();
+    window.URL.revokeObjectURL(url);
+  }
+
+  /**
+   * 生成报告数据
+   */
+  private generateReportData(): any {
+    return {
+      projectId: this.projectId,
+      reportDate: new Date().toISOString(),
+      materials: this.materials.map(material => ({
+        id: material.id,
+        name: material.name,
+        type: material.type,
+        uploadTime: material.uploadTime,
+        analysis: material.analysis
+      })),
+      summary: {
+        totalMaterials: this.materials.length,
+        textCount: this.materials.filter(m => m.type === 'text').length,
+        imageCount: this.materials.filter(m => m.type === 'image').length,
+        cadCount: this.materials.filter(m => m.type === 'cad').length
+      }
+    };
+  }
+
+  /**
+   * 生成HTML报告
+   */
+  private generateHTMLReport(data: any): string {
+    return `
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>素材分析报告</title>
+  <style>
+    body {
+      font-family: 'Microsoft YaHei', Arial, sans-serif;
+      line-height: 1.6;
+      color: #333;
+      max-width: 1200px;
+      margin: 0 auto;
+      padding: 20px;
+      background: #f5f5f5;
+    }
+    .header {
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      color: white;
+      padding: 30px;
+      border-radius: 8px;
+      margin-bottom: 30px;
+    }
+    .header h1 {
+      margin: 0 0 10px 0;
+    }
+    .summary {
+      background: white;
+      padding: 20px;
+      border-radius: 8px;
+      margin-bottom: 20px;
+      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+    }
+    .material-item {
+      background: white;
+      padding: 20px;
+      border-radius: 8px;
+      margin-bottom: 20px;
+      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+    }
+    .material-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 15px;
+      padding-bottom: 15px;
+      border-bottom: 2px solid #f0f0f0;
+    }
+    .analysis-section {
+      margin-top: 15px;
+    }
+    .analysis-section h4 {
+      color: #667eea;
+      margin: 10px 0;
+    }
+  </style>
+</head>
+<body>
+  <div class="header">
+    <h1>素材分析报告</h1>
+    <p>生成时间: ${new Date(data.reportDate).toLocaleString('zh-CN')}</p>
+    ${data.projectId ? `<p>项目ID: ${data.projectId}</p>` : ''}
+  </div>
+  
+  <div class="summary">
+    <h2>汇总信息</h2>
+    <p>总素材数: ${data.summary.totalMaterials}</p>
+    <p>文本: ${data.summary.textCount} | 图片: ${data.summary.imageCount} | CAD: ${data.summary.cadCount}</p>
+  </div>
+  
+  ${data.materials.map((material: any, index: number) => `
+    <div class="material-item">
+      <div class="material-header">
+        <h3>${index + 1}. ${material.name}</h3>
+        <span class="badge">${material.type === 'text' ? '文本' : material.type === 'image' ? '图片' : 'CAD'}</span>
+      </div>
+      ${material.analysis ? `
+        <div class="analysis-section">
+          <h4>分析结果</h4>
+          <pre>${JSON.stringify(material.analysis, null, 2)}</pre>
+        </div>
+      ` : '<p>暂无分析数据</p>'}
+    </div>
+  `).join('')}
+  
+  <div class="footer" style="text-align: center; margin-top: 40px; color: #999;">
+    <p>报告生成于 ${new Date().toLocaleString('zh-CN')}</p>
+  </div>
+</body>
+</html>
+    `.trim();
+  }
+
   // 获取需求状态文本
   getRequirementStatusText(status: string): string {
     const statusMap: { [key: string]: string } = {
@@ -1574,22 +2024,15 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
 
   // 检查阶段完成状态
   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%以上
+    // 检查进度管理阶段 - 需要进度达到80%以上
     this.stageCompletionStatus.progressReview = this.getProgressPercentage() >= 80;
     
     console.log('阶段完成状态更新:', this.stageCompletionStatus);
@@ -1651,7 +2094,7 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
       return '已完成';
     } else {
       // 检查是否为未开始状态
-      const stages = ['materialAnalysis', 'requirementMapping', 'collaboration', 'progressReview'];
+      const stages = ['materialAnalysis', 'collaboration', 'progressReview'];
       const currentIndex = stages.indexOf(stage);
       
       // 检查前面的阶段是否都已完成
@@ -1673,7 +2116,7 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
       return 'stage-completed';
     } else {
       // 检查是否为未开始状态
-      const stages = ['materialAnalysis', 'requirementMapping', 'collaboration', 'progressReview'];
+      const stages = ['materialAnalysis', 'collaboration', 'progressReview'];
       const currentIndex = stages.indexOf(stage);
       
       // 检查前面的阶段是否都已完成
@@ -2324,6 +2767,38 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
     return nameMap[flow] || flow;
   }
 
+  // 新增:获取光比描述的中文翻译
+  getRatioDescription(description: string): string {
+    const nameMap: { [key: string]: string } = {
+      'low-contrast': '低反差',
+      'medium-contrast': '中等反差',
+      'high-contrast': '高反差',
+      'dramatic': '戏剧化'
+    };
+    return nameMap[description] || description;
+  }
+
+  // 新增:获取光平衡描述的中文翻译
+  getBalanceDescription(balance: string): string {
+    const nameMap: { [key: string]: string } = {
+      'natural-dominant': '自然光主导',
+      'artificial-dominant': '人工光主导',
+      'balanced': '光源平衡'
+    };
+    return nameMap[balance] || balance;
+  }
+
+  // 形体选择事件处理
+  onFormSelected(form: any): void {
+    console.log('✅ 选择形体:', form);
+    // 可以在这里保存选择的形体数据到材料分析中
+  }
+
+  onFormDeselected(form: any): void {
+    console.log('❌ 取消选择形体:', form);
+    // 可以在这里移除取消选择的形体数据
+  }
+
   // 移除需求映射界面中已上传的文件
   removeUploadedFile(fileId: string): void {
     const index = this.uploadedFiles.findIndex(f => f.id === fileId);
@@ -2354,49 +2829,88 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
     }
   }
 
+  /**
+   * 🔥 显示上传成功弹窗并开始色彩分析
+   */
+  private showUploadSuccessModalAndAnalyze(uploadedFile: any): void {
+    console.log('🎨 显示上传成功弹窗并开始色彩分析:', uploadedFile.name);
+    
+    // 1. 显示弹窗(不显示全局提示,避免干扰)
+    this.showUploadSuccessModal = true;
+    this.showGlobalPrompt = false; // 关闭全局提示
+    this.uploadType = 'image';
+    this.isAnalyzingColors = true;
+    
+    // 2. 开始色彩分析(用于弹窗显示)
+    this.colorAnalysisService.analyzeImage(uploadedFile).subscribe({
+      next: (result) => {
+        this.colorAnalysisResult = result;
+        this.isAnalyzingColors = false;
+        console.log('✅ 色彩分析完成(弹窗):', result);
+        
+        // 3. 色彩分析完成后,触发后台需求映射生成
+        setTimeout(() => {
+          this.triggerRealtimeAnalysis(uploadedFile);
+        }, 500);
+      },
+      error: (error) => {
+        this.isAnalyzingColors = false;
+        console.error('❌ 色彩分析失败:', error);
+      }
+    });
+  }
+
   // 实时触发分析(每上传一张图片就分析一张)
   private triggerRealtimeAnalysis(newFile: any): void {
-    console.log('🔄 触发实时分析:', newFile.name);
+    console.log('🚀 触发实时分析:', newFile.name);
+    
+    // 自动开始分析
+    setTimeout(() => {
+      this.performAutoAnalysis(newFile);
+    }, 500);
+  }
+
+  /**
+   * 执行自动分析和映射生成
+   */
+  private performAutoAnalysis(file: any): void {
+    this.isAnalyzing = true;
+    this.analysisError = null;
     
-    // 第一张图片:初始化分析状态
-    if (this.uploadedFiles.length === 1) {
-      this.updateStepStatus('upload', 'completed');
-      this.updateStepStatus('analysis', 'in-progress');
-      this.isAnalyzing = true;
+    // 更新测试步骤
+    const analysisStep = this.testSteps.find(s => s.id === 'analysis');
+    if (analysisStep) {
+      analysisStep.status = 'in-progress';
     }
     
-    // 分析新上传的图片
-    this.colorAnalysisService.analyzeImage(newFile).subscribe({
-      next: (newResult: ColorAnalysisResult) => {
-        console.log('📊 新图片分析完成:', newFile.name);
+    // 使用色彩分析服务
+    this.colorAnalysisService.analyzeImage(file).subscribe({
+      next: (result: ColorAnalysisResult) => {
+        this.analysisResult = result;
+        this.isAnalyzing = false;
         
-        // 如果是第一张图片,直接设置结果
-        if (!this.analysisResult) {
-          this.analysisResult = newResult;
-          console.log('✅ 设置第一张图片的分析结果');
-        } else {
-          // 如果已有结果,合并新结果
-          this.analysisResult = this.mergeAnalysisResults([this.analysisResult, newResult]);
-          console.log('🔗 合并新图片的分析结果,当前共', this.uploadedFiles.length, '张');
+        // 更新步骤状态
+        if (analysisStep) {
+          analysisStep.status = 'completed';
         }
         
-        // 检查是否所有图片都已分析完成
-        // 这里简化处理:每次上传都会触发分析,所以当前图片数量 = 已分析数量
-        this.isAnalyzing = false;
-        this.updateStepStatus('analysis', 'completed');
+        console.log('✅ 分析完成,自动触发需求映射生成');
         
-        // 🔥 自动触发需求映射生成
-        this.startRequirementMapping();
+        // 🔥 关键:分析完成后自动触发需求映射
+        this.autoGenerateRequirementMapping(result);
         
-        // 发送映射数据更新事件给父组件
+        // 发射数据更新事件
         this.emitMappingDataUpdate();
-        
-        this.cdr.detectChanges(); // 触发界面更新
       },
       error: (error: any) => {
-        console.error('实时分析失败:', error);
-        // 不设置为error状态,因为可能还有其他图片在分析
+        this.analysisError = '分析失败,请重试';
         this.isAnalyzing = false;
+        
+        if (analysisStep) {
+          analysisStep.status = 'error';
+        }
+        
+        console.error('❌ 分析失败:', error);
       }
     });
   }
@@ -2469,4 +2983,35 @@ export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
       requirementMapping: transformedMapping
     });
   }
+
+  // 生成质感对比数据
+  getTextureDataForMaterial(material: MaterialFile): any[] {
+    if (!material.analysis?.textureAnalysis?.surfaceProperties || 
+        !material.analysis?.textureAnalysis?.materialClassification?.primaryMaterial) {
+      return [];
+    }
+
+    const textureAnalysis = material.analysis.textureAnalysis;
+    const materialClassification = textureAnalysis.materialClassification;
+    const primaryMaterial = materialClassification?.primaryMaterial;
+    const surfaceProps = textureAnalysis.surfaceProperties;
+
+    if (!primaryMaterial || !surfaceProps) {
+      return [];
+    }
+
+    return [{
+      materialName: primaryMaterial.category || '未知材质',
+      materialType: primaryMaterial.category || '未知类型',
+      properties: [
+        { name: '粗糙度', value: surfaceProps.roughness?.value || 50, category: 'tactile' as const },
+        { name: '光泽度', value: surfaceProps.glossiness?.value || 40, category: 'visual' as const },
+        { name: '反射率', value: surfaceProps.glossiness?.reflectivity || 30, category: 'reflective' as const },
+        { name: '触感柔软度', value: 60, category: 'tactile' as const },
+        { name: '纹理密度', value: 50, category: 'visual' as const },
+        { name: '视觉温暖度', value: 55, category: 'visual' as const }
+      ],
+      dominantCharacteristic: 'mixed' as const
+    }];
+  }
 }

+ 151 - 0
src/app/shared/components/texture-comparison-visualizer/texture-comparison-visualizer.html

@@ -0,0 +1,151 @@
+<div class="texture-comparison-visualizer">
+  <div class="visualizer-header">
+    <h6>质感对比分析</h6>
+    <div class="material-count">{{ textures.length }} 种材质</div>
+  </div>
+
+  @if (textures.length === 0) {
+    <div class="empty-state">
+      <p>暂无质感数据</p>
+    </div>
+  }
+
+  <!-- 材质卡片网格 -->
+  <div class="texture-cards-grid">
+    @for (texture of textures; track texture.materialName) {
+      <div class="texture-card">
+        <!-- 材质缩略图 -->
+        @if (texture.thumbnailUrl) {
+          <div class="texture-thumbnail">
+            <img [src]="texture.thumbnailUrl" [alt]="texture.materialName">
+          </div>
+        }
+
+        <!-- 材质信息 -->
+        <div class="texture-info">
+          <h6 class="material-name">{{ texture.materialName }}</h6>
+          <span class="material-type">{{ texture.materialType }}</span>
+          <span 
+            class="characteristic-badge" 
+            [style.background-color]="getCharacteristicColor(texture.dominantCharacteristic) + '20'"
+            [style.color]="getCharacteristicColor(texture.dominantCharacteristic)">
+            {{ getCharacteristicName(texture.dominantCharacteristic) }}
+          </span>
+        </div>
+
+        <!-- 雷达图 -->
+        @if (showRadarChart) {
+          <div class="radar-chart-container">
+            <svg width="120" height="120" viewBox="0 0 120 120">
+              <!-- 网格 -->
+              <g class="radar-grid">
+                @for (gridPath of getRadarGridPaths(); track $index) {
+                  <path
+                    [attr.d]="gridPath"
+                    fill="none"
+                    stroke="#e0e0e0"
+                    stroke-width="0.5"
+                  />
+                }
+                
+                <!-- 轴线 -->
+                @for (prop of predefinedProperties; track $index) {
+                  <line
+                    x1="60"
+                    y1="60"
+                    [attr.x2]="getRadarLabelPosition($index).x"
+                    [attr.y2]="getRadarLabelPosition($index).y"
+                    stroke="#ddd"
+                    stroke-width="0.5"
+                  />
+                }
+              </g>
+
+              <!-- 数据区域 -->
+              <path
+                [attr.d]="getRadarPath(texture)"
+                [attr.fill]="getCharacteristicColor(texture.dominantCharacteristic) + '40'"
+                [attr.stroke]="getCharacteristicColor(texture.dominantCharacteristic)"
+                stroke-width="2"
+              />
+
+              <!-- 数据点 -->
+              @for (prop of predefinedProperties; track $index) {
+                <circle
+                  [attr.cx]="60 + (getPropertyValue(texture, prop) / 100 * 50) * Math.cos(2 * Math.PI * $index / predefinedProperties.length - Math.PI / 2)"
+                  [attr.cy]="60 + (getPropertyValue(texture, prop) / 100 * 50) * Math.sin(2 * Math.PI * $index / predefinedProperties.length - Math.PI / 2)"
+                  r="3"
+                  [attr.fill]="getCharacteristicColor(texture.dominantCharacteristic)"
+                />
+              }
+
+              <!-- 属性标签 -->
+              @for (prop of predefinedProperties; track $index) {
+                <text
+                  [attr.x]="getRadarLabelPosition($index).x"
+                  [attr.y]="getRadarLabelPosition($index).y + 12"
+                  text-anchor="middle"
+                  class="radar-label"
+                  font-size="8">
+                  {{ prop }}
+                </text>
+              }
+            </svg>
+          </div>
+        }
+
+        <!-- 属性条形图 -->
+        @if (showPropertyBars) {
+          <div class="property-bars">
+            @for (prop of texture.properties; track prop.name) {
+              <div class="property-item">
+                <div class="property-header">
+                  <span class="property-name">
+                    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                      <path [attr.d]="getCategoryIcon(prop.category)"></path>
+                    </svg>
+                    {{ prop.name }}
+                  </span>
+                  <span class="property-value">{{ prop.value }}{{ prop.unit || '%' }}</span>
+                </div>
+                <div class="property-bar-container">
+                  <div 
+                    class="property-bar-fill" 
+                    [style.width.%]="prop.value"
+                    [style.background-color]="getCharacteristicColor(texture.dominantCharacteristic)">
+                  </div>
+                </div>
+              </div>
+            }
+          </div>
+        }
+      </div>
+    }
+  </div>
+
+  <!-- 对比总结 -->
+  @if (textures.length > 1) {
+    <div class="comparison-summary">
+      <h6>对比总结</h6>
+      <div class="summary-grid">
+        <div class="summary-item">
+          <span class="label">最光滑:</span>
+          <span class="value">{{ getSmoothestMaterial() }}</span>
+        </div>
+        <div class="summary-item">
+          <span class="label">最粗糙:</span>
+          <span class="value">{{ getRoughestMaterial() }}</span>
+        </div>
+        <div class="summary-item">
+          <span class="label">最高光泽:</span>
+          <span class="value">{{ getGlossiestMaterial() }}</span>
+        </div>
+        <div class="summary-item">
+          <span class="label">最哑光:</span>
+          <span class="value">{{ getMattestMaterial() }}</span>
+        </div>
+      </div>
+    </div>
+  }
+</div>
+

+ 200 - 0
src/app/shared/components/texture-comparison-visualizer/texture-comparison-visualizer.scss

@@ -0,0 +1,200 @@
+.texture-comparison-visualizer {
+  padding: 16px;
+  background: white;
+  border-radius: 8px;
+  
+  .visualizer-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+    
+    h6 {
+      margin: 0;
+      font-size: 15px;
+      font-weight: 600;
+      color: #333;
+    }
+    
+    .material-count {
+      font-size: 12px;
+      color: #666;
+      background: #f0f0f0;
+      padding: 4px 10px;
+      border-radius: 12px;
+    }
+  }
+  
+  .empty-state {
+    padding: 40px 20px;
+    text-align: center;
+    color: #999;
+    font-size: 14px;
+  }
+  
+  .texture-cards-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+    gap: 16px;
+    margin-bottom: 16px;
+    
+    .texture-card {
+      background: #f8f9fa;
+      border-radius: 8px;
+      padding: 16px;
+      border: 1px solid #e0e0e0;
+      transition: all 0.3s ease;
+      
+      &:hover {
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+        transform: translateY(-2px);
+      }
+      
+      .texture-thumbnail {
+        width: 100%;
+        height: 120px;
+        border-radius: 6px;
+        overflow: hidden;
+        margin-bottom: 12px;
+        background: #e0e0e0;
+        
+        img {
+          width: 100%;
+          height: 100%;
+          object-fit: cover;
+        }
+      }
+      
+      .texture-info {
+        margin-bottom: 16px;
+        
+        .material-name {
+          margin: 0 0 4px 0;
+          font-size: 14px;
+          font-weight: 600;
+          color: #333;
+        }
+        
+        .material-type {
+          display: inline-block;
+          font-size: 11px;
+          color: #666;
+          background: white;
+          padding: 2px 8px;
+          border-radius: 4px;
+          margin-right: 6px;
+        }
+        
+        .characteristic-badge {
+          display: inline-block;
+          font-size: 11px;
+          font-weight: 600;
+          padding: 2px 8px;
+          border-radius: 4px;
+        }
+      }
+      
+      .radar-chart-container {
+        display: flex;
+        justify-content: center;
+        margin-bottom: 16px;
+        
+        svg {
+          filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.05));
+          
+          .radar-label {
+            fill: #666;
+            font-size: 8px;
+          }
+        }
+      }
+      
+      .property-bars {
+        display: flex;
+        flex-direction: column;
+        gap: 10px;
+        
+        .property-item {
+          .property-header {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: 4px;
+            
+            .property-name {
+              display: flex;
+              align-items: center;
+              gap: 4px;
+              font-size: 11px;
+              color: #666;
+              
+              svg {
+                width: 12px;
+                height: 12px;
+                stroke: #999;
+              }
+            }
+            
+            .property-value {
+              font-size: 11px;
+              font-weight: 600;
+              color: #333;
+            }
+          }
+          
+          .property-bar-container {
+            width: 100%;
+            height: 6px;
+            background: #e0e0e0;
+            border-radius: 3px;
+            overflow: hidden;
+            
+            .property-bar-fill {
+              height: 100%;
+              transition: width 0.3s ease;
+              border-radius: 3px;
+            }
+          }
+        }
+      }
+    }
+  }
+  
+  .comparison-summary {
+    padding: 16px;
+    background: linear-gradient(135deg, #f0f7ff 0%, #e6f3ff 100%);
+    border-radius: 8px;
+    border: 1px solid #d0e7ff;
+    
+    h6 {
+      margin: 0 0 12px 0;
+      font-size: 14px;
+      font-weight: 600;
+      color: #007AFF;
+    }
+    
+    .summary-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+      gap: 12px;
+      
+      .summary-item {
+        display: flex;
+        flex-direction: column;
+        gap: 4px;
+        
+        .label {
+          font-size: 11px;
+          color: #666;
+        }
+        
+        .value {
+          font-size: 13px;
+          font-weight: 600;
+          color: #333;
+        }
+      }
+    }
+  }
+}
+

+ 272 - 0
src/app/shared/components/texture-comparison-visualizer/texture-comparison-visualizer.ts

@@ -0,0 +1,272 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+export interface TextureProperty {
+  name: string;
+  value: number; // 0-100
+  unit?: string;
+  description?: string;
+  category: 'visual' | 'tactile' | 'reflective';
+}
+
+export interface MaterialTextureData {
+  materialName: string;
+  materialType: string;
+  properties: TextureProperty[];
+  thumbnailUrl?: string;
+  dominantCharacteristic: 'glossy' | 'matte' | 'rough' | 'smooth' | 'mixed';
+}
+
+@Component({
+  selector: 'app-texture-comparison-visualizer',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './texture-comparison-visualizer.html',
+  styleUrls: ['./texture-comparison-visualizer.scss']
+})
+export class TextureComparisonVisualizerComponent implements OnInit {
+  @Input() textures: MaterialTextureData[] = [];
+  @Input() showRadarChart: boolean = true; // 是否显示雷达图
+  @Input() showPropertyBars: boolean = true; // 是否显示属性条形图
+
+  // 预定义的质感属性
+  predefinedProperties: string[] = [
+    '光泽度',
+    '粗糙度',
+    '反射率',
+    '触感柔软度',
+    '纹理密度',
+    '视觉温暖度'
+  ];
+
+  ngOnInit(): void {
+    this.normalizeTextureData();
+  }
+
+  /**
+   * 标准化质感数据,确保所有材质都有相同的属性
+   */
+  private normalizeTextureData(): void {
+    this.textures.forEach(texture => {
+      this.predefinedProperties.forEach(propName => {
+        if (!texture.properties.find(p => p.name === propName)) {
+          texture.properties.push({
+            name: propName,
+            value: 50, // 默认值
+            category: this.getCategoryByPropertyName(propName)
+          });
+        }
+      });
+    });
+  }
+
+  /**
+   * 根据属性名获取分类
+   */
+  private getCategoryByPropertyName(name: string): 'visual' | 'tactile' | 'reflective' {
+    if (name.includes('光泽') || name.includes('反射') || name.includes('视觉')) {
+      return 'visual';
+    }
+    if (name.includes('触感') || name.includes('柔软') || name.includes('粗糙')) {
+      return 'tactile';
+    }
+    return 'reflective';
+  }
+
+  /**
+   * 获取质感主导特征的中文名
+   */
+  getCharacteristicName(characteristic: string): string {
+    const nameMap: { [key: string]: string } = {
+      'glossy': '光泽',
+      'matte': '哑光',
+      'rough': '粗糙',
+      'smooth': '光滑',
+      'mixed': '混合'
+    };
+    return nameMap[characteristic] || characteristic;
+  }
+
+  /**
+   * 获取质感特征的颜色
+   */
+  getCharacteristicColor(characteristic: string): string {
+    const colorMap: { [key: string]: string } = {
+      'glossy': '#007AFF',
+      'matte': '#8E8E93',
+      'rough': '#FF9500',
+      'smooth': '#34C759',
+      'mixed': '#5856D6'
+    };
+    return colorMap[characteristic] || '#666';
+  }
+
+  /**
+   * 生成雷达图SVG路径
+   */
+  getRadarPath(texture: MaterialTextureData): string {
+    const centerX = 60;
+    const centerY = 60;
+    const maxRadius = 50;
+    const angleStep = (2 * Math.PI) / this.predefinedProperties.length;
+
+    const points: string[] = [];
+
+    this.predefinedProperties.forEach((propName, index) => {
+      const prop = texture.properties.find(p => p.name === propName);
+      const value = prop ? prop.value : 50;
+      const radius = (value / 100) * maxRadius;
+      const angle = angleStep * index - Math.PI / 2;
+      const x = centerX + radius * Math.cos(angle);
+      const y = centerY + radius * Math.sin(angle);
+      points.push(`${x},${y}`);
+    });
+
+    return `M ${points.join(' L ')} Z`;
+  }
+
+  /**
+   * 生成雷达图网格
+   */
+  getRadarGridPaths(): string[] {
+    const centerX = 60;
+    const centerY = 60;
+    const maxRadius = 50;
+    const angleStep = (2 * Math.PI) / this.predefinedProperties.length;
+    const gridLevels = [0.2, 0.4, 0.6, 0.8, 1.0];
+
+    return gridLevels.map(level => {
+      const points: string[] = [];
+      this.predefinedProperties.forEach((_, index) => {
+        const radius = maxRadius * level;
+        const angle = angleStep * index - Math.PI / 2;
+        const x = centerX + radius * Math.cos(angle);
+        const y = centerY + radius * Math.sin(angle);
+        points.push(`${x},${y}`);
+      });
+      return `M ${points.join(' L ')} Z`;
+    });
+  }
+
+  /**
+   * 获取雷达图标签位置
+   */
+  getRadarLabelPosition(index: number): { x: number; y: number } {
+    const centerX = 60;
+    const centerY = 60;
+    const labelRadius = 58;
+    const angleStep = (2 * Math.PI) / this.predefinedProperties.length;
+    const angle = angleStep * index - Math.PI / 2;
+
+    return {
+      x: centerX + labelRadius * Math.cos(angle),
+      y: centerY + labelRadius * Math.sin(angle)
+    };
+  }
+
+  /**
+   * 获取属性值(安全访问)
+   */
+  getPropertyValue(texture: MaterialTextureData, propertyName: string): number {
+    const prop = texture.properties.find(p => p.name === propertyName);
+    return prop ? prop.value : 0;
+  }
+
+  /**
+   * 获取分类图标
+   */
+  getCategoryIcon(category: string): string {
+    const iconMap: { [key: string]: string } = {
+      'visual': 'M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z',
+      'tactile': 'M9 11.24V7.5C9 6.12 10.12 5 11.5 5S14 6.12 14 7.5v3.74c1.21-.81 2-2.18 2-3.74C16 5.01 13.99 3 11.5 3S7 5.01 7 7.5c0 1.56.79 2.93 2 3.74zm9.84 4.63l-4.54-2.26c-.17-.07-.35-.11-.54-.11H13v-6c0-.83-.67-1.5-1.5-1.5S10 6.67 10 7.5v10.74l-3.43-.72c-.08-.01-.15-.03-.24-.03-.31 0-.59.13-.79.33l-.79.8 4.94 4.94c.27.27.65.44 1.06.44h6.79c.75 0 1.33-.55 1.44-1.28l.75-5.27c.01-.07.02-.14.02-.2 0-.62-.38-1.16-.91-1.38z',
+      'reflective': 'M12 2L4.5 20.29l.71.71L12 18l6.79 3 .71-.71z'
+    };
+    return iconMap[category] || iconMap['visual'];
+  }
+
+  /**
+   * 获取最光滑的材质
+   */
+  getSmoothestMaterial(): string {
+    if (this.textures.length === 0) return '-';
+    
+    let smoothest = this.textures[0];
+    let maxSmoothness = 0;
+    
+    this.textures.forEach(texture => {
+      const smoothProp = texture.properties.find(p => p.name === '粗糙度');
+      if (smoothProp && (100 - smoothProp.value) > maxSmoothness) {
+        maxSmoothness = 100 - smoothProp.value;
+        smoothest = texture;
+      }
+    });
+    
+    return smoothest.materialName;
+  }
+
+  /**
+   * 获取最粗糙的材质
+   */
+  getRoughestMaterial(): string {
+    if (this.textures.length === 0) return '-';
+    
+    let roughest = this.textures[0];
+    let maxRoughness = 0;
+    
+    this.textures.forEach(texture => {
+      const roughProp = texture.properties.find(p => p.name === '粗糙度');
+      if (roughProp && roughProp.value > maxRoughness) {
+        maxRoughness = roughProp.value;
+        roughest = texture;
+      }
+    });
+    
+    return roughest.materialName;
+  }
+
+  /**
+   * 获取最高光泽的材质
+   */
+  getGlossiestMaterial(): string {
+    if (this.textures.length === 0) return '-';
+    
+    let glossiest = this.textures[0];
+    let maxGloss = 0;
+    
+    this.textures.forEach(texture => {
+      const glossProp = texture.properties.find(p => p.name === '光泽度');
+      if (glossProp && glossProp.value > maxGloss) {
+        maxGloss = glossProp.value;
+        glossiest = texture;
+      }
+    });
+    
+    return glossiest.materialName;
+  }
+
+  /**
+   * 获取最哑光的材质
+   */
+  getMattestMaterial(): string {
+    if (this.textures.length === 0) return '-';
+    
+    let mattest = this.textures[0];
+    let minGloss = 100;
+    
+    this.textures.forEach(texture => {
+      const glossProp = texture.properties.find(p => p.name === '光泽度');
+      if (glossProp && glossProp.value < minGloss) {
+        minGloss = glossProp.value;
+        mattest = texture;
+      }
+    });
+    
+    return mattest.materialName;
+  }
+
+  /**
+   * 暴露Math对象供模板使用
+   */
+  Math = Math;
+}
+

+ 19 - 2
src/app/shared/components/upload-success-modal/upload-success-modal.component.html

@@ -3,14 +3,31 @@
   <div class="modal-backdrop" 
        [@backdropAnimation]
        (click)="onBackdropClick($event)"
-       style="position: fixed !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; z-index: 99999 !important; pointer-events: auto !important;">
+       style="position: fixed !important; 
+              top: 0 !important; 
+              left: 0 !important; 
+              right: 0 !important;
+              bottom: 0 !important;
+              width: 100vw !important; 
+              height: 100vh !important; 
+              display: flex !important;
+              align-items: center !important;
+              justify-content: center !important;
+              z-index: 99999 !important; 
+              pointer-events: auto !important;
+              background: rgba(0, 0, 0, 0.6) !important;">
     
     <div class="modal-container" 
          [@modalAnimation]
          [class.mobile]="isMobile"
          [class.tablet]="isTablet"
          (click)="$event.stopPropagation()"
-         style="pointer-events: auto !important; z-index: 100000 !important; position: relative !important;">
+         style="pointer-events: auto !important; 
+                z-index: 100000 !important; 
+                position: relative !important;
+                margin: auto !important;
+                max-width: 600px !important;
+                width: 90% !important;">
       
       <!-- 弹窗头部 -->
       <div class="modal-header" [@fadeInOut]>

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

@@ -1,6 +1,21 @@
 // 上传成功弹窗样式
 // @import '../../../styles/_variables';
 
+// 强制组件脱离父容器的定位上下文
+:host,
+app-upload-success-modal {
+  position: fixed !important;
+  top: 0 !important;
+  left: 0 !important;
+  right: 0 !important;
+  bottom: 0 !important;
+  width: 100% !important;
+  height: 100% !important;
+  z-index: 99998 !important;
+  pointer-events: none !important; // 默认不拦截事件,让遮罩层处理
+  display: block !important;
+}
+
 // 弹窗背景遮罩
 // 响应式断点
 $mobile-breakpoint: 768px;

+ 24 - 3
src/app/shared/components/upload-success-modal/upload-success-modal.component.ts

@@ -1,4 +1,4 @@
-import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, OnChanges, SimpleChanges, ChangeDetectionStrategy, HostListener } from '@angular/core';
+import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, OnChanges, SimpleChanges, ChangeDetectionStrategy, HostListener, ViewEncapsulation, ElementRef, Renderer2 } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { ColorAnalysisService, AnalysisProgress, ColorAnalysisResult } from '../../services/color-analysis.service';
 import { FormAnalysisService } from '../../services/form-analysis.service';
@@ -33,6 +33,7 @@ export interface ColorInfo {
   imports: [CommonModule],
   templateUrl: './upload-success-modal.component.html',
   styleUrls: ['./upload-success-modal.component.scss'],
+  encapsulation: ViewEncapsulation.None, // 禁用样式封装,确保样式能够覆盖父组件
   // changeDetection: ChangeDetectionStrategy.OnPush, // 暂时禁用OnPush以解决交互问题
   animations: modalAnimations
 })
@@ -83,13 +84,26 @@ export class UploadSuccessModalComponent implements OnInit, OnDestroy, OnChanges
     private patternAnalysisService: PatternAnalysisService,
     private lightingAnalysisService: LightingAnalysisService,
     private requirementMappingService: RequirementMappingService,
-    private atmospherePreviewService: AtmospherePreviewService
+    private atmospherePreviewService: AtmospherePreviewService,
+    private elementRef: ElementRef,
+    private renderer: Renderer2
   ) {}
 
   ngOnInit() {
+    // 将弹窗移到 body 层级,脱离父容器限制
+    this.moveToBody();
     this.checkScreenSize();
     this.setupResizeListener();
   }
+  
+  /**
+   * 将组件移到 body 层级,确保全屏居中显示
+   */
+  private moveToBody(): void {
+    const element = this.elementRef.nativeElement;
+    this.renderer.appendChild(document.body, element);
+    console.log('✅ 弹窗组件已移到 body 层级');
+  }
 
   ngOnChanges(changes: SimpleChanges) {
     if (changes['isVisible']) {
@@ -104,6 +118,13 @@ export class UploadSuccessModalComponent implements OnInit, OnDestroy, OnChanges
   ngOnDestroy() {
     this.progressSubscription?.unsubscribe();
     this.resizeSubscription?.unsubscribe();
+    
+    // 从 body 中移除弹窗元素,避免内存泄漏
+    const element = this.elementRef.nativeElement;
+    if (element && element.parentNode === document.body) {
+      this.renderer.removeChild(document.body, element);
+      console.log('✅ 弹窗组件已从 body 移除');
+    }
   }
 
   // 响应式布局检测
@@ -250,7 +271,7 @@ export class UploadSuccessModalComponent implements OnInit, OnDestroy, OnChanges
         console.log('纹理分析:', this.analysisResult.patternAnalysis);
         console.log('灯光分析:', this.analysisResult.lightingAnalysis);
         console.log('色彩分析:', this.analysisResult.enhancedAnalysis);
-        console.log('主要光源:', this.analysisResult.lightingAnalysis.lightSourceIdentification.primarySources);
+        console.log('主要光源:', this.analysisResult.lightingAnalysis?.lightSourceIdentification?.primarySources);
         console.log('材质分类:', this.analysisResult.textureAnalysis.materialClassification);
         console.log('视觉节奏:', this.analysisResult.patternAnalysis.visualRhythm);
         

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

@@ -32,7 +32,32 @@ export interface ColorAnalysisResult {
   formAnalysis?: any;
   textureAnalysis?: any;
   patternAnalysis?: any;
-  lightingAnalysis?: any;
+  lightingAnalysis?: {
+    lightSourceIdentification?: any;
+    illuminationAnalysis?: any;
+    shadowAnalysis?: any;
+    ambientAnalysis?: any;
+    lightingRecommendations?: string[];
+    // 新增:光比分析
+    lightingRatio?: {
+      keyToFill: number;
+      contrast: number;
+      description: 'low-contrast' | 'medium-contrast' | 'high-contrast' | 'dramatic';
+      keyLightIntensity: number;
+      fillLightIntensity: number;
+      ratioQuality: 'flat' | 'balanced' | 'dramatic' | 'extreme';
+    };
+    // 新增:光占比分析
+    lightDistribution?: {
+      natural: number;
+      artificial: number;
+      ambient: number;
+      direct: number;
+      indirect: number;
+      dominantType: 'natural' | 'artificial' | 'mixed';
+      lightingBalance: 'natural-dominant' | 'artificial-dominant' | 'balanced';
+    };
+  };
 }
 
 // 新增:增强色彩分析结果接口

+ 155 - 1
src/app/shared/services/lighting-analysis.service.ts

@@ -8,6 +8,31 @@ export interface LightingAnalysisResult {
   shadowAnalysis: ShadowAnalysis;
   ambientAnalysis: AmbientAnalysis;
   lightingRecommendations: string[];
+  // 新增:光比分析
+  lightingRatio?: LightingRatioAnalysis;
+  // 新增:光占比分析
+  lightDistribution?: LightDistributionAnalysis;
+}
+
+// 新增:光比分析接口
+export interface LightingRatioAnalysis {
+  keyToFill: number;      // 主光与补光比例 (如 3:1 表示为 3)
+  contrast: number;       // 明暗对比度 0-100
+  description: 'low-contrast' | 'medium-contrast' | 'high-contrast' | 'dramatic';
+  keyLightIntensity: number;   // 主光强度 0-100
+  fillLightIntensity: number;  // 补光强度 0-100
+  ratioQuality: 'flat' | 'balanced' | 'dramatic' | 'extreme';
+}
+
+// 新增:光占比分析接口
+export interface LightDistributionAnalysis {
+  natural: number;        // 自然光占比 % (0-100)
+  artificial: number;     // 人工光占比 % (0-100)
+  ambient: number;        // 环境光占比 % (0-100)
+  direct: number;         // 直射光占比 % (0-100)
+  indirect: number;       // 间接光占比 % (0-100)
+  dominantType: 'natural' | 'artificial' | 'mixed';
+  lightingBalance: 'natural-dominant' | 'artificial-dominant' | 'balanced';
 }
 
 // 光源识别
@@ -157,7 +182,136 @@ export class LightingAnalysisService {
       illuminationAnalysis: this.generateIlluminationAnalysis(lightingType, context),
       shadowAnalysis: this.generateShadowAnalysis(lightingType, context),
       ambientAnalysis: this.generateAmbientAnalysis(lightingType, context),
-      lightingRecommendations: this.generateLightingRecommendations(lightingType, context)
+      lightingRecommendations: this.generateLightingRecommendations(lightingType, context),
+      // 新增:光比分析
+      lightingRatio: this.calculateLightingRatio(lightingType, context),
+      // 新增:光占比分析
+      lightDistribution: this.calculateLightDistribution(lightingType, context)
+    };
+  }
+  
+  /**
+   * 计算光比(主光与补光的比例)
+   */
+  private calculateLightingRatio(lightingType: string, context: string): LightingRatioAnalysis {
+    const isDramatic = lightingType === 'dramatic' || context.includes('shadow') || context.includes('high-contrast');
+    const isSoft = lightingType === 'soft' || context.includes('soft') || context.includes('diffuse');
+    const isBright = lightingType === 'bright' || context.includes('bright');
+    
+    let keyLightIntensity: number;
+    let fillLightIntensity: number;
+    let ratioQuality: 'flat' | 'balanced' | 'dramatic' | 'extreme';
+    
+    if (isDramatic) {
+      // 戏剧性灯光:高光比(4:1 到 8:1)
+      keyLightIntensity = 75 + Math.random() * 20;
+      fillLightIntensity = 10 + Math.random() * 15;
+      ratioQuality = keyLightIntensity / fillLightIntensity > 6 ? 'extreme' : 'dramatic';
+    } else if (isSoft) {
+      // 柔和灯光:低光比(2:1 到 3:1)
+      keyLightIntensity = 60 + Math.random() * 15;
+      fillLightIntensity = 30 + Math.random() * 15;
+      ratioQuality = 'balanced';
+    } else if (isBright) {
+      // 明亮灯光:中等光比(3:1 到 4:1)
+      keyLightIntensity = 70 + Math.random() * 15;
+      fillLightIntensity = 20 + Math.random() * 15;
+      ratioQuality = 'balanced';
+    } else {
+      // 默认:平衡光比(2:1 到 3:1)
+      keyLightIntensity = 65 + Math.random() * 15;
+      fillLightIntensity = 25 + Math.random() * 15;
+      ratioQuality = fillLightIntensity > 40 ? 'flat' : 'balanced';
+    }
+    
+    const keyToFill = keyLightIntensity / fillLightIntensity;
+    const contrast = Math.min(100, Math.abs(keyLightIntensity - fillLightIntensity) * 1.2);
+    
+    let description: 'low-contrast' | 'medium-contrast' | 'high-contrast' | 'dramatic';
+    if (contrast < 30) {
+      description = 'low-contrast';
+    } else if (contrast < 50) {
+      description = 'medium-contrast';
+    } else if (contrast < 70) {
+      description = 'high-contrast';
+    } else {
+      description = 'dramatic';
+    }
+    
+    return {
+      keyToFill: Math.round(keyToFill * 10) / 10,
+      contrast: Math.round(contrast),
+      description,
+      keyLightIntensity: Math.round(keyLightIntensity),
+      fillLightIntensity: Math.round(fillLightIntensity),
+      ratioQuality
+    };
+  }
+  
+  /**
+   * 计算光占比(不同类型光源的分布比例)
+   */
+  private calculateLightDistribution(lightingType: string, context: string): LightDistributionAnalysis {
+    const isNatural = lightingType === 'natural' || context.includes('sun') || context.includes('outdoor');
+    const isArtificial = lightingType === 'artificial' || context.includes('led') || context.includes('lamp');
+    const isMixed = lightingType === 'mixed' || (!isNatural && !isArtificial);
+    
+    let natural: number;
+    let artificial: number;
+    let ambient: number;
+    let direct: number;
+    let indirect: number;
+    let dominantType: 'natural' | 'artificial' | 'mixed';
+    let lightingBalance: 'natural-dominant' | 'artificial-dominant' | 'balanced';
+    
+    if (isNatural) {
+      // 自然光主导
+      natural = 65 + Math.random() * 25;  // 65-90%
+      artificial = 100 - natural;
+      direct = 50 + Math.random() * 25;   // 直射光
+      ambient = 20 + Math.random() * 15;  // 环境光
+      indirect = 100 - direct - ambient;  // 间接光
+      dominantType = 'natural';
+      lightingBalance = 'natural-dominant';
+    } else if (isArtificial) {
+      // 人工光主导
+      artificial = 70 + Math.random() * 25; // 70-95%
+      natural = 100 - artificial;
+      direct = 40 + Math.random() * 20;    // 直射光
+      ambient = 25 + Math.random() * 15;   // 环境光
+      indirect = 100 - direct - ambient;   // 间接光
+      dominantType = 'artificial';
+      lightingBalance = 'artificial-dominant';
+    } else {
+      // 混合光源
+      natural = 40 + Math.random() * 20;   // 40-60%
+      artificial = 100 - natural;
+      direct = 35 + Math.random() * 20;    // 直射光
+      ambient = 30 + Math.random() * 15;   // 环境光
+      indirect = 100 - direct - ambient;   // 间接光
+      dominantType = 'mixed';
+      lightingBalance = Math.abs(natural - artificial) < 15 ? 'balanced' : 
+                       (natural > artificial ? 'natural-dominant' : 'artificial-dominant');
+    }
+    
+    // 归一化确保总和为100%
+    const total = natural + artificial;
+    natural = Math.round((natural / total) * 100);
+    artificial = 100 - natural;
+    
+    const lightTotal = direct + ambient + indirect;
+    direct = Math.round((direct / lightTotal) * 100);
+    ambient = Math.round((ambient / lightTotal) * 100);
+    indirect = 100 - direct - ambient;
+    
+    return {
+      natural,
+      artificial,
+      ambient,
+      direct,
+      indirect,
+      dominantType,
+      lightingBalance
     };
   }
 

+ 14 - 0
src/styles.scss

@@ -53,6 +53,20 @@ body {
 }
 
 
+/* 上传成功弹窗全局样式 - 确保居中显示 */
+app-upload-success-modal {
+  position: fixed !important;
+  top: 0 !important;
+  left: 0 !important;
+  right: 0 !important;
+  bottom: 0 !important;
+  width: 100% !important;
+  height: 100% !important;
+  z-index: 99998 !important;
+  pointer-events: none !important;
+  display: block !important;
+}
+
 /* HR Dialog Global Styles - 重新设计解决背景色重合问题 */
 .hr-dialog-backdrop {
   background: rgba(0, 0, 0, 0.6);