0235711 9 годин тому
батько
коміт
52d9423368

+ 1 - 1
src/app/pages/designer/dashboard/dashboard.ts

@@ -33,7 +33,7 @@ export class Dashboard implements OnInit {
   // 视图管理
   activeDashboard: 'main' | 'skills' | 'personal' = 'main';
   // 新增:工作台视图模式(卡片/列表)
-  viewMode: 'card' | 'list' = 'card';
+  viewMode: 'card' | 'list' = 'list';
   
   tasks: Task[] = [];
   overdueTasks: Task[] = [];

+ 177 - 4
src/app/pages/designer/project-detail/project-detail.html

@@ -285,10 +285,183 @@
                           (progressUpdated)="syncRequirementKeyInfo($event)">
                         </app-requirements-confirm-card>
                       } @else if (stage === '方案确认') {
-                        <!-- 方案确认阶段暂时显示占位内容 -->
-                        <div class="stage-placeholder">
-                          <h4>方案确认阶段</h4>
-                          <p>此阶段功能正在开发中...</p>
+                        <!-- 方案确认阶段 - 需求信息展示 -->
+                        <div class="proposal-confirm-section">
+                          <div class="section-header">
+                            <h4>方案确认</h4>
+                            <div class="progress-indicator">
+                              <span class="progress-text">{{ getRequiredStagesProgress() }}% 完成</span>
+                            </div>
+                          </div>
+                          
+                          <!-- 需求信息展示区域 -->
+                          @if (areRequiredStagesCompleted()) {
+                            <div class="requirement-info-display">
+                              <div class="info-header">
+                                <h5>确认的需求信息</h5>
+                                <span class="info-subtitle">来自需求沟通阶段</span>
+                              </div>
+                              
+                              <div class="info-grid">
+                                <!-- 色调和材质并排布局 -->
+                                <div class="color-material-row">
+                                  <!-- 色调 -->
+                                  @if (requirementKeyInfo.colorAtmosphere.description) {
+                                    <div class="info-item color-item">
+                                      <div class="info-label">
+                                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                                          <circle cx="12" cy="12" r="10"></circle>
+                                          <path d="M12 6v6l4 2"></path>
+                                        </svg>
+                                        色调
+                                      </div>
+                                      <div class="info-content">
+                                        <span class="info-value">{{ requirementKeyInfo.colorAtmosphere.description }}</span>
+                                        @if (requirementKeyInfo.colorAtmosphere.mainColor) {
+                                          <div class="color-preview" [style.background-color]="requirementKeyInfo.colorAtmosphere.mainColor"></div>
+                                        }
+                                      </div>
+                                    </div>
+                                  }
+                                  
+                                  <!-- 材质 -->
+                                  @if (requirementKeyInfo.colorAtmosphere.materials && requirementKeyInfo.colorAtmosphere.materials.length > 0) {
+                                    <div class="info-item material-item">
+                                      <div class="info-label">
+                                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                                          <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
+                                          <rect x="7" y="7" width="10" height="10" rx="1" ry="1"></rect>
+                                        </svg>
+                                        材质
+                                      </div>
+                                      <div class="info-content">
+                                        <div class="material-list">
+                                          @for (material of requirementKeyInfo.colorAtmosphere.materials; track material) {
+                                            <span class="material-tag">{{ material }}</span>
+                                          }
+                                        </div>
+                                      </div>
+                                    </div>
+                                  }
+                                </div>
+                                
+                                <!-- 空间结构和材质权重并排布局 -->
+                                <div class="structure-weight-row">
+                                  <!-- 空间结构 -->
+                                  @if (requirementKeyInfo.spaceStructure.aspectRatio > 0) {
+                                    <div class="info-item structure-item">
+                                      <div class="info-label">
+                                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                                          <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
+                                          <line x1="9" y1="9" x2="15" y2="15"></line>
+                                        </svg>
+                                        空间结构
+                                      </div>
+                                      <div class="info-content">
+                                        <div class="structure-details">
+                                          <span class="structure-item">比例: {{ requirementKeyInfo.spaceStructure.aspectRatio.toFixed(1) }}</span>
+                                          @if (requirementKeyInfo.spaceStructure.ceilingHeight > 0) {
+                                            <span class="structure-item">层高: {{ requirementKeyInfo.spaceStructure.ceilingHeight }}m</span>
+                                          }
+                                          @if (requirementKeyInfo.spaceStructure.flowWidth > 0) {
+                                            <span class="structure-item">流线宽度: {{ requirementKeyInfo.spaceStructure.flowWidth }}m</span>
+                                          }
+                                        </div>
+                                      </div>
+                                    </div>
+                                  }
+                                  
+                                  <!-- 材质权重 -->
+                                  @if (requirementKeyInfo.materialWeights.woodRatio > 0 || requirementKeyInfo.materialWeights.fabricRatio > 0 || requirementKeyInfo.materialWeights.metalRatio > 0) {
+                                    <div class="info-item weight-item">
+                                      <div class="info-label">
+                                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                                          <path d="M3 3v18h18"></path>
+                                          <path d="M7 16l4-4 4 4 6-6"></path>
+                                        </svg>
+                                        材质权重
+                                      </div>
+                                      <div class="info-content">
+                                        <div class="weight-list">
+                                          @if (requirementKeyInfo.materialWeights.woodRatio > 0) {
+                                            <div class="weight-item">
+                                              <span class="weight-label">木质</span>
+                                              <span class="weight-value">{{ requirementKeyInfo.materialWeights.woodRatio }}%</span>
+                                            </div>
+                                          }
+                                          @if (requirementKeyInfo.materialWeights.fabricRatio > 0) {
+                                            <div class="weight-item">
+                                              <span class="weight-label">布艺</span>
+                                              <span class="weight-value">{{ requirementKeyInfo.materialWeights.fabricRatio }}%</span>
+                                            </div>
+                                          }
+                                          @if (requirementKeyInfo.materialWeights.metalRatio > 0) {
+                                            <div class="weight-item">
+                                              <span class="weight-label">金属</span>
+                                              <span class="weight-value">{{ requirementKeyInfo.materialWeights.metalRatio }}%</span>
+                                            </div>
+                                          }
+                                        </div>
+                                      </div>
+                                    </div>
+                                  }
+                                </div>
+                                
+                                <!-- 预设氛围和小图时间并排布局 -->
+                                <div class="atmosphere-time-row">
+                                  <!-- 预设氛围 -->
+                                  @if (requirementKeyInfo.presetAtmosphere.name) {
+                                    <div class="info-item atmosphere-item">
+                                      <div class="info-label">
+                                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                                          <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"></path>
+                                        </svg>
+                                        预设氛围
+                                      </div>
+                                      <div class="info-content">
+                                        <div class="atmosphere-info">
+                                          <span class="atmosphere-name">{{ requirementKeyInfo.presetAtmosphere.name }}</span>
+                                          @if (requirementKeyInfo.presetAtmosphere.colorTemp) {
+                                            <span class="color-temp">{{ requirementKeyInfo.presetAtmosphere.colorTemp }}</span>
+                                          }
+                                        </div>
+                                      </div>
+                                    </div>
+                                  }
+                                  
+                                  <!-- 小图时间 -->
+                                  @if (getEstimatedSmallImageTime()) {
+                                    <div class="info-item time-item">
+                                      <div class="info-label">
+                                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                                          <circle cx="12" cy="12" r="10"></circle>
+                                          <polyline points="12,6 12,12 16,14"></polyline>
+                                        </svg>
+                                        小图时间
+                                      </div>
+                                      <div class="info-content">
+                                        <span class="time-estimate">{{ getEstimatedSmallImageTime() }}</span>
+                                      </div>
+                                    </div>
+                                  }
+                                </div>
+                              </div>
+                            </div>
+                          } @else {
+                            <div class="requirement-pending">
+                              <div class="pending-icon">
+                                <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                                  <circle cx="12" cy="12" r="10"></circle>
+                                  <polyline points="12,6 12,12 16,14"></polyline>
+                                </svg>
+                              </div>
+                              <h5>等待需求确认完成</h5>
+                              <p>请先完成"流程进度 > 确认需求 > 需求沟通"四个流程,确认需求信息后方可查看。</p>
+                              <div class="progress-status">
+                                <span>当前进度: {{ getRequiredStagesProgress() }}%</span>
+                              </div>
+                            </div>
+                          }
                         </div>
                       } @else if (stage === '建模') {
                         <div class="upload-section">

+ 521 - 29
src/app/pages/designer/project-detail/project-detail.scss

@@ -3,7 +3,7 @@
 
 /* 项目详情容器 */
 .project-detail-container {
-  padding: $ios-spacing-lg; // 使用统一的间距变量
+  padding: 2%; // 使用百分比单位
   background-color: #f5f7fa;
   color: #333;
   min-height: 100vh;
@@ -14,10 +14,10 @@
 /* 四大板块按钮组 - 位于项目标题区域正下方 */
 .sections-toolbar-header {
   display: flex;
-  gap: 12px;
+  gap: 1%; // 使用百分比间距
   width: 100%;
-  margin: 20px 0 24px 0;
-  padding: 0 24px; /* 与项目详情容器保持一致的左右边距 */
+  margin: 1.5% 0 2% 0; // 使用百分比边距
+  padding: 0 2%; /* 与项目详情容器保持一致的左右边距 */
   
   .section-btn {
     flex: 1;
@@ -25,14 +25,14 @@
     border: 1px solid $ios-border;
     background: $ios-background;
     color: $ios-text-primary;
-    padding: 12px 16px;
+    padding: 0.9rem 1.2rem; // 使用rem单位
     border-radius: $ios-radius-md;
-    font-size: 14px;
+    font-size: 0.9rem; // 使用rem单位
     font-weight: 500;
     cursor: pointer;
     transition: all 0.2s ease;
     text-align: center;
-    min-height: 44px;
+    min-height: 3rem; // 使用rem单位
     display: flex;
     align-items: center;
     justify-content: center;
@@ -44,22 +44,22 @@
     }
     
     &:hover {
-      transform: translateY(-1px);
-      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+      transform: translateY(-0.08rem); // 使用rem单位
+      box-shadow: 0 0.3rem 0.9rem rgba(0, 0, 0, 0.1);
     }
     
     &.completed {
       background: linear-gradient(135deg, #e6f7e6 0%, #d4edda 100%);
       color: $ios-success;
       border-color: $ios-success;
-      box-shadow: 0 2px 8px rgba(40, 167, 69, 0.15);
+      box-shadow: 0 0.15rem 0.6rem rgba(40, 167, 69, 0.15);
     }
     
     &.active {
       background: linear-gradient(135deg, #e8f0fe 0%, #cce7ff 100%);
       color: $ios-primary;
       border-color: $ios-primary;
-      box-shadow: 0 2px 8px rgba(0, 122, 255, 0.2);
+      box-shadow: 0 0.15rem 0.6rem rgba(0, 122, 255, 0.2);
     }
     
     &.pending {
@@ -337,6 +337,498 @@
   }
 }
 
+/* 方案确认阶段样式 */
+.proposal-confirm-section {
+  background: white;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+  border: 1px solid rgba(0, 0, 0, 0.06);
+  margin-bottom: 20px;
+
+  .section-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 24px;
+    padding-bottom: 16px;
+    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
+
+    h4 {
+      margin: 0;
+      font-size: 20px;
+      font-weight: 600;
+      color: #1d1d1f;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      &::before {
+        content: '✓';
+        width: 28px;
+        height: 28px;
+        background: linear-gradient(135deg, #34c759 0%, #30d158 100%);
+        color: white;
+        border-radius: 6px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 14px;
+        box-shadow: 0 2px 8px rgba(52, 199, 89, 0.3);
+      }
+    }
+
+    .progress-indicator {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      padding: 8px 16px;
+      background: rgba(52, 199, 89, 0.1);
+      border-radius: 20px;
+      border: 1px solid rgba(52, 199, 89, 0.2);
+
+      .progress-text {
+        font-size: 12px;
+        font-weight: 600;
+        color: #34c759;
+      }
+
+      &::after {
+        content: '✓';
+        font-size: 12px;
+        color: #34c759;
+      }
+    }
+  }
+
+  .requirement-info-display {
+    .info-header {
+      margin-bottom: 20px;
+
+      h5 {
+        margin: 0 0 4px 0;
+        font-size: 16px;
+        font-weight: 600;
+        color: #1d1d1f;
+      }
+
+      .info-subtitle {
+        font-size: 13px;
+        color: #8e8e93;
+      }
+    }
+
+    .info-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(45%, 1fr)); // 使用百分比替代固定像素
+      gap: 1.5%; // 使用百分比间距
+      
+      // 色调和材质并排布局
+        .color-material-row {
+          display: flex;
+          gap: 1.5%; // 使用百分比间距
+          
+          @media (max-width: 768px) {
+            flex-direction: column;
+            gap: 1.2%; // 移动端使用更小的百分比间距
+          }
+          
+          .color-item,
+          .material-item {
+            flex: 1;
+            min-width: 0; // 防止内容溢出
+          }
+        }
+        
+        // 空间结构和材质权重并排布局
+        .structure-weight-row {
+          display: flex;
+          gap: 1.5%; // 使用百分比间距
+          
+          @media (max-width: 768px) {
+            flex-direction: column;
+            gap: 1.2%; // 移动端使用更小的百分比间距
+          }
+          
+          .structure-item,
+          .weight-item {
+            flex: 1;
+            min-width: 0; // 防止内容溢出
+          }
+        }
+        
+        // 预设氛围和小图时间并排布局
+        .atmosphere-time-row {
+          display: flex;
+          gap: 1.5%; // 使用百分比间距
+          
+          @media (max-width: 768px) {
+            flex-direction: column;
+            gap: 1.2%; // 移动端使用更小的百分比间距
+          }
+          
+          .atmosphere-item,
+          .time-item {
+            flex: 1;
+            min-width: 0; // 防止内容溢出
+          }
+        }
+
+      .info-item {
+        background: rgba(0, 122, 255, 0.02);
+        border: 1px solid rgba(0, 122, 255, 0.08);
+        border-radius: 0.8rem; // 使用rem单位
+        padding: 1.2rem; // 使用rem单位
+        transition: all 0.2s ease;
+
+        &:hover {
+          transform: translateY(-0.15rem); // 使用rem单位
+          box-shadow: 0 0.3rem 1.2rem rgba(0, 122, 255, 0.1);
+          border-color: rgba(0, 122, 255, 0.15);
+        }
+
+        .info-label {
+          display: flex;
+          align-items: center;
+          gap: 0.6rem; // 使用rem单位
+          margin-bottom: 0.9rem; // 使用rem单位
+          font-size: 0.9rem; // 使用rem单位
+          font-weight: 600;
+          color: #1d1d1f;
+
+          svg {
+            width: 1rem; // 使用rem单位
+            height: 16px;
+            color: #007aff;
+          }
+        }
+
+        .info-content {
+          .info-value {
+            font-size: 14px;
+            color: #1d1d1f;
+            font-weight: 500;
+            display: block;
+            margin-bottom: 8px;
+          }
+
+          .color-preview {
+            width: 20px;
+            height: 20px;
+            border-radius: 4px;
+            border: 1px solid rgba(0, 0, 0, 0.1);
+            display: inline-block;
+            margin-left: 8px;
+          }
+
+          .material-list {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 6px;
+
+            .material-tag {
+              background: rgba(52, 199, 89, 0.1);
+              color: #34c759;
+              padding: 4px 8px;
+              border-radius: 4px;
+              font-size: 12px;
+              font-weight: 500;
+            }
+          }
+
+          .structure-details {
+            display: flex;
+            flex-direction: column;
+            gap: 4px;
+
+            .structure-item {
+              font-size: 13px;
+              color: #666;
+              padding: 2px 0;
+            }
+          }
+
+          .weight-list {
+            display: flex;
+            flex-direction: column;
+            gap: 8px;
+
+            .weight-item {
+              display: flex;
+              justify-content: space-between;
+              align-items: center;
+              padding: 6px 12px;
+              background: white;
+              border-radius: 6px;
+              border: 1px solid rgba(0, 0, 0, 0.06);
+
+              .weight-label {
+                font-size: 13px;
+                color: #666;
+                font-weight: 500;
+              }
+
+              .weight-value {
+                font-size: 13px;
+                color: #007aff;
+                font-weight: 600;
+                background: rgba(0, 122, 255, 0.1);
+                padding: 2px 8px;
+                border-radius: 4px;
+              }
+            }
+          }
+
+          .atmosphere-info {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+
+            .atmosphere-name {
+              font-size: 14px;
+              color: #1d1d1f;
+              font-weight: 600;
+            }
+
+            .color-temp {
+              font-size: 11px;
+              background: rgba(255, 149, 0, 0.1);
+              color: #ff9500;
+              padding: 2px 6px;
+              border-radius: 4px;
+            }
+          }
+
+          .time-estimate {
+            font-size: 16px;
+            color: #007aff;
+            font-weight: 600;
+            background: rgba(0, 122, 255, 0.1);
+            padding: 8px 16px;
+            border-radius: 8px;
+            display: inline-block;
+          }
+        }
+      }
+    }
+  }
+
+  .requirement-pending {
+    text-align: center;
+    padding: 40px 20px;
+    background: rgba(255, 149, 0, 0.02);
+    border: 2px dashed rgba(255, 149, 0, 0.2);
+    border-radius: 12px;
+
+    .pending-icon {
+      margin-bottom: 16px;
+
+      svg {
+        color: rgba(255, 149, 0, 0.4);
+      }
+    }
+
+    h5 {
+      font-size: 16px;
+      font-weight: 600;
+      color: #1d1d1f;
+      margin: 0 0 8px 0;
+    }
+
+    p {
+      font-size: 14px;
+      color: #8e8e93;
+      line-height: 1.4;
+      margin: 0;
+    }
+  }
+}
+
+/* 方案确认阶段样式 */
+.proposal-confirm-stage {
+  .requirement-summary-card {
+    background: white;
+    border-radius: 12px;
+    padding: 24px;
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+    border: 1px solid rgba(0, 0, 0, 0.06);
+    margin-bottom: 20px;
+
+    .card-header {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 20px;
+      padding-bottom: 16px;
+      border-bottom: 1px solid rgba(0, 0, 0, 0.08);
+
+      .header-left {
+        display: flex;
+        align-items: center;
+        gap: 12px;
+
+        .status-icon {
+          width: 32px;
+          height: 32px;
+          border-radius: 8px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          font-size: 16px;
+          background: linear-gradient(135deg, #34c759 0%, #30d158 100%);
+          color: white;
+          box-shadow: 0 2px 8px rgba(52, 199, 89, 0.3);
+        }
+
+        .header-text {
+          h3 {
+            margin: 0 0 4px 0;
+            font-size: 18px;
+            font-weight: 600;
+            color: #1d1d1f;
+          }
+
+          .subtitle {
+            font-size: 13px;
+            color: #8e8e93;
+            margin: 0;
+          }
+        }
+      }
+
+      .progress-indicator {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        padding: 8px 16px;
+        background: rgba(52, 199, 89, 0.1);
+        border-radius: 20px;
+        border: 1px solid rgba(52, 199, 89, 0.2);
+
+        .progress-text {
+          font-size: 12px;
+          font-weight: 600;
+          color: #34c759;
+        }
+
+        .check-icon {
+          font-size: 14px;
+          color: #34c759;
+        }
+      }
+    }
+
+    .requirement-info-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+      gap: 16px;
+
+      .info-section {
+        background: rgba(0, 122, 255, 0.02);
+        border: 1px solid rgba(0, 122, 255, 0.08);
+        border-radius: 10px;
+        padding: 16px;
+        transition: all 0.2s ease;
+
+        &:hover {
+          transform: translateY(-2px);
+          box-shadow: 0 4px 16px rgba(0, 122, 255, 0.1);
+          border-color: rgba(0, 122, 255, 0.15);
+        }
+
+        .section-title {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+          margin-bottom: 12px;
+
+          .title-icon {
+            width: 20px;
+            height: 20px;
+            border-radius: 4px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: 12px;
+            background: #007aff;
+            color: white;
+          }
+
+          .title-text {
+            font-size: 14px;
+            font-weight: 600;
+            color: #1d1d1f;
+          }
+        }
+
+        .info-content {
+          .info-item {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding: 10px 0;
+            border-bottom: 1px solid rgba(0, 0, 0, 0.04);
+
+            &:last-child {
+              border-bottom: none;
+              padding-bottom: 0;
+            }
+
+            .item-label {
+              font-size: 13px;
+              color: #666;
+              font-weight: 500;
+            }
+
+            .item-value {
+              font-size: 13px;
+              color: #1d1d1f;
+              font-weight: 600;
+              text-align: right;
+              max-width: 60%;
+              word-break: break-word;
+
+              &.highlight {
+                color: #007aff;
+                background: rgba(0, 122, 255, 0.1);
+                padding: 2px 8px;
+                border-radius: 4px;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    .completion-placeholder {
+      text-align: center;
+      padding: 40px 20px;
+      background: rgba(255, 149, 0, 0.02);
+      border: 2px dashed rgba(255, 149, 0, 0.2);
+      border-radius: 12px;
+
+      .placeholder-icon {
+        font-size: 48px;
+        color: rgba(255, 149, 0, 0.4);
+        margin-bottom: 16px;
+      }
+
+      .placeholder-title {
+        font-size: 16px;
+        font-weight: 600;
+        color: #1d1d1f;
+        margin-bottom: 8px;
+      }
+
+      .placeholder-subtitle {
+        font-size: 14px;
+        color: #8e8e93;
+        line-height: 1.4;
+      }
+    }
+  }
+}
+
 @keyframes modalFadeIn {
   from {
     opacity: 0;
@@ -828,9 +1320,9 @@
 /* 主内容布局 */
 .main-content-layout {
   display: grid;
-  grid-template-columns: 1fr 350px; // 调整为两列:主内容区和右侧栏
-  gap: $ios-spacing-lg;
-  max-width: calc(100vw - 40px);
+  grid-template-columns: 1fr 28%; // 使用百分比:主内容区和右侧栏(28%)
+  gap: 2%;
+  max-width: 96vw; // 使用视口单位,留出4%边距
   margin: 0 auto;
   overflow: visible;
   align-items: start;
@@ -848,46 +1340,46 @@
   
   .right-column {
     padding: 0;
-    min-width: 300px;
-    max-width: 400px;
+    min-width: 25%; // 使用百分比最小宽度
+    max-width: 32%; // 使用百分比最大宽度
   }
 }
 
 // 响应式设计优化
 @media (max-width: 1400px) {
   .project-detail-container {
-    padding: $ios-spacing-md;
+    padding: 1.5%;
   }
 
   .main-content-layout {
-    grid-template-columns: 1fr 300px; // 中等屏幕调整
-    gap: $ios-spacing-md;
+    grid-template-columns: 1fr 25%; // 中等屏幕调整为25%
+    gap: 1.5%;
   }
   
   .horizontal-nav-container {
-    margin-bottom: $ios-spacing-md;
-    padding: $ios-spacing-sm;
+    margin-bottom: 1.5%;
+    padding: 1%;
   }
 }
 
 @media (max-width: 1200px) {
   .project-detail-container {
-    padding: $ios-spacing-sm;
+    padding: 1.2%;
   }
 
   .main-content-layout {
-    grid-template-columns: 1fr 280px; // 进一步减小右侧栏宽度
-    gap: $ios-spacing-sm;
+    grid-template-columns: 1fr 23%; // 进一步减小右侧栏宽度为23%
+    gap: 1.2%;
     
     .right-column {
-      min-width: 260px;
-      max-width: 320px;
+      min-width: 22%; // 使用百分比
+      max-width: 26%; // 使用百分比
     }
   }
   
   .horizontal-nav {
     .nav-item {
-      padding: $ios-spacing-sm $ios-spacing-md;
+      padding: 1% 1.5%;
       font-size: $ios-font-size-sm;
     }
   }
@@ -895,12 +1387,12 @@
 
 @media (max-width: 768px) {
   .project-detail-container {
-    padding: $ios-spacing-sm;
+    padding: 1%;
   }
   
   .main-content-layout {
     grid-template-columns: 1fr;
-    gap: $ios-spacing-md;
+    gap: 1.5%;
     
     .left-column,
     .right-column {

+ 45 - 0
src/app/pages/designer/project-detail/project-detail.ts

@@ -1651,4 +1651,49 @@ export class ProjectDetail implements OnInit, OnDestroy {
     
     return summary;
   }
+
+  // 检查必需阶段是否全部完成(流程进度 > 确认需求 > 需求沟通四个流程)
+  areRequiredStagesCompleted(): boolean {
+    // 这里需要根据实际的需求确认流程来判断
+    // 暂时使用需求关键信息是否有数据来判断
+    return this.getRequirementSummary().length > 0 && 
+           (!!this.requirementKeyInfo.colorAtmosphere.description || 
+            this.requirementKeyInfo.spaceStructure.aspectRatio > 0 ||
+            this.requirementKeyInfo.materialWeights.woodRatio > 0 ||
+            !!this.requirementKeyInfo.presetAtmosphere.name);
+  }
+
+  // 获取必需阶段的完成进度百分比
+  getRequiredStagesProgress(): number {
+    let completedCount = 0;
+    const totalCount = 4; // 四个必需流程
+
+    // 检查各个关键信息是否已确认
+    if (this.requirementKeyInfo.colorAtmosphere.description) completedCount++;
+    if (this.requirementKeyInfo.spaceStructure.aspectRatio > 0) completedCount++;
+    if (this.requirementKeyInfo.materialWeights.woodRatio > 0 || 
+        this.requirementKeyInfo.materialWeights.fabricRatio > 0 || 
+        this.requirementKeyInfo.materialWeights.metalRatio > 0) completedCount++;
+    if (this.requirementKeyInfo.presetAtmosphere.name) completedCount++;
+
+    return Math.round((completedCount / totalCount) * 100);
+  }
+
+  // 获取预估小图时间
+  getEstimatedSmallImageTime(): string {
+    // 根据需求复杂度计算预估时间
+    const baseTime = 2; // 基础时间2小时
+    let additionalTime = 0;
+
+    // 根据材质复杂度增加时间
+    if (this.requirementKeyInfo.materialWeights.woodRatio > 0) additionalTime += 0.5;
+    if (this.requirementKeyInfo.materialWeights.fabricRatio > 0) additionalTime += 0.5;
+    if (this.requirementKeyInfo.materialWeights.metalRatio > 0) additionalTime += 0.5;
+
+    // 根据空间复杂度增加时间
+    if (this.requirementKeyInfo.spaceStructure.aspectRatio > 2) additionalTime += 1;
+
+    const totalTime = baseTime + additionalTime;
+    return `${totalTime}小时`;
+  }
 }

+ 42 - 0
src/app/shared/components/requirements-confirm-card/README.md

@@ -0,0 +1,42 @@
+# Requirements Confirm Card 布局方案
+
+## 概述
+本组件提供了两种布局方案用于"制作流程进度>需求沟通"部分的参考图片和CAD图纸上传功能。
+
+## 方案一:简洁并排布局(当前使用)
+- **文件**: `requirements-confirm-card.html`
+- **样式**: `requirements-confirm-card.scss`
+- **特点**: 
+  - 文本描述区域独立一行
+  - 参考图片和CAD图纸并排显示
+  - 简洁实用的设计风格
+  - 响应式布局,移动端自动变为单列
+
+## 方案二:美观卡片式布局(备选方案)
+- **文件**: `requirements-confirm-card-alternative.html`
+- **样式**: `requirements-confirm-card-alternative.scss`
+- **特点**:
+  - 现代化卡片式设计
+  - 渐变色彩和图标装饰
+  - 悬停动画效果
+  - 深色模式支持
+  - 更丰富的视觉层次
+
+## 使用方法
+
+### 切换到备选方案
+如需使用美观的卡片式布局,请按以下步骤操作:
+
+1. 将 `requirements-confirm-card-alternative.html` 的内容复制到 `requirements-confirm-card.html`
+2. 在组件的 HTML 模板中,将 `materials-section` 的 class 改为 `materials-section alternative-layout`
+
+### 响应式特性
+两种方案都支持响应式设计:
+- **桌面端**: 参考图片和CAD图纸并排显示
+- **移动端**: 自动切换为单列布局
+
+## 技术实现
+- 使用 CSS Grid 布局实现响应式设计
+- 采用 Angular 控制流指令 `@if` 和 `@for`
+- 支持文件拖拽上传和点击上传
+- 包含文件类型验证和预览功能

+ 83 - 0
src/app/shared/components/requirements-confirm-card/requirements-confirm-card-alternative.html

@@ -0,0 +1,83 @@
+<!-- 备选方案:美观的卡片式布局 -->
+<div class="materials-section alternative-layout">
+  <!-- 文本输入区域 - 独立一行 -->
+  <div class="text-upload-section">
+    <div class="upload-item text-item">
+      <h5>文本描述</h5>
+      <form [formGroup]="materialUploadForm" (ngSubmit)="onTextSubmit()">
+        <textarea 
+          formControlName="textContent" 
+          placeholder="请描述您的装修需求,如:希望温馨的暖木色调,适合亲子家庭,需要瑜伽区收纳..."
+          rows="4">
+        </textarea>
+        <button type="submit" class="btn-primary btn-sm" [disabled]="!materialUploadForm.get('textContent')?.value">
+          解析文本
+        </button>
+      </form>
+    </div>
+  </div>
+
+  <!-- 参考图片和CAD图纸 - 美观卡片式并排布局 -->
+  <div class="file-upload-cards">
+    <!-- 参考图片上传卡片 -->
+    <div class="upload-card image-card">
+      <div class="card-header">
+        <div class="card-icon image-icon">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
+            <circle cx="8.5" cy="8.5" r="1.5"></circle>
+            <polyline points="21,15 16,10 5,21"></polyline>
+          </svg>
+        </div>
+        <h5>参考图片</h5>
+      </div>
+      <div class="card-body">
+        <div class="file-upload-zone" (click)="imageInput.click()">
+          <div class="upload-content">
+            <div class="upload-icon">
+              <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+                <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>
+            </div>
+            <p>点击上传参考图片</p>
+            <span class="hint">支持多张图片,自动分析色调和材质</span>
+          </div>
+        </div>
+        <input #imageInput type="file" multiple accept="image/*" (change)="onFileSelected($event, 'image')" style="display: none;">
+      </div>
+    </div>
+
+    <!-- CAD图纸上传卡片 -->
+    <div class="upload-card cad-card">
+      <div class="card-header">
+        <div class="card-icon cad-icon">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+            <polyline points="14,2 14,8 20,8"></polyline>
+            <line x1="16" y1="13" x2="8" y2="13"></line>
+            <line x1="16" y1="17" x2="8" y2="17"></line>
+          </svg>
+        </div>
+        <h5>CAD图纸</h5>
+      </div>
+      <div class="card-body">
+        <div class="file-upload-zone" (click)="cadInput.click()">
+          <div class="upload-content">
+            <div class="upload-icon">
+              <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+                <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>
+            </div>
+            <p>点击上传CAD图纸</p>
+            <span class="hint">自动识别结构和空间尺寸</span>
+          </div>
+        </div>
+        <input #cadInput type="file" accept=".dwg,.dxf,.pdf" (change)="onFileSelected($event, 'cad')" style="display: none;">
+      </div>
+    </div>
+  </div>
+</div>

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

@@ -0,0 +1,256 @@
+// 备选方案:美观的卡片式布局样式
+.materials-section.alternative-layout {
+  // 文本输入区域 - 独立一行
+  .text-upload-section {
+    margin-bottom: 2rem;
+    
+    .upload-item.text-item {
+      background: white;
+      border: 1px solid #e5e5e7;
+      border-radius: 12px;
+      padding: 1.5rem;
+      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+      
+      h5 {
+        margin: 0 0 1rem 0;
+        font-size: 1rem;
+        font-weight: 600;
+        color: #1d1d1f;
+        display: flex;
+        align-items: center;
+        gap: 0.5rem;
+        
+        &::before {
+          content: "📝";
+          font-size: 1.2rem;
+        }
+      }
+      
+      textarea {
+        width: 100%;
+        padding: 1rem;
+        border: 1px solid #d2d2d7;
+        border-radius: 8px;
+        font-size: 0.9rem;
+        resize: vertical;
+        margin-bottom: 1rem;
+        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+        line-height: 1.5;
+        
+        &:focus {
+          outline: none;
+          border-color: #007aff;
+          box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
+        }
+        
+        &::placeholder {
+          color: #86868b;
+        }
+      }
+      
+      .btn-primary {
+        background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
+        border: none;
+        border-radius: 8px;
+        padding: 0.75rem 1.5rem;
+        color: white;
+        font-weight: 500;
+        font-size: 0.9rem;
+        cursor: pointer;
+        transition: all 0.2s ease;
+        
+        &:hover:not(:disabled) {
+          transform: translateY(-1px);
+          box-shadow: 0 4px 12px rgba(0, 122, 255, 0.3);
+        }
+        
+        &:disabled {
+          opacity: 0.5;
+          cursor: not-allowed;
+        }
+      }
+    }
+  }
+
+  // 参考图片和CAD图纸 - 美观卡片式并排布局
+  .file-upload-cards {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 1.5rem;
+    
+    // 移动端响应式
+    @media (max-width: 768px) {
+      grid-template-columns: 1fr;
+      gap: 1rem;
+    }
+    
+    .upload-card {
+      background: white;
+      border: 1px solid #e5e5e7;
+      border-radius: 16px;
+      overflow: hidden;
+      box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
+      transition: all 0.3s ease;
+      
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+      }
+      
+      .card-header {
+        padding: 1.25rem 1.5rem 1rem 1.5rem;
+        border-bottom: 1px solid #f2f2f7;
+        display: flex;
+        align-items: center;
+        gap: 0.75rem;
+        
+        .card-icon {
+          width: 40px;
+          height: 40px;
+          border-radius: 10px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          
+          &.image-icon {
+            background: linear-gradient(135deg, #ff9500 0%, #ff6b00 100%);
+            color: white;
+          }
+          
+          &.cad-icon {
+            background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
+            color: white;
+          }
+        }
+        
+        h5 {
+          margin: 0;
+          font-size: 1.1rem;
+          font-weight: 600;
+          color: #1d1d1f;
+        }
+      }
+      
+      .card-body {
+        padding: 1.5rem;
+        
+        .file-upload-zone {
+          border: 2px dashed #d2d2d7;
+          border-radius: 12px;
+          padding: 2rem 1rem;
+          text-align: center;
+          cursor: pointer;
+          transition: all 0.3s ease;
+          background: #fafafa;
+          min-height: 140px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          
+          &:hover {
+            border-color: #007aff;
+            background: rgba(0, 122, 255, 0.02);
+            transform: scale(1.02);
+          }
+          
+          .upload-content {
+            .upload-icon {
+              margin-bottom: 1rem;
+              
+              svg {
+                color: #86868b;
+                transition: color 0.2s ease;
+              }
+            }
+            
+            p {
+              margin: 0 0 0.5rem 0;
+              font-size: 1rem;
+              color: #1d1d1f;
+              font-weight: 500;
+            }
+            
+            .hint {
+              font-size: 0.85rem;
+              color: #86868b;
+              line-height: 1.4;
+            }
+          }
+          
+          &:hover .upload-content {
+            .upload-icon svg {
+              color: #007aff;
+            }
+          }
+        }
+      }
+      
+      // 特定卡片样式
+      &.image-card {
+        .file-upload-zone:hover {
+          border-color: #ff9500;
+          background: rgba(255, 149, 0, 0.02);
+          
+          .upload-content .upload-icon svg {
+            color: #ff9500;
+          }
+        }
+      }
+      
+      &.cad-card {
+        .file-upload-zone:hover {
+          border-color: #007aff;
+          background: rgba(0, 122, 255, 0.02);
+          
+          .upload-content .upload-icon svg {
+            color: #007aff;
+          }
+        }
+      }
+    }
+  }
+}
+
+// 深色模式支持
+@media (prefers-color-scheme: dark) {
+  .materials-section.alternative-layout {
+    .text-upload-section .upload-item.text-item,
+    .file-upload-cards .upload-card {
+      background: #1c1c1e;
+      border-color: #38383a;
+      
+      h5 {
+        color: #f2f2f7;
+      }
+      
+      textarea {
+        background: #2c2c2e;
+        border-color: #48484a;
+        color: #f2f2f7;
+        
+        &::placeholder {
+          color: #8e8e93;
+        }
+      }
+      
+      .file-upload-zone {
+        background: #2c2c2e;
+        border-color: #48484a;
+        
+        .upload-content {
+          p {
+            color: #f2f2f7;
+          }
+          
+          .hint {
+            color: #8e8e93;
+          }
+          
+          .upload-icon svg {
+            color: #8e8e93;
+          }
+        }
+      }
+    }
+  }
+}

+ 10 - 8
src/app/shared/components/requirements-confirm-card/requirements-confirm-card.html

@@ -91,10 +91,9 @@
     <!-- 素材解析标签页 -->
     @if (activeTab === 'materials') {
       <div class="materials-section">
-        <div class="upload-grid">
-          
-          <!-- 文本输入区域 -->
-          <div class="upload-item">
+        <!-- 文本输入区域 - 独立一行 -->
+        <div class="text-upload-section">
+          <div class="upload-item text-item">
             <h5>文本描述</h5>
             <form [formGroup]="materialUploadForm" (ngSubmit)="onTextSubmit()">
               <textarea 
@@ -107,9 +106,12 @@
               </button>
             </form>
           </div>
+        </div>
 
-          <!-- 图片上传区域 -->
-          <div class="upload-item">
+        <!-- 参考图片和CAD图纸并排布局 -->
+        <div class="file-upload-grid">
+          <!-- 参考图片上传区域 -->
+          <div class="upload-item image-item">
             <h5>参考图片</h5>
             <div class="file-upload-zone" (click)="imageInput.click()">
               <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -123,8 +125,8 @@
             <input #imageInput type="file" multiple accept="image/*" (change)="onFileSelected($event, 'image')" style="display: none;">
           </div>
 
-          <!-- CAD上传区域 -->
-          <div class="upload-item">
+          <!-- CAD图纸上传区域 -->
+          <div class="upload-item cad-item">
             <h5>CAD图纸</h5>
             <div class="file-upload-zone" (click)="cadInput.click()">
               <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">

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

@@ -5,6 +5,70 @@
   height: 100%; 
 }
 
+// 新增:文本输入区域独立样式
+.text-upload-section {
+  margin-bottom: 1.5rem;
+  
+  .upload-item {
+    background: white;
+    border: 1px solid #e0e0e0;
+    border-radius: 8px;
+    padding: 1rem;
+    
+    h5 {
+      margin: 0 0 0.75rem 0;
+      font-size: 0.9rem;
+      color: #333;
+      font-weight: 600;
+    }
+    
+    textarea {
+      width: 100%;
+      padding: 0.75rem;
+      border: 1px solid #ddd;
+      border-radius: 4px;
+      font-size: 0.85rem;
+      resize: vertical;
+      margin-bottom: 0.75rem;
+      
+      &:focus {
+        outline: none;
+        border-color: #007bff;
+      }
+    }
+    
+    .btn-primary {
+      background: #007bff;
+      border: none;
+      border-radius: 4px;
+      padding: 0.5rem 1rem;
+      color: white;
+      font-size: 0.85rem;
+      cursor: pointer;
+      
+      &:disabled {
+        opacity: 0.6;
+        cursor: not-allowed;
+      }
+    }
+  }
+}
+
+// 新增:参考图片和CAD图纸并排布局
+.file-upload-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 1rem;
+  
+  // 移动端响应式
+  @media (max-width: 768px) {
+    grid-template-columns: 1fr;
+  }
+}
+
+// 导入备选方案样式
+@import './requirements-confirm-card-alternative.scss';
+
 .requirements-confirm-card {
   .card-header {
     display: flex;
@@ -246,6 +310,98 @@
 
   // 素材解析部分
   .materials-section {
+    // 文本输入区域 - 独立一行
+    .text-upload-section {
+      margin-bottom: $ios-spacing-lg;
+      
+      .upload-item.text-item {
+        h5 {
+          margin: 0 0 $ios-spacing-sm 0;
+          font-size: $ios-font-size-xs;
+          font-weight: $ios-font-weight-semibold;
+          color: $ios-text-primary;
+        }
+        
+        textarea {
+          width: 100%;
+          padding: $ios-spacing-sm;
+          border: 1px solid $ios-border;
+          border-radius: 6px;
+          font-size: $ios-font-size-xs;
+          resize: vertical;
+          margin-bottom: $ios-spacing-sm;
+          
+          &:focus {
+            outline: none;
+            border-color: $ios-primary;
+            box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
+          }
+        }
+      }
+    }
+
+    // 参考图片和CAD图纸并排布局
+    .file-upload-grid {
+      display: grid;
+      grid-template-columns: 1fr 1fr;
+      gap: 2%;
+      margin-bottom: $ios-spacing-lg;
+      
+      // 移动端响应式 - 改为垂直布局
+      @media (max-width: 768px) {
+        grid-template-columns: 1fr;
+        gap: $ios-spacing-md;
+      }
+      
+      .upload-item {
+        &.image-item, &.cad-item {
+          h5 {
+            margin: 0 0 $ios-spacing-sm 0;
+            font-size: $ios-font-size-xs;
+            font-weight: $ios-font-weight-semibold;
+            color: $ios-text-primary;
+          }
+          
+          .file-upload-zone {
+            border: 2px dashed $ios-border;
+            border-radius: 8px;
+            padding: $ios-spacing-lg;
+            text-align: center;
+            cursor: pointer;
+            transition: all 0.2s ease;
+            min-height: 120px;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            
+            &:hover {
+              border-color: $ios-primary;
+              background: rgba(0, 122, 255, 0.02);
+            }
+            
+            svg {
+              color: $ios-text-secondary;
+              margin-bottom: $ios-spacing-sm;
+            }
+            
+            p {
+              margin: 0 0 $ios-spacing-xs 0;
+              font-size: $ios-font-size-xs;
+              color: $ios-text-primary;
+              font-weight: $ios-font-weight-medium;
+            }
+            
+            .hint {
+              font-size: $ios-font-size-xs;
+              color: $ios-text-secondary;
+            }
+          }
+        }
+      }
+    }
+
+    // 保留原有的upload-grid样式作为备选方案
     .upload-grid {
       display: grid;
       grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));