Browse Source

feat:customer11

徐福静0235668 6 hours ago
parent
commit
ad694b56e3
22 changed files with 2829 additions and 247 deletions
  1. 0 2
      src/app/app.routes.ts
  2. 7 17
      src/app/pages/customer-service/consultation-order/consultation-order.ts
  3. 13 3
      src/app/pages/customer-service/project-detail/project-detail.html
  4. 36 0
      src/app/pages/customer-service/project-detail/project-detail.scss
  5. 30 2
      src/app/pages/customer-service/project-detail/project-detail.ts
  6. 2 5
      src/app/pages/customer-service/project-list/project-list.ts
  7. 13 10
      src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav.component.html
  8. 112 109
      src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav.component.scss
  9. 24 2
      src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav.component.ts
  10. 67 9
      src/app/pages/designer/project-detail/project-detail.html
  11. 218 8
      src/app/pages/designer/project-detail/project-detail.scss
  12. 135 2
      src/app/pages/designer/project-detail/project-detail.ts
  13. 420 0
      src/app/shared/components/process-progress-bar/process-progress-bar.component.scss
  14. 189 0
      src/app/shared/components/process-progress-bar/process-progress-bar.ts
  15. 84 0
      src/app/shared/components/process-status-bar/process-status-bar.component.scss
  16. 44 0
      src/app/shared/components/process-status-bar/process-status-bar.ts
  17. 12 0
      src/app/shared/components/progress-bar/progress-bar.component.html
  18. 23 0
      src/app/shared/components/progress-bar/progress-bar.component.ts
  19. 228 65
      src/app/shared/components/requirements-confirm-card/requirements-confirm-card.html
  20. 452 0
      src/app/shared/components/requirements-confirm-card/requirements-confirm-card.scss
  21. 610 12
      src/app/shared/components/requirements-confirm-card/requirements-confirm-card.ts
  22. 110 1
      src/app/shared/styles/_hr-dialog.scss

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

@@ -8,7 +8,6 @@ import { CustomerServiceLayout } from './pages/customer-service/customer-service
 import { Dashboard as CustomerServiceDashboard } from './pages/customer-service/dashboard/dashboard';
 import { ConsultationOrder } from './pages/customer-service/consultation-order/consultation-order';
 import { ProjectList } from './pages/customer-service/project-list/project-list';
-import { ProjectDetail } from './pages/customer-service/project-detail/project-detail';
 import { CaseLibrary } from './pages/customer-service/case-library/case-library';
 import { CaseDetailComponent } from './pages/customer-service/case-detail/case-detail.component';
 import { AfterSalesComponent } from './pages/customer-service/dashboard/pages/after-sales/after-sales.component';
@@ -70,7 +69,6 @@ export const routes: Routes = [
       { path: 'dashboard', component: CustomerServiceDashboard, title: '客服工作台' },
       { path: 'consultation-order', component: ConsultationOrder, title: '客户咨询与下单' },
       { path: 'project-list', component: ProjectList, title: '项目列表' },
-      { path: 'project-detail/:id', component: DesignerProjectDetail, title: '项目详情' },
       { path: 'case-library', component: CaseLibrary, title: '案例库' },
       { path: 'case-detail/:id', component: CaseDetailComponent, title: '案例详情' },
       { path: 'after-sales', component: AfterSalesComponent, title: '售后服务' },

+ 7 - 17
src/app/pages/customer-service/consultation-order/consultation-order.ts

@@ -402,7 +402,7 @@ export class ConsultationOrder {
         this.isSubmitting.set(false);
         this.showSuccessMessage.set(true);
         
-        // 3秒后隐藏成功提示并跳转
+        // 1秒后隐藏成功提示并跳转
         setTimeout(() => {
           this.showSuccessMessage.set(false);
           
@@ -414,14 +414,9 @@ export class ConsultationOrder {
           };
           this.orderCreated.emit(orderData);
           
-          // 跳转到项目详情页面
-          this.router.navigate(['/customer-service/project-detail/mock-9'], {
-            queryParams: {
-              role: 'customer_service',
-              activeTab: 'overview'
-            }
-          });
-        }, 3000);
+          // 跳转到设计师项目详情页面
+          this.router.navigate(['/designer/project-detail/mock-9']);
+        }, 1000);
       }, 1500);
     }
   }
@@ -493,14 +488,9 @@ export class ConsultationOrder {
             };
             this.orderCreated.emit(orderData);
             
-            // 跳转到项目详情页面
-            this.router.navigate(['/customer-service/project-detail/mock-9'], {
-              queryParams: {
-                role: 'customer_service',
-                activeTab: 'overview'
-              }
-            });
-          }, 2500);
+            // 跳转到设计师项目详情页面
+            this.router.navigate(['/designer/project-detail/mock-9']);
+          }, 1000);
         } else {
           this.snackBar.open('创建失败,请稍后重试', '关闭', { duration: 3000 });
         }

+ 13 - 3
src/app/pages/customer-service/project-detail/project-detail.html

@@ -134,7 +134,17 @@
 
       <!-- 进度时间轴 -->
       <div class="card timeline-card">
-        <h3 class="card-title">项目阶段时间轴</h3>
+        <div class="timeline-header">
+          <h3 class="card-title">项目阶段时间轴</h3>
+          
+          <!-- 新的流程状态显示条 - 与标题右对齐 -->
+          <div class="process-status-display">
+            <app-process-status-bar 
+              [stageData]="processStatusStages">
+            </app-process-status-bar>
+          </div>
+        </div>
+        
         <div class="project-timeline">
           @for (stage of projectStages; track $index; let i = $index) {
             <div class="timeline-item" [class.stage-completed]="stage.completed" [class.stage-in-progress]="stage.inProgress">
@@ -149,8 +159,8 @@
                   }
                 </svg>
               </div>
-              @if (i < projectStages.length - 1) {
-                <div class="timeline-line" [class.line-completed]="stage.completed && projectStages[i+1].completed"></div>
+              @if (i < (projectStages?.length || 0) - 1) {
+                <div class="timeline-line" [class.line-completed]="stage.completed && projectStages?.[i+1]?.completed"></div>
               }
               <div class="timeline-content">
                 <div class="timeline-header">

+ 36 - 0
src/app/pages/customer-service/project-detail/project-detail.scss

@@ -1,5 +1,41 @@
 @use "sass:color";
 
+// 时间轴卡片头部布局
+.timeline-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24px;
+  
+  .card-title {
+    margin: 0;
+    flex: 1;
+  }
+  
+  .process-status-display {
+    flex-shrink: 0;
+    margin-left: 16px;
+    
+    // 确保流程状态显示条可见
+    app-process-status-bar {
+      display: block;
+      min-height: 32px;
+    }
+  }
+}
+
+// 流程进度条样式
+.process-progress-section {
+  margin: 24px 0 32px 0;
+  padding: 0;
+  display: block !important;
+  visibility: visible !important;
+  
+  // 确保与标题左对齐
+  margin-left: 0;
+  padding-left: 0;
+}
+
 // iOS风格全局变量
 $primary-color: #007AFF;
 $primary-dark: #0047AB;

+ 30 - 2
src/app/pages/customer-service/project-detail/project-detail.ts

@@ -8,6 +8,7 @@ import { Project, Task, Message, FileItem, CustomerFeedback, Milestone } from '.
 import { ModificationRequestDialog } from './modification-request-dialog';
 import { ComplaintWarningDialog } from './complaint-warning-dialog';
 import { RefundRequestDialog } from './refund-request-dialog';
+import { ProcessStatusBarComponent, ProcessStatusStage } from '../../../shared/components/process-status-bar/process-status-bar';
 
 // 定义项目阶段接口
 interface ProjectStage {
@@ -72,7 +73,7 @@ interface DecorationStyle {
 @Component({
   selector: 'app-project-detail',
   standalone: true,
-  imports: [CommonModule, FormsModule, ReactiveFormsModule, RouterModule, MatDialogModule],
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, RouterModule, MatDialogModule, ProcessStatusBarComponent],
   templateUrl: './project-detail.html',
   styleUrls: ['./project-detail.scss', '../customer-service-styles.scss']
 })
@@ -112,7 +113,7 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
   newMessage = signal('');
   
   // 项目阶段数据 - 进度时间轴
-  projectStages: ProjectStage[] = [
+  projectStages?: ProjectStage[] = [
     {      
       name: '需求沟通',
       completed: true,
@@ -165,6 +166,16 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
       details: '项目验收、交付和投诉处理'
     }
   ];
+
+  // 流程状态条数据
+  processStatusStages: ProcessStatusStage[] = [
+    { id: 'requirement', name: '需求沟通', status: 'completed' },
+    { id: 'modeling', name: '建模', status: 'completed' },
+    { id: 'soft-decoration', name: '软装', status: 'in-progress' },
+    { id: 'rendering', name: '渲染', status: 'pending' },
+    { id: 'post-processing', name: '后期', status: 'pending' },
+    { id: 'complaint-handling', name: '投诉处理', status: 'pending' }
+  ];
   
   // 企业微信聊天相关
   @ViewChild('wechatMessages') wechatMessagesContainer: any;
@@ -840,6 +851,23 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
     return new Date();
   });
   
