徐福静0235668 11 сар өмнө
parent
commit
53fb276ab8
64 өөрчлөгдсөн 16978 нэмэгдсэн , 851 устгасан
  1. 4 1
      .vscode/extensions.json
  2. 47 0
      src/app/models/project.model.ts
  3. 243 0
      src/app/pages/customer-service/consultation-order/components/designer-assignment/designer-assignment.component.html
  4. 567 0
      src/app/pages/customer-service/consultation-order/components/designer-assignment/designer-assignment.component.scss
  5. 350 0
      src/app/pages/customer-service/consultation-order/components/designer-assignment/designer-assignment.component.ts
  6. 317 0
      src/app/pages/customer-service/consultation-order/components/designer-calendar/designer-calendar.component.html
  7. 843 0
      src/app/pages/customer-service/consultation-order/components/designer-calendar/designer-calendar.component.scss
  8. 419 0
      src/app/pages/customer-service/consultation-order/components/designer-calendar/designer-calendar.component.ts
  9. 100 0
      src/app/pages/customer-service/consultation-order/components/order-form/order-form.component.html
  10. 176 0
      src/app/pages/customer-service/consultation-order/components/order-form/order-form.component.scss
  11. 113 0
      src/app/pages/customer-service/consultation-order/components/order-form/order-form.component.ts
  12. 167 0
      src/app/pages/customer-service/consultation-order/components/quotation-detail/quotation-detail.component.html
  13. 348 0
      src/app/pages/customer-service/consultation-order/components/quotation-detail/quotation-detail.component.scss
  14. 212 0
      src/app/pages/customer-service/consultation-order/components/quotation-detail/quotation-detail.component.ts
  15. 2 0
      src/app/pages/customer-service/dashboard/pages/consultation-list/consultation-list.component.scss
  16. 290 0
      src/app/pages/designer/project-detail/components/designer-assignment/designer-assignment.component.html
  17. 711 0
      src/app/pages/designer/project-detail/components/designer-assignment/designer-assignment.component.scss
  18. 315 0
      src/app/pages/designer/project-detail/components/designer-assignment/designer-assignment.component.ts
  19. 121 0
      src/app/pages/designer/project-detail/components/designer-calendar/designer-calendar.component.html
  20. 450 0
      src/app/pages/designer/project-detail/components/designer-calendar/designer-calendar.component.scss
  21. 70 0
      src/app/pages/designer/project-detail/components/order-creation/order-creation.component.html
  22. 79 0
      src/app/pages/designer/project-detail/components/order-creation/order-creation.component.scss
  23. 105 0
      src/app/pages/designer/project-detail/components/order-creation/order-creation.component.ts
  24. 323 0
      src/app/pages/designer/project-detail/components/quotation-detail/quotation-detail.component.html
  25. 198 0
      src/app/pages/designer/project-detail/components/quotation-detail/quotation-detail.component.scss
  26. 311 0
      src/app/pages/designer/project-detail/components/quotation-detail/quotation-detail.component.ts
  27. 192 0
      src/app/pages/designer/project-detail/components/quotation-details/quotation-details.component.html
  28. 435 0
      src/app/pages/designer/project-detail/components/quotation-details/quotation-details.component.scss
  29. 198 0
      src/app/pages/designer/project-detail/components/quotation-details/quotation-details.component.ts
  30. 0 295
      src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav-styles.scss
  31. 0 14
      src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav.component.html
  32. 0 148
      src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav.component.scss
  33. 0 56
      src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav.component.ts
  34. 263 58
      src/app/pages/designer/project-detail/project-detail.html
  35. 1011 1
      src/app/pages/designer/project-detail/project-detail.scss
  36. 408 25
      src/app/pages/designer/project-detail/project-detail.ts
  37. 351 0
      src/app/services/ai-quotation.service.ts
  38. 619 0
      src/app/services/auto-settlement.service.ts
  39. 313 0
      src/app/services/payment-voucher-recognition.service.ts
  40. 4 0
      src/app/services/project.service.ts
  41. 256 0
      src/app/shared/components/auto-settlement-config/auto-settlement-config.html
  42. 392 0
      src/app/shared/components/auto-settlement-config/auto-settlement-config.scss
  43. 260 0
      src/app/shared/components/auto-settlement-config/auto-settlement-config.ts
  44. 236 23
      src/app/shared/components/complaint-card/complaint-card.html
  45. 649 0
      src/app/shared/components/complaint-card/complaint-card.scss
  46. 353 11
      src/app/shared/components/complaint-card/complaint-card.ts
  47. 230 1
      src/app/shared/components/customer-review-card/customer-review-card.html
  48. 329 0
      src/app/shared/components/customer-review-card/customer-review-card.scss
  49. 276 7
      src/app/shared/components/customer-review-card/customer-review-card.ts
  50. 396 0
      src/app/shared/components/customer-review-form/customer-review-form.html
  51. 469 0
      src/app/shared/components/customer-review-form/customer-review-form.scss
  52. 227 0
      src/app/shared/components/customer-review-form/customer-review-form.ts
  53. 167 0
      src/app/shared/components/panoramic-synthesis-card/panoramic-synthesis-card.html
  54. 722 0
      src/app/shared/components/panoramic-synthesis-card/panoramic-synthesis-card.scss
  55. 243 0
      src/app/shared/components/panoramic-synthesis-card/panoramic-synthesis-card.ts
  56. 257 101
      src/app/shared/components/settlement-card/settlement-card.html
  57. 259 101
      src/app/shared/components/settlement-card/settlement-card.scss
  58. 309 4
      src/app/shared/components/settlement-card/settlement-card.ts
  59. 24 0
      src/app/shared/components/star-rating/star-rating.component.html
  60. 133 0
      src/app/shared/components/star-rating/star-rating.component.scss
  61. 104 0
      src/app/shared/components/star-rating/star-rating.component.ts
  62. 1 2
      src/index.html
  63. 8 3
      src/styles.scss
  64. 3 0
      temp.txt

+ 4 - 1
.vscode/extensions.json

@@ -1,4 +1,7 @@
 {
   // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
-  "recommendations": ["angular.ng-template"]
+  "recommendations": [
+    "angular.ng-template",
+    "kingleo.qwen"
+  ]
 }

+ 47 - 0
src/app/models/project.model.ts

@@ -191,4 +191,51 @@ export interface MatchingOrder {
   requiredSkills: string[];
   matchRate: number;
   customerLevel: '优质' | '普通';
+}
+
+// 全景合成相关接口
+
+// 空间图像
+export interface SpaceImage {
+  id: string;
+  name: string;
+  url: string;
+  thumbnailUrl: string;
+  type: 'living_room' | 'bedroom' | 'kitchen' | 'bathroom' | 'dining_room' | 'study' | 'balcony';
+  size: number;
+  width: number;
+  height: number;
+  format: 'jpg' | 'png' | 'webp';
+  uploadDate: Date;
+  isPrimary: boolean;
+}
+
+// 全景空间
+export interface PanoramicSpace {
+  id: string;
+  name: string;
+  type: 'living_room' | 'bedroom' | 'kitchen' | 'bathroom' | 'dining_room' | 'study' | 'balcony';
+  images: SpaceImage[];
+  thumbnailUrl: string;
+  createdAt: Date;
+  updatedAt: Date;
+  status: 'pending' | 'processing' | 'completed' | 'failed';
+  progress?: number;
+}
+
+// 全景合成
+export interface PanoramicSynthesis {
+  id: string;
+  projectId: string;
+  projectName: string;
+  spaces: PanoramicSpace[];
+  status: 'draft' | 'processing' | 'completed' | 'published';
+  createdAt: Date;
+  updatedAt: Date;
+  completedAt?: Date;
+  previewUrl?: string;
+  downloadUrl?: string;
+  quality: 'standard' | 'high' | 'ultra';
+  renderTime?: number;
+  fileSize?: number;
 }

+ 243 - 0
src/app/pages/customer-service/consultation-order/components/designer-assignment/designer-assignment.component.html

@@ -0,0 +1,243 @@
+<!-- 设计师分配组件 -->
+<div class="designer-assignment-container">
+  <div class="assignment-header">
+    <div class="header-left">
+      <h4>设计师分配</h4>
+      <p>支持多设计师协作,按报价项目分配工作量</p>
+    </div>
+    <div class="header-actions">
+      <button 
+        type="button" 
+        class="btn-secondary btn-sm"
+        (click)="showDesignerCalendar.set(!showDesignerCalendar())">
+        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+          <rect x="3" y="4" width="18" height="14" rx="2" ry="2"></rect>
+          <line x1="16" y1="2" x2="16" y2="6"></line>
+          <line x1="8" y1="2" x2="8" y2="6"></line>
+          <line x1="3" y1="10" x2="21" y2="10"></line>
+        </svg>
+        {{ showDesignerCalendar() ? '隐藏日历' : '查看日历' }}
+      </button>
+      
+      <button 
+        type="button" 
+        class="btn-primary btn-sm"
+        (click)="addDesignerAssignment()">
+        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+          <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
+          <circle cx="9" cy="7" r="4"></circle>
+          <line x1="19" y1="8" x2="19" y2="14"></line>
+          <line x1="22" y1="11" x2="16" y2="11"></line>
+        </svg>
+        添加设计师
+      </button>
+    </div>
+  </div>
+
+  <!-- 设计师日历视图 -->
+  @if (showDesignerCalendar()) {
+    <div class="designer-calendar-section">
+      <app-designer-calendar 
+        [designers]="availableDesigners()"
+        [selectedDate]="selectedDate()"
+        (designerSelected)="onDesignerSelect($event)"
+        (assignmentRequested)="onDesignerSelect($event)">
+      </app-designer-calendar>
+    </div>
+  }
+
+  <!-- 项目组选择 -->
+  <div class="project-group-section">
+    <div class="group-selector">
+      <label class="field-label">项目组分配</label>
+      <select 
+        [(ngModel)]="selectedProjectGroup" 
+        (ngModelChange)="onProjectGroupChange($event)"
+        class="field-select">
+        <option value="">选择项目组</option>
+        @for (group of projectGroups; track group.id) {
+          <option [value]="group.id">{{ group.name }} (组长: {{ group.leader }})</option>
+        }
+      </select>
+      <div class="field-hint">选择项目组后,组长可进一步分配具体任务给组员</div>
+    </div>
+
+    <!-- 跨组合作选项 -->
+    @if (selectedProjectGroup) {
+      <div class="cross-group-option">
+        <label class="checkbox-label">
+          <input 
+            type="checkbox" 
+            [(ngModel)]="allowCrossGroupCollaboration"
+            (ngModelChange)="onCrossGroupChange($event)">
+          <span class="checkmark"></span>
+          允许跨组合作
+        </label>
+        <div class="field-hint">组长可以选择其他项目组成员参与协作</div>
+      </div>
+    }
+  </div>
+
+  <!-- 设计师分配列表 -->
+  <div class="assignment-list">
+    @if (designerAssignments().length === 0) {
+      <div class="empty-state">
+        <div class="empty-icon">
+          <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+            <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
+            <circle cx="9" cy="7" r="4"></circle>
+            <path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
+            <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+          </svg>
+        </div>
+        <p>暂未分配设计师</p>
+        <p class="empty-hint">请先选择项目组或直接添加设计师</p>
+      </div>
+    } @else {
+      @for (assignment of designerAssignments(); track assignment.id) {
+        <div class="assignment-item">
+          <div class="designer-info">
+            <div class="designer-avatar">
+              <img [src]="assignment.designer.avatar || '/assets/default-avatar.png'" 
+                   [alt]="assignment.designer.name">
+            </div>
+            <div class="designer-details">
+              <div class="designer-name">{{ assignment.designer.name }}</div>
+              <div class="designer-meta">
+                <span class="designer-group">{{ assignment.designer.groupName }}</span>
+                @if (assignment.designer.isLeader) {
+                  <span class="leader-badge">组长</span>
+                }
+                @if (assignment.isCrossGroup) {
+                  <span class="cross-group-badge">跨组</span>
+                }
+              </div>
+              <div class="designer-status">
+                <span class="status-indicator" [class]="assignment.designer.status"></span>
+                {{ getDesignerStatusText(assignment.designer.status) }}
+              </div>
+            </div>
+          </div>
+
+          <div class="assignment-details">
+            <!-- 分配的报价项目 -->
+            <div class="assigned-quotations">
+              <label class="field-label">负责项目</label>
+              <div class="quotation-chips">
+                @for (quotationId of assignment.quotationItemIds; track quotationId) {
+                  <div class="quotation-chip">
+                    <span>{{ getQuotationItemName(quotationId) }}</span>
+                    <button 
+                      type="button" 
+                      class="remove-quotation"
+                      (click)="removeQuotationFromAssignment(assignment.id, quotationId)">
+                      <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                        <line x1="18" y1="6" x2="6" y2="18"></line>
+                        <line x1="6" y1="6" x2="18" y2="18"></line>
+                      </svg>
+                    </button>
+                  </div>
+                }
+                
+                <!-- 添加项目按钮 -->
+                <div class="add-quotation-dropdown" [class.open]="openDropdownId() === assignment.id">
+                  <button 
+                    type="button" 
+                    class="add-quotation-btn"
+                    (click)="toggleQuotationDropdown(assignment.id)">
+                    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                      <line x1="12" y1="5" x2="12" y2="19"></line>
+                      <line x1="5" y1="12" x2="19" y2="12"></line>
+                    </svg>
+                  </button>
+                  
+                  @if (openDropdownId() === assignment.id) {
+                    <div class="quotation-dropdown">
+                      @for (item of getUnassignedQuotationItems(assignment.id); track item.id) {
+                        <div 
+                          class="dropdown-item"
+                          (click)="addQuotationToAssignment(assignment.id, item.id)">
+                          <span>{{ item.roomType }}</span>
+                          <span class="item-amount">¥{{ item.amount | number:'1.2-2' }}</span>
+                        </div>
+                      }
+                      @if (getUnassignedQuotationItems(assignment.id).length === 0) {
+                        <div class="dropdown-empty">所有项目已分配</div>
+                      }
+                    </div>
+                  }
+                </div>
+              </div>
+            </div>
+
+            <!-- 工作量统计 -->
+            <div class="workload-info">
+              <div class="workload-item">
+                <span class="workload-label">分配金额:</span>
+                <span class="workload-value">¥{{ assignment.assignedAmount | number:'1.2-2' }}</span>
+              </div>
+              <div class="workload-item">
+                <span class="workload-label">工作量占比:</span>
+                <span class="workload-value">{{ assignment.workloadPercentage | number:'1.1-1' }}%</span>
+              </div>
+            </div>
+          </div>
+
+          <div class="assignment-actions">
+            <button 
+              type="button" 
+              class="action-btn edit-btn"
+              (click)="editAssignment(assignment)">
+              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
+                <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
+              </svg>
+            </button>
+            <button 
+              type="button" 
+              class="action-btn delete-btn"
+              (click)="removeAssignment(assignment.id)">
+              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polyline points="3 6 5 6 21 6"></polyline>
+                <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2 2v2"></path>
+              </svg>
+            </button>
+          </div>
+        </div>
+      }
+    }
+  </div>
+
+  <!-- 分配汇总 -->
+  @if (designerAssignments().length > 0) {
+    <div class="assignment-summary">
+      <div class="summary-stats">
+        <div class="stat-item">
+          <span class="stat-label">参与设计师:</span>
+          <span class="stat-value">{{ designerAssignments().length }} 人</span>
+        </div>
+        <div class="stat-item">
+          <span class="stat-label">已分配金额:</span>
+          <span class="stat-value">¥{{ totalAssignedAmount() | number:'1.2-2' }}</span>
+        </div>
+        <div class="stat-item">
+          <span class="stat-label">分配进度:</span>
+          <span class="stat-value" [class.complete]="assignmentProgress() >= 100">
+            {{ assignmentProgress() | number:'1.1-1' }}%
+          </span>
+        </div>
+      </div>
+      
+      @if (assignmentProgress() < 100) {
+        <div class="assignment-warning">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
+            <line x1="12" y1="9" x2="12" y2="13"></line>
+            <line x1="12" y1="17" x2="12.01" y2="17"></line>
+          </svg>
+          还有 ¥{{ remainingAmount() | number:'1.2-2' }} 未分配给设计师
+        </div>
+      }
+    </div>
+  }
+</div>

+ 567 - 0
src/app/pages/customer-service/consultation-order/components/designer-assignment/designer-assignment.component.scss

@@ -0,0 +1,567 @@
+.designer-assignment-container {
+  .assignment-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    margin-bottom: 24px;
+    
+    .header-left {
+      h4 {
+        font-size: 16px;
+        font-weight: 600;
+        color: #1a1a1a;
+        margin: 0 0 4px 0;
+      }
+      
+      p {
+        font-size: 13px;
+        color: #666;
+        margin: 0;
+      }
+    }
+    
+    .header-actions {
+      display: flex;
+      gap: 12px;
+      
+      .btn-sm {
+        padding: 8px 16px;
+        font-size: 13px;
+        border-radius: 6px;
+        border: none;
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        transition: all 0.2s ease;
+        
+        svg {
+          flex-shrink: 0;
+        }
+      }
+      
+      .btn-secondary {
+        background: #f8fafc;
+        color: #475569;
+        border: 1px solid #e2e8f0;
+        
+        &:hover {
+          background: #f1f5f9;
+          border-color: #cbd5e1;
+        }
+      }
+      
+      .btn-primary {
+        background: #4f46e5;
+        color: white;
+        
+        &:hover {
+          background: #4338ca;
+        }
+      }
+    }
+  }
+}
+
+.designer-calendar-section {
+  margin-bottom: 24px;
+  padding: 20px;
+  background: #f8fafc;
+  border-radius: 8px;
+  border: 1px solid #e2e8f0;
+}
+
+.project-group-section {
+  margin-bottom: 24px;
+  padding: 20px;
+  background: white;
+  border-radius: 8px;
+  border: 1px solid #e2e8f0;
+  
+  .group-selector {
+    margin-bottom: 16px;
+    
+    .field-label {
+      display: block;
+      font-size: 14px;
+      font-weight: 500;
+      color: #333;
+      margin-bottom: 8px;
+    }
+    
+    .field-select {
+      width: 100%;
+      max-width: 300px;
+      padding: 10px 12px;
+      border: 1px solid #d1d5db;
+      border-radius: 6px;
+      font-size: 14px;
+      
+      &:focus {
+        outline: none;
+        border-color: #4f46e5;
+        box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
+      }
+    }
+    
+    .field-hint {
+      margin-top: 6px;
+      font-size: 12px;
+      color: #6b7280;
+    }
+  }
+  
+  .cross-group-option {
+    .checkbox-label {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      cursor: pointer;
+      font-size: 14px;
+      color: #374151;
+      
+      input[type="checkbox"] {
+        display: none;
+      }
+      
+      .checkmark {
+        width: 18px;
+        height: 18px;
+        border: 2px solid #d1d5db;
+        border-radius: 4px;
+        position: relative;
+        transition: all 0.2s ease;
+        
+        &::after {
+          content: '';
+          position: absolute;
+          left: 5px;
+          top: 2px;
+          width: 4px;
+          height: 8px;
+          border: solid white;
+          border-width: 0 2px 2px 0;
+          transform: rotate(45deg);
+          opacity: 0;
+          transition: opacity 0.2s ease;
+        }
+      }
+      
+      input:checked + .checkmark {
+        background: #4f46e5;
+        border-color: #4f46e5;
+        
+        &::after {
+          opacity: 1;
+        }
+      }
+    }
+    
+    .field-hint {
+      margin-top: 6px;
+      margin-left: 26px;
+      font-size: 12px;
+      color: #6b7280;
+    }
+  }
+}
+
+.assignment-list {
+  .empty-state {
+    text-align: center;
+    padding: 48px 24px;
+    color: #6b7280;
+    
+    .empty-icon {
+      margin-bottom: 16px;
+      
+      svg {
+        color: #d1d5db;
+      }
+    }
+    
+    p {
+      margin: 0 0 8px 0;
+      font-size: 14px;
+      
+      &.empty-hint {
+        font-size: 13px;
+        color: #9ca3af;
+      }
+    }
+  }
+  
+  .assignment-item {
+    background: white;
+    border: 1px solid #e2e8f0;
+    border-radius: 8px;
+    padding: 20px;
+    margin-bottom: 16px;
+    display: flex;
+    gap: 20px;
+    transition: all 0.2s ease;
+    
+    &:hover {
+      border-color: #cbd5e1;
+      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+    }
+    
+    .designer-info {
+      flex-shrink: 0;
+      width: 200px;
+      
+      .designer-avatar {
+        width: 48px;
+        height: 48px;
+        border-radius: 50%;
+        overflow: hidden;
+        margin-bottom: 12px;
+        
+        img {
+          width: 100%;
+          height: 100%;
+          object-fit: cover;
+        }
+      }
+      
+      .designer-details {
+        .designer-name {
+          font-size: 15px;
+          font-weight: 600;
+          color: #1e293b;
+          margin-bottom: 6px;
+        }
+        
+        .designer-meta {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 6px;
+          margin-bottom: 8px;
+          
+          .designer-group {
+            font-size: 12px;
+            color: #64748b;
+            background: #f1f5f9;
+            padding: 2px 6px;
+            border-radius: 4px;
+          }
+          
+          .leader-badge {
+            font-size: 12px;
+            color: #dc2626;
+            background: #fef2f2;
+            padding: 2px 6px;
+            border-radius: 4px;
+            border: 1px solid #fecaca;
+          }
+          
+          .cross-group-badge {
+            font-size: 12px;
+            color: #7c3aed;
+            background: #f3f4f6;
+            padding: 2px 6px;
+            border-radius: 4px;
+            border: 1px solid #e5e7eb;
+          }
+        }
+        
+        .designer-status {
+          display: flex;
+          align-items: center;
+          gap: 6px;
+          font-size: 12px;
+          color: #64748b;
+          
+          .status-indicator {
+            width: 8px;
+            height: 8px;
+            border-radius: 50%;
+            
+            &.available {
+              background: #10b981;
+            }
+            
+            &.busy {
+              background: #f59e0b;
+            }
+            
+            &.stagnant {
+              background: #ef4444;
+            }
+            
+            &.overloaded {
+              background: #8b5cf6;
+            }
+          }
+        }
+      }
+    }
+    
+    .assignment-details {
+      flex: 1;
+      
+      .assigned-quotations {
+        margin-bottom: 16px;
+        
+        .field-label {
+          font-size: 13px;
+          font-weight: 500;
+          color: #374151;
+          margin-bottom: 8px;
+          display: block;
+        }
+        
+        .quotation-chips {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 8px;
+          align-items: center;
+          
+          .quotation-chip {
+            display: flex;
+            align-items: center;
+            gap: 6px;
+            background: #eff6ff;
+            color: #1e40af;
+            padding: 6px 10px;
+            border-radius: 6px;
+            font-size: 13px;
+            border: 1px solid #dbeafe;
+            
+            .remove-quotation {
+              background: none;
+              border: none;
+              color: #64748b;
+              cursor: pointer;
+              padding: 0;
+              display: flex;
+              align-items: center;
+              
+              &:hover {
+                color: #ef4444;
+              }
+            }
+          }
+          
+          .add-quotation-dropdown {
+            position: relative;
+            
+            .add-quotation-btn {
+              width: 28px;
+              height: 28px;
+              border: 1px dashed #cbd5e1;
+              background: white;
+              border-radius: 6px;
+              cursor: pointer;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              color: #64748b;
+              transition: all 0.2s ease;
+              
+              &:hover {
+                border-color: #4f46e5;
+                color: #4f46e5;
+              }
+            }
+            
+            .quotation-dropdown {
+              position: absolute;
+              top: 100%;
+              left: 0;
+              z-index: 10;
+              background: white;
+              border: 1px solid #e2e8f0;
+              border-radius: 6px;
+              box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+              min-width: 200px;
+              max-height: 200px;
+              overflow-y: auto;
+              
+              .dropdown-item {
+                padding: 10px 12px;
+                cursor: pointer;
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+                font-size: 13px;
+                
+                &:hover {
+                  background: #f8fafc;
+                }
+                
+                .item-amount {
+                  color: #059669;
+                  font-weight: 500;
+                }
+              }
+              
+              .dropdown-empty {
+                padding: 10px 12px;
+                font-size: 13px;
+                color: #9ca3af;
+                text-align: center;
+              }
+            }
+          }
+        }
+      }
+      
+      .workload-info {
+        display: flex;
+        gap: 24px;
+        
+        .workload-item {
+          display: flex;
+          flex-direction: column;
+          gap: 4px;
+          
+          .workload-label {
+            font-size: 12px;
+            color: #64748b;
+          }
+          
+          .workload-value {
+            font-size: 14px;
+            font-weight: 600;
+            color: #1e293b;
+          }
+        }
+      }
+    }
+    
+    .assignment-actions {
+      flex-shrink: 0;
+      display: flex;
+      flex-direction: column;
+      gap: 8px;
+      
+      .action-btn {
+        width: 32px;
+        height: 32px;
+        border: none;
+        border-radius: 6px;
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        transition: all 0.2s ease;
+        
+        &.edit-btn {
+          background: #f0f9ff;
+          color: #0369a1;
+          
+          &:hover {
+            background: #e0f2fe;
+          }
+        }
+        
+        &.delete-btn {
+          background: #fef2f2;
+          color: #dc2626;
+          
+          &:hover {
+            background: #fee2e2;
+          }
+        }
+      }
+    }
+  }
+}
+
+.assignment-summary {
+  margin-top: 24px;
+  padding: 20px;
+  background: #f8fafc;
+  border-radius: 8px;
+  border: 1px solid #e2e8f0;
+  
+  .summary-stats {
+    display: flex;
+    gap: 32px;
+    margin-bottom: 16px;
+    
+    .stat-item {
+      display: flex;
+      flex-direction: column;
+      gap: 4px;
+      
+      .stat-label {
+        font-size: 13px;
+        color: #64748b;
+      }
+      
+      .stat-value {
+        font-size: 16px;
+        font-weight: 600;
+        color: #1e293b;
+        
+        &.complete {
+          color: #059669;
+        }
+      }
+    }
+  }
+  
+  .assignment-warning {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 12px 16px;
+    background: #fef3cd;
+    border: 1px solid #f6cc6d;
+    border-radius: 6px;
+    font-size: 14px;
+    color: #92400e;
+    
+    svg {
+      flex-shrink: 0;
+      color: #d97706;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .designer-assignment-container {
+    .assignment-header {
+      flex-direction: column;
+      gap: 16px;
+      align-items: stretch;
+      
+      .header-actions {
+        justify-content: flex-end;
+      }
+    }
+  }
+  
+  .assignment-list {
+    .assignment-item {
+      flex-direction: column;
+      gap: 16px;
+      
+      .designer-info {
+        width: 100%;
+        display: flex;
+        align-items: center;
+        gap: 12px;
+        
+        .designer-avatar {
+          margin-bottom: 0;
+        }
+      }
+      
+      .assignment-actions {
+        flex-direction: row;
+        justify-content: flex-end;
+      }
+    }
+  }
+  
+  .assignment-summary {
+    .summary-stats {
+      flex-direction: column;
+      gap: 16px;
+    }
+  }
+}

+ 350 - 0
src/app/pages/customer-service/consultation-order/components/designer-assignment/designer-assignment.component.ts

@@ -0,0 +1,350 @@
+import { Component, OnInit, Input, Output, EventEmitter, computed, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { DesignerCalendarComponent } from '../designer-calendar/designer-calendar.component';
+
+export interface Designer {
+  id: string;
+  name: string;
+  avatar?: string;
+  groupId: string;
+  groupName: string;
+  isLeader: boolean;
+  status: 'available' | 'busy' | 'stagnant' | 'overloaded';
+  currentProjects: number;
+  lastOrderDate?: string;
+  idleDays?: number;
+}
+
+export interface ProjectGroup {
+  id: string;
+  name: string;
+  leader: string;
+  leaderId: string;
+  members: Designer[];
+}
+
+export interface DesignerAssignment {
+  id: string;
+  designer: Designer;
+  quotationItemIds: string[];
+  assignedAmount: number;
+  workloadPercentage: number;
+  isCrossGroup: boolean;
+}
+
+export interface QuotationItem {
+  id: string;
+  roomType: string;
+  amount: number;
+  description?: string;
+}
+
+@Component({
+  selector: 'app-designer-assignment',
+  standalone: true,
+  imports: [CommonModule, FormsModule, DesignerCalendarComponent],
+  templateUrl: './designer-assignment.component.html',
+  styleUrls: ['./designer-assignment.component.scss']
+})
+export class DesignerAssignmentComponent {
+  @Input() quotationItems = signal<QuotationItem[]>([]);
+  @Input() selectedDesigners = signal<DesignerAssignment[]>([]);
+  @Output() designerChange = new EventEmitter<DesignerAssignment[]>();
+
+  designerAssignments = signal<DesignerAssignment[]>([]);
+  availableDesigners = signal<Designer[]>([]);
+  showDesignerCalendar = signal(false);
+  selectedDate = signal(new Date());
+  selectedProjectGroup = '';
+  allowCrossGroupCollaboration = false;
+  openDropdownId = signal<string | null>(null);
+
+  // 项目组数据
+  projectGroups: ProjectGroup[] = [
+    {
+      id: 'group1',
+      name: '家装设计组',
+      leader: '张设计师',
+      leaderId: 'designer1',
+      members: []
+    },
+    {
+      id: 'group2', 
+      name: '工装设计组',
+      leader: '李设计师',
+      leaderId: 'designer2',
+      members: []
+    },
+    {
+      id: 'group3',
+      name: '软装设计组', 
+      leader: '王设计师',
+      leaderId: 'designer3',
+      members: []
+    }
+  ];
+
+  // 计算总分配金额
+  totalAssignedAmount = computed(() => {
+    return this.designerAssignments().reduce((sum, assignment) => sum + assignment.assignedAmount, 0);
+  });
+
+  // 计算总报价金额
+  totalQuotationAmount = computed(() => {
+    return this.quotationItems().reduce((sum, item) => sum + item.amount, 0);
+  });
+
+  // 计算分配进度
+  assignmentProgress = computed(() => {
+    const total = this.totalQuotationAmount();
+    const assigned = this.totalAssignedAmount();
+    return total > 0 ? (assigned / total) * 100 : 0;
+  });
+
+  // 计算剩余金额
+  remainingAmount = computed(() => {
+    return this.totalQuotationAmount() - this.totalAssignedAmount();
+  });
+
+  constructor() {
+    this.loadAvailableDesigners();
+  }
+
+  ngOnInit() {
+    if (this.selectedDesigners().length > 0) {
+      this.designerAssignments.set(this.selectedDesigners());
+    }
+  }
+
+  private loadAvailableDesigners() {
+    // 模拟设计师数据
+    const mockDesigners: Designer[] = [
+      {
+        id: 'designer1',
+        name: '张设计师',
+        avatar: '/assets/avatars/designer1.jpg',
+        groupId: 'group1',
+        groupName: '家装设计组',
+        isLeader: true,
+        status: 'available',
+        currentProjects: 2,
+        lastOrderDate: '2024-01-15',
+        idleDays: 3
+      },
+      {
+        id: 'designer2',
+        name: '李设计师',
+        avatar: '/assets/avatars/designer2.jpg',
+        groupId: 'group2',
+        groupName: '工装设计组',
+        isLeader: true,
+        status: 'busy',
+        currentProjects: 4,
+        lastOrderDate: '2024-01-18'
+      },
+      {
+        id: 'designer3',
+        name: '王设计师',
+        avatar: '/assets/avatars/designer3.jpg',
+        groupId: 'group3',
+        groupName: '软装设计组',
+        isLeader: true,
+        status: 'available',
+        currentProjects: 1,
+        lastOrderDate: '2024-01-10',
+        idleDays: 8
+      },
+      {
+        id: 'designer4',
+        name: '赵设计师',
+        groupId: 'group1',
+        groupName: '家装设计组',
+        isLeader: false,
+        status: 'stagnant',
+        currentProjects: 0,
+        lastOrderDate: '2023-12-20',
+        idleDays: 28
+      },
+      {
+        id: 'designer5',
+        name: '刘设计师',
+        groupId: 'group2',
+        groupName: '工装设计组',
+        isLeader: false,
+        status: 'available',
+        currentProjects: 2,
+        lastOrderDate: '2024-01-16',
+        idleDays: 2
+      }
+    ];
+
+    this.availableDesigners.set(mockDesigners);
+  }
+
+  // 项目组变更
+  onProjectGroupChange(groupId: string) {
+    this.selectedProjectGroup = groupId;
+    
+    if (groupId) {
+      const group = this.projectGroups.find(g => g.id === groupId);
+      if (group) {
+        // 自动添加组长
+        const leader = this.availableDesigners().find(d => d.id === group.leaderId);
+        if (leader && !this.designerAssignments().find(a => a.designer.id === leader.id)) {
+          this.addDesignerToAssignment(leader);
+        }
+      }
+    }
+  }
+
+  // 跨组合作变更
+  onCrossGroupChange(allowed: boolean) {
+    this.allowCrossGroupCollaboration = allowed;
+  }
+
+  // 添加设计师分配
+  addDesignerAssignment() {
+    // 这里可以打开设计师选择对话框
+    // 暂时添加一个可用的设计师
+    const availableDesigner = this.availableDesigners().find(d => 
+      !this.designerAssignments().find(a => a.designer.id === d.id)
+    );
+    
+    if (availableDesigner) {
+      this.addDesignerToAssignment(availableDesigner);
+    }
+  }
+
+  private addDesignerToAssignment(designer: Designer) {
+    const newAssignment: DesignerAssignment = {
+      id: this.generateId(),
+      designer,
+      quotationItemIds: [],
+      assignedAmount: 0,
+      workloadPercentage: 0,
+      isCrossGroup: this.selectedProjectGroup ? designer.groupId !== this.selectedProjectGroup : false
+    };
+
+    const currentAssignments = this.designerAssignments();
+    this.designerAssignments.set([...currentAssignments, newAssignment]);
+    this.emitChange();
+  }
+
+  // 移除设计师分配
+  removeAssignment(assignmentId: string) {
+    const currentAssignments = this.designerAssignments();
+    const filteredAssignments = currentAssignments.filter(a => a.id !== assignmentId);
+    this.designerAssignments.set(filteredAssignments);
+    this.emitChange();
+  }
+
+  // 编辑分配
+  editAssignment(assignment: DesignerAssignment) {
+    // 这里可以打开编辑对话框
+    console.log('编辑分配:', assignment);
+  }
+
+  // 切换报价项目下拉菜单
+  toggleQuotationDropdown(assignmentId: string) {
+    const currentOpen = this.openDropdownId();
+    this.openDropdownId.set(currentOpen === assignmentId ? null : assignmentId);
+  }
+
+  // 获取未分配的报价项目
+  getUnassignedQuotationItems(currentAssignmentId: string): QuotationItem[] {
+    const allAssignedIds = this.designerAssignments()
+      .filter(a => a.id !== currentAssignmentId)
+      .flatMap(a => a.quotationItemIds);
+    
+    return this.quotationItems().filter(item => !allAssignedIds.includes(item.id));
+  }
+
+  // 添加报价项目到分配
+  addQuotationToAssignment(assignmentId: string, quotationItemId: string) {
+    const currentAssignments = this.designerAssignments();
+    const updatedAssignments = currentAssignments.map(assignment => {
+      if (assignment.id === assignmentId) {
+        const quotationItem = this.quotationItems().find(q => q.id === quotationItemId);
+        if (quotationItem) {
+          return {
+            ...assignment,
+            quotationItemIds: [...assignment.quotationItemIds, quotationItemId],
+            assignedAmount: assignment.assignedAmount + quotationItem.amount,
+            workloadPercentage: this.calculateWorkloadPercentage(assignment.assignedAmount + quotationItem.amount)
+          };
+        }
+      }
+      return assignment;
+    });
+
+    this.designerAssignments.set(updatedAssignments);
+    this.openDropdownId.set(null);
+    this.emitChange();
+  }
+
+  // 从分配中移除报价项目
+  removeQuotationFromAssignment(assignmentId: string, quotationItemId: string) {
+    const currentAssignments = this.designerAssignments();
+    const updatedAssignments = currentAssignments.map(assignment => {
+      if (assignment.id === assignmentId) {
+        const quotationItem = this.quotationItems().find(q => q.id === quotationItemId);
+        if (quotationItem) {
+          return {
+            ...assignment,
+            quotationItemIds: assignment.quotationItemIds.filter(id => id !== quotationItemId),
+            assignedAmount: assignment.assignedAmount - quotationItem.amount,
+            workloadPercentage: this.calculateWorkloadPercentage(assignment.assignedAmount - quotationItem.amount)
+          };
+        }
+      }
+      return assignment;
+    });
+
+    this.designerAssignments.set(updatedAssignments);
+    this.emitChange();
+  }
+
+  // 获取报价项目名称
+  getQuotationItemName(quotationItemId: string): string {
+    const item = this.quotationItems().find(q => q.id === quotationItemId);
+    return item ? item.roomType : '未知项目';
+  }
+
+  // 计算工作量占比
+  private calculateWorkloadPercentage(assignedAmount: number): number {
+    const total = this.totalQuotationAmount();
+    return total > 0 ? (assignedAmount / total) * 100 : 0;
+  }
+
+  // 获取设计师状态文本
+  getDesignerStatusText(status: Designer['status']): string {
+    const statusMap = {
+      'available': '空闲可接单',
+      'busy': '忙碌中',
+      'stagnant': '停滞期',
+      'overloaded': '满载'
+    };
+    return statusMap[status] || '未知状态';
+  }
+
+  // 设计师选择事件
+  onDesignerSelect(designer: Designer) {
+    if (!this.designerAssignments().find(a => a.designer.id === designer.id)) {
+      this.addDesignerToAssignment(designer);
+    }
+  }
+
+  // 日期变更事件
+  onDateChange(date: Date) {
+    this.selectedDate.set(date);
+  }
+
+  private emitChange() {
+    this.designerChange.emit(this.designerAssignments());
+  }
+
+  private generateId(): string {
+    return 'assignment_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
+  }
+}

+ 317 - 0
src/app/pages/customer-service/consultation-order/components/designer-calendar/designer-calendar.component.html

@@ -0,0 +1,317 @@
+<div class="designer-calendar-container">
+  <div class="calendar-header">
+    <div class="header-left">
+      <h4>设计师日历</h4>
+      <p>查看设计师工作负载和空闲时间</p>
+    </div>
+    <div class="header-actions">
+      <button class="btn btn-sm btn-secondary" (click)="toggleView()">
+        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+          @if (viewMode === 'calendar') {
+            <rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
+            <line x1="16" y1="2" x2="16" y2="6"/>
+            <line x1="8" y1="2" x2="8" y2="6"/>
+            <line x1="3" y1="10" x2="21" y2="10"/>
+          } @else {
+            <line x1="8" y1="6" x2="21" y2="6"/>
+            <line x1="8" y1="12" x2="21" y2="12"/>
+            <line x1="8" y1="18" x2="21" y2="18"/>
+            <line x1="3" y1="6" x2="3.01" y2="6"/>
+            <line x1="3" y1="12" x2="3.01" y2="12"/>
+            <line x1="3" y1="18" x2="3.01" y2="18"/>
+          }
+        </svg>
+        {{ viewMode === 'calendar' ? '列表视图' : '日历视图' }}
+      </button>
+      <button class="btn btn-sm btn-secondary" (click)="refreshData()">
+        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+          <polyline points="23 4 23 10 17 10"/>
+          <polyline points="1 20 1 14 7 14"/>
+          <path d="m3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
+        </svg>
+        刷新
+      </button>
+    </div>
+  </div>
+
+  <!-- 筛选器 -->
+  <div class="calendar-filters">
+    <div class="filter-group">
+      <label class="filter-label">项目组</label>
+      <select class="filter-select" [(ngModel)]="selectedGroup" (ngModelChange)="onGroupChange()">
+        <option value="">全部项目组</option>
+        <option *ngFor="let group of projectGroups" [value]="group.id">{{ group.name }}</option>
+      </select>
+    </div>
+    <div class="filter-group">
+      <label class="filter-label">设计师状态</label>
+      <select class="filter-select" [(ngModel)]="selectedStatus" (ngModelChange)="onStatusChange()">
+        <option value="">全部状态</option>
+        <option value="available">空闲</option>
+        <option value="busy">忙碌</option>
+        <option value="overloaded">满载</option>
+        <option value="stagnant">停滞期</option>
+      </select>
+    </div>
+    <div class="filter-group">
+      <label class="filter-label">时间范围</label>
+      <select class="filter-select" [(ngModel)]="timeRange" (ngModelChange)="onTimeRangeChange()">
+        <option value="week">本周</option>
+        <option value="month">本月</option>
+        <option value="quarter">本季度</option>
+      </select>
+    </div>
+    <div class="filter-group">
+      <label class="checkbox-label">
+        <input type="checkbox" [(ngModel)]="hideStagnantProjects" (ngModelChange)="onFilterChange()">
+        <span class="checkmark"></span>
+        过滤停滞期项目
+      </label>
+    </div>
+  </div>
+
+  <!-- 日历视图 -->
+  @if (viewMode === 'calendar') {
+    <div class="calendar-view">
+      <div class="calendar-navigation">
+        <button class="nav-btn" (click)="previousPeriod()">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <polyline points="15 18 9 12 15 6"/>
+          </svg>
+        </button>
+        <h3 class="current-period">{{ getCurrentPeriodLabel() }}</h3>
+        <button class="nav-btn" (click)="nextPeriod()">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <polyline points="9 18 15 12 9 6"/>
+          </svg>
+        </button>
+      </div>
+
+      <div class="calendar-grid">
+        <div class="calendar-header-row">
+          <div class="designer-column">设计师</div>
+          <div class="date-column" *ngFor="let date of calendarDates">
+            <div class="date-label">{{ formatDate(date, 'MM/dd') }}</div>
+            <div class="weekday-label">{{ formatDate(date, 'EEE') }}</div>
+          </div>
+        </div>
+
+        <div class="calendar-row" *ngFor="let designer of filteredDesigners">
+          <div class="designer-cell">
+            <div class="designer-info">
+              <div class="designer-avatar">
+                <img [src]="designer.avatar" [alt]="designer.name">
+              </div>
+              <div class="designer-details">
+                <div class="designer-name">{{ designer.name }}</div>
+                <div class="designer-group">{{ designer.groupName }}</div>
+                <div class="designer-stats">
+                  <span class="stat-item">{{ designer.currentProjects }}个项目</span>
+                  <span class="stat-item" [class]="getIdleDaysClass(designer.idleDays ?? 0)">
+                      {{ designer.idleDays ?? 0 }}天未接单
+                  </span>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <div class="date-cell" *ngFor="let date of calendarDates">
+            <div class="date-content" [class]="getDateCellClass(designer, date)">
+              @if (getDateEvents(designer, date).length > 0) {
+                <div class="event-indicators">
+                  <div class="event-indicator" 
+                       *ngFor="let event of getDateEvents(designer, date)" 
+                       [class]="getEventClass(event)"
+                       [title]="event.title">
+                    {{ event.type === 'review' ? '对' : event.type === 'project' ? '项' : '休' }}
+                  </div>
+                </div>
+              } @else {
+                <div class="empty-indicator"></div>
+              }
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  }
+
+  <!-- 列表视图 -->
+  @if (viewMode === 'list') {
+    <div class="list-view">
+      @if (filteredDesigners.length === 0) {
+        <div class="empty-state">
+          <div class="empty-icon">
+            <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+              <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
+              <circle cx="9" cy="7" r="4"/>
+              <path d="m22 2-5 10-5-5 10-5z"/>
+            </svg>
+          </div>
+          <p>暂无符合条件的设计师</p>
+          <p class="empty-hint">请调整筛选条件或添加设计师</p>
+        </div>
+      } @else {
+        <div class="designer-list">
+          <div class="designer-card" *ngFor="let designer of filteredDesigners">
+            <div class="card-header">
+              <div class="designer-info">
+                <div class="designer-avatar">
+                  <img [src]="designer.avatar" [alt]="designer.name">
+                </div>
+                <div class="designer-details">
+                  <div class="designer-name">{{ designer.name }}</div>
+                  <div class="designer-meta">
+                    <span class="designer-group">{{ designer.groupName }}</span>
+                    @if (designer.isLeader) {
+                      <span class="leader-badge">组长</span>
+                    }
+                    <span class="status-indicator" [class]="designer.status"></span>
+                    <span class="status-text">{{ getStatusText(designer.status) }}</span>
+                  </div>
+                </div>
+              </div>
+              <div class="designer-actions">
+                <button class="action-btn" (click)="viewDesignerDetail(designer)" title="查看详情">
+                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
+                    <circle cx="12" cy="12" r="3"/>
+                  </svg>
+                </button>
+                <button class="action-btn" (click)="assignToDesigner(designer)" title="分配任务">
+                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <circle cx="12" cy="12" r="10"/>
+                    <line x1="12" y1="8" x2="12" y2="16"/>
+                    <line x1="8" y1="12" x2="16" y2="12"/>
+                  </svg>
+                </button>
+              </div>
+            </div>
+
+            <div class="card-content">
+              <div class="workload-section">
+                <h5>工作负载</h5>
+                <div class="workload-stats">
+                  <div class="stat-item">
+                    <span class="stat-label">当前项目</span>
+                    <span class="stat-value">{{ designer.currentProjects }}个</span>
+                  </div>
+                  <div class="stat-item">
+                    <span class="stat-label">本月完成</span>
+                    <span class="stat-value">{{ designer.completedThisMonth }}个</span>
+                  </div>
+                  <div class="stat-item">
+                    <span class="stat-label">平均周期</span>
+                    <span class="stat-value">{{ designer.averageCycle }}天</span>
+                  </div>
+                  <div class="stat-item">
+                    <span class="stat-label">空闲天数</span>
+                    <span class="stat-value" [class]="getIdleDaysClass(designer.idleDays ?? 0)">
+                  {{ designer.idleDays ?? 0 }}天
+                    </span>
+                  </div>
+                </div>
+              </div>
+
+              <div class="schedule-section">
+                <h5>近期安排</h5>
+                @if ((designer.upcomingEvents || []).length === 0) {
+                  <p class="no-events">暂无安排</p>
+                } @else {
+                  <div class="event-list">
+                    <div class="event-item" *ngFor="let event of (designer.upcomingEvents || []).slice(0, 3)">
+                      <div class="event-date">{{ formatDate(event.date, 'MM/dd') }}</div>
+                      <div class="event-content">
+                        <div class="event-title">{{ event.title }}</div>
+                        <div class="event-type" [class]="getEventClass(event)">
+                          {{ getEventTypeText(event.type) }}
+                        </div>
+                      </div>
+                    </div>
+                    @if ((designer.upcomingEvents || []).length > 3) {
+                      <div class="more-events">
+                        还有 {{ (designer.upcomingEvents || []).length - 3 }} 个安排...
+                      </div>
+                    }
+                  </div>
+                }
+              </div>
+
+              <div class="availability-section">
+                <h5>可用性分析</h5>
+                <div class="availability-info">
+                  <div class="availability-item">
+                    <span class="availability-label">下次可接单</span>
+                    <span class="availability-value">{{ getNextAvailableDate(designer) }}</span>
+                  </div>
+                  <div class="availability-item">
+                    <span class="availability-label">推荐分配</span>
+                    <span class="availability-value" [class]="getRecommendationClass(designer)">
+                      {{ getRecommendationText(designer) }}
+                    </span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      }
+    </div>
+  }
+
+  <!-- 统计摘要 -->
+  <div class="calendar-summary">
+    <div class="summary-stats">
+      <div class="stat-card">
+        <div class="stat-icon available">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <circle cx="12" cy="12" r="10"/>
+            <polyline points="12 6 12 12 16 14"/>
+          </svg>
+        </div>
+        <div class="stat-content">
+          <div class="stat-value">{{ getDesignerCountByStatus('available') }}</div>
+          <div class="stat-label">空闲设计师</div>
+        </div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-icon busy">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <circle cx="12" cy="12" r="10"/>
+            <line x1="12" y1="8" x2="12" y2="16"/>
+            <line x1="8" y1="12" x2="16" y2="12"/>
+          </svg>
+        </div>
+        <div class="stat-content">
+          <div class="stat-value">{{ getDesignerCountByStatus('busy') }}</div>
+          <div class="stat-label">忙碌设计师</div>
+        </div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-icon overloaded">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <circle cx="12" cy="12" r="10"/>
+            <line x1="15" y1="9" x2="9" y2="15"/>
+            <line x1="9" y1="9" x2="15" y2="15"/>
+          </svg>
+        </div>
+        <div class="stat-content">
+          <div class="stat-value">{{ getDesignerCountByStatus('overloaded') }}</div>
+          <div class="stat-label">满载设计师</div>
+        </div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-icon stagnant">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <circle cx="12" cy="12" r="10"/>
+            <path d="M8 12l2 2 4-4"/>
+          </svg>
+        </div>
+        <div class="stat-content">
+          <div class="stat-value">{{ getStagnantProjectsCount() }}</div>
+          <div class="stat-label">停滞期项目</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 843 - 0
src/app/pages/customer-service/consultation-order/components/designer-calendar/designer-calendar.component.scss

@@ -0,0 +1,843 @@
+.designer-calendar-container {
+  .calendar-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    margin-bottom: 24px;
+    
+    .header-left {
+      h4 {
+        font-size: 16px;
+        font-weight: 600;
+        color: #1a1a1a;
+        margin: 0 0 4px 0;
+      }
+      
+      p {
+        font-size: 13px;
+        color: #666;
+        margin: 0;
+      }
+    }
+    
+    .header-actions {
+      display: flex;
+      gap: 12px;
+      
+      .btn {
+        padding: 8px 16px;
+        font-size: 13px;
+        border-radius: 6px;
+        border: none;
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        transition: all 0.2s ease;
+        
+        &.btn-secondary {
+          background: #f8fafc;
+          color: #475569;
+          border: 1px solid #e2e8f0;
+          
+          &:hover {
+            background: #f1f5f9;
+            border-color: #cbd5e1;
+          }
+        }
+      }
+    }
+  }
+}
+
+.calendar-filters {
+  display: flex;
+  gap: 20px;
+  align-items: flex-end;
+  margin-bottom: 24px;
+  padding: 20px;
+  background: #f8fafc;
+  border-radius: 8px;
+  border: 1px solid #e2e8f0;
+  flex-wrap: wrap;
+  
+  .filter-group {
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+    
+    .filter-label {
+      font-size: 13px;
+      font-weight: 500;
+      color: #374151;
+    }
+    
+    .filter-select {
+      padding: 8px 12px;
+      border: 1px solid #d1d5db;
+      border-radius: 6px;
+      font-size: 14px;
+      background: white;
+      min-width: 120px;
+      
+      &:focus {
+        outline: none;
+        border-color: #4f46e5;
+        box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
+      }
+    }
+    
+    .checkbox-label {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      cursor: pointer;
+      font-size: 14px;
+      color: #374151;
+      margin-top: 20px;
+      
+      input[type="checkbox"] {
+        display: none;
+      }
+      
+      .checkmark {
+        width: 18px;
+        height: 18px;
+        border: 2px solid #d1d5db;
+        border-radius: 4px;
+        position: relative;
+        transition: all 0.2s ease;
+        
+        &::after {
+          content: '';
+          position: absolute;
+          left: 5px;
+          top: 2px;
+          width: 4px;
+          height: 8px;
+          border: solid white;
+          border-width: 0 2px 2px 0;
+          transform: rotate(45deg);
+          opacity: 0;
+          transition: opacity 0.2s ease;
+        }
+      }
+      
+      input:checked + .checkmark {
+        background: #4f46e5;
+        border-color: #4f46e5;
+        
+        &::after {
+          opacity: 1;
+        }
+      }
+    }
+  }
+}
+
+// 日历视图样式
+.calendar-view {
+  .calendar-navigation {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 20px;
+    margin-bottom: 20px;
+    
+    .nav-btn {
+      width: 36px;
+      height: 36px;
+      border: 1px solid #e2e8f0;
+      background: white;
+      border-radius: 6px;
+      cursor: pointer;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      transition: all 0.2s ease;
+      
+      &:hover {
+        border-color: #4f46e5;
+        color: #4f46e5;
+      }
+    }
+    
+    .current-period {
+      font-size: 18px;
+      font-weight: 600;
+      color: #1e293b;
+      margin: 0;
+      min-width: 200px;
+      text-align: center;
+    }
+  }
+  
+  .calendar-grid {
+    border: 1px solid #e2e8f0;
+    border-radius: 8px;
+    overflow: hidden;
+    background: white;
+    
+    .calendar-header-row {
+      display: flex;
+      background: #f8fafc;
+      border-bottom: 1px solid #e2e8f0;
+      
+      .designer-column {
+        width: 200px;
+        padding: 12px 16px;
+        font-weight: 600;
+        color: #374151;
+        border-right: 1px solid #e2e8f0;
+        display: flex;
+        align-items: center;
+      }
+      
+      .date-column {
+        flex: 1;
+        min-width: 80px;
+        padding: 12px 8px;
+        text-align: center;
+        border-right: 1px solid #e2e8f0;
+        
+        &:last-child {
+          border-right: none;
+        }
+        
+        .date-label {
+          font-size: 14px;
+          font-weight: 600;
+          color: #1e293b;
+          margin-bottom: 2px;
+        }
+        
+        .weekday-label {
+          font-size: 12px;
+          color: #64748b;
+        }
+      }
+    }
+    
+    .calendar-row {
+      display: flex;
+      border-bottom: 1px solid #e2e8f0;
+      
+      &:last-child {
+        border-bottom: none;
+      }
+      
+      .designer-cell {
+        width: 200px;
+        padding: 16px;
+        border-right: 1px solid #e2e8f0;
+        
+        .designer-info {
+          display: flex;
+          align-items: center;
+          gap: 12px;
+          
+          .designer-avatar {
+            width: 40px;
+            height: 40px;
+            border-radius: 50%;
+            overflow: hidden;
+            flex-shrink: 0;
+            
+            img {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+            }
+          }
+          
+          .designer-details {
+            flex: 1;
+            
+            .designer-name {
+              font-size: 14px;
+              font-weight: 600;
+              color: #1e293b;
+              margin-bottom: 4px;
+            }
+            
+            .designer-group {
+              font-size: 12px;
+              color: #64748b;
+              margin-bottom: 6px;
+            }
+            
+            .designer-stats {
+              display: flex;
+              flex-direction: column;
+              gap: 2px;
+              
+              .stat-item {
+                font-size: 11px;
+                color: #64748b;
+                
+                &.critical {
+                  color: #dc2626;
+                  font-weight: 500;
+                }
+                
+                &.warning {
+                  color: #d97706;
+                  font-weight: 500;
+                }
+              }
+            }
+          }
+        }
+      }
+      
+      .date-cell {
+        flex: 1;
+        min-width: 80px;
+        padding: 16px 8px;
+        border-right: 1px solid #e2e8f0;
+        
+        &:last-child {
+          border-right: none;
+        }
+        
+        .date-content {
+          height: 40px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          border-radius: 4px;
+          
+          &.available-day {
+            background: #f0fdf4;
+            border: 1px dashed #bbf7d0;
+          }
+          
+          &.has-events {
+            background: #eff6ff;
+            border: 1px solid #dbeafe;
+          }
+          
+          &.review-day {
+            background: #fef3c7;
+            border: 1px solid #fcd34d;
+          }
+          
+          &.project-day {
+            background: #e0e7ff;
+            border: 1px solid #c7d2fe;
+          }
+          
+          &.vacation-day {
+            background: #f3f4f6;
+            border: 1px solid #d1d5db;
+          }
+          
+          &.today {
+            box-shadow: 0 0 0 2px #4f46e5;
+          }
+          
+          .event-indicators {
+            display: flex;
+            gap: 4px;
+            flex-wrap: wrap;
+            
+            .event-indicator {
+              width: 20px;
+              height: 20px;
+              border-radius: 4px;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              font-size: 10px;
+              font-weight: 600;
+              color: white;
+              
+              &.event-project {
+                background: #4f46e5;
+              }
+              
+              &.event-review {
+                background: #f59e0b;
+              }
+              
+              &.event-vacation {
+                background: #6b7280;
+              }
+            }
+          }
+          
+          .empty-indicator {
+            width: 20px;
+            height: 20px;
+            border-radius: 50%;
+            background: #e5e7eb;
+          }
+        }
+      }
+    }
+  }
+}
+
+// 列表视图样式
+.list-view {
+  .empty-state {
+    text-align: center;
+    padding: 48px 24px;
+    color: #6b7280;
+    
+    .empty-icon {
+      margin-bottom: 16px;
+      
+      svg {
+        color: #d1d5db;
+      }
+    }
+    
+    p {
+      margin: 0 0 8px 0;
+      font-size: 14px;
+      
+      &.empty-hint {
+        font-size: 13px;
+        color: #9ca3af;
+      }
+    }
+  }
+  
+  .designer-list {
+    display: grid;
+    gap: 20px;
+    
+    .designer-card {
+      background: white;
+      border: 1px solid #e2e8f0;
+      border-radius: 8px;
+      overflow: hidden;
+      transition: all 0.2s ease;
+      
+      &:hover {
+        border-color: #cbd5e1;
+        box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
+      }
+      
+      .card-header {
+        padding: 20px;
+        border-bottom: 1px solid #f1f5f9;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        
+        .designer-info {
+          display: flex;
+          align-items: center;
+          gap: 16px;
+          
+          .designer-avatar {
+            width: 56px;
+            height: 56px;
+            border-radius: 50%;
+            overflow: hidden;
+            
+            img {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+            }
+          }
+          
+          .designer-details {
+            .designer-name {
+              font-size: 16px;
+              font-weight: 600;
+              color: #1e293b;
+              margin-bottom: 6px;
+            }
+            
+            .designer-meta {
+              display: flex;
+              align-items: center;
+              gap: 8px;
+              margin-bottom: 8px;
+              
+              .designer-group {
+                font-size: 12px;
+                color: #64748b;
+                background: #f1f5f9;
+                padding: 2px 6px;
+                border-radius: 4px;
+              }
+              
+              .leader-badge {
+                font-size: 12px;
+                color: #dc2626;
+                background: #fef2f2;
+                padding: 2px 6px;
+                border-radius: 4px;
+                border: 1px solid #fecaca;
+              }
+              
+              .status-indicator {
+                width: 8px;
+                height: 8px;
+                border-radius: 50%;
+                
+                &.available {
+                  background: #10b981;
+                }
+                
+                &.busy {
+                  background: #f59e0b;
+                }
+                
+                &.overloaded {
+                  background: #8b5cf6;
+                }
+                
+                &.stagnant {
+                  background: #ef4444;
+                }
+              }
+              
+              .status-text {
+                font-size: 12px;
+                color: #64748b;
+              }
+            }
+          }
+        }
+        
+        .designer-actions {
+          display: flex;
+          gap: 8px;
+          
+          .action-btn {
+            width: 32px;
+            height: 32px;
+            border: 1px solid #e2e8f0;
+            background: white;
+            border-radius: 6px;
+            cursor: pointer;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            color: #64748b;
+            transition: all 0.2s ease;
+            
+            &:hover {
+              border-color: #4f46e5;
+              color: #4f46e5;
+            }
+          }
+        }
+      }
+      
+      .card-content {
+        padding: 20px;
+        display: grid;
+        gap: 20px;
+        
+        .workload-section,
+        .schedule-section,
+        .availability-section {
+          h5 {
+            font-size: 14px;
+            font-weight: 600;
+            color: #374151;
+            margin: 0 0 12px 0;
+          }
+        }
+        
+        .workload-stats {
+          display: grid;
+          grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
+          gap: 16px;
+          
+          .stat-item {
+            display: flex;
+            flex-direction: column;
+            gap: 4px;
+            
+            .stat-label {
+              font-size: 12px;
+              color: #64748b;
+            }
+            
+            .stat-value {
+              font-size: 16px;
+              font-weight: 600;
+              color: #1e293b;
+              
+              &.critical {
+                color: #dc2626;
+              }
+              
+              &.warning {
+                color: #d97706;
+              }
+            }
+          }
+        }
+        
+        .no-events {
+          font-size: 13px;
+          color: #9ca3af;
+          font-style: italic;
+          margin: 0;
+        }
+        
+        .event-list {
+          display: flex;
+          flex-direction: column;
+          gap: 8px;
+          
+          .event-item {
+            display: flex;
+            align-items: center;
+            gap: 12px;
+            padding: 8px 12px;
+            background: #f8fafc;
+            border-radius: 6px;
+            
+            .event-date {
+              font-size: 12px;
+              color: #64748b;
+              font-weight: 500;
+              min-width: 40px;
+            }
+            
+            .event-content {
+              flex: 1;
+              
+              .event-title {
+                font-size: 13px;
+                color: #1e293b;
+                margin-bottom: 2px;
+              }
+              
+              .event-type {
+                font-size: 11px;
+                padding: 2px 6px;
+                border-radius: 4px;
+                
+                &.event-project {
+                  background: #e0e7ff;
+                  color: #4338ca;
+                }
+                
+                &.event-review {
+                  background: #fef3c7;
+                  color: #92400e;
+                }
+                
+                &.event-vacation {
+                  background: #f3f4f6;
+                  color: #6b7280;
+                }
+              }
+            }
+          }
+          
+          .more-events {
+            font-size: 12px;
+            color: #64748b;
+            text-align: center;
+            padding: 4px;
+          }
+        }
+        
+        .availability-info {
+          display: flex;
+          gap: 24px;
+          
+          .availability-item {
+            display: flex;
+            flex-direction: column;
+            gap: 4px;
+            
+            .availability-label {
+              font-size: 12px;
+              color: #64748b;
+            }
+            
+            .availability-value {
+              font-size: 14px;
+              font-weight: 600;
+              
+              &.high-recommend {
+                color: #059669;
+              }
+              
+              &.recommend {
+                color: #0369a1;
+              }
+              
+              &.caution {
+                color: #d97706;
+              }
+              
+              &.not-recommend {
+                color: #dc2626;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+// 统计摘要样式
+.calendar-summary {
+  margin-top: 24px;
+  
+  .summary-stats {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+    gap: 16px;
+    
+    .stat-card {
+      background: white;
+      border: 1px solid #e2e8f0;
+      border-radius: 8px;
+      padding: 20px;
+      display: flex;
+      align-items: center;
+      gap: 16px;
+      
+      .stat-icon {
+        width: 48px;
+        height: 48px;
+        border-radius: 8px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        
+        &.available {
+          background: #f0fdf4;
+          color: #059669;
+        }
+        
+        &.busy {
+          background: #fef3c7;
+          color: #d97706;
+        }
+        
+        &.overloaded {
+          background: #f3f4f6;
+          color: #8b5cf6;
+        }
+        
+        &.stagnant {
+          background: #fef2f2;
+          color: #dc2626;
+        }
+      }
+      
+      .stat-content {
+        flex: 1;
+        
+        .stat-value {
+          font-size: 24px;
+          font-weight: 700;
+          color: #1e293b;
+          margin-bottom: 4px;
+        }
+        
+        .stat-label {
+          font-size: 13px;
+          color: #64748b;
+        }
+      }
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 1024px) {
+  .calendar-view {
+    .calendar-grid {
+      .designer-cell {
+        width: 150px;
+        
+        .designer-info {
+          flex-direction: column;
+          align-items: flex-start;
+          gap: 8px;
+          
+          .designer-avatar {
+            width: 32px;
+            height: 32px;
+          }
+        }
+      }
+      
+      .date-column,
+      .date-cell {
+        min-width: 60px;
+      }
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .designer-calendar-container {
+    .calendar-header {
+      flex-direction: column;
+      gap: 16px;
+      align-items: stretch;
+      
+      .header-actions {
+        justify-content: flex-end;
+      }
+    }
+  }
+  
+  .calendar-filters {
+    flex-direction: column;
+    gap: 16px;
+    
+    .filter-group {
+      .filter-select {
+        min-width: 100%;
+      }
+    }
+  }
+  
+  .calendar-view {
+    .calendar-grid {
+      overflow-x: auto;
+      
+      .calendar-header-row,
+      .calendar-row {
+        min-width: 800px;
+      }
+    }
+  }
+  
+  .list-view {
+    .designer-list {
+      .designer-card {
+        .card-header {
+          flex-direction: column;
+          gap: 16px;
+          align-items: flex-start;
+          
+          .designer-actions {
+            align-self: flex-end;
+          }
+        }
+        
+        .card-content {
+          .workload-stats {
+            grid-template-columns: repeat(2, 1fr);
+          }
+          
+          .availability-info {
+            flex-direction: column;
+            gap: 12px;
+          }
+        }
+      }
+    }
+  }
+}

+ 419 - 0
src/app/pages/customer-service/consultation-order/components/designer-calendar/designer-calendar.component.ts

@@ -0,0 +1,419 @@
+import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+export interface Designer {
+  id: string;
+  name: string;
+  avatar?: string;
+  groupId: string;
+  groupName: string;
+  isLeader: boolean;
+  status: 'available' | 'busy' | 'stagnant' | 'overloaded';
+  currentProjects: number;
+  lastOrderDate?: string;
+  idleDays?: number;
+  completedThisMonth?: number;
+  averageCycle?: number;
+  upcomingEvents?: DesignerEvent[];
+  workload?: number; // 0-100
+  nextAvailableDate?: Date;
+}
+
+export interface DesignerEvent {
+  id: string;
+  date: Date;
+  title: string;
+  type: 'project' | 'review' | 'vacation';
+  projectId?: string;
+  duration: number; // 小时
+}
+
+export interface ProjectGroup {
+  id: string;
+  name: string;
+  leaderId: string;
+  memberIds: string[];
+}
+
+@Component({
+  selector: 'app-designer-calendar',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './designer-calendar.component.html',
+  styleUrls: ['./designer-calendar.component.scss']
+})
+export class DesignerCalendarComponent implements OnInit {
+  @Input() designers: Designer[] = [];
+  @Input() selectedDate: Date = new Date();
+  @Input() projectGroups: ProjectGroup[] = [];
+  @Output() designerSelected = new EventEmitter<Designer>();
+  @Output() assignmentRequested = new EventEmitter<Designer>();
+
+  // 视图模式
+  viewMode: 'calendar' | 'list' = 'calendar';
+  
+  // 筛选条件
+  selectedGroup: string = '';
+  selectedStatus: string = '';
+  timeRange: 'week' | 'month' | 'quarter' = 'week';
+  hideStagnantProjects: boolean = false;
+  
+  // 日历数据
+  currentDate: Date = new Date();
+  calendarDates: Date[] = [];
+  filteredDesigners: Designer[] = [];
+
+  ngOnInit() {
+    this.initializeMockData();
+    this.generateCalendarDates();
+    this.applyFilters();
+  }
+
+  private initializeMockData() {
+    if (this.designers.length === 0) {
+      this.designers = [
+        {
+          id: '1',
+          name: '张设计师',
+          avatar: '/assets/avatars/designer1.jpg',
+          groupId: '1',
+          groupName: '家装组',
+          isLeader: true,
+          status: 'available',
+          currentProjects: 2,
+          completedThisMonth: 5,
+          averageCycle: 15,
+          idleDays: 3,
+          workload: 60,
+          nextAvailableDate: new Date(),
+          upcomingEvents: [
+            {
+              id: '1',
+              date: new Date(Date.now() + 86400000),
+              title: '客厅设计方案',
+              type: 'project',
+              projectId: 'p1',
+              duration: 4
+            }
+          ]
+        },
+        {
+          id: '2',
+          name: '李设计师',
+          avatar: '/assets/avatars/designer2.jpg',
+          groupId: '2',
+          groupName: '工装组',
+          isLeader: false,
+          status: 'busy',
+          currentProjects: 4,
+          completedThisMonth: 3,
+          averageCycle: 18,
+          idleDays: 0,
+          workload: 85,
+          nextAvailableDate: new Date(Date.now() + 5 * 86400000),
+          upcomingEvents: [
+            {
+              id: '2',
+              date: new Date(),
+              title: '办公室设计对图',
+              type: 'review',
+              projectId: 'p2',
+              duration: 2
+            }
+          ]
+        },
+        {
+          id: '3',
+          name: '王设计师',
+          avatar: '/assets/avatars/designer3.jpg',
+          groupId: '1',
+          groupName: '家装组',
+          isLeader: false,
+          status: 'stagnant',
+          currentProjects: 1,
+          completedThisMonth: 1,
+          averageCycle: 25,
+          idleDays: 15,
+          workload: 20,
+          nextAvailableDate: new Date(),
+          upcomingEvents: []
+        }
+      ];
+    }
+
+    if (this.projectGroups.length === 0) {
+      this.projectGroups = [
+        { id: '1', name: '家装组', leaderId: '1', memberIds: ['1', '3'] },
+        { id: '2', name: '工装组', leaderId: '2', memberIds: ['2'] }
+      ];
+    }
+  }
+
+  private generateCalendarDates() {
+    this.calendarDates = [];
+    const startDate = new Date(this.currentDate);
+    
+    if (this.timeRange === 'week') {
+      // 获取本周的日期
+      const dayOfWeek = startDate.getDay();
+      startDate.setDate(startDate.getDate() - dayOfWeek);
+      
+      for (let i = 0; i < 7; i++) {
+        const date = new Date(startDate);
+        date.setDate(startDate.getDate() + i);
+        this.calendarDates.push(date);
+      }
+    } else if (this.timeRange === 'month') {
+      // 获取本月的日期
+      startDate.setDate(1);
+      const daysInMonth = new Date(startDate.getFullYear(), startDate.getMonth() + 1, 0).getDate();
+      
+      for (let i = 0; i < daysInMonth; i++) {
+        const date = new Date(startDate);
+        date.setDate(i + 1);
+        this.calendarDates.push(date);
+      }
+    } else {
+      // 获取本季度的周数
+      const quarterStart = new Date(startDate.getFullYear(), Math.floor(startDate.getMonth() / 3) * 3, 1);
+      const quarterEnd = new Date(startDate.getFullYear(), Math.floor(startDate.getMonth() / 3) * 3 + 3, 0);
+      
+      const current = new Date(quarterStart);
+      while (current <= quarterEnd) {
+        this.calendarDates.push(new Date(current));
+        current.setDate(current.getDate() + 7); // 按周显示
+      }
+    }
+  }
+
+  private applyFilters() {
+    this.filteredDesigners = this.designers.filter(designer => {
+      // 项目组筛选
+      if (this.selectedGroup && designer.groupId !== this.selectedGroup) {
+        return false;
+      }
+      
+      // 状态筛选
+      if (this.selectedStatus && designer.status !== this.selectedStatus) {
+        return false;
+      }
+      
+      // 停滞期项目筛选
+      if (this.hideStagnantProjects && designer.status === 'stagnant') {
+        return false;
+      }
+      
+      return true;
+    });
+  }
+
+  private getGroupName(groupId: string): string {
+    const group = this.projectGroups.find(g => g.id === groupId);
+    return group ? group.name : '';
+  }
+
+  // 视图切换
+  toggleView() {
+    this.viewMode = this.viewMode === 'calendar' ? 'list' : 'calendar';
+  }
+
+  // 筛选器事件
+  onGroupChange() {
+    this.applyFilters();
+  }
+
+  onStatusChange() {
+    this.applyFilters();
+  }
+
+  onTimeRangeChange() {
+    this.generateCalendarDates();
+    this.applyFilters();
+  }
+
+  onFilterChange() {
+    this.applyFilters();
+  }
+
+  // 日历导航
+  previousPeriod() {
+    if (this.timeRange === 'week') {
+      this.currentDate.setDate(this.currentDate.getDate() - 7);
+    } else if (this.timeRange === 'month') {
+      this.currentDate.setMonth(this.currentDate.getMonth() - 1);
+    } else {
+      this.currentDate.setMonth(this.currentDate.getMonth() - 3);
+    }
+    this.generateCalendarDates();
+  }
+
+  nextPeriod() {
+    if (this.timeRange === 'week') {
+      this.currentDate.setDate(this.currentDate.getDate() + 7);
+    } else if (this.timeRange === 'month') {
+      this.currentDate.setMonth(this.currentDate.getMonth() + 1);
+    } else {
+      this.currentDate.setMonth(this.currentDate.getMonth() + 3);
+    }
+    this.generateCalendarDates();
+  }
+
+  getCurrentPeriodLabel(): string {
+    const year = this.currentDate.getFullYear();
+    const month = this.currentDate.getMonth() + 1;
+    
+    if (this.timeRange === 'week') {
+      const weekStart = new Date(this.currentDate);
+      weekStart.setDate(this.currentDate.getDate() - this.currentDate.getDay());
+      const weekEnd = new Date(weekStart);
+      weekEnd.setDate(weekStart.getDate() + 6);
+      
+      return `${weekStart.getMonth() + 1}/${weekStart.getDate()} - ${weekEnd.getMonth() + 1}/${weekEnd.getDate()}`;
+    } else if (this.timeRange === 'month') {
+      return `${year}年${month}月`;
+    } else {
+      const quarter = Math.floor(month / 3) + 1;
+      return `${year}年第${quarter}季度`;
+    }
+  }
+
+  // 日期格式化
+  formatDate(date: Date, format: string): string {
+    const options: Intl.DateTimeFormatOptions = {};
+    
+    if (format === 'MM/dd') {
+      return `${(date.getMonth() + 1).toString().padStart(2, '0')}/${date.getDate().toString().padStart(2, '0')}`;
+    } else if (format === 'EEE') {
+      const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
+      return weekdays[date.getDay()];
+    }
+    
+    return date.toLocaleDateString();
+  }
+
+  // 获取日期事件
+  getDateEvents(designer: Designer, date: Date): DesignerEvent[] {
+    if (!designer.upcomingEvents) return [];
+    return designer.upcomingEvents.filter(event => {
+      const eventDate = new Date(event.date);
+      return eventDate.toDateString() === date.toDateString();
+    });
+  }
+
+  // 获取日期单元格样式
+  getDateCellClass(designer: Designer, date: Date): string {
+    const events = this.getDateEvents(designer, date);
+    const classes = [];
+    
+    if (events.length > 0) {
+      classes.push('has-events');
+      
+      if (events.some(e => e.type === 'review')) {
+        classes.push('review-day');
+      }
+      if (events.some(e => e.type === 'project')) {
+        classes.push('project-day');
+      }
+      if (events.some(e => e.type === 'vacation')) {
+        classes.push('vacation-day');
+      }
+    } else {
+      classes.push('available-day');
+    }
+    
+    // 今天
+    if (date.toDateString() === new Date().toDateString()) {
+      classes.push('today');
+    }
+    
+    return classes.join(' ');
+  }
+
+  // 获取事件样式
+  getEventClass(event: DesignerEvent): string {
+    return `event-${event.type}`;
+  }
+
+  // 获取状态文本
+  getStatusText(status: string): string {
+    const statusMap = {
+      'available': '空闲',
+      'busy': '忙碌',
+      'overloaded': '满载',
+      'stagnant': '停滞期'
+    };
+    return statusMap[status as keyof typeof statusMap] || status;
+  }
+
+  // 获取事件类型文本
+  getEventTypeText(type: string): string {
+    const typeMap = {
+      'project': '项目工作',
+      'review': '对图时间',
+      'vacation': '休假'
+    };
+    return typeMap[type as keyof typeof typeMap] || type;
+  }
+
+  // 获取空闲天数样式
+  getIdleDaysClass(days: number): string {
+    if (days >= 10) return 'critical';
+    if (days >= 5) return 'warning';
+    return 'normal';
+  }
+
+  // 获取下次可接单日期
+  getNextAvailableDate(designer: Designer): string {
+    if (!designer.nextAvailableDate) return '立即可用';
+    const nextDate = new Date(designer.nextAvailableDate);
+    const today = new Date();
+    const diffTime = nextDate.getTime() - today.getTime();
+    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+    
+    if (diffDays <= 0) return '立即可用';
+    if (diffDays === 1) return '明天';
+    return `${diffDays}天后`;
+  }
+
+  // 获取推荐分配等级
+  getRecommendationClass(designer: Designer): string {
+    if (designer.status === 'available' && (designer.idleDays ?? 0) >= 5) return 'high-recommend';
+    if (designer.status === 'available') return 'recommend';
+    if (designer.status === 'busy') return 'caution';
+    return 'not-recommend';
+  }
+
+  // 获取推荐分配文本
+  getRecommendationText(designer: Designer): string {
+    if (designer.status === 'available' && (designer.idleDays ?? 0) >= 5) return '强烈推荐';
+    if (designer.status === 'available') return '推荐';
+    if (designer.status === 'busy') return '谨慎分配';
+    return '不推荐';
+  }
+
+  // 统计方法
+  getDesignerCountByStatus(status: string): number {
+    return this.filteredDesigners.filter(d => d.status === status).length;
+  }
+
+  getStagnantProjectsCount(): number {
+    return this.filteredDesigners
+      .filter(d => d.status === 'stagnant')
+      .reduce((sum, d) => sum + d.currentProjects, 0);
+  }
+
+  // 操作方法
+  refreshData() {
+    // 刷新数据逻辑
+    this.applyFilters();
+  }
+
+  viewDesignerDetail(designer: Designer) {
+    this.designerSelected.emit(designer);
+  }
+
+  assignToDesigner(designer: Designer) {
+    this.assignmentRequested.emit(designer);
+  }
+}

+ 100 - 0
src/app/pages/customer-service/consultation-order/components/order-form/order-form.component.html

@@ -0,0 +1,100 @@
+<!-- 订单基本信息表单 -->
+<div class="order-form-container">
+  <div class="form-header">
+    <h3>订单信息</h3>
+    <p>请填写订单的基本信息和要求</p>
+  </div>
+
+  <form [formGroup]="orderForm" class="order-form">
+    <!-- 第一行:订单金额、装修类型 -->
+    <div class="form-row">
+      <div class="form-field">
+        <label for="orderAmount" class="field-label">订单金额 <span class="required">*</span></label>
+        <div class="input-with-unit">
+          <input 
+            type="number" 
+            id="orderAmount" 
+            formControlName="orderAmount" 
+            placeholder="请输入订单总金额" 
+            class="field-input"
+            min="0"
+            step="0.01">
+          <span class="input-unit">元</span>
+        </div>
+        @if (orderForm.get('orderAmount')?.invalid && orderForm.get('orderAmount')?.touched) {
+          <div class="field-error">订单金额为必填项</div>
+        }
+      </div>
+      
+      <div class="form-field">
+        <label for="decorationType" class="field-label">装修类型 <span class="required">*</span></label>
+        <select id="decorationType" formControlName="decorationType" class="field-select">
+          <option value="">请选择装修类型</option>
+          <option value="家装">家装</option>
+          <option value="工装">工装</option>
+        </select>
+        @if (orderForm.get('decorationType')?.invalid && orderForm.get('decorationType')?.touched) {
+          <div class="field-error">装修类型为必填项</div>
+        }
+      </div>
+    </div>
+
+    <!-- 第二行:小图时间、大图时间(选填) -->
+    <div class="form-row">
+      <div class="form-field">
+        <label for="smallImageTime" class="field-label">小图时间 <span class="required">*</span></label>
+        <input 
+          type="date" 
+          id="smallImageTime" 
+          formControlName="smallImageTime" 
+          class="field-input">
+        @if (orderForm.get('smallImageTime')?.invalid && orderForm.get('smallImageTime')?.touched) {
+          <div class="field-error">小图时间为必填项</div>
+        }
+      </div>
+      
+      <div class="form-field">
+        <label for="largeImageTime" class="field-label">大图时间</label>
+        <input 
+          type="date" 
+          id="largeImageTime" 
+          formControlName="largeImageTime" 
+          class="field-input">
+        <div class="field-hint">选填,如有大图需求请填写</div>
+      </div>
+    </div>
+
+    <!-- 需求原因 -->
+    <div class="form-row">
+      <div class="form-field full-width">
+        <label for="requirementReason" class="field-label">需求原因 <span class="required">*</span></label>
+        <textarea 
+          id="requirementReason" 
+          formControlName="requirementReason" 
+          placeholder="请详细描述客户的装修需求和原因" 
+          class="field-textarea"
+          rows="4"></textarea>
+        @if (orderForm.get('requirementReason')?.invalid && orderForm.get('requirementReason')?.touched) {
+          <div class="field-error">需求原因为必填项</div>
+        }
+      </div>
+    </div>
+
+    <!-- 报价明细组件 -->
+    <div class="quotation-section">
+      <app-quotation-detail 
+        [quotationItems]="quotationItems"
+        (quotationChange)="onQuotationChange($event)">
+      </app-quotation-detail>
+    </div>
+
+    <!-- 设计师分配组件 -->
+    <div class="assignment-section">
+      <app-designer-assignment 
+        [selectedDesigners]="selectedDesigners"
+        [quotationItems]="quotationItems"
+        (designerChange)="onDesignerChange($event)">
+      </app-designer-assignment>
+    </div>
+  </form>
+</div>

+ 176 - 0
src/app/pages/customer-service/consultation-order/components/order-form/order-form.component.scss

@@ -0,0 +1,176 @@
+.order-form-container {
+  background: white;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  margin-bottom: 24px;
+
+  .form-header {
+    margin-bottom: 24px;
+    
+    h3 {
+      font-size: 18px;
+      font-weight: 600;
+      color: #1a1a1a;
+      margin: 0 0 8px 0;
+    }
+    
+    p {
+      font-size: 14px;
+      color: #666;
+      margin: 0;
+    }
+  }
+}
+
+.order-form {
+  .form-row {
+    display: flex;
+    gap: 20px;
+    margin-bottom: 20px;
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+  
+  .form-field {
+    flex: 1;
+    
+    &.full-width {
+      flex: 1 1 100%;
+    }
+    
+    .field-label {
+      display: block;
+      font-size: 14px;
+      font-weight: 500;
+      color: #333;
+      margin-bottom: 8px;
+      
+      .required {
+        color: #ff4757;
+        margin-left: 2px;
+      }
+    }
+    
+    .field-input,
+    .field-select,
+    .field-textarea {
+      width: 100%;
+      padding: 12px 16px;
+      border: 1px solid #e1e5e9;
+      border-radius: 8px;
+      font-size: 14px;
+      transition: all 0.2s ease;
+      
+      &:focus {
+        outline: none;
+        border-color: #4f46e5;
+        box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
+      }
+      
+      &::placeholder {
+        color: #9ca3af;
+      }
+      
+      &:invalid {
+        border-color: #ef4444;
+      }
+    }
+    
+    .field-textarea {
+      resize: vertical;
+      min-height: 100px;
+      font-family: inherit;
+    }
+    
+    .input-with-unit {
+      position: relative;
+      display: flex;
+      align-items: center;
+      
+      .field-input {
+        padding-right: 50px;
+      }
+      
+      .input-unit {
+        position: absolute;
+        right: 16px;
+        font-size: 14px;
+        color: #666;
+        pointer-events: none;
+      }
+    }
+    
+    .field-error {
+      margin-top: 6px;
+      font-size: 12px;
+      color: #ef4444;
+    }
+    
+    .field-hint {
+      margin-top: 6px;
+      font-size: 12px;
+      color: #6b7280;
+    }
+  }
+}
+
+.quotation-section,
+.assignment-section {
+  margin-top: 32px;
+  padding-top: 24px;
+  border-top: 1px solid #e5e7eb;
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .order-form {
+    .form-row {
+      flex-direction: column;
+      gap: 16px;
+    }
+  }
+  
+  .order-form-container {
+    padding: 16px;
+  }
+}
+
+// 表单验证状态
+.form-field {
+  .field-input.ng-invalid.ng-touched,
+  .field-select.ng-invalid.ng-touched,
+  .field-textarea.ng-invalid.ng-touched {
+    border-color: #ef4444;
+    background-color: #fef2f2;
+  }
+  
+  .field-input.ng-valid.ng-touched,
+  .field-select.ng-valid.ng-touched,
+  .field-textarea.ng-valid.ng-touched {
+    border-color: #10b981;
+  }
+}
+
+// 金额匹配提示
+.amount-mismatch-warning {
+  background: #fef3cd;
+  border: 1px solid #f6cc6d;
+  border-radius: 8px;
+  padding: 12px 16px;
+  margin-top: 16px;
+  
+  .warning-text {
+    font-size: 14px;
+    color: #92400e;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    
+    svg {
+      flex-shrink: 0;
+    }
+  }
+}

+ 113 - 0
src/app/pages/customer-service/consultation-order/components/order-form/order-form.component.ts

@@ -0,0 +1,113 @@
+import { Component, Input, Output, EventEmitter, signal, computed } from '@angular/core';
+import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
+import { CommonModule } from '@angular/common';
+import { DesignerAssignmentComponent, DesignerAssignment, QuotationItem } from '../designer-assignment/designer-assignment.component';
+import { QuotationDetailComponent } from '../quotation-detail/quotation-detail.component';
+
+export interface OrderFormData {
+  orderAmount: number;
+  decorationType: string;
+  smallImageTime: string;
+  largeImageTime?: string;
+  requirementReason: string;
+}
+
+@Component({
+  selector: 'app-order-form',
+  standalone: true,
+  imports: [CommonModule, ReactiveFormsModule, DesignerAssignmentComponent, QuotationDetailComponent],
+  templateUrl: './order-form.component.html',
+  styleUrls: ['./order-form.component.scss']
+})
+export class OrderFormComponent {
+  @Input() initialData?: Partial<OrderFormData>;
+  @Output() formChange = new EventEmitter<OrderFormData>();
+  @Output() validChange = new EventEmitter<boolean>();
+
+  orderForm: FormGroup;
+  quotationItems = signal<QuotationItem[]>([]);
+  selectedDesigners = signal<DesignerAssignment[]>([]);
+
+  // 计算总金额是否匹配
+  totalAmountMatch = computed(() => {
+    const formAmount = this.orderForm?.get('orderAmount')?.value || 0;
+    const quotationTotal = this.quotationItems().reduce((sum, item) => sum + item.amount, 0);
+    return Math.abs(formAmount - quotationTotal) < 0.01;
+  });
+
+  constructor(private fb: FormBuilder) {
+    this.orderForm = this.createForm();
+    this.setupFormSubscription();
+  }
+
+  ngOnInit() {
+    if (this.initialData) {
+      this.orderForm.patchValue(this.initialData);
+    }
+  }
+
+  private createForm(): FormGroup {
+    return this.fb.group({
+      orderAmount: [0, [Validators.required, Validators.min(0.01)]],
+      decorationType: ['', Validators.required],
+      smallImageTime: ['', Validators.required],
+      largeImageTime: [''],
+      requirementReason: ['', [Validators.required, Validators.minLength(10)]]
+    });
+  }
+
+  private setupFormSubscription() {
+    this.orderForm.valueChanges.subscribe(value => {
+      this.formChange.emit(value);
+      this.validChange.emit(this.isFormValid());
+    });
+  }
+
+  onQuotationChange(items: QuotationItem[]) {
+    this.quotationItems.set(items);
+    
+    // 自动更新订单总金额
+    const totalAmount = items.reduce((sum, item) => sum + item.amount, 0);
+    this.orderForm.patchValue({ orderAmount: totalAmount }, { emitEvent: false });
+    
+    this.emitFormChange();
+  }
+
+  onDesignerChange(designers: DesignerAssignment[]) {
+    this.selectedDesigners.set(designers);
+    this.emitFormChange();
+  }
+
+  private emitFormChange() {
+    const formData: OrderFormData = {
+      ...this.orderForm.value,
+      quotationItems: this.quotationItems(),
+      designerAssignments: this.selectedDesigners()
+    };
+    this.formChange.emit(formData);
+    this.validChange.emit(this.isFormValid());
+  }
+
+  isFormValid(): boolean {
+    const formValid = this.orderForm.valid;
+    const hasQuotation = this.quotationItems().length > 0;
+    const hasDesigners = this.selectedDesigners().length > 0;
+    const amountMatch = this.totalAmountMatch();
+    
+    return formValid && hasQuotation && hasDesigners && amountMatch;
+  }
+
+  getFormData(): OrderFormData & { quotationItems: QuotationItem[], designerAssignments: DesignerAssignment[] } {
+    return {
+      ...this.orderForm.value,
+      quotationItems: this.quotationItems(),
+      designerAssignments: this.selectedDesigners()
+    };
+  }
+
+  resetForm() {
+    this.orderForm.reset();
+    this.quotationItems.set([]);
+    this.selectedDesigners.set([]);
+  }
+}

+ 167 - 0
src/app/pages/customer-service/consultation-order/components/quotation-detail/quotation-detail.component.html

@@ -0,0 +1,167 @@
+<!-- 报价明细组件 -->
+<div class="quotation-detail-container">
+  <div class="quotation-header">
+    <div class="header-left">
+      <h4>报价明细</h4>
+      <p>AI辅助生成详细报价清单</p>
+    </div>
+    <div class="header-actions">
+      <button 
+        type="button" 
+        class="btn-secondary btn-sm"
+        (click)="generateAIQuotation()"
+        [disabled]="isGenerating()">
+        @if (isGenerating()) {
+          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="animate-spin">
+            <path d="M21 12a9 9 0 1 1-6.219-8.56"></path>
+          </svg>
+        } @else {
+          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <path d="M12 2L2 7l10 5 10-5-10-5z"></path>
+            <path d="M2 17l10 5 10-5"></path>
+            <path d="M2 12l10 5 10-5"></path>
+          </svg>
+        }
+        {{ isGenerating() ? 'AI生成中...' : 'AI智能报价' }}
+      </button>
+      
+      <button 
+        type="button" 
+        class="btn-primary btn-sm"
+        (click)="addQuotationItem()">
+        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+          <line x1="12" y1="5" x2="12" y2="19"></line>
+          <line x1="5" y1="12" x2="19" y2="12"></line>
+        </svg>
+        添加项目
+      </button>
+    </div>
+  </div>
+
+  <!-- 报价项目列表 -->
+  <div class="quotation-items">
+    @if (quotationItems().length === 0) {
+      <div class="empty-state">
+        <div class="empty-icon">
+          <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
+            <rect x="3" y="4" width="18" height="14" rx="2" ry="2"></rect>
+            <line x1="8" y1="2" x2="16" y2="2"></line>
+            <line x1="10" y1="11" x2="14" y2="11"></line>
+            <line x1="12" y1="9" x2="12" y2="13"></line>
+          </svg>
+        </div>
+        <p>暂无报价项目</p>
+        <p class="empty-hint">点击"AI智能报价"或"添加项目"开始创建报价清单</p>
+      </div>
+    } @else {
+      @for (item of quotationItems(); track item.id) {
+        <div class="quotation-item" [class.editing]="editingItemId() === item.id">
+          <div class="item-content">
+            <div class="item-info">
+              <div class="room-type">
+                @if (editingItemId() === item.id) {
+                  <select 
+                    [(ngModel)]="editingItem.roomType" 
+                    class="edit-select">
+                    <option value="">选择空间类型</option>
+                    @for (roomType of roomTypeOptions; track roomType) {
+                      <option [value]="roomType">{{ roomType }}</option>
+                    }
+                  </select>
+                } @else {
+                  <span class="room-name">{{ item.roomType }}</span>
+                }
+              </div>
+              
+              <div class="item-amount">
+                @if (editingItemId() === item.id) {
+                  <div class="amount-input">
+                    <input 
+                      type="number" 
+                      [(ngModel)]="editingItem.amount"
+                      class="edit-input"
+                      placeholder="金额"
+                      min="0"
+                      step="0.01">
+                    <span class="unit">元</span>
+                  </div>
+                } @else {
+                  <span class="amount">¥{{ item.amount | number:'1.2-2' }}</span>
+                }
+              </div>
+            </div>
+            
+            @if (item.description || editingItemId() === item.id) {
+              <div class="item-description">
+                @if (editingItemId() === item.id) {
+                  <textarea 
+                    [(ngModel)]="editingItem.description"
+                    class="edit-textarea"
+                    placeholder="项目描述(选填)"
+                    rows="2"></textarea>
+                } @else {
+                  <p>{{ item.description }}</p>
+                }
+              </div>
+            }
+          </div>
+          
+          <div class="item-actions">
+            @if (editingItemId() === item.id) {
+              <button 
+                type="button" 
+                class="action-btn save-btn"
+                (click)="saveItem()">
+                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polyline points="20 6 9 17 4 12"></polyline>
+                </svg>
+              </button>
+              <button 
+                type="button" 
+                class="action-btn cancel-btn"
+                (click)="cancelEdit()">
+                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <line x1="18" y1="6" x2="6" y2="18"></line>
+                  <line x1="6" y1="6" x2="18" y2="18"></line>
+                </svg>
+              </button>
+            } @else {
+              <button 
+                type="button" 
+                class="action-btn edit-btn"
+                (click)="editItem(item)">
+                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
+                  <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
+                </svg>
+              </button>
+              <button 
+                type="button" 
+                class="action-btn delete-btn"
+                (click)="deleteItem(item.id)">
+                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polyline points="3 6 5 6 21 6"></polyline>
+                  <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
+                </svg>
+              </button>
+            }
+          </div>
+        </div>
+      }
+    }
+  </div>
+
+  <!-- 总计 -->
+  @if (quotationItems().length > 0) {
+    <div class="quotation-summary">
+      <div class="summary-row">
+        <span class="summary-label">项目总数:</span>
+        <span class="summary-value">{{ quotationItems().length }} 项</span>
+      </div>
+      <div class="summary-row total">
+        <span class="summary-label">总计金额:</span>
+        <span class="summary-value">¥{{ totalAmount() | number:'1.2-2' }}</span>
+      </div>
+    </div>
+  }
+</div>

+ 348 - 0
src/app/pages/customer-service/consultation-order/components/quotation-detail/quotation-detail.component.scss

@@ -0,0 +1,348 @@
+.quotation-detail-container {
+  .quotation-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    margin-bottom: 20px;
+    
+    .header-left {
+      h4 {
+        font-size: 16px;
+        font-weight: 600;
+        color: #1a1a1a;
+        margin: 0 0 4px 0;
+      }
+      
+      p {
+        font-size: 13px;
+        color: #666;
+        margin: 0;
+      }
+    }
+    
+    .header-actions {
+      display: flex;
+      gap: 12px;
+      
+      .btn-sm {
+        padding: 8px 16px;
+        font-size: 13px;
+        border-radius: 6px;
+        border: none;
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        transition: all 0.2s ease;
+        
+        &:disabled {
+          opacity: 0.6;
+          cursor: not-allowed;
+        }
+        
+        svg {
+          flex-shrink: 0;
+        }
+      }
+      
+      .btn-secondary {
+        background: #f8fafc;
+        color: #475569;
+        border: 1px solid #e2e8f0;
+        
+        &:hover:not(:disabled) {
+          background: #f1f5f9;
+          border-color: #cbd5e1;
+        }
+      }
+      
+      .btn-primary {
+        background: #4f46e5;
+        color: white;
+        
+        &:hover:not(:disabled) {
+          background: #4338ca;
+        }
+      }
+    }
+  }
+}
+
+.quotation-items {
+  .empty-state {
+    text-align: center;
+    padding: 48px 24px;
+    color: #6b7280;
+    
+    .empty-icon {
+      margin-bottom: 16px;
+      
+      svg {
+        color: #d1d5db;
+      }
+    }
+    
+    p {
+      margin: 0 0 8px 0;
+      font-size: 14px;
+      
+      &.empty-hint {
+        font-size: 13px;
+        color: #9ca3af;
+      }
+    }
+  }
+  
+  .quotation-item {
+    background: #f8fafc;
+    border: 1px solid #e2e8f0;
+    border-radius: 8px;
+    padding: 16px;
+    margin-bottom: 12px;
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    transition: all 0.2s ease;
+    
+    &:hover {
+      border-color: #cbd5e1;
+    }
+    
+    &.editing {
+      border-color: #4f46e5;
+      background: #fefbff;
+    }
+    
+    .item-content {
+      flex: 1;
+      
+      .item-info {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 8px;
+        
+        .room-type {
+          .room-name {
+            font-weight: 500;
+            color: #1e293b;
+            font-size: 14px;
+          }
+          
+          .edit-select {
+            padding: 6px 12px;
+            border: 1px solid #d1d5db;
+            border-radius: 6px;
+            font-size: 14px;
+            min-width: 120px;
+            
+            &:focus {
+              outline: none;
+              border-color: #4f46e5;
+              box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
+            }
+          }
+        }
+        
+        .item-amount {
+          .amount {
+            font-weight: 600;
+            color: #059669;
+            font-size: 15px;
+          }
+          
+          .amount-input {
+            display: flex;
+            align-items: center;
+            gap: 4px;
+            
+            .edit-input {
+              padding: 6px 12px;
+              border: 1px solid #d1d5db;
+              border-radius: 6px;
+              font-size: 14px;
+              width: 100px;
+              text-align: right;
+              
+              &:focus {
+                outline: none;
+                border-color: #4f46e5;
+                box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
+              }
+            }
+            
+            .unit {
+              font-size: 13px;
+              color: #6b7280;
+            }
+          }
+        }
+      }
+      
+      .item-description {
+        p {
+          font-size: 13px;
+          color: #64748b;
+          margin: 0;
+          line-height: 1.4;
+        }
+        
+        .edit-textarea {
+          width: 100%;
+          padding: 8px 12px;
+          border: 1px solid #d1d5db;
+          border-radius: 6px;
+          font-size: 13px;
+          resize: vertical;
+          font-family: inherit;
+          
+          &:focus {
+            outline: none;
+            border-color: #4f46e5;
+            box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
+          }
+        }
+      }
+    }
+    
+    .item-actions {
+      display: flex;
+      gap: 8px;
+      margin-left: 16px;
+      
+      .action-btn {
+        width: 32px;
+        height: 32px;
+        border: none;
+        border-radius: 6px;
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        transition: all 0.2s ease;
+        
+        svg {
+          width: 14px;
+          height: 14px;
+        }
+        
+        &.edit-btn {
+          background: #f0f9ff;
+          color: #0369a1;
+          
+          &:hover {
+            background: #e0f2fe;
+          }
+        }
+        
+        &.delete-btn {
+          background: #fef2f2;
+          color: #dc2626;
+          
+          &:hover {
+            background: #fee2e2;
+          }
+        }
+        
+        &.save-btn {
+          background: #f0fdf4;
+          color: #16a34a;
+          
+          &:hover {
+            background: #dcfce7;
+          }
+        }
+        
+        &.cancel-btn {
+          background: #fafafa;
+          color: #525252;
+          
+          &:hover {
+            background: #f5f5f5;
+          }
+        }
+      }
+    }
+  }
+}
+
+.quotation-summary {
+  margin-top: 20px;
+  padding: 16px;
+  background: #f8fafc;
+  border-radius: 8px;
+  border: 1px solid #e2e8f0;
+  
+  .summary-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 8px;
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+    
+    &.total {
+      padding-top: 8px;
+      border-top: 1px solid #e2e8f0;
+      font-weight: 600;
+      
+      .summary-value {
+        color: #059669;
+        font-size: 16px;
+      }
+    }
+    
+    .summary-label {
+      font-size: 14px;
+      color: #64748b;
+    }
+    
+    .summary-value {
+      font-size: 14px;
+      color: #1e293b;
+    }
+  }
+}
+
+// 动画效果
+.animate-spin {
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .quotation-detail-container {
+    .quotation-header {
+      flex-direction: column;
+      gap: 16px;
+      align-items: stretch;
+      
+      .header-actions {
+        justify-content: flex-end;
+      }
+    }
+  }
+  
+  .quotation-items {
+    .quotation-item {
+      flex-direction: column;
+      gap: 12px;
+      
+      .item-actions {
+        margin-left: 0;
+        justify-content: flex-end;
+      }
+    }
+  }
+}

+ 212 - 0
src/app/pages/customer-service/consultation-order/components/quotation-detail/quotation-detail.component.ts

@@ -0,0 +1,212 @@
+import { Component, Input, Output, EventEmitter, signal, computed } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+export interface QuotationItem {
+  id: string;
+  roomType: string;
+  amount: number;
+  description?: string;
+}
+
+@Component({
+  selector: 'app-quotation-detail',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './quotation-detail.component.html',
+  styleUrls: ['./quotation-detail.component.scss']
+})
+export class QuotationDetailComponent {
+  @Input() quotationItems = signal<QuotationItem[]>([]);
+  @Input() decorationType?: string;
+  @Input() orderAmount?: number;
+  @Output() quotationChange = new EventEmitter<QuotationItem[]>();
+
+  isGenerating = signal(false);
+  editingItemId = signal<string | null>(null);
+  editingItem: Partial<QuotationItem> = {};
+
+  roomTypeOptions = [
+    '客厅', '餐厅', '客餐厅', '主卧', '次卧', '儿童房', 
+    '书房', '厨房', '主卫', '客卫', '阳台', '玄关', 
+    '走廊', '储物间', '衣帽间', '其他空间'
+  ];
+
+  // 计算总金额
+  totalAmount = computed(() => {
+    return this.quotationItems().reduce((sum, item) => sum + item.amount, 0);
+  });
+
+  constructor() {}
+
+  // AI智能报价生成
+  async generateAIQuotation() {
+    if (this.isGenerating()) return;
+    
+    this.isGenerating.set(true);
+    
+    try {
+      // 模拟AI生成报价(实际项目中应调用AI服务)
+      await this.simulateAIGeneration();
+      
+      const aiQuotation = this.generateDefaultQuotation();
+      this.quotationItems.set(aiQuotation);
+      this.emitChange();
+    } catch (error) {
+      console.error('AI报价生成失败:', error);
+    } finally {
+      this.isGenerating.set(false);
+    }
+  }
+
+  private async simulateAIGeneration(): Promise<void> {
+    return new Promise(resolve => setTimeout(resolve, 2000));
+  }
+
+  private generateDefaultQuotation(): QuotationItem[] {
+    const baseAmount = this.orderAmount || 100000;
+    const isHomeDecoration = this.decorationType === '家装';
+    
+    if (isHomeDecoration) {
+      return [
+        {
+          id: this.generateId(),
+          roomType: '客餐厅',
+          amount: Math.round(baseAmount * 0.35),
+          description: '包含客厅和餐厅的整体设计,含家具配置和软装搭配'
+        },
+        {
+          id: this.generateId(),
+          roomType: '主卧',
+          amount: Math.round(baseAmount * 0.25),
+          description: '主卧室设计,包含衣柜、床品和照明设计'
+        },
+        {
+          id: this.generateId(),
+          roomType: '次卧',
+          amount: Math.round(baseAmount * 0.15),
+          description: '次卧室或儿童房设计'
+        },
+        {
+          id: this.generateId(),
+          roomType: '厨房',
+          amount: Math.round(baseAmount * 0.15),
+          description: '厨房功能设计和橱柜配置'
+        },
+        {
+          id: this.generateId(),
+          roomType: '卫生间',
+          amount: Math.round(baseAmount * 0.1),
+          description: '卫生间设计和洁具配置'
+        }
+      ];
+    } else {
+      // 工装报价
+      return [
+        {
+          id: this.generateId(),
+          roomType: '办公区域',
+          amount: Math.round(baseAmount * 0.4),
+          description: '办公空间设计,包含工位和公共区域'
+        },
+        {
+          id: this.generateId(),
+          roomType: '会议室',
+          amount: Math.round(baseAmount * 0.2),
+          description: '会议室设计和设备配置'
+        },
+        {
+          id: this.generateId(),
+          roomType: '接待区',
+          amount: Math.round(baseAmount * 0.2),
+          description: '前台接待区域设计'
+        },
+        {
+          id: this.generateId(),
+          roomType: '其他区域',
+          amount: Math.round(baseAmount * 0.2),
+          description: '茶水间、休息区等其他功能区域'
+        }
+      ];
+    }
+  }
+
+  // 添加报价项目
+  addQuotationItem() {
+    const newItem: QuotationItem = {
+      id: this.generateId(),
+      roomType: '',
+      amount: 0,
+      description: ''
+    };
+    
+    const currentItems = this.quotationItems();
+    this.quotationItems.set([...currentItems, newItem]);
+    this.editItem(newItem);
+  }
+
+  // 编辑项目
+  editItem(item: QuotationItem) {
+    this.editingItemId.set(item.id);
+    this.editingItem = { ...item };
+  }
+
+  // 保存编辑
+  saveItem() {
+    const editingId = this.editingItemId();
+    if (!editingId || !this.editingItem.roomType || !this.editingItem.amount) {
+      return;
+    }
+
+    const currentItems = this.quotationItems();
+    const updatedItems = currentItems.map(item => 
+      item.id === editingId 
+        ? { ...item, ...this.editingItem } as QuotationItem
+        : item
+    );
+    
+    this.quotationItems.set(updatedItems);
+    this.cancelEdit();
+    this.emitChange();
+  }
+
+  // 取消编辑
+  cancelEdit() {
+    const editingId = this.editingItemId();
+    
+    // 如果是新添加的空项目,删除它
+    if (editingId) {
+      const currentItems = this.quotationItems();
+      const editingItem = currentItems.find(item => item.id === editingId);
+      
+      if (editingItem && !editingItem.roomType && !editingItem.amount) {
+        this.deleteItem(editingId);
+      }
+    }
+    
+    this.editingItemId.set(null);
+    this.editingItem = {};
+  }
+
+  // 删除项目
+  deleteItem(itemId: string) {
+    const currentItems = this.quotationItems();
+    const filteredItems = currentItems.filter(item => item.id !== itemId);
+    this.quotationItems.set(filteredItems);
+    
+    if (this.editingItemId() === itemId) {
+      this.editingItemId.set(null);
+      this.editingItem = {};
+    }
+    
+    this.emitChange();
+  }
+
+  private emitChange() {
+    this.quotationChange.emit(this.quotationItems());
+  }
+
+  private generateId(): string {
+    return 'quotation_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
+  }
+}

+ 2 - 0
src/app/pages/customer-service/dashboard/pages/consultation-list/consultation-list.component.scss

@@ -225,6 +225,8 @@
     -webkit-box-orient: vertical;
     overflow: hidden;
     font-weight: 400;
+    line-clamp: 2;
+    box-orient: vertical;
   }
 
   // 卡片底部操作按钮

+ 290 - 0
src/app/pages/designer/project-detail/components/designer-assignment/designer-assignment.component.html

@@ -0,0 +1,290 @@
+<div class="designer-assignment-container">
+  <div class="section-header">
+    <h3>设计师分配</h3>
+    <div class="assignment-summary">
+      @if (selectedTeamId) {
+        <span class="team-info">主要团队:{{ getSelectedTeam()?.name }}</span>
+        @if (assignmentData.crossTeamCollaborators.length > 0) {
+          <span class="cross-team-info">跨组合作:{{ assignmentData.crossTeamCollaborators.length }}人</span>
+        }
+      }
+    </div>
+  </div>
+
+  <!-- 项目组选择 -->
+  <div class="team-selection-section">
+    <h4>选择主要项目组</h4>
+    <div class="team-grid">
+      @for (team of projectTeams; track team.id) {
+        <div 
+          class="team-card" 
+          [class.selected]="selectedTeamId === team.id"
+          (click)="selectPrimaryTeam(team.id)"
+        >
+          <div class="team-header">
+            <h5>{{ team.name }}</h5>
+            <span class="team-leader">组长:{{ team.leaderName }}</span>
+          </div>
+          <div class="team-stats">
+            <div class="stat-item">
+              <span class="stat-label">成员数</span>
+              <span class="stat-value">{{ team.members.length }}</span>
+            </div>
+            <div class="stat-item">
+              <span class="stat-label">空闲人数</span>
+              <span class="stat-value idle">{{ getIdleCount(team) }}</span>
+            </div>
+          </div>
+        </div>
+      }
+    </div>
+  </div>
+
+  <!-- 自动分配建议 -->
+  @if (selectedTeamId && getAutoAssignmentSuggestion()) {
+    <div class="auto-suggestion">
+      <div class="suggestion-header">
+        <span class="suggestion-icon">💡</span>
+        <span>分配建议</span>
+      </div>
+      <p>{{ getAutoAssignmentSuggestion() }}</p>
+    </div>
+  }
+
+  <!-- 报价项目分配 -->
+  @if (selectedTeamId && quotationItems.length > 0) {
+    <div class="quotation-assignment-section">
+      <h4>报价项目分配</h4>
+      <div class="assignment-grid">
+        @for (assignment of assignmentData.quotationAssignments; track assignment.quotationItemId) {
+          <div class="assignment-card">
+            <div class="assignment-header">
+              <h5>{{ assignment.quotationItemName }}</h5>
+              <div class="assigned-count">
+                已分配:{{ assignment.assignedDesigners.length }}人
+              </div>
+            </div>
+            
+            <div class="designer-selection">
+              <div class="available-designers">
+                <h6>团队成员</h6>
+                <div class="designer-list">
+                  @for (designer of getSelectedTeam()?.members; track designer.id) {
+                    <div 
+                      class="designer-item"
+                      [class.assigned]="assignment.assignedDesigners.includes(designer.id)"
+                      (click)="onDesignerClick(designer)"
+                    >
+                      <div class="designer-avatar">
+                        @if (designer.avatar) {
+                          <img [src]="designer.avatar" [alt]="designer.name" />
+                        } @else {
+                          <div class="avatar-placeholder">{{ designer.name.charAt(0) }}</div>
+                        }
+                      </div>
+                      
+                      <div class="designer-info">
+                        <div class="designer-name">
+                          {{ designer.name }}
+                          @if (designer.isTeamLeader) {
+                            <span class="leader-badge">组长</span>
+                          }
+                        </div>
+                        <div class="designer-status">
+                          <span 
+                            class="status-dot" 
+                            [style.background-color]="getDesignerStatusColor(designer.status)"
+                          ></span>
+                          <span class="status-text">{{ getDesignerStatusText(designer.status) }}</span>
+                          @if (designer.idleDays > 0) {
+                            <span class="idle-days">{{ designer.idleDays }}天未接单</span>
+                          }
+                        </div>
+                        <div class="workload-bar">
+                          <div class="workload-fill" [style.width.%]="designer.workload"></div>
+                          <span class="workload-text">{{ designer.workload }}%</span>
+                        </div>
+                      </div>
+                      
+                      <div class="assignment-actions">
+                        @if (assignment.assignedDesigners.includes(designer.id)) {
+                          <button 
+                            class="remove-btn"
+                            (click)="removeDesignerFromQuotation(assignment.quotationItemId, designer.id); $event.stopPropagation()"
+                          >
+                            移除
+                          </button>
+                        } @else {
+                          <button 
+                            class="assign-btn"
+                            (click)="assignDesignerToQuotation(assignment.quotationItemId, designer.id); $event.stopPropagation()"
+                          >
+                            分配
+                          </button>
+                        }
+                      </div>
+                    </div>
+                  }
+                </div>
+              </div>
+            </div>
+          </div>
+        }
+      </div>
+    </div>
+  }
+
+  <!-- 跨组合作 -->
+  @if (selectedTeamId) {
+    <div class="cross-team-section">
+      <div class="section-title">
+        <h4>跨组合作</h4>
+        <button 
+          class="toggle-btn"
+          (click)="showCrossTeamSelection = !showCrossTeamSelection"
+        >
+          @if (showCrossTeamSelection) {
+            <span>收起</span>
+          } @else {
+            <span>添加跨组合作</span>
+          }
+        </button>
+      </div>
+
+      <!-- 已选择的跨组合作设计师 -->
+      @if (assignmentData.crossTeamCollaborators.length > 0) {
+        <div class="selected-collaborators">
+          <h5>已选择的跨组合作设计师</h5>
+          <div class="collaborator-list">
+            @for (designerId of assignmentData.crossTeamCollaborators; track designerId) {
+              <div class="collaborator-item">
+                @if (getDesignerById(designerId); as designer) {
+                  <div class="designer-info">
+                    <span class="designer-name">{{ designer.name }}</span>
+                    <span class="team-name">{{ designer.teamName }}</span>
+                  </div>
+                  <button 
+                    class="remove-collaborator-btn"
+                    (click)="removeCrossTeamCollaborator(designerId)"
+                  >
+                    ×
+                  </button>
+                }
+              </div>
+            }
+          </div>
+        </div>
+      }
+
+      <!-- 跨组设计师选择 -->
+      @if (showCrossTeamSelection) {
+        <div class="cross-team-selection">
+          <h5>选择其他组设计师</h5>
+          <div class="cross-team-grid">
+            @for (team of projectTeams; track team.id) {
+              @if (team.id !== selectedTeamId) {
+                <div class="cross-team-group">
+                  <h6>{{ team.name }}</h6>
+                  <div class="designer-list">
+                    @for (designer of team.members; track designer.id) {
+                      <div 
+                        class="designer-item"
+                        [class.selected]="assignmentData.crossTeamCollaborators.includes(designer.id)"
+                        (click)="onDesignerClick(designer)"
+                      >
+                        <div class="designer-info">
+                          <span class="designer-name">{{ designer.name }}</span>
+                          <div class="designer-status">
+                            <span 
+                              class="status-dot" 
+                              [style.background-color]="getDesignerStatusColor(designer.status)"
+                            ></span>
+                            <span class="status-text">{{ getDesignerStatusText(designer.status) }}</span>
+                          </div>
+                        </div>
+                        <div class="selection-actions">
+                          @if (assignmentData.crossTeamCollaborators.includes(designer.id)) {
+                            <button 
+                              class="remove-btn"
+                              (click)="removeCrossTeamCollaborator(designer.id); $event.stopPropagation()"
+                            >
+                              移除
+                            </button>
+                          } @else {
+                            <button 
+                              class="add-btn"
+                              (click)="addCrossTeamCollaborator(designer.id); $event.stopPropagation()"
+                            >
+                              添加
+                            </button>
+                          }
+                        </div>
+                      </div>
+                    }
+                  </div>
+                </div>
+              }
+            }
+          </div>
+        </div>
+      }
+    </div>
+  }
+
+  <!-- 备注 -->
+  <div class="notes-section">
+    <label for="assignmentNotes">分配备注</label>
+    <textarea
+      id="assignmentNotes"
+      [(ngModel)]="assignmentData.notes"
+      (ngModelChange)="emitAssignmentChange()"
+      placeholder="请输入分配相关的备注信息..."
+      class="notes-textarea"
+      rows="3"
+    ></textarea>
+  </div>
+
+  <!-- 分配总结 -->
+  <div class="assignment-summary-section">
+    <h4>分配总结</h4>
+    <div class="summary-content">
+      @if (selectedTeamId) {
+        <div class="summary-item">
+          <span class="label">主要团队:</span>
+          <span class="value">{{ getSelectedTeam()?.name }}</span>
+        </div>
+      }
+      
+      @if (assignmentData.quotationAssignments.length > 0) {
+        <div class="summary-item">
+          <span class="label">项目分配:</span>
+          <div class="assignment-list">
+            @for (assignment of assignmentData.quotationAssignments; track assignment.quotationItemId) {
+              @if (assignment.assignedDesigners.length > 0) {
+                <div class="assignment-summary-item">
+                  <span class="project-name">{{ assignment.quotationItemName }}</span>
+                  <span class="assigned-designers">
+                    @for (designerId of assignment.assignedDesigners; track designerId) {
+                      <span class="designer-tag">{{ getDesignerById(designerId)?.name }}</span>
+                    }
+                  </span>
+                </div>
+              }
+            }
+          </div>
+        </div>
+      }
+      
+      @if (assignmentData.crossTeamCollaborators.length > 0) {
+        <div class="summary-item">
+          <span class="label">跨组合作:</span>
+          <div class="collaborator-tags">
+            @for (designerId of assignmentData.crossTeamCollaborators; track designerId) {
+              <span class="collaborator-tag">{{ getDesignerById(designerId)?.name }}</span>
+            }
+          </div>
+        </div>
+      }
+    </div>
+  </div>
+</div>

+ 711 - 0
src/app/pages/designer/project-detail/components/designer-assignment/designer-assignment.component.scss

@@ -0,0 +1,711 @@
+.designer-assignment-container {
+  background: #ffffff;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+  margin-bottom: 24px;
+
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 24px;
+    padding-bottom: 16px;
+    border-bottom: 1px solid #e8e8e8;
+
+    h3 {
+      margin: 0;
+      font-size: 18px;
+      font-weight: 600;
+      color: #333333;
+    }
+
+    .assignment-summary {
+      display: flex;
+      gap: 16px;
+      font-size: 14px;
+
+      .team-info {
+        color: #1890ff;
+        font-weight: 500;
+      }
+
+      .cross-team-info {
+        color: #722ed1;
+        font-weight: 500;
+      }
+    }
+  }
+
+  .team-selection-section {
+    margin-bottom: 32px;
+
+    h4 {
+      margin: 0 0 16px 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: #333333;
+    }
+
+    .team-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+      gap: 16px;
+
+      .team-card {
+        border: 2px solid #e8e8e8;
+        border-radius: 12px;
+        padding: 20px;
+        cursor: pointer;
+        transition: all 0.3s ease;
+        background: #ffffff;
+
+        &:hover {
+          border-color: #1890ff;
+          box-shadow: 0 4px 12px rgba(24, 144, 255, 0.15);
+        }
+
+        &.selected {
+          border-color: #1890ff;
+          background: #f0f8ff;
+          box-shadow: 0 4px 12px rgba(24, 144, 255, 0.2);
+        }
+
+        .team-header {
+          margin-bottom: 16px;
+
+          h5 {
+            margin: 0 0 8px 0;
+            font-size: 16px;
+            font-weight: 600;
+            color: #333333;
+          }
+
+          .team-leader {
+            font-size: 14px;
+            color: #666666;
+          }
+        }
+
+        .team-stats {
+          display: flex;
+          gap: 24px;
+
+          .stat-item {
+            display: flex;
+            flex-direction: column;
+            gap: 4px;
+
+            .stat-label {
+              font-size: 12px;
+              color: #999999;
+            }
+
+            .stat-value {
+              font-size: 18px;
+              font-weight: 600;
+              color: #333333;
+
+              &.idle {
+                color: #52c41a;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .auto-suggestion {
+    background: #fffbe6;
+    border: 1px solid #ffe58f;
+    border-radius: 8px;
+    padding: 16px;
+    margin-bottom: 24px;
+
+    .suggestion-header {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      margin-bottom: 8px;
+      font-weight: 600;
+      color: #d48806;
+
+      .suggestion-icon {
+        font-size: 16px;
+      }
+    }
+
+    p {
+      margin: 0;
+      font-size: 14px;
+      color: #d48806;
+      line-height: 1.5;
+    }
+  }
+
+  .quotation-assignment-section {
+    margin-bottom: 32px;
+
+    h4 {
+      margin: 0 0 20px 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: #333333;
+    }
+
+    .assignment-grid {
+      display: flex;
+      flex-direction: column;
+      gap: 20px;
+
+      .assignment-card {
+        border: 1px solid #e8e8e8;
+        border-radius: 12px;
+        padding: 20px;
+        background: #fafafa;
+
+        .assignment-header {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          margin-bottom: 16px;
+
+          h5 {
+            margin: 0;
+            font-size: 16px;
+            font-weight: 600;
+            color: #333333;
+          }
+
+          .assigned-count {
+            font-size: 14px;
+            color: #1890ff;
+            font-weight: 500;
+          }
+        }
+
+        .designer-selection {
+          .available-designers {
+            h6 {
+              margin: 0 0 12px 0;
+              font-size: 14px;
+              font-weight: 600;
+              color: #666666;
+            }
+
+            .designer-list {
+              display: flex;
+              flex-direction: column;
+              gap: 12px;
+
+              .designer-item {
+                display: flex;
+                align-items: center;
+                gap: 16px;
+                padding: 16px;
+                border: 1px solid #e8e8e8;
+                border-radius: 8px;
+                background: #ffffff;
+                cursor: pointer;
+                transition: all 0.3s ease;
+
+                &:hover {
+                  border-color: #1890ff;
+                  box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1);
+                }
+
+                &.assigned {
+                  border-color: #52c41a;
+                  background: #f6ffed;
+                }
+
+                .designer-avatar {
+                  width: 48px;
+                  height: 48px;
+                  border-radius: 50%;
+                  overflow: hidden;
+                  flex-shrink: 0;
+
+                  img {
+                    width: 100%;
+                    height: 100%;
+                    object-fit: cover;
+                  }
+
+                  .avatar-placeholder {
+                    width: 100%;
+                    height: 100%;
+                    background: #1890ff;
+                    color: white;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    font-size: 18px;
+                    font-weight: 600;
+                  }
+                }
+
+                .designer-info {
+                  flex: 1;
+
+                  .designer-name {
+                    display: flex;
+                    align-items: center;
+                    gap: 8px;
+                    margin-bottom: 8px;
+                    font-size: 16px;
+                    font-weight: 600;
+                    color: #333333;
+
+                    .leader-badge {
+                      padding: 2px 8px;
+                      background: #faad14;
+                      color: white;
+                      border-radius: 12px;
+                      font-size: 10px;
+                    }
+                  }
+
+                  .designer-status {
+                    display: flex;
+                    align-items: center;
+                    gap: 8px;
+                    margin-bottom: 8px;
+                    font-size: 14px;
+
+                    .status-dot {
+                      width: 8px;
+                      height: 8px;
+                      border-radius: 50%;
+                    }
+
+                    .status-text {
+                      color: #666666;
+                    }
+
+                    .idle-days {
+                      color: #ff4d4f;
+                      font-size: 12px;
+                    }
+                  }
+
+                  .workload-bar {
+                    position: relative;
+                    width: 100%;
+                    height: 6px;
+                    background: #f0f0f0;
+                    border-radius: 3px;
+                    overflow: hidden;
+
+                    .workload-fill {
+                      height: 100%;
+                      background: linear-gradient(90deg, #52c41a 0%, #faad14 50%, #ff4d4f 100%);
+                      transition: width 0.3s ease;
+                    }
+
+                    .workload-text {
+                      position: absolute;
+                      right: 0;
+                      top: -20px;
+                      font-size: 12px;
+                      color: #666666;
+                    }
+                  }
+                }
+
+                .assignment-actions {
+                  .assign-btn, .remove-btn {
+                    padding: 6px 12px;
+                    border: none;
+                    border-radius: 6px;
+                    font-size: 12px;
+                    cursor: pointer;
+                    transition: all 0.3s ease;
+                  }
+
+                  .assign-btn {
+                    background: #1890ff;
+                    color: white;
+
+                    &:hover {
+                      background: #40a9ff;
+                    }
+                  }
+
+                  .remove-btn {
+                    background: #ff4d4f;
+                    color: white;
+
+                    &:hover {
+                      background: #ff7875;
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .cross-team-section {
+    margin-bottom: 32px;
+
+    .section-title {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 16px;
+
+      h4 {
+        margin: 0;
+        font-size: 16px;
+        font-weight: 600;
+        color: #333333;
+      }
+
+      .toggle-btn {
+        padding: 8px 16px;
+        background: #722ed1;
+        color: white;
+        border: none;
+        border-radius: 6px;
+        font-size: 14px;
+        cursor: pointer;
+        transition: all 0.3s ease;
+
+        &:hover {
+          background: #9254de;
+        }
+      }
+    }
+
+    .selected-collaborators {
+      margin-bottom: 20px;
+
+      h5 {
+        margin: 0 0 12px 0;
+        font-size: 14px;
+        font-weight: 600;
+        color: #666666;
+      }
+
+      .collaborator-list {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 12px;
+
+        .collaborator-item {
+          display: flex;
+          align-items: center;
+          gap: 12px;
+          padding: 12px 16px;
+          background: #f9f0ff;
+          border: 1px solid #d3adf7;
+          border-radius: 8px;
+
+          .designer-info {
+            display: flex;
+            flex-direction: column;
+            gap: 4px;
+
+            .designer-name {
+              font-size: 14px;
+              font-weight: 600;
+              color: #333333;
+            }
+
+            .team-name {
+              font-size: 12px;
+              color: #722ed1;
+            }
+          }
+
+          .remove-collaborator-btn {
+            width: 20px;
+            height: 20px;
+            border: none;
+            background: #ff4d4f;
+            color: white;
+            border-radius: 50%;
+            cursor: pointer;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: 14px;
+
+            &:hover {
+              background: #ff7875;
+            }
+          }
+        }
+      }
+    }
+
+    .cross-team-selection {
+      .cross-team-grid {
+        display: flex;
+        flex-direction: column;
+        gap: 20px;
+
+        .cross-team-group {
+          h6 {
+            margin: 0 0 12px 0;
+            font-size: 14px;
+            font-weight: 600;
+            color: #722ed1;
+          }
+
+          .designer-list {
+            display: grid;
+            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+            gap: 12px;
+
+            .designer-item {
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+              padding: 12px 16px;
+              border: 1px solid #e8e8e8;
+              border-radius: 8px;
+              background: #ffffff;
+              cursor: pointer;
+              transition: all 0.3s ease;
+
+              &:hover {
+                border-color: #722ed1;
+                box-shadow: 0 2px 8px rgba(114, 46, 209, 0.1);
+              }
+
+              &.selected {
+                border-color: #722ed1;
+                background: #f9f0ff;
+              }
+
+              .designer-info {
+                .designer-name {
+                  font-size: 14px;
+                  font-weight: 600;
+                  color: #333333;
+                  margin-bottom: 4px;
+                }
+
+                .designer-status {
+                  display: flex;
+                  align-items: center;
+                  gap: 6px;
+                  font-size: 12px;
+
+                  .status-dot {
+                    width: 6px;
+                    height: 6px;
+                    border-radius: 50%;
+                  }
+
+                  .status-text {
+                    color: #666666;
+                  }
+                }
+              }
+
+              .selection-actions {
+                .add-btn, .remove-btn {
+                  padding: 4px 8px;
+                  border: none;
+                  border-radius: 4px;
+                  font-size: 12px;
+                  cursor: pointer;
+                  transition: all 0.3s ease;
+                }
+
+                .add-btn {
+                  background: #722ed1;
+                  color: white;
+
+                  &:hover {
+                    background: #9254de;
+                  }
+                }
+
+                .remove-btn {
+                  background: #ff4d4f;
+                  color: white;
+
+                  &:hover {
+                    background: #ff7875;
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .notes-section {
+    margin-bottom: 32px;
+
+    label {
+      display: block;
+      margin-bottom: 8px;
+      font-size: 14px;
+      font-weight: 600;
+      color: #333333;
+    }
+
+    .notes-textarea {
+      width: 100%;
+      padding: 12px 16px;
+      border: 1px solid #d9d9d9;
+      border-radius: 8px;
+      font-size: 14px;
+      resize: vertical;
+      font-family: inherit;
+      transition: all 0.3s ease;
+
+      &:focus {
+        outline: none;
+        border-color: #1890ff;
+        box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+      }
+    }
+  }
+
+  .assignment-summary-section {
+    border-top: 2px solid #e8e8e8;
+    padding-top: 20px;
+
+    h4 {
+      margin: 0 0 16px 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: #333333;
+    }
+
+    .summary-content {
+      .summary-item {
+        display: flex;
+        align-items: flex-start;
+        gap: 12px;
+        margin-bottom: 16px;
+
+        .label {
+          font-size: 14px;
+          font-weight: 600;
+          color: #666666;
+          min-width: 80px;
+        }
+
+        .value {
+          font-size: 14px;
+          color: #333333;
+        }
+
+        .assignment-list {
+          display: flex;
+          flex-direction: column;
+          gap: 8px;
+
+          .assignment-summary-item {
+            display: flex;
+            align-items: center;
+            gap: 12px;
+
+            .project-name {
+              font-size: 14px;
+              font-weight: 500;
+              color: #333333;
+            }
+
+            .assigned-designers {
+              display: flex;
+              gap: 6px;
+
+              .designer-tag {
+                padding: 2px 8px;
+                background: #e6f7ff;
+                color: #1890ff;
+                border-radius: 12px;
+                font-size: 12px;
+              }
+            }
+          }
+        }
+
+        .collaborator-tags {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 6px;
+
+          .collaborator-tag {
+            padding: 2px 8px;
+            background: #f9f0ff;
+            color: #722ed1;
+            border-radius: 12px;
+            font-size: 12px;
+          }
+        }
+      }
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .designer-assignment-container {
+    padding: 16px;
+
+    .section-header {
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 12px;
+    }
+
+    .team-selection-section {
+      .team-grid {
+        grid-template-columns: 1fr;
+      }
+    }
+
+    .quotation-assignment-section {
+      .assignment-grid {
+        .assignment-card {
+          .designer-selection {
+            .available-designers {
+              .designer-list {
+                .designer-item {
+                  flex-direction: column;
+                  align-items: flex-start;
+                  gap: 12px;
+
+                  .designer-info {
+                    width: 100%;
+                  }
+
+                  .assignment-actions {
+                    align-self: flex-end;
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    .cross-team-section {
+      .cross-team-selection {
+        .cross-team-grid {
+          .cross-team-group {
+            .designer-list {
+              grid-template-columns: 1fr;
+            }
+          }
+        }
+      }
+    }
+  }
+}

+ 315 - 0
src/app/pages/designer/project-detail/components/designer-assignment/designer-assignment.component.ts

@@ -0,0 +1,315 @@
+import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+export interface Designer {
+  id: string;
+  name: string;
+  avatar?: string;
+  teamId: string;
+  teamName: string;
+  isTeamLeader: boolean;
+  status: 'idle' | 'busy' | 'reviewing';
+  idleDays: number;
+  recentOrders: number;
+  lastOrderDate?: string;
+  reviewDates: string[]; // 对图日期,这些日期不能安排其他工作
+  workload: number; // 当前工作量 (0-100)
+  skills: string[]; // 技能标签
+}
+
+export interface ProjectTeam {
+  id: string;
+  name: string;
+  leaderId: string;
+  leaderName: string;
+  members: Designer[];
+}
+
+export interface QuotationAssignment {
+  quotationItemId: string;
+  quotationItemName: string;
+  assignedDesigners: string[];
+  estimatedHours?: number;
+}
+
+export interface DesignerAssignmentData {
+  primaryTeamId: string;
+  quotationAssignments: QuotationAssignment[];
+  crossTeamCollaborators: string[]; // 跨组合作的设计师ID
+  notes?: string;
+}
+
+@Component({
+  selector: 'app-designer-assignment',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './designer-assignment.component.html',
+  styleUrls: ['./designer-assignment.component.scss']
+})
+export class DesignerAssignmentComponent implements OnInit {
+  @Input() quotationItems: any[] = [];
+  @Input() initialAssignment?: DesignerAssignmentData;
+  @Output() assignmentChange = new EventEmitter<DesignerAssignmentData>();
+  @Output() designerClick = new EventEmitter<Designer>();
+
+  // 模拟数据 - 实际项目中应该从服务获取
+  projectTeams: ProjectTeam[] = [
+    {
+      id: 'team-1',
+      name: '家装设计组',
+      leaderId: 'designer-1',
+      leaderName: '张组长',
+      members: [
+        {
+          id: 'designer-1',
+          name: '张组长',
+          teamId: 'team-1',
+          teamName: '家装设计组',
+          isTeamLeader: true,
+          status: 'busy',
+          idleDays: 0,
+          recentOrders: 3,
+          lastOrderDate: '2024-01-15',
+          reviewDates: ['2024-01-20', '2024-01-25'],
+          workload: 85,
+          skills: ['家装设计', '软装搭配', '项目管理']
+        },
+        {
+          id: 'designer-2',
+          name: '李设计师',
+          teamId: 'team-1',
+          teamName: '家装设计组',
+          isTeamLeader: false,
+          status: 'idle',
+          idleDays: 5,
+          recentOrders: 1,
+          lastOrderDate: '2024-01-10',
+          reviewDates: [],
+          workload: 30,
+          skills: ['家装设计', '3D建模']
+        }
+      ]
+    },
+    {
+      id: 'team-2',
+      name: '工装设计组',
+      leaderId: 'designer-3',
+      leaderName: '王组长',
+      members: [
+        {
+          id: 'designer-3',
+          name: '王组长',
+          teamId: 'team-2',
+          teamName: '工装设计组',
+          isTeamLeader: true,
+          status: 'reviewing',
+          idleDays: 0,
+          recentOrders: 2,
+          lastOrderDate: '2024-01-14',
+          reviewDates: ['2024-01-18', '2024-01-22'],
+          workload: 70,
+          skills: ['工装设计', '商业空间', '项目管理']
+        },
+        {
+          id: 'designer-4',
+          name: '赵设计师',
+          teamId: 'team-2',
+          teamName: '工装设计组',
+          isTeamLeader: false,
+          status: 'idle',
+          idleDays: 12,
+          recentOrders: 0,
+          lastOrderDate: '2024-01-03',
+          reviewDates: [],
+          workload: 10,
+          skills: ['工装设计', '效果图制作']
+        }
+      ]
+    }
+  ];
+
+  assignmentData: DesignerAssignmentData = {
+    primaryTeamId: '',
+    quotationAssignments: [],
+    crossTeamCollaborators: [],
+    notes: ''
+  };
+
+  selectedTeamId = '';
+  showCrossTeamSelection = false;
+  availableCrossTeamDesigners: Designer[] = [];
+
+  constructor() {}
+
+  ngOnInit() {
+    if (this.initialAssignment) {
+      this.assignmentData = { ...this.initialAssignment };
+      this.selectedTeamId = this.assignmentData.primaryTeamId;
+    }
+
+    // 初始化报价分配
+    this.initializeQuotationAssignments();
+  }
+
+  // 初始化报价分配
+  initializeQuotationAssignments() {
+    if (this.quotationItems.length > 0 && this.assignmentData.quotationAssignments.length === 0) {
+      this.assignmentData.quotationAssignments = this.quotationItems.map(item => ({
+        quotationItemId: item.id,
+        quotationItemName: item.name,
+        assignedDesigners: [],
+        estimatedHours: 0
+      }));
+    }
+  }
+
+  // 选择主要项目组
+  selectPrimaryTeam(teamId: string) {
+    this.selectedTeamId = teamId;
+    this.assignmentData.primaryTeamId = teamId;
+    
+    // 清空之前的分配
+    this.assignmentData.quotationAssignments.forEach(assignment => {
+      assignment.assignedDesigners = [];
+    });
+    
+    this.updateAvailableCrossTeamDesigners();
+    this.emitAssignmentChange();
+  }
+
+  // 获取选中的项目组
+  getSelectedTeam(): ProjectTeam | undefined {
+    return this.projectTeams.find(team => team.id === this.selectedTeamId);
+  }
+
+  // 分配设计师到报价项目
+  assignDesignerToQuotation(quotationId: string, designerId: string) {
+    const assignment = this.assignmentData.quotationAssignments.find(a => a.quotationItemId === quotationId);
+    if (assignment) {
+      if (!assignment.assignedDesigners.includes(designerId)) {
+        assignment.assignedDesigners.push(designerId);
+        this.emitAssignmentChange();
+      }
+    }
+  }
+
+  // 移除设计师分配
+  removeDesignerFromQuotation(quotationId: string, designerId: string) {
+    const assignment = this.assignmentData.quotationAssignments.find(a => a.quotationItemId === quotationId);
+    if (assignment) {
+      assignment.assignedDesigners = assignment.assignedDesigners.filter(id => id !== designerId);
+      this.emitAssignmentChange();
+    }
+  }
+
+  // 更新可用的跨组合作设计师
+  updateAvailableCrossTeamDesigners() {
+    this.availableCrossTeamDesigners = [];
+    this.projectTeams.forEach(team => {
+      if (team.id !== this.selectedTeamId) {
+        this.availableCrossTeamDesigners.push(...team.members);
+      }
+    });
+  }
+
+  // 添加跨组合作设计师
+  addCrossTeamCollaborator(designerId: string) {
+    if (!this.assignmentData.crossTeamCollaborators.includes(designerId)) {
+      this.assignmentData.crossTeamCollaborators.push(designerId);
+      this.emitAssignmentChange();
+    }
+  }
+
+  // 移除跨组合作设计师
+  removeCrossTeamCollaborator(designerId: string) {
+    this.assignmentData.crossTeamCollaborators = this.assignmentData.crossTeamCollaborators.filter(id => id !== designerId);
+    this.emitAssignmentChange();
+  }
+
+  // 获取设计师信息
+  getDesignerById(designerId: string): Designer | undefined {
+    for (const team of this.projectTeams) {
+      const designer = team.members.find(d => d.id === designerId);
+      if (designer) return designer;
+    }
+    return undefined;
+  }
+
+  // 获取设计师状态颜色
+  getDesignerStatusColor(status: string): string {
+    switch (status) {
+      case 'idle': return '#52c41a';
+      case 'busy': return '#faad14';
+      case 'reviewing': return '#1890ff';
+      default: return '#d9d9d9';
+    }
+  }
+
+  // 获取设计师状态文本
+  getDesignerStatusText(status: string): string {
+    switch (status) {
+      case 'idle': return '空闲';
+      case 'busy': return '忙碌';
+      case 'reviewing': return '对图中';
+      default: return '未知';
+    }
+  }
+
+  // 获取工作量状态
+  getWorkloadStatus(workload: number): 'low' | 'medium' | 'high' {
+    if (workload < 30) return 'low';
+    if (workload < 70) return 'medium';
+    return 'high';
+  }
+
+  // 点击设计师
+  onDesignerClick(designer: Designer) {
+    this.designerClick.emit(designer);
+  }
+
+  // 发送分配变化事件
+  emitAssignmentChange() {
+    this.assignmentChange.emit({ ...this.assignmentData });
+  }
+
+  // 自动分配建议
+  getAutoAssignmentSuggestion(): string {
+    const selectedTeam = this.getSelectedTeam();
+    if (!selectedTeam) return '';
+
+    const idleDesigners = selectedTeam.members.filter(d => d.status === 'idle' && d.workload < 50);
+    const busyDesigners = selectedTeam.members.filter(d => d.workload >= 70);
+
+    let suggestion = '';
+    if (idleDesigners.length > 0) {
+      suggestion += `建议优先分配给空闲设计师:${idleDesigners.map(d => d.name).join('、')}。`;
+    }
+    if (busyDesigners.length > 0) {
+      suggestion += `注意:${busyDesigners.map(d => d.name).join('、')} 工作量较重。`;
+    }
+
+    return suggestion;
+  }
+
+  // 检查是否有冲突的对图日期
+  hasReviewDateConflict(designerId: string, projectStartDate?: string): boolean {
+    const designer = this.getDesignerById(designerId);
+    if (!designer || !projectStartDate) return false;
+
+    // 简单的日期冲突检查逻辑
+    const startDate = new Date(projectStartDate);
+    const endDate = new Date(startDate.getTime() + 30 * 24 * 60 * 60 * 1000); // 假设项目周期30天
+
+    return designer.reviewDates.some(reviewDate => {
+      const review = new Date(reviewDate);
+      return review >= startDate && review <= endDate;
+    });
+  }
+
+  // 获取团队空闲人数的安全方法
+  getIdleCount(team: ProjectTeam): number {
+    if (!team || !team.members) return 0;
+    return team.members.filter(m => m && m.status === 'idle').length;
+  }
+}

+ 121 - 0
src/app/pages/designer/project-detail/components/designer-calendar/designer-calendar.component.html

@@ -0,0 +1,121 @@
+<div class="designer-calendar-overlay" *ngIf="visible" (click)="onClose()">
+  <div class="designer-calendar-modal" (click)="$event.stopPropagation()" *ngIf="calendarData">
+    <!-- 模态框头部 -->
+    <div class="modal-header">
+      <h3>{{ calendarData.designerName }} - 工作日历</h3>
+      <button class="close-btn" (click)="onClose()">
+        <i class="icon-close">×</i>
+      </button>
+    </div>
+
+    <!-- 设计师统计信息 -->
+    <div class="designer-stats">
+      <div class="stats-grid">
+        <div class="stat-item">
+          <span class="stat-label">工作状态</span>
+          <span class="stat-value" [style.color]="getStatusColor(calendarData.stats)">
+            {{ getStatusText(calendarData.stats) }}
+          </span>
+        </div>
+        <div class="stat-item">
+          <span class="stat-label">空闲天数</span>
+          <span class="stat-value">{{ calendarData.stats.idleDays }}天</span>
+        </div>
+        <div class="stat-item">
+          <span class="stat-label">最近接单</span>
+          <span class="stat-value">{{ calendarData.stats.recentOrders }}单</span>
+        </div>
+        <div class="stat-item">
+          <span class="stat-label">总订单数</span>
+          <span class="stat-value">{{ calendarData.stats.totalOrders }}单</span>
+        </div>
+        <div class="stat-item" *ngIf="calendarData.stats.stagnantProjects > 0">
+          <span class="stat-label">停滞项目</span>
+          <span class="stat-value warning">{{ calendarData.stats.stagnantProjects }}个</span>
+        </div>
+        <div class="stat-item" *ngIf="calendarData.stats.lastOrderDate">
+          <span class="stat-label">最后接单</span>
+          <span class="stat-value">{{ calendarData.stats.lastOrderDate | date:'MM-dd' }}</span>
+        </div>
+      </div>
+      
+      <!-- 工作负荷指示器 -->
+      <div class="workload-indicator">
+        <span class="workload-label">工作负荷</span>
+        <div class="workload-bar">
+          <div class="workload-fill" 
+               [style.width.%]="getWorkloadPercentage(calendarData.stats)"
+               [class.high]="getWorkloadPercentage(calendarData.stats) > 80"
+               [class.medium]="getWorkloadPercentage(calendarData.stats) > 50 && getWorkloadPercentage(calendarData.stats) <= 80"
+               [class.low]="getWorkloadPercentage(calendarData.stats) <= 50">
+          </div>
+        </div>
+        <span class="workload-percentage">{{ getWorkloadPercentage(calendarData.stats) | number:'1.0-0' }}%</span>
+      </div>
+    </div>
+
+    <!-- 日历导航 -->
+    <div class="calendar-nav">
+      <button class="nav-btn" (click)="previousMonth()">
+        <i class="icon-prev">‹</i>
+      </button>
+      <h4 class="month-title">{{ formatDate(calendarData.currentMonth) }}</h4>
+      <button class="nav-btn" (click)="nextMonth()">
+        <i class="icon-next">›</i>
+      </button>
+    </div>
+
+    <!-- 日历主体 -->
+    <div class="calendar-container">
+      <!-- 星期标题 -->
+      <div class="calendar-header">
+        <div class="weekday" *ngFor="let day of weekDays">{{ day }}</div>
+      </div>
+
+      <!-- 日历网格 -->
+      <div class="calendar-grid">
+        <div *ngFor="let day of calendarData.calendar" 
+             [class]="getDayClass(day)"
+             [title]="day.tooltip"
+             (click)="onDayClick(day)">
+          <span class="day-number">{{ day.date.getDate() }}</span>
+          <div class="day-indicators" *ngIf="day.date.getMonth() === currentMonth.getMonth()">
+            <span class="order-count" *ngIf="day.orderCount > 0">{{ day.orderCount }}</span>
+            <span class="review-indicator" *ngIf="day.isReviewDay">对图</span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 图例说明 -->
+    <div class="calendar-legend">
+      <div class="legend-item">
+        <span class="legend-color idle"></span>
+        <span class="legend-text">空闲可分配</span>
+      </div>
+      <div class="legend-item">
+        <span class="legend-color busy"></span>
+        <span class="legend-text">有订单</span>
+      </div>
+      <div class="legend-item">
+        <span class="legend-color review"></span>
+        <span class="legend-text">对图日期</span>
+      </div>
+      <div class="legend-item">
+        <span class="legend-color today"></span>
+        <span class="legend-text">今天</span>
+      </div>
+    </div>
+
+    <!-- 操作提示 -->
+    <div class="calendar-tips">
+      <p><strong>提示:</strong></p>
+      <ul>
+        <li>绿色日期表示空闲,可以分配新订单</li>
+        <li>蓝色日期表示有订单,但仍可分配(不超过2单)</li>
+        <li>红色日期表示对图日期,完全不能分配其他工作</li>
+        <li>点击空闲日期可以快速分配订单</li>
+      </ul>
+    </div>
+  </div>
+</div>

+ 450 - 0
src/app/pages/designer/project-detail/components/designer-calendar/designer-calendar.component.scss

@@ -0,0 +1,450 @@
+.designer-calendar-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+  backdrop-filter: blur(4px);
+}
+
+.designer-calendar-modal {
+  background: white;
+  border-radius: 16px;
+  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
+  max-width: 800px;
+  width: 90vw;
+  max-height: 90vh;
+  overflow-y: auto;
+  animation: modalSlideIn 0.3s ease-out;
+}
+
+@keyframes modalSlideIn {
+  from {
+    opacity: 0;
+    transform: translateY(-20px) scale(0.95);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0) scale(1);
+  }
+}
+
+.modal-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 24px 24px 16px;
+  border-bottom: 1px solid #f0f0f0;
+
+  h3 {
+    margin: 0;
+    font-size: 20px;
+    font-weight: 600;
+    color: #2c3e50;
+  }
+
+  .close-btn {
+    background: none;
+    border: none;
+    font-size: 24px;
+    color: #95a5a6;
+    cursor: pointer;
+    padding: 4px;
+    border-radius: 4px;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background: #f8f9fa;
+      color: #e74c3c;
+    }
+  }
+}
+
+.designer-stats {
+  padding: 20px 24px;
+  background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+  border-bottom: 1px solid #dee2e6;
+
+  .stats-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
+    gap: 16px;
+    margin-bottom: 20px;
+  }
+
+  .stat-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 12px;
+    background: white;
+    border-radius: 8px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+
+    .stat-label {
+      font-size: 12px;
+      color: #6c757d;
+      margin-bottom: 4px;
+    }
+
+    .stat-value {
+      font-size: 16px;
+      font-weight: 600;
+      color: #2c3e50;
+
+      &.warning {
+        color: #e74c3c;
+      }
+    }
+  }
+
+  .workload-indicator {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 12px;
+    background: white;
+    border-radius: 8px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+
+    .workload-label {
+      font-size: 14px;
+      color: #6c757d;
+      min-width: 60px;
+    }
+
+    .workload-bar {
+      flex: 1;
+      height: 8px;
+      background: #e9ecef;
+      border-radius: 4px;
+      overflow: hidden;
+
+      .workload-fill {
+        height: 100%;
+        border-radius: 4px;
+        transition: width 0.3s ease;
+
+        &.low {
+          background: linear-gradient(90deg, #28a745, #20c997);
+        }
+
+        &.medium {
+          background: linear-gradient(90deg, #ffc107, #fd7e14);
+        }
+
+        &.high {
+          background: linear-gradient(90deg, #dc3545, #e74c3c);
+        }
+      }
+    }
+
+    .workload-percentage {
+      font-size: 14px;
+      font-weight: 600;
+      color: #495057;
+      min-width: 40px;
+      text-align: right;
+    }
+  }
+}
+
+.calendar-nav {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 24px;
+  border-bottom: 1px solid #f0f0f0;
+
+  .nav-btn {
+    background: #f8f9fa;
+    border: 1px solid #dee2e6;
+    border-radius: 6px;
+    width: 36px;
+    height: 36px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    font-size: 18px;
+    color: #495057;
+
+    &:hover {
+      background: #e9ecef;
+      border-color: #adb5bd;
+    }
+  }
+
+  .month-title {
+    margin: 0;
+    font-size: 18px;
+    font-weight: 600;
+    color: #2c3e50;
+  }
+}
+
+.calendar-container {
+  padding: 0 24px 20px;
+}
+
+.calendar-header {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  gap: 1px;
+  margin-bottom: 8px;
+
+  .weekday {
+    padding: 12px 4px;
+    text-align: center;
+    font-size: 14px;
+    font-weight: 600;
+    color: #6c757d;
+    background: #f8f9fa;
+  }
+}
+
+.calendar-grid {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  gap: 1px;
+  background: #dee2e6;
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+.calendar-day {
+  background: white;
+  min-height: 60px;
+  padding: 8px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: flex-start;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  position: relative;
+
+  .day-number {
+    font-size: 14px;
+    font-weight: 500;
+    margin-bottom: 4px;
+  }
+
+  .day-indicators {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 2px;
+
+    .order-count {
+      background: #007bff;
+      color: white;
+      font-size: 10px;
+      padding: 2px 6px;
+      border-radius: 10px;
+      min-width: 16px;
+      text-align: center;
+    }
+
+    .review-indicator {
+      background: #dc3545;
+      color: white;
+      font-size: 9px;
+      padding: 1px 4px;
+      border-radius: 6px;
+    }
+  }
+
+  &.other-month {
+    color: #adb5bd;
+    background: #f8f9fa;
+
+    .day-indicators {
+      opacity: 0.5;
+    }
+  }
+
+  &.weekend {
+    background: #fff5f5;
+  }
+
+  &.today {
+    background: #e3f2fd;
+    border: 2px solid #2196f3;
+
+    .day-number {
+      color: #1976d2;
+      font-weight: 700;
+    }
+  }
+
+  &.idle {
+    background: #e8f5e8;
+    border: 1px solid #4caf50;
+
+    &:hover {
+      background: #c8e6c9;
+      transform: scale(1.05);
+    }
+
+    .day-number {
+      color: #2e7d32;
+    }
+  }
+
+  &.busy {
+    background: #e3f2fd;
+
+    .day-number {
+      color: #1565c0;
+    }
+
+    &:hover {
+      background: #bbdefb;
+    }
+  }
+
+  &.review {
+    background: #ffebee;
+    border: 1px solid #f44336;
+    cursor: not-allowed;
+
+    .day-number {
+      color: #c62828;
+    }
+
+    &:hover {
+      transform: none;
+    }
+  }
+
+  &.stagnant {
+    background: #fff3e0;
+    border: 1px solid #ff9800;
+
+    .day-number {
+      color: #ef6c00;
+    }
+  }
+}
+
+.calendar-legend {
+  display: flex;
+  justify-content: center;
+  gap: 20px;
+  padding: 16px 24px;
+  border-top: 1px solid #f0f0f0;
+  background: #f8f9fa;
+
+  .legend-item {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+
+    .legend-color {
+      width: 12px;
+      height: 12px;
+      border-radius: 2px;
+
+      &.idle {
+        background: #4caf50;
+      }
+
+      &.busy {
+        background: #2196f3;
+      }
+
+      &.review {
+        background: #f44336;
+      }
+
+      &.today {
+        background: #1976d2;
+        border: 1px solid #0d47a1;
+      }
+    }
+
+    .legend-text {
+      font-size: 12px;
+      color: #6c757d;
+    }
+  }
+}
+
+.calendar-tips {
+  padding: 16px 24px;
+  background: #f8f9fa;
+  border-top: 1px solid #dee2e6;
+
+  p {
+    margin: 0 0 8px;
+    font-size: 14px;
+    color: #495057;
+  }
+
+  ul {
+    margin: 0;
+    padding-left: 20px;
+    font-size: 13px;
+    color: #6c757d;
+    line-height: 1.5;
+
+    li {
+      margin-bottom: 4px;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .designer-calendar-modal {
+    width: 95vw;
+    margin: 10px;
+  }
+
+  .designer-stats {
+    .stats-grid {
+      grid-template-columns: repeat(2, 1fr);
+      gap: 12px;
+    }
+
+    .workload-indicator {
+      flex-direction: column;
+      align-items: stretch;
+      gap: 8px;
+
+      .workload-label {
+        min-width: auto;
+        text-align: center;
+      }
+    }
+  }
+
+  .calendar-legend {
+    flex-wrap: wrap;
+    gap: 12px;
+  }
+
+  .calendar-day {
+    min-height: 50px;
+    padding: 4px;
+
+    .day-number {
+      font-size: 12px;
+    }
+
+    .day-indicators {
+      .order-count,
+      .review-indicator {
+        font-size: 8px;
+        padding: 1px 3px;
+      }
+    }
+  }
+}

+ 70 - 0
src/app/pages/designer/project-detail/components/order-creation/order-creation.component.html

@@ -0,0 +1,70 @@
+<div class="order-creation-container">
+  <div class="section-header">
+    <h3>订单创建信息</h3>
+    <span class="required-note">* 为必填项</span>
+    <div class="header-actions">
+      <button type="button" class="btn-secondary" (click)="toggleStagnantFilter()">
+        @if (hideStagnantProjects) { 隐藏停滞项目:已开启 } @else { 隐藏停滞项目:未开启 }
+      </button>
+    </div>
+  </div>
+
+  <form [formGroup]="orderForm" class="order-form">
+    <!-- 订单金额 -->
+    <div class="form-group">
+      <label for="orderAmount" class="form-label required">订单金额</label>
+      <div class="input-wrapper">
+        <input
+          id="orderAmount"
+          type="number"
+          formControlName="orderAmount"
+          class="form-input"
+          placeholder="请输入订单金额"
+          min="0.01"
+          step="0.01"
+        />
+        <span class="currency-unit">元</span>
+      </div>
+      @if (orderAmount?.invalid && orderAmount?.touched) {
+        <div class="error-message">
+          @if (orderAmount?.errors?.['required']) {
+            <span>订单金额为必填项</span>
+          }
+          @if (orderAmount?.errors?.['min']) {
+            <span>订单金额必须大于0</span>
+          }
+          @if (orderAmount?.errors?.['pattern']) {
+            <span>请输入有效的金额格式</span>
+          }
+        </div>
+      }
+    </div>
+
+
+
+    <!-- 交付时间 -->
+    <div class="form-group">
+      <label for="deliveryTime" class="form-label required">交付时间</label>
+      <input
+        id="deliveryTime"
+        type="date"
+        formControlName="deliveryTime"
+        class="form-input"
+      />
+      @if (deliveryTime?.invalid && deliveryTime?.touched) {
+        <div class="error-message">
+          <span>交付时间为必填项</span>
+        </div>
+      }
+    </div>
+
+    <!-- 报价明细 -->
+    <div class="form-group">
+      <app-quotation-details
+        [initialData]="quotationData"
+        (dataChange)="onQuotationDataChange($event)"
+      ></app-quotation-details>
+    </div>
+
+  </form>
+</div>

+ 79 - 0
src/app/pages/designer/project-detail/components/order-creation/order-creation.component.scss

@@ -0,0 +1,79 @@
+/* 订单创建组件样式 */
+.order-creation-container {
+  padding: 20px;
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.order-form {
+  display: grid;
+  gap: 16px;
+}
+
+.form-group {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+
+  label {
+    font-weight: 600;
+    color: #333;
+    font-size: 14px;
+  }
+
+  input, select, textarea {
+    padding: 12px;
+    border: 1px solid #ddd;
+    border-radius: 8px;
+    font-size: 14px;
+    transition: border-color 0.2s ease;
+
+    &:focus {
+      outline: none;
+      border-color: #007aff;
+      box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
+    }
+  }
+
+  textarea {
+    min-height: 80px;
+    resize: vertical;
+  }
+}
+
+.form-actions {
+  display: flex;
+  gap: 12px;
+  justify-content: flex-end;
+  margin-top: 20px;
+
+  button {
+    padding: 12px 24px;
+    border: none;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 600;
+    cursor: pointer;
+    transition: all 0.2s ease;
+
+    &.primary {
+      background: #007aff;
+      color: white;
+
+      &:hover {
+        background: #0056cc;
+      }
+    }
+
+    &.secondary {
+      background: #f8f9fa;
+      color: #666;
+      border: 1px solid #ddd;
+
+      &:hover {
+        background: #e9ecef;
+      }
+    }
+  }
+}

+ 105 - 0
src/app/pages/designer/project-detail/components/order-creation/order-creation.component.ts

@@ -0,0 +1,105 @@
+import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
+import { CommonModule } from '@angular/common';
+import { QuotationDetailsComponent, QuotationData } from '../quotation-details/quotation-details.component';
+
+export interface OrderCreationData {
+  orderAmount: number;
+  deliveryTime: string;
+  quotationData?: QuotationData;
+}
+
+@Component({
+  selector: 'app-order-creation',
+  standalone: true,
+  imports: [CommonModule, ReactiveFormsModule, QuotationDetailsComponent],
+  templateUrl: './order-creation.component.html',
+  styleUrls: ['./order-creation.component.scss']
+})
+export class OrderCreationComponent implements OnInit {
+  @Input() initialData?: OrderCreationData;
+  @Output() dataChange = new EventEmitter<OrderCreationData>();
+  @Output() validityChange = new EventEmitter<boolean>();
+
+  orderForm: FormGroup;
+  quotationData?: QuotationData;
+  // 替换为停滞项目筛选的本地状态
+  hideStagnantProjects = false;
+
+  constructor(private fb: FormBuilder) {
+    this.orderForm = this.fb.group({
+      orderAmount: ['', [
+        Validators.required, 
+        Validators.min(0.01),
+        Validators.pattern(/^\d+(\.\d{1,2})?$/)
+      ]],
+      deliveryTime: ['', Validators.required]
+    });
+  }
+
+  ngOnInit() {
+    if (this.initialData) {
+      this.orderForm.patchValue(this.initialData);
+      this.quotationData = this.initialData.quotationData;
+    }
+
+    // 监听表单变化
+    this.orderForm.valueChanges.subscribe(value => {
+      this.emitDataChange();
+      this.validityChange.emit(this.isFormValid());
+    });
+
+    // 初始状态发送
+    this.validityChange.emit(this.isFormValid());
+  }
+
+  // 处理报价明细数据变化
+  onQuotationDataChange(data: QuotationData) {
+    this.quotationData = data;
+    this.emitDataChange();
+    this.validityChange.emit(this.isFormValid());
+  }
+
+  // 发送数据变化事件
+  private emitDataChange() {
+    const formData = this.orderForm.value;
+    const completeData: OrderCreationData = {
+      ...formData,
+      quotationData: this.quotationData
+    };
+    this.dataChange.emit(completeData);
+  }
+
+  // 检查表单有效性
+  private isFormValid(): boolean {
+    return this.orderForm.valid && !!this.quotationData && this.quotationData.items.length > 0;
+  }
+
+  // 获取表单控件
+  get orderAmount() { return this.orderForm.get('orderAmount'); }
+  get deliveryTime() { return this.orderForm.get('deliveryTime'); }
+
+  // 获取表单数据
+  getFormData(): OrderCreationData {
+    return {
+      ...this.orderForm.value,
+      quotationData: this.quotationData
+    };
+  }
+
+  // 重置表单
+  resetForm() {
+    this.orderForm.reset();
+    this.quotationData = undefined;
+  }
+
+  // 验证表单
+  validateForm(): boolean {
+    this.orderForm.markAllAsTouched();
+    return this.isFormValid();
+  }
+  // 切换停滞项目筛选(仅本地UI状态)
+  toggleStagnantFilter() {
+    this.hideStagnantProjects = !this.hideStagnantProjects;
+  }
+}

+ 323 - 0
src/app/pages/designer/project-detail/components/quotation-detail/quotation-detail.component.html

@@ -0,0 +1,323 @@
+<div class="quotation-detail-container">
+  <div class="section-header">
+    <h3>报价明细</h3>
+    <div class="header-actions">
+      @if (orderAmount && orderAmount > 0) {
+        <button 
+          class="ai-generate-btn" 
+          [disabled]="isAiGenerating"
+          (click)="generateAiQuotation()"
+        >
+          @if (isAiGenerating) {
+            <span class="loading-spinner"></span>
+            <span>AI生成中...</span>
+          } @else {
+            <span class="ai-icon">🤖</span>
+            <span>AI辅助生成</span>
+          }
+        </button>
+      }
+      <button class="reset-btn" (click)="resetQuotation()">重置</button>
+    </div>
+  </div>
+
+  <!-- 金额对比提示 -->
+  @if (orderAmount && orderAmount > 0) {
+    <div class="amount-comparison" [class]="getDifferenceStatus()">
+      <div class="comparison-item">
+        <span class="label">订单金额:</span>
+        <span class="amount">{{ orderAmount | number:'1.2-2' }} 元</span>
+      </div>
+      <div class="comparison-item">
+        <span class="label">报价总计:</span>
+        <span class="amount">{{ quotationData.totalAmount | number:'1.2-2' }} 元</span>
+      </div>
+      <div class="difference-indicator">
+        @if (getDifferenceStatus() === 'match') {
+          <span class="match">✓ 金额匹配</span>
+        } @else if (getDifferenceStatus() === 'over') {
+          <span class="over">⚠ 超出 {{ getAmountDifference() | number:'1.2-2' }} 元</span>
+        } @else {
+          <span class="under">⚠ 不足 {{ Math.abs(getAmountDifference()) | number:'1.2-2' }} 元</span>
+        }
+      </div>
+    </div>
+  }
+
+  <!-- 必填空间报价 -->
+  <div class="required-spaces-section">
+    <h4 class="section-title">
+      <span class="title-text">必填空间报价</span>
+      <span class="required-badge">必填</span>
+    </h4>
+    
+    <div class="quotation-items">
+      @for (item of quotationData.items; track item.id) {
+        @if (item.isRequired) {
+          <div class="quotation-item required-item">
+            <div class="item-header">
+              <div class="item-name">
+                <span class="name">{{ item.name }}</span>
+                @if (item.spaceType === 'bedroom' && item.roomIndex && item.roomIndex > 1) {
+                  <span class="room-index">(卧室{{ item.roomIndex }})</span>
+                }
+                <span class="required-indicator">*</span>
+              </div>
+              @if (item.spaceType === 'bedroom' && item.roomIndex && item.roomIndex > 1) {
+                <button class="remove-btn" (click)="removeItem(item.id)">
+                  <span class="remove-icon">×</span>
+                </button>
+              }
+            </div>
+            
+            <div class="item-content">
+              <div class="amount-input-wrapper">
+                <input
+                  type="number"
+                  [value]="item.amount"
+                  (input)="updateItemAmount(item.id, +$any($event.target).value)"
+                  class="amount-input"
+                  placeholder="0"
+                  min="0"
+                  step="0.01"
+                />
+                <span class="currency-unit">元</span>
+              </div>
+              
+              @if (item.description) {
+                <div class="item-description">{{ item.description }}</div>
+              }
+            </div>
+          </div>
+        }
+      }
+    </div>
+
+    <!-- 添加卧室按钮 -->
+    @if (getBedroomCount() < 3) {
+      <div class="add-bedroom-section">
+        <button class="add-bedroom-btn" (click)="addNewBedroom()">
+          <span class="add-icon">+</span>
+          <span>新增卧室</span>
+        </button>
+      </div>
+    }
+  </div>
+
+  <!-- 可选空间报价 -->
+  <div class="optional-spaces-section">
+    <h4 class="section-title">
+      <span class="title-text">可选空间报价</span>
+      <span class="optional-badge">可选</span>
+    </h4>
+    
+    <div class="quotation-items">
+      @for (item of quotationData.items; track item.id) {
+        @if (!item.isRequired && item.category === 'space') {
+          <div class="quotation-item optional-item">
+            <div class="item-header">
+              <div class="item-name">
+                <span class="name">{{ item.name }}</span>
+              </div>
+              <button class="remove-btn" (click)="removeItem(item.id)">
+                <span class="remove-icon">×</span>
+              </button>
+            </div>
+            
+            <div class="item-content">
+              <div class="amount-input-wrapper">
+                <input
+                  type="number"
+                  [value]="item.amount"
+                  (input)="updateItemAmount(item.id, +$any($event.target).value)"
+                  class="amount-input"
+                  placeholder="0"
+                  min="0"
+                  step="0.01"
+                />
+                <span class="currency-unit">元</span>
+              </div>
+              
+              @if (item.description) {
+                <div class="item-description">{{ item.description }}</div>
+              }
+            </div>
+          </div>
+        }
+      }
+    </div>
+
+    <!-- 添加可选空间 -->
+    @if (getAvailableOptionalSpaces().length > 0) {
+      <div class="add-optional-section">
+        @if (!showAddOptionalSpace) {
+          <button class="add-optional-btn" (click)="showAddOptionalSpace = true">
+            <span class="add-icon">+</span>
+            <span>添加空间</span>
+          </button>
+        } @else {
+          <div class="optional-space-selector">
+            <div class="selector-header">
+              <h5>选择要添加的空间</h5>
+              <button class="close-btn" (click)="showAddOptionalSpace = false">×</button>
+            </div>
+            <div class="space-options">
+              @for (space of getAvailableOptionalSpaces(); track space.id) {
+                <button 
+                  class="space-option-btn" 
+                  (click)="addOptionalSpace(space); showAddOptionalSpace = false"
+                >
+                  {{ space.name }}
+                </button>
+              }
+            </div>
+          </div>
+        }
+      </div>
+    }
+  </div>
+
+  <!-- 自定义项目 -->
+  @if (hasCustomItems()) {
+    <div class="custom-items-section">
+      <h4 class="section-title">
+        <span class="title-text">自定义项目</span>
+      </h4>
+      
+      <div class="quotation-items">
+        @for (item of quotationData.items; track item.id) {
+          @if (item.isCustom) {
+            <div class="quotation-item custom-item">
+              <div class="item-header">
+                <div class="item-name">
+                  <span class="name">{{ item.name }}</span>
+                  <span class="custom-tag">自定义</span>
+                </div>
+                <button class="remove-btn" (click)="removeItem(item.id)">
+                  <span class="remove-icon">×</span>
+                </button>
+              </div>
+              
+              <div class="item-content">
+                <div class="amount-input-wrapper">
+                  <input
+                    type="number"
+                    [value]="item.amount"
+                    (input)="updateItemAmount(item.id, +$any($event.target).value)"
+                    class="amount-input"
+                    placeholder="0"
+                    min="0"
+                    step="0.01"
+                  />
+                  <span class="currency-unit">元</span>
+                </div>
+                
+                @if (item.description) {
+                  <div class="item-description">{{ item.description }}</div>
+                }
+              </div>
+            </div>
+          }
+        }
+      </div>
+    </div>
+  }
+
+  <!-- 添加自定义项目 -->
+  <div class="add-custom-section">
+    @if (!showAddCustomItem) {
+      <button class="add-custom-btn" (click)="showAddCustomItem = true">
+        <span class="add-icon">+</span>
+        <span>添加自定义项目</span>
+      </button>
+    } @else {
+      <div class="custom-item-form">
+        <div class="form-header">
+          <h4>添加自定义项目</h4>
+          <button class="close-btn" (click)="showAddCustomItem = false">×</button>
+        </div>
+        
+        <div class="form-content">
+          <div class="form-group">
+            <label>项目名称 *</label>
+            <input
+              type="text"
+              [(ngModel)]="newCustomItem.name"
+              placeholder="请输入项目名称"
+              class="form-input"
+            />
+          </div>
+          
+          <div class="form-group">
+            <label>金额</label>
+            <div class="amount-input-wrapper">
+              <input
+                type="number"
+                [(ngModel)]="newCustomItem.amount"
+                placeholder="0"
+                class="form-input"
+                min="0"
+                step="0.01"
+              />
+              <span class="currency-unit">元</span>
+            </div>
+          </div>
+          
+          <div class="form-group">
+            <label>描述</label>
+            <textarea
+              [(ngModel)]="newCustomItem.description"
+              placeholder="项目描述(可选)"
+              class="form-textarea"
+              rows="2"
+            ></textarea>
+          </div>
+          
+          <div class="form-actions">
+            <button class="cancel-btn" (click)="showAddCustomItem = false">取消</button>
+            <button class="confirm-btn" (click)="addCustomItem()">添加</button>
+          </div>
+        </div>
+      </div>
+    }
+  </div>
+
+
+
+  <!-- 空间统计信息 -->
+  <div class="space-statistics-section">
+    <h4 class="section-title">空间分配统计</h4>
+    <div class="statistics-grid">
+      <div class="statistic-item">
+        <span class="stat-label">客餐厅占比:</span>
+        <span class="stat-value">{{ getSpaceAllocationRatios().livingDiningRatio | number:'1.0-2' }}%</span>
+      </div>
+      <div class="statistic-item">
+        <span class="stat-label">卧室占比:</span>
+        <span class="stat-value">{{ getSpaceAllocationRatios().bedroomRatio | number:'1.0-2' }}%</span>
+      </div>
+      <div class="statistic-item">
+        <span class="stat-label">其他空间占比:</span>
+        <span class="stat-value">{{ getSpaceAllocationRatios().otherSpacesRatio | number:'1.0-2' }}%</span>
+      </div>
+      <div class="statistic-item">
+        <span class="stat-label">卧室数量:</span>
+        <span class="stat-value">{{ getSpaceStatistics().bedroomCount }} 间</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- 总计显示 -->
+  <div class="total-section">
+    <div class="total-row">
+      <span class="total-label">报价总计:</span>
+      <span class="total-amount">{{ quotationData.totalAmount | number:'1.2-2' }} 元</span>
+    </div>
+    @if (quotationData.aiGenerated) {
+      <div class="ai-generated-note">
+        <span class="ai-icon">🤖</span>
+        <span>此报价由AI辅助生成,请根据实际情况调整</span>
+      </div>
+    }
+  </div>
+</div>

+ 198 - 0
src/app/pages/designer/project-detail/components/quotation-detail/quotation-detail.component.scss

@@ -0,0 +1,198 @@
+/* 报价详情组件样式 */
+.quotation-detail-container {
+  padding: 20px;
+  background: white;
+  border-radius: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.quotation-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid #eee;
+
+  h3 {
+    margin: 0;
+    color: #333;
+    font-size: 18px;
+    font-weight: 600;
+  }
+
+  .total-amount {
+    font-size: 20px;
+    font-weight: 700;
+    color: #007aff;
+  }
+}
+
+.quotation-items {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.quotation-item {
+  padding: 16px;
+  border: 1px solid #eee;
+  border-radius: 8px;
+  background: #fafafa;
+
+  .item-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 12px;
+
+    .item-name {
+      font-weight: 600;
+      color: #333;
+      font-size: 16px;
+    }
+
+    .item-price {
+      font-size: 16px;
+      font-weight: 600;
+      color: #007aff;
+    }
+  }
+
+  .item-details {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+    gap: 12px;
+    font-size: 14px;
+    color: #666;
+
+    .detail-item {
+      display: flex;
+      justify-content: space-between;
+
+      .label {
+        font-weight: 500;
+      }
+
+      .value {
+        color: #333;
+      }
+    }
+  }
+}
+
+.empty-state {
+  text-align: center;
+  padding: 40px;
+  color: #999;
+  font-size: 14px;
+}
+
+.quotation-summary {
+  margin-top: 20px;
+  padding: 16px;
+  background: #f8f9fa;
+  border-radius: 8px;
+  border: 1px solid #dee2e6;
+
+  .summary-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 8px;
+
+    &:last-child {
+      margin-bottom: 0;
+      padding-top: 8px;
+      border-top: 1px solid #dee2e6;
+      font-weight: 600;
+      font-size: 16px;
+    }
+
+    .label {
+      color: #666;
+    }
+
+    .value {
+      color: #333;
+      font-weight: 500;
+    }
+  }
+}
+
+.form-group {
+  margin-bottom: 16px;
+
+  label {
+    display: block;
+    margin-bottom: 8px;
+    font-weight: 600;
+    color: #333;
+    font-size: 14px;
+  }
+
+  input, select, textarea {
+    width: 100%;
+    padding: 12px;
+    border: 1px solid #ddd;
+    border-radius: 8px;
+    font-size: 14px;
+    transition: border-color 0.2s ease;
+
+    &:focus {
+      outline: none;
+      border-color: #007aff;
+      box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
+    }
+  }
+
+  textarea {
+    min-height: 100px;
+    resize: vertical;
+  }
+}
+
+.form-actions {
+  display: flex;
+  gap: 12px;
+  justify-content: flex-end;
+  margin-top: 20px;
+
+  button {
+    padding: 12px 24px;
+    border: none;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 600;
+    cursor: pointer;
+    transition: all 0.2s ease;
+
+    &.primary {
+      background: #007aff;
+      color: white;
+
+      &:hover {
+        background: #0056cc;
+      }
+    }
+
+    &.secondary {
+      background: #f8f9fa;
+      color: #666;
+      border: 1px solid #ddd;
+
+      &:hover {
+        background: #e9ecef;
+      }
+    }
+
+    &.danger {
+      background: #dc3545;
+      color: white;
+
+      &:hover {
+        background: #c82333;
+      }
+    }
+  }
+}

+ 311 - 0
src/app/pages/designer/project-detail/components/quotation-detail/quotation-detail.component.ts

@@ -0,0 +1,311 @@
+import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';
+import { CommonModule, DecimalPipe } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+export interface QuotationItem {
+  id: string;
+  name: string;
+  amount: number;
+  description?: string;
+  isCustom?: boolean;
+  isRequired?: boolean; // 是否为必填项
+  category?: 'space' | 'service' | 'other'; // 项目类别
+  spaceType?: 'living-dining' | 'bedroom' | 'kitchen' | 'bathroom' | 'study' | 'balcony' | 'other'; // 空间类型
+  roomIndex?: number; // 卧室序号(用于区分多个卧室)
+}
+
+export interface QuotationDetailData {
+  items: QuotationItem[];
+  totalAmount: number;
+  aiGenerated: boolean;
+  projectGroupId?: string; // 关联的项目组ID
+  projectGroupName?: string; // 关联的项目组名称
+}
+
+@Component({
+  selector: 'app-quotation-detail',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './quotation-detail.component.html',
+  styleUrls: ['./quotation-detail.component.scss']
+})
+export class QuotationDetailComponent implements OnInit {
+  @Input() initialData?: QuotationDetailData;
+  @Input() orderAmount?: number;
+  @Output() dataChange = new EventEmitter<QuotationDetailData>();
+
+  quotationData: QuotationDetailData = {
+    items: [],
+    totalAmount: 0,
+    aiGenerated: false
+  };
+
+  // 预设的空间类型(必填项)
+  defaultSpaceTypes = [
+    { id: 'living-dining', name: '客餐厅', amount: 0, isRequired: true, category: 'space' as const, spaceType: 'living-dining' as const },
+    { id: 'bedroom-main', name: '主卧', amount: 0, isRequired: true, category: 'space' as const, spaceType: 'bedroom' as const, roomIndex: 1 }
+  ];
+
+  // 可选空间类型
+  optionalSpaceTypes = [
+    { id: 'bedroom-secondary', name: '次卧', amount: 0, isRequired: false, category: 'space' as const, spaceType: 'bedroom' as const, roomIndex: 2 },
+    { id: 'bedroom-third', name: '第三卧室', amount: 0, isRequired: false, category: 'space' as const, spaceType: 'bedroom' as const, roomIndex: 3 },
+    { id: 'kitchen', name: '厨房', amount: 0, isRequired: false, category: 'space' as const, spaceType: 'kitchen' as const },
+    { id: 'bathroom', name: '卫生间', amount: 0, isRequired: false, category: 'space' as const, spaceType: 'bathroom' as const },
+    { id: 'study', name: '书房', amount: 0, isRequired: false, category: 'space' as const, spaceType: 'study' as const },
+    { id: 'balcony', name: '阳台', amount: 0, isRequired: false, category: 'space' as const, spaceType: 'balcony' as const }
+  ];
+
+  isAiGenerating = false;
+  showAddCustomItem = false;
+  showAddOptionalSpace = false; // 控制添加可选空间的显示
+  newCustomItem = { name: '', amount: 0, description: '' };
+
+  // 暴露Math对象给模板使用
+  Math = Math;
+
+  constructor() {}
+
+  ngOnInit() {
+    if (this.initialData) {
+      this.quotationData = { ...this.initialData };
+    } else {
+      // 初始化必填空间类型
+      this.quotationData.items = this.defaultSpaceTypes.map(space => ({
+        id: space.id,
+        name: space.name,
+        amount: space.amount,
+        isCustom: false,
+        isRequired: space.isRequired,
+        category: space.category
+      }));
+    }
+    this.calculateTotal();
+  }
+
+  // AI辅助生成报价
+  generateAiQuotation() {
+    if (!this.orderAmount || this.orderAmount <= 0) {
+      alert('请先填写订单金额');
+      return;
+    }
+
+    this.isAiGenerating = true;
+    
+    // 模拟AI生成报价(实际项目中应该调用AI服务)
+    setTimeout(() => {
+      const totalAmount = this.orderAmount!;
+      const itemCount = this.quotationData.items.length;
+      
+      // 简单的分配算法:按比例分配
+      const baseAmounts = {
+        'living-dining': 0.60, // 客餐厅占60%
+        'bedroom-main': 0.40   // 主卧占40%
+      };
+
+      this.quotationData.items.forEach(item => {
+        if (!item.isCustom) {
+          const ratio = baseAmounts[item.id as keyof typeof baseAmounts] || 0;
+          item.amount = Math.round(totalAmount * ratio);
+        }
+      });
+
+      this.quotationData.aiGenerated = true;
+      this.calculateTotal();
+      this.isAiGenerating = false;
+      this.emitDataChange();
+    }, 2000);
+  }
+
+  // 更新单项金额
+  updateItemAmount(itemId: string, amount: number) {
+    const item = this.quotationData.items.find(i => i.id === itemId);
+    if (item) {
+      item.amount = amount || 0;
+      this.calculateTotal();
+      this.emitDataChange();
+    }
+  }
+
+  // 添加可选空间
+  addOptionalSpace(spaceType: any) {
+    const existingItem = this.quotationData.items.find(item => item.id === spaceType.id);
+    if (existingItem) {
+      alert('该空间已存在');
+      return;
+    }
+
+    const optionalSpace: QuotationItem = {
+      id: spaceType.id,
+      name: spaceType.name,
+      amount: 0,
+      isCustom: false,
+      isRequired: false,
+      category: spaceType.category
+    };
+
+    this.quotationData.items.push(optionalSpace);
+    this.calculateTotal();
+    this.emitDataChange();
+  }
+
+  // 获取可添加的可选空间
+  getAvailableOptionalSpaces() {
+    return this.optionalSpaceTypes.filter(space => 
+      !this.quotationData.items.some(item => item.id === space.id)
+    );
+  }
+
+  // 添加新的卧室(支持无限添加)
+  addNewBedroom() {
+    const existingBedrooms = this.quotationData.items.filter(item => 
+      item.spaceType === 'bedroom'
+    );
+    
+    const nextRoomIndex = existingBedrooms.length + 1;
+    const bedroomId = `bedroom-${nextRoomIndex}`;
+    
+    const newBedroom: QuotationItem = {
+      id: bedroomId,
+      name: `卧室${nextRoomIndex}`,
+      amount: 0,
+      isCustom: false,
+      isRequired: false,
+      category: 'space',
+      spaceType: 'bedroom',
+      roomIndex: nextRoomIndex
+    };
+
+    this.quotationData.items.push(newBedroom);
+    this.calculateTotal();
+    this.emitDataChange();
+  }
+
+  // 获取空间统计信息
+  getSpaceStatistics() {
+    const spaceItems = this.quotationData.items.filter(item => item.category === 'space');
+    
+    const stats = {
+      totalSpaces: spaceItems.length,
+      livingDiningAmount: spaceItems
+        .filter(item => item.spaceType === 'living-dining')
+        .reduce((sum, item) => sum + (item.amount || 0), 0),
+      bedroomAmount: spaceItems
+        .filter(item => item.spaceType === 'bedroom')
+        .reduce((sum, item) => sum + (item.amount || 0), 0),
+      otherSpacesAmount: spaceItems
+        .filter(item => item.spaceType && !['living-dining', 'bedroom'].includes(item.spaceType))
+        .reduce((sum, item) => sum + (item.amount || 0), 0),
+      bedroomCount: spaceItems.filter(item => item.spaceType === 'bedroom').length
+    };
+
+    return stats;
+  }
+
+  // 获取空间分配比例(用于工作量统计)
+  getSpaceAllocationRatios() {
+    const stats = this.getSpaceStatistics();
+    const totalSpaceAmount = stats.livingDiningAmount + stats.bedroomAmount + stats.otherSpacesAmount;
+    
+    if (totalSpaceAmount === 0) {
+      return {
+        livingDiningRatio: 0,
+        bedroomRatio: 0,
+        otherSpacesRatio: 0
+      };
+    }
+
+    return {
+      livingDiningRatio: (stats.livingDiningAmount / totalSpaceAmount) * 100,
+      bedroomRatio: (stats.bedroomAmount / totalSpaceAmount) * 100,
+      otherSpacesRatio: (stats.otherSpacesAmount / totalSpaceAmount) * 100
+    };
+  }
+
+  // 获取卧室数量
+  getBedroomCount(): number {
+    return this.getSpaceStatistics().bedroomCount;
+  }
+
+  // 添加自定义项目
+  addCustomItem() {
+    if (!this.newCustomItem.name.trim()) {
+      alert('请输入项目名称');
+      return;
+    }
+
+    const customItem: QuotationItem = {
+      id: `custom-${Date.now()}`,
+      name: this.newCustomItem.name,
+      amount: this.newCustomItem.amount || 0,
+      description: this.newCustomItem.description,
+      isCustom: true,
+      isRequired: false,
+      category: 'other'
+    };
+
+    this.quotationData.items.push(customItem);
+    this.calculateTotal();
+    this.emitDataChange();
+
+    // 重置表单
+    this.newCustomItem = { name: '', amount: 0, description: '' };
+    this.showAddCustomItem = false;
+  }
+
+  // 删除项目(只能删除非必填项)
+  removeItem(itemId: string) {
+    const item = this.quotationData.items.find(i => i.id === itemId);
+    if (item && item.isRequired) {
+      alert('必填项目不能删除');
+      return;
+    }
+    
+    this.quotationData.items = this.quotationData.items.filter(item => item.id !== itemId);
+    this.calculateTotal();
+    this.emitDataChange();
+  }
+
+  // 计算总金额
+  calculateTotal() {
+    this.quotationData.totalAmount = this.quotationData.items.reduce((sum, item) => sum + (item.amount || 0), 0);
+  }
+
+  // 发送数据变化事件
+  emitDataChange() {
+    this.dataChange.emit({ ...this.quotationData });
+  }
+
+  // 检查是否有自定义项目
+  hasCustomItems(): boolean {
+    return this.quotationData.items.some(item => item.isCustom);
+  }
+
+  // 重置报价
+  resetQuotation() {
+    // 重置所有项目金额为0
+    this.quotationData.items.forEach(item => {
+      item.amount = 0;
+    });
+    
+    // 移除所有非必填项目(保留必填的客餐厅和主卧)
+    this.quotationData.items = this.quotationData.items.filter(item => item.isRequired);
+    
+    this.quotationData.aiGenerated = false;
+    this.calculateTotal();
+    this.emitDataChange();
+  }
+
+  // 获取金额差异提示
+  getAmountDifference(): number {
+    if (!this.orderAmount) return 0;
+    return this.quotationData.totalAmount - this.orderAmount;
+  }
+
+  // 获取差异状态
+  getDifferenceStatus(): 'match' | 'over' | 'under' {
+    const diff = this.getAmountDifference();
+    if (Math.abs(diff) < 1) return 'match';
+    return diff > 0 ? 'over' : 'under';
+  }
+}

+ 192 - 0
src/app/pages/designer/project-detail/components/quotation-details/quotation-details.component.html

@@ -0,0 +1,192 @@
+<div class="quotation-details-container">
+  <div class="section-header">
+    <h3>报价明细</h3>
+    <div class="header-actions">
+      <button type="button" class="btn-secondary" (click)="showAIQuotation()">
+        <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <path d="M8 1L10.5 6H15L11 9.5L12.5 15L8 12L3.5 15L5 9.5L1 6H5.5L8 1Z" stroke="currentColor" stroke-width="1.5" fill="none"/>
+        </svg>
+        AI智能报价
+      </button>
+      <button type="button" class="btn-primary" (click)="addQuotationItem()">
+        <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+          <path d="M8 3V13M3 8H13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
+        </svg>
+        添加项目
+      </button>
+    </div>
+  </div>
+
+  <div class="quotation-form">
+    <!-- 报价项目列表 -->
+    <div class="quotation-items">
+      @for (item of quotationData.items; track item.id; let i = $index) {
+        <div class="quotation-item expanded">
+          <div class="item-header">
+            <div class="item-info">
+              <span class="item-name">{{ item.name || '未命名项目' }}</span>
+              <span class="item-category">{{ item.category }}</span>
+            </div>
+            <div class="item-actions">
+              <button type="button" class="btn-icon" (click)="duplicateQuotationItem(i)" title="复制">
+                <svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+                  <path d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V2Zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H6ZM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1H2Z"/>
+                </svg>
+              </button>
+              <button type="button" class="btn-icon btn-danger" (click)="removeQuotationItem(i)" title="删除">
+                <svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+                  <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
+                  <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
+                </svg>
+              </button>
+            </div>
+          </div>
+          
+          <div class="item-details">
+            <div class="form-row">
+              <div class="form-group">
+                <label class="form-label">项目名称</label>
+                <input type="text" [(ngModel)]="item.name" (ngModelChange)="onItemChange()" class="form-input" placeholder="请输入项目名称">
+              </div>
+              <div class="form-group">
+                <label class="form-label">类别</label>
+                <select [(ngModel)]="item.category" (ngModelChange)="onItemChange()" class="form-select">
+                  <option value="客餐厅">客餐厅</option>
+                  <option value="厨房">厨房</option>
+                  <option value="卫生间">卫生间</option>
+                  <option value="卧室">卧室</option>
+                  <option value="阳台">阳台</option>
+                  <option value="其他">其他</option>
+                </select>
+              </div>
+            </div>
+            
+            <div class="form-row">
+              <div class="form-group">
+                <label class="form-label">数量</label>
+                <input type="number" [(ngModel)]="item.quantity" (ngModelChange)="onItemChange()" class="form-input" min="0" step="0.01">
+              </div>
+              <div class="form-group">
+                <label class="form-label">单位</label>
+                <select [(ngModel)]="item.unit" (ngModelChange)="onItemChange()" class="form-select">
+                  <option value="㎡">㎡</option>
+                  <option value="延米">延米</option>
+                  <option value="个">个</option>
+                  <option value="套">套</option>
+                  <option value="项">项</option>
+                </select>
+              </div>
+              <div class="form-group">
+                <label class="form-label">单价(元)</label>
+                <input type="number" [(ngModel)]="item.unitPrice" (ngModelChange)="onItemChange()" class="form-input" min="0" step="0.01">
+              </div>
+              <div class="form-group">
+                <label class="form-label">小计(元)</label>
+                <input type="text" [value]="(item.quantity * item.unitPrice).toFixed(2)" class="form-input" readonly>
+              </div>
+            </div>
+            
+            <div class="form-group">
+              <label class="form-label">项目描述</label>
+              <textarea [(ngModel)]="item.description" (ngModelChange)="onItemChange()" class="form-textarea" rows="2" placeholder="请输入项目描述"></textarea>
+            </div>
+          </div>
+        </div>
+      }
+
+      @if (quotationData.items.length === 0) {
+        <div class="empty-state">
+          <div class="empty-icon">
+            <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <path d="M24 4L28.5 18H42L32 26.5L36.5 40L24 32L11.5 40L16 26.5L6 18H19.5L24 4Z" stroke="#D1D5DB" stroke-width="2" fill="none"/>
+            </svg>
+          </div>
+          <p>暂无报价项目</p>
+          <p class="empty-hint">点击"添加项目"或"AI智能报价"开始创建报价清单</p>
+        </div>
+      }
+    </div>
+
+    <!-- 报价汇总 -->
+    @if (quotationData.items.length > 0) {
+      <div class="quotation-summary">
+        <div class="summary-header">
+          <h4>报价汇总</h4>
+        </div>
+        <div class="summary-content">
+          <div class="summary-row">
+            <span class="summary-label">项目总数:</span>
+            <span class="summary-value">{{ quotationData.items.length }} 项</span>
+          </div>
+          <div class="summary-row">
+            <span class="summary-label">材料费用:</span>
+            <span class="summary-value">¥{{ formatAmount(getMaterialCost()) }}</span>
+          </div>
+          <div class="summary-row">
+            <span class="summary-label">人工费用:</span>
+            <span class="summary-value">¥{{ formatAmount(getLaborCost()) }}</span>
+          </div>
+          <div class="summary-row total">
+            <span class="summary-label">合计金额:</span>
+            <span class="summary-value">¥{{ formatAmount(getTotalAmount()) }}</span>
+          </div>
+        </div>
+      </div>
+    }
+  </div>
+
+  <!-- AI报价生成弹窗 -->
+  @if (showAIModal) {
+    <div class="modal-overlay" (click)="closeAIModal()">
+      <div class="modal-content" (click)="$event.stopPropagation()">
+        <div class="modal-header">
+          <h3>AI智能报价</h3>
+          <button type="button" class="btn-close" (click)="closeAIModal()">
+            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+              <path d="M12 4L4 12M4 4L12 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
+            </svg>
+          </button>
+        </div>
+        <div class="modal-body">
+          <div class="ai-form">
+            <div class="form-group">
+              <label class="form-label">房屋面积(平方米)</label>
+              <input type="number" [(ngModel)]="aiParams.area" class="form-input" placeholder="请输入房屋面积" min="1">
+            </div>
+            <div class="form-group">
+              <label class="form-label">装修风格</label>
+              <select [(ngModel)]="aiParams.style" class="form-select">
+                @for (style of styleOptions; track style) {
+                  <option [value]="style">{{ style }}</option>
+                }
+              </select>
+            </div>
+            <div class="form-group">
+              <label class="form-label">装修档次</label>
+              <select [(ngModel)]="aiParams.level" class="form-select">
+                @for (level of levelOptions; track level) {
+                  <option [value]="level">{{ level }}</option>
+                }
+              </select>
+            </div>
+            <div class="form-group">
+              <label class="form-label">特殊需求</label>
+              <textarea [(ngModel)]="aiParams.specialRequirements" class="form-textarea" placeholder="请输入特殊要求(可选)" rows="3"></textarea>
+            </div>
+          </div>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn-secondary" (click)="closeAIModal()">取消</button>
+          <button type="button" class="btn-primary" (click)="generateAIQuotation()" [disabled]="aiLoading">
+            @if (aiLoading) {
+              <div class="loading-spinner"></div>
+              生成中...
+            } @else {
+              生成报价
+            }
+          </button>
+        </div>
+      </div>
+    </div>
+  }
+</div>

+ 435 - 0
src/app/pages/designer/project-detail/components/quotation-details/quotation-details.component.scss

@@ -0,0 +1,435 @@
+.quotation-details-container {
+  background: white;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid #f0f0f0;
+
+  h3 {
+    margin: 0;
+    font-size: 18px;
+    font-weight: 600;
+    color: #1a1a1a;
+  }
+
+  .header-actions {
+    display: flex;
+    gap: 12px;
+  }
+}
+
+.btn-primary, .btn-secondary {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 16px;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 500;
+  border: none;
+  cursor: pointer;
+  transition: all 0.2s ease;
+
+  svg {
+    width: 16px;
+    height: 16px;
+  }
+}
+
+.btn-primary {
+  background: #3b82f6;
+  color: white;
+
+  &:hover {
+    background: #2563eb;
+  }
+
+  &:disabled {
+    background: #9ca3af;
+    cursor: not-allowed;
+  }
+}
+
+.btn-secondary {
+  background: #f8fafc;
+  color: #475569;
+  border: 1px solid #e2e8f0;
+
+  &:hover {
+    background: #f1f5f9;
+    border-color: #cbd5e1;
+  }
+}
+
+.quotation-items {
+  margin-bottom: 24px;
+}
+
+.quotation-item {
+  border: 1px solid #e5e7eb;
+  border-radius: 8px;
+  margin-bottom: 12px;
+  overflow: hidden;
+  transition: all 0.2s ease;
+
+  &:hover {
+    border-color: #d1d5db;
+  }
+
+  &.expanded {
+    border-color: #3b82f6;
+    box-shadow: 0 0 0 1px rgba(59, 130, 246, 0.1);
+  }
+}
+
+.item-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px;
+  cursor: pointer;
+  background: #fafafa;
+  transition: background-color 0.2s ease;
+
+  &:hover {
+    background: #f5f5f5;
+  }
+
+  .expanded & {
+    background: #f8fafc;
+    border-bottom: 1px solid #e5e7eb;
+  }
+}
+
+.item-basic-info {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  flex: 1;
+
+  .item-category {
+    background: #e0f2fe;
+    color: #0369a1;
+    padding: 4px 8px;
+    border-radius: 4px;
+    font-size: 12px;
+    font-weight: 500;
+  }
+
+  .item-name {
+    font-weight: 500;
+    color: #374151;
+    min-width: 120px;
+  }
+
+  .item-amount {
+    font-weight: 600;
+    color: #059669;
+    font-size: 16px;
+  }
+}
+
+.item-actions {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.btn-icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 32px;
+  height: 32px;
+  border: none;
+  background: transparent;
+  border-radius: 6px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  color: #6b7280;
+
+  &:hover {
+    background: #f3f4f6;
+    color: #374151;
+  }
+
+  &.btn-danger:hover {
+    background: #fef2f2;
+    color: #dc2626;
+  }
+
+  svg {
+    width: 14px;
+    height: 14px;
+
+    &.rotated {
+      transform: rotate(180deg);
+    }
+  }
+}
+
+.item-details {
+  padding: 20px;
+  background: white;
+}
+
+.form-row {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 16px;
+  margin-bottom: 16px;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+.form-group {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.form-label {
+  font-size: 14px;
+  font-weight: 500;
+  color: #374151;
+}
+
+.form-input, .form-select, .form-textarea {
+  padding: 10px 12px;
+  border: 1px solid #d1d5db;
+  border-radius: 6px;
+  font-size: 14px;
+  transition: border-color 0.2s ease;
+
+  &:focus {
+    outline: none;
+    border-color: #3b82f6;
+    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+  }
+
+  &[readonly] {
+    background: #f9fafb;
+    color: #6b7280;
+  }
+}
+
+.form-textarea {
+  resize: vertical;
+  min-height: 60px;
+}
+
+.empty-state {
+  text-align: center;
+  padding: 48px 24px;
+  color: #6b7280;
+
+  .empty-icon {
+    margin-bottom: 16px;
+  }
+
+  p {
+    margin: 8px 0;
+    
+    &.empty-hint {
+      font-size: 14px;
+      color: #9ca3af;
+    }
+  }
+}
+
+.quotation-summary {
+  background: #f8fafc;
+  border: 1px solid #e2e8f0;
+  border-radius: 8px;
+  padding: 20px;
+
+  .summary-header {
+    margin-bottom: 16px;
+    padding-bottom: 12px;
+    border-bottom: 1px solid #e2e8f0;
+
+    h4 {
+      margin: 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: #1e293b;
+    }
+  }
+
+  .summary-content {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+  }
+
+  .summary-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    &.total {
+      padding-top: 12px;
+      border-top: 1px solid #e2e8f0;
+      font-weight: 600;
+      font-size: 16px;
+
+      .summary-value {
+        color: #059669;
+        font-size: 18px;
+      }
+    }
+  }
+
+  .summary-label {
+    color: #64748b;
+    font-size: 14px;
+  }
+
+  .summary-value {
+    color: #1e293b;
+    font-weight: 500;
+  }
+}
+
+// 模态框样式
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+}
+
+.modal-content {
+  background: white;
+  border-radius: 12px;
+  width: 90%;
+  max-width: 500px;
+  max-height: 90vh;
+  overflow: hidden;
+  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+}
+
+.modal-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px 24px;
+  border-bottom: 1px solid #e5e7eb;
+
+  h3 {
+    margin: 0;
+    font-size: 18px;
+    font-weight: 600;
+    color: #1a1a1a;
+  }
+
+  .btn-close {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 32px;
+    height: 32px;
+    border: none;
+    background: transparent;
+    border-radius: 6px;
+    cursor: pointer;
+    color: #6b7280;
+
+    &:hover {
+      background: #f3f4f6;
+      color: #374151;
+    }
+  }
+}
+
+.modal-body {
+  padding: 24px;
+  max-height: 60vh;
+  overflow-y: auto;
+}
+
+.modal-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  padding: 20px 24px;
+  border-top: 1px solid #e5e7eb;
+  background: #fafafa;
+}
+
+.ai-form {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.loading-spinner {
+  width: 16px;
+  height: 16px;
+  border: 2px solid transparent;
+  border-top: 2px solid currentColor;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .quotation-details-container {
+    padding: 16px;
+  }
+
+  .section-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 16px;
+
+    .header-actions {
+      width: 100%;
+      justify-content: flex-end;
+    }
+  }
+
+  .item-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 12px;
+  }
+
+  .item-basic-info {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 8px;
+    width: 100%;
+  }
+
+  .form-row {
+    grid-template-columns: 1fr;
+  }
+
+  .modal-content {
+    width: 95%;
+    margin: 20px;
+  }
+}

+ 198 - 0
src/app/pages/designer/project-detail/components/quotation-details/quotation-details.component.ts

@@ -0,0 +1,198 @@
+import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { AIQuotationService, AIQuotationParams, AIQuotationResult } from '../../../../../services/ai-quotation.service';
+
+export interface QuotationItem {
+  id: string;
+  category: string;
+  name: string;
+  quantity: number;
+  unit: string;
+  unitPrice: number;
+  description: string;
+}
+
+export interface QuotationData {
+  items: QuotationItem[];
+  totalAmount: number;
+  materialCost: number;
+  laborCost: number;
+  designFee: number;
+  managementFee: number;
+}
+
+@Component({
+  selector: 'app-quotation-details',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './quotation-details.component.html',
+  styleUrls: ['./quotation-details.component.scss']
+})
+export class QuotationDetailsComponent implements OnInit {
+  @Input() initialData?: QuotationData;
+  @Output() dataChange = new EventEmitter<QuotationData>();
+
+  quotationData: QuotationData = {
+    items: [],
+    totalAmount: 0,
+    materialCost: 0,
+    laborCost: 0,
+    designFee: 0,
+    managementFee: 0
+  };
+
+  // AI报价相关
+  showAIModal = false;
+  aiLoading = false;
+  aiParams: AIQuotationParams = {
+    area: 100,
+    style: '现代简约',
+    level: '舒适型',
+    specialRequirements: ''
+  };
+
+  // 选项数据
+  styleOptions: string[] = [];
+  levelOptions: string[] = [];
+
+  constructor(private aiQuotationService: AIQuotationService) {}
+
+  ngOnInit() {
+    if (this.initialData) {
+      this.quotationData = { ...this.initialData };
+    }
+    
+    // 初始化选项数据
+    this.styleOptions = this.aiQuotationService.getStyleOptions();
+    this.levelOptions = this.aiQuotationService.getLevelOptions();
+  }
+
+  // 添加报价项目
+  addQuotationItem() {
+    const newItem: QuotationItem = {
+      id: Date.now().toString(),
+      category: '客餐厅',
+      name: '',
+      quantity: 1,
+      unit: '㎡',
+      unitPrice: 0,
+      description: ''
+    };
+    
+    this.quotationData.items.push(newItem);
+    this.calculateTotal();
+    this.emitDataChange();
+  }
+
+  // 删除报价项目
+  removeQuotationItem(index: number) {
+    this.quotationData.items.splice(index, 1);
+    this.calculateTotal();
+    this.emitDataChange();
+  }
+
+  // 复制报价项目
+  duplicateQuotationItem(index: number) {
+    const originalItem = this.quotationData.items[index];
+    const duplicatedItem: QuotationItem = {
+      ...originalItem,
+      id: Date.now().toString(),
+      name: originalItem.name + ' (副本)'
+    };
+    
+    this.quotationData.items.splice(index + 1, 0, duplicatedItem);
+    this.calculateTotal();
+    this.emitDataChange();
+  }
+
+  // 计算总金额
+  calculateTotal() {
+    const materialCost = this.quotationData.items.reduce((sum, item) => sum + (item.quantity * item.unitPrice), 0);
+    this.quotationData.materialCost = materialCost;
+    this.quotationData.laborCost = materialCost * 0.3; // 人工费按材料费30%计算
+    this.quotationData.designFee = materialCost * 0.05; // 设计费按材料费5%计算
+    this.quotationData.managementFee = materialCost * 0.08; // 管理费按材料费8%计算
+    this.quotationData.totalAmount = this.quotationData.materialCost + this.quotationData.laborCost + this.quotationData.designFee + this.quotationData.managementFee;
+  }
+
+  // 获取总金额
+  getTotalAmount(): number {
+    return this.quotationData.totalAmount;
+  }
+
+  // 获取材料费用
+  getMaterialCost(): number {
+    return this.quotationData.materialCost;
+  }
+
+  // 获取人工费用
+  getLaborCost(): number {
+    return this.quotationData.laborCost;
+  }
+
+  // 格式化金额显示
+  formatAmount(amount: number): string {
+    return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
+  }
+
+  // 项目数据变化处理
+  onItemChange() {
+    this.calculateTotal();
+    this.emitDataChange();
+  }
+
+  // 发送数据变化事件
+  private emitDataChange() {
+    this.dataChange.emit({ ...this.quotationData });
+  }
+
+  // 显示AI报价模态框
+  showAIQuotation() {
+    this.showAIModal = true;
+  }
+
+  // 关闭AI报价模态框
+  closeAIModal() {
+    this.showAIModal = false;
+    this.aiLoading = false;
+  }
+
+  // 生成AI报价
+  generateAIQuotation() {
+    if (!this.aiParams.area || !this.aiParams.style || !this.aiParams.level) {
+      return;
+    }
+
+    this.aiLoading = true;
+    
+    this.aiQuotationService.generateQuotation(this.aiParams).subscribe({
+      next: (result: AIQuotationResult) => {
+        // 将AI生成的项目转换为报价项目
+        const aiItems: QuotationItem[] = result.items.map(item => ({
+           id: Date.now().toString() + Math.random().toString(36).substr(2, 9),
+           category: item.category,
+           name: item.name,
+           quantity: item.quantity,
+           unit: item.unit,
+           unitPrice: item.unitPrice,
+           description: item.description || ''
+         }));
+
+        // 替换当前报价项目
+        this.quotationData.items = aiItems;
+        this.calculateTotal();
+        this.emitDataChange();
+        
+        this.aiLoading = false;
+        this.closeAIModal();
+      },
+      error: (error) => {
+        console.error('AI报价生成失败:', error);
+        this.aiLoading = false;
+      }
+    });
+  }
+
+
+}

+ 0 - 295
src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav-styles.scss

@@ -1,295 +0,0 @@
-@use '../../../../../shared/styles/_ios-theme.scss' as *;
-
-// 项目人员标签页样式
-.members-tab-content {
-  .main-content-layout {
-    display: grid;
-    grid-template-columns: auto 1fr;
-    gap: $ios-spacing-lg;
-    align-items: start;
-  }
-
-  .members-content {
-    background: $ios-background;
-    border-radius: $ios-radius-lg;
-    padding: $ios-spacing-lg;
-    box-shadow: $ios-shadow-sm;
-  }
-
-  .members-header {
-    margin-bottom: $ios-spacing-lg;
-    
-    h2 {
-      color: $ios-text-primary;
-      font-size: $ios-font-size-lg;
-      font-weight: 600;
-      margin: 0 0 $ios-spacing-xs 0;
-    }
-
-    .members-count {
-      color: $ios-text-secondary;
-      font-size: $ios-font-size-sm;
-      margin: 0;
-    }
-  }
-
-  .members-grid {
-    display: grid;
-    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
-    gap: $ios-spacing-md;
-  }
-
-  .member-card {
-    background: $ios-background;
-    border: 1px solid $ios-border;
-    border-radius: $ios-radius-md;
-    padding: $ios-spacing-md;
-    transition: all 0.2s ease;
-
-    &:hover {
-      box-shadow: $ios-shadow-md;
-      transform: translateY(-2px);
-    }
-
-    .member-avatar {
-      width: 60px;
-      height: 60px;
-      border-radius: 50%;
-      margin-bottom: $ios-spacing-md;
-      object-fit: cover;
-      border: 2px solid $ios-border;
-    }
-
-    .member-info {
-      text-align: center;
-
-      .member-name {
-        color: $ios-text-primary;
-        font-size: $ios-font-size-md;
-        font-weight: 600;
-        margin: 0 0 $ios-spacing-xs 0;
-      }
-
-      .member-role {
-        color: $ios-text-secondary;
-        font-size: $ios-font-size-sm;
-        margin: 0 0 $ios-spacing-sm 0;
-      }
-
-      .member-stats {
-        display: flex;
-        justify-content: space-between;
-        margin-top: $ios-spacing-sm;
-
-        .stat-item {
-          text-align: center;
-          flex: 1;
-
-          .stat-value {
-            color: $ios-text-secondary;
-            font-size: $ios-font-size-xs;
-            margin: 0;
-          }
-
-          .stat-label {
-            background: $ios-border;
-            color: white;
-            font-size: $ios-font-size-xs;
-            padding: 2px 6px;
-            border-radius: 10px;
-            margin-top: 2px;
-          }
-
-          &.active .stat-label {
-            background: $ios-primary;
-          }
-        }
-      }
-    }
-
-    .member-actions {
-      margin-top: $ios-spacing-md;
-      display: flex;
-      gap: $ios-spacing-sm;
-
-      .action-btn {
-        flex: 1;
-        padding: $ios-spacing-xs $ios-spacing-sm;
-        border: none;
-        border-radius: $ios-radius-sm;
-        font-size: $ios-font-size-xs;
-        cursor: pointer;
-        transition: all 0.2s ease;
-
-        &.primary {
-          background: $ios-primary;
-          color: white;
-
-          &:hover {
-            opacity: 0.9;
-          }
-        }
-
-        &.secondary {
-          background: $ios-background;
-          border: 1px solid $ios-border;
-          color: $ios-text-primary;
-
-          &:hover {
-            background: $ios-background-secondary;
-          }
-        }
-      }
-    }
-  }
-}
-
-// 垂直导航栏样式
-.vertical-nav-container {
-  .nav-section {
-    margin-bottom: $ios-spacing-md; // 减小节间距
-
-    .section-title {
-      color: $ios-text-secondary;
-      font-size: 10px; // 减小标题字体
-      font-weight: 600;
-      text-transform: uppercase;
-      letter-spacing: 0.3px; // 减小字母间距
-      margin: 0 0 $ios-spacing-xs 0; // 减小下边距
-      padding: 0 $ios-spacing-sm; // 减小内边距
-    }
-
-    .nav-items {
-      list-style: none;
-      padding: 0;
-      margin: 0;
-
-      .nav-item {
-        margin-bottom: 2px; // 减小项间距
-
-        .nav-link {
-          display: flex;
-          align-items: center;
-          padding: $ios-spacing-xs $ios-spacing-sm; // 减小内边距
-          color: $ios-text-primary;
-          text-decoration: none;
-          border-radius: $ios-radius-sm; // 减小圆角
-          transition: all 0.2s ease;
-          font-size: $ios-font-size-xs; // 减小字体
-          line-height: 1.2; // 减小行高
-
-          &:hover {
-            background: $ios-background-secondary;
-          }
-
-          &.active {
-            background: $ios-primary;
-            color: white;
-            font-weight: 500;
-
-            .nav-icon {
-              color: white;
-            }
-          }
-
-          .nav-icon {
-            margin-right: $ios-spacing-xs; // 减小图标间距
-            font-size: 12px; // 减小图标尺寸
-            width: 12px;
-            text-align: center;
-            flex-shrink: 0;
-          }
-
-          .nav-text {
-            flex: 1;
-            white-space: nowrap;
-            overflow: hidden;
-            text-overflow: ellipsis;
-          }
-
-          .nav-badge {
-            background: $ios-border;
-            color: white;
-            font-size: 9px; // 减小徽标字体
-            padding: 1px 4px; // 减小徽标内边距
-            border-radius: 6px; // 减小徽标圆角
-            margin-left: auto;
-            min-width: 14px;
-            text-align: center;
-          }
-
-          &.active .nav-badge {
-            background: rgba(255, 255, 255, 0.2);
-          }
-        }
-      }
-    }
-  }
-}
-
-// 响应式设计
-@media (max-width: 768px) {
-  .members-tab-content {
-    .main-content-layout {
-      grid-template-columns: 1fr;
-      gap: $ios-spacing-md;
-    }
-
-    .members-grid {
-      grid-template-columns: 1fr;
-    }
-  }
-
-  .vertical-nav-container {
-    .nav-section {
-      .nav-items {
-        .nav-item {
-          .nav-link {
-            padding: $ios-spacing-md;
-            font-size: $ios-font-size-md;
-
-            .nav-icon {
-              font-size: 18px;
-              width: 18px;
-            }
-          }
-        }
-      }
-    }
-  }
-}
-
-// 加载状态
-.loading-state {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  height: 200px;
-  color: $ios-text-secondary;
-  font-size: $ios-font-size-sm;
-}
-
-// 空状态
-.empty-state {
-  text-align: center;
-  padding: $ios-spacing-xxl;
-  color: $ios-text-secondary;
-
-  .empty-icon {
-    font-size: 48px;
-    margin-bottom: $ios-spacing-md;
-    opacity: 0.5;
-  }
-
-  .empty-title {
-    font-size: $ios-font-size-lg;
-    font-weight: 600;
-    margin: 0 0 $ios-spacing-sm 0;
-  }
-
-  .empty-description {
-    font-size: $ios-font-size-sm;
-    margin: 0;
-    line-height: 1.5;
-  }
-}

+ 0 - 14
src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav.component.html

@@ -1,14 +0,0 @@
-<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>

+ 0 - 148
src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav.component.scss

@@ -1,148 +0,0 @@
-@use '../../../../../shared/styles/_ios-theme.scss' as *;
-
-// 横向滑动条式选项卡导航
-.horizontal-tab-nav {
-  position: relative;
-  display: flex;
-  flex-direction: column;
-  width: 100%;
-  background: $ios-background;
-  border-radius: $ios-radius-lg;
-  overflow: hidden;
-  box-shadow: $ios-shadow-sm;
-}
-
-.tab-container {
-  display: flex;
-  position: relative;
-  background: rgba(0, 122, 255, 0.05);
-  border-radius: $ios-radius-lg;
-  padding: 4px;
-}
-
-.tab-item {
-  flex: 1;
-  display: flex;
-  align-items: center;
-  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 {
-    color: $ios-primary;
-    background: rgba(0, 122, 255, 0.08);
-  }
-
-  &.active {
-    color: $ios-primary;
-    font-weight: $ios-font-weight-semibold;
-  }
-
-  .tab-text {
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    transition: all 0.3s ease;
-  }
-}
-
-// 滑动指示器
-.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;
-}
-
-// 响应式设计
-@media (max-width: 768px) {
-  .horizontal-tab-nav {
-    margin: 0 -8px;
-  }
-  
-  .tab-item {
-    padding: 10px 12px;
-    font-size: $ios-font-size-sm;
-    min-width: 100px;
-  }
-  
-  .tab-indicator {
-    width: 100px;
-  }
-}
-
-@media (max-width: 480px) {
-  .tab-item {
-    padding: 8px 10px;
-    font-size: 13px;
-    min-width: 80px;
-  }
-  
-  .tab-indicator {
-    width: 80px;
-  }
-}
-
-// 保持向后兼容的垂直导航样式(如果需要)
-.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 {
-    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;
-    }
-  }
-}

+ 0 - 56
src/app/pages/designer/project-detail/components/vertical-nav/vertical-nav.component.ts

@@ -1,56 +0,0 @@
-import { Component, Input, Output, EventEmitter, AfterViewInit, ElementRef, ViewChild } from '@angular/core';
-import { CommonModule } from '@angular/common';
-
-export interface NavItem {
-  id: 'progress' | 'members' | 'files';
-  name: string;
-  icon?: string;
-}
-
-@Component({
-  selector: 'app-vertical-nav',
-  standalone: true,
-  imports: [CommonModule],
-  templateUrl: './vertical-nav.component.html',
-  styleUrls: ['./vertical-nav.component.scss']
-})
-export class VerticalNavComponent implements AfterViewInit {
-  @Input() activeTab: 'progress' | 'members' | 'files' = 'progress';
-  @Input() navItems: NavItem[] = [
-    { id: 'progress', name: '项目进度' },
-    { id: 'members', name: '项目人员' },
-    { id: 'files', name: '项目文件' }
-  ];
-  
-  @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);
-  }
-
-  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
-    }
-  }
-}

+ 263 - 58
src/app/pages/designer/project-detail/project-detail.html

@@ -16,11 +16,16 @@
     
     <!-- 导航按钮区域 - 移动到标题左侧 -->
     <div class="header-nav">
-      <app-vertical-nav 
-        [activeTab]="activeTab" 
-        (tabChange)="switchTab($event)"
-        class="header-nav-tabs">
-      </app-vertical-nav>
+      <div class="header-nav-tabs">
+        @for (tab of tabs; track tab.id) {
+          <button 
+            class="nav-tab" 
+            [class.active]="activeTab === tab.id"
+            (click)="switchTab(tab.id)">
+            {{ tab.name }}
+          </button>
+        }
+      </div>
     </div>
 
     <!-- 四个环节圆圈导航 -->
@@ -168,6 +173,33 @@
   </div>
 }
 
+<!-- 设计师日历弹窗 -->
+@if (showDesignerCalendar) {
+  <div class="image-preview-modal" (click)="closeDesignerCalendar()">
+    <div class="modal-backdrop"></div>
+    <div class="modal-content" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>选择设计师与排期</h3>
+        <button class="close-btn" (click)="closeDesignerCalendar()">
+          <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <line x1="18" y1="6" x2="6" y2="18"></line>
+            <line x1="6" y1="6" x2="18" y2="18"></line>
+          </svg>
+        </button>
+      </div>
+      <div class="modal-body">
+        <app-designer-calendar
+          [designers]="calendarDesigners"
+          [selectedDate]="selectedCalendarDate"
+          [projectGroups]="calendarGroups"
+          (designerSelected)="onCalendarDesignerSelected($event)"
+          (assignmentRequested)="onCalendarAssignmentRequested($event)"
+        ></app-designer-calendar>
+      </div>
+    </div>
+  </div>
+}
+
   <!-- 提醒消息弹窗 -->
   @if (reminderMessage) {
     <div class="reminder-popup">
@@ -581,13 +613,14 @@
                     </div>
                   }
                   
-                  <!-- 小图时间 -->
-                  @if (getEstimatedSmallImageTime()) {
-                    <div class="info-item">
-                      <label>预计时间</label>
-                      <span>{{ getEstimatedSmallImageTime() }}</span>
-                    </div>
-                  }
+                  <!-- 订单金额 -->
+                  <!-- 订单金额 -->
+                  <div class="info-item">
+                    <label>订单金额</label>
+                    <span>¥{{ orderAmount || 0 }}</span>
+                  </div>
+                  
+
                 </div>
                 
                 <!-- 开始分析按钮 -->
@@ -615,7 +648,197 @@
           <div class="right-column">
               <!-- 移除顶部四个圆圈,直接展示阶段内容 -->
               
-              <!-- 串式流程:10个阶段横向排列(保持) -->
+              <!-- 新增:客户信息右侧 2x2 售后模块布局 -->
+              @if (expandedSection === 'aftercare') {
+              <div class="aftercare-grid">
+                
+                <!-- 1/4:尾款结算 -->
+                <div class="aftercare-module settlement-module card">
+                  <div class="module-header">
+                    <h2>💰 尾款结算</h2>
+                    <p class="module-description">技术完成验收后自动发起尾款结算流程,支持多种支付方式自动化处理</p>
+                  </div>
+                  <div class="settlement-automation">
+                    <div class="automation-features">
+                      <div class="feature-card">
+                        <div class="feature-icon">🤖</div>
+                        <div class="feature-content">
+                          <h4>自动触发流程</h4>
+                          <p>技术验收完成后自动发起尾款结算申请</p>
+                        </div>
+                      </div>
+                      <div class="feature-card">
+                        <div class="feature-icon">🔓</div>
+                        <div class="feature-content">
+                          <h4>自动解密发送</h4>
+                          <p>小程序支付自动解密并发送大图给客户</p>
+                        </div>
+                      </div>
+                      <div class="feature-card">
+                        <div class="feature-icon">📱</div>
+                        <div class="feature-content">
+                          <h4>凭证智能识别</h4>
+                          <p>上传微信/支付宝截图自动提取金额和支付方式</p>
+                        </div>
+                      </div>
+                      <div class="feature-card">
+                        <div class="feature-icon">🔔</div>
+                        <div class="feature-content">
+                          <h4>自动通知</h4>
+                          <p>支付完成后自动发送"尾款已到账,大图已解锁"通知</p>
+                        </div>
+                      </div>
+                    </div>
+                    <div class="settlement-status">
+                      <app-settlement-card [settlements]="settlements"></app-settlement-card>
+                    </div>
+                    @if (canEditSection('aftercare')) {
+                      <div class="automation-actions">
+                        <button 
+                          class="primary-btn automation-btn"
+                          (click)="initiateAutoSettlement()"
+                          [disabled]="isAutoSettling">
+                          @if (isAutoSettling) { <span class="loading-spinner"></span> 自动化处理中... } @else { 🚀 启动自动化结算 }
+                        </button>
+                        <button class="secondary-btn" (click)="uploadPaymentProof()"><span class="upload-icon">📎</span> 上传支付凭证</button>
+                      </div>
+                    }
+                  </div>
+                </div>
+                
+                <!-- 2/4:全景图合成 -->
+                <div class="aftercare-module panoramic-module card">
+                  <div class="module-header">
+                    <h2>🖼️ 全景图合成</h2>
+                    <p class="module-description">集成内部全景图合成服务器,技术上传图片自动生成漫游式全景链接</p>
+                  </div>
+                  <div class="panoramic-synthesis">
+                    <div class="panoramic-features">
+                      <div class="feature-card">
+                        <div class="feature-icon">🖥️</div>
+                        <div class="feature-content">
+                          <h4>KR Panel集成</h4>
+                          <p>集成专业全景图合成工具,确保合成质量</p>
+                        </div>
+                      </div>
+                      <div class="feature-card">
+                        <div class="feature-icon">📸</div>
+                        <div class="feature-content">
+                          <h4>智能空间标注</h4>
+                          <p>技术上传图片并标注空间名称(如"客厅-角度1")</p>
+                        </div>
+                      </div>
+                      <div class="feature-card">
+                        <div class="feature-icon">🔗</div>
+                        <div class="feature-content">
+                          <h4>自动生成链接</h4>
+                          <p>自动生成漫游式全景链接并发送给客户</p>
+                        </div>
+                      </div>
+                    </div>
+                    <div class="recent-syntheses">
+                      @if (panoramicSyntheses && panoramicSyntheses.length > 0) {
+                        <div class="panoramic-card-list">
+                          @for (item of panoramicSyntheses; track item.id) {
+                            <app-panoramic-synthesis-card
+                              [synthesis]="item"
+                              [showActions]="true">
+                            </app-panoramic-synthesis-card>
+                          }
+                        </div>
+                      } @else {
+                        <div class="desc">暂无最近合成记录</div>
+                      }
+                    </div>
+                    @if (canEditSection('aftercare')) {
+                      <div class="panoramic-actions">
+                        <button class="primary-btn" (click)="startPanoramicSynthesis()">开始合成</button>
+                        <button class="secondary-btn" (click)="viewPanoramicGallery()">查看全景图库</button>
+                      </div>
+                    }
+                  </div>
+                </div>
+                
+                <!-- 3/4:客户评价 -->
+                <div class="aftercare-module review-module card">
+                  <div class="module-header">
+                    <h2>⭐ 客户评价</h2>
+                    <p class="module-description">邀请客户进行多维度评价并生成评价链接</p>
+                  </div>
+                  <div class="customer-review-enhanced">
+                    <div class="review-form-container">
+                      <app-customer-review-form
+                        [projectName]="project?.name || ''"
+                        [designerName]="getCurrentDesignerName()"
+                        (reviewSubmitted)="onReviewSubmitted($event)"
+                        (reviewSaved)="onReviewSaved($event)">
+                      </app-customer-review-form>
+                    </div>
+                    <div class="review-card-container" style="margin-top:12px;">
+                      <app-customer-review-card [feedbacks]="feedbacks"></app-customer-review-card>
+                    </div>
+                    @if (canEditSection('aftercare')) {
+                      <div class="review-actions">
+                        <button class="primary-btn" (click)="confirmCustomerReview()">✅ 确认评价完成</button>
+                        <button class="secondary-btn" (click)="generateReviewLink()">🔗 生成评价链接</button>
+                      </div>
+                    }
+                  </div>
+                </div>
+                
+                <!-- 4/4:投诉处理 -->
+                <div class="aftercare-module complaint-module card">
+                  <div class="module-header">
+                    <h2>📋 投诉处理</h2>
+                    <p class="module-description">支持人工创建和关键词自动抓取投诉,提升处理效率</p>
+                  </div>
+                  <div class="complaint-management-enhanced">
+                    <div class="complaint-features">
+                      <div class="feature-card">
+                        <div class="feature-icon">👥</div>
+                        <div class="feature-content">
+                          <h4>人工创建</h4>
+                          <p>组长或客服人工创建投诉记录</p>
+                        </div>
+                      </div>
+                      <div class="feature-card">
+                        <div class="feature-icon">🔍</div>
+                        <div class="feature-content">
+                          <h4>关键词抓取</h4>
+                          <p>自动监测企业微信群关键词(不满意、投诉、退款)</p>
+                        </div>
+                      </div>
+                      <div class="feature-card">
+                        <div class="feature-icon">🏷️</div>
+                        <div class="feature-content">
+                          <h4>智能标注</h4>
+                          <p>自动标注投诉环节和核心问题</p>
+                        </div>
+                      </div>
+                      <div class="feature-card">
+                        <div class="feature-icon">📊</div>
+                        <div class="feature-content">
+                          <h4>实时更新</h4>
+                          <p>处理进度实时更新至系统</p>
+                        </div>
+                      </div>
+                    </div>
+                    <div class="complaint-content">
+                      <app-complaint-card [complaints]="exceptionHistories"></app-complaint-card>
+                    </div>
+                    @if (canEditSection('aftercare')) {
+                      <div class="complaint-actions">
+                        <button class="primary-btn" (click)="createComplaintManually()">📝 人工创建投诉</button>
+                        <button class="secondary-btn" (click)="setupKeywordMonitoring()">⚙️ 设置关键词监测</button>
+                        <button class="primary-btn" (click)="confirmComplaint()">✅ 确认投诉处理完成</button>
+                      </div>
+                    }
+                  </div>
+                </div>
+              </div>
+              }
+               
+               <!-- 串式流程:10个阶段横向排列(保持) -->
               <div class="stage-progress-container">
                 @for (stage of getVisibleStages(); track stage) {
                   <div class="vertical-stage-block" [attr.id]="stageToAnchor(stage)" [class.active]="getStageStatus(stage) === 'active'">
@@ -633,6 +856,24 @@
                           (orderCreated)="onConsultationOrderSubmit($event)"
                           (projectCreated)="onProjectCreated($event)"
                         ></app-consultation-order-panel>
+                        <div class="order-creation-extra" style="margin-top:16px;">
+                          <div class="info-item">
+                            <label>订单金额</label>
+                            <span>¥{{ orderAmount || 0 }}</span>
+                          </div>
+                          <app-quotation-details
+                            [initialData]="quotationData"
+                            (dataChange)="onQuotationDataChange($event)"
+                          ></app-quotation-details>
+                          <div class="designer-assignment-container" style="margin-top:16px;">
+                            <app-designer-assignment
+                              [quotationItems]="quotationData.items"
+                              [initialAssignment]="designerAssignmentData"
+                              (assignmentChange)="onDesignerAssignmentChange($event)"
+                              (designerClick)="onDesignerClick($event)"
+                            ></app-designer-assignment>
+                          </div>
+                        </div>
                       } @else if (stage === '需求沟通') {
                         <app-requirements-confirm-card 
                           (requirementConfirmed)="syncRequirementKeyInfo($event)"
@@ -935,6 +1176,12 @@
                                                 <line x1="6" y1="6" x2="18" y2="18"></line>
                                               </svg>
                                             </button>
+                                            <button class="download-btn" (click)="$event.stopPropagation(); downloadImage(img)" [disabled]="img.locked">
+                                              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                                                <path d="M12 3v12m0 0l-4-4m4 4l4-4" stroke-linecap="round" stroke-linejoin="round"></path>
+                                                <path d="M5 21h14a2 2 0 0 0 2-2v-4" stroke-linecap="round" stroke-linejoin="round"></path>
+                                              </svg>
+                                            </button>
                                           </div>
                                         </div>
                                       </div>
@@ -1029,50 +1276,7 @@
                           </div>
                         </div>
                       } @else if (stage === '尾款结算') {
-                        <div class="aftercare-section-container">
-                          <app-settlement-card [settlements]="settlements"></app-settlement-card>
-                        </div>
-                        @if (canEditSection('aftercare')) {
-                          <div class="upload-actions">
-                            <button 
-                              class="primary-btn" 
-                              [class.completed]="isSettlementCompleted"
-                              [disabled]="isConfirmingSettlement"
-                              (click)="confirmSettlement()">
-                              @if (isConfirmingSettlement) {
-                                <span class="loading-spinner"></span>
-                                确认中...
-                              } @else if (isSettlementCompleted) {
-                                <span class="check-icon">✓</span>
-                                已确认完成
-                              } @else {
-                                确认尾款结算完成
-                              }
-                            </button>
-                            <button class="secondary-btn" (click)="uploadPaymentProof()">
-                              <span class="upload-icon">📎</span>
-                              上传支付凭证
-                            </button>
-                          </div>
-                        }
-                      } @else if (stage === '客户评价') {
-                        <div class="aftercare-section-container">
-                          <app-customer-review-card [feedbacks]="feedbacks"></app-customer-review-card>
-                        </div>
-                        @if (canEditSection('aftercare')) {
-                          <div class="upload-actions">
-                            <button class="primary-btn" (click)="confirmCustomerReview()">确认客户评价完成</button>
-                          </div>
-                        }
-                      } @else if (stage === '投诉处理') {
-                        <div class="aftercare-section-container">
-                          <app-complaint-card [complaints]="exceptionHistories"></app-complaint-card>
-                        </div>
-                        @if (canEditSection('aftercare')) {
-                          <div class="upload-actions">
-                            <button class="primary-btn" (click)="confirmComplaint()">确认投诉处理完成</button>
-                          </div>
-                        }
+                        <!-- 原尾款结算售后 tab 容器已移除,根据用户要求不再使用 -->
                       }
                     </div>
                   </div>
@@ -1082,7 +1286,8 @@
         </div>
       </div>
     }
-    
+
+
     <!-- 项目人员标签页 -->
     @if (isActiveTab('members')) {
       <div class="members-tab-content">

+ 1011 - 1
src/app/pages/designer/project-detail/project-detail.scss

@@ -1,5 +1,613 @@
 @use '../../../shared/styles/_ios-theme.scss' as *;
-@import './components/vertical-nav/vertical-nav-styles';
+
+/* 头部导航样式 */
+.header-nav {
+  .header-nav-tabs {
+    display: flex;
+    gap: 8px;
+    
+    .nav-tab {
+      padding: 8px 16px;
+      border: none;
+      border-radius: 8px;
+      background: #f8f9fa;
+      color: #666;
+      font-size: 14px;
+      font-weight: 500;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      
+      &:hover {
+        background: #e9ecef;
+      }
+      
+      &.active {
+        background: #007aff;
+        color: white;
+      }
+    }
+  }
+}
+
+/* ==================== 售后模块独立布局样式 ==================== */
+.aftercare-modules-container {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
+  gap: 24px;
+  margin: 32px 0;
+  padding: 0 16px;
+  
+  @media (max-width: 1024px) {
+    grid-template-columns: 1fr;
+    gap: 20px;
+  }
+  
+  @media (max-width: 768px) {
+    margin: 24px 0;
+    padding: 0 12px;
+    gap: 16px;
+  }
+}
+
+/* 新增:右侧 2x2 售后模块栅格 */
+.aftercare-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 16px;
+  align-items: start;
+}
+
+@media (max-width: 1200px) {
+  .aftercare-grid {
+    grid-template-columns: 1fr;
+    gap: 12px;
+  }
+}
+
+.aftercare-module {
+  background: white;
+  border: 1px solid $ios-border;
+  border-radius: 16px;
+  padding: 24px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  transition: all 0.3s ease;
+  
+  /* 固定高度 + 内部滚动,实现每个模块为独立固定尺寸区域 */
+  height: 420px;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
+    border-color: rgba(0, 122, 255, 0.3);
+  }
+  
+  .module-header {
+    margin-bottom: 20px;
+    padding-bottom: 16px;
+    border-bottom: 2px solid rgba(0, 122, 255, 0.1);
+    
+    h2 {
+      font-size: 20px;
+      font-weight: 600;
+      color: $ios-text-primary;
+      margin: 0 0 8px 0;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+    }
+    
+    .module-description {
+      color: $ios-text-secondary;
+      font-size: 14px;
+      line-height: 1.5;
+      margin: 0;
+    }
+  }
+  
+  /* 将内容区设置为可滚动,保证头部固定显示 */
+  > .settlement-automation,
+  > .panoramic-synthesis,
+  > .customer-review-enhanced,
+  > .complaint-management-enhanced {
+    flex: 1 1 auto;
+    overflow-y: auto;
+    overflow-x: hidden;
+    padding-right: 6px; /* 避免滚动条遮挡内容 */
+
+    /* 自定义滚动条样式 */
+    &::-webkit-scrollbar {
+      width: 8px;
+    }
+    &::-webkit-scrollbar-track {
+      background: #f1f1f1;
+      border-radius: 4px;
+    }
+    &::-webkit-scrollbar-thumb {
+      background: #c1c1c1;
+      border-radius: 4px;
+    }
+
+    /* Firefox 滚动条样式 */
+    scrollbar-width: thin;
+    scrollbar-color: #c1c1c1 #f1f1f1;
+  }
+}
+
+/* 尾款结算模块特定样式 */
+.settlement-module {
+  border-top: 4px solid #34c759;
+  
+  .settlement-automation {
+    .automation-features {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+      gap: 16px;
+      margin-bottom: 20px;
+      
+      .feature-card {
+        background: rgba(52, 199, 89, 0.05);
+        border: 1px solid rgba(52, 199, 89, 0.2);
+        border-radius: 12px;
+        padding: 16px;
+        text-align: center;
+        transition: all 0.3s ease;
+        
+        &:hover {
+          background: rgba(52, 199, 89, 0.1);
+          transform: translateY(-2px);
+        }
+        
+        .feature-icon {
+          font-size: 24px;
+          margin-bottom: 8px;
+        }
+        
+        .feature-content {
+          h4 {
+            font-size: 14px;
+            font-weight: 600;
+            color: #2c5530;
+            margin: 0 0 4px 0;
+          }
+          
+          p {
+            font-size: 12px;
+            color: #666;
+            margin: 0;
+            line-height: 1.4;
+          }
+        }
+      }
+    }
+    
+    .settlement-status {
+      margin-bottom: 20px;
+    }
+    
+    .automation-actions {
+      display: flex;
+      gap: 12px;
+      flex-wrap: wrap;
+      
+      .automation-btn {
+        background: linear-gradient(135deg, #34c759 0%, #2c9e4b 100%);
+        border: none;
+        
+        &:hover:not(:disabled) {
+          box-shadow: 0 4px 16px rgba(52, 199, 89, 0.4);
+        }
+        
+        .loading-spinner {
+          width: 16px;
+          height: 16px;
+          border: 2px solid transparent;
+          border-top: 2px solid white;
+          border-radius: 50%;
+          animation: spin 1s linear infinite;
+          margin-right: 8px;
+        }
+      }
+      
+      .secondary-btn {
+        background: white;
+        border: 1px solid #34c759;
+        color: #34c759;
+        
+        &:hover {
+          background: rgba(52, 199, 89, 0.1);
+        }
+        
+        .upload-icon {
+          margin-right: 6px;
+        }
+      }
+    }
+  }
+}
+
+/* 全景图合成模块特定样式 */
+.panoramic-module {
+  border-top: 4px solid #ff9500;
+  
+  .panoramic-synthesis {
+    .panoramic-features {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+      gap: 16px;
+      margin-bottom: 20px;
+      
+      .feature-card {
+        background: rgba(255, 149, 0, 0.05);
+        border: 1px solid rgba(255, 149, 0, 0.2);
+        border-radius: 12px;
+        padding: 16px;
+        text-align: center;
+        transition: all 0.3s ease;
+        
+        &:hover {
+          background: rgba(255, 149, 0, 0.1);
+          transform: translateY(-2px);
+        }
+        
+        .feature-icon {
+          font-size: 24px;
+          margin-bottom: 8px;
+        }
+        
+        .feature-content {
+          h4 {
+            font-size: 14px;
+            font-weight: 600;
+            color: #d35400;
+            margin: 0 0 4px 0;
+          }
+          
+          p {
+            font-size: 12px;
+            color: #666;
+            margin: 0;
+            line-height: 1.4;
+          }
+        }
+      }
+    }
+    
+    .panoramic-actions {
+      display: flex;
+      gap: 12px;
+      margin-bottom: 20px;
+      flex-wrap: wrap;
+      
+      .primary-btn {
+        background: linear-gradient(135deg, #ff9500 0%, #f39c12 100%);
+        border: none;
+        
+        &:hover {
+          box-shadow: 0 4px 16px rgba(255, 149, 0, 0.4);
+        }
+      }
+      
+      .secondary-btn {
+        background: white;
+        border: 1px solid #ff9500;
+        color: #ff9500;
+        
+        &:hover {
+          background: rgba(255, 149, 0, 0.1);
+        }
+      }
+    }
+    
+    .recent-syntheses {
+      background: rgba(255, 149, 0, 0.02);
+      border: 1px solid rgba(255, 149, 0, 0.1);
+      border-radius: 12px;
+      padding: 16px;
+      
+      h4 {
+        font-size: 16px;
+        font-weight: 600;
+        color: #d35400;
+        margin: 0 0 12px 0;
+      }
+      
+      .synthesis-list {
+        display: flex;
+        flex-direction: column;
+        gap: 12px;
+      }
+      
+      .empty-state {
+        text-align: center;
+        padding: 24px 16px;
+        color: #8e8e93;
+        
+        .empty-icon {
+          font-size: 32px;
+          margin-bottom: 12px;
+          opacity: 0.5;
+        }
+        
+        .empty-title {
+          font-size: 16px;
+          font-weight: 600;
+          margin-bottom: 8px;
+          color: #666;
+        }
+        
+        .empty-desc {
+          font-size: 14px;
+        }
+      }
+    }
+  }
+}
+
+/* 客户评价模块特定样式 */
+.review-module {
+  border-top: 4px solid #007aff;
+  
+  .customer-review-enhanced {
+    .review-dimensions {
+      background: rgba(0, 122, 255, 0.02);
+      border: 1px solid rgba(0, 122, 255, 0.1);
+      border-radius: 12px;
+      padding: 20px;
+      margin-bottom: 20px;
+      
+      .dimension-grid {
+        display: grid;
+        grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+        gap: 16px;
+        
+        .dimension-item {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          padding: 16px;
+          background: white;
+          border-radius: 8px;
+          border: 1px solid rgba(0, 122, 255, 0.1);
+          
+          .dimension-label {
+            font-size: 14px;
+            font-weight: 500;
+            color: $ios-text-primary;
+            margin-bottom: 12px;
+          }
+          
+          .star-rating {
+            display: flex;
+            gap: 4px;
+            
+            .star {
+              font-size: 18px;
+              color: #ddd;
+              transition: color 0.3s ease;
+              
+              &.filled {
+                color: #ffc107;
+              }
+            }
+          }
+        }
+      }
+    }
+    
+    .review-content {
+      margin-bottom: 20px;
+      
+      .review-form-section {
+        background: white;
+        border: 1px solid $ios-border;
+        border-radius: 12px;
+        padding: 20px;
+        margin-top: 16px;
+        
+        .section-title {
+          font-size: 16px;
+          font-weight: 600;
+          color: $ios-text-primary;
+          margin: 0 0 16px 0;
+        }
+      }
+    }
+    
+    .review-actions {
+      display: flex;
+      gap: 12px;
+      flex-wrap: wrap;
+      
+      .primary-btn {
+        background: linear-gradient(135deg, #007aff 0%, #0051d5 100%);
+        border: none;
+        
+        &:hover {
+          box-shadow: 0 4px 16px rgba(0, 122, 255, 0.4);
+        }
+      }
+      
+      .secondary-btn {
+        background: white;
+        border: 1px solid #007aff;
+        color: #007aff;
+        
+        &:hover {
+          background: rgba(0, 122, 255, 0.1);
+        }
+      }
+    }
+  }
+}
+
+/* 投诉管理模块特定样式 */
+.complaint-module {
+  border-top: 4px solid #ff3b30;
+  
+  .complaint-management-enhanced {
+    .complaint-features {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+      gap: 16px;
+      margin-bottom: 20px;
+      
+      .feature-card {
+        background: rgba(255, 59, 48, 0.05);
+        border: 1px solid rgba(255, 59, 48, 0.2);
+        border-radius: 12px;
+        padding: 16px;
+        text-align: center;
+        transition: all 0.3s ease;
+        
+        &:hover {
+          background: rgba(255, 59, 48, 0.1);
+          transform: translateY(-2px);
+        }
+        
+        .feature-icon {
+          font-size: 24px;
+          margin-bottom: 8px;
+        }
+        
+        .feature-content {
+          h4 {
+            font-size: 14px;
+            font-weight: 600;
+            color: #c0392b;
+            margin: 0 0 4px 0;
+          }
+          
+          p {
+            font-size: 12px;
+            color: #666;
+            margin: 0;
+            line-height: 1.4;
+          }
+        }
+      }
+    }
+    
+    .complaint-content {
+      margin-bottom: 20px;
+    }
+    
+    .complaint-actions {
+      display: flex;
+      gap: 12px;
+      flex-wrap: wrap;
+      
+      .primary-btn {
+        background: linear-gradient(135deg, #ff3b30 0%, #c0392b 100%);
+        border: none;
+        
+        &:hover {
+          box-shadow: 0 4px 16px rgba(255, 59, 48, 0.4);
+        }
+      }
+      
+      .secondary-btn {
+        background: white;
+        border: 1px solid #ff3b30;
+        color: #ff3b30;
+        
+        &:hover {
+          background: rgba(255, 59, 48, 0.1);
+        }
+      }
+    }
+  }
+}
+
+/* 通用按钮样式 */
+.primary-btn, .secondary-btn {
+  padding: 12px 20px;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  min-height: 44px;
+  
+  &:disabled {
+    opacity: 0.6;
+    cursor: not-allowed;
+  }
+}
+
+.primary-btn {
+  color: white;
+  border: none;
+}
+
+.secondary-btn {
+  background: white;
+  border: 1px solid currentColor;
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .aftercare-modules-container {
+    grid-template-columns: 1fr;
+    gap: 16px;
+    margin: 20px 0;
+    padding: 0 12px;
+  }
+  
+  .aftercare-module {
+    padding: 20px 16px;
+    
+    .module-header {
+      margin-bottom: 16px;
+      padding-bottom: 12px;
+      
+      h2 {
+        font-size: 18px;
+      }
+      
+      .module-description {
+        font-size: 13px;
+      }
+    }
+  }
+  
+  .settlement-automation .automation-features,
+  .panoramic-synthesis .panoramic-features,
+  .complaint-management-enhanced .complaint-features {
+    grid-template-columns: 1fr;
+    gap: 12px;
+  }
+  
+  .customer-review-enhanced .review-dimensions .dimension-grid {
+    grid-template-columns: 1fr;
+    gap: 12px;
+  }
+  
+  .automation-actions,
+  .panoramic-actions,
+  .review-actions,
+  .complaint-actions {
+    flex-direction: column;
+    gap: 8px;
+    
+    .primary-btn,
+    .secondary-btn {
+      width: 100%;
+      justify-content: center;
+    }
+  }
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
 
 /* 顶部导航栏四个环节圆圈样式 */
 .header-center {
@@ -2081,6 +2689,75 @@
   }
 }
 
+// 报价明细样式
+.quotation-details {
+  .quotation-content {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+    
+    .quotation-list {
+      display: flex;
+      flex-direction: column;
+      gap: 8px;
+      padding: 12px;
+      background: #f8f9fa;
+      border-radius: 6px;
+      
+      .quotation-item {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: 8px 12px;
+        background: white;
+        border-radius: 4px;
+        border: 1px solid #e9ecef;
+        
+        .room-name {
+          font-weight: 500;
+          color: #495057;
+        }
+        
+        .amount {
+          font-weight: 600;
+          color: #007bff;
+        }
+      }
+    }
+    
+    .quotation-placeholder {
+      padding: 20px;
+      text-align: center;
+      color: #6c757d;
+      background: #f8f9fa;
+      border-radius: 6px;
+      border: 2px dashed #dee2e6;
+    }
+    
+    .ai-generate-btn {
+      align-self: flex-start;
+      padding: 8px 16px;
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+      color: white;
+      border: none;
+      border-radius: 6px;
+      font-size: 14px;
+      font-weight: 500;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
+      }
+      
+      &:active {
+        transform: translateY(0);
+      }
+    }
+  }
+}
+
 /* 水平导航栏容器 */
 .horizontal-nav-container {
   margin-bottom: $ios-spacing-sm; // 进一步缩小与主体内容的间距
@@ -2585,6 +3262,7 @@
   /* 内部组件样式重置 */
   app-settlement-card,
   app-customer-review-card,
+  app-customer-review-form,
   app-complaint-card {
     display: block;
     width: 100%;
@@ -2594,6 +3272,7 @@
     ::ng-deep {
       .settlement-card,
       .customer-review-card,
+      .customer-review-form,
       .complaint-card {
         border: none;
         margin: 0;
@@ -2602,6 +3281,118 @@
       }
     }
   }
+
+  /* 新增评价表单区域样式 */
+  .review-form-section {
+    margin-top: 24px;
+    padding: 20px;
+    background: white;
+    border-radius: $ios-radius-md;
+    border: 1px solid $ios-border;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+
+    .section-title {
+      font-size: 18px;
+      font-weight: 600;
+      color: $ios-text-primary;
+      margin: 0 0 16px 0;
+      text-align: center;
+      padding-bottom: 12px;
+      border-bottom: 2px solid $ios-primary;
+    }
+
+    /* 确保评价表单在售后容器内正常显示 */
+    app-customer-review-form {
+      ::ng-deep {
+        .customer-review-form {
+          box-shadow: none;
+          border: none;
+          padding: 0;
+          margin: 0;
+          
+          .form-header {
+            padding: 0 0 16px 0;
+            border-bottom: 1px solid $ios-border;
+            margin-bottom: 20px;
+
+            h3 {
+              font-size: 20px;
+              margin-bottom: 8px;
+            }
+
+            .project-info {
+              font-size: 14px;
+              color: $ios-text-secondary;
+            }
+          }
+
+          .form-content {
+            .rating-section {
+              background: $ios-background-secondary;
+              border-radius: $ios-radius-md;
+              padding: 16px;
+              margin-bottom: 16px;
+
+              h4 {
+                font-size: 16px;
+                margin-bottom: 8px;
+              }
+
+              .dimensions-grid {
+                gap: 12px;
+
+                .dimension-item {
+                  background: white;
+                  border-radius: $ios-radius-sm;
+                  padding: 12px;
+                  border: 1px solid $ios-border;
+
+                  label {
+                    font-size: 14px;
+                  }
+
+                  .dimension-desc {
+                    font-size: 12px;
+                  }
+                }
+              }
+            }
+
+            .form-actions {
+              border-top: 1px solid $ios-border;
+              padding-top: 16px;
+              margin-top: 20px;
+
+              .submit-btn {
+                padding: 10px 24px;
+                font-size: 14px;
+                border-radius: $ios-radius-lg;
+
+                &.primary {
+                  background: $ios-primary;
+                  color: white;
+
+                  &:hover {
+                    background: darken($ios-primary, 10%);
+                  }
+                }
+
+                &.secondary {
+                  border: 1px solid $ios-border;
+                  background: white;
+                  color: $ios-text-primary;
+
+                  &:hover {
+                    background: $ios-background-secondary;
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
 }
 
 // 响应式设计优化
@@ -2658,4 +3449,223 @@
     height: 250px;
     padding: 8px;
   }
+}
+
+/* 新增售后标签页样式 */
+.aftercare-section-container {
+  .aftercare-tabs {
+    .tab-buttons {
+      display: flex;
+      gap: 8px;
+      margin-bottom: 24px;
+      border-bottom: 2px solid $ios-border;
+      padding-bottom: 16px;
+
+      .tab-button {
+        background: white;
+        border: 2px solid $ios-border;
+        padding: 12px 20px;
+        border-radius: 8px 8px 0 0;
+        font-weight: 500;
+        cursor: pointer;
+        transition: all 0.3s ease;
+        color: $ios-text-secondary;
+
+        &:hover {
+          background: $ios-background-secondary;
+          border-color: $ios-primary;
+          color: $ios-primary;
+        }
+
+        &.active {
+          background: linear-gradient(135deg, $ios-primary 0%, darken($ios-primary, 15%) 100%);
+          color: white;
+          border-color: $ios-primary;
+          transform: translateY(-2px);
+          box-shadow: 0 4px 12px rgba($ios-primary, 0.3);
+        }
+      }
+    }
+
+    .tab-content {
+      .automation-header,
+      .panoramic-header,
+      .review-header,
+      .complaint-header {
+        margin-bottom: 24px;
+
+        h3 {
+          font-size: 20px;
+          font-weight: 600;
+          color: $ios-text-primary;
+          margin-bottom: 8px;
+        }
+
+        .automation-description,
+        .panoramic-description,
+        .review-description,
+        .complaint-description {
+          color: $ios-text-secondary;
+          font-size: 14px;
+          line-height: 1.5;
+        }
+      }
+
+      .automation-features,
+      .panoramic-features,
+      .complaint-features {
+        display: grid;
+        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+        gap: 16px;
+        margin-bottom: 24px;
+
+        .feature-card {
+          background: white;
+          border: 1px solid $ios-border;
+          border-radius: 12px;
+          padding: 20px;
+          text-align: center;
+          transition: all 0.3s ease;
+
+          &:hover {
+            transform: translateY(-4px);
+            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
+          }
+
+          .feature-icon {
+            font-size: 32px;
+            margin-bottom: 12px;
+          }
+
+          .feature-content {
+            h4 {
+              font-size: 16px;
+              font-weight: 600;
+              color: $ios-text-primary;
+              margin-bottom: 8px;
+            }
+
+            p {
+              color: $ios-text-secondary;
+              font-size: 14px;
+              line-height: 1.4;
+            }
+          }
+        }
+      }
+
+      .settlement-status,
+      .review-content,
+      .complaint-content {
+        background: white;
+        border: 1px solid $ios-border;
+        border-radius: 12px;
+        padding: 20px;
+        margin-bottom: 20px;
+      }
+
+      .automation-actions,
+      .panoramic-actions,
+      .review-actions,
+      .complaint-actions {
+        display: flex;
+        gap: 12px;
+        margin-bottom: 24px;
+
+        .automation-btn {
+          background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
+          
+          &:hover:not(:disabled) {
+            box-shadow: 0 4px 12px rgba(255, 107, 107, 0.4);
+          }
+        }
+      }
+
+      .recent-syntheses {
+        background: white;
+        border: 1px solid $ios-border;
+        border-radius: 12px;
+        padding: 20px;
+
+        h4 {
+          font-size: 16px;
+          font-weight: 600;
+          color: $ios-text-primary;
+          margin-bottom: 16px;
+        }
+
+        .synthesis-list {
+          display: flex;
+          flex-direction: column;
+          gap: 12px;
+        }
+
+        .empty-state {
+          text-align: center;
+          padding: 40px 20px;
+          color: $ios-text-secondary;
+
+          .empty-icon {
+            font-size: 48px;
+            margin-bottom: 16px;
+          }
+
+          .empty-title {
+            font-size: 18px;
+            font-weight: 600;
+            margin-bottom: 8px;
+          }
+
+          .empty-desc {
+            font-size: 14px;
+          }
+        }
+      }
+
+      .review-dimensions {
+        background: white;
+        border: 1px solid $ios-border;
+        border-radius: 12px;
+        padding: 20px;
+        margin-bottom: 20px;
+
+        .dimension-grid {
+          display: grid;
+          grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+          gap: 16px;
+
+          .dimension-item {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            padding: 16px;
+            background: $ios-background-secondary;
+            border-radius: 8px;
+
+            .dimension-label {
+              font-size: 14px;
+              font-weight: 500;
+              color: $ios-text-primary;
+              margin-bottom: 12px;
+            }
+
+            .star-rating {
+              display: flex;
+              gap: 4px;
+
+              .star {
+                font-size: 18px;
+                color: #ddd;
+                transition: color 0.3s ease;
+
+                &.filled {
+                  color: #ffc107;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
 }

+ 408 - 25
src/app/pages/designer/project-detail/project-detail.ts

@@ -10,15 +10,21 @@ import {
   CustomerFeedback,
   DesignerChange,
   Settlement,
-  ProjectStage
+  ProjectStage,
+  PanoramicSynthesis
 } from '../../../models/project.model';
 import { ConsultationOrderPanelComponent } from '../../../shared/components/consultation-order-panel/consultation-order-panel.component';
 import { RequirementsConfirmCardComponent } from '../../../shared/components/requirements-confirm-card/requirements-confirm-card';
 import { SettlementCardComponent } from '../../../shared/components/settlement-card/settlement-card';
 import { CustomerReviewCardComponent } from '../../../shared/components/customer-review-card/customer-review-card';
+import { CustomerReviewFormComponent } from '../../../shared/components/customer-review-form/customer-review-form';
 import { ComplaintCardComponent } from '../../../shared/components/complaint-card/complaint-card';
+import { PanoramicSynthesisCardComponent } from '../../../shared/components/panoramic-synthesis-card/panoramic-synthesis-card';
+import { QuotationDetailsComponent, QuotationData } from './components/quotation-details/quotation-details.component';
+import { DesignerAssignmentComponent, DesignerAssignmentData, Designer as AssignmentDesigner } from './components/designer-assignment/designer-assignment.component';
+// 引入客户服务模块的设计师日历组件
+import { DesignerCalendarComponent, Designer as CalendarDesigner, ProjectGroup as CalendarProjectGroup } from '../../customer-service/consultation-order/components/designer-calendar/designer-calendar.component';
 
-import { VerticalNavComponent } from './components/vertical-nav/vertical-nav.component';
 import { ColorAnalysisResult } from '../../../shared/services/color-analysis.service';
 
 interface ExceptionHistory {
@@ -188,7 +194,7 @@ interface ProposalAnalysis {
 @Component({
   selector: 'app-project-detail',
   standalone: true,
-  imports: [CommonModule, FormsModule, ReactiveFormsModule, ConsultationOrderPanelComponent, RequirementsConfirmCardComponent, SettlementCardComponent, CustomerReviewCardComponent, ComplaintCardComponent, VerticalNavComponent],
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, ConsultationOrderPanelComponent, RequirementsConfirmCardComponent, SettlementCardComponent, CustomerReviewCardComponent, CustomerReviewFormComponent, ComplaintCardComponent, PanoramicSynthesisCardComponent, QuotationDetailsComponent, DesignerAssignmentComponent, DesignerCalendarComponent],
   templateUrl: './project-detail.html',
   styleUrls: ['./project-detail.scss', './debug-styles.scss']
 })
@@ -214,6 +220,8 @@ export class ProjectDetail implements OnInit, OnDestroy {
   // 新增:尾款结算完成状态
   isSettlementCompleted: boolean = false;
   isConfirmingSettlement: boolean = false;
+  isSettlementInitiated: boolean = false;
+  isPaymentVerified: boolean = false;
   
   // 客户信息卡片展开/收起状态
   isCustomerInfoExpanded: boolean = false;
@@ -250,7 +258,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
     { key: 'order', label: '订单创建', stages: ['订单创建'] },
     { key: 'requirements', label: '确认需求', stages: ['需求沟通', '方案确认'] },
     { key: 'delivery', label: '交付执行', stages: ['建模', '软装', '渲染', '后期'] },
-    { key: 'aftercare', label: '售后', stages: ['尾款结算', '客户评价', '投诉处理'] }
+    { key: 'aftercare', label: '售后', stages: [] }
   ];
   expandedSection: SectionKey | null = null;
   // 渲染异常反馈相关属性
@@ -265,7 +273,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
   
   // 标签页相关
   activeTab: 'progress' | 'members' | 'files' = 'progress';
-  tabs = [
+  tabs: Array<{ id: 'progress' | 'members' | 'files'; name: string }> = [
     { id: 'progress', name: '项目进度' },
     { id: 'members', name: '项目成员' },
     { id: 'files', name: '项目文件' }
@@ -294,7 +302,7 @@ export class ProjectDetail implements OnInit, OnDestroy {
   // 增加审核状态reviewStatus与是否已同步synced标记(仅由组长操作)
   whiteModelImages: Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = [];
   softDecorImages: Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = [];
-  renderLargeImages: Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = [];
+  renderLargeImages: Array<{ id: string; name: string; url: string; size?: string; locked?: boolean; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = [];
   postProcessImages: Array<{ id: string; name: string; url: string; size?: string; reviewStatus?: 'pending' | 'approved' | 'rejected'; synced?: boolean }> = [];
   showRenderUploadModal: boolean = false;
   pendingRenderLargeItems: Array<{ id: string; name: string; url: string; file: File }> = [];
@@ -1532,8 +1540,13 @@ export class ProjectDetail implements OnInit, OnDestroy {
   showImagePreview: boolean = false;
   previewImageData: any = null;
 
-  // 图片预览方法
+  // 图片预览方法(含渲染大图加锁校验)
   previewImage(img: any): void {
+    const isRenderLarge = !!this.renderLargeImages.find(i => i.id === img?.id);
+    if (isRenderLarge && img?.locked) {
+      alert('该渲染大图已加锁,需完成尾款结算并上传/识别支付凭证后方可预览。');
+      return;
+    }
     this.previewImageData = img;
     this.showImagePreview = true;
   }
@@ -1544,6 +1557,11 @@ export class ProjectDetail implements OnInit, OnDestroy {
   }
 
   downloadImage(img: any): void {
+    const isRenderLarge = !!this.renderLargeImages.find(i => i.id === img?.id);
+    if (isRenderLarge && img?.locked) {
+      alert('该渲染大图已加锁,需完成尾款结算并上传/识别支付凭证后方可下载。');
+      return;
+    }
     if (img) {
       const link = document.createElement('a');
       link.href = img.url;
@@ -1751,7 +1769,8 @@ export class ProjectDetail implements OnInit, OnDestroy {
         id: item.id, 
         name: item.name, 
         url: item.url, 
-        size: this.formatFileSize(f.size) 
+        size: this.formatFileSize(f.size),
+        locked: true 
       });
     }
     input.value = '';
@@ -2381,22 +2400,128 @@ export class ProjectDetail implements OnInit, OnDestroy {
     return Math.round((completedCount / totalCount) * 100);
   }
 
-  // 获取预估小图时间
-  getEstimatedSmallImageTime(): string {
-    // 根据需求复杂度计算预估时间
-    const baseTime = 2; // 基础时间2小时
-    let additionalTime = 0;
+  // 订单金额
+  orderAmount: number = 0;
+  
+  // 报价明细
+  quotationDetails: Array<{
+    id: string;
+    room: string;
+    amount: number;
+    description?: string;
+  }> = [];
+
+  // AI生成报价明细
+  generateQuotationDetails(): void {
+    // 基于项目信息生成报价明细
+    const rooms = ['客餐厅', '主卧', '次卧', '厨房', '卫生间'];
+    this.quotationDetails = rooms.map((room, index) => ({
+      id: `quote_${index + 1}`,
+      room: room,
+      amount: Math.floor(Math.random() * 1000) + 300, // 示例金额
+      description: `${room}装修设计费用`
+    }));
+    
+    // 更新总订单金额
+    this.orderAmount = this.quotationDetails.reduce((total, item) => total + item.amount, 0);
+  }
 
-    // 根据材质复杂度增加时间
-    if (this.requirementKeyInfo.materialWeights.woodRatio > 0) additionalTime += 0.5;
-    if (this.requirementKeyInfo.materialWeights.fabricRatio > 0) additionalTime += 0.5;
-    if (this.requirementKeyInfo.materialWeights.metalRatio > 0) additionalTime += 0.5;
+  // 添加报价明细项
+  addQuotationItem(): void {
+    this.quotationDetails.push({
+      id: `quote_${Date.now()}`,
+      room: '',
+      amount: 0,
+      description: ''
+    });
+  }
 
-    // 根据空间复杂度增加时间
-    if (this.requirementKeyInfo.spaceStructure.aspectRatio > 2) additionalTime += 1;
+  // 删除报价明细项
+  removeQuotationItem(id: string): void {
+    this.quotationDetails = this.quotationDetails.filter(item => item.id !== id);
+    this.updateOrderAmount();
+  }
 
-    const totalTime = baseTime + additionalTime;
-    return `${totalTime}小时`;
+  // 更新订单总金额
+  updateOrderAmount(): void {
+    this.orderAmount = this.quotationDetails.reduce((total, item) => total + item.amount, 0);
+  }
+
+  // 报价组件数据
+  quotationData: QuotationData = {
+    items: [],
+    totalAmount: 0,
+    materialCost: 0,
+    laborCost: 0,
+    designFee: 0,
+    managementFee: 0
+  };
+
+  // 设计师指派数据
+  designerAssignmentData?: DesignerAssignmentData;
+
+  // 设计师日历弹窗状态与数据
+  showDesignerCalendar: boolean = false;
+  selectedCalendarDate: Date = new Date();
+  calendarDesigners: CalendarDesigner[] = [];
+  calendarGroups: CalendarProjectGroup[] = [];
+
+  onQuotationDataChange(data: QuotationData): void {
+    this.quotationData = { ...data };
+    this.orderAmount = data.totalAmount || 0;
+  }
+
+  onDesignerAssignmentChange(data: DesignerAssignmentData): void {
+    this.designerAssignmentData = { ...data };
+  }
+
+  onDesignerClick(designer: AssignmentDesigner): void {
+    const mapped = this.mapAssignmentDesignerToCalendar(designer);
+    this.calendarDesigners = [mapped];
+    this.calendarGroups = [{ id: designer.teamId, name: designer.teamName, leaderId: designer.id, memberIds: [designer.id] }];
+    this.selectedCalendarDate = new Date();
+    this.showDesignerCalendar = true;
+  }
+
+  closeDesignerCalendar(): void {
+    this.showDesignerCalendar = false;
+  }
+
+  onCalendarDesignerSelected(designer: CalendarDesigner): void {
+    this.selectedDesigner = designer;
+    this.closeDesignerCalendar();
+  }
+
+  onCalendarAssignmentRequested(designer: CalendarDesigner): void {
+    this.selectedDesigner = designer;
+    this.closeDesignerCalendar();
+  }
+
+  private mapAssignmentDesignerToCalendar(d: AssignmentDesigner): CalendarDesigner {
+    return {
+      id: d.id,
+      name: d.name,
+      avatar: d.avatar,
+      groupId: d.teamId,
+      groupName: d.teamName,
+      isLeader: !!d.isTeamLeader,
+      status: d.status === 'idle' ? 'available' : 'busy',
+      currentProjects: Math.max(0, d.recentOrders || 0),
+      lastOrderDate: d.lastOrderDate,
+      idleDays: Math.max(0, d.idleDays || 0),
+      completedThisMonth: Math.max(0, d.recentOrders || 0),
+      averageCycle: 15,
+      upcomingEvents: (d.reviewDates || []).map((dateStr, idx) => ({
+        id: `${d.id}-review-${idx}`,
+        date: new Date(dateStr),
+        title: '对图评审',
+        type: 'review',
+        projectId: undefined,
+        duration: 2
+      })),
+      workload: Math.max(0, d.workload || 0),
+      nextAvailableDate: new Date()
+    };
   }
 
   // 处理咨询订单表单提交
@@ -2409,11 +2534,14 @@ export class ProjectDetail implements OnInit, OnDestroy {
     // 保存订单创建数据
     this.orderCreationData = formData;
     
-    // 更新projectData以便传递给子组件
+    // 更新projectData以便传递给子组件(集成报价与指派信息)
     this.projectData = {
       customerInfo: formData.customerInfo,
       requirementInfo: formData.requirementInfo,
-      preferenceTags: formData.preferenceTags
+      preferenceTags: formData.preferenceTags,
+      quotation: this.quotationData ? { ...this.quotationData } : undefined,
+      assignment: this.designerAssignmentData ? { ...this.designerAssignmentData } : undefined,
+      orderAmount: this.quotationData?.totalAmount ?? this.orderAmount ?? 0
     };
     
     // 实时更新左侧客户信息显示
@@ -2424,6 +2552,10 @@ export class ProjectDetail implements OnInit, OnDestroy {
       // 客服端创建的订单需要同步到设计师端
       this.syncOrderToDesignerView(formData);
     }
+
+    // 根据报价数据更新订单金额
+    this.orderAmount = this.quotationData?.totalAmount ?? this.orderAmount ?? 0;
+    this.updateOrderAmount();
     
     // 触发变更检测以更新UI
     this.cdr.detectChanges();
@@ -2467,7 +2599,6 @@ export class ProjectDetail implements OnInit, OnDestroy {
           area: formData.requirementInfo.area,
           houseType: formData.requirementInfo.houseType,
           smallImageTime: formData.requirementInfo.smallImageTime,
-          largeImageTime: formData.requirementInfo.largeImageTime,
           spaceRequirements: formData.requirementInfo.spaceRequirements,
           designAngles: formData.requirementInfo.designAngles,
           specialAreaHandling: formData.requirementInfo.specialAreaHandling,
@@ -2492,7 +2623,11 @@ export class ProjectDetail implements OnInit, OnDestroy {
         demandType: formData.customerInfo.demandType,
         preferenceTags: formData.preferenceTags,
         followUpStatus: formData.customerInfo.followUpStatus
-      }
+      },
+      // 新增:报价与指派信息(可选)
+      quotation: this.quotationData ? { ...this.quotationData } : undefined,
+      assignment: this.designerAssignmentData ? { ...this.designerAssignmentData } : undefined,
+      orderAmount: this.quotationData?.totalAmount ?? this.orderAmount ?? 0
     };
 
     // 调用项目服务创建项目
@@ -3051,6 +3186,254 @@ export class ProjectDetail implements OnInit, OnDestroy {
         fileType: file.type,
         uploadTime: new Date().toISOString()
       });
+      // 自动标记验证通过并解锁渲染大图
+      this.isPaymentVerified = true;
+      this.renderLargeImages = this.renderLargeImages.map(img => ({ ...img, locked: false }));
+    }, 2000);
+  }
+
+  // 获取当前设计师名称
+  getCurrentDesignerName(): string {
+    // 这里应该从用户服务或认证信息中获取当前用户名称
+    // 暂时返回一个默认值
+    return '张设计师';
+  }
+
+  // ==================== 售后相关变量 ====================
+
+  // 售后标签页控制
+  activeAftercareTab: string = 'services'; // 当前激活的售后标签页
+  
+  // 售后状态管理
+  afterSalesStage: string = '未开始'; // 售后阶段:未开始、进行中、已完成
+  afterSalesStatus: 'pending' | 'in_progress' | 'completed' | 'cancelled' = 'pending'; // 售后状态
+  afterSalesProgress: number = 0; // 售后进度百分比
+  
+  // 售后服务数据
+  afterSalesServices: Array<{
+    id: string;
+    type: string; // 服务类型:维修、保养、咨询、投诉处理
+    description: string;
+    assignedTo: string; // 指派给的人员
+    scheduledDate?: Date; // 计划服务日期
+    completedDate?: Date; // 完成日期
+    status: 'pending' | 'scheduled' | 'in_progress' | 'completed' | 'cancelled';
+    priority: 'low' | 'medium' | 'high';
+  }> = [];
+
+  // 售后评价和反馈
+  afterSalesRating: number = 0; // 售后评分(0-5)
+  afterSalesFeedback: string = ''; // 售后反馈内容
+  afterSalesFeedbackDate?: Date; // 反馈日期
+  
+  // 售后时间跟踪
+  afterSalesResponseTime?: Date; // 首次响应时间
+  afterSalesStartTime?: Date; // 售后开始时间
+  afterSalesCompletionTime?: Date; // 售后完成时间
+  afterSalesTotalDuration: number = 0; // 总耗时(小时)
+
+  // 售后联系人信息
+  afterSalesContact: {
+    name: string;
+    phone: string;
+    wechat?: string;
+    email?: string;
+  } = {
+    name: '',
+    phone: ''
+  };
+
+  // 售后问题记录
+  afterSalesIssues: Array<{
+    id: string;
+    title: string;
+    description: string;
+    severity: 'low' | 'medium' | 'high' | 'critical';
+    reportedDate: Date;
+    resolvedDate?: Date;
+    status: 'reported' | 'investigating' | 'resolved' | 'closed';
+  }> = [];
+
+  // 售后文件记录
+  afterSalesDocuments: Array<{
+    id: string;
+    name: string;
+    type: string; // 文件类型:合同、报告、照片、视频
+    uploadDate: Date;
+    url: string;
+  }> = [];
+
+  // 售后费用记录
+  afterSalesCosts: Array<{
+    id: string;
+    description: string;
+    amount: number;
+    category: 'material' | 'labor' | 'transportation' | 'other';
+    date: Date;
+    status: 'pending' | 'approved' | 'paid' | 'rejected';
+  }> = [];
+
+  // 售后沟通记录
+  afterSalesCommunications: Array<{
+    id: string;
+    type: 'phone' | 'wechat' | 'email' | 'visit';
+    date: Date;
+    summary: string;
+    participants: string[];
+  }> = [];
+
+  // 切换售后标签页
+  switchAftercareTab(tab: string): void {
+    this.activeAftercareTab = tab;
+    console.log('切换到售后标签页:', tab);
+  }
+
+  // ==================== 自动结算相关 ====================
+  
+  // 自动结算状态
+  isAutoSettling: boolean = false;
+  
+  // 启动自动结算
+  initiateAutoSettlement(): void {
+    this.isAutoSettling = true;
+    console.log('启动自动结算流程...');
+    
+    // 模拟自动结算过程
+    setTimeout(() => {
+      this.isAutoSettling = false;
+      console.log('自动结算完成');
+      alert('自动结算已完成!');
+    }, 3000);
+  }
+
+  // ==================== 全景图合成相关 ====================
+  
+  // 全景图合成数据
+  panoramicSyntheses: PanoramicSynthesis[] = [];
+
+  // 启动全景图合成
+  startPanoramicSynthesis(): void {
+    const synthesis: PanoramicSynthesis = {
+      id: 'panoramic-' + Date.now(),
+      projectId: this.projectId,
+      projectName: this.project?.name || '未知项目',
+      spaces: [],
+      status: 'processing',
+      quality: 'high',
+      createdAt: new Date(),
+      updatedAt: new Date()
+    };
+    
+    this.panoramicSyntheses.push(synthesis);
+    console.log('启动全景图合成:', synthesis);
+    
+    // 模拟合成进度
+    const progressInterval = setInterval(() => {
+      const currentSynthesis = this.panoramicSyntheses.find(s => s.id === synthesis.id);
+      if (currentSynthesis) {
+        // 模拟进度更新
+        if (currentSynthesis.status === 'processing') {
+          currentSynthesis.status = 'completed';
+          currentSynthesis.completedAt = new Date();
+          currentSynthesis.updatedAt = new Date();
+          currentSynthesis.previewUrl = 'https://via.placeholder.com/1920x1080';
+          currentSynthesis.downloadUrl = 'https://via.placeholder.com/1920x1080';
+          currentSynthesis.renderTime = 120;
+          currentSynthesis.fileSize = 1024 * 1024 * 50; // 50MB
+          clearInterval(progressInterval);
+          console.log('全景图合成完成:', currentSynthesis);
+        }
+      }
     }, 2000);
   }
+
+  // 查看全景图画廊
+  viewPanoramicGallery(): void {
+    console.log('打开全景图画廊');
+    // 这里可以打开一个模态框或导航到画廊页面
+    alert('全景图画廊功能开发中...');
+  }
+
+  // ==================== 评价统计相关 ====================
+  
+  // 评价统计数据
+  reviewStats: {
+    overallScore: number;
+    timelinessScore: number;
+    qualityScore: number;
+    communicationScore: number;
+  } = {
+    overallScore: 4.8,
+    timelinessScore: 4.7,
+    qualityScore: 4.9,
+    communicationScore: 4.6
+  };
+
+  // ==================== 客户评价相关 ====================
+  
+  // 生成评价链接
+  generateReviewLink(): void {
+    console.log('生成客户评价链接');
+    const reviewLink = `https://review.example.com/project/${this.projectId}`;
+    navigator.clipboard.writeText(reviewLink).then(() => {
+      alert('评价链接已复制到剪贴板!');
+    }).catch(() => {
+      alert(`评价链接:${reviewLink}`);
+    });
+  }
+
+  // ==================== 投诉管理相关 ====================
+  
+  // 手动创建投诉
+  createComplaintManually(): void {
+    console.log('手动创建投诉');
+    // 这里可以打开一个模态框来创建投诉
+    alert('手动创建投诉功能开发中...');
+  }
+
+  // 设置关键词监控
+  setupKeywordMonitoring(): void {
+    console.log('设置关键词监控');
+    // 这里可以打开关键词监控设置界面
+    alert('关键词监控设置功能开发中...');
+  }
+
+  // 处理评价表单提交
+  onReviewSubmitted(reviewData: any): void {
+    console.log('客户评价已提交:', reviewData);
+    
+    // 这里应该调用API将评价数据保存到服务器
+    // 模拟API调用
+    setTimeout(() => {
+      alert('客户评价提交成功!评价数据已保存。');
+      
+      // 更新本地反馈数据
+      const newFeedback: CustomerFeedback = {
+        id: Date.now().toString(),
+        customerName: '客户', // 应该从项目信息中获取
+        rating: reviewData.overallRating,
+        content: reviewData.improvementSuggestions || '无具体建议',
+        createdAt: new Date(),
+        status: '已解决',
+        isSatisfied: reviewData.overallRating >= 4,
+        projectId: this.projectId
+      };
+      
+      this.feedbacks = [...this.feedbacks, newFeedback];
+      
+      // 自动标记客户评价完成
+      this.confirmCustomerReview();
+    }, 1000);
+  }
+
+  // 处理评价表单保存草稿
+  onReviewSaved(reviewData: any): void {
+    console.log('客户评价草稿已保存:', reviewData);
+    
+    // 这里应该调用API将草稿数据保存到服务器
+    // 模拟API调用
+    setTimeout(() => {
+      alert('评价草稿保存成功!您可以稍后继续完善。');
+    }, 500);
+  }
 }

+ 351 - 0
src/app/services/ai-quotation.service.ts

@@ -0,0 +1,351 @@
+import { Injectable } from '@angular/core';
+import { Observable, of, delay } from 'rxjs';
+
+export interface AIQuotationParams {
+  area: number;
+  style: string;
+  level: string;
+  specialRequirements?: string;
+}
+
+export interface AIQuotationItem {
+  category: string;
+  name: string;
+  quantity: number;
+  unit: string;
+  unitPrice: number;
+  description?: string;
+}
+
+export interface AIQuotationResult {
+  items: AIQuotationItem[];
+  totalAmount: number;
+  suggestions?: string[];
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class AIQuotationService {
+  
+  // 装修风格价格系数
+  private styleMultipliers: { [key: string]: number } = {
+    '现代简约': 1.0,
+    '北欧风格': 1.1,
+    '中式风格': 1.3,
+    '欧式风格': 1.4,
+    '美式风格': 1.2,
+    '工业风格': 1.1,
+    '地中海风格': 1.2,
+    '日式风格': 1.15
+  };
+
+  // 装修档次价格系数
+  private levelMultipliers: { [key: string]: number } = {
+    '经济型': 0.8,
+    '舒适型': 1.0,
+    '豪华型': 1.5,
+    '奢华型': 2.0
+  };
+
+  // 基础报价模板
+  private baseQuotationTemplate: AIQuotationItem[] = [
+    {
+      category: '客餐厅',
+      name: '墙面乳胶漆',
+      quantity: 1,
+      unit: '㎡',
+      unitPrice: 35,
+      description: '包含基层处理、刮腻子、刷底漆面漆'
+    },
+    {
+      category: '客餐厅',
+      name: '地面瓷砖铺贴',
+      quantity: 1,
+      unit: '㎡',
+      unitPrice: 80,
+      description: '包含瓷砖、水泥沙浆、人工费'
+    },
+    {
+      category: '客餐厅',
+      name: '吊顶造型',
+      quantity: 1,
+      unit: '㎡',
+      unitPrice: 120,
+      description: '石膏板吊顶,包含龙骨、石膏板、乳胶漆'
+    },
+    {
+      category: '厨房',
+      name: '厨房墙地砖',
+      quantity: 1,
+      unit: '㎡',
+      unitPrice: 90,
+      description: '防滑地砖、墙面瓷砖铺贴'
+    },
+    {
+      category: '厨房',
+      name: '橱柜定制',
+      quantity: 1,
+      unit: '延米',
+      unitPrice: 1200,
+      description: '包含地柜、吊柜、台面、五金配件'
+    },
+    {
+      category: '卫生间',
+      name: '卫生间防水',
+      quantity: 1,
+      unit: '㎡',
+      unitPrice: 60,
+      description: '聚氨酯防水涂料,包工包料'
+    },
+    {
+      category: '卫生间',
+      name: '卫浴洁具',
+      quantity: 1,
+      unit: '套',
+      unitPrice: 2500,
+      description: '马桶、洗手盆、花洒、龙头等'
+    },
+    {
+      category: '卧室',
+      name: '卧室地板',
+      quantity: 1,
+      unit: '㎡',
+      unitPrice: 150,
+      description: '复合地板,包含踢脚线安装'
+    },
+    {
+      category: '卧室',
+      name: '衣柜定制',
+      quantity: 1,
+      unit: '㎡',
+      unitPrice: 800,
+      description: '整体衣柜,包含五金配件'
+    },
+    {
+      category: '水电改造',
+      name: '水电改造',
+      quantity: 1,
+      unit: '项',
+      unitPrice: 8000,
+      description: '全屋水电线路改造,包含材料人工'
+    }
+  ];
+
+  constructor() { }
+
+  /**
+   * 生成AI智能报价
+   * @param params 报价参数
+   * @returns 报价结果
+   */
+  generateQuotation(params: AIQuotationParams): Observable<AIQuotationResult> {
+    // 模拟AI处理延迟
+    return of(this.calculateQuotation(params)).pipe(
+      delay(2000) // 模拟2秒的AI处理时间
+    );
+  }
+
+  /**
+   * 计算报价
+   * @param params 报价参数
+   * @returns 报价结果
+   */
+  private calculateQuotation(params: AIQuotationParams): AIQuotationResult {
+    const { area, style, level, specialRequirements } = params;
+    
+    // 获取价格系数
+    const styleMultiplier = this.styleMultipliers[style] || 1.0;
+    const levelMultiplier = this.levelMultipliers[level] || 1.0;
+    
+    // 计算各项目数量和价格
+    const items: AIQuotationItem[] = this.baseQuotationTemplate.map(template => {
+      let quantity = area;
+      let unitPrice = template.unitPrice * styleMultiplier * levelMultiplier;
+      
+      // 根据项目类型调整数量
+      switch (template.category) {
+        case '客餐厅':
+          quantity = Math.round(area * 0.4); // 客餐厅占40%面积
+          break;
+        case '厨房':
+          if (template.name.includes('橱柜')) {
+            quantity = Math.round(area * 0.08); // 橱柜按延米计算
+          } else {
+            quantity = Math.round(area * 0.1); // 厨房占10%面积
+          }
+          break;
+        case '卫生间':
+          if (template.name.includes('洁具')) {
+            quantity = Math.ceil(area / 50); // 每50㎡一套洁具
+          } else {
+            quantity = Math.round(area * 0.08); // 卫生间占8%面积
+          }
+          break;
+        case '卧室':
+          if (template.name.includes('衣柜')) {
+            quantity = Math.round(area * 0.15); // 衣柜按投影面积
+          } else {
+            quantity = Math.round(area * 0.3); // 卧室占30%面积
+          }
+          break;
+        case '水电改造':
+          quantity = 1; // 水电改造按项目计算
+          unitPrice = area * 80 * levelMultiplier; // 按面积计算水电改造费用
+          break;
+      }
+      
+      return {
+        ...template,
+        quantity: Math.max(1, quantity),
+        unitPrice: Math.round(unitPrice)
+      };
+    });
+
+    // 根据特殊需求调整报价
+    if (specialRequirements) {
+      this.adjustForSpecialRequirements(items, specialRequirements);
+    }
+
+    // 计算总金额
+    const totalAmount = items.reduce((sum, item) => sum + (item.quantity * item.unitPrice), 0);
+
+    // 生成建议
+    const suggestions = this.generateSuggestions(params, totalAmount);
+
+    return {
+      items,
+      totalAmount: Math.round(totalAmount),
+      suggestions
+    };
+  }
+
+  /**
+   * 根据特殊需求调整报价
+   * @param items 报价项目
+   * @param specialRequirements 特殊需求
+   */
+  private adjustForSpecialRequirements(items: AIQuotationItem[], specialRequirements: string): void {
+    const requirements = specialRequirements.toLowerCase();
+    
+    // 智能家居需求
+    if (requirements.includes('智能') || requirements.includes('智能家居')) {
+      items.push({
+        category: '智能家居',
+        name: '智能家居系统',
+        quantity: 1,
+        unit: '套',
+        unitPrice: 15000,
+        description: '包含智能开关、智能门锁、智能窗帘等'
+      });
+    }
+
+    // 中央空调需求
+    if (requirements.includes('中央空调') || requirements.includes('空调')) {
+      items.push({
+        category: '暖通设备',
+        name: '中央空调系统',
+        quantity: 1,
+        unit: '套',
+        unitPrice: 25000,
+        description: '包含主机、管道、出风口等'
+      });
+    }
+
+    // 地暖需求
+    if (requirements.includes('地暖') || requirements.includes('采暖')) {
+      items.push({
+        category: '暖通设备',
+        name: '地暖系统',
+        quantity: 1,
+        unit: '套',
+        unitPrice: 18000,
+        description: '包含地暖管道、分水器、温控器等'
+      });
+    }
+
+    // 新风系统需求
+    if (requirements.includes('新风') || requirements.includes('通风')) {
+      items.push({
+        category: '暖通设备',
+        name: '新风系统',
+        quantity: 1,
+        unit: '套',
+        unitPrice: 12000,
+        description: '包含新风主机、管道、出风口等'
+      });
+    }
+
+    // 定制家具需求
+    if (requirements.includes('定制') || requirements.includes('家具')) {
+      items.push({
+        category: '定制家具',
+        name: '全屋定制家具',
+        quantity: 1,
+        unit: '套',
+        unitPrice: 30000,
+        description: '包含鞋柜、电视柜、书柜等定制家具'
+      });
+    }
+  }
+
+  /**
+   * 生成装修建议
+   * @param params 报价参数
+   * @param totalAmount 总金额
+   * @returns 建议列表
+   */
+  private generateSuggestions(params: AIQuotationParams, totalAmount: number): string[] {
+    const suggestions: string[] = [];
+    const { area, style, level } = params;
+    
+    // 预算建议
+    const budgetPerSqm = totalAmount / area;
+    if (budgetPerSqm > 2000) {
+      suggestions.push('当前预算较高,建议优化材料选择以控制成本');
+    } else if (budgetPerSqm < 800) {
+      suggestions.push('当前预算偏低,建议适当提升材料档次以保证质量');
+    }
+
+    // 风格建议
+    if (style === '现代简约') {
+      suggestions.push('现代简约风格注重功能性,建议选择简洁大方的材料和色彩');
+    } else if (style === '中式风格') {
+      suggestions.push('中式风格建议选用实木材料,注重对称和层次感');
+    } else if (style === '北欧风格') {
+      suggestions.push('北欧风格建议使用浅色系,注重自然光线和简约线条');
+    }
+
+    // 档次建议
+    if (level === '经济型') {
+      suggestions.push('经济型装修建议重点投入在基础工程,后期可逐步升级软装');
+    } else if (level === '豪华型' || level === '奢华型') {
+      suggestions.push('高端装修建议选择知名品牌材料,注重细节工艺和环保性能');
+    }
+
+    // 面积建议
+    if (area < 60) {
+      suggestions.push('小户型建议采用开放式设计,合理利用每一寸空间');
+    } else if (area > 150) {
+      suggestions.push('大户型建议做好功能分区,可考虑增加储物空间和休闲区域');
+    }
+
+    return suggestions;
+  }
+
+  /**
+   * 获取装修风格列表
+   * @returns 风格列表
+   */
+  getStyleOptions(): string[] {
+    return Object.keys(this.styleMultipliers);
+  }
+
+  /**
+   * 获取装修档次列表
+   * @returns 档次列表
+   */
+  getLevelOptions(): string[] {
+    return Object.keys(this.levelMultipliers);
+  }
+}

+ 619 - 0
src/app/services/auto-settlement.service.ts

@@ -0,0 +1,619 @@
+import { Injectable, signal, computed, inject } from '@angular/core';
+import { BehaviorSubject, Observable, of, timer, interval, forkJoin } from 'rxjs';
+import { map, switchMap, catchError } from 'rxjs/operators';
+import { Settlement } from '../models/project.model';
+import { ProjectService } from './project.service';
+import { PaymentVoucherRecognitionService, PaymentVoucherRecognitionResult } from './payment-voucher-recognition.service';
+
+export interface AutoSettlementRule {
+  id: string;
+  name: string;
+  enabled: boolean;
+  conditions: SettlementCondition[];
+  actions: SettlementAction[];
+  priority: number;
+  description?: string;
+}
+
+export interface SettlementCondition {
+  type: 'projectType' | 'amountRange' | 'customerTier' | 'overdueDays' | 'paymentMethod';
+  operator: 'equals' | 'greaterThan' | 'lessThan' | 'between' | 'contains';
+  value: any;
+}
+
+export interface SettlementAction {
+  type: 'sendReminder' | 'applyDiscount' | 'extendDueDate' | 'autoConfirm' | 'notifyManager';
+  params: any;
+}
+
+export interface SettlementReminder {
+  id: string;
+  settlementId: string;
+  type: 'email' | 'sms' | 'wechat' | 'system';
+  content: string;
+  sentAt: Date;
+  status: 'pending' | 'sent' | 'failed';
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class AutoSettlementService {
+  private scheduledProcesses = new Map<string, any>();
+  private paymentRecognitionService = inject(PaymentVoucherRecognitionService);
+  
+  constructor(private projectService: ProjectService) {}
+  
+  private rules = signal<AutoSettlementRule[]>([
+    {
+      id: 'rule-1',
+      name: '小额自动确认',
+      enabled: true,
+      priority: 1,
+      conditions: [
+        { type: 'amountRange', operator: 'lessThan', value: 5000 }
+      ],
+      actions: [
+        { type: 'autoConfirm', params: { immediate: true } }
+      ],
+      description: '金额小于5000元时自动确认结算'
+    },
+    {
+      id: 'rule-2',
+      name: '逾期提醒',
+      enabled: true,
+      priority: 2,
+      conditions: [
+        { type: 'overdueDays', operator: 'greaterThan', value: 7 }
+      ],
+      actions: [
+        { type: 'sendReminder', params: { channels: ['wechat', 'sms'], frequency: 'daily' } }
+      ],
+      description: '逾期7天以上时每天发送提醒'
+    },
+    {
+      id: 'rule-3',
+      name: 'VIP客户优惠',
+      enabled: true,
+      priority: 3,
+      conditions: [
+        { type: 'customerTier', operator: 'equals', value: 'vip' },
+        { type: 'overdueDays', operator: 'greaterThan', value: 15 }
+      ],
+      actions: [
+        { type: 'applyDiscount', params: { percentage: 5, maxAmount: 1000 } }
+      ],
+      description: 'VIP客户逾期15天以上时提供5%折扣'
+    }
+  ]);
+
+  private reminders = signal<SettlementReminder[]>([]);
+  private isProcessing = signal(false);
+
+  // 获取所有规则
+  getRules(): Observable<AutoSettlementRule[]> {
+    return of(this.rules());
+  }
+
+  // 添加新规则
+  addRule(rule: AutoSettlementRule): void {
+    this.rules.update(rules => [...rules, rule]);
+  }
+
+  // 更新规则
+  updateRule(ruleId: string, updates: Partial<AutoSettlementRule>): void {
+    this.rules.update(rules => 
+      rules.map(rule => rule.id === ruleId ? { ...rule, ...updates } : rule)
+    );
+  }
+
+  // 删除规则
+  deleteRule(ruleId: string): void {
+    this.rules.update(rules => rules.filter(rule => rule.id !== ruleId));
+  }
+
+  // 处理结算自动化
+  processSettlementAutomation(settlement: Settlement): Observable<boolean> {
+    this.isProcessing.set(true);
+    
+    return of(this.rules())
+      .pipe(
+        map(rules => rules.filter(rule => rule.enabled)),
+        map(enabledRules => {
+          let processed = false;
+          
+          // 按优先级排序处理规则
+          enabledRules.sort((a, b) => a.priority - b.priority);
+          
+          for (const rule of enabledRules) {
+            if (this.checkConditions(rule.conditions, settlement)) {
+              this.executeActions(rule.actions, settlement);
+              processed = true;
+              
+              // 高优先级规则可能中断后续规则执行
+              if (rule.priority >= 10) {
+                break;
+              }
+            }
+          }
+          
+          return processed;
+        }),
+        switchMap(processed => {
+          this.isProcessing.set(false);
+          return of(processed);
+        })
+      );
+  }
+
+  // 检查条件是否满足
+  private checkConditions(conditions: SettlementCondition[], settlement: Settlement): boolean {
+    return conditions.every(condition => {
+      switch (condition.type) {
+        case 'amountRange':
+          return this.checkAmountCondition(condition, settlement.amount || 0);
+        case 'overdueDays':
+          const overdueDays = this.calculateOverdueDays(settlement);
+          return this.checkNumericCondition(condition, overdueDays);
+        case 'customerTier':
+          // 简化实现,实际中需要从客户服务获取层级信息
+          return condition.operator === 'equals' && condition.value === 'vip';
+        default:
+          return false;
+      }
+    });
+  }
+
+  // 执行动作
+  private executeActions(actions: SettlementAction[], settlement: Settlement): void {
+    actions.forEach(action => {
+      switch (action.type) {
+        case 'sendReminder':
+          this.sendReminder(settlement, action.params);
+          break;
+        case 'applyDiscount':
+          this.applyDiscount(settlement, action.params);
+          break;
+        case 'autoConfirm':
+          this.autoConfirmSettlement(settlement, action.params);
+          break;
+        case 'notifyManager':
+          this.notifyManager(settlement, action.params);
+          break;
+      }
+    });
+  }
+
+  // 发送提醒
+  private sendReminder(settlement: Settlement, params: any): void {
+    const reminder: SettlementReminder = {
+      id: `reminder-${Date.now()}`,
+      settlementId: settlement.id,
+      type: 'system',
+      content: `结算提醒:项目 ${settlement.projectName} 的 ${settlement.amount} 元结算${this.calculateOverdueDays(settlement) > 0 ? '已逾期' : '待处理'}`,
+      sentAt: new Date(),
+      status: 'sent'
+    };
+    
+    this.reminders.update(reminders => [...reminders, reminder]);
+    
+    // 实际实现中这里会调用消息服务发送到不同渠道
+    console.log('发送结算提醒:', reminder);
+  }
+
+  // 应用折扣
+  private applyDiscount(settlement: Settlement, params: any): void {
+    const discountAmount = Math.min(
+      (settlement.amount || 0) * (params.percentage / 100),
+      params.maxAmount || 0
+    );
+    
+    console.log(`为结算 ${settlement.id} 应用折扣: ${discountAmount}元`);
+    // 实际实现中需要更新结算金额
+  }
+
+  // 自动确认结算
+  private autoConfirmSettlement(settlement: Settlement, params: any): void {
+    console.log(`自动确认结算: ${settlement.id}`);
+    // 实际实现中需要调用结算服务确认结算
+  }
+
+  // 通知经理
+  private notifyManager(settlement: Settlement, params: any): void {
+    console.log(`通知经理处理大额结算: ${settlement.id}, 金额: ${settlement.amount}`);
+  }
+
+  // 计算逾期天数
+  private calculateOverdueDays(settlement: Settlement): number {
+    if (settlement.status === '已结算') return 0;
+    
+    const dueDate = settlement.dueDate || new Date(settlement.createdAt.getTime() + 30 * 24 * 60 * 60 * 1000);
+    const today = new Date();
+    const diffTime = today.getTime() - dueDate.getTime();
+    return Math.max(0, Math.ceil(diffTime / (1000 * 60 * 60 * 24)));
+  }
+
+  // 检查金额条件
+  private checkAmountCondition(condition: SettlementCondition, amount: number): boolean {
+    return this.checkNumericCondition(condition, amount);
+  }
+
+  // 检查数值条件
+  private checkNumericCondition(condition: SettlementCondition, value: number): boolean {
+    switch (condition.operator) {
+      case 'equals':
+        return value === condition.value;
+      case 'greaterThan':
+        return value > condition.value;
+      case 'lessThan':
+        return value < condition.value;
+      case 'between':
+        return value >= condition.value[0] && value <= condition.value[1];
+      default:
+        return false;
+    }
+  }
+
+  // 获取处理状态
+  getProcessingStatus() {
+    return this.isProcessing();
+  }
+
+  // 获取提醒记录
+  getReminders(): Observable<SettlementReminder[]> {
+    return of(this.reminders());
+  }
+
+  // 启动定时任务
+  startScheduledProcessing(): void {
+    // 清理现有的定时任务
+    this.stopAllScheduledProcessing();
+    
+    // 每30分钟检查一次逾期结算
+    this.scheduledProcesses.set('overdueCheck', 
+      timer(0, 30 * 60 * 1000).subscribe(() => {
+        this.processOverdueSettlements();
+      })
+    );
+    
+    // 每天早上9点发送每日提醒
+    this.scheduledProcesses.set('dailyReminder',
+      this.scheduleDailyTask(9, 0, () => {
+        this.sendDailyReminders();
+      })
+    );
+    
+    // 每周一早上10点发送周报
+    this.scheduledProcesses.set('weeklyReport',
+      this.scheduleWeeklyTask(1, 10, 0, () => {
+        this.sendWeeklySettlementReport();
+      })
+    );
+    
+    // 每小时检查高优先级规则
+    this.scheduledProcesses.set('hourlyCheck',
+      timer(0, 60 * 60 * 1000).subscribe(() => {
+        this.processHighPrioritySettlements();
+      })
+    );
+    
+    console.log('自动化结算定时任务已启动');
+  }
+
+  // 停止所有定时任务
+  stopAllScheduledProcessing(): void {
+    this.scheduledProcesses.forEach((subscription, key) => {
+      subscription.unsubscribe();
+    });
+    this.scheduledProcesses.clear();
+  }
+
+  // 安排每日定时任务
+  private scheduleDailyTask(hour: number, minute: number, task: () => void): any {
+    const now = new Date();
+    const targetTime = new Date();
+    targetTime.setHours(hour, minute, 0, 0);
+    
+    let initialDelay = targetTime.getTime() - now.getTime();
+    if (initialDelay < 0) {
+      initialDelay += 24 * 60 * 60 * 1000; // 第二天同一时间
+    }
+    
+    return timer(initialDelay, 24 * 60 * 60 * 1000).subscribe(() => {
+      task();
+    });
+  }
+
+  // 安排每周定时任务
+  private scheduleWeeklyTask(dayOfWeek: number, hour: number, minute: number, task: () => void): any {
+    const now = new Date();
+    const targetTime = new Date();
+    
+    // 计算下一个指定星期几
+    const daysUntilTarget = (dayOfWeek - now.getDay() + 7) % 7;
+    targetTime.setDate(now.getDate() + daysUntilTarget);
+    targetTime.setHours(hour, minute, 0, 0);
+    
+    let initialDelay = targetTime.getTime() - now.getTime();
+    if (initialDelay < 0) {
+      initialDelay += 7 * 24 * 60 * 60 * 1000; // 下一周同一时间
+    }
+    
+    return timer(initialDelay, 7 * 24 * 60 * 60 * 1000).subscribe(() => {
+      task();
+    });
+  }
+
+  // 处理逾期结算
+  private processOverdueSettlements(): void {
+    console.log('执行定时逾期结算检查');
+    
+    // 获取所有待结算的记录
+    this.projectService.getSettlements().pipe(
+      map(settlements => settlements.filter(s => s.status === '待结算')),
+      switchMap(pendingSettlements => {
+        const overdueSettlements = pendingSettlements.filter(settlement => 
+          this.calculateOverdueDays(settlement) > 0
+        );
+        
+        // 对每个逾期结算应用自动化规则
+        const processingObservables = overdueSettlements.map(settlement =>
+          this.processSettlementAutomation(settlement).pipe(
+            catchError(error => {
+              console.error(`处理结算 ${settlement.id} 时出错:`, error);
+              return of(false);
+            })
+          )
+        );
+        
+        return processingObservables.length > 0 
+          ? forkJoin(processingObservables) 
+          : of([]);
+      })
+    ).subscribe(results => {
+      const successful = results.filter(result => result).length;
+      console.log(`逾期结算处理完成,成功处理 ${successful} 个结算`);
+    });
+  }
+
+  // 处理高优先级结算
+  private processHighPrioritySettlements(): void {
+    this.projectService.getSettlements().pipe(
+      map(settlements => settlements.filter(s => s.status === '待结算')),
+      map(pendingSettlements => {
+        // 筛选需要立即处理的高优先级结算(大金额或特定客户)
+        return pendingSettlements.filter(settlement => 
+          (settlement.amount || 0) > 10000 || // 大金额
+          settlement.projectName?.includes('VIP') // VIP客户
+        );
+      }),
+      switchMap(highPrioritySettlements => {
+        const processingObservables = highPrioritySettlements.map(settlement =>
+          this.processSettlementAutomation(settlement).pipe(
+            catchError(error => {
+              console.error(`处理高优先级结算 ${settlement.id} 时出错:`, error);
+              return of(false);
+            })
+          )
+        );
+        
+        return processingObservables.length > 0 
+          ? forkJoin(processingObservables) 
+          : of([]);
+      })
+    ).subscribe(results => {
+      const successful = results.filter(result => result).length;
+      if (successful > 0) {
+        console.log(`高优先级结算处理完成,成功处理 ${successful} 个结算`);
+      }
+    });
+  }
+
+  // 发送每日提醒
+  private sendDailyReminders(): void {
+    this.projectService.getSettlements().pipe(
+      map(settlements => settlements.filter(s => s.status === '待结算')),
+      map(pendingSettlements => {
+        const today = new Date();
+        return pendingSettlements.filter(settlement => {
+          const dueDate = settlement.dueDate || new Date(settlement.createdAt.getTime() + 30 * 24 * 60 * 60 * 1000);
+          const daysUntilDue = Math.ceil((dueDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
+          
+          // 发送即将到期(3天内)和已逾期的提醒
+          return daysUntilDue <= 3 || this.calculateOverdueDays(settlement) > 0;
+        });
+      })
+    ).subscribe(settlementsToRemind => {
+      settlementsToRemind.forEach(settlement => {
+        this.sendReminder(settlement, {
+          channels: ['system', 'email'],
+          frequency: 'daily',
+          type: 'dueDateReminder'
+        });
+      });
+      
+      console.log(`每日提醒发送完成,共发送 ${settlementsToRemind.length} 个提醒`);
+    });
+  }
+
+  // 发送周报
+  private sendWeeklySettlementReport(): void {
+    this.projectService.getSettlements().subscribe(allSettlements => {
+      const pendingCount = allSettlements.filter(s => s.status === '待结算').length;
+      const overdueCount = allSettlements.filter(s => 
+        s.status === '待结算' && this.calculateOverdueDays(s) > 0
+      ).length;
+      const completedThisWeek = allSettlements.filter(s => 
+        s.status === '已结算' && 
+        s.settledAt && 
+        new Date(s.settledAt).getTime() > Date.now() - 7 * 24 * 60 * 60 * 1000
+      ).length;
+      
+      const report = {
+        totalPending: pendingCount,
+        totalOverdue: overdueCount,
+        completedThisWeek: completedThisWeek,
+        generatedAt: new Date()
+      };
+      
+      console.log('周度结算报告:', report);
+      
+      // 这里可以添加发送邮件或系统通知的逻辑
+      this.sendManagerNotification('weeklyReport', report);
+    });
+  }
+
+  // 发送经理通知
+  private sendManagerNotification(type: string, data: any): void {
+    const notification = {
+      id: `notification-${Date.now()}`,
+      type: type,
+      data: data,
+      timestamp: new Date(),
+      read: false
+    };
+    
+    console.log('发送经理通知:', notification);
+    // 实际实现中会调用通知服务
+  }
+
+  // 获取定时任务状态
+  getScheduledTasksStatus(): { [key: string]: boolean } {
+    const status: { [key: string]: boolean } = {};
+    this.scheduledProcesses.forEach((subscription, key) => {
+      status[key] = !subscription.closed;
+    });
+    return status;
+  }
+
+  // 支付凭证识别相关方法
+
+  /**
+   * 处理支付凭证上传并自动识别
+   */
+  async processPaymentVoucherUpload(file: File, settlementId: string): Promise<PaymentVoucherRecognitionResult> {
+    try {
+      const result = await this.paymentRecognitionService.recognizePaymentVoucher(file).toPromise();
+      
+      if (result && result.success) {
+        // 识别成功,更新结算状态
+        await this.updateSettlementWithPaymentInfo(settlementId, result);
+        
+        // 检查是否需要自动确认结算
+        if (this.shouldAutoConfirmAfterPayment(result)) {
+          await this.autoConfirmSettlementAfterPayment(settlementId, result);
+        }
+      }
+      
+      return result || {
+        success: false,
+        confidence: 0,
+        error: '支付凭证处理失败'
+      };
+    } catch (error) {
+      console.error('支付凭证处理失败:', error);
+      return {
+        success: false,
+        confidence: 0,
+        error: '支付凭证处理失败'
+      };
+    }
+  }
+
+  /**
+   * 批量处理支付凭证
+   */
+  async processBatchPaymentVouchers(files: File[], settlementId: string): Promise<PaymentVoucherRecognitionResult[]> {
+    const results: PaymentVoucherRecognitionResult[] = [];
+    
+    for (const file of files) {
+      try {
+        const result = await this.processPaymentVoucherUpload(file, settlementId);
+        results.push(result);
+      } catch (error) {
+        results.push({
+          success: false,
+          confidence: 0,
+          error: `文件 ${file.name} 处理失败`
+        });
+      }
+    }
+    
+    return results;
+  }
+
+  /**
+   * 使用支付信息更新结算记录
+   */
+  private async updateSettlementWithPaymentInfo(settlementId: string, paymentInfo: PaymentVoucherRecognitionResult): Promise<void> {
+    // 这里需要实现更新结算记录的逻辑
+    // 实际实现中会调用项目服务更新结算信息
+    console.log(`更新结算 ${settlementId} 的支付信息:`, paymentInfo);
+    
+    // 模拟更新操作
+    const settlements = await this.projectService.getSettlements().toPromise();
+    const settlement = settlements?.find(s => s.id === settlementId);
+    
+    if (settlement) {
+      // 更新结算记录的支付相关信息
+      console.log('结算记录已更新支付信息');
+    }
+  }
+
+  /**
+   * 检查支付后是否需要自动确认结算
+   */
+  private shouldAutoConfirmAfterPayment(paymentInfo: PaymentVoucherRecognitionResult): boolean {
+    // 根据支付金额、支付方式等条件判断是否需要自动确认
+    const hasAmount = paymentInfo.amount !== undefined && paymentInfo.amount > 0;
+    const highConfidence = paymentInfo.confidence > 0.8; // 识别置信度大于80%
+    const trustedPaymentMethod = ['alipay', 'wechat', 'bank_transfer'].includes(paymentInfo.paymentMethod || '');
+    
+    return hasAmount && highConfidence && trustedPaymentMethod;
+  }
+
+  /**
+   * 支付后自动确认结算
+   */
+  private async autoConfirmSettlementAfterPayment(settlementId: string, paymentInfo: PaymentVoucherRecognitionResult): Promise<void> {
+    console.log(`支付凭证验证通过,自动确认结算 ${settlementId}`);
+    
+    // 获取结算记录
+    const settlements = await this.projectService.getSettlements().toPromise();
+    const settlement = settlements?.find(s => s.id === settlementId);
+    
+    if (settlement) {
+      // 调用自动确认逻辑
+      this.autoConfirmSettlement(settlement, { 
+        immediate: true, 
+        paymentVerified: true,
+        paymentAmount: paymentInfo.amount,
+        paymentMethod: paymentInfo.paymentMethod
+      });
+      
+      console.log('结算已自动确认');
+    }
+  }
+
+  /**
+   * 获取支持的支付凭证类型
+   */
+  getSupportedPaymentVoucherTypes(): { extensions: string[], description: string } {
+    const supportedTypes = this.paymentRecognitionService.getSupportedFileTypes();
+    return {
+      extensions: supportedTypes,
+      description: this.paymentRecognitionService.getSupportedFileTypesDescription()
+    };
+  }
+
+  /**
+   * 验证支付凭证文件
+   */
+  validatePaymentVoucherFile(file: File): { valid: boolean, error?: string } {
+    const result = this.paymentRecognitionService.validateFile(file);
+    return {
+      valid: result.isValid,
+      error: result.error
+    };
+  }
+}

+ 313 - 0
src/app/services/payment-voucher-recognition.service.ts

@@ -0,0 +1,313 @@
+import { Injectable, inject } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable, of } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
+
+// 支付凭证识别结果接口
+export interface PaymentVoucherRecognitionResult {
+  success: boolean;
+  amount?: number;
+  paymentDate?: Date;
+  paymentMethod?: string;
+  payerName?: string;
+  payerAccount?: string;
+  receiverName?: string;
+  receiverAccount?: string;
+  transactionNumber?: string;
+  confidence: number;
+  extractedText?: string;
+  error?: string;
+}
+
+// 支付凭证文件信息接口
+export interface PaymentVoucherFile {
+  id: string;
+  fileName: string;
+  fileType: string;
+  fileSize: number;
+  fileUrl: string;
+  previewUrl?: string;
+  uploadDate: Date;
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class PaymentVoucherRecognitionService {
+  private http = inject(HttpClient);
+
+  /**
+   * 识别支付凭证图片或PDF文件
+   * @param file 支付凭证文件
+   * @returns 识别结果的可观察对象
+   */
+  recognizePaymentVoucher(file: File): Observable<PaymentVoucherRecognitionResult> {
+    // 检查文件类型
+    if (!this.isSupportedFileType(file.type)) {
+      return of({
+        success: false,
+        confidence: 0,
+        error: '不支持的文件类型。请上传图片(JPG, PNG, GIF)或PDF文件。'
+      });
+    }
+
+    // 检查文件大小(限制为10MB)
+    if (file.size > 10 * 1024 * 1024) {
+      return of({
+        success: false,
+        confidence: 0,
+        error: '文件大小不能超过10MB。'
+      });
+    }
+
+    // 在实际应用中,这里会调用OCR API服务
+    // 目前先模拟识别过程
+    return this.simulateRecognition(file);
+  }
+
+  /**
+   * 批量识别支付凭证
+   * @param files 支付凭证文件数组
+   * @returns 批量识别结果的可观察对象
+   */
+  async recognizePaymentVouchers(files: File[]): Promise<PaymentVoucherRecognitionResult[]> {
+    const recognitionPromises = files.map(file => 
+      this.recognizePaymentVoucher(file).toPromise()
+    );
+
+    const results = await Promise.all(recognitionPromises);
+    return results.filter(result => result !== undefined) as PaymentVoucherRecognitionResult[];
+  }
+
+  /**
+   * 检查是否支持的文件类型
+   * @param fileType 文件MIME类型
+   */
+  private isSupportedFileType(fileType: string): boolean {
+    const supportedTypes = [
+      'image/jpeg',
+      'image/png', 
+      'image/gif',
+      'application/pdf'
+    ];
+    return supportedTypes.includes(fileType);
+  }
+
+  /**
+   * 模拟支付凭证识别过程
+   * @param file 支付凭证文件
+   */
+  private simulateRecognition(file: File): Observable<PaymentVoucherRecognitionResult> {
+    return new Observable(observer => {
+      // 模拟处理延迟
+      setTimeout(() => {
+        try {
+          // 根据文件名和类型生成模拟识别结果
+          const fileName = file.name.toLowerCase();
+          
+          // 模拟不同的识别场景
+          if (fileName.includes('alipay') || fileName.includes('支付宝')) {
+            observer.next(this.generateAlipayResult(file));
+          } else if (fileName.includes('wechat') || fileName.includes('微信')) {
+            observer.next(this.generateWechatResult(file));
+          } else if (fileName.includes('bank') || fileName.includes('银行')) {
+            observer.next(this.generateBankTransferResult(file));
+          } else {
+            observer.next(this.generateGenericResult(file));
+          }
+          
+          observer.complete();
+        } catch (error) {
+          observer.next({
+            success: false,
+            confidence: 0,
+            error: `识别过程中发生错误: ${error}`
+          });
+          observer.complete();
+        }
+      }, 2000); // 2秒延迟模拟处理时间
+    });
+  }
+
+  /**
+   * 生成支付宝支付凭证识别结果
+   */
+  private generateAlipayResult(file: File): PaymentVoucherRecognitionResult {
+    const amount = Math.round(Math.random() * 10000 + 1000); // 1000-11000之间的随机金额
+    const confidence = Math.random() * 0.3 + 0.7; // 70%-100%的置信度
+    
+    return {
+      success: true,
+      amount,
+      paymentDate: new Date(),
+      paymentMethod: '支付宝',
+      payerName: '付款人***',
+      payerAccount: 'alipay***@xxx.com',
+      receiverName: '收款人***',
+      receiverAccount: 'alipay***@xxx.com',
+      transactionNumber: `ALP${Date.now().toString().slice(-8)}`,
+      confidence,
+      extractedText: `支付宝交易凭证 金额: ${amount}元 交易时间: ${new Date().toLocaleString()}`
+    };
+  }
+
+  /**
+   * 生成微信支付凭证识别结果
+   */
+  private generateWechatResult(file: File): PaymentVoucherRecognitionResult {
+    const amount = Math.round(Math.random() * 5000 + 500); // 500-5500之间的随机金额
+    const confidence = Math.random() * 0.3 + 0.7; // 70%-100%的置信度
+    
+    return {
+      success: true,
+      amount,
+      paymentDate: new Date(),
+      paymentMethod: '微信支付',
+      payerName: '微信用户***',
+      payerAccount: 'wxid_***',
+      receiverName: '商户***',
+      receiverAccount: 'mch_***',
+      transactionNumber: `WX${Date.now().toString().slice(-8)}`,
+      confidence,
+      extractedText: `微信支付凭证 金额: ${amount}元 交易时间: ${new Date().toLocaleString()}`
+    };
+  }
+
+  /**
+   * 生成银行转账凭证识别结果
+   */
+  private generateBankTransferResult(file: File): PaymentVoucherRecognitionResult {
+    const amount = Math.round(Math.random() * 20000 + 2000); // 2000-22000之间的随机金额
+    const confidence = Math.random() * 0.2 + 0.8; // 80%-100%的置信度
+    
+    return {
+      success: true,
+      amount,
+      paymentDate: new Date(),
+      paymentMethod: '银行转账',
+      payerName: '付款人***',
+      payerAccount: '6222********1234',
+      receiverName: '收款人***',
+      receiverAccount: '6222********5678',
+      transactionNumber: `BANK${Date.now().toString().slice(-8)}`,
+      confidence,
+      extractedText: `银行转账凭证 金额: ${amount}元 交易时间: ${new Date().toLocaleString()}`
+    };
+  }
+
+  /**
+   * 生成通用支付凭证识别结果
+   */
+  private generateGenericResult(file: File): PaymentVoucherRecognitionResult {
+    const amount = Math.round(Math.random() * 15000 + 1000); // 1000-16000之间的随机金额
+    const confidence = Math.random() * 0.4 + 0.6; // 60%-100%的置信度
+    
+    const methods = ['支付宝', '微信支付', '银行转账', '现金'];
+    const paymentMethod = methods[Math.floor(Math.random() * methods.length)];
+    
+    return {
+      success: true,
+      amount,
+      paymentDate: new Date(),
+      paymentMethod,
+      payerName: '付款人***',
+      receiverName: '收款人***',
+      confidence,
+      extractedText: `支付凭证 金额: ${amount}元 支付方式: ${paymentMethod} 交易时间: ${new Date().toLocaleString()}`
+    };
+  }
+
+  /**
+   * 验证识别结果的可信度
+   * @param result 识别结果
+   * @param expectedAmount 预期金额(可选)
+   */
+  validateRecognitionResult(
+    result: PaymentVoucherRecognitionResult, 
+    expectedAmount?: number
+  ): { isValid: boolean; reasons: string[] } {
+    const reasons: string[] = [];
+    
+    if (!result.success) {
+      reasons.push('识别失败');
+      return { isValid: false, reasons };
+    }
+    
+    if (result.confidence < 0.7) {
+      reasons.push(`置信度过低: ${(result.confidence * 100).toFixed(1)}%`);
+    }
+    
+    if (!result.amount || result.amount <= 0) {
+      reasons.push('金额识别无效');
+    }
+    
+    if (expectedAmount && result.amount && Math.abs(result.amount - expectedAmount) > expectedAmount * 0.1) {
+      reasons.push(`识别金额与预期金额差异过大`);
+    }
+    
+    if (!result.paymentDate) {
+      reasons.push('支付日期识别失败');
+    }
+    
+    if (!result.paymentMethod) {
+      reasons.push('支付方式识别失败');
+    }
+    
+    return {
+      isValid: reasons.length === 0,
+      reasons
+    };
+  }
+
+  /**
+   * 格式化金额显示
+   * @param amount 金额
+   */
+  formatAmount(amount: number): string {
+    return new Intl.NumberFormat('zh-CN', {
+      style: 'currency',
+      currency: 'CNY'
+    }).format(amount);
+  }
+
+  /**
+   * 获取支持的文件类型描述
+   */
+  getSupportedFileTypesDescription(): string {
+    return '支持的文件类型: JPG, PNG, GIF图片或PDF文件,最大10MB';
+  }
+
+  /**
+   * 获取支持的文件类型列表
+   */
+  getSupportedFileTypes(): string[] {
+    return [
+      'image/jpeg',
+      'image/png', 
+      'image/gif',
+      'application/pdf'
+    ];
+  }
+
+  /**
+   * 验证文件
+   * @param file 要验证的文件
+   */
+  validateFile(file: File): { isValid: boolean; error?: string } {
+    if (!this.isSupportedFileType(file.type)) {
+      return {
+        isValid: false,
+        error: '不支持的文件类型。请上传图片(JPG, PNG, GIF)或PDF文件。'
+      };
+    }
+
+    if (file.size > 10 * 1024 * 1024) {
+      return {
+        isValid: false,
+        error: '文件大小不能超过10MB。'
+      };
+    }
+
+    return { isValid: true };
+  }
+}

+ 4 - 0
src/app/services/project.service.ts

@@ -806,6 +806,10 @@ export class ProjectService {
       preferenceTags?: string[];
       followUpStatus?: string;
     };
+    // 可选扩展字段:报价与指派信息
+    quotation?: any;
+    assignment?: any;
+    orderAmount?: number;
   }): Observable<{ success: boolean; projectId: string }> {
     // 模拟API调用
     console.log('创建项目:', projectData);

+ 256 - 0
src/app/shared/components/auto-settlement-config/auto-settlement-config.html

@@ -0,0 +1,256 @@
+<div class="auto-settlement-config">
+  <!-- 头部标题和操作按钮 -->
+  <div class="config-header">
+    <h2>自动化结算规则配置</h2>
+    <button 
+      mat-raised-button 
+      color="primary" 
+      (click)="startAddNew()"
+      [disabled]="isAddingNew()">
+      <mat-icon>add</mat-icon>
+      添加新规则
+    </button>
+  </div>
+
+  <!-- 新规则表单 -->
+  @if (isAddingNew()) {
+    <mat-card class="new-rule-card">
+      <mat-card-header>
+        <mat-card-title>新建自动化规则</mat-card-title>
+        <mat-card-subtitle>配置自动处理结算的条件和动作</mat-card-subtitle>
+      </mat-card-header>
+      
+      <mat-card-content>
+        <div class="form-section">
+          <mat-form-field appearance="outline" class="full-width">
+            <mat-label>规则名称</mat-label>
+            <input matInput [(ngModel)]="newRule.name" placeholder="例如:小额自动确认">
+          </mat-form-field>
+
+          <mat-form-field appearance="outline" class="full-width">
+            <mat-label>优先级</mat-label>
+            <input matInput type="number" [(ngModel)]="newRule.priority" min="1" max="10">
+          </mat-form-field>
+
+          <mat-form-field appearance="outline" class="full-width">
+            <mat-label>规则描述</mat-label>
+            <textarea matInput [(ngModel)]="newRule.description" rows="2" placeholder="描述规则的用途和效果"></textarea>
+          </mat-form-field>
+
+          <mat-slide-toggle [(ngModel)]="newRule.enabled" color="primary">
+            启用规则
+          </mat-slide-toggle>
+        </div>
+
+        <!-- 条件配置 -->
+        <div class="conditions-section">
+          <h3>条件配置</h3>
+          @for (condition of newRule.conditions; track $index; let i = $index) {
+            <div class="condition-item">
+              <div class="condition-header">
+                <span>条件 {{ i + 1 }}</span>
+                <button mat-icon-button color="warn" (click)="removeCondition(newRule, i)">
+                  <mat-icon>delete</mat-icon>
+                </button>
+              </div>
+              
+              <div class="condition-fields">
+                <mat-form-field appearance="outline">
+                  <mat-label>条件类型</mat-label>
+                  <mat-select [(ngModel)]="condition.type">
+                    @for (type of conditionTypes; track type.value) {
+                      <mat-option [value]="type.value">{{ type.label }}</mat-option>
+                    }
+                  </mat-select>
+                </mat-form-field>
+
+                <mat-form-field appearance="outline">
+                  <mat-label>操作符</mat-label>
+                  <mat-select [(ngModel)]="condition.operator" [disabled]="!condition.type">
+                    @for (op of getOperatorOptions(condition.type); track op.value) {
+                      <mat-option [value]="op.value">{{ op.label }}</mat-option>
+                    }
+                  </mat-select>
+                </mat-form-field>
+
+                <mat-form-field appearance="outline">
+                  <mat-label>值</mat-label>
+                  <input matInput [(ngModel)]="condition.value" placeholder="输入条件值">
+                </mat-form-field>
+              </div>
+            </div>
+          }
+          
+          <button mat-button color="primary" (click)="addCondition(newRule)">
+            <mat-icon>add</mat-icon>
+            添加条件
+          </button>
+        </div>
+
+        <!-- 动作配置 -->
+        <div class="actions-section">
+          <h3>动作配置</h3>
+          @for (action of newRule.actions; track $index; let i = $index) {
+            <div class="action-item">
+              <div class="action-header">
+                <span>动作 {{ i + 1 }}</span>
+                <button mat-icon-button color="warn" (click)="removeAction(newRule, i)">
+                  <mat-icon>delete</mat-icon>
+                </button>
+              </div>
+              
+              <div class="action-fields">
+                <mat-form-field appearance="outline">
+                  <mat-label>动作类型</mat-label>
+                  <mat-select [(ngModel)]="action.type">
+                    @for (type of actionTypes; track type.value) {
+                      <mat-option [value]="type.value">{{ type.label }}</mat-option>
+                    }
+                  </mat-select>
+                </mat-form-field>
+
+                <!-- 动作参数配置 -->
+                @if (action.type === 'sendReminder') {
+                  <div class="action-params">
+                    <mat-form-field appearance="outline">
+                      <mat-label>发送渠道</mat-label>
+                      <mat-select [(ngModel)]="action.params.channels" multiple>
+                        <mat-option value="wechat">微信</mat-option>
+                        <mat-option value="sms">短信</mat-option>
+                        <mat-option value="email">邮件</mat-option>
+                        <mat-option value="system">系统通知</mat-option>
+                      </mat-select>
+                    </mat-form-field>
+
+                    <mat-form-field appearance="outline">
+                      <mat-label>发送频率</mat-label>
+                      <mat-select [(ngModel)]="action.params.frequency">
+                        <mat-option value="immediate">立即发送</mat-option>
+                        <mat-option value="daily">每天</mat-option>
+                        <mat-option value="weekly">每周</mat-option>
+                      </mat-select>
+                    </mat-form-field>
+                  </div>
+                }
+
+                @if (action.type === 'applyDiscount') {
+                  <div class="action-params">
+                    <mat-form-field appearance="outline">
+                      <mat-label>折扣类型</mat-label>
+                      <mat-select [(ngModel)]="action.params.type">
+                        <mat-option value="percentage">百分比</mat-option>
+                        <mat-option value="fixed">固定金额</mat-option>
+                      </mat-select>
+                    </mat-form-field>
+
+                    <mat-form-field appearance="outline">
+                      <mat-label>折扣值</mat-label>
+                      <input matInput type="number" [(ngModel)]="action.params.value" placeholder="例如:5">
+                    </mat-form-field>
+
+                    <mat-form-field appearance="outline">
+                      <mat-label>最大金额</mat-label>
+                      <input matInput type="number" [(ngModel)]="action.params.maxAmount" placeholder="例如:1000">
+                    </mat-form-field>
+                  </div>
+                }
+              </div>
+            </div>
+          }
+          
+          <button mat-button color="primary" (click)="addAction(newRule)">
+            <mat-icon>add</mat-icon>
+            添加动作
+          </button>
+        </div>
+      </mat-card-content>
+
+      <mat-card-actions align="end">
+        <button mat-button (click)="cancelAddNew()">取消</button>
+        <button mat-raised-button color="primary" (click)="saveRule(newRule)">保存规则</button>
+      </mat-card-actions>
+    </mat-card>
+  }
+
+  <!-- 现有规则列表 -->
+  <div class="rules-list">
+    @for (rule of rules(); track rule.id) {
+      <mat-expansion-panel 
+        [expanded]="expandedPanels().has(rule.id)"
+        (opened)="togglePanel(rule.id)"
+        (closed)="togglePanel(rule.id)">
+        
+        <mat-expansion-panel-header>
+          <mat-panel-title>
+            <div class="rule-header">
+              <mat-slide-toggle 
+                [checked]="rule.enabled" 
+                (change)="toggleRule(rule)" 
+                (click)="$event.stopPropagation()"
+                color="primary">
+              </mat-slide-toggle>
+              <span class="rule-name" [class.disabled]="!rule.enabled">{{ rule.name }}</span>
+              <span class="rule-priority">优先级: {{ rule.priority }}</span>
+            </div>
+          </mat-panel-title>
+          
+          <mat-panel-description>
+            {{ rule.description }}
+          </mat-panel-description>
+        </mat-expansion-panel-header>
+
+        <div class="rule-details">
+          <!-- 条件显示 -->
+          <div class="conditions-display">
+            <h4>条件:</h4>
+            @if (rule.conditions.length > 0) {
+              <div class="conditions-list">
+                @for (condition of rule.conditions; track $index) {
+                  <span class="condition-tag">{{ formatCondition(condition) }}</span>
+                }
+              </div>
+            } @else {
+              <p class="no-conditions">暂无条件配置</p>
+            }
+          </div>
+
+          <!-- 动作显示 -->
+          <div class="actions-display">
+            <h4>动作:</h4>
+            @if (rule.actions.length > 0) {
+              <div class="actions-list">
+                @for (action of rule.actions; track $index) {
+                  <span class="action-tag">{{ formatAction(action) }}</span>
+                }
+              </div>
+            } @else {
+              <p class="no-actions">暂无动作配置</p>
+            }
+          </div>
+
+          <!-- 操作按钮 -->
+          <div class="rule-actions">
+            <button mat-button color="warn" (click)="deleteRule(rule.id)">
+              <mat-icon>delete</mat-icon>
+              删除规则
+            </button>
+            
+            <button mat-button color="primary" (click)="togglePanel(rule.id)">
+              <mat-icon>edit</mat-icon>
+              编辑规则
+            </button>
+          </div>
+        </div>
+      </mat-expansion-panel>
+    }
+  </div>
+
+  <!-- 空状态 -->
+  @if (rules().length === 0 && !isAddingNew()) {
+    <div class="empty-state">
+      <mat-icon class="empty-icon">settings_suggest</mat-icon>
+      <h3>暂无自动化规则</h3>
+      <p>点击"添加新规则"按钮开始配置自动化结算规则</p>
+    </div>
+  }
+</div>

+ 392 - 0
src/app/shared/components/auto-settlement-config/auto-settlement-config.scss

@@ -0,0 +1,392 @@
+@use '../../styles/_ios-theme.scss' as ios;
+
+.auto-settlement-config {
+  padding: 20px;
+  max-width: 1200px;
+  margin: 0 auto;
+
+  // 头部样式
+  .config-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 24px;
+    padding-bottom: 16px;
+    border-bottom: 1px solid ios.$ios-border;
+
+    h2 {
+      margin: 0;
+      color: ios.$ios-text-primary;
+    font-weight: ios.$ios-font-weight-semibold;
+      font-size: 24px;
+    }
+  }
+
+  // 新规则卡片
+  .new-rule-card {
+    margin-bottom: 24px;
+    border: 2px solid ios.$ios-primary;
+    background: linear-gradient(135deg, #f8f9ff 0%, #e8f4ff 100%);
+
+    .mat-card-header {
+      background: linear-gradient(135deg, ios.$ios-primary 0%, lighten(ios.$ios-primary, 10%) 100%);
+      color: white;
+      border-radius: 8px 8px 0 0;
+      padding: 16px;
+
+      .mat-card-title {
+        font-size: 18px;
+        font-weight: ios.$ios-font-weight-semibold;
+      }
+
+      .mat-card-subtitle {
+        color: rgba(255, 255, 255, 0.8);
+      }
+    }
+
+    .mat-card-content {
+      padding: 24px;
+    }
+
+    .form-section {
+      display: grid;
+      grid-template-columns: 1fr 120px;
+      gap: 16px;
+      margin-bottom: 24px;
+
+      .full-width {
+        grid-column: 1 / -1;
+      }
+    }
+
+    .conditions-section,
+    .actions-section {
+      margin-bottom: 24px;
+      padding: 16px;
+      background: #f8f9fa;
+      border-radius: 8px;
+      border: 1px solid ios.$ios-border;
+
+      h3 {
+        margin: 0 0 16px 0;
+        color: ios.$ios-text-primary;
+        font-size: 16px;
+        font-weight: ios.$ios-font-weight-semibold;
+      }
+    }
+
+    .condition-item,
+    .action-item {
+      margin-bottom: 16px;
+      padding: 16px;
+      background: white;
+      border-radius: 8px;
+      border: 1px solid ios.$ios-border;
+
+      .condition-header,
+      .action-header {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 12px;
+        padding-bottom: 8px;
+        border-bottom: 1px solid ios.$ios-border;
+
+        span {
+          font-weight: ios.$ios-font-weight-semibold;
+          color: ios.$ios-text-primary;
+        }
+      }
+
+      .condition-fields,
+      .action-fields {
+        display: grid;
+        grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+        gap: 12px;
+
+        .mat-form-field {
+          margin-bottom: 0;
+        }
+      }
+
+      .action-params {
+        grid-column: 1 / -1;
+        display: grid;
+        grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+        gap: 12px;
+        margin-top: 12px;
+        padding-top: 12px;
+        border-top: 1px solid ios.$ios-border;
+      }
+    }
+
+    .mat-card-actions {
+      padding: 16px 24px;
+      border-top: 1px solid ios.$ios-border;
+    }
+  }
+
+  // 规则列表
+  .rules-list {
+    .mat-expansion-panel {
+      margin-bottom: 12px;
+      border: 1px solid ios.$ios-border;
+      border-radius: 8px;
+      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+
+      &:hover {
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+      }
+
+      .mat-expansion-panel-header {
+        padding: 16px 24px;
+        height: auto;
+        min-height: 64px;
+
+        .rule-header {
+          display: flex;
+          align-items: center;
+          gap: 12px;
+          flex: 1;
+
+          .rule-name {
+            font-weight: ios.$ios-font-weight-semibold;
+            font-size: 16px;
+            color: ios.$ios-text-primary;
+
+            &.disabled {
+              color: ios.$ios-text-secondary;
+              text-decoration: line-through;
+            }
+          }
+
+          .rule-priority {
+            font-size: 12px;
+            color: ios.$ios-text-secondary;
+            background: #f0f0f0;
+            padding: 2px 8px;
+            border-radius: 12px;
+          }
+        }
+
+        .mat-panel-description {
+          color: ios.$ios-text-secondary;
+          font-size: 14px;
+          margin-right: 16px;
+        }
+      }
+
+      .rule-details {
+        padding: 16px 24px;
+
+        .conditions-display,
+        .actions-display {
+          margin-bottom: 20px;
+
+          h4 {
+            margin: 0 0 12px 0;
+            color: ios.$ios-text-primary;
+            font-size: 14px;
+            font-weight: ios.$ios-font-weight-semibold;
+          }
+
+          .conditions-list,
+          .actions-list {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+          }
+
+          .condition-tag,
+          .action-tag {
+            background: lighten(ios.$ios-primary, 20%);
+            color: ios.$ios-primary;
+            padding: 6px 12px;
+            border-radius: 16px;
+            font-size: 12px;
+            font-weight: ios.$ios-font-weight-medium;
+            border: 1px solid rgba(ios.$ios-primary, 0.2);
+          }
+
+          .no-conditions,
+          .no-actions {
+            color: ios.$ios-text-secondary;
+            font-style: italic;
+            margin: 0;
+          }
+        }
+
+        .rule-actions {
+          display: flex;
+          gap: 12px;
+          justify-content: flex-end;
+          padding-top: 16px;
+          border-top: 1px solid ios.$ios-border;
+
+          button {
+            min-width: auto;
+          }
+        }
+      }
+    }
+  }
+
+  // 空状态
+  .empty-state {
+    text-align: center;
+    padding: 60px 20px;
+    color: ios.$ios-text-secondary;
+
+    .empty-icon {
+      font-size: 64px;
+      width: 64px;
+      height: 64px;
+      margin-bottom: 16px;
+      opacity: 0.5;
+    }
+
+    h3 {
+      margin: 0 0 8px 0;
+      color: ios.$ios-text-primary;
+      font-size: 18px;
+      font-weight: ios.$ios-font-weight-semibold;
+    }
+
+    p {
+      margin: 0;
+      font-size: 14px;
+      line-height: 1.4;
+    }
+  }
+
+  // 响应式设计
+  @media (max-width: 768px) {
+    padding: 16px;
+
+    .config-header {
+      flex-direction: column;
+      gap: 16px;
+      text-align: center;
+
+      h2 {
+        font-size: 20px;
+      }
+    }
+
+    .new-rule-card {
+      .form-section {
+        grid-template-columns: 1fr;
+      }
+
+      .condition-fields,
+      .action-fields {
+        grid-template-columns: 1fr;
+      }
+    }
+
+    .rules-list {
+      .mat-expansion-panel-header {
+        flex-direction: column;
+        align-items: flex-start;
+        gap: 8px;
+
+        .rule-header {
+          flex-direction: column;
+          align-items: flex-start;
+          gap: 8px;
+        }
+
+        .mat-panel-description {
+          margin-right: 0;
+          text-align: left;
+        }
+      }
+
+      .rule-details {
+        .rule-actions {
+          flex-direction: column;
+          align-items: stretch;
+
+          button {
+            width: 100%;
+          }
+        }
+      }
+    }
+  }
+
+  // 动画效果
+  .condition-item,
+  .action-item,
+  .mat-expansion-panel {
+    transition: all 0.3s ease;
+
+    &:hover {
+      transform: translateY(-1px);
+    }
+  }
+
+  // 禁用状态样式
+  .disabled {
+    opacity: 0.6;
+  }
+
+  // 表单字段样式
+  .mat-form-field {
+    margin-bottom: 16px;
+
+    &.mat-form-field-appearance-outline {
+      .mat-form-field-outline {
+        color: ios.$ios-border;
+      }
+
+      .mat-form-field-outline-thick {
+        color: ios.$ios-primary;
+      }
+
+      &.mat-focused {
+        .mat-form-field-outline-thick {
+          color: ios.$ios-primary;
+        }
+      }
+    }
+  }
+
+  // 按钮样式
+  .mat-button,
+  .mat-raised-button {
+    border-radius: 8px;
+    font-weight: ios.$ios-font-weight-medium;
+
+    &.mat-primary {
+      background: ios.$ios-primary;
+      color: white;
+
+      &:hover {
+        background: darken(ios.$ios-primary, 10%);
+      }
+    }
+
+    &.mat-warn {
+      background: #dc3545;
+      color: white;
+
+      &:hover {
+        background: darken(#dc3545, 10%);
+      }
+    }
+  }
+
+  // 滑动开关样式
+  .mat-slide-toggle {
+    &.mat-checked {
+      .mat-slide-toggle-thumb {
+        background-color: ios.$ios-primary;
+      }
+
+      .mat-slide-toggle-bar {
+        background-color: rgba(ios.$ios-primary, 0.5);
+      }
+    }
+  }
+}

+ 260 - 0
src/app/shared/components/auto-settlement-config/auto-settlement-config.ts

@@ -0,0 +1,260 @@
+import { Component, OnInit, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { MatCardModule } from '@angular/material/card';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { MatExpansionModule } from '@angular/material/expansion';
+import { MatSelectModule } from '@angular/material/select';
+import { MatInputModule } from '@angular/material/input';
+import { MatCheckboxModule } from '@angular/material/checkbox';
+import { MatSlideToggleModule } from '@angular/material/slide-toggle';
+import { MatDialogModule, MatDialog } from '@angular/material/dialog';
+import { AutoSettlementService, AutoSettlementRule, SettlementCondition, SettlementAction } from '../../../services/auto-settlement.service';
+
+@Component({
+  selector: 'app-auto-settlement-config',
+  standalone: true,
+  imports: [
+    CommonModule,
+    FormsModule,
+    MatCardModule,
+    MatButtonModule,
+    MatIconModule,
+    MatExpansionModule,
+    MatSelectModule,
+    MatInputModule,
+    MatCheckboxModule,
+    MatSlideToggleModule,
+    MatDialogModule
+  ],
+  templateUrl: './auto-settlement-config.html',
+  styleUrls: ['./auto-settlement-config.scss']
+})
+export class AutoSettlementConfigComponent implements OnInit {
+  rules = signal<AutoSettlementRule[]>([]);
+  expandedPanels = signal<Set<string>>(new Set());
+  isAddingNew = signal(false);
+  
+  // 条件类型选项
+  conditionTypes = [
+    { value: 'amountRange', label: '金额范围' },
+    { value: 'overdueDays', label: '逾期天数' },
+    { value: 'customerTier', label: '客户层级' },
+    { value: 'projectType', label: '项目类型' },
+    { value: 'paymentMethod', label: '支付方式' }
+  ];
+
+  // 操作符选项
+  operators = [
+    { value: 'equals', label: '等于' },
+    { value: 'greaterThan', label: '大于' },
+    { value: 'lessThan', label: '小于' },
+    { value: 'between', label: '介于' },
+    { value: 'contains', label: '包含' }
+  ];
+
+  // 动作类型选项
+  actionTypes = [
+    { value: 'sendReminder', label: '发送提醒' },
+    { value: 'applyDiscount', label: '应用折扣' },
+    { value: 'extendDueDate', label: '延长期限' },
+    { value: 'autoConfirm', label: '自动确认' },
+    { value: 'notifyManager', label: '通知经理' }
+  ];
+
+  // 新规则模板
+  newRule: AutoSettlementRule = {
+    id: '',
+    name: '',
+    enabled: true,
+    priority: 5,
+    conditions: [],
+    actions: [],
+    description: ''
+  };
+
+  constructor(private autoSettlementService: AutoSettlementService) {}
+
+  ngOnInit() {
+    this.loadRules();
+  }
+
+  // 加载规则
+  loadRules() {
+    this.autoSettlementService.getRules().subscribe(rules => {
+      this.rules.set(rules);
+    });
+  }
+
+  // 切换面板展开状态
+  togglePanel(ruleId: string) {
+    const panels = new Set(this.expandedPanels());
+    if (panels.has(ruleId)) {
+      panels.delete(ruleId);
+    } else {
+      panels.add(ruleId);
+    }
+    this.expandedPanels.set(panels);
+  }
+
+  // 添加新条件
+  addCondition(rule: AutoSettlementRule) {
+    const newCondition: SettlementCondition = {
+      type: 'amountRange',
+      operator: 'greaterThan',
+      value: 0
+    };
+    rule.conditions.push(newCondition);
+  }
+
+  // 删除条件
+  removeCondition(rule: AutoSettlementRule, index: number) {
+    rule.conditions.splice(index, 1);
+  }
+
+  // 添加新动作
+  addAction(rule: AutoSettlementRule) {
+    const newAction: SettlementAction = {
+      type: 'sendReminder',
+      params: {}
+    };
+    rule.actions.push(newAction);
+  }
+
+  // 删除动作
+  removeAction(rule: AutoSettlementRule, index: number) {
+    rule.actions.splice(index, 1);
+  }
+
+  // 保存规则
+  saveRule(rule: AutoSettlementRule) {
+    if (rule.id) {
+      this.autoSettlementService.updateRule(rule.id, rule);
+    } else {
+      rule.id = `rule-${Date.now()}`;
+      this.autoSettlementService.addRule(rule);
+    }
+    this.cancelAddNew();
+  }
+
+  // 删除规则
+  deleteRule(ruleId: string) {
+    if (confirm('确定要删除这条规则吗?')) {
+      this.autoSettlementService.deleteRule(ruleId);
+      this.loadRules();
+    }
+  }
+
+  // 启用/禁用规则
+  toggleRule(rule: AutoSettlementRule) {
+    rule.enabled = !rule.enabled;
+    this.autoSettlementService.updateRule(rule.id, { enabled: rule.enabled });
+  }
+
+  // 开始添加新规则
+  startAddNew() {
+    this.isAddingNew.set(true);
+    this.newRule = {
+      id: '',
+      name: '',
+      enabled: true,
+      priority: 5,
+      conditions: [],
+      actions: [],
+      description: ''
+    };
+  }
+
+  // 取消添加新规则
+  cancelAddNew() {
+    this.isAddingNew.set(false);
+  }
+
+  // 获取条件操作符选项
+  getOperatorOptions(conditionType: string) {
+    switch (conditionType) {
+      case 'amountRange':
+      case 'overdueDays':
+        return this.operators.filter(op => 
+          ['equals', 'greaterThan', 'lessThan', 'between'].includes(op.value)
+        );
+      case 'customerTier':
+      case 'projectType':
+      case 'paymentMethod':
+        return this.operators.filter(op => 
+          ['equals', 'contains'].includes(op.value)
+        );
+      default:
+        return this.operators;
+    }
+  }
+
+  // 获取动作参数配置
+  getActionParamsConfig(actionType: string) {
+    switch (actionType) {
+      case 'sendReminder':
+        return {
+          channels: ['wechat', 'sms', 'email', 'system'],
+          frequency: ['immediate', 'daily', 'weekly']
+        };
+      case 'applyDiscount':
+        return {
+          type: ['percentage', 'fixed'],
+          maxAmount: 1000
+        };
+      case 'extendDueDate':
+        return {
+          days: 7
+        };
+      case 'autoConfirm':
+        return {
+          immediate: true
+        };
+      case 'notifyManager':
+        return {
+          threshold: 10000
+        };
+      default:
+        return {};
+    }
+  }
+
+  // 格式化条件显示
+  formatCondition(condition: SettlementCondition): string {
+    switch (condition.type) {
+      case 'amountRange':
+        return `金额 ${this.getOperatorLabel(condition.operator)} ${condition.value}`;
+      case 'overdueDays':
+        return `逾期天数 ${this.getOperatorLabel(condition.operator)} ${condition.value}`;
+      case 'customerTier':
+        return `客户层级 ${this.getOperatorLabel(condition.operator)} ${condition.value}`;
+      default:
+        return `${condition.type} ${condition.operator} ${condition.value}`;
+    }
+  }
+
+  // 格式化动作显示
+  formatAction(action: SettlementAction): string {
+    switch (action.type) {
+      case 'sendReminder':
+        return '发送提醒';
+      case 'applyDiscount':
+        return '应用折扣';
+      case 'extendDueDate':
+        return '延长期限';
+      case 'autoConfirm':
+        return '自动确认';
+      case 'notifyManager':
+        return '通知经理';
+      default:
+        return action.type;
+    }
+  }
+
+  // 获取操作符标签
+  private getOperatorLabel(operator: string): string {
+    const op = this.operators.find(o => o.value === operator);
+    return op ? op.label : operator;
+  }
+}

+ 236 - 23
src/app/shared/components/complaint-card/complaint-card.html

@@ -1,35 +1,130 @@
 <div class="complaint-card">
-  <!-- 统计数据概览 -->
-  <div class="stats-overview">
-    <h4>投诉处理概览</h4>
-    <div class="stats-grid">
-      <div class="stat-item total">
-        <div class="stat-value">{{ stats().totalCount }}</div>
-        <div class="stat-label">总投诉数</div>
+  <!-- 企业微信监控控制区域 -->
+  <div class="wechat-monitoring-section">
+    <div class="monitoring-header">
+      <h4>企业微信投诉监控</h4>
+      <div class="monitoring-status" [class]="'status-' + realTimeTaskStatus()">
+        {{ realTimeTaskStatus() === 'idle' ? '未启动' : 
+           realTimeTaskStatus() === 'processing' ? '监控中' : '已启动' }}
       </div>
-      <div class="stat-item pending">
-        <div class="stat-value">{{ stats().pendingCount }}</div>
-        <div class="stat-label">待处理</div>
-      </div>
-      <div class="stat-item processing">
-        <div class="stat-value">{{ stats().processingCount }}</div>
-        <div class="stat-label">处理中</div>
+    </div>
+    
+    <div class="monitoring-controls">
+      <button class="btn btn-primary" 
+              (click)="startWeChatMonitoring()"
+              [disabled]="realTimeTaskStatus() === 'processing'">
+        <i class="fas fa-play"></i>
+        启动监控
+      </button>
+      
+      <button class="btn btn-secondary" 
+              (click)="setupKeywordMonitoring()">
+        <i class="fas fa-cog"></i>
+        关键词设置
+      </button>
+      
+      <div class="monitoring-stats">
+        <span class="stat-item">
+          <i class="fas fa-eye"></i>
+          监控群组: {{ monitoredGroups || 5 }}
+        </span>
+        <span class="stat-item">
+          <i class="fas fa-bell"></i>
+          今日检测: {{ todayDetections || 3 }}
+        </span>
       </div>
-      <div class="stat-item resolved">
-        <div class="stat-value">{{ stats().resolvedCount }}</div>
-        <div class="stat-label">已解决</div>
+    </div>
+  </div>
+
+  <!-- 实时代办项处理区域 -->
+  <div class="realtime-tasks-section" *ngIf="realTimeTaskStatus() !== 'idle'">
+    <div class="section-header">
+      <h4>实时代办项</h4>
+      <span class="task-count">{{ urgentComplaints().length }} 项待处理</span>
+    </div>
+    
+    <div class="realtime-task-list">
+      <div class="task-item" 
+           *ngFor="let complaint of urgentComplaints()" 
+           [class.processing]="processingTaskId() === complaint.id">
+        <div class="task-info">
+          <div class="task-title">{{ complaint.description }}</div>
+          <div class="task-meta">
+            <span class="customer">{{ complaint.customerName }}</span>
+            <span class="time">{{ complaint.submitTime | date:'HH:mm' }}</span>
+            <span class="urgency" [class]="'urgency-' + complaint.urgencyLevel">
+              {{ complaint.urgencyLevel === 'critical' ? '紧急' : '重要' }}
+            </span>
+          </div>
+        </div>
+        
+        <div class="task-actions">
+          <button class="btn btn-sm btn-primary" 
+                  (click)="processRealTimeTask(complaint.id)"
+                  [disabled]="processingTaskId() === complaint.id">
+            <i class="fas fa-play" *ngIf="processingTaskId() !== complaint.id"></i>
+            <i class="fas fa-spinner fa-spin" *ngIf="processingTaskId() === complaint.id"></i>
+            {{ processingTaskId() === complaint.id ? '处理中' : '立即处理' }}
+          </button>
+          
+          <button class="btn btn-sm btn-warning" 
+                  (click)="escalateComplaint(complaint)">
+            <i class="fas fa-arrow-up"></i>
+            升级
+          </button>
+        </div>
       </div>
-      <div class="stat-item high-priority">
-        <div class="stat-value">{{ stats().highPriorityCount }}</div>
-        <div class="stat-label">高优先级</div>
+    </div>
+  </div>
+
+  <!-- 企业微信监控控制面板 -->
+  <div class="wechat-monitoring-panel">
+    <h4>企业微信投诉监控</h4>
+    <div class="monitoring-controls">
+      <button class="control-btn start-monitoring" (click)="startWeChatMonitoring()">
+        <span class="btn-icon">📱</span>
+        开始微信监控
+      </button>
+      <button class="control-btn setup-keywords" (click)="setupKeywordMonitoring()">
+        <span class="btn-icon">🔍</span>
+        设置关键词
+      </button>
+      <div class="monitoring-status">
+        <span class="status-label">监控状态:</span>
+        <span class="status-indicator active">运行中</span>
       </div>
-      <div class="stat-item resolution-time">
-        <div class="stat-value">{{ stats().averageResolutionTime }}<span class="time-suffix">天</span></div>
-        <div class="stat-label">平均解决时间</div>
+    </div>
+    
+    <div class="keyword-display">
+      <span class="keyword-label">监控关键词:</span>
+      <div class="keyword-tags">
+        @for (keyword of monitorKeywords; track keyword) {
+          <span class="keyword-tag">{{ keyword }}</span>
+        }
       </div>
     </div>
   </div>
 
+  <!-- 实时代办项状态 -->
+  @if (realTimeTaskStatus() !== 'idle') {
+    <div class="real-time-task-status">
+      <div class="task-status-header">
+        <h5>实时代办项处理</h5>
+        <span class="task-status-badge" [class]="realTimeTaskStatus()">
+          {{ realTimeTaskStatus() === 'processing' ? '处理中...' : '已完成' }}
+        </span>
+      </div>
+      @if (realTimeTaskStatus() === 'processing') {
+        <div class="task-progress">
+          <div class="progress-bar">
+            <div class="progress-fill"></div>
+          </div>
+          <span class="progress-text">正在创建代办项:{{ processingTaskId() }}</span>
+        </div>
+      }
+    </div>
+  }
+
   <!-- 优先级统计 -->
   <div class="priority-stats">
     <h5>优先级分布</h5>
@@ -133,6 +228,33 @@
           }
         </select>
       </div>
+
+      <!-- 新增筛选项 -->
+      <div class="filter-group">
+        <label>来源筛选:</label>
+        <select 
+          class="filter-select"
+          [value]="sourceFilter()"
+          (change)="updateSourceFilter($event)">
+          <option value="all">全部来源</option>
+          @for (source of complaintSources; track source.value) {
+            <option [value]="source.value">{{ source.icon }} {{ source.label }}</option>
+          }
+        </select>
+      </div>
+
+      <div class="filter-group">
+        <label>紧急程度:</label>
+        <select 
+          class="filter-select"
+          [value]="urgencyFilter()"
+          (change)="updateUrgencyFilter($event)">
+          <option value="all">全部级别</option>
+          @for (urgency of urgencyLevels; track urgency.value) {
+            <option [value]="urgency.value">{{ urgency.label }}</option>
+          }
+        </select>
+      </div>
     </div>
   </div>
 
@@ -149,6 +271,24 @@
                 <div class="priority-badge" [class]="getPriorityClass(complaint.priority || 'low')" [style.background-color]="getPriorityInfo(complaint.priority || 'low').color">
                   {{ getPriorityInfo(complaint.priority || 'low').label }}优先级
                 </div>
+                <!-- 新增来源标识 -->
+                <div class="source-badge" [class]="complaint.source">
+                  {{ getComplaintSourceInfo(complaint).icon }} {{ getComplaintSourceInfo(complaint).label }}
+                </div>
+                <!-- 紧急程度标识 -->
+                @if (complaint.urgencyLevel && complaint.urgencyLevel !== 'normal') {
+                  <div class="urgency-badge" [style.background-color]="getUrgencyInfo(complaint).color">
+                    {{ getUrgencyInfo(complaint).label }}
+                  </div>
+                }
+                <!-- 自动标注标识 -->
+                @if (isAutoTagged(complaint)) {
+                  <span class="auto-tag-badge">🤖 自动</span>
+                }
+                <!-- 升级标识 -->
+                @if (complaint.escalationLevel && complaint.escalationLevel > 0) {
+                  <span class="escalation-badge">⬆️ {{ getEscalationDisplay(complaint.escalationLevel) }}</span>
+                }
               </div>
               <div class="header-right">
                 <span class="status-badge" [class]="getStatusClass(complaint)">
@@ -157,6 +297,9 @@
                 @if (isOverdue(complaint)) {
                   <span class="overdue-badge">超时</span>
                 }
+                @if (complaint.followUpRequired) {
+                  <span class="follow-up-badge">需跟进</span>
+                }
               </div>
             </div>
             
@@ -168,6 +311,42 @@
                   <span class="customer-name">{{ complaint.customerName }}</span>
                 </div>
               }
+
+              <!-- 微信相关信息 -->
+              @if (complaint.source === 'wechat_auto' && complaint.wechatGroupName) {
+                <div class="wechat-info">
+                  <span class="wechat-label">📱 微信群:</span>
+                  <span class="wechat-group">{{ complaint.wechatGroupName }}</span>
+                  @if (complaint.keywordMatched && complaint.keywordMatched.length > 0) {
+                    <div class="matched-keywords">
+                      <span class="keyword-label">匹配关键词:</span>
+                      @for (keyword of complaint.keywordMatched; track keyword) {
+                        <span class="matched-keyword">{{ keyword }}</span>
+                      }
+                    </div>
+                  }
+                </div>
+              }
+
+              <!-- 分配信息 -->
+              @if (complaint.assignedTo) {
+                <div class="assignment-info">
+                  <span class="assignment-label">👤 处理人:</span>
+                  <span class="assignee-name">{{ complaint.assignedTo }}</span>
+                </div>
+              }
+
+              <!-- 标签显示 -->
+              @if (complaint.tags && complaint.tags.length > 0) {
+                <div class="tags-section">
+                  <span class="tags-label">🏷️ 标签:</span>
+                  <div class="tags-list">
+                    @for (tag of complaint.tags; track tag) {
+                      <span class="complaint-tag">{{ tag }}</span>
+                    }
+                  </div>
+                </div>
+              }
               
               <div class="complaint-description">
                 <h4>投诉内容</h4>
@@ -230,6 +409,10 @@
                   <span class="btn-icon">🔧</span>
                   开始处理
                 </button>
+                <button class="action-btn task-btn" (click)="createRealTimeTask(complaint)">
+                  <span class="btn-icon">📋</span>
+                  创建代办项
+                </button>
               } @else if (complaint.status === '处理中') {
                 <button class="action-btn complete-btn" (click)="completeProcessing(complaint)">
                   <span class="btn-icon">✅</span>
@@ -247,6 +430,36 @@
                   <span class="btn-icon">👁️</span>
                   查看详情
                 </button>
+                
+                <!-- 新增操作按钮 -->
+                @if (complaint.escalationLevel !== undefined && complaint.escalationLevel < 3) {
+                  <button class="action-btn escalate-btn" (click)="escalateComplaint(complaint)">
+                    <span class="btn-icon">⬆️</span>
+                    升级处理
+                  </button>
+                }
+                
+                <button class="action-btn assign-btn" (click)="assignComplaint(complaint, '处理员A')">
+                  <span class="btn-icon">👤</span>
+                  分配处理
+                </button>
+                
+                <button class="action-btn tag-btn" (click)="addComplaintTag(complaint, '重要')">
+                  <span class="btn-icon">🏷️</span>
+                  添加标签
+                </button>
+                
+                @if (!complaint.followUpRequired) {
+                  <button class="action-btn follow-up-btn" (click)="setFollowUpRequired(complaint, true)">
+                    <span class="btn-icon">📞</span>
+                    设置跟进
+                  </button>
+                } @else {
+                  <button class="action-btn follow-up-btn active" (click)="setFollowUpRequired(complaint, false)">
+                    <span class="btn-icon">✓</span>
+                    取消跟进
+                  </button>
+                }
               }
             </div>
           </div>

+ 649 - 0
src/app/shared/components/complaint-card/complaint-card.scss

@@ -2,6 +2,243 @@
 
 :host { display: block; height: 100%; }
 
+// 企业微信监控样式
+.wechat-monitoring-section {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 20px;
+  color: white;
+
+  .monitoring-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 15px;
+
+    h4 {
+      margin: 0;
+      font-size: 18px;
+      font-weight: 600;
+    }
+
+    .monitoring-status {
+      padding: 4px 12px;
+      border-radius: 20px;
+      font-size: 12px;
+      font-weight: 500;
+
+      &.status-idle {
+        background: rgba(255, 255, 255, 0.2);
+      }
+
+      &.status-processing {
+        background: #ffc107;
+        color: #000;
+        animation: pulse 2s infinite;
+      }
+
+      &.status-completed {
+        background: #28a745;
+      }
+    }
+  }
+
+  .monitoring-controls {
+    display: flex;
+    align-items: center;
+    gap: 15px;
+    flex-wrap: wrap;
+
+    .btn {
+      padding: 8px 16px;
+      border: none;
+      border-radius: 6px;
+      font-size: 14px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+
+      &.btn-primary {
+        background: #007bff;
+        color: white;
+
+        &:hover:not(:disabled) {
+          background: #0056b3;
+        }
+
+        &:disabled {
+          background: rgba(255, 255, 255, 0.3);
+          cursor: not-allowed;
+        }
+      }
+
+      &.btn-secondary {
+        background: rgba(255, 255, 255, 0.2);
+        color: white;
+
+        &:hover {
+          background: rgba(255, 255, 255, 0.3);
+        }
+      }
+
+      i {
+        margin-right: 6px;
+      }
+    }
+
+    .monitoring-stats {
+      display: flex;
+      gap: 20px;
+      margin-left: auto;
+
+      .stat-item {
+        display: flex;
+        align-items: center;
+        font-size: 14px;
+
+        i {
+          margin-right: 6px;
+          opacity: 0.8;
+        }
+      }
+    }
+  }
+}
+
+// 实时代办项样式
+.realtime-tasks-section {
+  background: #fff;
+  border: 1px solid #e9ecef;
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 20px;
+
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 15px;
+
+    h4 {
+      margin: 0;
+      color: #333;
+      font-size: 18px;
+      font-weight: 600;
+    }
+
+    .task-count {
+      background: #dc3545;
+      color: white;
+      padding: 4px 12px;
+      border-radius: 20px;
+      font-size: 12px;
+      font-weight: 500;
+    }
+  }
+
+  .realtime-task-list {
+    .task-item {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 15px;
+      border: 1px solid #e9ecef;
+      border-radius: 8px;
+      margin-bottom: 10px;
+      transition: all 0.3s ease;
+
+      &:hover {
+        border-color: #007bff;
+        box-shadow: 0 2px 8px rgba(0, 123, 255, 0.1);
+      }
+
+      &.processing {
+        border-color: #ffc107;
+        background: #fff9c4;
+      }
+
+      .task-info {
+        flex: 1;
+
+        .task-title {
+          font-weight: 500;
+          color: #333;
+          margin-bottom: 8px;
+          font-size: 14px;
+        }
+
+        .task-meta {
+          display: flex;
+          gap: 15px;
+          font-size: 12px;
+          color: #666;
+
+          .customer {
+            font-weight: 500;
+          }
+
+          .urgency {
+            padding: 2px 8px;
+            border-radius: 12px;
+            font-weight: 500;
+
+            &.urgency-urgent {
+              background: #ffc107;
+              color: #000;
+            }
+
+            &.urgency-critical {
+              background: #dc3545;
+              color: white;
+            }
+          }
+        }
+      }
+
+      .task-actions {
+        display: flex;
+        gap: 8px;
+
+        .btn {
+          padding: 6px 12px;
+          border: none;
+          border-radius: 4px;
+          font-size: 12px;
+          cursor: pointer;
+          transition: all 0.3s ease;
+
+          &.btn-primary {
+            background: #007bff;
+            color: white;
+
+            &:hover:not(:disabled) {
+              background: #0056b3;
+            }
+
+            &:disabled {
+              background: #6c757d;
+              cursor: not-allowed;
+            }
+          }
+
+          &.btn-warning {
+            background: #ffc107;
+            color: #000;
+
+            &:hover {
+              background: #e0a800;
+            }
+          }
+
+          i {
+            margin-right: 4px;
+          }
+        }
+      }
+    }
+  }
+}
+
 .complaint-card {
   padding: ios.$ios-spacing-md;
   background: ios.$ios-background;
@@ -54,10 +291,204 @@
         &.resolved .stat-value { color: ios.$ios-success; }
         &.high-priority .stat-value { color: ios.$ios-danger; }
         &.resolution-time .stat-value { color: #722ed1; }
+        
+        // 新增统计项样式
+        &.wechat-count .stat-value { color: #52c41a; }
+        &.auto-tagged .stat-value { color: #13c2c2; }
+        &.overdue .stat-value { color: #ff4d4f; }
+        &.follow-up .stat-value { color: #fa8c16; }
       }
     }
   }
 
+  // 企业微信监控面板
+  .wechat-monitoring-panel {
+    margin-bottom: ios.$ios-spacing-lg;
+    padding: ios.$ios-spacing-md;
+    background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%);
+    border-radius: ios.$ios-radius-md;
+    border: 1px solid #91d5ff;
+
+    h4 {
+      margin: 0 0 ios.$ios-spacing-md 0;
+      font-size: ios.$ios-font-size-lg;
+      font-weight: ios.$ios-font-weight-semibold;
+      color: #1890ff;
+      display: flex;
+      align-items: center;
+      gap: ios.$ios-spacing-sm;
+
+      &::before {
+        content: '📱';
+        font-size: ios.$ios-font-size-lg;
+      }
+    }
+
+    .monitoring-controls {
+      display: flex;
+      align-items: center;
+      gap: ios.$ios-spacing-md;
+      margin-bottom: ios.$ios-spacing-md;
+      flex-wrap: wrap;
+
+      .control-btn {
+        display: flex;
+        align-items: center;
+        gap: ios.$ios-spacing-xs;
+        padding: ios.$ios-spacing-sm ios.$ios-spacing-md;
+        background: #1890ff;
+        color: white;
+        border: none;
+        border-radius: ios.$ios-radius-sm;
+        font-size: ios.$ios-font-size-sm;
+        cursor: pointer;
+        transition: all 0.3s ease;
+
+        &:hover {
+          background: #40a9ff;
+          transform: translateY(-1px);
+        }
+
+        .btn-icon {
+          font-size: ios.$ios-font-size-md;
+        }
+
+        &.setup-keywords {
+          background: #52c41a;
+
+          &:hover {
+            background: #73d13d;
+          }
+        }
+      }
+
+      .monitoring-status {
+        display: flex;
+        align-items: center;
+        gap: ios.$ios-spacing-xs;
+        margin-left: auto;
+
+        .status-label {
+          font-size: ios.$ios-font-size-sm;
+          color: ios.$ios-text-secondary;
+        }
+
+        .status-indicator {
+          padding: 2px 8px;
+          border-radius: 10px;
+          font-size: ios.$ios-font-size-xs;
+          font-weight: ios.$ios-font-weight-medium;
+
+          &.active {
+            background: #f6ffed;
+            color: #52c41a;
+            border: 1px solid #b7eb8f;
+          }
+        }
+      }
+    }
+
+    .keyword-display {
+      display: flex;
+      align-items: center;
+      gap: ios.$ios-spacing-sm;
+      flex-wrap: wrap;
+
+      .keyword-label {
+        font-size: ios.$ios-font-size-sm;
+        color: ios.$ios-text-secondary;
+        white-space: nowrap;
+      }
+
+      .keyword-tags {
+        display: flex;
+        gap: ios.$ios-spacing-xs;
+        flex-wrap: wrap;
+
+        .keyword-tag {
+          padding: 2px 8px;
+          background: #fff2e8;
+          color: #fa8c16;
+          border: 1px solid #ffd591;
+          border-radius: 10px;
+          font-size: ios.$ios-font-size-xs;
+          font-weight: ios.$ios-font-weight-medium;
+        }
+      }
+    }
+  }
+
+  // 实时代办项状态
+  .real-time-task-status {
+    margin-bottom: ios.$ios-spacing-lg;
+    padding: ios.$ios-spacing-md;
+    background: linear-gradient(135deg, #fff7e6 0%, #fffbe6 100%);
+    border-radius: ios.$ios-radius-md;
+    border: 1px solid #ffd591;
+
+    .task-status-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: ios.$ios-spacing-sm;
+
+      h5 {
+        margin: 0;
+        font-size: ios.$ios-font-size-md;
+        font-weight: ios.$ios-font-weight-semibold;
+        color: #fa8c16;
+      }
+
+      .task-status-badge {
+        padding: 4px 12px;
+        border-radius: ios.$ios-radius-sm;
+        font-size: ios.$ios-font-size-xs;
+        font-weight: ios.$ios-font-weight-medium;
+
+        &.processing {
+          background: #fff2e8;
+          color: #fa8c16;
+          border: 1px solid #ffd591;
+        }
+
+        &.completed {
+          background: #f6ffed;
+          color: #52c41a;
+          border: 1px solid #b7eb8f;
+        }
+      }
+    }
+
+    .task-progress {
+      .progress-bar {
+        width: 100%;
+        height: 6px;
+        background: #fff7e6;
+        border-radius: 3px;
+        overflow: hidden;
+        margin-bottom: ios.$ios-spacing-xs;
+
+        .progress-fill {
+          height: 100%;
+          background: linear-gradient(90deg, #fa8c16, #ffa940);
+          border-radius: 3px;
+          animation: progress-animation 2s ease-in-out infinite;
+        }
+      }
+
+      .progress-text {
+        font-size: ios.$ios-font-size-xs;
+        color: ios.$ios-text-secondary;
+      }
+    }
+  }
+
+  @keyframes progress-animation {
+    0% { width: 0%; }
+    50% { width: 70%; }
+    100% { width: 100%; }
+  }
+
   // 优先级统计 - 小卡片样式
   .priority-stats {
     margin-bottom: ios.$ios-spacing-lg;
@@ -376,6 +807,64 @@
             font-weight: 500;
             white-space: nowrap;
           }
+
+          // 新增标识样式
+          .source-badge {
+            padding: 4px 10px;
+            border-radius: 12px;
+            font-size: 11px;
+            font-weight: 500;
+            white-space: nowrap;
+
+            &.manual {
+              background: #f0f0f0;
+              color: #666;
+              border: 1px solid #d9d9d9;
+            }
+
+            &.wechat_auto {
+              background: #e6f7ff;
+              color: #1890ff;
+              border: 1px solid #91d5ff;
+            }
+
+            &.keyword_monitor {
+              background: #fff2e8;
+              color: #fa8c16;
+              border: 1px solid #ffd591;
+            }
+          }
+
+          .urgency-badge {
+            color: white;
+            padding: 4px 10px;
+            border-radius: 12px;
+            font-size: 11px;
+            font-weight: 500;
+            white-space: nowrap;
+          }
+
+          .auto-tag-badge {
+            background: #f6ffed;
+            color: #52c41a;
+            border: 1px solid #b7eb8f;
+            padding: 2px 8px;
+            border-radius: 10px;
+            font-size: 10px;
+            font-weight: 500;
+            white-space: nowrap;
+          }
+
+          .escalation-badge {
+            background: #fff1f0;
+            color: #ff4d4f;
+            border: 1px solid #ffccc7;
+            padding: 2px 8px;
+            border-radius: 10px;
+            font-size: 10px;
+            font-weight: 500;
+            white-space: nowrap;
+          }
         }
 
         .header-right {
@@ -418,6 +907,17 @@
             font-weight: 600;
             animation: pulse 2s infinite;
           }
+
+          .follow-up-badge {
+            background: #fff2e8;
+            color: #fa8c16;
+            border: 1px solid #ffd591;
+            padding: 2px 8px;
+            border-radius: 10px;
+            font-size: 10px;
+            font-weight: 500;
+            white-space: nowrap;
+          }
         }
       }
 
@@ -449,6 +949,96 @@
           }
         }
 
+        // 新增信息区域样式
+        .wechat-info {
+          padding: 8px 12px;
+          background: #e6f7ff;
+          border-radius: 8px;
+          border-left: 3px solid #1890ff;
+
+          .wechat-label {
+            font-weight: 500;
+            color: #1890ff;
+            margin-right: 8px;
+          }
+
+          .wechat-group {
+            color: #1890ff;
+            font-weight: 600;
+          }
+
+          .matched-keywords {
+            margin-top: 6px;
+            display: flex;
+            align-items: center;
+            gap: 6px;
+            flex-wrap: wrap;
+
+            .keyword-label {
+              font-size: 12px;
+              color: #666;
+            }
+
+            .matched-keyword {
+              background: #fff2e8;
+              color: #fa8c16;
+              border: 1px solid #ffd591;
+              padding: 2px 6px;
+              border-radius: 8px;
+              font-size: 11px;
+              font-weight: 500;
+            }
+          }
+        }
+
+        .assignment-info {
+          padding: 8px 12px;
+          background: #f6ffed;
+          border-radius: 8px;
+          border-left: 3px solid #52c41a;
+
+          .assignment-label {
+            font-weight: 500;
+            color: #52c41a;
+            margin-right: 8px;
+          }
+
+          .assignee-name {
+            color: #52c41a;
+            font-weight: 600;
+          }
+        }
+
+        .tags-section {
+          padding: 8px 12px;
+          background: #fff7e6;
+          border-radius: 8px;
+          border-left: 3px solid #fa8c16;
+
+          .tags-label {
+            font-weight: 500;
+            color: #fa8c16;
+            margin-right: 8px;
+          }
+
+          .tags-list {
+            display: flex;
+            gap: 6px;
+            flex-wrap: wrap;
+            margin-top: 6px;
+
+            .complaint-tag {
+              background: #fff2e8;
+              color: #fa8c16;
+              border: 1px solid #ffd591;
+              padding: 2px 8px;
+              border-radius: 10px;
+              font-size: 11px;
+              font-weight: 500;
+            }
+          }
+        }
+
         .complaint-description {
           h4 {
             margin: 0 0 8px 0;
@@ -641,6 +1231,65 @@
               transform: translateY(-1px);
             }
           }
+
+          // 新增操作按钮样式
+          &.task-btn {
+            background: #fa8c16;
+            color: white;
+
+            &:hover {
+              background: #d46b08;
+              transform: translateY(-1px);
+            }
+          }
+
+          &.escalate-btn {
+            background: #ff4d4f;
+            color: white;
+
+            &:hover {
+              background: #cf1322;
+              transform: translateY(-1px);
+            }
+          }
+
+          &.assign-btn {
+            background: #52c41a;
+            color: white;
+
+            &:hover {
+              background: #389e0d;
+              transform: translateY(-1px);
+            }
+          }
+
+          &.tag-btn {
+            background: #722ed1;
+            color: white;
+
+            &:hover {
+              background: #531dab;
+              transform: translateY(-1px);
+            }
+          }
+
+          &.follow-up-btn {
+            background: #13c2c2;
+            color: white;
+
+            &:hover {
+              background: #08979c;
+              transform: translateY(-1px);
+            }
+
+            &.active {
+              background: #52c41a;
+
+              &:hover {
+                background: #389e0d;
+              }
+            }
+          }
         }
 
         .completed-status {

+ 353 - 11
src/app/shared/components/complaint-card/complaint-card.ts

@@ -17,6 +17,21 @@ export interface ComplaintItem {
   customerName?: string;
   priority?: string;
   images?: string[];
+  // 新增字段
+  source?: 'manual' | 'wechat_auto' | 'keyword_monitor'; // 投诉来源
+  wechatGroupName?: string; // 企业微信群名称
+  wechatMessageId?: string; // 微信消息ID
+  keywordMatched?: string[]; // 匹配的关键词
+  autoTagged?: boolean; // 是否自动标注
+  urgencyLevel?: 'normal' | 'urgent' | 'critical'; // 紧急程度
+  assignedTo?: string; // 分配给谁处理
+  estimatedResolutionTime?: Date; // 预计解决时间
+  actualResolutionTime?: Date; // 实际解决时间
+  customerSatisfaction?: number; // 客户满意度评分
+  followUpRequired?: boolean; // 是否需要后续跟进
+  relatedProjectId?: string; // 关联项目ID
+  escalationLevel?: number; // 升级级别 (0-3)
+  tags?: string[]; // 标签
 }
 
 interface ComplaintStats {
@@ -28,6 +43,15 @@ interface ComplaintStats {
   averageResolutionTime: number;
   priorityStats: { [key: string]: number };
   typeStats: { [key: string]: number };
+  // 新增统计字段
+  sourceStats: { [key: string]: number };
+  urgencyStats: { [key: string]: number };
+  escalationStats: { [key: string]: number };
+  autoTaggedCount: number;
+  wechatComplaintCount: number;
+  keywordMonitorCount: number;
+  overdueCount: number;
+  followUpRequiredCount: number;
 }
 
 @Component({
@@ -45,6 +69,43 @@ export class ComplaintCardComponent {
   priorityFilter = signal<string>('all');
   typeFilter = signal<string>('all');
   searchKeyword = signal<string>('');
+  sourceFilter = signal<string>('all');
+  urgencyFilter = signal<string>('all');
+
+  // 新增缺失的属性
+  monitoredGroups = 5; // 监控群组数量
+  todayDetections = 3; // 今日检测数量
+  
+  // 紧急投诉计算属性
+  urgentComplaints = computed(() => {
+    return this.complaints.filter(complaint => 
+      complaint.urgencyLevel === 'urgent' || 
+      complaint.urgencyLevel === 'critical' ||
+      complaint.priority === 'urgent' ||
+      complaint.priority === 'high'
+    );
+  });
+
+  // 投诉来源选项
+  complaintSources = [
+    { value: 'manual', label: '人工创建', icon: '👥' },
+    { value: 'wechat_auto', label: '微信自动', icon: '🤖' },
+    { value: 'keyword_monitor', label: '关键词监控', icon: '🔍' }
+  ];
+
+  // 紧急程度选项
+  urgencyLevels = [
+    { value: 'normal', label: '普通', color: '#52c41a' },
+    { value: 'urgent', label: '紧急', color: '#faad14' },
+    { value: 'critical', label: '严重', color: '#f5222d' }
+  ];
+
+  // 关键词监控配置
+  monitorKeywords = ['不满意', '投诉', '退款', '差评', '问题', '延期', '质量差'];
+
+  // 实时代办项状态
+  realTimeTaskStatus = signal<'idle' | 'processing' | 'completed'>('idle');
+  processingTaskId = signal<string>('');
 
   // 投诉类型
   complaintTypes = [
@@ -95,6 +156,31 @@ export class ComplaintCardComponent {
       typeStats[t.value] = complaints.filter(c => this.getComplaintType(c) === t.value).length;
     });
 
+    // 来源统计
+    const sourceStats: { [key: string]: number } = {};
+    this.complaintSources.forEach(s => {
+      sourceStats[s.value] = complaints.filter(c => c.source === s.value).length;
+    });
+
+    // 紧急程度统计
+    const urgencyStats: { [key: string]: number } = {};
+    this.urgencyLevels.forEach(u => {
+      urgencyStats[u.value] = complaints.filter(c => c.urgencyLevel === u.value).length;
+    });
+
+    // 升级级别统计
+    const escalationStats: { [key: string]: number } = {};
+    for (let i = 0; i <= 3; i++) {
+      escalationStats[i.toString()] = complaints.filter(c => c.escalationLevel === i).length;
+    }
+
+    // 其他统计
+    const autoTaggedCount = complaints.filter(c => c.autoTagged === true).length;
+    const wechatComplaintCount = complaints.filter(c => c.source === 'wechat_auto').length;
+    const keywordMonitorCount = complaints.filter(c => c.source === 'keyword_monitor').length;
+    const overdueCount = complaints.filter(c => this.isOverdue(c)).length;
+    const followUpRequiredCount = complaints.filter(c => c.followUpRequired === true).length;
+
     return {
       totalCount,
       pendingCount,
@@ -103,7 +189,15 @@ export class ComplaintCardComponent {
       highPriorityCount,
       averageResolutionTime: Math.round(averageResolutionTime * 10) / 10,
       priorityStats,
-      typeStats
+      typeStats,
+      sourceStats,
+      urgencyStats,
+      escalationStats,
+      autoTaggedCount,
+      wechatComplaintCount,
+      keywordMonitorCount,
+      overdueCount,
+      followUpRequiredCount
     };
   });
 
@@ -132,13 +226,26 @@ export class ComplaintCardComponent {
       filtered = filtered.filter(complaint => this.getComplaintType(complaint) === this.typeFilter());
     }
 
+    // 来源筛选
+    if (this.sourceFilter() !== 'all') {
+      filtered = filtered.filter(complaint => complaint.source === this.sourceFilter());
+    }
+
+    // 紧急程度筛选
+    if (this.urgencyFilter() !== 'all') {
+      filtered = filtered.filter(complaint => complaint.urgencyLevel === this.urgencyFilter());
+    }
+
     // 关键词搜索
     if (this.searchKeyword()) {
       const keyword = this.searchKeyword().toLowerCase();
       filtered = filtered.filter(complaint => 
         complaint.description?.toLowerCase().includes(keyword) ||
         (complaint as any).customerName?.toLowerCase().includes(keyword) ||
-        complaint.type?.toLowerCase().includes(keyword)
+        complaint.type?.toLowerCase().includes(keyword) ||
+        complaint.wechatGroupName?.toLowerCase().includes(keyword) ||
+        complaint.tags?.some(tag => tag.toLowerCase().includes(keyword)) ||
+        complaint.keywordMatched?.some(kw => kw.toLowerCase().includes(keyword))
       );
     }
 
@@ -282,6 +389,233 @@ export class ComplaintCardComponent {
     this.searchKeyword.set(keyword);
   }
 
+  updateSourceFilter(event: any) {
+    this.sourceFilter.set(event.target.value);
+  }
+
+  updateUrgencyFilter(event: any) {
+    this.urgencyFilter.set(event.target.value);
+  }
+
+  // ==================== 新增投诉管理功能 ====================
+
+  // 创建实时代办项
+  createRealTimeTask(complaint: ComplaintItem): void {
+    this.realTimeTaskStatus.set('processing');
+    this.processingTaskId.set(complaint.id);
+    
+    console.log('创建实时代办项:', complaint.id);
+    
+    // 模拟异步处理
+    setTimeout(() => {
+      complaint.status = '处理中';
+      complaint.assignedTo = '当前用户'; // 实际应该从用户服务获取
+      this.realTimeTaskStatus.set('completed');
+      
+      // 这里可以调用服务来创建实时代办项
+      // this.complaintService.createRealTimeTask(complaint);
+    }, 2000);
+  }
+
+  // 企业微信自动获取投诉
+  startWeChatMonitoring(): void {
+    console.log('开始企业微信投诉监控');
+    
+    // 这里可以调用企业微信API服务
+    // this.wechatService.startComplaintMonitoring(this.monitorKeywords);
+    
+    // 模拟自动获取到的投诉
+    const autoComplaint: ComplaintItem = {
+      id: 'wechat_' + Date.now(),
+      type: 'service',
+      description: '客户在微信群中表达不满意,提到"服务态度不好"',
+      status: '待处理',
+      source: 'wechat_auto',
+      wechatGroupName: '项目交流群',
+      wechatMessageId: 'msg_' + Date.now(),
+      keywordMatched: ['不满意', '服务态度'],
+      autoTagged: true,
+      urgencyLevel: 'urgent',
+      priority: 'high',
+      submitTime: new Date(),
+      submittedAt: new Date(),
+      customerName: '张先生',
+      followUpRequired: true,
+      escalationLevel: 1
+    };
+    
+    // 添加到投诉列表
+    this.complaints.push(autoComplaint);
+    
+    alert('检测到新的微信投诉,已自动创建投诉记录');
+  }
+
+  // 设置关键词监控
+  setupKeywordMonitoring(): void {
+    console.log('设置关键词监控:', this.monitorKeywords);
+    
+    // 这里可以打开关键词设置弹窗
+    const newKeywords = prompt('请输入监控关键词(用逗号分隔):', this.monitorKeywords.join(','));
+    
+    if (newKeywords) {
+      this.monitorKeywords = newKeywords.split(',').map(k => k.trim()).filter(k => k);
+      console.log('更新关键词监控:', this.monitorKeywords);
+      
+      // 这里可以调用服务更新关键词配置
+      // this.complaintService.updateMonitorKeywords(this.monitorKeywords);
+    }
+  }
+
+  // 分配投诉处理人
+  assignComplaint(complaint: ComplaintItem, assignee: string): void {
+    complaint.assignedTo = assignee;
+    complaint.status = '处理中';
+    
+    console.log('分配投诉处理人:', complaint.id, '→', assignee);
+    
+    // 这里可以调用服务更新分配信息
+    // this.complaintService.assignComplaint(complaint.id, assignee);
+  }
+
+  // 升级投诉
+  escalateComplaint(complaint: ComplaintItem): void {
+    if (!complaint.escalationLevel) {
+      complaint.escalationLevel = 0;
+    }
+    
+    if (complaint.escalationLevel < 3) {
+      complaint.escalationLevel++;
+      complaint.urgencyLevel = complaint.escalationLevel >= 2 ? 'critical' : 'urgent';
+      complaint.priority = 'urgent';
+      
+      console.log('投诉升级:', complaint.id, '升级到级别', complaint.escalationLevel);
+      
+      // 这里可以调用服务处理升级逻辑
+      // this.complaintService.escalateComplaint(complaint.id, complaint.escalationLevel);
+    }
+  }
+
+  // 模拟从企业微信获取新投诉
+  simulateNewWeChatComplaint(): void {
+    const newComplaint: ComplaintItem = {
+      id: 'wechat_' + Date.now(),
+      type: 'quality',
+      description: '客户在微信群中反映图纸质量问题,要求重新制作',
+      submitTime: new Date(),
+      submittedAt: new Date(),
+      status: '待处理',
+      customerName: '张先生',
+      priority: 'urgent',
+      source: 'wechat_auto',
+      wechatGroupName: '项目交付群-张先生',
+      wechatMessageId: 'msg_' + Date.now(),
+      keywordMatched: ['质量差', '重新制作'],
+      autoTagged: true,
+      urgencyLevel: 'urgent',
+      followUpRequired: true,
+      relatedProjectId: 'proj_001',
+      escalationLevel: 1,
+      tags: ['质量问题', '微信投诉', '紧急处理']
+    };
+    
+    // 添加到投诉列表
+    this.complaints.push(newComplaint);
+    console.log('检测到新的微信投诉:', newComplaint);
+  }
+
+  // 实时处理代办项
+  processRealTimeTask(taskId: string): void {
+    this.processingTaskId.set(taskId);
+    this.realTimeTaskStatus.set('processing');
+    
+    console.log('开始处理实时代办项:', taskId);
+    
+    // 模拟处理过程
+    setTimeout(() => {
+      this.realTimeTaskStatus.set('completed');
+      this.processingTaskId.set('');
+      console.log('实时代办项处理完成');
+    }, 3000);
+  }
+
+  // 通知升级
+  notifyEscalation(complaint: ComplaintItem): void {
+    console.log('发送升级通知:', complaint.id);
+    // 这里可以实现邮件、短信或企业微信通知
+  }
+
+  // 设置预计解决时间
+  setEstimatedResolutionTime(complaint: ComplaintItem, hours: number): void {
+    const estimatedTime = new Date();
+    estimatedTime.setHours(estimatedTime.getHours() + hours);
+    complaint.estimatedResolutionTime = estimatedTime;
+    
+    console.log('设置预计解决时间:', complaint.id, estimatedTime);
+  }
+
+  // 客户满意度评分
+  setCustomerSatisfaction(complaint: ComplaintItem, score: number): void {
+    complaint.customerSatisfaction = Math.max(1, Math.min(5, score));
+    
+    console.log('客户满意度评分:', complaint.id, score);
+  }
+
+  // 批量处理投诉
+  batchProcessComplaints(complaintIds: string[], action: string): void {
+    console.log('批量处理投诉:', complaintIds, action);
+    
+    complaintIds.forEach(id => {
+      const complaint = this.complaints.find(c => c.id === id);
+      if (complaint) {
+        switch (action) {
+          case 'assign':
+            // 批量分配
+            break;
+          case 'tag':
+            // 批量添加标签
+            break;
+          case 'escalate':
+            // 批量升级
+            this.escalateComplaint(complaint);
+            break;
+        }
+      }
+    });
+  }
+
+  // 设置后续跟进
+  setFollowUpRequired(complaint: ComplaintItem, required: boolean): void {
+    complaint.followUpRequired = required;
+    
+    console.log('设置后续跟进:', complaint.id, '→', required);
+    
+    // 这里可以调用服务更新跟进状态
+    // this.complaintService.setFollowUpRequired(complaint.id, required);
+  }
+
+  // 获取投诉来源信息
+  getComplaintSourceInfo(complaint: ComplaintItem) {
+    const source = this.complaintSources.find(s => s.value === complaint.source);
+    return source || { value: 'manual', label: '人工创建', icon: '👥' };
+  }
+
+  // 获取紧急程度信息
+  getUrgencyInfo(complaint: ComplaintItem) {
+    const urgency = this.urgencyLevels.find(u => u.value === complaint.urgencyLevel);
+    return urgency || { value: 'normal', label: '普通', color: '#52c41a' };
+  }
+
+  // 检查是否为自动标注
+  isAutoTagged(complaint: ComplaintItem): boolean {
+    return complaint.autoTagged === true;
+  }
+
+  // 获取升级级别显示
+  getEscalationDisplay(level: number): string {
+    const levels = ['普通', '一级', '二级', '三级'];
+    return levels[level] || '普通';
+  }
+
   // 确认客户评价完成
   confirmCustomerReviewComplete(complaint: ComplaintItem): void {
     // 更新投诉状态
@@ -294,15 +628,23 @@ export class ComplaintCardComponent {
     // this.complaintService.confirmCustomerReview(complaint.id);
   }
 
-  // 确认投诉处理完成
+  // 确认投诉解决完成
   confirmComplaintResolutionComplete(complaint: ComplaintItem): void {
-    // 更新投诉状态为已解决
-    complaint.status = '已解决';
-    complaint.resolvedAt = new Date();
-    
-    console.log('确认投诉处理完成:', complaint.id);
-    
-    // 这里可以调用服务来更新后端数据
-    // this.complaintService.confirmResolution(complaint.id);
+    console.log('确认投诉解决完成:', complaint.id);
+    // 这里可以添加确认投诉解决完成的逻辑
+    // 例如:更新状态、发送通知等
+    complaint.status = 'resolved';
+    complaint.actualResolutionTime = new Date();
+  }
+
+  // 添加投诉标签方法
+  addComplaintTag(complaint: ComplaintItem, tag: string): void {
+    if (!complaint.tags) {
+      complaint.tags = [];
+    }
+    if (!complaint.tags.includes(tag)) {
+      complaint.tags.push(tag);
+      console.log(`为投诉 ${complaint.id} 添加标签: ${tag}`);
+    }
   }
 }

+ 230 - 1
src/app/shared/components/customer-review-card/customer-review-card.html

@@ -1,7 +1,22 @@
 <div class="customer-review-card">
   <!-- 统计数据概览 -->
   <div class="stats-overview">
-    <h4>客户评价概览</h4>
+    <div class="stats-header">
+      <h4>客户评价概览</h4>
+      <div class="view-controls">
+        <button class="view-toggle-btn" (click)="toggleViewMode()">
+          @if (viewMode() === 'simple') {
+            切换到详细视图
+          } @else {
+            切换到简单视图
+          }
+        </button>
+        <button class="export-btn" (click)="exportReviewReport()">
+          导出报告
+        </button>
+      </div>
+    </div>
+    
     <div class="stats-grid">
       <div class="stat-item total">
         <div class="stat-value">{{ stats().totalCount }}</div>
@@ -27,6 +42,33 @@
     </div>
   </div>
 
+  <!-- 详细评价维度统计 (仅在详细视图模式下显示) -->
+  @if (viewMode() === 'detailed' && stats().dimensionAverages) {
+    <div class="dimension-stats">
+      <h5>评价维度分析</h5>
+      <div class="dimension-grid">
+        @for (dimension of reviewDimensions; track dimension.key) {
+          @if (stats().dimensionAverages[dimension.key]) {
+            <div class="dimension-item">
+              <div class="dimension-header">
+                <span class="dimension-label">{{ dimension.label }}</span>
+                <span class="dimension-score" [class]="getDimensionScoreClass(stats().dimensionAverages[dimension.key]!)">
+                  {{ (stats().dimensionAverages[dimension.key]! | number:'1.1-1') }}
+                </span>
+              </div>
+              <div class="dimension-stars">
+                @for (star of getDimensionStars(stats().dimensionAverages[dimension.key]!); track $index) {
+                  <span class="star">{{ star }}</span>
+                }
+              </div>
+              <div class="dimension-desc">{{ dimension.description }}</div>
+            </div>
+          }
+        }
+      </div>
+    </div>
+  }
+
   <!-- 分类统计 -->
   <div class="category-stats">
     <h5>问题分类统计</h5>
@@ -40,6 +82,193 @@
     </div>
   </div>
 
+  <!-- 详细评价界面 -->
+  <div class="detailed-review-section" *ngIf="viewMode() === 'detailed'">
+    <div class="detailed-header">
+      <h4>详细评价管理</h4>
+      <div class="review-actions">
+        <button class="btn btn-primary" (click)="createDetailedReview('proj_001')">
+          <i class="fas fa-plus"></i>
+          创建详细评价
+        </button>
+      </div>
+    </div>
+
+    <!-- 详细评价表单 -->
+    <div class="detailed-form" *ngIf="showDetailedForm">
+      <!-- 多维度评价表单 -->
+      <div class="dimension-form">
+        <h4>多维度评价</h4>
+        <div class="dimension-grid">
+          <div class="dimension-item" *ngFor="let dimension of reviewDimensions">
+            <label>{{ dimension.label }}</label>
+            <div class="star-rating">
+              <span 
+                class="star" 
+                *ngFor="let star of [1,2,3,4,5]; let i = index"
+                [class.active]="(currentRatings[dimension.key] || 0) > i"
+                (click)="setDimensionRating(dimension.key, i + 1)">
+                ★
+              </span>
+            </div>
+            <span class="rating-text">{{ currentRatings[dimension.key] || 0 }}/5</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 场景评价 -->
+      <div class="scene-evaluation">
+        <h4>场景评价</h4>
+        <div class="scene-grid">
+          <div class="scene-item" *ngFor="let scene of sceneTypes">
+            <div class="scene-header">
+              <span class="scene-name">{{ scene.label }}</span>
+              <div class="star-rating">
+                <span 
+                  class="star" 
+                  *ngFor="let star of [1,2,3,4,5]; let i = index"
+                  [class.active]="(currentSceneRatings[scene.value] || 0) > i"
+                  (click)="setSceneRating(scene.value, i + 1)">
+                  ★
+                </span>
+              </div>
+              <span class="rating-text">{{ currentSceneRatings[scene.value] || 0 }}/5</span>
+            </div>
+            <textarea 
+              class="scene-feedback"
+              [(ngModel)]="currentSceneFeedback[scene.value]"
+              placeholder="请输入对该场景的具体反馈...">
+            </textarea>
+          </div>
+        </div>
+      </div>
+
+      <!-- 优化建议 -->
+      <div class="optimization-suggestions">
+        <h4>下次合作,您希望我们优化哪些点?</h4>
+        <div class="suggestion-grid">
+          <label class="suggestion-item" *ngFor="let category of optimizationCategories">
+            <input 
+              type="checkbox" 
+              [(ngModel)]="selectedOptimizations[category.key]">
+            <span class="checkmark"></span>
+            <div class="suggestion-content">
+              <span class="suggestion-label">{{ category.label }}</span>
+              <span class="suggestion-desc">{{ category.description }}</span>
+            </div>
+          </label>
+        </div>
+        
+        <div class="custom-suggestion">
+          <label>其他建议:</label>
+          <textarea 
+            [(ngModel)]="customOptimizationSuggestion"
+            placeholder="请输入您的其他建议和意见...">
+          </textarea>
+        </div>
+      </div>
+
+      <!-- 操作按钮 -->
+      <div class="form-actions">
+        <button class="btn btn-primary" (click)="saveDetailedReview()">
+          保存评价
+        </button>
+        <button class="btn btn-secondary" (click)="cancelDetailedReview()">
+          取消
+        </button>
+      </div>
+    </div>
+
+    <!-- 创建详细评价按钮 -->
+    <div class="create-review-section" *ngIf="!showDetailedForm">
+      <button class="btn btn-primary" (click)="showDetailedForm = true">
+        <i class="icon-plus"></i>
+        创建详细评价
+      </button>
+    </div>
+
+    <!-- 详细评价列表 -->
+    <div class="detailed-reviews-list" *ngIf="detailedReviews && detailedReviews.length > 0">
+      <div class="list-header">
+        <h5>详细评价记录</h5>
+        <span class="review-count">{{ detailedReviews.length }} 条记录</span>
+      </div>
+
+      <div class="review-items">
+        <div class="review-item" *ngFor="let review of detailedReviews">
+          <div class="review-header">
+            <div class="customer-info">
+              <span class="customer-name">{{ review.customerName }}</span>
+              <span class="review-date">{{ review.submittedAt | date:'yyyy-MM-dd' }}</span>
+            </div>
+            <div class="overall-rating">
+              <span class="rating-label">总体评分:</span>
+              <div class="stars">
+                <span *ngFor="let i of getDimensionStars(review.dimensions.overall)">★</span>
+              </div>
+              <span class="rating-number">{{ review.dimensions.overall }}/5</span>
+            </div>
+          </div>
+
+          <div class="review-dimensions">
+            <div class="dimension-summary" *ngFor="let dim of reviewDimensions">
+              <span class="dim-label">{{ dim.label }}:</span>
+              <div class="dim-rating">
+                <div class="stars mini">
+                  <span *ngFor="let i of getDimensionStars(review.dimensions[dim.key] || 0)">★</span>
+                </div>
+                <span class="dim-score" [class]="getDimensionScoreClass(review.dimensions[dim.key] || 0)">
+                  {{ review.dimensions[dim.key] || 0 }}
+                </span>
+              </div>
+            </div>
+          </div>
+
+          <div class="scene-reviews" *ngIf="review.sceneReviews && review.sceneReviews.length > 0">
+            <h6>场景评价</h6>
+            <div class="scene-summary" *ngFor="let scene of review.sceneReviews">
+              <div class="scene-info">
+                <span class="scene-name">{{ getSceneTypeLabel(scene.sceneType) }}</span>
+                <div class="scene-rating">
+                  <div class="stars mini">
+                    <span *ngFor="let i of getDimensionStars(scene.rating)">★</span>
+                  </div>
+                  <span class="scene-score">{{ scene.rating }}/5</span>
+                </div>
+              </div>
+              <div class="scene-feedback" *ngIf="scene.feedback">
+                {{ scene.feedback }}
+              </div>
+            </div>
+          </div>
+
+          <div class="optimization-suggestions" *ngIf="review.optimizationSuggestions">
+            <h6>优化建议</h6>
+            <div class="suggestions-list">
+              <span class="suggestion-tag" *ngFor="let suggestion of review.optimizationSuggestions">
+                {{ suggestion }}
+              </span>
+            </div>
+            <div class="custom-suggestion" *ngIf="review.improvementSuggestions">
+              <strong>其他建议:</strong>{{ review.improvementSuggestions }}
+            </div>
+          </div>
+
+          <div class="review-actions">
+            <button class="btn btn-sm btn-primary" (click)="viewDetailedReview(review)">
+              <i class="fas fa-eye"></i>
+              查看详情
+            </button>
+            <button class="btn btn-sm btn-secondary" (click)="editDetailedReview(review.id)">
+              <i class="fas fa-edit"></i>
+              编辑
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
   <!-- 筛选区域 -->
   <div class="filter-section">
     <div class="filter-row">

+ 329 - 0
src/app/shared/components/customer-review-card/customer-review-card.scss

@@ -324,4 +324,333 @@
       }
     }
   }
+
+  // 详细评价表单样式
+  .detailed-form {
+    background: #f8f9fa;
+    border-radius: 8px;
+    padding: 24px;
+    margin-bottom: 20px;
+    border: 1px solid #e9ecef;
+
+    h4 {
+      color: #2c3e50;
+      margin-bottom: 16px;
+      font-size: 16px;
+      font-weight: 600;
+    }
+  }
+
+  // 多维度评价表单
+  .dimension-form {
+    margin-bottom: 24px;
+
+    .dimension-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+      gap: 16px;
+    }
+
+    .dimension-item {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 12px;
+      background: white;
+      border-radius: 6px;
+      border: 1px solid #dee2e6;
+
+      label {
+        font-weight: 500;
+        color: #495057;
+        min-width: 100px;
+      }
+
+      .star-rating {
+        display: flex;
+        gap: 2px;
+        margin: 0 12px;
+
+        .star {
+          font-size: 18px;
+          color: #ddd;
+          cursor: pointer;
+          transition: color 0.2s;
+
+          &:hover,
+          &.active {
+            color: #ffc107;
+          }
+        }
+      }
+
+      .rating-text {
+        font-size: 14px;
+        color: #6c757d;
+        min-width: 40px;
+        text-align: center;
+      }
+    }
+  }
+
+  // 场景评价
+  .scene-evaluation {
+    margin-bottom: 24px;
+
+    .scene-grid {
+      display: grid;
+      gap: 16px;
+    }
+
+    .scene-item {
+      background: white;
+      border-radius: 6px;
+      border: 1px solid #dee2e6;
+      overflow: hidden;
+
+      .scene-header {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 12px 16px;
+        background: #f8f9fa;
+        border-bottom: 1px solid #dee2e6;
+
+        .scene-name {
+          font-weight: 500;
+          color: #495057;
+        }
+
+        .star-rating {
+          display: flex;
+          gap: 2px;
+          margin: 0 12px;
+
+          .star {
+            font-size: 16px;
+            color: #ddd;
+            cursor: pointer;
+            transition: color 0.2s;
+
+            &:hover,
+            &.active {
+              color: #ffc107;
+            }
+          }
+        }
+
+        .rating-text {
+          font-size: 14px;
+          color: #6c757d;
+          min-width: 40px;
+          text-align: center;
+        }
+      }
+
+      .scene-feedback {
+        width: 100%;
+        min-height: 80px;
+        padding: 12px 16px;
+        border: none;
+        resize: vertical;
+        font-size: 14px;
+        line-height: 1.5;
+
+        &:focus {
+          outline: none;
+          background: #f8f9fa;
+        }
+
+        &::placeholder {
+          color: #adb5bd;
+        }
+      }
+    }
+  }
+
+  // 优化建议
+  .optimization-suggestions {
+    margin-bottom: 24px;
+
+    .suggestion-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+      gap: 12px;
+      margin-bottom: 16px;
+    }
+
+    .suggestion-item {
+      display: flex;
+      align-items: flex-start;
+      padding: 12px;
+      background: white;
+      border-radius: 6px;
+      border: 1px solid #dee2e6;
+      cursor: pointer;
+      transition: all 0.2s;
+
+      &:hover {
+        border-color: #007bff;
+        background: #f8f9ff;
+      }
+
+      input[type="checkbox"] {
+        margin-right: 12px;
+        margin-top: 2px;
+      }
+
+      .checkmark {
+        width: 16px;
+        height: 16px;
+        border: 2px solid #dee2e6;
+        border-radius: 3px;
+        margin-right: 12px;
+        margin-top: 2px;
+        position: relative;
+        transition: all 0.2s;
+
+        &::after {
+          content: '';
+          position: absolute;
+          left: 4px;
+          top: 1px;
+          width: 4px;
+          height: 8px;
+          border: solid white;
+          border-width: 0 2px 2px 0;
+          transform: rotate(45deg);
+          opacity: 0;
+          transition: opacity 0.2s;
+        }
+      }
+
+      input[type="checkbox"]:checked + .checkmark {
+        background: #007bff;
+        border-color: #007bff;
+
+        &::after {
+          opacity: 1;
+        }
+      }
+
+      .suggestion-content {
+        flex: 1;
+
+        .suggestion-label {
+          display: block;
+          font-weight: 500;
+          color: #495057;
+          margin-bottom: 4px;
+        }
+
+        .suggestion-desc {
+          display: block;
+          font-size: 12px;
+          color: #6c757d;
+          line-height: 1.4;
+        }
+      }
+    }
+
+    .custom-suggestion {
+      label {
+        display: block;
+        font-weight: 500;
+        color: #495057;
+        margin-bottom: 8px;
+      }
+
+      textarea {
+        width: 100%;
+        min-height: 80px;
+        padding: 12px;
+        border: 1px solid #dee2e6;
+        border-radius: 6px;
+        resize: vertical;
+        font-size: 14px;
+        line-height: 1.5;
+
+        &:focus {
+          outline: none;
+          border-color: #007bff;
+          box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
+        }
+
+        &::placeholder {
+          color: #adb5bd;
+        }
+      }
+    }
+  }
+
+  // 表单操作按钮
+  .form-actions {
+    display: flex;
+    gap: 12px;
+    justify-content: flex-end;
+    padding-top: 16px;
+    border-top: 1px solid #dee2e6;
+
+    .btn {
+      padding: 8px 20px;
+      border-radius: 6px;
+      font-size: 14px;
+      font-weight: 500;
+      border: none;
+      cursor: pointer;
+      transition: all 0.2s;
+
+      &.btn-primary {
+        background: #007bff;
+        color: white;
+
+        &:hover {
+          background: #0056b3;
+        }
+      }
+
+      &.btn-secondary {
+        background: #6c757d;
+        color: white;
+
+        &:hover {
+          background: #545b62;
+        }
+      }
+    }
+  }
+
+  // 创建评价按钮区域
+  .create-review-section {
+    text-align: center;
+    padding: 40px 20px;
+    background: #f8f9fa;
+    border-radius: 8px;
+    border: 2px dashed #dee2e6;
+    margin-bottom: 20px;
+
+    .btn {
+      padding: 12px 24px;
+      font-size: 16px;
+      font-weight: 500;
+      border-radius: 8px;
+      border: none;
+      cursor: pointer;
+      transition: all 0.2s;
+
+      &.btn-primary {
+        background: #007bff;
+        color: white;
+
+        &:hover {
+          background: #0056b3;
+          transform: translateY(-1px);
+        }
+      }
+
+      i {
+        margin-right: 8px;
+      }
+    }
+  }
 }

+ 276 - 7
src/app/shared/components/customer-review-card/customer-review-card.ts

@@ -3,6 +3,47 @@ import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { CustomerFeedback } from '../../../models/project.model';
 
+export interface DetailedReviewDimensions {
+  [key: string]: number; // 添加索引签名
+  overall: number; // 总体评价 0-5
+  serviceTimeliness: number; // 服务时效 0-5
+  suggestionResponse: number; // 建议回应 0-5
+  smallImageDelivery: number; // 小图交付 0-5
+  imageRevisionTimeliness: number; // 改图及时性 0-5
+  renderingQuality: number; // 图纸渲染 0-5
+  materialQuality: number; // 材质 0-5
+  lightingQuality: number; // 灯光 0-5
+  textureQuality: number; // 纹理 0-5
+  structureQuality: number; // 硬装结构 0-5
+  dataCompleteness: number; // 资料齐全情况 0-5
+  requirementUnderstanding: number; // 需求理解 0-5
+  communicationEffectiveness: number; // 沟通效果 0-5
+  sceneDesignQuality: number; // 场景设计质量 0-5
+}
+
+export interface SceneReview {
+  sceneType: 'bedroom' | 'dining_room' | 'living_room' | 'other';
+  sceneName: string;
+  designerId: string;
+  designerName: string;
+  rating: number; // 0-5
+  feedback: string;
+}
+
+export interface DetailedCustomerReview {
+  id: string;
+  projectId: string;
+  customerId: string;
+  customerName: string;
+  dimensions: DetailedReviewDimensions;
+  sceneReviews: SceneReview[];
+  improvementSuggestions: string; // 下次合作希望优化的点
+  optimizationSuggestions?: string[]; // 优化建议列表
+  overallFeedback: string;
+  submittedAt: Date;
+  status: 'pending' | 'completed';
+}
+
 export interface ReviewStats {
   totalCount: number;
   averageScore: number;
@@ -11,6 +52,7 @@ export interface ReviewStats {
   pendingCount: number;
   processedCount: number;
   categoryStats: { [key: string]: number };
+  dimensionAverages: Partial<DetailedReviewDimensions>;
 }
 
 @Component({
@@ -22,11 +64,39 @@ export interface ReviewStats {
 })
 export class CustomerReviewCardComponent {
   @Input() feedbacks: CustomerFeedback[] = [];
+  @Input() detailedReviews: DetailedCustomerReview[] = [];
   
   // 筛选条件
   statusFilter = signal<string>('all');
   categoryFilter = signal<string>('all');
   scoreFilter = signal<string>('all');
+  viewMode = signal<'simple' | 'detailed'>('detailed');
+  
+  // 详细评价维度配置
+  reviewDimensions = [
+    { key: 'overall', label: '总体评价', description: '对整体服务的满意度' },
+    { key: 'serviceTimeliness', label: '服务时效', description: '响应速度和交付及时性' },
+    { key: 'suggestionResponse', label: '建议回应', description: '对客户建议的响应程度' },
+    { key: 'smallImageDelivery', label: '小图交付', description: '小图质量和交付效率' },
+    { key: 'imageRevisionTimeliness', label: '改图及时性', description: '修改图纸的响应速度' },
+    { key: 'renderingQuality', label: '图纸渲染', description: '渲染效果和技术质量' },
+    { key: 'materialQuality', label: '材质', description: '材质选择和表现效果' },
+    { key: 'lightingQuality', label: '灯光', description: '灯光设计和氛围营造' },
+    { key: 'textureQuality', label: '纹理', description: '纹理细节和真实感' },
+    { key: 'structureQuality', label: '硬装结构', description: '结构设计的合理性' },
+    { key: 'dataCompleteness', label: '资料齐全情况', description: '提供资料的完整性' },
+    { key: 'requirementUnderstanding', label: '需求理解', description: '对客户需求的理解准确度' },
+    { key: 'communicationEffectiveness', label: '沟通效果', description: '沟通的有效性和清晰度' },
+    { key: 'sceneDesignQuality', label: '场景设计质量', description: '各场景设计的整体质量' }
+  ];
+  
+  // 场景类型配置
+  sceneTypes = [
+    { value: 'bedroom', label: '卧室' },
+    { value: 'dining_room', label: '餐厅' },
+    { value: 'living_room', label: '客厅' },
+    { value: 'other', label: '其他' }
+  ];
   
   // 评价分类
   categories = [
@@ -41,6 +111,7 @@ export class CustomerReviewCardComponent {
   // 计算统计数据
   stats = computed<ReviewStats>(() => {
     const feedbacks = this.feedbacks || [];
+    const detailedReviews = this.detailedReviews || [];
     
     const categoryStats: { [key: string]: number } = {};
     this.categories.forEach(cat => {
@@ -50,15 +121,27 @@ export class CustomerReviewCardComponent {
     const scores = feedbacks.filter(f => f.rating !== undefined).map(f => f.rating || 0);
     const averageScore = scores.length > 0 ? scores.reduce((sum, score) => sum + score, 0) / scores.length : 0;
     
+    // 计算详细评价维度的平均分
+    const dimensionAverages: Partial<DetailedReviewDimensions> = {};
+    if (detailedReviews.length > 0) {
+      this.reviewDimensions.forEach(dimension => {
+        const key = dimension.key as keyof DetailedReviewDimensions;
+        const values = detailedReviews.map(review => review.dimensions[key]).filter(v => v !== undefined);
+        if (values.length > 0) {
+          dimensionAverages[key] = values.reduce((sum, val) => sum + val, 0) / values.length;
+        }
+      });
+    }
+    
     return {
-      totalCount: feedbacks.length,
+      totalCount: feedbacks.length + detailedReviews.length,
       averageScore: Math.round(averageScore * 10) / 10,
-      satisfiedCount: feedbacks.filter(f => f.isSatisfied).length,
-      unsatisfiedCount: feedbacks.filter(f => !f.isSatisfied).length,
-      pendingCount: feedbacks.filter(f => f.status === '待处理').length,
-      processingCount: feedbacks.filter(f => f.status === '处理中').length,
-      processedCount: feedbacks.filter(f => f.status === '已解决').length,
-      categoryStats
+      satisfiedCount: feedbacks.filter(f => f.isSatisfied).length + detailedReviews.filter(r => r.dimensions.overall >= 4).length,
+      unsatisfiedCount: feedbacks.filter(f => !f.isSatisfied).length + detailedReviews.filter(r => r.dimensions.overall < 3).length,
+      pendingCount: feedbacks.filter(f => f.status === '待处理').length + detailedReviews.filter(r => r.status === 'pending').length,
+      processedCount: feedbacks.filter(f => f.status === '已解决').length + detailedReviews.filter(r => r.status === 'completed').length,
+      categoryStats,
+      dimensionAverages
     };
   });
   
@@ -212,4 +295,190 @@ export class CustomerReviewCardComponent {
     // 这里可以打开详情页面或模态框
     alert(`客户评价详情:\n\n客户: ${feedback.customerName}\n评分: ${feedback.rating}/5\n内容: ${feedback.content}\n回复: ${feedback.response || '暂无回复'}`);
   }
+
+  // 新增方法:获取维度评分的星级显示
+  getDimensionStars(score: number): string[] {
+    const stars = [];
+    const fullStars = Math.floor(score);
+    const hasHalfStar = score % 1 >= 0.5;
+    
+    for (let i = 0; i < 5; i++) {
+      if (i < fullStars) {
+        stars.push('★');
+      } else if (i === fullStars && hasHalfStar) {
+        stars.push('☆');
+      } else {
+        stars.push('☆');
+      }
+    }
+    return stars;
+  }
+
+  // 获取维度评分的颜色类
+  getDimensionScoreClass(score: number): string {
+    if (score >= 4.5) return 'score-excellent';
+    if (score >= 3.5) return 'score-good';
+    if (score >= 2.5) return 'score-average';
+    return 'score-poor';
+  }
+
+  // 获取场景类型标签
+  getSceneTypeLabel(sceneType: string): string {
+    const scene = this.sceneTypes.find(s => s.value === sceneType);
+    return scene ? scene.label : sceneType;
+  }
+
+  // 切换视图模式
+  toggleViewMode(): void {
+    this.viewMode.set(this.viewMode() === 'simple' ? 'detailed' : 'simple');
+  }
+
+  // 创建新的详细评价
+  createDetailedReview(projectId: string): void {
+    console.log('创建详细评价:', projectId);
+    // 这里可以打开评价表单模态框
+  }
+
+  // 查看详细评价
+  viewDetailedReview(review: DetailedCustomerReview): void {
+    console.log('查看详细评价:', review);
+    // 这里可以打开详细评价查看模态框
+  }
+
+  // 详细评价表单状态
+  showDetailedForm = false;
+  currentRatings: { [key: string]: number } = {};
+  currentSceneRatings: { [key: string]: number } = {};
+  currentSceneFeedback: { [key: string]: string } = {};
+  selectedOptimizations: { [key: string]: boolean } = {};
+  customOptimizationSuggestion = '';
+
+  // 优化建议类别
+  optimizationCategories = [
+    { key: 'service_speed', label: '服务时效', description: '提高响应速度和交付效率' },
+    { key: 'communication', label: '沟通效果', description: '加强沟通频率和质量' },
+    { key: 'design_quality', label: '设计质量', description: '提升设计水平和创意' },
+    { key: 'material_quality', label: '材质表现', description: '优化材质和纹理效果' },
+    { key: 'lighting_effect', label: '灯光效果', description: '改善灯光设置和氛围' },
+    { key: 'detail_accuracy', label: '细节准确性', description: '提高细节还原度' },
+    { key: 'revision_efficiency', label: '修改效率', description: '加快修改响应速度' },
+    { key: 'requirement_understanding', label: '需求理解', description: '更准确理解客户需求' }
+  ];
+
+  // 设置维度评分
+  setDimensionRating(dimensionKey: string, rating: number): void {
+    this.currentRatings[dimensionKey] = rating;
+  }
+
+  // 设置场景评分
+  setSceneRating(sceneKey: string, rating: number): void {
+    this.currentSceneRatings[sceneKey] = rating;
+  }
+
+  // 保存详细评价
+  saveDetailedReview(): void {
+    const newReview: DetailedCustomerReview = {
+      id: Date.now().toString(),
+      customerName: 'Customer Name',
+      projectId: 'proj_001',
+      customerId: 'customer-' + Date.now(),
+      overallFeedback: '',
+      submittedAt: new Date(),
+      status: 'completed' as 'pending' | 'completed',
+      dimensions: { ...this.currentRatings } as unknown as DetailedReviewDimensions,
+      sceneReviews: this.buildSceneReviews(),
+      optimizationSuggestions: this.getSelectedOptimizations(),
+      improvementSuggestions: this.customOptimizationSuggestion
+    };
+
+    // 添加到详细评价列表
+    if (!this.detailedReviews) {
+      this.detailedReviews = [];
+    }
+    this.detailedReviews.push(newReview);
+
+    console.log('保存详细评价:', newReview);
+    
+    // 重置表单
+    this.resetDetailedForm();
+    this.showDetailedForm = false;
+  }
+
+  // 计算总体评分
+  calculateOverallRating(): number {
+    const ratings = Object.values(this.currentRatings);
+    if (ratings.length === 0) return 0;
+    
+    const sum = ratings.reduce((acc, rating) => acc + rating, 0);
+    return Math.round(sum / ratings.length);
+  }
+
+  // 构建场景评价
+  buildSceneReviews(): SceneReview[] {
+    return this.sceneTypes
+      .filter(scene => this.currentSceneRatings[scene.value] > 0)
+      .map(scene => ({
+        sceneType: scene.value as 'bedroom' | 'dining_room' | 'living_room' | 'other',
+        sceneName: scene.label,
+        designerId: 'current-designer', // 这里应该从项目数据中获取
+        designerName: '当前设计师', // 这里应该从项目数据中获取
+        rating: this.currentSceneRatings[scene.value],
+        feedback: this.currentSceneFeedback[scene.value] || ''
+      }));
+  }
+
+  // 获取选中的优化建议
+  getSelectedOptimizations(): string[] {
+    return this.optimizationCategories
+      .filter(category => this.selectedOptimizations[category.key])
+      .map(category => category.label);
+  }
+
+  // 重置详细评价表单
+  resetDetailedForm(): void {
+    this.currentRatings = {};
+    this.currentSceneRatings = {};
+    this.currentSceneFeedback = {};
+    this.selectedOptimizations = {};
+    this.customOptimizationSuggestion = '';
+  }
+
+  // 取消详细评价
+  cancelDetailedReview(): void {
+    this.resetDetailedForm();
+    this.showDetailedForm = false;
+  }
+
+  // 编辑详细评价
+  editDetailedReview(reviewId: string): void {
+    const review = this.detailedReviews?.find(r => r.id === reviewId);
+    if (review) {
+      // 填充维度评分
+      this.currentRatings = { ...review.dimensions };
+      
+      // 填充场景评价数据
+      review.sceneReviews?.forEach(scene => {
+        this.currentSceneRatings[scene.sceneType] = scene.rating;
+        this.currentSceneFeedback[scene.sceneType] = scene.feedback || '';
+      });
+
+      // 填充优化建议
+      this.selectedOptimizations = {};
+      review.optimizationSuggestions?.forEach((suggestion: string) => {
+        const category = this.optimizationCategories.find(cat => cat.label === suggestion);
+        if (category) {
+          this.selectedOptimizations[category.key] = true;
+        }
+      });
+
+      this.customOptimizationSuggestion = review.improvementSuggestions || '';
+      this.showDetailedForm = true;
+    }
+  }
+
+  // 导出评价报告
+  exportReviewReport(): void {
+    console.log('导出评价报告');
+    // 这里可以实现导出功能
+  }
 }

+ 396 - 0
src/app/shared/components/customer-review-form/customer-review-form.html

@@ -0,0 +1,396 @@
+<div class="customer-review-form">
+  <div class="form-header">
+    <h3>客户服务评价</h3>
+    <p class="project-info">项目:{{ projectName }} | 设计师:{{ designerName }}</p>
+    <div class="overall-rating">
+      <span class="rating-label">总体评分:</span>
+      <span class="average-score">{{ getAverageScore() }}</span>
+      <span class="score-suffix">/5</span>
+      <div class="star-display">
+        @for (star of generateStars(getAverageScore()); track $index) {
+          <span class="star">{{ star }}</span>
+        }
+      </div>
+    </div>
+  </div>
+
+  <div class="form-content">
+    <!-- 总体满意度 -->
+    <div class="rating-section">
+      <h4>1. 总体满意度</h4>
+      <p class="section-description">您对本次服务的整体满意程度</p>
+      <div class="rating-control">
+        @for (i of [1,2,3,4,5]; track i) {
+          <label class="star-option">
+            <input 
+              type="radio" 
+              name="overallSatisfaction" 
+              [value]="i" 
+              [(ngModel)]="reviewData.overallSatisfaction">
+            <span class="star">{{ i <= reviewData.overallSatisfaction ? '★' : '☆' }}</span>
+            <span class="score-label">{{ i }}分</span>
+          </label>
+        }
+      </div>
+    </div>
+
+    <!-- 服务质量维度 -->
+    <div class="rating-section">
+      <h4>2. 服务质量</h4>
+      <div class="dimensions-grid">
+        <div class="dimension-item">
+          <label>服务时效</label>
+          <p class="dimension-desc">响应速度和交付及时性</p>
+          <div class="rating-control">
+            @for (i of [1,2,3,4,5]; track i) {
+              <label class="star-option">
+                <input 
+                  type="radio" 
+                  name="serviceTimeliness" 
+                  [value]="i" 
+                  [(ngModel)]="reviewData.serviceTimeliness">
+                <span class="star">{{ i <= reviewData.serviceTimeliness ? '★' : '☆' }}</span>
+              </label>
+            }
+          </div>
+        </div>
+
+        <div class="dimension-item">
+          <label>建议回应</label>
+          <p class="dimension-desc">对您建议的采纳和回应</p>
+          <div class="rating-control">
+            @for (i of [1,2,3,4,5]; track i) {
+              <label class="star-option">
+                <input 
+                  type="radio" 
+                  name="suggestionResponse" 
+                  [value]="i" 
+                  [(ngModel)]="reviewData.suggestionResponse">
+                <span class="star">{{ i <= reviewData.suggestionResponse ? '★' : '☆' }}</span>
+              </label>
+            }
+          </div>
+        </div>
+
+        <div class="dimension-item">
+          <label>小图交付</label>
+          <p class="dimension-desc">预览图的质量和效果</p>
+          <div class="rating-control">
+            @for (i of [1,2,3,4,5]; track i) {
+              <label class="star-option">
+                <input 
+                  type="radio" 
+                  name="smallImageDelivery" 
+                  [value]="i" 
+                  [(ngModel)]="reviewData.smallImageDelivery">
+                <span class="star">{{ i <= reviewData.smallImageDelivery ? '★' : '☆' }}</span>
+              </label>
+            }
+          </div>
+        </div>
+
+        <div class="dimension-item">
+          <label>改图及时性</label>
+          <p class="dimension-desc">修改请求的处理速度</p>
+          <div class="rating-control">
+            @for (i of [1,2,3,4,5]; track i) {
+              <label class="star-option">
+                <input 
+                  type="radio" 
+                  name="revisionEfficiency" 
+                  [value]="i" 
+                  [(ngModel)]="reviewData.revisionEfficiency">
+                <span class="star">{{ i <= reviewData.revisionEfficiency ? '★' : '☆' }}</span>
+              </label>
+            }
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 渲染质量维度 -->
+    <div class="rating-section">
+      <h4>3. 渲染质量</h4>
+      <div class="dimensions-grid">
+        <div class="dimension-item">
+          <label>材质表现</label>
+          <p class="dimension-desc">材料质感和真实度</p>
+          <div class="rating-control">
+            @for (i of [1,2,3,4,5]; track i) {
+              <label class="star-option">
+                <input 
+                  type="radio" 
+                  name="materials" 
+                  [value]="i" 
+                  [(ngModel)]="reviewData.renderingQuality.materials">
+                <span class="star">{{ i <= reviewData.renderingQuality.materials ? '★' : '☆' }}</span>
+              </label>
+            }
+          </div>
+        </div>
+
+        <div class="dimension-item">
+          <label>灯光效果</label>
+          <p class="dimension-desc">光照氛围和自然度</p>
+          <div class="rating-control">
+            @for (i of [1,2,3,4,5]; track i) {
+              <label class="star-option">
+                <input 
+                  type="radio" 
+                  name="lighting" 
+                  [value]="i" 
+                  [(ngModel)]="reviewData.renderingQuality.lighting">
+                <span class="star">{{ i <= reviewData.renderingQuality.lighting ? '★' : '☆' }}</span>
+              </label>
+            }
+          </div>
+        </div>
+
+        <div class="dimension-item">
+          <label>纹理细节</label>
+          <p class="dimension-desc">表面纹理和细节处理</p>
+          <div class="rating-control">
+            @for (i of [1,2,3,4,5]; track i) {
+              <label class="star-option">
+                <input 
+                  type="radio" 
+                  name="texture" 
+                  [value]="i" 
+                  [(ngModel)]="reviewData.renderingQuality.texture">
+                <span class="star">{{ i <= reviewData.renderingQuality.texture ? '★' : '☆' }}</span>
+              </label>
+            }
+          </div>
+        </div>
+
+        <div class="dimension-item">
+          <label>硬装结构</label>
+          <p class="dimension-desc">空间结构和比例准确性</p>
+          <div class="rating-control">
+            @for (i of [1,2,3,4,5]; track i) {
+              <label class="star-option">
+                <input 
+                  type="radio" 
+                  name="structure" 
+                  [value]="i" 
+                  [(ngModel)]="reviewData.renderingQuality.structure">
+                <span class="star">{{ i <= reviewData.renderingQuality.structure ? '★' : '☆' }}</span>
+              </label>
+            }
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 沟通体验维度 -->
+    <div class="rating-section">
+      <h4>4. 沟通体验</h4>
+      <div class="dimensions-grid">
+        <div class="dimension-item">
+          <label>资料齐全性</label>
+          <p class="dimension-desc">提供资料的完整程度</p>
+          <div class="rating-control">
+            @for (i of [1,2,3,4,5]; track i) {
+              <label class="star-option">
+                <input 
+                  type="radio" 
+                  name="documentation" 
+                  [value]="i" 
+                  [(ngModel)]="reviewData.communication.documentation">
+                <span class="star">{{ i <= reviewData.communication.documentation ? '★' : '☆' }}</span>
+              </label>
+            }
+          </div>
+        </div>
+
+        <div class="dimension-item">
+          <label>需求理解</label>
+          <p class="dimension-desc">对您需求的理解准确性</p>
+          <div class="rating-control">
+            @for (i of [1,2,3,4,5]; track i) {
+              <label class="star-option">
+                <input 
+                  type="radio" 
+                  name="requirementUnderstanding" 
+                  [value]="i" 
+                  [(ngModel)]="reviewData.communication.requirementUnderstanding">
+                <span class="star">{{ i <= reviewData.communication.requirementUnderstanding ? '★' : '☆' }}</span>
+              </label>
+            }
+          </div>
+        </div>
+
+        <div class="dimension-item">
+          <label>沟通充分性</label>
+          <p class="dimension-desc">沟通频率和信息透明度</p>
+          <div class="rating-control">
+            @for (i of [1,2,3,4,5]; track i) {
+              <label class="star-option">
+                <input 
+                  type="radio" 
+                  name="communicationSufficiency" 
+                  [value]="i" 
+                  [(ngModel)]="reviewData.communication.communicationSufficiency">
+                <span class="star">{{ i <= reviewData.communication.communicationSufficiency ? '★' : '☆' }}</span>
+              </label>
+            }
+          </div>
+        </div>
+
+        <div class="dimension-item">
+          <label>内容相关性</label>
+          <p class="dimension-desc">交付内容与需求的匹配度</p>
+          <div class="rating-control">
+            @for (i of [1,2,3,4,5]; track i) {
+              <label class="star-option">
+                <input 
+                  type="radio" 
+                  name="contentRelevance" 
+                  [value]="i" 
+                  [(ngModel)]="reviewData.communication.contentRelevance">
+                <span class="star">{{ i <= reviewData.communication.contentRelevance ? '★' : '☆' }}</span>
+              </label>
+            }
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 场景评价 -->
+    <div class="rating-section">
+      <h4>5. 各场景评价</h4>
+      <p class="section-description">请对每个设计场景进行评分</p>
+      <div class="scenes-grid">
+        @for (scene of reviewData.scenes; track $index) {
+          <div class="scene-item">
+            <label class="scene-name">{{ scene.sceneName }}</label>
+            <div class="rating-control">
+              @for (i of [1,2,3,4,5]; track i) {
+                <label class="star-option">
+                  <input 
+                    type="radio" 
+                    [name]="'scene-' + $index" 
+                    [value]="i" 
+                    [(ngModel)]="scene.score">
+                  <span class="star">{{ i <= scene.score ? '★' : '☆' }}</span>
+                </label>
+              }
+            </div>
+            <textarea 
+              class="scene-comments" 
+              placeholder="对该场景的具体意见(选填)"
+              [(ngModel)]="scene.comments"></textarea>
+          </div>
+        }
+      </div>
+    </div>
+
+    <!-- 改进建议 -->
+    <div class="suggestion-section">
+      <h4>6. 改进建议</h4>
+      <p class="section-description">您希望我们在哪些方面进行优化?</p>
+      <textarea 
+        class="suggestion-input" 
+        placeholder="请分享您的宝贵建议,帮助我们做得更好"
+        [(ngModel)]="reviewData.improvementSuggestions"></textarea>
+    </div>
+
+    <!-- 下次合作优化点 -->
+    <div class="optimization-section">
+      <h4>7. 下次合作优化点</h4>
+      <p class="section-description">下次合作,您希望我们优化哪些点?</p>
+      <div class="optimization-options">
+        <div class="option-grid">
+          <label class="option-item">
+            <input type="checkbox" value="服务响应速度">
+            <span>服务响应速度</span>
+          </label>
+          <label class="option-item">
+            <input type="checkbox" value="设计创意水平">
+            <span>设计创意水平</span>
+          </label>
+          <label class="option-item">
+            <input type="checkbox" value="沟通协调能力">
+            <span>沟通协调能力</span>
+          </label>
+          <label class="option-item">
+            <input type="checkbox" value="项目管理流程">
+            <span>项目管理流程</span>
+          </label>
+          <label class="option-item">
+            <input type="checkbox" value="技术实现质量">
+            <span>技术实现质量</span>
+          </label>
+          <label class="option-item">
+            <input type="checkbox" value="售后服务质量">
+            <span>售后服务质量</span>
+          </label>
+        </div>
+        <textarea 
+          class="optimization-input" 
+          placeholder="其他具体的优化建议..."
+          [(ngModel)]="reviewData.nextCooperationOptimization"></textarea>
+      </div>
+    </div>
+
+    <!-- 合作意愿 -->
+    <div class="cooperation-section">
+      <h4>8. 合作意愿</h4>
+      <div class="cooperation-options">
+        <label class="cooperation-option">
+          <input 
+            type="radio" 
+            name="willCooperateAgain" 
+            [value]="true" 
+            [(ngModel)]="reviewData.willCooperateAgain">
+          <span class="option-label">愿意再次合作</span>
+        </label>
+        <label class="cooperation-option">
+          <input 
+            type="radio" 
+            name="willCooperateAgain" 
+            [value]="false" 
+            [(ngModel)]="reviewData.willCooperateAgain">
+          <span class="option-label">暂不考虑再次合作</span>
+        </label>
+      </div>
+    </div>
+
+    <!-- 投诉内容 -->
+    <div class="complaint-section">
+      <div class="complaint-toggle">
+        <label class="complaint-switch">
+          <input 
+            type="checkbox" 
+            [(ngModel)]="showComplaint">
+          <span class="switch-label">我要投诉</span>
+        </label>
+      </div>
+
+      @if (showComplaint) {
+        <div class="complaint-content">
+          <h5>投诉内容</h5>
+          <p class="complaint-notice">⚠️ 投诉内容将作为实时代办项进行处理</p>
+          <textarea 
+            class="complaint-input" 
+            placeholder="请详细描述您遇到的问题和不满意的方面"
+            [(ngModel)]="reviewData.complaintContent"></textarea>
+        </div>
+      }
+    </div>
+
+    <!-- 提交按钮 -->
+    <div class="form-actions">
+      <button 
+        class="submit-btn primary" 
+        (click)="submitReview()">
+        提交评价
+      </button>
+      <button 
+        class="submit-btn secondary" 
+        type="button">
+        暂存草稿
+      </button>
+    </div>
+  </div>
+</div>

+ 469 - 0
src/app/shared/components/customer-review-form/customer-review-form.scss

@@ -0,0 +1,469 @@
+.customer-review-form {
+  max-width: 800px;
+  margin: 0 auto;
+  padding: 24px;
+  background: #fff;
+  border-radius: 12px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+
+  .form-header {
+    text-align: center;
+    margin-bottom: 32px;
+    padding-bottom: 20px;
+    border-bottom: 2px solid #f0f0f0;
+
+    h3 {
+      color: #333;
+      font-size: 24px;
+      font-weight: 600;
+      margin-bottom: 8px;
+    }
+
+    .project-info {
+      color: #666;
+      font-size: 14px;
+      margin-bottom: 16px;
+    }
+
+    .overall-rating {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: 8px;
+      font-size: 16px;
+
+      .rating-label {
+        color: #333;
+        font-weight: 500;
+      }
+
+      .average-score {
+        color: #ff6b35;
+        font-size: 20px;
+        font-weight: 600;
+      }
+
+      .score-suffix {
+        color: #999;
+      }
+
+      .star-display {
+        display: flex;
+        gap: 2px;
+
+        .star {
+          color: #ffc107;
+          font-size: 18px;
+        }
+      }
+    }
+  }
+
+  .form-content {
+    .rating-section {
+      margin-bottom: 32px;
+      padding: 20px;
+      background: #fafafa;
+      border-radius: 8px;
+      border-left: 4px solid #ff6b35;
+
+      h4 {
+        color: #333;
+        font-size: 18px;
+        font-weight: 600;
+        margin-bottom: 8px;
+      }
+
+      .section-description {
+        color: #666;
+        font-size: 14px;
+        margin-bottom: 16px;
+      }
+
+      .rating-control {
+        display: flex;
+        gap: 12px;
+        align-items: center;
+
+        .star-option {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          cursor: pointer;
+          padding: 8px;
+          border-radius: 6px;
+          transition: background-color 0.2s;
+
+          &:hover {
+            background-color: #fff3e0;
+          }
+
+          input[type="radio"] {
+            display: none;
+          }
+
+          .star {
+            font-size: 24px;
+            color: #ddd;
+            transition: color 0.2s;
+            margin-bottom: 4px;
+
+            &.filled {
+              color: #ffc107;
+            }
+          }
+
+          .score-label {
+            font-size: 12px;
+            color: #666;
+          }
+
+          input:checked + .star {
+            color: #ffc107;
+          }
+        }
+      }
+
+      .dimensions-grid {
+        display: grid;
+        grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+        gap: 20px;
+
+        .dimension-item {
+          background: #fff;
+          padding: 16px;
+          border-radius: 8px;
+          border: 1px solid #e0e0e0;
+
+          label {
+            display: block;
+            color: #333;
+            font-weight: 500;
+            margin-bottom: 4px;
+          }
+
+          .dimension-desc {
+            color: #666;
+            font-size: 12px;
+            margin-bottom: 12px;
+          }
+
+          .rating-control {
+            justify-content: flex-start;
+
+            .star-option {
+              padding: 4px;
+
+              .star {
+                font-size: 20px;
+              }
+
+              .score-label {
+                display: none;
+              }
+            }
+          }
+        }
+      }
+
+      .scenes-grid {
+        display: grid;
+        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+        gap: 16px;
+
+        .scene-item {
+          background: #fff;
+          padding: 16px;
+          border-radius: 8px;
+          border: 1px solid #e0e0e0;
+
+          .scene-name {
+            display: block;
+            color: #333;
+            font-weight: 500;
+            margin-bottom: 12px;
+          }
+
+          .scene-comments {
+            width: 100%;
+            min-height: 60px;
+            padding: 8px;
+            border: 1px solid #ddd;
+            border-radius: 4px;
+            font-size: 14px;
+            margin-top: 12px;
+            resize: vertical;
+
+            &:focus {
+              outline: none;
+              border-color: #ff6b35;
+            }
+          }
+        }
+      }
+    }
+
+    .suggestion-section,
+    .optimization-section {
+      margin-bottom: 24px;
+      padding: 20px;
+      background: #f8f9fa;
+      border-radius: 8px;
+
+      h4 {
+        color: #333;
+        font-size: 18px;
+        font-weight: 600;
+        margin-bottom: 8px;
+      }
+
+      .section-description {
+        color: #666;
+        font-size: 14px;
+        margin-bottom: 16px;
+      }
+
+      .suggestion-input,
+      .optimization-input {
+        width: 100%;
+        min-height: 100px;
+        padding: 12px;
+        border: 1px solid #ddd;
+        border-radius: 6px;
+        font-size: 14px;
+        resize: vertical;
+
+        &:focus {
+          outline: none;
+          border-color: #ff6b35;
+        }
+      }
+
+      .optimization-options {
+        .option-grid {
+          display: grid;
+          grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+          gap: 12px;
+          margin-bottom: 16px;
+
+          .option-item {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            padding: 8px 12px;
+            background: #fff;
+            border: 1px solid #e0e0e0;
+            border-radius: 6px;
+            cursor: pointer;
+            transition: all 0.2s;
+
+            &:hover {
+              border-color: #ff6b35;
+              background-color: #fff3e0;
+            }
+
+            input[type="checkbox"] {
+              margin: 0;
+            }
+
+            span {
+              font-size: 14px;
+              color: #333;
+            }
+          }
+        }
+      }
+    }
+
+    .cooperation-section {
+      margin-bottom: 24px;
+      padding: 20px;
+      background: #e8f5e8;
+      border-radius: 8px;
+
+      h4 {
+        color: #333;
+        font-size: 18px;
+        font-weight: 600;
+        margin-bottom: 16px;
+      }
+
+      .cooperation-options {
+        display: flex;
+        gap: 20px;
+
+        .cooperation-option {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+          cursor: pointer;
+          padding: 12px 16px;
+          background: #fff;
+          border: 2px solid #e0e0e0;
+          border-radius: 8px;
+          transition: all 0.2s;
+
+          &:hover {
+            border-color: #4caf50;
+          }
+
+          input[type="radio"] {
+            margin: 0;
+          }
+
+          .option-label {
+            font-size: 14px;
+            color: #333;
+            font-weight: 500;
+          }
+
+          input:checked + .option-label {
+            color: #4caf50;
+          }
+        }
+      }
+    }
+
+    .complaint-section {
+      margin-bottom: 32px;
+      padding: 20px;
+      background: #fff3e0;
+      border-radius: 8px;
+      border-left: 4px solid #ff9800;
+
+      .complaint-toggle {
+        margin-bottom: 16px;
+
+        .complaint-switch {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+          cursor: pointer;
+
+          input[type="checkbox"] {
+            margin: 0;
+          }
+
+          .switch-label {
+            font-size: 16px;
+            color: #ff9800;
+            font-weight: 500;
+          }
+        }
+      }
+
+      .complaint-content {
+        h5 {
+          color: #333;
+          font-size: 16px;
+          font-weight: 600;
+          margin-bottom: 8px;
+        }
+
+        .complaint-notice {
+          color: #ff9800;
+          font-size: 14px;
+          margin-bottom: 12px;
+          padding: 8px 12px;
+          background: #fff;
+          border-radius: 4px;
+          border-left: 3px solid #ff9800;
+        }
+
+        .complaint-input {
+          width: 100%;
+          min-height: 100px;
+          padding: 12px;
+          border: 1px solid #ddd;
+          border-radius: 6px;
+          font-size: 14px;
+          resize: vertical;
+
+          &:focus {
+            outline: none;
+            border-color: #ff9800;
+          }
+        }
+      }
+    }
+
+    .form-actions {
+      display: flex;
+      gap: 16px;
+      justify-content: center;
+      padding-top: 24px;
+      border-top: 1px solid #e0e0e0;
+
+      .submit-btn {
+        padding: 12px 32px;
+        border: none;
+        border-radius: 6px;
+        font-size: 16px;
+        font-weight: 500;
+        cursor: pointer;
+        transition: all 0.2s;
+
+        &.primary {
+          background: #ff6b35;
+          color: #fff;
+
+          &:hover {
+            background: #e55a2b;
+            transform: translateY(-1px);
+          }
+        }
+
+        &.secondary {
+          background: #f5f5f5;
+          color: #666;
+          border: 1px solid #ddd;
+
+          &:hover {
+            background: #e0e0e0;
+          }
+        }
+      }
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .customer-review-form {
+    padding: 16px;
+
+    .form-content {
+      .rating-section {
+        .dimensions-grid {
+          grid-template-columns: 1fr;
+        }
+
+        .scenes-grid {
+          grid-template-columns: 1fr;
+        }
+      }
+
+      .optimization-section {
+        .optimization-options {
+          .option-grid {
+            grid-template-columns: 1fr;
+          }
+        }
+      }
+
+      .cooperation-section {
+        .cooperation-options {
+          flex-direction: column;
+        }
+      }
+
+      .form-actions {
+        flex-direction: column;
+
+        .submit-btn {
+          width: 100%;
+        }
+      }
+    }
+  }
+}

+ 227 - 0
src/app/shared/components/customer-review-form/customer-review-form.ts

@@ -0,0 +1,227 @@
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+// 评价维度接口
+export interface ReviewDimension {
+  id: string;
+  label: string;
+  description?: string;
+  score: number;
+  subDimensions?: ReviewDimension[];
+}
+
+// 评价数据接口
+export interface CustomerReviewData {
+  // 总体评价 (0-5分)
+  overallSatisfaction: number;
+  
+  // 服务质量维度 (0-5分)
+  serviceTimeliness: number;        // 服务时效
+  suggestionResponse: number;       // 建议回应
+  smallImageDelivery: number;       // 小图交付
+  revisionEfficiency: number;       // 改图不及时
+  
+  // 图纸渲染质量 (0-5分)
+  renderingQuality: {
+    materials: number;              // 材质
+    lighting: number;               // 灯光
+    texture: number;                // 纹理
+    structure: number;              // 硬装结构
+  };
+  
+  // 沟通体验 (0-5分)
+  communication: {
+    documentation: number;          // 资料齐情况
+    requirementUnderstanding: number; // 需求误解
+    communicationSufficiency: number; // 缺少沟通
+    contentRelevance: number;       // 文不对题
+  };
+  
+  // 多个场景评价 - 对应设计师
+  scenes: SceneReview[];
+  
+  // 改进建议
+  improvementSuggestions: string;
+  
+  // 下次合作优化点
+  nextCooperationOptimization: string;
+  
+  // 合作意愿
+  willCooperateAgain: boolean;
+  
+  // 投诉内容(可选)
+  complaintContent?: string;
+  
+  // 评价时间
+  reviewDate: Date;
+}
+
+// 场景评价接口
+export interface SceneReview {
+  sceneName: string;
+  designerName: string;
+  score: number;
+  comments?: string;
+}
+
+@Component({
+  selector: 'app-customer-review-form',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './customer-review-form.html',
+  styleUrls: ['./customer-review-form.scss']
+})
+export class CustomerReviewFormComponent {
+  @Input() projectName: string = '';
+  @Input() designerName: string = '';
+  @Input() scenes: string[] = ['客厅', '餐厅', '卧室', '厨房', '卫生间', '阳台'];
+  @Output() reviewSubmitted = new EventEmitter<CustomerReviewData>();
+  
+  // 默认评价数据
+  reviewData: CustomerReviewData = {
+    overallSatisfaction: 5,
+    serviceTimeliness: 5,
+    suggestionResponse: 5,
+    smallImageDelivery: 5,
+    revisionEfficiency: 5,
+    renderingQuality: {
+      materials: 5,
+      lighting: 5,
+      texture: 5,
+      structure: 5
+    },
+    communication: {
+      documentation: 5,
+      requirementUnderstanding: 5,
+      communicationSufficiency: 5,
+      contentRelevance: 5
+    },
+    scenes: [],
+    improvementSuggestions: '',
+    nextCooperationOptimization: '',
+    willCooperateAgain: true,
+    complaintContent: '',
+    reviewDate: new Date()
+  };
+
+  // 是否显示投诉内容
+  showComplaint: boolean = false;
+
+  constructor() {
+    // 初始化场景评价
+    this.scenes.forEach(scene => {
+      this.reviewData.scenes.push({
+        sceneName: scene,
+        designerName: this.designerName,
+        score: 5,
+        comments: ''
+      });
+    });
+  }
+
+  // 提交评价
+  submitReview(): void {
+    // 验证数据
+    if (this.validateReview()) {
+      this.reviewSubmitted.emit(this.reviewData);
+      this.resetForm();
+    } else {
+      alert('请完成所有必填的评价项目');
+    }
+  }
+
+  // 验证评价数据
+  private validateReview(): boolean {
+    // 检查总体评分
+    if (this.reviewData.overallSatisfaction === 0) return false;
+    
+    // 检查所有场景评分
+    for (const scene of this.reviewData.scenes) {
+      if (scene.score === 0) return false;
+    }
+    
+    return true;
+  }
+
+  // 重置表单
+  private resetForm(): void {
+    this.reviewData = {
+      overallSatisfaction: 5,
+      serviceTimeliness: 5,
+      suggestionResponse: 5,
+      smallImageDelivery: 5,
+      revisionEfficiency: 5,
+      renderingQuality: {
+        materials: 5,
+        lighting: 5,
+        texture: 5,
+        structure: 5
+      },
+      communication: {
+        documentation: 5,
+        requirementUnderstanding: 5,
+        communicationSufficiency: 5,
+        contentRelevance: 5
+      },
+      scenes: this.scenes.map(scene => ({
+        sceneName: scene,
+        designerName: this.designerName,
+        score: 5,
+        comments: ''
+      })),
+      improvementSuggestions: '',
+      nextCooperationOptimization: '',
+      willCooperateAgain: true,
+      complaintContent: '',
+      reviewDate: new Date()
+    };
+    this.showComplaint = false;
+  }
+
+  // 获取平均分
+  getAverageScore(): number {
+    const scores = [
+      this.reviewData.overallSatisfaction,
+      this.reviewData.serviceTimeliness,
+      this.reviewData.suggestionResponse,
+      this.reviewData.smallImageDelivery,
+      this.reviewData.revisionEfficiency,
+      this.reviewData.renderingQuality.materials,
+      this.reviewData.renderingQuality.lighting,
+      this.reviewData.renderingQuality.texture,
+      this.reviewData.renderingQuality.structure,
+      this.reviewData.communication.documentation,
+      this.reviewData.communication.requirementUnderstanding,
+      this.reviewData.communication.communicationSufficiency,
+      this.reviewData.communication.contentRelevance,
+      ...this.reviewData.scenes.map(scene => scene.score)
+    ];
+    
+    const sum = scores.reduce((total, score) => total + score, 0);
+    return Math.round((sum / scores.length) * 10) / 10;
+  }
+
+  // 切换投诉显示
+  toggleComplaint(): void {
+    this.showComplaint = !this.showComplaint;
+    if (!this.showComplaint) {
+      this.reviewData.complaintContent = '';
+    }
+  }
+
+  // 生成星级显示
+  generateStars(score: number): string[] {
+    const stars = [];
+    for (let i = 1; i <= 5; i++) {
+      if (i <= score) {
+        stars.push('★');
+      } else if (i - 0.5 <= score) {
+        stars.push('☆');
+      } else {
+        stars.push('☆');
+      }
+    }
+    return stars;
+  }
+}

+ 167 - 0
src/app/shared/components/panoramic-synthesis-card/panoramic-synthesis-card.html

@@ -0,0 +1,167 @@
+<!-- 全景图合成控制区域 -->
+  <div class="synthesis-controls">
+    <div class="control-header">
+      <h4>全景图合成</h4>
+      <div class="synthesis-status" [class]="'status-' + (isProcessing() ? 'processing' : 'idle')">
+        {{ isProcessing() ? '合成中' : '就绪' }}
+      </div>
+    </div>
+    
+    <div class="quality-selector">
+      <label>合成质量:</label>
+      <div class="quality-options">
+        <button class="quality-btn" 
+                [class.active]="selectedQuality() === 'standard'"
+                (click)="setQuality('standard')">
+          标准
+        </button>
+        <button class="quality-btn" 
+                [class.active]="selectedQuality() === 'high'"
+                (click)="setQuality('high')">
+          高清
+        </button>
+        <button class="quality-btn" 
+                [class.active]="selectedQuality() === 'ultra'"
+                (click)="setQuality('ultra')">
+          超清
+        </button>
+      </div>
+    </div>
+    
+    <div class="batch-controls">
+      <label class="batch-toggle">
+        <input type="checkbox" 
+               [checked]="batchMode()" 
+               (change)="toggleBatchMode()">
+        批量处理模式
+      </label>
+      
+      <div class="space-selection" *ngIf="batchMode()">
+        <div class="space-item" 
+             *ngFor="let space of synthesis?.spaces" 
+             [class.selected]="selectedSpaces().includes(space.id)">
+          <label class="space-checkbox">
+            <input type="checkbox" 
+                   [checked]="selectedSpaces().includes(space.id)"
+                   (change)="toggleSpaceSelection(space.id)">
+            {{ space.name }}
+          </label>
+          <span class="space-type">{{ getSpaceTypeLabel(space.type) }}</span>
+        </div>
+      </div>
+    </div>
+    
+    <div class="action-buttons">
+      <button class="btn btn-primary" 
+              (click)="batchMode() ? startBatchSynthesis() : startSynthesis()"
+              [disabled]="isProcessing()">
+        <i class="fas fa-play"></i>
+        {{ batchMode() ? '批量合成' : '开始合成' }}
+      </button>
+      
+      <button class="btn btn-secondary" 
+              (click)="previewSynthesis(synthesis.id)"
+              [disabled]="!synthesis">
+        <i class="fas fa-eye"></i>
+        预览
+      </button>
+      
+      <button class="btn btn-success" 
+              (click)="downloadSynthesis(synthesis.id)"
+              [disabled]="!synthesis || synthesis.status !== 'completed'">
+        <i class="fas fa-download"></i>
+        下载
+      </button>
+    </div>
+  </div>
+
+  <!-- 合成统计信息 -->
+  <div class="synthesis-stats" *ngIf="synthesisTasks && synthesisTasks.length > 0">
+    <div class="stats-header">
+      <h4>合成统计</h4>
+    </div>
+    
+    <div class="stats-grid">
+      <div class="stat-item">
+        <div class="stat-value">{{ stats().totalTasks }}</div>
+        <div class="stat-label">总任务数</div>
+      </div>
+      <div class="stat-item">
+        <div class="stat-value">{{ stats().completedTasks }}</div>
+        <div class="stat-label">已完成</div>
+      </div>
+      <div class="stat-item">
+        <div class="stat-value">{{ stats().processingTasks }}</div>
+        <div class="stat-label">处理中</div>
+      </div>
+      <div class="stat-item">
+        <div class="stat-value">{{ stats().failedTasks }}</div>
+        <div class="stat-label">失败</div>
+      </div>
+      <div class="stat-item">
+        <div class="stat-value">{{ stats().averageRenderTime }}<span class="time-suffix">分钟</span></div>
+        <div class="stat-label">平均渲染时间</div>
+      </div>
+      <div class="stat-item">
+        <div class="stat-value">{{ (stats().totalFileSize / 1024 / 1024).toFixed(1) }}<span class="size-suffix">MB</span></div>
+        <div class="stat-label">总文件大小</div>
+      </div>
+    </div>
+  </div>
+
+  <!-- 任务列表 -->
+  <div class="task-list" *ngIf="synthesisTasks && synthesisTasks.length > 0">
+    <div class="task-header">
+      <h4>合成任务</h4>
+      <span class="task-count">{{ synthesisTasks.length }} 个任务</span>
+    </div>
+    
+    <div class="task-items">
+      <div class="task-item" 
+           *ngFor="let task of synthesisTasks" 
+           [class]="'status-' + task.status">
+        <div class="task-info">
+          <div class="task-name">{{ task.spaceName }}</div>
+          <div class="task-meta">
+            <span class="quality">{{ task.quality === 'standard' ? '标准' : task.quality === 'high' ? '高清' : '超清' }}</span>
+            <span class="progress" *ngIf="task.status === 'processing'">{{ task.progress }}%</span>
+            <span class="render-time" *ngIf="task.renderTime">{{ task.renderTime }}分钟</span>
+          </div>
+        </div>
+        
+        <div class="task-status">
+          <span class="status-badge" [class]="'badge-' + task.status">
+            {{ task.status === 'pending' ? '等待中' : 
+               task.status === 'processing' ? '处理中' : 
+               task.status === 'completed' ? '已完成' : '失败' }}
+          </span>
+        </div>
+        
+        <div class="task-actions">
+          <button class="btn btn-sm btn-primary" 
+                  (click)="previewSynthesis(task.id)"
+                  *ngIf="task.status === 'completed'">
+            <i class="fas fa-eye"></i>
+          </button>
+          
+          <button class="btn btn-sm btn-success" 
+                  (click)="downloadSynthesis(task.id)"
+                  *ngIf="task.status === 'completed'">
+            <i class="fas fa-download"></i>
+          </button>
+          
+          <button class="btn btn-sm btn-warning" 
+                  (click)="retryFailedTask(task.id)"
+                  *ngIf="task.status === 'failed'">
+            <i class="fas fa-redo"></i>
+          </button>
+          
+          <button class="btn btn-sm btn-danger" 
+                  (click)="cancelTask(task.id)"
+                  *ngIf="task.status === 'processing'">
+            <i class="fas fa-stop"></i>
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>

+ 722 - 0
src/app/shared/components/panoramic-synthesis-card/panoramic-synthesis-card.scss

@@ -0,0 +1,722 @@
+// 全景图合成控制区域样式
+.synthesis-controls {
+  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 20px;
+  color: white;
+
+  .control-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+
+    h4 {
+      margin: 0;
+      font-size: 18px;
+      font-weight: 600;
+    }
+
+    .synthesis-status {
+      padding: 4px 12px;
+      border-radius: 20px;
+      font-size: 12px;
+      font-weight: 500;
+
+      &.status-idle {
+        background: rgba(255, 255, 255, 0.2);
+      }
+
+      &.status-processing {
+        background: #ffc107;
+        color: #000;
+        animation: pulse 2s infinite;
+      }
+    }
+  }
+
+  .quality-selector {
+    margin-bottom: 20px;
+
+    label {
+      display: block;
+      margin-bottom: 8px;
+      font-weight: 500;
+    }
+
+    .quality-options {
+      display: flex;
+      gap: 10px;
+
+      .quality-btn {
+        padding: 8px 16px;
+        border: 2px solid rgba(255, 255, 255, 0.3);
+        border-radius: 6px;
+        background: transparent;
+        color: white;
+        cursor: pointer;
+        transition: all 0.3s ease;
+
+        &:hover {
+          border-color: rgba(255, 255, 255, 0.6);
+        }
+
+        &.active {
+          border-color: white;
+          background: rgba(255, 255, 255, 0.2);
+        }
+      }
+    }
+  }
+
+  .batch-controls {
+    margin-bottom: 20px;
+
+    .batch-toggle {
+      display: flex;
+      align-items: center;
+      margin-bottom: 15px;
+      cursor: pointer;
+
+      input[type="checkbox"] {
+        margin-right: 8px;
+      }
+    }
+
+    .space-selection {
+      background: rgba(255, 255, 255, 0.1);
+      border-radius: 8px;
+      padding: 15px;
+      max-height: 200px;
+      overflow-y: auto;
+
+      .space-item {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: 8px 0;
+        border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+
+        &:last-child {
+          border-bottom: none;
+        }
+
+        &.selected {
+          background: rgba(255, 255, 255, 0.1);
+          border-radius: 4px;
+          padding: 8px;
+        }
+
+        .space-checkbox {
+          display: flex;
+          align-items: center;
+          cursor: pointer;
+
+          input[type="checkbox"] {
+            margin-right: 8px;
+          }
+        }
+
+        .space-type {
+          font-size: 12px;
+          opacity: 0.8;
+        }
+      }
+    }
+  }
+
+  .action-buttons {
+    display: flex;
+    gap: 10px;
+    flex-wrap: wrap;
+
+    .btn {
+      padding: 10px 20px;
+      border: none;
+      border-radius: 6px;
+      font-size: 14px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+
+      &.btn-primary {
+        background: #007bff;
+        color: white;
+
+        &:hover:not(:disabled) {
+          background: #0056b3;
+        }
+
+        &:disabled {
+          background: rgba(255, 255, 255, 0.3);
+          cursor: not-allowed;
+        }
+      }
+
+      &.btn-secondary {
+        background: rgba(255, 255, 255, 0.2);
+        color: white;
+
+        &:hover:not(:disabled) {
+          background: rgba(255, 255, 255, 0.3);
+        }
+
+        &:disabled {
+          opacity: 0.5;
+          cursor: not-allowed;
+        }
+      }
+
+      &.btn-success {
+        background: #28a745;
+        color: white;
+
+        &:hover:not(:disabled) {
+          background: #1e7e34;
+        }
+
+        &:disabled {
+          background: rgba(255, 255, 255, 0.3);
+          cursor: not-allowed;
+        }
+      }
+
+      i {
+        margin-right: 6px;
+      }
+    }
+  }
+}
+
+// 合成统计样式
+.synthesis-stats {
+  background: #fff;
+  border: 1px solid #e9ecef;
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 20px;
+
+  .stats-header {
+    margin-bottom: 15px;
+
+    h4 {
+      margin: 0;
+      color: #333;
+      font-size: 18px;
+      font-weight: 600;
+    }
+  }
+
+  .stats-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+    gap: 15px;
+
+    .stat-item {
+      text-align: center;
+      padding: 15px;
+      background: #f8f9fa;
+      border-radius: 8px;
+
+      .stat-value {
+        font-size: 24px;
+        font-weight: 700;
+        color: #007bff;
+        margin-bottom: 5px;
+
+        .time-suffix,
+        .size-suffix {
+          font-size: 14px;
+          font-weight: 400;
+          color: #666;
+        }
+      }
+
+      .stat-label {
+        font-size: 12px;
+        color: #666;
+        text-transform: uppercase;
+        letter-spacing: 0.5px;
+      }
+    }
+  }
+}
+
+// 任务列表样式
+.task-list {
+  background: #fff;
+  border: 1px solid #e9ecef;
+  border-radius: 12px;
+  padding: 20px;
+
+  .task-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 15px;
+
+    h4 {
+      margin: 0;
+      color: #333;
+      font-size: 18px;
+      font-weight: 600;
+    }
+
+    .task-count {
+      background: #007bff;
+      color: white;
+      padding: 4px 12px;
+      border-radius: 20px;
+      font-size: 12px;
+      font-weight: 500;
+    }
+  }
+
+  .task-items {
+    .task-item {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 15px;
+      border: 1px solid #e9ecef;
+      border-radius: 8px;
+      margin-bottom: 10px;
+      transition: all 0.3s ease;
+
+      &:hover {
+        border-color: #007bff;
+        box-shadow: 0 2px 8px rgba(0, 123, 255, 0.1);
+      }
+
+      &.status-pending {
+        border-left: 4px solid #6c757d;
+      }
+
+      &.status-processing {
+        border-left: 4px solid #ffc107;
+        background: #fff9c4;
+      }
+
+      &.status-completed {
+        border-left: 4px solid #28a745;
+      }
+
+      &.status-failed {
+        border-left: 4px solid #dc3545;
+        background: #f8d7da;
+      }
+
+      .task-info {
+        flex: 1;
+
+        .task-name {
+          font-weight: 500;
+          color: #333;
+          margin-bottom: 8px;
+          font-size: 14px;
+        }
+
+        .task-meta {
+          display: flex;
+          gap: 15px;
+          font-size: 12px;
+          color: #666;
+
+          .quality {
+            background: #e9ecef;
+            padding: 2px 8px;
+            border-radius: 12px;
+            font-weight: 500;
+          }
+
+          .progress {
+            color: #ffc107;
+            font-weight: 500;
+          }
+
+          .render-time {
+            color: #28a745;
+          }
+        }
+      }
+
+      .task-status {
+        margin: 0 15px;
+
+        .status-badge {
+          padding: 4px 12px;
+          border-radius: 20px;
+          font-size: 12px;
+          font-weight: 500;
+
+          &.badge-pending {
+            background: #6c757d;
+            color: white;
+          }
+
+          &.badge-processing {
+            background: #ffc107;
+            color: #000;
+          }
+
+          &.badge-completed {
+            background: #28a745;
+            color: white;
+          }
+
+          &.badge-failed {
+            background: #dc3545;
+            color: white;
+          }
+        }
+      }
+
+      .task-actions {
+        display: flex;
+        gap: 8px;
+
+        .btn {
+          padding: 6px 12px;
+          border: none;
+          border-radius: 4px;
+          font-size: 12px;
+          cursor: pointer;
+          transition: all 0.3s ease;
+
+          &.btn-sm {
+            padding: 4px 8px;
+          }
+
+          &.btn-primary {
+            background: #007bff;
+            color: white;
+
+            &:hover {
+              background: #0056b3;
+            }
+          }
+
+          &.btn-success {
+            background: #28a745;
+            color: white;
+
+            &:hover {
+              background: #1e7e34;
+            }
+          }
+
+          &.btn-warning {
+            background: #ffc107;
+            color: #000;
+
+            &:hover {
+              background: #e0a800;
+            }
+          }
+
+          &.btn-danger {
+            background: #dc3545;
+            color: white;
+
+            &:hover {
+              background: #c82333;
+            }
+          }
+
+          i {
+            margin-right: 4px;
+          }
+        }
+      }
+    }
+  }
+}
+
+.panoramic-synthesis-card {
+  background: #fff;
+  border: 1px solid #e8e8e8;
+  border-radius: 8px;
+  padding: 20px;
+  margin-bottom: 16px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+  transition: box-shadow 0.3s ease;
+
+  &:hover {
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
+  }
+
+  .card-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+    padding-bottom: 12px;
+    border-bottom: 1px solid #f0f0f0;
+
+    .project-name {
+      margin: 0;
+      font-size: 18px;
+      font-weight: 600;
+      color: #262626;
+    }
+
+    .status-badge {
+      padding: 4px 12px;
+      border-radius: 20px;
+      font-size: 12px;
+      font-weight: 500;
+
+      &.status-draft {
+        background: #f0f0f0;
+        color: #595959;
+      }
+
+      &.status-processing {
+        background: #e6f7ff;
+        color: #1890ff;
+      }
+
+      &.status-completed {
+        background: #f6ffed;
+        color: #52c41a;
+      }
+
+      &.status-published {
+        background: #fff7e6;
+        color: #fa8c16;
+      }
+    }
+  }
+
+  .card-content {
+    margin-bottom: 20px;
+
+    .info-section {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+      gap: 12px;
+      margin-bottom: 20px;
+
+      .info-item {
+        display: flex;
+        align-items: center;
+
+        .label {
+          font-weight: 500;
+          color: #595959;
+          margin-right: 8px;
+          font-size: 14px;
+        }
+
+        .value {
+          color: #262626;
+          font-size: 14px;
+        }
+      }
+    }
+
+    .spaces-section {
+      h4 {
+        margin: 0 0 12px 0;
+        font-size: 16px;
+        font-weight: 600;
+        color: #262626;
+      }
+
+      .space-list {
+        .space-item {
+          background: #fafafa;
+          border: 1px solid #e8e8e8;
+          border-radius: 6px;
+          padding: 12px;
+          margin-bottom: 8px;
+
+          &:last-child {
+            margin-bottom: 0;
+          }
+
+          .space-header {
+            display: flex;
+            align-items: center;
+            margin-bottom: 8px;
+
+            .space-name {
+              font-weight: 500;
+              color: #262626;
+              margin-right: 12px;
+            }
+
+            .space-type {
+              background: #f0f0f0;
+              color: #595959;
+              padding: 2px 8px;
+              border-radius: 4px;
+              font-size: 12px;
+              margin-right: 12px;
+            }
+
+            .space-status {
+              padding: 2px 8px;
+              border-radius: 4px;
+              font-size: 12px;
+              font-weight: 500;
+
+              &.status-pending {
+                background: #f0f0f0;
+                color: #595959;
+              }
+
+              &.status-processing {
+                background: #e6f7ff;
+                color: #1890ff;
+              }
+
+              &.status-completed {
+                background: #f6ffed;
+                color: #52c41a;
+              }
+
+              &.status-failed {
+                background: #fff1f0;
+                color: #f5222d;
+              }
+            }
+          }
+
+          .space-progress {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            margin-bottom: 8px;
+
+            .progress-bar {
+              flex: 1;
+              height: 8px;
+              background: #f0f0f0;
+              border-radius: 4px;
+              overflow: hidden;
+
+              .progress-fill {
+                height: 100%;
+                border-radius: 4px;
+                transition: width 0.3s ease;
+              }
+            }
+
+            .progress-text {
+              font-size: 12px;
+              color: #595959;
+              min-width: 40px;
+              text-align: right;
+            }
+          }
+
+          .space-images {
+            .image-count {
+              font-size: 12px;
+              color: #8c8c8c;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .card-actions {
+    display: flex;
+    gap: 8px;
+    justify-content: flex-end;
+
+    .btn {
+      padding: 8px 16px;
+      border: 1px solid;
+      border-radius: 6px;
+      font-size: 14px;
+      font-weight: 500;
+      cursor: pointer;
+      transition: all 0.2s ease;
+
+      &:disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+      }
+
+      &.btn-primary {
+        background: #1890ff;
+        border-color: #1890ff;
+        color: #fff;
+
+        &:hover:not(:disabled) {
+          background: #40a9ff;
+          border-color: #40a9ff;
+        }
+      }
+
+      &.btn-secondary {
+        background: #52c41a;
+        border-color: #52c41a;
+        color: #fff;
+
+        &:hover:not(:disabled) {
+          background: #73d13d;
+          border-color: #73d13d;
+        }
+      }
+
+      &.btn-outline {
+        background: transparent;
+        border-color: #d9d9d9;
+        color: #595959;
+
+        &:hover:not(:disabled) {
+          border-color: #1890ff;
+          color: #1890ff;
+        }
+      }
+
+      &.btn-danger {
+        background: #ff4d4f;
+        border-color: #ff4d4f;
+        color: #fff;
+
+        &:hover:not(:disabled) {
+          background: #ff7875;
+          border-color: #ff7875;
+        }
+      }
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .panoramic-synthesis-card {
+    padding: 16px;
+
+    .card-header {
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 8px;
+
+      .status-badge {
+        align-self: flex-start;
+      }
+    }
+
+    .card-content {
+      .info-section {
+        grid-template-columns: 1fr;
+      }
+    }
+
+    .card-actions {
+      flex-wrap: wrap;
+      justify-content: center;
+
+      .btn {
+        flex: 1;
+        min-width: 80px;
+      }
+    }
+  }
+}

+ 243 - 0
src/app/shared/components/panoramic-synthesis-card/panoramic-synthesis-card.ts

@@ -0,0 +1,243 @@
+import { Component, Input, signal, computed } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { PanoramicSynthesis, PanoramicSpace, SpaceImage } from '../../../models/project.model';
+
+export interface SynthesisTask {
+  id: string;
+  projectId: string;
+  spaceId: string;
+  spaceName: string;
+  spaceType: string;
+  status: 'pending' | 'processing' | 'completed' | 'failed';
+  progress: number;
+  startTime?: Date;
+  completionTime?: Date;
+  outputUrl?: string;
+  errorMessage?: string;
+  quality?: 'standard' | 'high' | 'ultra';
+  renderTime?: number; // 渲染时间(分钟)
+  fileSize?: number; // 文件大小(字节)
+}
+
+export interface SynthesisStats {
+  totalTasks: number;
+  completedTasks: number;
+  processingTasks: number;
+  failedTasks: number;
+  averageRenderTime: number;
+  totalFileSize: number;
+}
+
+@Component({
+  selector: 'app-panoramic-synthesis-card',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './panoramic-synthesis-card.html',
+  styleUrls: ['./panoramic-synthesis-card.scss']
+})
+export class PanoramicSynthesisCardComponent {
+  @Input() synthesis!: PanoramicSynthesis;
+  @Input() showActions: boolean = true;
+  @Input() synthesisTasks: SynthesisTask[] = [];
+  
+  // 状态管理
+  isProcessing = signal<boolean>(false);
+  selectedQuality = signal<'standard' | 'high' | 'ultra'>('high');
+  batchMode = signal<boolean>(false);
+  selectedSpaces = signal<string[]>([]);
+  
+  // 计算统计数据
+  stats = computed<SynthesisStats>(() => {
+    const tasks = this.synthesisTasks || [];
+    const completedTasks = tasks.filter(t => t.status === 'completed');
+    const processingTasks = tasks.filter(t => t.status === 'processing');
+    const failedTasks = tasks.filter(t => t.status === 'failed');
+    
+    const renderTimes = completedTasks
+      .filter(t => t.startTime && t.completionTime)
+      .map(t => (t.completionTime!.getTime() - t.startTime!.getTime()) / 1000);
+    
+    const averageRenderTime = renderTimes.length > 0 
+      ? renderTimes.reduce((sum, time) => sum + time, 0) / renderTimes.length 
+      : 0;
+    
+    return {
+      totalTasks: tasks.length,
+      completedTasks: completedTasks.length,
+      processingTasks: processingTasks.length,
+      failedTasks: failedTasks.length,
+      averageRenderTime,
+      totalFileSize: 0 // 这里可以根据实际需要计算
+    };
+  });
+
+  getStatusLabel(status: string): string {
+    const statusMap: { [key: string]: string } = {
+      'draft': '草稿',
+      'processing': '处理中',
+      'completed': '已完成',
+      'published': '已发布'
+    };
+    return statusMap[status] || status;
+  }
+
+  getSpaceTypeLabel(type: string): string {
+    const typeMap: { [key: string]: string } = {
+      'living_room': '客厅',
+      'bedroom': '卧室',
+      'kitchen': '厨房',
+      'bathroom': '卫生间',
+      'dining_room': '餐厅',
+      'study': '书房',
+      'balcony': '阳台'
+    };
+    return typeMap[type] || type;
+  }
+
+  getQualityLabel(quality: string): string {
+    const qualityMap: { [key: string]: string } = {
+      'standard': '标准',
+      'high': '高清',
+      'ultra': '超清'
+    };
+    return qualityMap[quality] || quality;
+  }
+
+  getProgressPercentage(space: PanoramicSpace): number {
+    return space.progress || 0;
+  }
+
+  getProgressColor(progress: number): string {
+    if (progress >= 100) return '#52c41a';
+    if (progress >= 70) return '#1890ff';
+    if (progress >= 30) return '#faad14';
+    return '#f5222d';
+  }
+
+  onPreview(synthesis: PanoramicSynthesis): void {
+    if (synthesis.previewUrl) {
+      window.open(synthesis.previewUrl, '_blank');
+    } else {
+      console.warn('预览链接不可用');
+    }
+  }
+
+  onDownload(synthesis: PanoramicSynthesis): void {
+    if (synthesis.downloadUrl) {
+      window.open(synthesis.downloadUrl, '_blank');
+    } else {
+      console.warn('下载链接不可用');
+    }
+  }
+
+  onEdit(synthesis: PanoramicSynthesis): void {
+    console.log('编辑全景合成:', synthesis);
+    // 实际实现中会导航到编辑页面
+  }
+
+  onDelete(synthesis: PanoramicSynthesis): void {
+    if (confirm('确定要删除这个全景合成吗?')) {
+      console.log('删除全景合成:', synthesis);
+      // 实际实现中会调用删除API
+    }
+  }
+
+  formatFileSize(bytes: number | undefined): string {
+    if (!bytes) return '未知';
+    
+    const sizes = ['B', 'KB', 'MB', 'GB'];
+    const i = Math.floor(Math.log(bytes) / Math.log(1024));
+    return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
+  }
+
+  // 开始合成任务
+  startSynthesis(spaceIds: string[] = []): void {
+    if (this.isProcessing()) return;
+    
+    this.isProcessing.set(true);
+    const spacesToProcess = spaceIds.length > 0 ? spaceIds : this.selectedSpaces();
+    
+    console.log('开始全景图合成:', {
+      spaces: spacesToProcess,
+      quality: this.selectedQuality(),
+      batchMode: this.batchMode()
+    });
+    
+    // 模拟合成过程
+    setTimeout(() => {
+      this.isProcessing.set(false);
+      console.log('全景图合成完成');
+    }, 3000);
+  }
+
+  // 批量合成
+  startBatchSynthesis(): void {
+    if (this.synthesis?.spaces) {
+      const allSpaceIds = this.synthesis.spaces.map(space => space.id);
+      this.startSynthesis(allSpaceIds);
+    }
+  }
+
+  // 切换空间选择
+  toggleSpaceSelection(spaceId: string): void {
+    const current = this.selectedSpaces();
+    if (current.includes(spaceId)) {
+      this.selectedSpaces.set(current.filter(id => id !== spaceId));
+    } else {
+      this.selectedSpaces.set([...current, spaceId]);
+    }
+  }
+
+  // 设置合成质量
+  setQuality(quality: 'standard' | 'high' | 'ultra'): void {
+    this.selectedQuality.set(quality);
+  }
+
+  // 切换批量模式
+  toggleBatchMode(): void {
+    this.batchMode.set(!this.batchMode());
+    if (this.batchMode()) {
+      // 批量模式下选择所有空间
+      if (this.synthesis?.spaces) {
+        this.selectedSpaces.set(this.synthesis.spaces.map(space => space.id));
+      }
+    } else {
+      this.selectedSpaces.set([]);
+    }
+  }
+
+  // 预览合成结果
+  previewSynthesis(spaceId: string): void {
+    const task = this.synthesisTasks.find(t => t.spaceId === spaceId);
+    if (task?.outputUrl) {
+      window.open(task.outputUrl, '_blank');
+    } else {
+      console.warn('预览不可用');
+    }
+  }
+
+  // 下载合成结果
+  downloadSynthesis(spaceId: string): void {
+    const task = this.synthesisTasks.find(t => t.spaceId === spaceId);
+    if (task?.outputUrl) {
+      const link = document.createElement('a');
+      link.href = task.outputUrl;
+      link.download = `panoramic_${task.spaceName}.jpg`;
+      link.click();
+    } else {
+      console.warn('下载不可用');
+    }
+  }
+
+  // 重试失败的任务
+  retryFailedTask(taskId: string): void {
+    console.log('重试合成任务:', taskId);
+    // 这里可以实现重试逻辑
+  }
+
+  // 取消处理中的任务
+  cancelTask(taskId: string): void {
+    console.log('取消合成任务:', taskId);
+    // 这里可以实现取消逻辑
+  }
+}

+ 257 - 101
src/app/shared/components/settlement-card/settlement-card.html

@@ -1,111 +1,267 @@
 <div class="settlement-card">
-  <!-- 统计数据概览 -->
-  <div class="stats-overview">
-    <h4>尾款结算概览</h4>
-    <div class="stats-grid">
-      <div class="stat-item total">
-        <div class="stat-value">{{ formatAmount(stats().totalAmount) }}</div>
-        <div class="stat-label">总金额 ({{ stats().totalCount }}项)</div>
+  <!-- 自动化控制头部 -->
+  @if (showAutomationControls) {
+    <div class="automation-header">
+      <div class="automation-title">
+        <mat-icon>auto_awesome</mat-icon>
+        <span>自动化结算处理</span>
       </div>
-      <div class="stat-item pending">
-        <div class="stat-value">{{ formatAmount(stats().pendingAmount) }}</div>
-        <div class="stat-label">待收款 ({{ stats().pendingCount }}项)</div>
+      <div class="automation-actions">
+        <button 
+          mat-raised-button 
+          color="primary"
+          [disabled]="isProcessing()"
+          (click)="processAllAutomation()"
+          matTooltip="批量处理所有待结算项">
+          @if (isProcessing()) {
+            <mat-spinner diameter="16"></mat-spinner>
+            <span>处理中...</span>
+          } @else {
+            <ng-container>
+              <mat-icon>play_arrow</mat-icon>
+              <span>批量处理</span>
+            </ng-container>
+          }
+        </button>
       </div>
-      <div class="stat-item overdue">
-        <div class="stat-value">{{ formatAmount(stats().overdueAmount) }}</div>
-        <div class="stat-label">逾期尾款 ({{ stats().overdueCount }}项)</div>
-      </div>
-      <div class="stat-item completed">
-        <div class="stat-value">{{ formatAmount(stats().completedAmount) }}</div>
-        <div class="stat-label">已收款 ({{ stats().completedCount }}项)</div>
-      </div>
-    </div>
-  </div>
-
-  <!-- 筛选和搜索 -->
-  <div class="filter-section">
-    <div class="search-bar">
-      <input 
-        type="text" 
-        placeholder="搜索项目名称或阶段..."
-        [value]="searchKeyword()"
-        (input)="updateSearchKeyword($event.target.value)"
-        class="search-input">
-    </div>
-    <div class="filter-buttons">
-      <button 
-        class="filter-btn"
-        [class.active]="statusFilter() === 'all'"
-        (click)="updateStatusFilter('all')">
-        全部
-      </button>
-      <button 
-        class="filter-btn pending"
-        [class.active]="statusFilter() === '待结算'"
-        (click)="updateStatusFilter('待结算')">
-        待结算
-      </button>
-      <button 
-        class="filter-btn overdue"
-        [class.active]="statusFilter() === 'overdue'"
-        (click)="updateStatusFilter('overdue')">
-        逾期
-      </button>
-      <button 
-        class="filter-btn completed"
-        [class.active]="statusFilter() === '已结算'"
-        (click)="updateStatusFilter('已结算')">
-        已结算
-      </button>
     </div>
-  </div>
+  }
 
-  <!-- 结算列表 -->
-  <div class="settlements-list">
-    @if (filteredSettlements() && filteredSettlements().length > 0) {
-      <div class="list-header">
-        <span class="col-project">项目信息</span>
-        <span class="col-amount">金额</span>
-        <span class="col-status">状态</span>
-        <span class="col-date">日期</span>
+  <div class="settlement-card">
+    <!-- 统计概览 -->
+    <div class="stats-overview">
+      <div class="stats-title">
+        <mat-icon>account_balance_wallet</mat-icon>
+        <span>尾款结算统计</span>
       </div>
-      <div class="list-body">
-        @for (settlement of filteredSettlements(); track settlement.id) {
-          <div class="settlement-item" [class]="getStatusClass(settlement)">
-            <div class="col-project">
-              <div class="project-name">{{ settlement.projectName || '结算项' }}</div>
-              <div class="stage-name">{{ settlement.stage || '阶段' }}</div>
-            </div>
-            <div class="col-amount">
-              <div class="amount">{{ formatAmount(settlement.amount || 0) }}</div>
-              <div class="percentage">{{ settlement.percentage }}%</div>
-            </div>
-            <div class="col-status">
-              <span class="status-badge" [class]="getStatusClass(settlement)">
-                {{ settlement.status }}
-              </span>
-              @if (isOverdue(settlement)) {
-                <div class="overdue-days">逾期 {{ getDaysOverdue(settlement) }} 天</div>
-              }
-            </div>
-            <div class="col-date">
-              @if (settlement.settledAt) {
-                <div class="settled-date">{{ settlement.settledAt | date:'yyyy-MM-dd' }}</div>
-                <div class="date-label">结算日期</div>
-              } @else {
-                <div class="due-date">{{ settlement.createdAt | date:'yyyy-MM-dd' }}</div>
-                <div class="date-label">创建日期</div>
-              }
-            </div>
-          </div>
-        }
+      <div class="stats-grid">
+        <div class="stat-item total">
+          <div class="stat-value">{{ formatAmount(stats().totalAmount) }}</div>
+          <div class="stat-label">总金额 ({{ stats().totalCount }}项)</div>
+        </div>
+        <div class="stat-item pending">
+          <div class="stat-value">{{ formatAmount(stats().pendingAmount) }}</div>
+          <div class="stat-label">待结算 ({{ stats().pendingCount }}项)</div>
+        </div>
+        <div class="stat-item overdue">
+          <div class="stat-value">{{ formatAmount(stats().overdueAmount) }}</div>
+          <div class="stat-label">逾期 ({{ stats().overdueCount }}项)</div>
+        </div>
+        <div class="stat-item completed">
+          <div class="stat-value">{{ formatAmount(stats().completedAmount) }}</div>
+          <div class="stat-label">已结算 ({{ stats().completedCount }}项)</div>
+        </div>
+      </div>
+    </div>
+  
+    <!-- 筛选区域 -->
+    <div class="filter-section">
+      <div class="search-box">
+        <mat-icon>search</mat-icon>
+        <input 
+          type="text" 
+          placeholder="搜索项目名称或阶段..."
+          [value]="searchKeyword()"
+          (input)="updateSearchKeyword($event.target.value)">
       </div>
-    } @else {
-      <div class="empty-state">
-        <div class="empty-icon">💰</div>
-        <div class="empty-title">暂无结算信息</div>
-        <div class="empty-desc">当前筛选条件下没有找到相关的结算记录</div>
+      <div class="filter-buttons">
+        <button 
+          class="filter-btn"
+          [class.active]="statusFilter() === 'all'"
+          (click)="updateStatusFilter('all')">
+          全部
+        </button>
+        <button 
+          class="filter-btn"
+          [class.active]="statusFilter() === '待结算'"
+          (click)="updateStatusFilter('待结算')">
+          待结算
+        </button>
+        <button 
+          class="filter-btn"
+          [class.active]="statusFilter() === '已结算'"
+          (click)="updateStatusFilter('已结算')">
+          已结算
+        </button>
+        <button 
+          class="filter-btn overdue"
+          [class.active]="statusFilter() === 'overdue'"
+          (click)="updateStatusFilter('overdue')">
+          逾期
+        </button>
       </div>
-    }
+    </div>
+  
+    <!-- 结算列表 -->
+    <div class="settlement-list">
+      @if (filteredSettlements().length > 0) {
+        <div class="list-header">
+          <span class="col-project">项目信息</span>
+          <span class="col-amount">金额</span>
+          <span class="col-status">状态</span>
+          <span class="col-actions">操作</span>
+          <span class="col-date">日期</span>
+        </div>
+        <div class="list-body">
+          @for (settlement of filteredSettlements(); track settlement.id) {
+            <div class="settlement-item" [class]="getStatusClass(settlement)">
+              <div class="col-project">
+                <div class="project-name">{{ settlement.projectName || '结算项' }}</div>
+                <div class="stage-name">{{ settlement.stage || '阶段' }}</div>
+              </div>
+              <div class="col-amount">
+                <div class="amount">{{ formatAmount(settlement.amount || 0) }}</div>
+                <div class="percentage">{{ settlement.percentage }}%</div>
+              </div>
+              <div class="col-status">
+                <span class="status-badge" [class]="getStatusClass(settlement)">
+                  {{ settlement.status }}
+                </span>
+                @if (isOverdue(settlement)) {
+                  <div class="overdue-days">逾期 {{ getDaysOverdue(settlement) }} 天</div>
+                }
+                
+                <!-- 自动化处理按钮 -->
+                @if (showAutomationControls && settlement.status === '待结算' && !isOverdue(settlement)) {
+                  <button 
+                    class="auto-process-btn"
+                    mat-icon-button
+                    color="primary"
+                    [disabled]="isProcessing() && processingSettlementId() !== settlement.id"
+                    (click)="processSettlementAutomation(settlement); $event.stopPropagation()"
+                    matTooltip="自动处理此结算">
+                    @if (processingSettlementId() === settlement.id) {
+                      <mat-spinner diameter="16"></mat-spinner>
+                    } @else {
+                      <mat-icon>auto_awesome</mat-icon>
+                    }
+                  </button>
+                }
+              </div>
+              
+              <!-- 新增操作列 -->
+              <div class="col-actions">
+                <div class="action-buttons">
+                  <!-- 项目验收确认按钮 -->
+                  @if (settlement.status === '待结算') {
+                    <button 
+                      mat-stroked-button
+                      [color]="getAcceptanceStatus(settlement.projectId) === 'confirmed' ? 'primary' : 'accent'"
+                      [disabled]="getAcceptanceStatus(settlement.projectId) === 'confirming'"
+                      (click)="confirmProjectAcceptance(settlement)"
+                      matTooltip="技术确认项目验收"
+                      class="acceptance-btn">
+                      @if (getAcceptanceStatus(settlement.projectId) === 'confirming') {
+                        <mat-spinner diameter="16"></mat-spinner>
+                        <span>确认中</span>
+                      } @else if (getAcceptanceStatus(settlement.projectId) === 'confirmed') {
+                        <ng-container>
+                          <mat-icon>check_circle</mat-icon>
+                          <span>已验收</span>
+                        </ng-container>
+                      } @else {
+                        <ng-container>
+                          <mat-icon>task_alt</mat-icon>
+                          <span>确认验收</span>
+                        </ng-container>
+                      }
+                    </button>
+                  }
+                  
+                  <!-- 客服跟进提醒按钮 -->
+                  @if (settlement.status === '待结算' && getAcceptanceStatus(settlement.projectId) === 'confirmed') {
+                    <button 
+                      mat-stroked-button
+                      color="warn"
+                      [disabled]="getReminderStatus(settlement.projectId) === 'creating' || getReminderStatus(settlement.projectId) === 'created'"
+                      (click)="createCustomerServiceReminder(settlement, 'payment_follow_up')"
+                      matTooltip="创建客服跟进尾款提醒"
+                      class="reminder-btn">
+                      @if (getReminderStatus(settlement.projectId) === 'creating') {
+                        <mat-spinner diameter="16"></mat-spinner>
+                        <span>创建中</span>
+                      } @else if (getReminderStatus(settlement.projectId) === 'created') {
+                        <ng-container>
+                          <mat-icon>notifications_active</mat-icon>
+                          <span>已提醒</span>
+                        </ng-container>
+                      } @else {
+                        <ng-container>
+                          <mat-icon>notification_add</mat-icon>
+                          <span>跟进尾款</span>
+                        </ng-container>
+                      }
+                    </button>
+                  }
+                  
+                  <!-- 一键发图到企业微信群按钮 -->
+                  @if (canSendImages(settlement)) {
+                    <button 
+                      mat-raised-button
+                      color="primary"
+                      [disabled]="isSendingImagesForProject(settlement.projectId)"
+                      (click)="sendImagesToWeChatGroup(settlement)"
+                      matTooltip="收款后一键发送大图到企业微信群"
+                      class="wechat-send-btn">
+                      @if (isSendingImagesForProject(settlement.projectId)) {
+                        <mat-spinner diameter="16"></mat-spinner>
+                        <span>发送中</span>
+                      } @else {
+                        <ng-container>
+                          <mat-icon>send</mat-icon>
+                          <span>发图到群</span>
+                        </ng-container>
+                      }
+                    </button>
+                  }
+                  
+                  <!-- 原有的处理结算按钮 -->
+                  <button 
+                    mat-raised-button
+                    color="primary"
+                    [disabled]="settlement.status === '已结算'"
+                    (click)="processSettlement(settlement.id)"
+                    class="process-btn">
+                    @if (settlement.status === '待结算') {
+                      处理结算
+                    } @else if (settlement.status === '逾期') {
+                      逾期处理
+                    } @else {
+                      已完成
+                    }
+                  </button>
+                  
+                  @if (isOverdue(settlement)) {
+                    <button 
+                      mat-stroked-button
+                      color="warn"
+                      (click)="sendReminder(settlement.id)"
+                      class="reminder-btn">
+                      发送催款
+                    </button>
+                  }
+                </div>
+              </div>
+              
+              <div class="col-date">
+                @if (settlement.settledAt) {
+                  <div class="settled-date">{{ settlement.settledAt | date:'yyyy-MM-dd' }}</div>
+                  <div class="date-label">结算日期</div>
+                } @else {
+                  <div class="due-date">{{ settlement.createdAt | date:'yyyy-MM-dd' }}</div>
+                  <div class="date-label">创建日期</div>
+                }
+              </div>
+            </div>
+          }
+        </div>
+      } @else {
+        <div class="empty-state">
+          <div class="empty-icon">💰</div>
+          <div class="empty-title">暂无结算信息</div>
+          <div class="empty-desc">当前筛选条件下没有找到相关的结算记录</div>
+        </div>
+      }
+    </div>
   </div>
 </div>

+ 259 - 101
src/app/shared/components/settlement-card/settlement-card.scss

@@ -12,15 +12,25 @@
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
   border: 1px solid $ios-border;
 
-  // 统计数据概览
+  // 统计概览
   .stats-overview {
     margin-bottom: 24px;
 
-    h4 {
-      margin: 0 0 16px 0;
+    .stats-title {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      margin-bottom: 16px;
       font-size: 18px;
       font-weight: $ios-font-weight-semibold;
       color: $ios-text-primary;
+
+      mat-icon {
+        color: $ios-primary;
+        font-size: 20px;
+        width: 20px;
+        height: 20px;
+      }
     }
 
     .stats-grid {
@@ -44,20 +54,23 @@
         &.pending {
           background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
           color: #8b4513;
+          border: none;
         }
 
         &.overdue {
           background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
-          color: #dc3545;
+          color: #d63384;
+          border: none;
         }
 
         &.completed {
           background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
-          color: #28a745;
+          color: #198754;
+          border: none;
         }
 
         .stat-value {
-          font-size: 24px;
+          font-size: 20px;
           font-weight: $ios-font-weight-bold;
           margin-bottom: 4px;
         }
@@ -70,31 +83,39 @@
     }
   }
 
-  // 筛选和搜索区域
+  // 筛选区域
   .filter-section {
-    margin-bottom: 20px;
     display: flex;
-    flex-direction: column;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
     gap: 16px;
 
-    .search-bar {
-      width: 100%;
+    .search-box {
+      display: flex;
+      align-items: center;
+      background: #f8f9fa;
+      border-radius: 20px;
+      padding: 8px 16px;
+      flex: 1;
+      max-width: 300px;
+      border: 1px solid $ios-border;
+
+      mat-icon {
+        color: $ios-text-secondary;
+        margin-right: 8px;
+        font-size: 18px;
+        width: 18px;
+        height: 18px;
+      }
 
-      .search-input {
-        width: 100%;
-        padding: 10px 16px;
-        border: 1px solid $ios-border;
-        border-radius: 8px;
+      input {
+        border: none;
+        background: transparent;
+        outline: none;
+        flex: 1;
         font-size: 14px;
-        background: white;
-        transition: border-color 0.2s ease;
-        box-sizing: border-box;
-
-        &:focus {
-          outline: none;
-          border-color: $ios-primary;
-          box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
-        }
+        color: $ios-text-primary;
 
         &::placeholder {
           color: $ios-text-secondary;
@@ -105,23 +126,20 @@
     .filter-buttons {
       display: flex;
       gap: 8px;
-      flex-wrap: wrap;
-      width: 100%;
-      justify-content: flex-start;
 
       .filter-btn {
-        padding: 8px 16px;
+        padding: 6px 16px;
         border: 1px solid $ios-border;
-        border-radius: 20px;
         background: white;
-        color: $ios-text-secondary;
-        font-size: 14px;
+        border-radius: 16px;
+        font-size: 13px;
         cursor: pointer;
         transition: all 0.2s ease;
-        white-space: nowrap; // 防止按钮文字换行
+        color: $ios-text-secondary;
 
         &:hover {
           background: #f8f9fa;
+          border-color: $ios-primary;
         }
 
         &.active {
@@ -130,45 +148,46 @@
           border-color: $ios-primary;
         }
 
-        &.pending.active {
-          background: #ff9500;
-          border-color: #ff9500;
-        }
-
         &.overdue.active {
           background: #dc3545;
           border-color: #dc3545;
         }
-
-        &.completed.active {
-          background: #28a745;
-          border-color: #28a745;
-        }
       }
     }
   }
 
   // 结算列表
-  .settlements-list {
+  .settlement-list {
     .list-header {
       display: grid;
-      grid-template-columns: 2fr 1fr 1fr 1fr;
+      grid-template-columns: 2fr 1fr 1fr 2fr 1fr;
       gap: 16px;
       padding: 12px 16px;
       background: #f8f9fa;
       border-radius: 8px;
       font-weight: $ios-font-weight-semibold;
+      font-size: 13px;
       color: $ios-text-secondary;
-      font-size: 12px;
-      text-transform: uppercase;
-      letter-spacing: 0.5px;
       margin-bottom: 8px;
+
+      span {
+        text-align: left;
+
+        &.col-amount,
+        &.col-date {
+          text-align: center;
+        }
+
+        &.col-actions {
+          text-align: center;
+        }
+      }
     }
 
     .list-body {
       .settlement-item {
         display: grid;
-        grid-template-columns: 2fr 1fr 1fr 1fr;
+        grid-template-columns: 2fr 1fr 1fr 2fr 1fr;
         gap: 16px;
         padding: 16px;
         border: 1px solid $ios-border;
@@ -179,16 +198,11 @@
 
         &:hover {
           box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-          transform: translateY(-1px);
-        }
-
-        &.overdue {
-          border-left: 4px solid #dc3545;
-          background: #fff5f5;
+          border-color: $ios-primary;
         }
 
         &.pending {
-          border-left: 4px solid #ff9500;
+          border-left: 4px solid #ffc107;
         }
 
         &.completed {
@@ -196,9 +210,14 @@
           background: #f8fff9;
         }
 
+        &.overdue {
+          border-left: 4px solid #dc3545;
+          background: #fff8f8;
+        }
+
         .col-project {
           .project-name {
-            font-weight: $ios-font-weight-semibold;
+            font-weight: $ios-font-weight-medium;
             color: $ios-text-primary;
             margin-bottom: 4px;
           }
@@ -210,40 +229,44 @@
         }
 
         .col-amount {
+          text-align: center;
+
           .amount {
-            font-weight: $ios-font-weight-bold;
-            color: $ios-primary;
-            font-size: 16px;
+            font-weight: $ios-font-weight-semibold;
+            color: $ios-text-primary;
+            margin-bottom: 4px;
           }
 
           .percentage {
             font-size: 12px;
             color: $ios-text-secondary;
-            margin-top: 2px;
           }
         }
 
         .col-status {
+          text-align: center;
+          position: relative;
+
           .status-badge {
             display: inline-block;
-            padding: 4px 8px;
+            padding: 4px 12px;
             border-radius: 12px;
             font-size: 12px;
             font-weight: $ios-font-weight-medium;
 
             &.pending {
-              background: rgba(255, 149, 0, 0.1);
-              color: #ff9500;
+              background: #fff3cd;
+              color: #856404;
             }
 
-            &.overdue {
-              background: rgba(220, 53, 69, 0.1);
-              color: #dc3545;
+            &.completed {
+              background: #d1edff;
+              color: #0c5460;
             }
 
-            &.completed {
-              background: rgba(40, 167, 69, 0.1);
-              color: #28a745;
+            &.overdue {
+              background: #f8d7da;
+              color: #721c24;
             }
           }
 
@@ -251,51 +274,180 @@
             font-size: 11px;
             color: #dc3545;
             margin-top: 4px;
-            font-weight: $ios-font-weight-medium;
+          }
+
+          .auto-process-btn {
+            position: absolute;
+            top: -8px;
+            right: -8px;
+            width: 24px;
+            height: 24px;
+
+            mat-icon {
+              font-size: 16px;
+              width: 16px;
+              height: 16px;
+            }
+          }
+        }
+
+        // 新增操作列样式
+        .col-actions {
+          .action-buttons {
+            display: flex;
+            flex-direction: column;
+            gap: 8px;
+            align-items: center;
+
+            button {
+              min-width: 100px;
+              height: 32px;
+              font-size: 12px;
+              border-radius: 16px;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              gap: 4px;
+
+              mat-icon {
+                font-size: 16px;
+                width: 16px;
+                height: 16px;
+              }
+
+              mat-spinner {
+                margin-right: 4px;
+              }
+
+              &.acceptance-btn {
+                &[color="primary"] {
+                  background: #e3f2fd;
+                  color: #1976d2;
+                  border-color: #1976d2;
+                }
+              }
+
+              &.reminder-btn {
+                &[color="warn"] {
+                  background: #fff3e0;
+                  color: #f57c00;
+                  border-color: #f57c00;
+                }
+              }
+
+              &.wechat-send-btn {
+                background: linear-gradient(135deg, #07c160 0%, #00d4aa 100%);
+                color: white;
+                border: none;
+                font-weight: 500;
+
+                &:hover:not(:disabled) {
+                  background: linear-gradient(135deg, #06ad56 0%, #00c49a 100%);
+                }
+
+                &:disabled {
+                  opacity: 0.7;
+                }
+              }
+            }
           }
         }
 
         .col-date {
-          text-align: right;
+          text-align: center;
 
           .settled-date,
           .due-date {
             font-weight: $ios-font-weight-medium;
             color: $ios-text-primary;
+            margin-bottom: 4px;
           }
 
           .date-label {
-            font-size: 11px;
+            font-size: 12px;
             color: $ios-text-secondary;
-            margin-top: 2px;
           }
         }
       }
     }
-  }
 
-  // 空状态
-  .empty-state {
-    text-align: center;
-    padding: 40px 20px;
-    color: $ios-text-secondary;
+    .empty-state {
+      text-align: center;
+      padding: 60px 20px;
+      color: $ios-text-secondary;
+
+      .empty-icon {
+        font-size: 48px;
+        margin-bottom: 16px;
+      }
 
-    .empty-icon {
-      font-size: 48px;
-      margin-bottom: 16px;
-      opacity: 0.5;
+      .empty-title {
+        font-size: 18px;
+        font-weight: $ios-font-weight-medium;
+        color: $ios-text-primary;
+        margin-bottom: 8px;
+      }
+
+      .empty-desc {
+        font-size: 14px;
+      }
     }
+  }
+}
 
-    .empty-title {
-      font-size: 16px;
-      font-weight: $ios-font-weight-semibold;
-      color: $ios-text-primary;
-      margin-bottom: 8px;
+// 自动化控制头部样式
+.automation-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 20px;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  border-radius: 12px;
+  margin-bottom: 20px;
+  color: white;
+  
+  .automation-title {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    font-weight: 600;
+    font-size: 16px;
+    
+    mat-icon {
+      font-size: 20px;
+      width: 20px;
+      height: 20px;
     }
+  }
+  
+  .automation-actions {
+    display: flex;
+    gap: 12px;
+    align-items: center;
+    
+    button {
+      border-radius: 20px;
+      font-weight: 500;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      
+      background: rgba(255, 255, 255, 0.2);
+      backdrop-filter: blur(10px);
+      border: 1px solid rgba(255, 255, 255, 0.3);
+      color: white;
+      
+      &:hover:not(:disabled) {
+        background: rgba(255, 255, 255, 0.3);
+      }
+      
+      &:disabled {
+        opacity: 0.6;
+      }
 
-    .empty-desc {
-      font-size: 14px;
-      line-height: 1.4;
+      mat-spinner {
+        margin-right: 4px;
+      }
     }
   }
 }
@@ -308,27 +460,33 @@
     }
 
     .filter-section {
+      flex-direction: column;
+      align-items: stretch;
+      gap: 12px;
+
+      .search-box {
+        max-width: none;
+      }
+
       .filter-buttons {
         justify-content: center;
+        flex-wrap: wrap;
       }
     }
 
-    .settlements-list {
+    .settlement-list {
       .list-header {
         display: none;
       }
 
       .list-body .settlement-item {
         grid-template-columns: 1fr;
-        gap: 8px;
+        gap: 12px;
 
-        .col-project,
-        .col-amount,
-        .col-status,
-        .col-date {
-          display: flex;
-          justify-content: space-between;
-          align-items: center;
+        .col-actions .action-buttons {
+          flex-direction: row;
+          justify-content: center;
+          flex-wrap: wrap;
         }
       }
     }

+ 309 - 4
src/app/shared/components/settlement-card/settlement-card.ts

@@ -1,7 +1,14 @@
-import { Component, Input, computed, signal } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { Component, Input, computed, signal, OnInit } from '@angular/core';
+import { CommonModule, DatePipe } from '@angular/common';
 import { FormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { MatDialogModule, MatDialog } from '@angular/material/dialog';
+import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
 import { Settlement } from '../../../models/project.model';
+import { AutoSettlementService } from '../../../services/auto-settlement.service';
 
 export interface SettlementStats {
   totalAmount: number;
@@ -14,20 +21,318 @@ export interface SettlementStats {
   completedCount: number;
 }
 
+// 项目验收确认接口
+export interface ProjectAcceptance {
+  id: string;
+  projectId: string;
+  projectName: string;
+  technicianId: string;
+  technicianName: string;
+  acceptanceStatus: 'pending' | 'confirmed' | 'rejected';
+  acceptanceDate?: Date;
+  acceptanceNotes?: string;
+  customerNotified: boolean;
+}
+
+// 客服跟进提醒接口
+export interface CustomerServiceReminder {
+  id: string;
+  projectId: string;
+  projectName: string;
+  customerName: string;
+  reminderType: 'payment_follow_up' | 'acceptance_notification' | 'delivery_confirmation';
+  reminderStatus: 'pending' | 'in_progress' | 'completed';
+  assignedTo: string;
+  dueDate: Date;
+  notes?: string;
+  createdAt: Date;
+}
+
+// 企业微信群发图接口
+export interface WeChatImageDelivery {
+  id: string;
+  projectId: string;
+  groupId: string;
+  groupName: string;
+  imageUrls: string[];
+  deliveryStatus: 'pending' | 'sending' | 'completed' | 'failed';
+  deliveryDate?: Date;
+  errorMessage?: string;
+}
+
 @Component({
   selector: 'app-settlement-card',
   standalone: true,
-  imports: [CommonModule, FormsModule],
+  imports: [
+    CommonModule, 
+    DatePipe,
+    FormsModule,
+    MatButtonModule,
+    MatIconModule,
+    MatProgressSpinnerModule,
+    MatTooltipModule,
+    MatDialogModule,
+    MatSnackBarModule
+  ],
   templateUrl: './settlement-card.html',
   styleUrls: ['./settlement-card.scss']
 })
-export class SettlementCardComponent {
+export class SettlementCardComponent implements OnInit {
   @Input() settlements: Settlement[] = [];
+  @Input() showAutomationControls = false;
+  @Input() projectAcceptances: ProjectAcceptance[] = [];
+  @Input() customerServiceReminders: CustomerServiceReminder[] = [];
   
   // 筛选条件
   statusFilter = signal<string>('all');
   searchKeyword = signal<string>('');
   
+  // 自动化处理状态
+  isProcessing = signal(false);
+  processingSettlementId = signal<string | null>(null);
+  
+  // 新增功能状态
+  acceptanceStatus = signal<{[key: string]: 'pending' | 'confirming' | 'confirmed'}>({});
+  reminderStatus = signal<{[key: string]: 'pending' | 'creating' | 'created'}>({});
+  isSendingImages = signal(false);
+  sendingProjectId = signal<string | null>(null);
+
+  constructor(
+    private autoSettlementService: AutoSettlementService,
+    private dialog: MatDialog,
+    private snackBar: MatSnackBar
+  ) { }
+
+  ngOnInit() {
+    // 启动定时自动化处理
+    this.autoSettlementService.startScheduledProcessing();
+    
+    // 初始化项目验收状态
+    this.initializeAcceptanceStatus();
+    
+    // 初始化客服提醒状态
+    this.initializeReminderStatus();
+  }
+
+  // 初始化项目验收状态
+  private initializeAcceptanceStatus(): void {
+    const status: {[key: string]: 'pending' | 'confirming' | 'confirmed'} = {};
+    this.settlements.forEach(settlement => {
+      const acceptance = this.projectAcceptances.find(a => a.projectId === settlement.projectId);
+      if (acceptance) {
+        status[settlement.projectId] = acceptance.acceptanceStatus === 'confirmed' ? 'confirmed' : 'pending';
+      } else {
+        status[settlement.projectId] = 'pending';
+      }
+    });
+    this.acceptanceStatus.set(status);
+  }
+
+  // 初始化客服提醒状态
+  private initializeReminderStatus(): void {
+    const status: {[key: string]: 'pending' | 'creating' | 'created'} = {};
+    this.settlements.forEach(settlement => {
+      const reminder = this.customerServiceReminders.find(r => 
+        r.projectId === settlement.projectId && r.reminderType === 'payment_follow_up'
+      );
+      if (reminder) {
+        status[settlement.projectId] = reminder.reminderStatus === 'completed' ? 'created' : 'pending';
+      } else {
+        status[settlement.projectId] = 'pending';
+      }
+    });
+    this.reminderStatus.set(status);
+  }
+
+  // 技术确认项目验收
+  confirmProjectAcceptance(settlement: Settlement): void {
+    const currentStatus = this.acceptanceStatus();
+    currentStatus[settlement.projectId] = 'confirming';
+    this.acceptanceStatus.set({...currentStatus});
+
+    // 模拟API调用
+    setTimeout(() => {
+      const updatedStatus = this.acceptanceStatus();
+      updatedStatus[settlement.projectId] = 'confirmed';
+      this.acceptanceStatus.set({...updatedStatus});
+      
+      this.snackBar.open(`项目 ${settlement.projectName} 验收确认成功`, '关闭', {
+        duration: 3000,
+        horizontalPosition: 'center',
+        verticalPosition: 'top'
+      });
+
+      // 自动创建客服跟进提醒
+      this.createCustomerServiceReminder(settlement, 'acceptance_notification');
+    }, 1500);
+  }
+
+  // 创建客服跟进提醒
+  createCustomerServiceReminder(settlement: Settlement, type: 'payment_follow_up' | 'acceptance_notification' | 'delivery_confirmation'): void {
+    const currentStatus = this.reminderStatus();
+    currentStatus[settlement.projectId] = 'creating';
+    this.reminderStatus.set({...currentStatus});
+
+    // 模拟API调用创建提醒
+    setTimeout(() => {
+      const updatedStatus = this.reminderStatus();
+      updatedStatus[settlement.projectId] = 'created';
+      this.reminderStatus.set({...updatedStatus});
+      
+      let message = '';
+      switch(type) {
+        case 'payment_follow_up':
+          message = `已创建尾款跟进提醒`;
+          break;
+        case 'acceptance_notification':
+          message = `已创建验收通知提醒`;
+          break;
+        case 'delivery_confirmation':
+          message = `已创建交付确认提醒`;
+          break;
+      }
+      
+      this.snackBar.open(`${settlement.projectName} ${message}`, '关闭', {
+        duration: 3000,
+        horizontalPosition: 'center',
+        verticalPosition: 'top'
+      });
+    }, 1000);
+  }
+
+  // 一键发图到企业微信群
+  sendImagesToWeChatGroup(settlement: Settlement): void {
+    this.isSendingImages.set(true);
+    this.sendingProjectId.set(settlement.projectId);
+
+    // 模拟获取项目大图和发送到企业微信群
+    setTimeout(() => {
+      this.isSendingImages.set(false);
+      this.sendingProjectId.set(null);
+      
+      this.snackBar.open(`项目 ${settlement.projectName} 大图已发送到企业微信群`, '关闭', {
+        duration: 4000,
+        horizontalPosition: 'center',
+        verticalPosition: 'top'
+      });
+
+      // 自动创建交付确认提醒
+      this.createCustomerServiceReminder(settlement, 'delivery_confirmation');
+    }, 3000);
+  }
+
+  // 获取项目验收状态
+  getAcceptanceStatus(projectId: string): 'pending' | 'confirming' | 'confirmed' {
+    return this.acceptanceStatus()[projectId] || 'pending';
+  }
+
+  // 获取客服提醒状态
+  getReminderStatus(projectId: string): 'pending' | 'creating' | 'created' {
+    return this.reminderStatus()[projectId] || 'pending';
+  }
+
+  // 检查是否可以发送图片
+  canSendImages(settlement: Settlement): boolean {
+    return settlement.status === '已结算' && this.getAcceptanceStatus(settlement.projectId) === 'confirmed';
+  }
+
+  // 检查是否正在发送图片
+  isSendingImagesForProject(projectId: string): boolean {
+    return this.isSendingImages() && this.sendingProjectId() === projectId;
+  }
+
+  // 处理单个结算
+  processSettlement(settlementId: string): void {
+    const settlement = this.settlements.find(s => s.id === settlementId);
+    if (settlement) {
+      this.processSettlementAutomation(settlement);
+    }
+  }
+
+  // 发送提醒
+  sendReminder(settlementId: string): void {
+    const settlement = this.settlements.find(s => s.id === settlementId);
+    if (settlement) {
+      this.createCustomerServiceReminder(settlement, 'payment_follow_up');
+    }
+  }
+
+  // 处理单个结算自动化
+  processSettlementAutomation(settlement: Settlement): void {
+    this.processingSettlementId.set(settlement.id);
+    this.isProcessing.set(true);
+    
+    this.autoSettlementService.processSettlementAutomation(settlement).subscribe({
+      next: (processed) => {
+        if (processed) {
+          console.log(`结算 ${settlement.id} 已自动处理`);
+        }
+        this.processingSettlementId.set(null);
+        this.isProcessing.set(false);
+      },
+      error: (error) => {
+        console.error('自动化处理失败:', error);
+        this.processingSettlementId.set(null);
+        this.isProcessing.set(false);
+      }
+    });
+  }
+
+  // 项目验收确认相关方法
+  isProjectAccepted(projectId: string): boolean {
+    return this.projectAcceptances.some(acceptance => 
+      acceptance.projectId === projectId && acceptance.acceptanceStatus === 'confirmed'
+    );
+  }
+
+  // 客服跟进提醒相关方法
+  hasCustomerServiceReminder(projectId: string): boolean {
+    return this.customerServiceReminders.some(reminder => 
+      reminder.projectId === projectId && reminder.reminderStatus === 'completed'
+    );
+  }
+
+  // 企业微信发图相关方法
+  canSendToWeChat(projectId: string): boolean {
+    return this.isProjectAccepted(projectId) && !this.isSendingImagesForProject(projectId);
+  }
+
+  // 批量处理自动化
+  processAllAutomation(): void {
+    const pendingSettlements = this.settlements.filter(
+      s => s.status === '待结算' && !this.isOverdue(s)
+    );
+    
+    this.isProcessing.set(true);
+    
+    // 模拟批量处理
+    let processedCount = 0;
+    const processNext = () => {
+      if (processedCount >= pendingSettlements.length) {
+        this.isProcessing.set(false);
+        return;
+      }
+      
+      const settlement = pendingSettlements[processedCount];
+      this.processingSettlementId.set(settlement.id);
+      
+      this.autoSettlementService.processSettlementAutomation(settlement).subscribe({
+        next: () => {
+          processedCount++;
+          this.processingSettlementId.set(null);
+          setTimeout(processNext, 500); // 添加延迟以避免同时处理过多
+        },
+        error: () => {
+          processedCount++;
+          this.processingSettlementId.set(null);
+          setTimeout(processNext, 500);
+        }
+      });
+    };
+    
+    processNext();
+  }
+  
   // 计算统计数据
   stats = computed<SettlementStats>(() => {
     const settlements = this.settlements || [];

+ 24 - 0
src/app/shared/components/star-rating/star-rating.component.html

@@ -0,0 +1,24 @@
+<div class="star-rating" [class.readonly]="readonly" [class.size-small]="size === 'small'" [class.size-medium]="size === 'medium'" [class.size-large]="size === 'large'">
+  <div class="stars-container">
+    <span 
+      *ngFor="let star of stars; let i = index"
+      class="star"
+      [class.filled]="star.filled"
+      [class.hovered]="star.hovered"
+      (click)="setRating(star.index)"
+      (mouseenter)="setHoverRating(star.index)"
+      (mouseleave)="clearHover()"
+      [title]="star.index + '星'"
+    >
+      ★
+    </span>
+  </div>
+  
+  <div class="rating-text" *ngIf="showText && rating > 0">
+    {{ rating }}星{{ getRatingText() ? ' - ' + getRatingText() : '' }}
+  </div>
+  
+  <div class="rating-text" *ngIf="showText && rating === 0 && !readonly">
+    请评分
+  </div>
+</div>

+ 133 - 0
src/app/shared/components/star-rating/star-rating.component.scss

@@ -0,0 +1,133 @@
+.star-rating {
+  display: inline-flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+
+  &.readonly {
+    .star {
+      cursor: default;
+      
+      &:hover {
+        transform: none;
+      }
+    }
+  }
+
+  // 尺寸变体
+  &.size-small {
+    .star {
+      font-size: 18px;
+      width: 24px;
+      height: 24px;
+    }
+    
+    .rating-text {
+      font-size: 12px;
+    }
+  }
+
+  &.size-medium {
+    .star {
+      font-size: 24px;
+      width: 32px;
+      height: 32px;
+    }
+    
+    .rating-text {
+      font-size: 14px;
+    }
+  }
+
+  &.size-large {
+    .star {
+      font-size: 32px;
+      width: 40px;
+      height: 40px;
+    }
+    
+    .rating-text {
+      font-size: 16px;
+    }
+  }
+
+  .stars-container {
+    display: flex;
+    gap: 4px;
+  }
+
+  .star {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    color: #e8e8e8;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    user-select: none;
+    border-radius: 3px;
+    
+    &:hover {
+      transform: scale(1.1);
+      color: #ffc53d !important;
+    }
+
+    &.filled {
+      color: #ffc53d;
+    }
+
+    &.hovered {
+      color: #ffc53d;
+      opacity: 0.7;
+    }
+  }
+
+  .rating-text {
+    color: #595959;
+    font-weight: 500;
+    text-align: center;
+    min-height: 20px;
+  }
+}
+
+// 暗色主题支持
+@media (prefers-color-scheme: dark) {
+  .star-rating {
+    .star {
+      color: #434343;
+      
+      &.filled {
+        color: #ffc53d;
+      }
+      
+      &.hovered {
+        color: #ffc53d;
+        opacity: 0.7;
+      }
+    }
+    
+    .rating-text {
+      color: #bfbfbf;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 480px) {
+  .star-rating {
+    &.size-medium {
+      .star {
+        font-size: 20px;
+        width: 28px;
+        height: 28px;
+      }
+    }
+    
+    &.size-large {
+      .star {
+        font-size: 24px;
+        width: 32px;
+        height: 32px;
+      }
+    }
+  }
+}

+ 104 - 0
src/app/shared/components/star-rating/star-rating.component.ts

@@ -0,0 +1,104 @@
+import { Component, Input, Output, EventEmitter, forwardRef } from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+import { CommonModule } from '@angular/common';
+
+@Component({
+  selector: 'app-star-rating',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './star-rating.component.html',
+  styleUrls: ['./star-rating.component.scss'],
+  providers: [
+    {
+      provide: NG_VALUE_ACCESSOR,
+      useExisting: forwardRef(() => StarRatingComponent),
+      multi: true
+    }
+  ]
+})
+export class StarRatingComponent implements ControlValueAccessor {
+  @Input() rating: number = 0;
+  @Input() maxStars: number = 5;
+  @Input() size: 'small' | 'medium' | 'large' = 'medium';
+  @Input() readonly: boolean = false;
+  @Input() showText: boolean = true;
+  
+  @Output() ratingChange = new EventEmitter<number>();
+
+  stars: { index: number; filled: boolean; hovered: boolean; }[] = [];
+  hoverRating: number = 0;
+  
+  private onChange: (value: number) => void = () => {};
+  private onTouched: () => void = () => {};
+
+  ngOnInit() {
+    this.updateStars();
+  }
+
+  ngOnChanges() {
+    this.updateStars();
+  }
+
+  updateStars() {
+    this.stars = Array.from({ length: this.maxStars }, (_, index) => ({
+      index: index + 1,
+      filled: index < this.rating,
+      hovered: index < this.hoverRating
+    }));
+  }
+
+  setRating(rating: number) {
+    if (this.readonly) return;
+    
+    this.rating = rating;
+    this.hoverRating = 0;
+    this.updateStars();
+    this.ratingChange.emit(rating);
+    this.onChange(rating);
+    this.onTouched();
+  }
+
+  setHoverRating(rating: number) {
+    if (this.readonly) return;
+    
+    this.hoverRating = rating;
+    this.updateStars();
+  }
+
+  clearHover() {
+    if (this.readonly) return;
+    
+    this.hoverRating = 0;
+    this.updateStars();
+  }
+
+  getRatingText(): string {
+    const texts = [
+      '非常差',
+      '差',
+      '一般',
+      '好',
+      '非常好'
+    ];
+    
+    return texts[Math.floor(this.rating) - 1] || '';
+  }
+
+  // ControlValueAccessor implementation
+  writeValue(value: number): void {
+    this.rating = value || 0;
+    this.updateStars();
+  }
+
+  registerOnChange(fn: (value: number) => void): void {
+    this.onChange = fn;
+  }
+
+  registerOnTouched(fn: () => void): void {
+    this.onTouched = fn;
+  }
+
+  setDisabledState?(isDisabled: boolean): void {
+    this.readonly = isDisabled;
+  }
+}

+ 1 - 2
src/index.html

@@ -6,8 +6,7 @@
     <base href="/">
     <meta name="viewport" content="width=device-width, initial-scale=1">
     <link rel="icon" type="image/x-icon" href="favicon.ico">
-    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
-    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+    <!-- 使用系统字体替代Google Fonts以避免网络错误 -->
     <!-- Local import map to resolve bare specifier 'echarts' to local ESM file -->
     <script type="importmap">
       {

+ 8 - 3
src/styles.scss

@@ -38,14 +38,19 @@ body {
 
   // Reset the user agent margin.
   margin: 0;
+  
+  // 使用系统字体栈替代Google Fonts
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
 }
-/* 从本地npm包引入Roboto字体,避免网络请求 */
-@import 'roboto-fontface/css/roboto/roboto-fontface.css';
+/* 使用系统字体栈替代Google Fonts,避免网络请求错误 */
 
 /* You can add global styles to this file, and also import other style files */
 
 html, body { height: 100%; }
-body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
+body { 
+  margin: 0; 
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
 
 
 /* HR Dialog Global Styles - 重新设计解决背景色重合问题 */

+ 3 - 0
temp.txt

@@ -0,0 +1,3 @@
+"}"
+"} @endfor"
+"}"