+  // 流程阶段点击处理
+  onStageClick(stage: any): void {
+    console.log('Stage clicked:', stage);
+    
+    // 查找对应的项目阶段
+    const stageIndex = this.projectStages?.findIndex(s => s.name === stage.name) ?? -1;
+    
+    if (stageIndex >= 0 && stageIndex < (this.projectStages?.length || 0)) {
+      const projectStage = this.projectStages?.[stageIndex];
+      
+      if (projectStage) {
+        console.log('Project stage details:', projectStage);
+        // 这里可以添加更多的交互逻辑,比如显示详情弹窗
+      }
+    }
+  }
+  
   // 售后处理入口方法
   openModificationRequest(): void {
     const dialogRef = this.dialog.open(ModificationRequestDialog, {

+ 2 - 5
src/app/pages/customer-service/project-list/project-list.ts

@@ -417,12 +417,9 @@ export class ProjectList implements OnInit {
     return 'req';
   }
 
-  // 详情跳转(附带角色与模块)
+  // 详情跳转到设计师项目详情页面
   navigateToProject(project: ProjectListItem, columnId: 'pending' | 'req' | 'delivery' | 'done') {
-    const tab = columnId === 'pending' ? 'members' : (columnId === 'req' ? 'requirements' : 'overview');
-    this.router.navigate(['/customer-service/project-detail', project.id], {
-      queryParams: { role: 'customer_service', activeTab: tab }
-    });
+    this.router.navigate(['/designer/project-detail', project.id]);
   }
 
   // 新增:直接进入沟通管理(消息)标签

+ 13 - 10
src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav.component.html

@@ -1,11 +1,14 @@
-<div class="vertical-nav">
-  @for (item of navItems; track item.id) {
-    <button 
-      class="nav-item"
-      [class.active]="isActive(item.id)"
-      (click)="onTabClick(item.id)"
-      type="button">
-      <span class="nav-text">{{ item.name }}</span>
-    </button>
-  }
+<div class="horizontal-tab-nav">
+  <div class="tab-container" #tabContainer>
+    @for (item of navItems; track item.id) {
+      <button 
+        class="tab-item"
+        [class.active]="isActive(item.id)"
+        (click)="onTabClick(item.id)"
+        type="button">
+        <span class="tab-text">{{ item.name }}</span>
+      </button>
+    }
+  </div>
+  <div class="tab-indicator" [style.transform]="'translateX(' + getIndicatorPosition() + 'px)'"></div>
 </div>

+ 112 - 109
src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav.component.scss

@@ -1,145 +1,148 @@
 @use '../../../../../shared/styles/_ios-theme.scss' as *;
 
-.vertical-nav {
+// 横向滑动条式选项卡导航
+.horizontal-tab-nav {
+  position: relative;
   display: flex;
   flex-direction: column;
-  gap: $ios-spacing-xs; // 减小间距
-  padding: $ios-spacing-sm; // 减小内边距
-  background: $ios-background;
-  border-radius: $ios-radius-md; // 减小圆角
-  border: 1px solid $ios-border;
-  box-shadow: $ios-shadow-sm; // 减小阴影
-  min-width: 160px; // 减小最小宽度
-  max-width: 200px; // 减小最大宽度
   width: 100%;
+  background: $ios-background;
+  border-radius: $ios-radius-lg;
+  overflow: hidden;
+  box-shadow: $ios-shadow-sm;
+}
 
-  // 当父容器有 horizontal-nav 类时,改为横向布局
-  :host(.horizontal-nav) & {
-    flex-direction: row;
-    gap: 0;
-    padding: 0;
-    background: transparent;
-    border: none;
-    box-shadow: none;
-    min-width: unset;
-    max-width: unset;
-    width: 100%;
-  }
+.tab-container {
+  display: flex;
+  position: relative;
+  background: rgba(0, 122, 255, 0.05);
+  border-radius: $ios-radius-lg;
+  padding: 4px;
 }
 
-.nav-item {
+.tab-item {
+  flex: 1;
   display: flex;
   align-items: center;
-  padding: $ios-spacing-xs $ios-spacing-sm; // 减小内边距
-  border-radius: $ios-radius-sm; // 减小圆角
-  cursor: pointer;
-  transition: all 0.2s ease;
-  color: $ios-text-primary;
-  text-decoration: none;
-  font-size: $ios-font-size-sm; // 减小字体
-  font-weight: $ios-font-weight-regular;
-  line-height: 1.2; // 减小行高
-  background: transparent;
+  justify-content: center;
+  padding: 12px 16px;
   border: none;
+  background: transparent;
+  color: $ios-text-secondary;
+  font-size: $ios-font-size-md;
+  font-weight: $ios-font-weight-medium;
+  cursor: pointer;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  border-radius: $ios-radius-md;
+  position: relative;
+  z-index: 2;
+  min-width: 120px;
 
   &:hover {
-    background-color: $ios-background-secondary;
+    color: $ios-primary;
+    background: rgba(0, 122, 255, 0.08);
   }
 
   &.active {
-    background-color: $ios-primary;
-    color: white;
-    font-weight: $ios-font-weight-medium;
+    color: $ios-primary;
+    font-weight: $ios-font-weight-semibold;
   }
 
-  // 水平布局时的样式
-  :host(.horizontal-nav) & {
-    flex: 1;
-    justify-content: center;
-    padding: $ios-spacing-md $ios-spacing-lg;
-    border-radius: 0;
-    font-size: $ios-font-size-md;
-    font-weight: $ios-font-weight-medium;
-    line-height: 1.4;
-    border-bottom: 3px solid transparent;
-
-    &:first-child {
-      border-top-left-radius: $ios-radius-md;
-      border-bottom-left-radius: $ios-radius-md;
-    }
-
-    &:last-child {
-      border-top-right-radius: $ios-radius-md;
-      border-bottom-right-radius: $ios-radius-md;
-    }
-
-    &.active {
-      background-color: rgba(0, 122, 255, 0.1);
-      color: $ios-primary;
-      font-weight: $ios-font-weight-semibold;
-      border-bottom-color: $ios-primary;
-    }
+  .tab-text {
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    transition: all 0.3s ease;
   }
+}
 
-  .nav-icon {
-    margin-right: $ios-spacing-xs; // 减小图标间距
-    font-size: 14px; // 减小图标尺寸
-    width: 14px;
-    flex-shrink: 0;
+// 滑动指示器
+.tab-indicator {
+  position: absolute;
+  top: 4px;
+  left: 4px;
+  height: calc(100% - 8px);
+  width: 120px;
+  background: $ios-background;
+  border-radius: $ios-radius-md;
+  box-shadow: 0 2px 8px rgba(0, 122, 255, 0.15);
+  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  z-index: 1;
+}
 
-    // 水平布局时隐藏图标
-    :host(.horizontal-nav) & {
-      display: none;
-    }
+// 响应式设计
+@media (max-width: 768px) {
+  .horizontal-tab-nav {
+    margin: 0 -8px;
   }
   
-  .nav-text {
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    flex: 1;
-
-    // 水平布局时居中对齐
-    :host(.horizontal-nav) & {
-      text-align: center;
-    }
+  .tab-item {
+    padding: 10px 12px;
+    font-size: $ios-font-size-sm;
+    min-width: 100px;
+  }
+  
+  .tab-indicator {
+    width: 100px;
   }
 }
 
-// 响应式适配
-@media (max-width: 1200px) {
-  .vertical-nav {
-    min-width: 140px;
-    max-width: 180px;
+@media (max-width: 480px) {
+  .tab-item {
+    padding: 8px 10px;
+    font-size: 13px;
+    min-width: 80px;
   }
   
-  .nav-item {
-    font-size: $ios-font-size-xs;
-    padding: 6px $ios-spacing-xs;
-    
-    .nav-icon {
-      font-size: 12px;
-      width: 12px;
-    }
+  .tab-indicator {
+    width: 80px;
   }
 }
 
-@media (max-width: 768px) {
-  .vertical-nav {
-    min-width: 120px;
-    max-width: 160px;
-    padding: $ios-spacing-xs;
-    gap: 2px;
-  }
+// 保持向后兼容的垂直导航样式(如果需要)
+.vertical-nav {
+  display: flex;
+  flex-direction: column;
+  gap: $ios-spacing-xs;
+  padding: $ios-spacing-sm;
+  background: $ios-background;
+  border-radius: $ios-radius-md;
+  border: 1px solid $ios-border;
+  box-shadow: $ios-shadow-sm;
+  min-width: 160px;
+  max-width: 200px;
+  width: 100%;
 
   .nav-item {
-    padding: 4px $ios-spacing-xs;
-    font-size: 11px;
-    
-    .nav-icon {
-      font-size: 10px;
-      width: 10px;
-      margin-right: 4px;
+    display: flex;
+    align-items: center;
+    padding: $ios-spacing-xs $ios-spacing-sm;
+    border-radius: $ios-radius-sm;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    color: $ios-text-primary;
+    text-decoration: none;
+    font-size: $ios-font-size-sm;
+    font-weight: $ios-font-weight-regular;
+    line-height: 1.2;
+    background: transparent;
+    border: none;
+
+    &:hover {
+      background-color: $ios-background-secondary;
+    }
+
+    &.active {
+      background-color: $ios-primary;
+      color: white;
+      font-weight: $ios-font-weight-medium;
+    }
+
+    .nav-text {
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      flex: 1;
     }
   }
 }

+ 24 - 2
src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav.component.ts

@@ -1,4 +1,4 @@
-import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { Component, Input, Output, EventEmitter, AfterViewInit, ElementRef, ViewChild } from '@angular/core';
 import { CommonModule } from '@angular/common';
 
 export interface NavItem {
@@ -14,7 +14,7 @@ export interface NavItem {
   templateUrl: './vertical-nav.component.html',
   styleUrls: ['./vertical-nav.component.scss']
 })
-export class VerticalNavComponent {
+export class VerticalNavComponent implements AfterViewInit {
   @Input() activeTab: 'progress' | 'members' | 'files' = 'progress';
   @Input() navItems: NavItem[] = [
     { id: 'progress', name: '项目进度' },
@@ -23,6 +23,14 @@ export class VerticalNavComponent {
   ];
   
   @Output() tabChange = new EventEmitter<'progress' | 'members' | 'files'>();
+  @ViewChild('tabContainer', { static: false }) tabContainer!: ElementRef;
+
+  private tabWidth = 120; // 默认选项卡宽度
+
+  ngAfterViewInit(): void {
+    // 动态计算选项卡宽度
+    this.calculateTabWidth();
+  }
 
   onTabClick(tabId: 'progress' | 'members' | 'files'): void {
     this.tabChange.emit(tabId);
@@ -31,4 +39,18 @@ export class VerticalNavComponent {
   isActive(tabId: 'progress' | 'members' | 'files'): boolean {
     return this.activeTab === tabId;
   }
+
+  // 计算滑动指示器的位置
+  getIndicatorPosition(): number {
+    const activeIndex = this.navItems.findIndex(item => item.id === this.activeTab);
+    return activeIndex * this.tabWidth;
+  }
+
+  // 动态计算选项卡宽度
+  private calculateTabWidth(): void {
+    if (this.tabContainer) {
+      const containerWidth = this.tabContainer.nativeElement.offsetWidth;
+      this.tabWidth = (containerWidth - 8) / this.navItems.length; // 减去padding
+    }
+  }
 }

+ 67 - 9
src/app/pages/designer/project-detail/project-detail.html

@@ -16,6 +16,15 @@
     
     <div class="header-right">
       <div class="header-actions">
+        <!-- 导航条 - 移动到顶部与导出按钮水平对齐 -->
+        <div class="top-nav-container">
+          <app-vertical-nav 
+            [activeTab]="activeTab" 
+            (tabChange)="switchTab($event)"
+            class="top-nav">
+          </app-vertical-nav>
+        </div>
+        
         <!-- 导出阶段报告 -->
         <button (click)="exportProjectReport()" class="action-btn secondary-btn">
           <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -124,14 +133,7 @@
   <!-- 顶部导航标签页 -->
   <!-- 原有代码保留 -->
 
-  <!-- 水平导航栏 - 位于顶部导航栏下方 -->
-  <div class="horizontal-nav-container">
-    <app-vertical-nav 
-      [activeTab]="activeTab" 
-      (tabChange)="switchTab($event)"
-      class="horizontal-nav">
-    </app-vertical-nav>
-  </div>
+  <!-- 水平导航栏 - 已移动到顶部,此处删除 -->
 
   <div class="tab-content">
     <!-- 项目进度标签页 -->
@@ -167,6 +169,59 @@
                       }
                       @if (project.highPriorityNeeds.length === 0) { <li class="desc">无</li> }
                     </ul>
+                    
+                    <!-- 新增:需求关键信息同步区域 -->
+                    <div class="requirement-sync-info">
+                      <h4>确认需求关键信息</h4>
+                      <div class="key-info-grid">
+                        @if (requirementKeyInfo.colorAtmosphere.description) {
+                          <div class="info-item">
+                            <span class="info-label">色彩氛围</span>
+                            <span class="info-value">{{ requirementKeyInfo.colorAtmosphere.description }}</span>
+                            <div class="color-preview" [style.background-color]="requirementKeyInfo.colorAtmosphere.mainColor"></div>
+                          </div>
+                        }
+                        
+                        @if (requirementKeyInfo.spaceStructure.aspectRatio > 0) {
+                          <div class="info-item">
+                            <span class="info-label">空间结构</span>
+                            <span class="info-value">比例 {{ requirementKeyInfo.spaceStructure.aspectRatio.toFixed(1) }}</span>
+                          </div>
+                        }
+                        
+                        @if (requirementKeyInfo.materialWeights.woodRatio > 0 || requirementKeyInfo.materialWeights.fabricRatio > 0) {
+                          <div class="info-item">
+                            <span class="info-label">材质权重</span>
+                            <div class="material-weights">
+                              @if (requirementKeyInfo.materialWeights.woodRatio > 0) {
+                                <span class="weight-item">木质 {{ requirementKeyInfo.materialWeights.woodRatio }}%</span>
+                              }
+                              @if (requirementKeyInfo.materialWeights.fabricRatio > 0) {
+                                <span class="weight-item">布艺 {{ requirementKeyInfo.materialWeights.fabricRatio }}%</span>
+                              }
+                              @if (requirementKeyInfo.materialWeights.metalRatio > 0) {
+                                <span class="weight-item">金属 {{ requirementKeyInfo.materialWeights.metalRatio }}%</span>
+                              }
+                            </div>
+                          </div>
+                        }
+                        
+                        @if (requirementKeyInfo.presetAtmosphere.name) {
+                          <div class="info-item">
+                            <span class="info-label">预设氛围</span>
+                            <span class="info-value">{{ requirementKeyInfo.presetAtmosphere.name }}</span>
+                            <span class="color-temp">{{ requirementKeyInfo.presetAtmosphere.colorTemp }}</span>
+                          </div>
+                        }
+                      </div>
+                      
+                      @if (getRequirementSummary().length === 0) {
+                        <div class="sync-placeholder">
+                          <span class="placeholder-text">暂无同步的需求信息</span>
+                          <button class="sync-btn" (click)="syncRequirementKeyInfo({})">手动同步</button>
+                        </div>
+                      }
+                    </div>
                   </div>
                 </div>
               } @else {
@@ -211,7 +266,10 @@
                           (formSubmit)="onConsultationOrderSubmit($event)"
                         ></app-consultation-order-panel>
                       } @else if (stage === '需求沟通') {
-                        <app-requirements-confirm-card></app-requirements-confirm-card>
+                        <app-requirements-confirm-card 
+                          (requirementConfirmed)="syncRequirementKeyInfo($event)"
+                          (progressUpdated)="syncRequirementKeyInfo($event)">
+                        </app-requirements-confirm-card>
                       } @else if (stage === '方案确认') {
                         <!-- 方案确认阶段暂时显示占位内容 -->
                         <div class="stage-placeholder">

+ 218 - 8
src/app/pages/designer/project-detail/project-detail.scss

@@ -153,6 +153,128 @@
         }
       }
     }
+
+    // 需求关键信息同步区域样式
+    .requirement-sync-info {
+      margin-top: 16px;
+      padding: 16px;
+      background: rgba(0, 122, 255, 0.02);
+      border: 1px solid rgba(0, 122, 255, 0.1);
+      border-radius: 8px;
+
+      h4 {
+        font-size: 14px;
+        font-weight: 600;
+        color: #1d1d1f;
+        margin: 0 0 12px 0;
+        display: flex;
+        align-items: center;
+        gap: 6px;
+
+        &::before {
+          content: '';
+          width: 3px;
+          height: 14px;
+          background: #007aff;
+          border-radius: 2px;
+        }
+      }
+
+      .key-info-grid {
+        display: grid;
+        gap: 12px;
+
+        .info-item {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+          padding: 8px 12px;
+          background: white;
+          border-radius: 6px;
+          border: 1px solid rgba(0, 0, 0, 0.06);
+          font-size: 13px;
+
+          .info-label {
+            font-weight: 500;
+            color: #666;
+            min-width: 60px;
+            flex-shrink: 0;
+          }
+
+          .info-value {
+            color: #1d1d1f;
+            font-weight: 500;
+            flex: 1;
+          }
+
+          .color-preview {
+            width: 16px;
+            height: 16px;
+            border-radius: 3px;
+            border: 1px solid rgba(0, 0, 0, 0.1);
+            flex-shrink: 0;
+          }
+
+          .color-temp {
+            font-size: 11px;
+            color: #8e8e93;
+            background: rgba(142, 142, 147, 0.1);
+            padding: 2px 6px;
+            border-radius: 4px;
+          }
+
+          .material-weights {
+            display: flex;
+            gap: 6px;
+            flex-wrap: wrap;
+
+            .weight-item {
+              font-size: 11px;
+              background: rgba(52, 199, 89, 0.1);
+              color: #34c759;
+              padding: 2px 6px;
+              border-radius: 4px;
+              font-weight: 500;
+            }
+          }
+        }
+      }
+
+      .sync-placeholder {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 12px;
+        background: rgba(142, 142, 147, 0.05);
+        border-radius: 6px;
+        border: 1px dashed rgba(142, 142, 147, 0.3);
+
+        .placeholder-text {
+          font-size: 13px;
+          color: #8e8e93;
+        }
+
+        .sync-btn {
+          padding: 6px 12px;
+          background: #007aff;
+          color: white;
+          border: none;
+          border-radius: 4px;
+          font-size: 12px;
+          font-weight: 500;
+          cursor: pointer;
+          transition: background-color 0.2s ease;
+
+          &:hover {
+            background: #0056cc;
+          }
+
+          &:active {
+            background: #004499;
+          }
+        }
+      }
+    }
   }
 }
 
@@ -232,6 +354,57 @@
       align-items: center;
       gap: $ios-spacing-sm;
       
+      // 顶部导航条容器样式
+      .top-nav-container {
+        display: flex;
+        align-items: center;
+        margin-right: $ios-spacing-md; // 与导出按钮保持间距
+        
+        // 顶部导航条样式
+        .top-nav {
+          .vertical-nav {
+            display: flex;
+            flex-direction: row; // 水平排列
+            gap: 10px; // 导航按钮之间的间距
+            background: rgba(0, 122, 255, 0.05);
+            border-radius: 8px;
+            padding: 4px;
+            
+            .nav-item {
+              padding: 8px 16px;
+              border: none;
+              border-radius: 6px;
+              background: transparent;
+              color: $ios-text-primary;
+              font-size: 14px;
+              font-weight: 500;
+              cursor: pointer;
+              transition: all 0.2s ease;
+              white-space: nowrap;
+              min-height: 36px;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              
+              &:hover {
+                background: rgba(0, 122, 255, 0.1);
+                color: $ios-primary;
+              }
+              
+              &.active {
+                background: $ios-primary;
+                color: white;
+                font-weight: 600;
+              }
+              
+              .nav-text {
+                white-space: nowrap;
+              }
+            }
+          }
+        }
+      }
+      
       .action-btn {
         padding: 10px 16px;
         border: none;
@@ -705,6 +878,33 @@
       justify-content: flex-start;
       flex-wrap: wrap;
       gap: $ios-spacing-sm;
+      
+      // 移动端顶部导航条样式调整
+      .top-nav-container {
+        order: -1; // 确保导航条在移动端显示在最前面
+        width: 100%;
+        margin-right: 0;
+        margin-bottom: $ios-spacing-sm;
+        
+        .top-nav {
+          .vertical-nav {
+            width: 100%;
+            justify-content: center;
+            gap: 8px; // 移动端减小间距
+            
+            .nav-item {
+              flex: 1;
+              min-height: 32px;
+              padding: 6px 12px;
+              font-size: 13px;
+              
+              .nav-text {
+                font-size: 13px;
+              }
+            }
+          }
+        }
+      }
     }
   }
 }
@@ -725,16 +925,26 @@
     }
   }
   
+  // 超小屏幕下的顶部导航条优化
   .project-header {
-    padding: $ios-spacing-md;
-    
     .header-actions {
-      flex-direction: column;
-      align-items: stretch;
-      
-      .action-btn {
-        width: 100%;
-        margin-bottom: $ios-spacing-xs;
+      .top-nav-container {
+        .top-nav {
+          .vertical-nav {
+            gap: 6px;
+            padding: 3px;
+            
+            .nav-item {
+              min-height: 28px;
+              padding: 4px 8px;
+              font-size: 12px;
+              
+              .nav-text {
+                font-size: 12px;
+              }
+            }
+          }
+        }
       }
     }
   }

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

@@ -93,6 +93,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
     '建模': false,
     '软装': false,
     '渲染': false,
+    '后期': false,
     '尾款结算': false,
     '客户评价': false,
     '投诉处理': false,
@@ -1411,6 +1412,36 @@ export class ProjectDetail implements OnInit, OnDestroy {
     { value: 'lost', label: '已失联' }
   ];
 
+  // 需求关键信息同步数据
+  requirementKeyInfo = {
+    colorAtmosphere: {
+      description: '',
+      mainColor: '',
+      colorTemp: '',
+      materials: [] as string[]
+    },
+    spaceStructure: {
+      lineRatio: 0,
+      blankRatio: 0,
+      flowWidth: 0,
+      aspectRatio: 0,
+      ceilingHeight: 0
+    },
+    materialWeights: {
+      fabricRatio: 0,
+      woodRatio: 0,
+      metalRatio: 0,
+      smoothness: 0,
+      glossiness: 0
+    },
+    presetAtmosphere: {
+      name: '',
+      rgb: '',
+      colorTemp: '',
+      materials: [] as string[]
+    }
+  };
+
   // 客户信息:搜索/选择/清空/同步/快速填写 逻辑
   searchCustomer(): void {
     if (this.customerSearchKeyword.trim().length >= 2) {
@@ -1477,8 +1508,110 @@ export class ProjectDetail implements OnInit, OnDestroy {
   }
 
   previewFile(file: ProjectFile): void {
-    // 实现文件预览逻辑
-    window.open(file.url, '_blank');
+    // 预览文件逻辑
+    console.log('预览文件:', file.name);
+  }
+
+  // 同步需求关键信息到客户信息卡片
+  syncRequirementKeyInfo(requirementData: any): void {
+    if (requirementData) {
+      // 同步色彩氛围信息
+      if (requirementData.colorIndicators) {
+        this.requirementKeyInfo.colorAtmosphere = {
+          description: requirementData.colorIndicators.colorRange || '',
+          mainColor: `rgb(${requirementData.colorIndicators.mainColor?.r || 0}, ${requirementData.colorIndicators.mainColor?.g || 0}, ${requirementData.colorIndicators.mainColor?.b || 0})`,
+          colorTemp: `${requirementData.colorIndicators.colorTemperature || 0}K`,
+          materials: []
+        };
+      }
+
+      // 同步空间结构信息
+      if (requirementData.spaceIndicators) {
+        this.requirementKeyInfo.spaceStructure = {
+          lineRatio: requirementData.spaceIndicators.lineRatio || 0,
+          blankRatio: requirementData.spaceIndicators.blankRatio || 0,
+          flowWidth: requirementData.spaceIndicators.flowWidth || 0,
+          aspectRatio: requirementData.spaceIndicators.aspectRatio || 0,
+          ceilingHeight: requirementData.spaceIndicators.ceilingHeight || 0
+        };
+      }
+
+      // 同步材质权重信息
+      if (requirementData.materialIndicators) {
+        this.requirementKeyInfo.materialWeights = {
+          fabricRatio: requirementData.materialIndicators.fabricRatio || 0,
+          woodRatio: requirementData.materialIndicators.woodRatio || 0,
+          metalRatio: requirementData.materialIndicators.metalRatio || 0,
+          smoothness: requirementData.materialIndicators.smoothness || 0,
+          glossiness: requirementData.materialIndicators.glossiness || 0
+        };
+      }
+
+      // 同步预设氛围信息
+      if (requirementData.selectedPresetAtmosphere) {
+        this.requirementKeyInfo.presetAtmosphere = {
+          name: requirementData.selectedPresetAtmosphere.name || '',
+          rgb: requirementData.selectedPresetAtmosphere.rgb || '',
+          colorTemp: requirementData.selectedPresetAtmosphere.colorTemp || '',
+          materials: requirementData.selectedPresetAtmosphere.materials || []
+        };
+      }
+
+      console.log('需求关键信息已同步:', this.requirementKeyInfo);
+    } else {
+      // 模拟数据用于演示
+      this.requirementKeyInfo = {
+        colorAtmosphere: {
+          description: '温馨暖调',
+          mainColor: 'rgb(255, 230, 180)',
+          colorTemp: '2700K',
+          materials: ['木质', '布艺']
+        },
+        spaceStructure: {
+          lineRatio: 60,
+          blankRatio: 30,
+          flowWidth: 0.9,
+          aspectRatio: 1.6,
+          ceilingHeight: 2.8
+        },
+        materialWeights: {
+          fabricRatio: 50,
+          woodRatio: 30,
+          metalRatio: 20,
+          smoothness: 7,
+          glossiness: 4
+        },
+        presetAtmosphere: {
+          name: '现代简约',
+          rgb: '200,220,240',
+          colorTemp: '5000K',
+          materials: ['金属', '玻璃']
+        }
+      };
+    }
+  }
+
+  // 获取同步的关键信息摘要
+  getRequirementSummary(): string[] {
+    const summary: string[] = [];
+    
+    if (this.requirementKeyInfo.colorAtmosphere.description) {
+      summary.push(`色彩氛围: ${this.requirementKeyInfo.colorAtmosphere.description}`);
+    }
+    
+    if (this.requirementKeyInfo.spaceStructure.aspectRatio > 0) {
+      summary.push(`空间比例: ${this.requirementKeyInfo.spaceStructure.aspectRatio.toFixed(1)}`);
+    }
+    
+    if (this.requirementKeyInfo.materialWeights.woodRatio > 0) {
+      summary.push(`木质占比: ${this.requirementKeyInfo.materialWeights.woodRatio}%`);
+    }
+    
+    if (this.requirementKeyInfo.presetAtmosphere.name) {
+      summary.push(`预设氛围: ${this.requirementKeyInfo.presetAtmosphere.name}`);
+    }
+    
+    return summary;
   }
 
   // 处理咨询订单表单提交

+ 420 - 0
src/app/shared/components/process-progress-bar/process-progress-bar.component.scss

@@ -0,0 +1,420 @@
+// 流程进度条组件样式
+.process-progress-bar {
+  width: 100%;
+  padding: 24px 0;
+  
+  // 进度概览
+  .progress-overview {
+    margin-bottom: 32px;
+    padding: 20px;
+    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
+    border-radius: 12px;
+    border: 1px solid #e9ecef;
+    
+    .progress-info {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 12px;
+      
+      .progress-label {
+        font-size: 16px;
+        font-weight: 600;
+        color: #495057;
+      }
+      
+      .progress-percentage {
+        font-size: 24px;
+        font-weight: 700;
+        color: #1976d2;
+      }
+    }
+    
+    .progress-bar-overview {
+      height: 8px;
+      background: #e9ecef;
+      border-radius: 4px;
+      overflow: hidden;
+      
+      .progress-fill {
+        height: 100%;
+        background: linear-gradient(90deg, #1976d2, #42a5f5);
+        border-radius: 4px;
+        transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
+        box-shadow: 0 2px 4px rgba(25, 118, 210, 0.3);
+      }
+    }
+  }
+  
+  .progress-stages-container {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    position: relative;
+    width: 100%;
+    
+    .stage-wrapper {
+      display: flex;
+      align-items: center;
+      flex: 1;
+      
+      &:last-child {
+        flex: none;
+      }
+      
+      .stage-node {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          min-width: 140px;
+          padding: 20px 16px;
+          border-radius: 16px;
+          transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+          position: relative;
+          background: #ffffff;
+          border: 2px solid transparent;
+          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+          
+          // 确保点击区域不小于44x44像素
+          min-height: 44px;
+          
+          &.clickable {
+            cursor: pointer;
+            
+            &:hover {
+              transform: translateY(-3px);
+              box-shadow: 0 12px 28px rgba(0, 0, 0, 0.15);
+              border-color: rgba(25, 118, 210, 0.3);
+            }
+            
+            &:active {
+              transform: translateY(-1px);
+              transition-duration: 0.1s;
+            }
+            
+            &:focus {
+              outline: none;
+              box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.2);
+            }
+          }
+        
+        // 待开始状态
+        &.pending {
+          background: #f8f9fa;
+          border-color: #e9ecef;
+          
+          .stage-icon {
+            background: #e9ecef;
+            color: #6c757d;
+          }
+          
+          .stage-content {
+            .stage-title {
+              color: #6c757d;
+            }
+            
+            .stage-description {
+              color: #adb5bd;
+            }
+            
+            .stage-status-indicator.status-pending {
+              background: #f8f9fa;
+              color: #6c757d;
+              border-color: #e9ecef;
+            }
+          }
+        }
+        
+        // 进行中状态
+        &.in-progress {
+          background: linear-gradient(135deg, #fff3e0 0%, #ffffff 100%);
+          border-color: #ff9800;
+          box-shadow: 0 4px 12px rgba(255, 152, 0, 0.15);
+          
+          .stage-icon {
+            background: linear-gradient(135deg, #ff9800, #f57c00);
+            color: #ffffff;
+            
+            .progress-dot {
+              animation: pulse 2s infinite;
+            }
+          }
+          
+          .stage-content {
+            .stage-title {
+              color: #e65100;
+              font-weight: 600;
+            }
+            
+            .stage-description {
+              color: #f57c00;
+            }
+            
+            .stage-status-indicator.status-progress {
+              background: linear-gradient(135deg, #ff9800, #f57c00);
+              color: #ffffff;
+              border-color: #ff9800;
+            }
+          }
+        }
+        
+        // 已完成状态
+        &.completed {
+          background: linear-gradient(135deg, #e8f5e9 0%, #ffffff 100%);
+          border-color: #4caf50;
+          box-shadow: 0 4px 12px rgba(76, 175, 80, 0.15);
+          
+          .stage-icon {
+            background: linear-gradient(135deg, #4caf50, #388e3c);
+            color: #ffffff;
+          }
+          
+          .stage-content {
+            .stage-title {
+              color: #2e7d32;
+              font-weight: 600;
+            }
+            
+            .stage-description {
+              color: #388e3c;
+            }
+            
+            .stage-status-indicator.status-completed {
+              background: linear-gradient(135deg, #4caf50, #388e3c);
+              color: #ffffff;
+              border-color: #4caf50;
+            }
+          }
+        }
+        
+        .stage-icon {
+          width: 48px;
+          height: 48px;
+          border-radius: 50%;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          margin-bottom: 12px;
+          transition: all 0.3s ease;
+          
+          svg {
+            width: 20px;
+            height: 20px;
+          }
+          
+          .progress-dot {
+            width: 10px;
+            height: 10px;
+            border-radius: 50%;
+            background: #ffffff;
+          }
+          
+          .pending-dot {
+            width: 8px;
+            height: 8px;
+            border-radius: 50%;
+            background: #6c757d;
+          }
+        }
+        
+        .stage-content {
+          text-align: center;
+          width: 100%;
+          
+          .stage-title {
+            font-size: 15px;
+            font-weight: 500;
+            margin-bottom: 6px;
+            line-height: 1.3;
+            transition: all 0.3s ease;
+          }
+          
+          .stage-description {
+            font-size: 12px;
+            margin-bottom: 8px;
+            line-height: 1.4;
+            opacity: 0.8;
+            transition: all 0.3s ease;
+          }
+          
+          .stage-status-indicator {
+            font-size: 11px;
+            padding: 6px 12px;
+            border-radius: 16px;
+            border: 1px solid;
+            font-weight: 500;
+            transition: all 0.3s ease;
+            display: inline-block;
+          }
+        }
+      }
+      
+      .stage-connector {
+        flex: 1;
+        height: 3px;
+        margin: 0 12px;
+        position: relative;
+        display: flex;
+        align-items: center;
+        
+        .connector-line {
+          flex: 1;
+          height: 3px;
+          background: #e9ecef;
+          border-radius: 2px;
+          transition: all 0.6s ease;
+        }
+        
+        .connector-arrow {
+          margin-left: 8px;
+          color: #e9ecef;
+          transition: all 0.6s ease;
+          
+          svg {
+            width: 12px;
+            height: 12px;
+          }
+        }
+        
+        &.connector-completed {
+          .connector-line {
+            background: linear-gradient(90deg, #4caf50, #66bb6a);
+            box-shadow: 0 1px 3px rgba(76, 175, 80, 0.3);
+          }
+          
+          .connector-arrow {
+            color: #4caf50;
+          }
+        }
+      }
+    }
+  }
+}
+
+// 脉冲动画
+@keyframes pulse {
+  0% {
+    transform: scale(1);
+    opacity: 1;
+  }
+  50% {
+    transform: scale(1.2);
+    opacity: 0.7;
+  }
+  100% {
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .process-progress-bar {
+    .progress-stages-container {
+      .stage-wrapper {
+        .stage-node {
+          min-width: 100px;
+          padding: 12px 8px;
+          
+          .stage-title {
+            font-size: 13px;
+          }
+          
+          .stage-icon {
+            width: 36px;
+            height: 36px;
+          }
+        }
+      }
+    }
+  }
+}
+
+@media (max-width: 992px) {
+  .process-progress-bar {
+    .progress-stages-container {
+      .stage-wrapper {
+        .stage-node {
+          min-width: 80px;
+          padding: 10px 6px;
+          
+          .stage-title {
+            font-size: 12px;
+          }
+          
+          .stage-icon {
+            width: 32px;
+            height: 32px;
+          }
+          
+          .stage-status-indicator {
+            font-size: 10px;
+            padding: 3px 6px;
+          }
+        }
+        
+        .stage-connector {
+          margin: 0 4px;
+        }
+      }
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .process-progress-bar {
+    padding: 16px 0;
+    
+    .progress-stages-container {
+      flex-direction: column;
+      align-items: stretch;
+      
+      .stage-wrapper {
+        flex-direction: column;
+        margin-bottom: 16px;
+        
+        &:last-child {
+          margin-bottom: 0;
+        }
+        
+        .stage-node {
+          width: 100%;
+          min-width: auto;
+          flex-direction: row;
+          justify-content: flex-start;
+          padding: 16px;
+          text-align: left;
+          
+          .stage-icon {
+            margin-right: 12px;
+            margin-bottom: 0;
+          }
+          
+          .stage-content {
+            flex: 1;
+            
+            .stage-title {
+              text-align: left;
+              margin-bottom: 4px;
+            }
+          }
+          
+          .stage-status-indicator {
+            margin-left: auto;
+          }
+        }
+        
+        .stage-connector {
+          width: 2px;
+          height: 20px;
+          margin: 0 auto;
+          
+          .connector-line {
+            width: 2px;
+            height: 100%;
+          }
+        }
+      }
+    }
+  }
+}

+ 189 - 0
src/app/shared/components/process-progress-bar/process-progress-bar.ts

@@ -0,0 +1,189 @@
+import { Component, Input, Output, EventEmitter, signal, computed, OnInit, OnChanges, SimpleChanges } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+// 定义流程阶段接口
+export interface ProcessStage {
+  id: string;
+  title: string;
+  status: 'pending' | 'in-progress' | 'completed';
+  icon?: string;
+  description?: string;
+  clickable?: boolean;
+}
+
+@Component({
+  selector: 'app-process-progress-bar',
+  standalone: true,
+  imports: [CommonModule],
+  template: `
+    <div class="process-progress-bar">
+      <!-- 进度概览 -->
+      <div class="progress-overview">
+        <div class="progress-info">
+          <span class="progress-label">整体进度</span>
+          <span class="progress-percentage">{{ completionPercentage() }}%</span>
+        </div>
+        <div class="progress-bar-overview">
+          <div class="progress-fill" [style.width.%]="completionPercentage()"></div>
+        </div>
+      </div>
+      
+      <!-- 横向串联式流程状态显示条 -->
+      <div class="progress-stages-container">
+        @for (stage of stages; track stage.id; let i = $index) {
+          <div class="stage-wrapper">
+            <!-- 阶段节点 -->
+            <div class="stage-node" 
+                 [class.pending]="stage.status === 'pending'"
+                 [class.in-progress]="stage.status === 'in-progress'"
+                 [class.completed]="stage.status === 'completed'"
+                 [class.clickable]="clickable()"
+                 [attr.aria-label]="getAriaLabel(stage)"
+                 [attr.tabindex]="clickable() ? 0 : -1"
+                 (click)="onStageClick(stage)"
+                 (keydown)="onStageKeydown($event, stage)"
+                 (mouseenter)="onStageHover(stage, true)"
+                 (mouseleave)="onStageHover(stage, false)">
+              
+              <!-- 状态图标 -->
+              <div class="stage-icon">
+                @if (stage.status === 'completed') {
+                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <path d="m9 12 2 2 4-4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                  </svg>
+                } @else if (stage.status === 'in-progress') {
+                  <div class="progress-dot"></div>
+                } @else {
+                  <div class="pending-dot"></div>
+                }
+              </div>
+              
+              <!-- 阶段内容 -->
+              <div class="stage-content">
+                <!-- 阶段标题 -->
+                <div class="stage-title">{{ stage.title }}</div>
+                
+                <!-- 阶段描述 -->
+                @if (stage.description) {
+                  <div class="stage-description">{{ stage.description }}</div>
+                }
+                
+                <!-- 状态指示器 -->
+                <div class="stage-status-indicator" 
+                     [class.status-pending]="stage.status === 'pending'"
+                     [class.status-progress]="stage.status === 'in-progress'"
+                     [class.status-completed]="stage.status === 'completed'">
+                  {{ getStatusText(stage.status) }}
+                </div>
+              </div>
+            </div>
+            
+            <!-- 连接线 -->
+            @if (i < stages.length - 1) {
+              <div class="stage-connector" 
+                   [class.connector-completed]="isConnectorCompleted(i)">
+                <div class="connector-line"></div>
+                <div class="connector-arrow">
+                  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <polyline points="9 18 15 12 9 6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+                  </svg>
+                </div>
+              </div>
+            }
+          </div>
+        }
+      </div>
+    </div>
+  `,
+  styleUrls: ['./process-progress-bar.component.scss']
+})
+export class ProcessProgressBarComponent implements OnInit, OnChanges {
+  @Input() stages: ProcessStage[] = [];
+  @Input() clickable = signal<boolean>(true);
+  
+  @Output() stageClick = new EventEmitter<ProcessStage>();
+  @Output() stageHover = new EventEmitter<{stage: ProcessStage, isHover: boolean}>();
+  
+  ngOnInit(): void {
+    console.log('ProcessProgressBarComponent initialized');
+    console.log('Initial stages:', this.stages);
+    console.log('Initial stages length:', this.stages?.length || 0);
+    
+    if (!this.stages) {
+      console.warn('Stages input is null or undefined');
+      this.stages = [];
+    }
+  }
+  
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes['stages']) {
+      console.log('ProcessProgressBarComponent stages changed');
+      console.log('New stages:', changes['stages'].currentValue);
+      console.log('New stages length:', changes['stages'].currentValue?.length || 0);
+      
+      if (!changes['stages'].currentValue) {
+        console.warn('New stages value is null or undefined');
+        this.stages = [];
+      }
+    }
+  }
+  
+  completionPercentage(): number {
+    if (!this.stages || this.stages.length === 0) {
+      console.log('No stages available for completion calculation');
+      return 0;
+    }
+    
+    const completedCount = this.stages.filter(stage => {
+      if (!stage) {
+        console.warn('Found null stage in completion calculation');
+        return false;
+      }
+      return stage.status === 'completed';
+    }).length;
+    
+    const percentage = Math.round((completedCount / this.stages.length) * 100);
+    console.log(`Completion percentage: ${percentage}% (${completedCount}/${this.stages.length})`);
+    return percentage;
+  }
+  
+  onStageClick(stage: ProcessStage): void {
+    if (this.clickable()) {
+      this.stageClick.emit(stage);
+    }
+  }
+  
+  onStageKeydown(event: KeyboardEvent, stage: ProcessStage): void {
+    if (event.key === 'Enter' || event.key === ' ') {
+      event.preventDefault();
+      this.onStageClick(stage);
+    }
+  }
+  
+  onStageHover(stage: ProcessStage, isHover: boolean): void {
+    this.stageHover.emit({ stage, isHover });
+  }
+  
+  isConnectorCompleted(index: number): boolean {
+    const currentStage = this.stages[index];
+    const nextStage = this.stages[index + 1];
+    return currentStage.status === 'completed' && nextStage && nextStage.status !== 'pending';
+  }
+  
+  getAriaLabel(stage: ProcessStage): string {
+    return `${stage.title} - ${this.getStatusText(stage.status)}${stage.description ? ': ' + stage.description : ''}`;
+  }
+  
+  getStatusText(status: string): string {
+    switch (status) {
+      case 'completed':
+        return '已完成';
+      case 'in-progress':
+        return '进行中';
+      case 'pending':
+        return '未开始';
+      default:
+        return '未知';
+    }
+  }
+}

+ 84 - 0
src/app/shared/components/process-status-bar/process-status-bar.component.scss

@@ -0,0 +1,84 @@
+.process-status-bar {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  flex-wrap: nowrap;
+  
+  .status-item {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 6px 12px;
+    border-radius: 16px;
+    font-size: 12px;
+    font-weight: 500;
+    white-space: nowrap;
+    transition: all 0.3s ease;
+    min-width: 60px;
+    
+    .status-text {
+      color: white;
+      text-align: center;
+    }
+    
+    // 已完成状态 - 绿色背景
+    &.status-completed {
+      background-color: #10b981;
+      box-shadow: 0 2px 4px rgba(16, 185, 129, 0.2);
+      
+      &:hover {
+        background-color: #059669;
+        transform: translateY(-1px);
+        box-shadow: 0 4px 8px rgba(16, 185, 129, 0.3);
+      }
+    }
+    
+    // 进行中状态 - 红色背景
+    &.status-in-progress {
+      background-color: #ef4444;
+      box-shadow: 0 2px 4px rgba(239, 68, 68, 0.2);
+      
+      &:hover {
+        background-color: #dc2626;
+        transform: translateY(-1px);
+        box-shadow: 0 4px 8px rgba(239, 68, 68, 0.3);
+      }
+    }
+    
+    // 待完成状态 - 黄色背景
+    &.status-pending {
+      background-color: #f59e0b;
+      box-shadow: 0 2px 4px rgba(245, 158, 11, 0.2);
+      
+      &:hover {
+        background-color: #d97706;
+        transform: translateY(-1px);
+        box-shadow: 0 4px 8px rgba(245, 158, 11, 0.3);
+      }
+    }
+  }
+  
+  .status-separator {
+    width: 8px;
+    height: 1px;
+    background-color: #e5e7eb;
+    flex-shrink: 0;
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .process-status-bar {
+    gap: 4px;
+    
+    .status-item {
+      padding: 4px 8px;
+      font-size: 11px;
+      min-width: 50px;
+    }
+    
+    .status-separator {
+      width: 4px;
+    }
+  }
+}

+ 44 - 0
src/app/shared/components/process-status-bar/process-status-bar.ts

@@ -0,0 +1,44 @@
+import { Component, Input, signal, computed } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+// 定义流程阶段状态接口
+export interface ProcessStatusStage {
+  id: string;
+  name: string;
+  status: 'pending' | 'in-progress' | 'completed';
+}
+
+@Component({
+  selector: 'app-process-status-bar',
+  standalone: true,
+  imports: [CommonModule],
+  template: `
+    <div class="process-status-bar">
+      @for (stage of stages(); track stage.id; let i = $index) {
+        <div class="status-item" 
+             [class.status-completed]="stage.status === 'completed'"
+             [class.status-in-progress]="stage.status === 'in-progress'"
+             [class.status-pending]="stage.status === 'pending'">
+          <span class="status-text">{{ stage.name }}</span>
+        </div>
+        @if (i < stages().length - 1) {
+          <div class="status-separator"></div>
+        }
+      }
+    </div>
+  `,
+  styleUrls: ['./process-status-bar.component.scss']
+})
+export class ProcessStatusBarComponent {
+  @Input() set stageData(value: ProcessStatusStage[]) {
+    console.log('ProcessStatusBarComponent 接收到数据:', value);
+    this.stages.set(value || []);
+  }
+
+  // 内部状态管理
+  stages = signal<ProcessStatusStage[]>([]);
+
+  constructor() {
+    console.log('ProcessStatusBarComponent 构造函数被调用');
+  }
+}

+ 12 - 0
src/app/shared/components/progress-bar/progress-bar.component.html

@@ -0,0 +1,12 @@
+<div class="progress-bar">
+  <div class="progress-sections">
+    @for (section of sections; track section.key; let i = $index) {
+      <div class="section" [class]="getSectionStatus(section.key)">
+        <div class="label">{{section.label}}</div>
+        @if (i < sections.length - 1) {
+          <div class="connector"></div>
+        }
+      </div>
+    }
+  </div>
+</div>

+ 23 - 0
src/app/shared/components/progress-bar/progress-bar.component.ts

@@ -0,0 +1,23 @@
+import { Component, Input } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+export interface ProgressSection {
+  key: string;
+  label: string;
+}
+
+@Component({
+  selector: 'app-progress-bar',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './progress-bar.component.html',
+  styles: [`
+    :host {
+      display: block;
+    }
+  `]
+})
+export class ProgressBarComponent {
+  @Input() sections: ProgressSection[] = [];
+  @Input() getSectionStatus!: (key: string) => 'completed' | 'active' | 'pending';
+}

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

@@ -1,11 +1,43 @@
 <div class="info-card requirements-confirm-card">
   <div class="card-header">
     <h4>确认需求</h4>
-    <div class="progress-indicator">
-      <div class="progress-bar">
-        <div class="progress-fill" [style.width.%]="getProgressPercentage()"></div>
+    <div class="header-actions">
+      <button class="btn-ghost btn-sm" (click)="refreshProgress()">刷新进度</button>
+      
+      <!-- 紧凑型流程进度卡片 -->
+      <div class="compact-stage-indicators">
+        <div class="stage-chain">
+          <div class="stage-dot" [class]="getStageStatusClass('materialAnalysis')" 
+               title="素材分析 - {{ getStageStatusText('materialAnalysis') }}">
+            <span class="stage-number">1</span>
+          </div>
+          <div class="stage-connector" [class]="stageCompletionStatus.materialAnalysis ? 'completed' : 'pending'"></div>
+          
+          <div class="stage-dot" [class]="getStageStatusClass('requirementMapping')" 
+               title="需求映射 - {{ getStageStatusText('requirementMapping') }}">
+            <span class="stage-number">2</span>
+          </div>
+          <div class="stage-connector" [class]="stageCompletionStatus.requirementMapping ? 'completed' : 'pending'"></div>
+          
+          <div class="stage-dot" [class]="getStageStatusClass('collaboration')" 
+               title="协作验证 - {{ getStageStatusText('collaboration') }}">
+            <span class="stage-number">3</span>
+          </div>
+          <div class="stage-connector" [class]="stageCompletionStatus.collaboration ? 'completed' : 'pending'"></div>
+          
+          <div class="stage-dot" [class]="getStageStatusClass('progressReview')" 
+               title="进度审查 - {{ getStageStatusText('progressReview') }}">
+            <span class="stage-number">4</span>
+          </div>
+        </div>
+      </div>
+      
+      <div class="progress-indicator">
+        <div class="progress-bar">
+          <div class="progress-fill" [style.width.%]="getProgressPercentage()"></div>
+        </div>
+        <span class="progress-text">{{ getProgressPercentage() | number:'1.0-0' }}% 完成</span>
       </div>
-      <span class="progress-text">{{ getProgressPercentage() | number:'1.0-0' }}% 完成</span>
     </div>
   </div>
 
@@ -183,9 +215,16 @@
                     type="range" 
                     [min]="indicatorRanges.color.r.min" 
                     [max]="indicatorRanges.color.r.max" 
-                    [value]="colorIndicators.mainColor.r"
-                    (input)="colorIndicators.mainColor.r = +$event.target.value; updateColorIndicator('mainColor', colorIndicators.mainColor)">
-                  <span>{{ colorIndicators.mainColor.r }}</span>
+                    [(ngModel)]="colorIndicators.mainColor.r"
+                    (ngModelChange)="onSliderChange('r', $event)">
+                  <input 
+                    type="number" 
+                    class="slider-input"
+                    [min]="indicatorRanges.color.r.min" 
+                    [max]="indicatorRanges.color.r.max" 
+                    [(ngModel)]="colorIndicators.mainColor.r"
+                    (ngModelChange)="onInputChange('r', $event)"
+                    (blur)="validateInput('r', $event.target.value)">
                 </div>
                 <div class="rgb-slider">
                   <span>G</span>
@@ -193,9 +232,16 @@
                     type="range" 
                     [min]="indicatorRanges.color.g.min" 
                     [max]="indicatorRanges.color.g.max" 
-                    [value]="colorIndicators.mainColor.g"
-                    (input)="colorIndicators.mainColor.g = +$event.target.value; updateColorIndicator('mainColor', colorIndicators.mainColor)">
-                  <span>{{ colorIndicators.mainColor.g }}</span>
+                    [(ngModel)]="colorIndicators.mainColor.g"
+                    (ngModelChange)="onSliderChange('g', $event)">
+                  <input 
+                    type="number" 
+                    class="slider-input"
+                    [min]="indicatorRanges.color.g.min" 
+                    [max]="indicatorRanges.color.g.max" 
+                    [(ngModel)]="colorIndicators.mainColor.g"
+                    (ngModelChange)="onInputChange('g', $event)"
+                    (blur)="validateInput('g', $event.target.value)">
                 </div>
                 <div class="rgb-slider">
                   <span>B</span>
@@ -203,11 +249,18 @@
                     type="range" 
                     [min]="indicatorRanges.color.b.min" 
                     [max]="indicatorRanges.color.b.max" 
-                    [value]="colorIndicators.mainColor.b"
-                    (input)="colorIndicators.mainColor.b = +$event.target.value; updateColorIndicator('mainColor', colorIndicators.mainColor)">
-                  <span>{{ colorIndicators.mainColor.b }}</span>
+                    [(ngModel)]="colorIndicators.mainColor.b"
+                    (ngModelChange)="onSliderChange('b', $event)">
+                  <input 
+                    type="number" 
+                    class="slider-input"
+                    [min]="indicatorRanges.color.b.min" 
+                    [max]="indicatorRanges.color.b.max" 
+                    [(ngModel)]="colorIndicators.mainColor.b"
+                    (ngModelChange)="onInputChange('b', $event)"
+                    (blur)="validateInput('b', $event.target.value)">
                 </div>
-                <div class="color-preview" [style.background]="getRgbString()"></div>
+                <div class="color-preview" [style.background-color]="getRgbString()"></div>
               </div>
             </div>
             
@@ -220,8 +273,16 @@
                   [min]="indicatorRanges.color.temperature.min" 
                   [max]="indicatorRanges.color.temperature.max" 
                   [value]="colorIndicators.colorTemperature"
-                  (input)="colorIndicators.colorTemperature = +$event.target.value; updateColorIndicator('colorTemperature')">
-                <span class="slider-value">{{ colorIndicators.colorTemperature }}K</span>
+                  (input)="onSliderChange('colorTemperature', +$event.target.value)">
+                <input 
+                  type="number" 
+                  class="slider-input"
+                  [min]="indicatorRanges.color.temperature.min" 
+                  [max]="indicatorRanges.color.temperature.max" 
+                  [value]="colorIndicators.colorTemperature"
+                  (input)="onInputChange('colorTemperature', +$event.target.value)"
+                  (blur)="validateInput('colorTemperature', $event.target.value)">
+                <span class="unit-label">K</span>
               </div>
             </div>
 
@@ -234,8 +295,16 @@
                   [min]="indicatorRanges.color.saturation.min" 
                   [max]="indicatorRanges.color.saturation.max" 
                   [value]="colorIndicators.saturation"
-                  (input)="colorIndicators.saturation = +$event.target.value; updateColorIndicator('saturation')">
-                <span class="slider-value">{{ colorIndicators.saturation }}%</span>
+                  (input)="onSliderChange('saturation', +$event.target.value)">
+                <input 
+                  type="number" 
+                  class="slider-input"
+                  [min]="indicatorRanges.color.saturation.min" 
+                  [max]="indicatorRanges.color.saturation.max" 
+                  [value]="colorIndicators.saturation"
+                  (input)="onInputChange('saturation', +$event.target.value)"
+                  (blur)="validateInput('saturation', $event.target.value)">
+                <span class="unit-label">%</span>
               </div>
             </div>
           </div>
@@ -251,8 +320,16 @@
                   [min]="indicatorRanges.space.blankRatio.min" 
                   [max]="indicatorRanges.space.blankRatio.max" 
                   [value]="spaceIndicators.blankRatio"
-                  (input)="updateSpaceIndicator('blankRatio', +$event.target.value)">
-                <span class="slider-value">{{ spaceIndicators.blankRatio }}%</span>
+                  (input)="onSliderChange('blankRatio', +$event.target.value)">
+                <input 
+                  type="number" 
+                  class="slider-input"
+                  [min]="indicatorRanges.space.blankRatio.min" 
+                  [max]="indicatorRanges.space.blankRatio.max" 
+                  [value]="spaceIndicators.blankRatio"
+                  (input)="onInputChange('blankRatio', +$event.target.value)"
+                  (blur)="validateInput('blankRatio', $event.target.value)">
+                <span class="unit-label">%</span>
               </div>
             </div>
             
@@ -264,8 +341,16 @@
                   [min]="indicatorRanges.space.lineRatio.min" 
                   [max]="indicatorRanges.space.lineRatio.max" 
                   [value]="spaceIndicators.lineRatio"
-                  (input)="updateSpaceIndicator('lineRatio', +$event.target.value)">
-                <span class="slider-value">{{ spaceIndicators.lineRatio }}%</span>
+                  (input)="onSliderChange('lineRatio', +$event.target.value)">
+                <input 
+                  type="number" 
+                  class="slider-input"
+                  [min]="indicatorRanges.space.lineRatio.min" 
+                  [max]="indicatorRanges.space.lineRatio.max" 
+                  [value]="spaceIndicators.lineRatio"
+                  (input)="onInputChange('lineRatio', +$event.target.value)"
+                  (blur)="validateInput('lineRatio', $event.target.value)">
+                <span class="unit-label">%</span>
               </div>
             </div>
 
@@ -278,8 +363,17 @@
                   [max]="indicatorRanges.space.flowWidth.max" 
                   step="0.1"
                   [value]="spaceIndicators.flowWidth"
-                  (input)="updateSpaceIndicator('flowWidth', +$event.target.value)">
-                <span class="slider-value">{{ spaceIndicators.flowWidth }}m</span>
+                  (input)="onSliderChange('flowWidth', +$event.target.value)">
+                <input 
+                  type="number" 
+                  class="slider-input"
+                  [min]="indicatorRanges.space.flowWidth.min" 
+                  [max]="indicatorRanges.space.flowWidth.max" 
+                  step="0.1"
+                  [value]="spaceIndicators.flowWidth"
+                  (input)="onInputChange('flowWidth', +$event.target.value)"
+                  (blur)="validateInput('flowWidth', $event.target.value)">
+                <span class="unit-label">m</span>
               </div>
             </div>
           </div>
@@ -295,8 +389,16 @@
                   [min]="indicatorRanges.material.fabricRatio.min" 
                   [max]="indicatorRanges.material.fabricRatio.max" 
                   [value]="materialIndicators.fabricRatio"
-                  (input)="updateMaterialIndicator('fabricRatio', +$event.target.value)">
-                <span class="slider-value">{{ materialIndicators.fabricRatio }}%</span>
+                  (input)="onSliderChange('fabricRatio', +$event.target.value)">
+                <input 
+                  type="number" 
+                  class="slider-input"
+                  [min]="indicatorRanges.material.fabricRatio.min" 
+                  [max]="indicatorRanges.material.fabricRatio.max" 
+                  [value]="materialIndicators.fabricRatio"
+                  (input)="onInputChange('fabricRatio', +$event.target.value)"
+                  (blur)="validateInput('fabricRatio', $event.target.value)">
+                <span class="unit-label">%</span>
               </div>
             </div>
 
@@ -308,8 +410,16 @@
                   [min]="indicatorRanges.material.woodRatio.min" 
                   [max]="indicatorRanges.material.woodRatio.max" 
                   [value]="materialIndicators.woodRatio"
-                  (input)="updateMaterialIndicator('woodRatio', +$event.target.value)">
-                <span class="slider-value">{{ materialIndicators.woodRatio }}%</span>
+                  (input)="onSliderChange('woodRatio', +$event.target.value)">
+                <input 
+                  type="number" 
+                  class="slider-input"
+                  [min]="indicatorRanges.material.woodRatio.min" 
+                  [max]="indicatorRanges.material.woodRatio.max" 
+                  [value]="materialIndicators.woodRatio"
+                  (input)="onInputChange('woodRatio', +$event.target.value)"
+                  (blur)="validateInput('woodRatio', $event.target.value)">
+                <span class="unit-label">%</span>
               </div>
             </div>
 
@@ -321,8 +431,16 @@
                   [min]="indicatorRanges.material.metalRatio.min" 
                   [max]="indicatorRanges.material.metalRatio.max" 
                   [value]="materialIndicators.metalRatio"
-                  (input)="updateMaterialIndicator('metalRatio', +$event.target.value)">
-                <span class="slider-value">{{ materialIndicators.metalRatio }}%</span>
+                  (input)="onSliderChange('metalRatio', +$event.target.value)">
+                <input 
+                  type="number" 
+                  class="slider-input"
+                  [min]="indicatorRanges.material.metalRatio.min" 
+                  [max]="indicatorRanges.material.metalRatio.max" 
+                  [value]="materialIndicators.metalRatio"
+                  (input)="onInputChange('metalRatio', +$event.target.value)"
+                  (blur)="validateInput('metalRatio', $event.target.value)">
+                <span class="unit-label">%</span>
               </div>
             </div>
 
@@ -334,8 +452,16 @@
                   [min]="indicatorRanges.material.smoothness.min" 
                   [max]="indicatorRanges.material.smoothness.max" 
                   [value]="materialIndicators.smoothness"
-                  (input)="updateMaterialIndicator('smoothness', +$event.target.value)">
-                <span class="slider-value">{{ materialIndicators.smoothness }}/10</span>
+                  (input)="onSliderChange('smoothness', +$event.target.value)">
+                <input 
+                  type="number" 
+                  class="slider-input"
+                  [min]="indicatorRanges.material.smoothness.min" 
+                  [max]="indicatorRanges.material.smoothness.max" 
+                  [value]="materialIndicators.smoothness"
+                  (input)="onInputChange('smoothness', +$event.target.value)"
+                  (blur)="validateInput('smoothness', $event.target.value)">
+                <span class="unit-label">/10</span>
               </div>
             </div>
           </div>
@@ -467,42 +593,28 @@
     <!-- 进度管理标签页 -->
     @if (activeTab === 'progress') {
       <div class="progress-section">
-        <!-- 进度概览 -->
-        <div class="progress-overview">
-          <div class="progress-stats">
-            <div class="stat-item">
-                <div class="stat-number">{{ getRequirementCountByStatus('confirmed') }}</div>
-                <div class="stat-label">已确认</div>
-              </div>
-              <div class="stat-item">
-                <div class="stat-number">{{ getRequirementCountByStatus('pending') }}</div>
-                <div class="stat-label">待确认</div>
-              </div>
-              <div class="stat-item">
-                <div class="stat-number">{{ getRequirementCountByStatus('rejected') }}</div>
-                <div class="stat-label">已拒绝</div>
-              </div>
-            <div class="stat-item">
-              <div class="stat-number">{{ getUnresolvedCommentsCount() }}</div>
-              <div class="stat-label">待解决评论</div>
-            </div>
+        <!-- 整体进度 -->
+        <div class="overall-progress">
+          <div class="section-header">
+            <h5>整体进度</h5>
           </div>
           
-          <div class="progress-chart">
-            <div class="chart-container">
-              <div class="progress-circle">
-                <svg width="120" height="120" viewBox="0 0 120 120">
-                  <circle cx="60" cy="60" r="50" fill="none" stroke="#e5e5ea" stroke-width="8"></circle>
-                  <circle 
-                    cx="60" 
-                    cy="60" 
-                    r="50" 
-                    fill="none" 
-                    stroke="#34C759" 
-                    stroke-width="8"
+          <div class="progress-container">
+            <div class="progress-visual">
+              <div class="linear-progress">
+                <div class="progress-track">
+                  <div class="progress-bar-fill" [style.width.%]="getProgressPercentage()"></div>
+                </div>
+                <div class="progress-text">{{ getProgressPercentage() }}%</div>
+              </div>
+              
+              <div class="circular-progress">
+                <svg viewBox="0 0 120 120" class="progress-circle">
+                  <circle cx="60" cy="60" r="54" fill="none" stroke="#e5e7eb" stroke-width="8"></circle>
+                  <circle cx="60" cy="60" r="54" fill="none" stroke="#3b82f6" stroke-width="8"
                     stroke-linecap="round"
-                    [style.stroke-dasharray]="314"
-                    [style.stroke-dashoffset]="314 - (314 * getProgressPercentage() / 100)"
+                    [attr.stroke-dasharray]="339.292"
+                    [attr.stroke-dashoffset]="339.292 - (339.292 * getProgressPercentage() / 100)"
                     transform="rotate(-90 60 60)">
                   </circle>
                 </svg>
@@ -515,6 +627,26 @@
           </div>
         </div>
 
+        <!-- 进度统计 -->
+        <div class="progress-stats">
+          <div class="stat-item">
+              <div class="stat-number">{{ getRequirementCountByStatus('confirmed') }}</div>
+              <div class="stat-label">已确认</div>
+            </div>
+            <div class="stat-item">
+              <div class="stat-number">{{ getRequirementCountByStatus('pending') }}</div>
+              <div class="stat-label">待确认</div>
+            </div>
+            <div class="stat-item">
+              <div class="stat-number">{{ getRequirementCountByStatus('rejected') }}</div>
+              <div class="stat-label">已拒绝</div>
+            </div>
+          <div class="stat-item">
+            <div class="stat-number">{{ getUnresolvedCommentsCount() }}</div>
+            <div class="stat-label">待解决评论</div>
+          </div>
+        </div>
+
         <!-- 历史状态 -->
         <div class="history-section">
           <div class="section-header">
@@ -597,4 +729,35 @@
       </div>
     }
   </div>
+</div>
+
+<!-- 保存状态和手动保存按钮 -->
+<div class="save-section">
+  <div class="save-status">
+    <span class="save-icon" [class]="'save-icon-' + saveStatus">{{ getSaveStatusIcon() }}</span>
+    <span class="save-text">{{ getSaveStatusText() }}</span>
+  </div>
+  
+  <div class="save-actions">
+    <button class="btn-secondary" 
+            [disabled]="isSaving || !hasUnsavedChanges"
+            (click)="manualSave()">
+      @if (isSaving) {
+        <span class="loading-spinner"></span>
+        保存中...
+      } @else {
+        手动保存
+      }
+    </button>
+    
+    <div class="auto-save-toggle">
+      <label class="toggle-label">
+        <input type="checkbox" 
+               [(ngModel)]="autoSaveEnabled"
+               class="toggle-input">
+        <span class="toggle-slider"></span>
+        <span class="toggle-text">自动保存</span>
+      </label>
+    </div>
+  </div>
 </div>

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

@@ -19,6 +19,16 @@
       color: $ios-text-primary;
     }
     
+    .header-actions {
+      display: flex;
+      align-items: center;
+      gap: $ios-spacing-md;
+      
+      .btn-ghost {
+        white-space: nowrap;
+      }
+    }
+    
     .progress-indicator {
       display: flex;
       align-items: center;
@@ -46,6 +56,147 @@
     }
   }
 
+  // 滑动条输入框样式
+  .slider-container {
+    display: flex;
+    align-items: center;
+    gap: $ios-spacing-xs;
+    
+    input[type="range"] {
+      flex: 1;
+    }
+    
+    .slider-input {
+      width: 60px;
+      padding: 4px 8px;
+      border: 1px solid $ios-border;
+      border-radius: 4px;
+      font-size: $ios-font-size-xs;
+      text-align: center;
+      background: white;
+      
+      &:focus {
+        outline: none;
+        border-color: $ios-primary;
+        box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2);
+      }
+    }
+    
+    .unit-label {
+      font-size: $ios-font-size-xs;
+      color: $ios-text-secondary;
+      min-width: 20px;
+    }
+  }
+
+  // 进度条样式
+  .progress-container {
+    margin-bottom: $ios-spacing-md;
+    
+    &.progress-updated {
+      .progress-bar-fill {
+        animation: progressPulse 0.5s ease;
+      }
+    }
+    
+    .progress-visual {
+      display: flex;
+      align-items: center;
+      gap: $ios-spacing-md;
+      
+      .linear-progress {
+        flex: 1;
+        display: flex;
+        align-items: center;
+        gap: $ios-spacing-sm;
+        
+        .progress-track {
+          flex: 1;
+          height: 8px;
+          background: $ios-background-secondary;
+          border-radius: 4px;
+          overflow: hidden;
+          
+          .progress-bar-fill {
+            height: 100%;
+            background: linear-gradient(90deg, #007AFF 0%, #34C759 100%);
+            border-radius: 4px;
+            transition: width 0.3s ease;
+          }
+        }
+        
+        .progress-text {
+          font-size: $ios-font-size-sm;
+          font-weight: $ios-font-weight-semibold;
+          color: $ios-text-primary;
+          min-width: 40px;
+        }
+      }
+      
+      .circular-progress {
+        position: relative;
+        width: 120px;
+        height: 120px;
+        
+        .progress-circle {
+          width: 100%;
+          height: 100%;
+          transform: rotate(-90deg);
+          
+          circle {
+            transition: stroke-dashoffset 0.3s ease;
+          }
+        }
+        
+        .progress-text {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
+          text-align: center;
+          
+          .progress-percentage {
+            font-size: $ios-font-size-lg;
+            font-weight: $ios-font-weight-bold;
+            color: $ios-text-primary;
+          }
+          
+          .progress-label {
+            font-size: $ios-font-size-xs;
+            color: $ios-text-secondary;
+          }
+        }
+      }
+    }
+  }
+
+  // 进度统计样式
+  .progress-stats {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
+    gap: $ios-spacing-sm;
+    margin-bottom: $ios-spacing-md;
+    
+    .stat-item {
+      text-align: center;
+      padding: $ios-spacing-sm;
+      background: $ios-background-secondary;
+      border-radius: 8px;
+      
+      .stat-number {
+        font-size: $ios-font-size-lg;
+        font-weight: $ios-font-weight-bold;
+        color: $ios-text-primary;
+      }
+      
+      .stat-label {
+        font-size: $ios-font-size-xs;
+        color: $ios-text-secondary;
+        margin-top: 2px;
+      }
+    }
+  }
+
   // 标签页导航
   .tab-navigation {
     display: flex;
@@ -388,6 +539,9 @@
               border-radius: 6px;
               border: 1px solid $ios-border;
               margin-top: $ios-spacing-xs;
+              transition: background-color 0.1s ease; // 减少过渡时间,提高响应速度
+              // 移除默认背景色,完全依赖Angular绑定
+              will-change: background-color; // 优化GPU渲染
             }
           }
         }
@@ -1060,4 +1214,302 @@
       color: $ios-text-primary;
     }
   }
+}
+
+// 进度动画
+@keyframes progressPulse {
+  0% { opacity: 0.6; }
+  50% { opacity: 1; }
+  100% { opacity: 0.6; }
+}
+
+// 全局通知样式
+.execution-notification {
+  position: fixed;
+  top: 20px;
+  right: 20px;
+  background: #4CAF50;
+  color: white;
+  padding: 16px 24px;
+  border-radius: 8px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  z-index: 1000;
+  animation: slideInRight 0.3s ease-out;
+  
+  &.error {
+    background: #f44336;
+  }
+  
+  &.warning {
+    background: #ff9800;
+  }
+}
+
+@keyframes slideInRight {
+  from {
+    transform: translateX(100%);
+    opacity: 0;
+  }
+  to {
+    transform: translateX(0);
+    opacity: 1;
+  }
+}
+
+// 紧凑型流程进度指示器
+.compact-stage-indicators {
+  display: flex;
+  align-items: center;
+  margin: 0 16px;
+  
+  .stage-chain {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+  
+  .stage-dot {
+    width: 28px;
+    height: 28px;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 12px;
+    font-weight: 600;
+    color: white;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    position: relative;
+    
+    .stage-number {
+      font-size: 11px;
+      font-weight: bold;
+    }
+    
+    // 已完成状态 - 绿色
+    &.stage-completed {
+      background: linear-gradient(135deg, #10b981, #059669);
+      box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
+      
+      &:hover {
+        transform: scale(1.1);
+        box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
+      }
+    }
+    
+    // 进行中状态 - 红色
+    &.stage-in-progress {
+      background: linear-gradient(135deg, #ef4444, #dc2626);
+      box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
+      animation: pulse-red 2s infinite;
+      
+      &:hover {
+        transform: scale(1.1);
+        box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
+      }
+    }
+    
+    // 未进行状态 - 黄色
+    &.stage-pending {
+      background: linear-gradient(135deg, #f59e0b, #d97706);
+      box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);
+      
+      &:hover {
+        transform: scale(1.1);
+        box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4);
+      }
+    }
+  }
+  
+  .stage-connector {
+    width: 20px;
+    height: 3px;
+    border-radius: 2px;
+    transition: all 0.3s ease;
+    
+    &.completed {
+      background: linear-gradient(90deg, #10b981, #059669);
+    }
+    
+    &.pending {
+      background: #e5e7eb;
+    }
+  }
+}
+
+// 进行中状态的脉冲动画
+@keyframes pulse-red {
+  0%, 100% {
+    box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
+  }
+  50% {
+    box-shadow: 0 2px 12px rgba(239, 68, 68, 0.6);
+  }
+}
+
+// 更新header-actions布局
+.header-actions {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  
+  .btn-ghost {
+    flex-shrink: 0;
+  }
+  
+  .compact-stage-indicators {
+    flex-shrink: 0;
+  }
+  
+  .progress-indicator {
+    flex-shrink: 0;
+  }
+}
+
+// 保存状态区域
+.save-section {
+  margin-top: 24px;
+  padding: 16px;
+  background: #f8fafc;
+  border-radius: 8px;
+  border: 1px solid #e2e8f0;
+  
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 16px;
+  
+  .save-status {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    
+    .save-icon {
+      font-size: 16px;
+      font-weight: bold;
+      
+      &.save-icon-saved {
+        color: #10b981;
+      }
+      
+      &.save-icon-saving {
+        color: #3b82f6;
+        animation: spin 1s linear infinite;
+      }
+      
+      &.save-icon-error {
+        color: #ef4444;
+      }
+      
+      &.save-icon-unsaved {
+        color: #f59e0b;
+      }
+    }
+    
+    .save-text {
+      font-size: 14px;
+      color: #64748b;
+    }
+  }
+  
+  .save-actions {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    
+    .btn-secondary {
+      padding: 8px 16px;
+      background: #3b82f6;
+      color: white;
+      border: none;
+      border-radius: 6px;
+      font-size: 14px;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      
+      &:hover:not(:disabled) {
+        background: #2563eb;
+        transform: translateY(-1px);
+      }
+      
+      &:disabled {
+        background: #94a3b8;
+        cursor: not-allowed;
+        transform: none;
+      }
+      
+      .loading-spinner {
+        display: inline-block;
+        width: 12px;
+        height: 12px;
+        border: 2px solid transparent;
+        border-top: 2px solid currentColor;
+        border-radius: 50%;
+        animation: spin 1s linear infinite;
+        margin-right: 8px;
+      }
+    }
+    
+    .auto-save-toggle {
+      .toggle-label {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        cursor: pointer;
+        
+        .toggle-input {
+          display: none;
+        }
+        
+        .toggle-slider {
+          width: 40px;
+          height: 20px;
+          background: #cbd5e1;
+          border-radius: 10px;
+          position: relative;
+          transition: all 0.3s ease;
+          
+          &::after {
+            content: '';
+            position: absolute;
+            top: 2px;
+            left: 2px;
+            width: 16px;
+            height: 16px;
+            background: white;
+            border-radius: 50%;
+            transition: all 0.3s ease;
+          }
+        }
+        
+        .toggle-input:checked + .toggle-slider {
+          background: #10b981;
+          
+          &::after {
+            transform: translateX(20px);
+          }
+        }
+        
+        .toggle-text {
+          font-size: 14px;
+          color: #64748b;
+        }
+      }
+    }
+  }
+}
+
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@keyframes pulse {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.5; }
 }

+ 610 - 12
src/app/shared/components/requirements-confirm-card/requirements-confirm-card.ts

@@ -1,4 +1,4 @@
-import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
+import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
 
@@ -75,9 +75,10 @@ interface RequirementItem {
   standalone: true,
   imports: [CommonModule, FormsModule, ReactiveFormsModule],
   templateUrl: './requirements-confirm-card.html',
-  styleUrls: ['./requirements-confirm-card.scss']
+  styleUrls: ['./requirements-confirm-card.scss'],
+  changeDetection: ChangeDetectionStrategy.Default // 确保使用默认变更检测策略
 })
-export class RequirementsConfirmCardComponent implements OnInit {
+export class RequirementsConfirmCardComponent implements OnInit, OnDestroy {
   @Input() projectId?: string;
   @Output() requirementConfirmed = new EventEmitter<RequirementItem>();
   @Output() progressUpdated = new EventEmitter<number>();
@@ -102,6 +103,24 @@ export class RequirementsConfirmCardComponent implements OnInit {
   showConsistencyWarning = false;
   warningMessage = '';
 
+  // 自动保存相关状态
+  autoSaveEnabled = true;
+  lastSaveTime?: Date;
+  hasUnsavedChanges = false;
+  isSaving = false;
+  saveStatus: 'saved' | 'saving' | 'error' | 'unsaved' = 'saved';
+
+  // 流程状态
+  stageCompletionStatus = {
+    materialAnalysis: false,
+    requirementMapping: false,
+    collaboration: false,
+    progressReview: false
+  };
+
+  // 自动保存定时器
+  private autoSaveTimer?: any;
+
   // 需求指标
   colorIndicators = {
     mainColor: { r: 255, g: 230, b: 180 },
@@ -170,12 +189,27 @@ export class RequirementsConfirmCardComponent implements OnInit {
   consistencyWarnings: string[] = [];
   historyStates: any[] = [];
 
-  constructor(private fb: FormBuilder) {}
+  constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) {}
 
   ngOnInit() {
     this.initializeForms();
     this.initializeRequirements();
     this.loadPresetMetrics();
+    
+    // 启用自动保存
+    this.autoSaveEnabled = true;
+    
+    // 定期检查阶段完成状态
+    setInterval(() => {
+      this.checkStageCompletion();
+    }, 5000);
+  }
+
+  ngOnDestroy(): void {
+    // 清理自动保存定时器
+    if (this.autoSaveTimer) {
+      clearTimeout(this.autoSaveTimer);
+    }
   }
 
   private initializeForms() {
@@ -436,16 +470,32 @@ export class RequirementsConfirmCardComponent implements OnInit {
 
   // 需求确认功能
   confirmRequirement(requirementId: string) {
+    // 检查是否可以进行需求确认操作
+    if (!this.canProceedToNextStage('materialAnalysis')) {
+      alert('请先完成素材分析阶段的所有必要操作');
+      return;
+    }
+    
     const requirement = this.requirementItems.find(r => r.id === requirementId);
     if (requirement) {
       requirement.status = 'confirmed';
       requirement.lastUpdated = new Date();
       this.requirementConfirmed.emit(requirement);
       this.updateProgress();
+      this.triggerAutoSave();
+      
+      // 检查阶段完成状态
+      this.checkStageCompletion();
     }
   }
 
   rejectRequirement(requirementId: string, reason?: string) {
+    // 检查是否可以进行需求拒绝操作
+    if (!this.canProceedToNextStage('materialAnalysis')) {
+      alert('请先完成素材分析阶段的所有必要操作');
+      return;
+    }
+    
     const requirement = this.requirementItems.find(r => r.id === requirementId);
     if (requirement) {
       requirement.status = 'rejected';
@@ -456,6 +506,10 @@ export class RequirementsConfirmCardComponent implements OnInit {
       }
       
       this.updateProgress();
+      this.triggerAutoSave();
+      
+      // 检查阶段完成状态
+      this.checkStageCompletion();
     }
   }
 
@@ -474,6 +528,10 @@ export class RequirementsConfirmCardComponent implements OnInit {
       
       this.collaborationComments.push(comment);
       this.commentForm.reset();
+      this.triggerAutoSave();
+      
+      // 检查阶段完成状态
+      this.checkStageCompletion();
     }
   }
 
@@ -481,6 +539,10 @@ export class RequirementsConfirmCardComponent implements OnInit {
     const comment = this.collaborationComments.find(c => c.id === commentId);
     if (comment) {
       comment.status = 'resolved';
+      this.triggerAutoSave();
+      
+      // 检查阶段完成状态
+      this.checkStageCompletion();
     }
   }
 
@@ -489,7 +551,149 @@ export class RequirementsConfirmCardComponent implements OnInit {
     const totalRequirements = this.requirementItems.length;
     const confirmedRequirements = this.requirementItems.filter(r => r.status === 'confirmed').length;
     const progress = totalRequirements > 0 ? (confirmedRequirements / totalRequirements) * 100 : 0;
+    
+    // 实时更新进度条
+    this.updateProgressBar(progress);
     this.progressUpdated.emit(progress);
+    
+    // 检查是否完成所有需求
+    if (progress === 100) {
+      this.onRequirementCommunicationComplete();
+    }
+  }
+
+  private updateProgressBar(progress: number): void {
+    // 更新进度条显示
+    const progressBar = document.querySelector('.progress-bar-fill') as HTMLElement;
+    if (progressBar) {
+      progressBar.style.width = `${progress}%`;
+    }
+    
+    // 更新进度文本
+    const progressText = document.querySelector('.progress-text') as HTMLElement;
+    if (progressText) {
+      progressText.textContent = `${Math.round(progress)}%`;
+    }
+    
+    // 添加进度动画效果
+    this.animateProgressUpdate(progress);
+  }
+
+  private animateProgressUpdate(progress: number): void {
+    // 添加进度更新的视觉反馈
+    const progressContainer = document.querySelector('.progress-container') as HTMLElement;
+    if (progressContainer) {
+      progressContainer.classList.add('progress-updated');
+      setTimeout(() => {
+        progressContainer.classList.remove('progress-updated');
+      }, 500);
+    }
+  }
+
+  // 需求沟通阶段完成处理
+  private onRequirementCommunicationComplete(): void {
+    console.log('需求沟通阶段完成,开始同步关键信息');
+    this.syncKeyInfoToSolutionConfirmation();
+  }
+
+  // 同步关键信息到方案确认阶段
+  private syncKeyInfoToSolutionConfirmation(): void {
+    const keyInfo = {
+      colorAtmosphere: {
+        mainColor: this.colorIndicators.mainColor,
+        colorTemperature: this.colorIndicators.colorTemperature,
+        saturation: this.colorIndicators.saturation,
+        brightness: this.colorIndicators.brightness
+      },
+      spaceStructure: {
+        lineRatio: this.spaceIndicators.lineRatio,
+        blankRatio: this.spaceIndicators.blankRatio,
+        flowWidth: this.spaceIndicators.flowWidth
+      },
+      materialWeights: {
+        fabricRatio: this.materialIndicators.fabricRatio,
+        woodRatio: this.materialIndicators.woodRatio,
+        metalRatio: this.materialIndicators.metalRatio,
+        smoothness: this.materialIndicators.smoothness
+      },
+      presetAtmosphere: this.getSelectedPresetAtmosphere()
+    };
+
+    // 触发同步事件
+    this.requirementConfirmed.emit({
+      id: 'requirement-sync',
+      title: '需求沟通完成',
+      description: '关键信息已同步至方案确认阶段',
+      priority: 'high',
+      status: 'confirmed',
+      lastUpdated: new Date(),
+      metrics: this.convertToMetrics(keyInfo)
+    });
+
+    // 启动交付执行流程
+    setTimeout(() => {
+      this.startDeliveryExecution();
+    }, 1000);
+  }
+
+  private getSelectedPresetAtmosphere(): any {
+    // 根据当前颜色指标找到最匹配的预设氛围
+    const currentRgb = `${this.colorIndicators.mainColor.r},${this.colorIndicators.mainColor.g},${this.colorIndicators.mainColor.b}`;
+    return this.presetAtmospheres.find(preset => preset.rgb === currentRgb) || this.presetAtmospheres[0];
+  }
+
+  private convertToMetrics(keyInfo: any): RequirementMetric[] {
+    return [
+      {
+        id: 'color-sync',
+        category: 'color',
+        name: '色彩氛围',
+        value: keyInfo.colorAtmosphere
+      },
+      {
+        id: 'space-sync',
+        category: 'space',
+        name: '空间结构',
+        value: keyInfo.spaceStructure
+      },
+      {
+        id: 'material-sync',
+        category: 'material',
+        name: '材质权重',
+        value: keyInfo.materialWeights
+      }
+    ];
+  }
+
+  // 启动交付执行流程
+  private startDeliveryExecution(): void {
+    console.log('启动交付执行流程');
+    
+    // 显示执行启动提示
+    this.showExecutionStartNotification();
+    
+    // 这里可以触发路由跳转或其他业务逻辑
+    // 例如:this.router.navigate(['/project', this.projectId, 'execution']);
+  }
+
+  private showExecutionStartNotification(): void {
+    // 显示执行流程启动的通知
+    const notification = document.createElement('div');
+    notification.className = 'execution-notification';
+    notification.innerHTML = `
+      <div class="notification-content">
+        <svg viewBox="0 0 24 24" fill="currentColor">
+          <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
+        </svg>
+        <span>交付执行流程已启动</span>
+      </div>
+    `;
+    
+    document.body.appendChild(notification);
+    
+    setTimeout(() => {
+      notification.remove();
+    }, 3000);
   }
 
   getProgressPercentage(): number {
@@ -541,12 +745,154 @@ export class RequirementsConfirmCardComponent implements OnInit {
     }
   }
 
+  // 滑动条数值同步方法
+  onSliderChange(key: string, value: number): void {
+    console.log(`滑动条变化: ${key} = ${value}`);
+    console.log('变化前的mainColor:', JSON.stringify(this.colorIndicators.mainColor));
+    
+    // 直接更新数据,不需要额外的updateIndicatorValue调用
+    // 因为使用了双向绑定,数据已经自动更新
+    if (key === 'r' || key === 'g' || key === 'b') {
+      // 确保RGB值在有效范围内
+      const range = this.getIndicatorRange(key);
+      if (range) {
+        const clampedValue = Math.max(range.min, Math.min(range.max, value));
+        this.colorIndicators.mainColor[key as keyof typeof this.colorIndicators.mainColor] = clampedValue;
+        console.log(`RGB值已更新: ${key} = ${clampedValue}`);
+        console.log('变化后的mainColor:', JSON.stringify(this.colorIndicators.mainColor));
+      }
+      
+      // 触发颜色指标更新
+      this.updateColorIndicator('mainColor', this.colorIndicators.mainColor);
+    } else {
+      // 处理其他颜色指标
+      this.updateIndicatorValue(key, value);
+    }
+    
+    this.triggerAutoSave(); // 触发自动保存
+    this.checkStageCompletion(); // 检查阶段完成状态
+    
+    // 强制触发变更检测
+    this.cdr.detectChanges();
+    console.log('滑动条变更检测已触发');
+  }
+
+  onInputChange(key: string, value: number): void {
+    console.log(`输入框变化: ${key} = ${value}`);
+    console.log('变化前的mainColor:', JSON.stringify(this.colorIndicators.mainColor));
+    
+    // 直接更新数据,不需要额外的updateIndicatorValue调用
+    // 因为使用了双向绑定,数据已经自动更新
+    if (key === 'r' || key === 'g' || key === 'b') {
+      // 确保RGB值在有效范围内
+      const range = this.getIndicatorRange(key);
+      if (range) {
+        const clampedValue = Math.max(range.min, Math.min(range.max, value));
+        this.colorIndicators.mainColor[key as keyof typeof this.colorIndicators.mainColor] = clampedValue;
+        console.log(`RGB值已更新: ${key} = ${clampedValue}`);
+        console.log('变化后的mainColor:', JSON.stringify(this.colorIndicators.mainColor));
+      }
+      
+      // 触发颜色指标更新
+      this.updateColorIndicator('mainColor', this.colorIndicators.mainColor);
+    } else {
+      // 处理其他颜色指标
+      this.updateIndicatorValue(key, value);
+    }
+    
+    this.triggerAutoSave();
+    
+    // 强制触发变更检测
+    this.cdr.detectChanges();
+    console.log('输入框变更检测已触发');
+  }
+
+  validateInput(key: string, value: string): void {
+    const numValue = parseFloat(value);
+    if (isNaN(numValue)) {
+      // 如果输入无效,恢复原值
+      this.restoreIndicatorValue(key);
+      return;
+    }
+    
+    const range = this.getIndicatorRange(key);
+    if (range) {
+      const clampedValue = Math.max(range.min, Math.min(range.max, numValue));
+      this.updateIndicatorValue(key, clampedValue);
+    }
+  }
+
+  private updateIndicatorValue(key: string, value: number): void {
+    console.log(`updateIndicatorValue调用: ${key} = ${value}`);
+    
+    // 更新颜色指标
+    if (key in this.colorIndicators) {
+      if (key === 'r' || key === 'g' || key === 'b') {
+        // 创建新的mainColor对象以确保变更检测
+        const oldColor = { ...this.colorIndicators.mainColor };
+        this.colorIndicators.mainColor = {
+          ...this.colorIndicators.mainColor,
+          [key]: value
+        };
+        console.log(`RGB更新: ${key}从${oldColor[key as keyof typeof oldColor]}变为${value}`, 
+                   '新mainColor:', this.colorIndicators.mainColor);
+        this.updateColorIndicator('mainColor', this.colorIndicators.mainColor);
+      } else {
+        (this.colorIndicators as any)[key] = value;
+        this.updateColorIndicator(key, value);
+      }
+      return;
+    }
+
+    // 更新空间指标
+    if (key in this.spaceIndicators) {
+      (this.spaceIndicators as any)[key] = value;
+      this.updateSpaceIndicator(key, value);
+      return;
+    }
+
+    // 更新材质指标
+    if (key in this.materialIndicators) {
+      (this.materialIndicators as any)[key] = value;
+      this.updateMaterialIndicator(key, value);
+      return;
+    }
+  }
+
+  private restoreIndicatorValue(key: string): void {
+    // 这里可以从历史状态或默认值恢复
+    console.log(`恢复指标值: ${key}`);
+  }
+
+  private getIndicatorRange(key: string): { min: number; max: number } | null {
+    // 检查颜色范围
+    if (key === 'r' || key === 'g' || key === 'b') {
+      return this.indicatorRanges.color[key as keyof typeof this.indicatorRanges.color];
+    }
+    if (key in this.indicatorRanges.color) {
+      return (this.indicatorRanges.color as any)[key];
+    }
+
+    // 检查空间范围
+    if (key in this.indicatorRanges.space) {
+      return (this.indicatorRanges.space as any)[key];
+    }
+
+    // 检查材质范围
+    if (key in this.indicatorRanges.material) {
+      return (this.indicatorRanges.material as any)[key];
+    }
+
+    return null;
+  }
+
   // 指标更新方法
   updateColorIndicator(type: string, value?: any): void {
     switch (type) {
       case 'mainColor':
         if (value && typeof value === 'object' && 'r' in value) {
-          this.colorIndicators.mainColor = value;
+          this.colorIndicators.mainColor = { ...value }; // 创建新对象以触发变更检测
+          console.log('mainColor更新后:', this.colorIndicators.mainColor);
         }
         break;
       case 'colorTemperature':
@@ -557,9 +903,12 @@ export class RequirementsConfirmCardComponent implements OnInit {
         break;
     }
     
-    console.log(`更新颜色指示器 ${type}:`, value);
+    console.log(`更新颜色指示器 ${type}:`, value, '当前RGB:', this.getRgbString());
     this.checkConsistency();
     this.updatePreview();
+    // 强制触发变更检测
+    this.cdr.detectChanges();
+    console.log('变更检测已触发');
   }
 
   updateSpaceIndicator(key: string, value: number) {
@@ -593,17 +942,17 @@ export class RequirementsConfirmCardComponent implements OnInit {
 
   // 预览更新
   private updatePreview() {
-    const { r, g, b } = this.colorIndicators.mainColor;
-    const previewElement = document.querySelector('.color-preview') as HTMLElement;
-    if (previewElement) {
-      previewElement.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
-    }
+    // 移除直接DOM操作,依赖Angular的属性绑定
+    console.log('updatePreview调用,当前RGB:', this.getRgbString());
+    // 不再直接操作DOM,让Angular的变更检测处理
   }
 
   // 获取RGB字符串
   getRgbString(): string {
     const { r, g, b } = this.colorIndicators.mainColor;
-    return `rgb(${r}, ${g}, ${b})`;
+    const rgbString = `rgb(${r}, ${g}, ${b})`;
+    console.log('getRgbString调用:', rgbString, '完整mainColor对象:', this.colorIndicators.mainColor);
+    return rgbString;
   }
 
   // 获取色温描述
@@ -796,4 +1145,253 @@ export class RequirementsConfirmCardComponent implements OnInit {
     };
     return priorityMap[priority] || priority;
   }
+
+  // 刷新进度(保留用于手动刷新,但不再强制要求)
+  refreshProgress(): void {
+    // 重新计算进度
+    this.updateProgress();
+    
+    // 更新进度条显示
+    const progress = this.getProgressPercentage();
+    this.updateProgressBar(progress);
+    
+    // 检查阶段完成状态
+    this.checkStageCompletion();
+    
+    // 触发进度更新事件
+    this.progressUpdated.emit(progress);
+    
+    console.log('进度已手动刷新:', progress + '%');
+  }
+
+  // 自动保存功能
+  private triggerAutoSave(): void {
+    if (!this.autoSaveEnabled) return;
+    
+    this.hasUnsavedChanges = true;
+    this.saveStatus = 'unsaved';
+    
+    // 清除之前的定时器
+    if (this.autoSaveTimer) {
+      clearTimeout(this.autoSaveTimer);
+    }
+    
+    // 设置新的自动保存定时器(延迟1秒)
+    this.autoSaveTimer = setTimeout(() => {
+      this.performAutoSave();
+    }, 1000);
+  }
+
+  private async performAutoSave(): Promise<void> {
+    if (this.isSaving) return;
+    
+    this.isSaving = true;
+    this.saveStatus = 'saving';
+    
+    try {
+      // 收集所有需要保存的数据
+      const saveData = this.collectSaveData();
+      
+      // 模拟保存操作(实际项目中这里会调用API)
+      await this.saveToServer(saveData);
+      
+      this.hasUnsavedChanges = false;
+      this.saveStatus = 'saved';
+      this.lastSaveTime = new Date();
+      
+      console.log('自动保存成功:', new Date().toLocaleTimeString());
+    } catch (error) {
+      this.saveStatus = 'error';
+      console.error('自动保存失败:', error);
+    } finally {
+      this.isSaving = false;
+    }
+  }
+
+  // 手动保存
+  async manualSave(): Promise<void> {
+    if (this.isSaving) return;
+    
+    // 清除自动保存定时器
+    if (this.autoSaveTimer) {
+      clearTimeout(this.autoSaveTimer);
+    }
+    
+    await this.performAutoSave();
+  }
+
+  private collectSaveData(): any {
+    return {
+      projectId: this.projectId,
+      colorIndicators: this.colorIndicators,
+      spaceIndicators: this.spaceIndicators,
+      materialIndicators: this.materialIndicators,
+      requirementItems: this.requirementItems,
+      requirementMetrics: this.requirementMetrics,
+      materialFiles: this.materialFiles,
+      collaborationComments: this.collaborationComments,
+      stageCompletionStatus: this.stageCompletionStatus,
+      lastUpdated: new Date()
+    };
+  }
+
+  private async saveToServer(data: any): Promise<void> {
+    // 模拟API调用延迟
+    return new Promise((resolve, reject) => {
+      setTimeout(() => {
+        // 模拟90%成功率
+        if (Math.random() > 0.1) {
+          resolve();
+        } else {
+          reject(new Error('网络错误'));
+        }
+      }, 500);
+    });
+  }
+
+  // 检查阶段完成状态
+  private checkStageCompletion(): void {
+    // 检查素材分析阶段 - 需要有素材文件且都已分析
+    this.stageCompletionStatus.materialAnalysis = this.materialFiles.length > 0 && 
+      this.materialFiles.every(m => m.analysis);
+    
+    // 检查需求映射阶段 - 需要有确认的需求项,或者滑动条数据已调整且保存
+    const hasConfirmedRequirements = this.requirementItems.length > 0 && 
+      this.requirementItems.some(r => r.status === 'confirmed');
+    const hasAdjustedIndicators = this.hasIndicatorChanges();
+    this.stageCompletionStatus.requirementMapping = hasConfirmedRequirements || 
+      (hasAdjustedIndicators && this.saveStatus === 'saved');
+    
+    // 检查协作验证阶段 - 需要有协作评论或需求评论
+    this.stageCompletionStatus.collaboration = this.collaborationComments.length > 0 || 
+      this.requirementItems.some(r => r.comments && r.comments.length > 0);
+    
+    // 检查进度审查阶段 - 需要进度达到80%以上
+    this.stageCompletionStatus.progressReview = this.getProgressPercentage() >= 80;
+    
+    console.log('阶段完成状态更新:', this.stageCompletionStatus);
+  }
+
+  // 检查指示器是否有变化
+  private hasIndicatorChanges(): boolean {
+    // 检查颜色指示器是否有变化(与默认值比较)
+    const defaultColorIndicators = {
+      mainColor: { r: 255, g: 230, b: 180 },
+      colorTemperature: 2700,
+      colorRange: '暖色调',
+      saturation: 70,
+      brightness: 80,
+      contrast: 60
+    };
+    
+    const defaultSpaceIndicators = {
+      lineRatio: 60,
+      blankRatio: 30,
+      flowWidth: 0.9,
+      aspectRatio: 1.6,
+      ceilingHeight: 2.8,
+      lightingLevel: 300
+    };
+    
+    const defaultMaterialIndicators = {
+      fabricRatio: 50,
+      woodRatio: 30,
+      metalRatio: 20,
+      smoothness: 7,
+      glossiness: 4,
+      texture: 6
+    };
+    
+    // 检查颜色指示器变化
+    const colorChanged = JSON.stringify(this.colorIndicators) !== JSON.stringify(defaultColorIndicators);
+    
+    // 检查空间指示器变化
+    const spaceChanged = JSON.stringify(this.spaceIndicators) !== JSON.stringify(defaultSpaceIndicators);
+    
+    // 检查材质指示器变化
+    const materialChanged = JSON.stringify(this.materialIndicators) !== JSON.stringify(defaultMaterialIndicators);
+    
+    return colorChanged || spaceChanged || materialChanged;
+  }
+
+  // 获取阶段状态文本
+  getStageStatusText(stage: keyof typeof this.stageCompletionStatus): string {
+    if (this.stageCompletionStatus[stage]) {
+      return '已完成';
+    } else {
+      // 检查是否为未开始状态
+      const stages = ['materialAnalysis', 'requirementMapping', 'collaboration', 'progressReview'];
+      const currentIndex = stages.indexOf(stage);
+      
+      // 检查前面的阶段是否都已完成
+      let allPreviousCompleted = true;
+      for (let i = 0; i < currentIndex; i++) {
+        if (!this.stageCompletionStatus[stages[i] as keyof typeof this.stageCompletionStatus]) {
+          allPreviousCompleted = false;
+          break;
+        }
+      }
+      
+      return allPreviousCompleted ? '进行中' : '未进行';
+    }
+  }
+
+  // 获取阶段状态类名
+  getStageStatusClass(stage: keyof typeof this.stageCompletionStatus): string {
+    if (this.stageCompletionStatus[stage]) {
+      return 'stage-completed';
+    } else {
+      // 检查是否为未开始状态
+      const stages = ['materialAnalysis', 'requirementMapping', 'collaboration', 'progressReview'];
+      const currentIndex = stages.indexOf(stage);
+      
+      // 检查前面的阶段是否都已完成
+      let allPreviousCompleted = true;
+      for (let i = 0; i < currentIndex; i++) {
+        if (!this.stageCompletionStatus[stages[i] as keyof typeof this.stageCompletionStatus]) {
+          allPreviousCompleted = false;
+          break;
+        }
+      }
+      
+      return allPreviousCompleted ? 'stage-in-progress' : 'stage-pending';
+    }
+  }
+
+  // 检查是否可以进入下一阶段
+  canProceedToNextStage(currentStage: keyof typeof this.stageCompletionStatus): boolean {
+    const stages = Object.keys(this.stageCompletionStatus) as (keyof typeof this.stageCompletionStatus)[];
+    const currentIndex = stages.indexOf(currentStage);
+    
+    // 检查当前阶段之前的所有阶段是否都已完成
+    for (let i = 0; i <= currentIndex; i++) {
+      if (!this.stageCompletionStatus[stages[i]]) {
+        return false;
+      }
+    }
+    
+    return true;
+  }
+
+  // 获取保存状态文本
+  getSaveStatusText(): string {
+    switch (this.saveStatus) {
+      case 'saved': return this.lastSaveTime ? `已保存 ${this.lastSaveTime.toLocaleTimeString()}` : '已保存';
+      case 'saving': return '保存中...';
+      case 'error': return '保存失败';
+      case 'unsaved': return '有未保存的更改';
+      default: return '';
+    }
+  }
+
+  // 获取保存状态图标
+  getSaveStatusIcon(): string {
+    switch (this.saveStatus) {
+      case 'saved': return '✓';
+      case 'saving': return '⟳';
+      case 'error': return '⚠';
+      case 'unsaved': return '●';
+      default: return '';
+    }
+  }
 }

+ 110 - 1
src/app/shared/styles/_hr-dialog.scss

@@ -1,6 +1,5 @@
 @use 'variables';
 @use 'ios-theme' as ios;
-
 // HR模块通用对话框样式
 // 基于新增员工面板的设计,提供统一的对话框外观
 
@@ -131,6 +130,73 @@
       }
     }
 
+    // 表单字段样式
+    mat-form-field {
+      margin-bottom: ios.$ios-spacing-xs;
+      
+      .mat-mdc-form-field-outline {
+        border-radius: ios.$ios-radius-md;
+        border-color: rgba(0, 122, 255, 0.2);
+        transition: all 0.3s ease;
+      }
+
+      &:hover .mat-mdc-form-field-outline {
+        border-color: rgba(0, 122, 255, 0.4);
+      }
+
+      &.mat-focused .mat-mdc-form-field-outline {
+        border-color: ios.$ios-primary;
+        border-width: 2px;
+        box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
+      }
+
+      .mat-mdc-form-field-label {
+        color: ios.$ios-text-secondary;
+        font-family: ios.$ios-font-family;
+        font-weight: ios.$ios-font-weight-medium;
+      }
+
+      .mat-mdc-input-element {
+        color: ios.$ios-text-primary;
+        font-family: ios.$ios-font-family;
+        padding: ios.$ios-spacing-sm;
+      }
+
+      .mat-mdc-form-field-error.error-message {
+        color: ios.$ios-error;
+        font-family: ios.$ios-font-family;
+        font-size: ios.$ios-font-size-caption-1;
+        margin-top: ios.$ios-spacing-xs;
+      }
+
+      // 选择框样式
+      .mat-mdc-select-value {
+        color: ios.$ios-text-primary;
+        font-family: ios.$ios-font-family;
+        padding: ios.$ios-spacing-sm 0;
+      }
+
+      .mat-mdc-select-arrow {
+        color: ios.$ios-text-secondary;
+        transition: transform 0.3s ease;
+      }
+
+      &.mat-focused .mat-mdc-select-arrow {
+        transform: rotate(180deg);
+        color: ios.$ios-primary;
+      }
+
+      // 后缀图标样式
+      .mat-mdc-form-field-icon-suffix {
+        color: ios.$ios-text-secondary;
+        transition: color 0.3s ease;
+      }
+
+      &:hover .mat-mdc-form-field-icon-suffix {
+        color: ios.$ios-primary;
+      }
+    }
+
     // 信息展示区域
     .info-section {
       display: flex;
@@ -179,6 +245,49 @@
           box-shadow: ios.$ios-shadow-md;
         }
 
+        .preview-header {
+          display: flex;
+          align-items: center;
+          gap: ios.$ios-spacing-md;
+          margin-bottom: ios.$ios-spacing-md;
+
+          .preview-icon {
+            width: 32px;
+            height: 32px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            border-radius: ios.$ios-radius-md;
+            background: rgba(0, 122, 255, 0.1);
+            transition: all 0.3s ease;
+
+            &:hover {
+              background: rgba(0, 122, 255, 0.2);
+              transform: scale(1.1);
+            }
+          }
+
+          .preview-info {
+            flex: 1;
+
+            .preview-category {
+              font-size: ios.$ios-font-size-caption-1;
+              color: ios.$ios-text-secondary;
+              font-family: ios.$ios-font-family;
+              text-transform: uppercase;
+              letter-spacing: 0.5px;
+              margin-bottom: ios.$ios-spacing-xs;
+            }
+
+            .preview-name {
+              font-size: ios.$ios-font-size-body;
+              color: ios.$ios-text-primary;
+              font-family: ios.$ios-font-family;
+              font-weight: ios.$ios-font-weight-medium;
+            }
+          }
+        }
+
         .content-grid {
           display: grid;
           grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));