소스 검색

feet:designer-demo03

0235711 1 일 전
부모
커밋
c723dc173d

+ 32 - 0
package-lock.json

@@ -14,6 +14,7 @@
         "@angular/forms": "^20.1.0",
         "@angular/platform-browser": "^20.1.0",
         "@angular/router": "^20.1.0",
+        "echarts": "^6.0.0",
         "rxjs": "~7.8.0",
         "tslib": "^2.3.0",
         "zone.js": "~0.15.0"
@@ -4863,6 +4864,22 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/echarts": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
+      "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "2.3.0",
+        "zrender": "6.0.0"
+      }
+    },
+    "node_modules/echarts/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
+      "license": "0BSD"
+    },
     "node_modules/ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -9965,6 +9982,21 @@
       "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz",
       "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==",
       "license": "MIT"
+    },
+    "node_modules/zrender": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz",
+      "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "tslib": "2.3.0"
+      }
+    },
+    "node_modules/zrender/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
+      "license": "0BSD"
     }
   }
 }

+ 1 - 0
package.json

@@ -26,6 +26,7 @@
     "@angular/forms": "^20.1.0",
     "@angular/platform-browser": "^20.1.0",
     "@angular/router": "^20.1.0",
+    "echarts": "^6.0.0",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",
     "zone.js": "~0.15.0"

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

@@ -71,6 +71,7 @@ export interface CustomerFeedback {
   status: '待处理' | '处理中' | '已解决';
   createdAt: Date;
   updatedAt?: Date;
+  tag?: string;
 }
 
 // 设计师变更记录
@@ -98,6 +99,7 @@ export interface Settlement {
   createdAt: Date;
   settledAt?: Date;
   completionTime?: Date;
+  projectName?: string;
 }
 
 // 技能标签

+ 60 - 5
src/app/pages/designer/dashboard/dashboard.html

@@ -4,6 +4,27 @@
   </header>
 
   <main class="dashboard-main">
+    <!-- 紧急任务区域 -->
+    <section class="urgent-section" *ngIf="urgentTasks.length > 0">
+      <div class="section-header">
+        <h2>紧急任务</h2>
+      </div>
+      
+      <div class="urgent-list">
+        <div *ngFor="let task of urgentTasks" class="urgent-item">
+          <div class="urgent-content">
+            <p class="urgent-title">⚠️ {{ task.title }} - 渲染即将超期</p>
+            <p class="urgent-detail">项目: {{ task.projectName }},剩余时间: {{ getTaskCountdown(task.id) }}</p>
+          </div>
+          <div class="urgent-actions">
+            <button [routerLink]="['/designer/project-detail', task.projectId]" class="btn-urgent-detail">
+              立即处理
+            </button>
+          </div>
+        </div>
+      </div>
+    </section>
+
     <!-- 待办任务区域 -->
     <section class="task-section">
       <div class="section-header">
@@ -25,7 +46,19 @@
             <p class="deadline" [class.overdue]="task.isOverdue">
               截止日期: {{ task.deadline | date:'yyyy-MM-dd HH:mm' }}
               <span *ngIf="task.isOverdue" class="overdue-badge">已超期</span>
+              <span *ngIf="task.stage === '渲染' && !task.isOverdue" class="countdown-badge">
+                剩余: {{ getTaskCountdown(task.id) }}
+              </span>
             </p>
+            
+            <!-- 进度条 -->
+            <div class="task-progress" *ngIf="task.stage !== '完成'">
+              <div class="progress-bar">
+                <div class="progress-fill" [style.width]="getTaskStageProgress(task.id) + '%'">
+                </div>
+              </div>
+              <p class="progress-text">{{ getTaskStageProgress(task.id) }}%</p>
+            </div>
           </div>
           <div class="task-actions">
             <button *ngIf="!task.isCompleted" (click)="markTaskAsCompleted(task.id)" class="btn-complete">
@@ -39,8 +72,31 @@
       </div>
     </section>
 
+    <!-- 待处理反馈区域 -->
+    <section class="feedback-section" *ngIf="pendingFeedbacks.length > 0">
+      <div class="section-header">
+        <h2>待处理反馈</h2>
+      </div>
+      
+      <div class="feedback-list">
+        <div *ngFor="let item of pendingFeedbacks" class="feedback-item">
+          <div class="feedback-content">
+            <p class="feedback-title">⚠️ {{ item.task.title }} - 有客户反馈需处理</p>
+            <p class="feedback-project">项目: {{ item.task.projectName }}</p>
+            <p class="feedback-summary">反馈类型: {{ !item.feedback.isSatisfied ? '不满意' : '满意' }}</p>
+            <p class="feedback-time">反馈时间: {{ item.feedback.createdAt | date:'yyyy-MM-dd HH:mm' }}</p>
+          </div>
+          <div class="feedback-actions">
+            <button (click)="handleFeedback(item.task.id)" class="btn-handle-feedback">
+              处理反馈
+            </button>
+          </div>
+        </div>
+      </div>
+    </section>
+
     <!-- 时间预警区域 -->
-    <section class="warning-section">
+    <section class="warning-section" *ngIf="overdueTasks.length > 0">
       <div class="section-header">
         <h2>时间预警</h2>
       </div>
@@ -71,15 +127,14 @@
       </div>
       
       <div class="quick-access-grid">
-        <a [routerLink]="['/designer/project-detail', tasks[0].projectId]" class="quick-access-item">
+        <a [routerLink]="['/designer/project-detail', tasks[0].projectId || '']" [ngClass]="{ 'quick-access-item': true, 'has-items': tasks.length > 0 }" *ngIf="tasks.length > 0">
           <h3>待确认项目</h3>
           <p>{{ tasks.length }}个项目待处理</p>
         </a>
         
-        
-        <a [routerLink]="['/designer/project-detail', feedbackProjectId]" class="quick-access-item">
+        <a [routerLink]="['/designer/project-detail', feedbackProjectId]" [ngClass]="{ 'quick-access-item': true, 'has-items': pendingFeedbacks.length > 0 }" *ngIf="pendingFeedbacks.length > 0">
           <h3>待反馈项目</h3>
-          <p>{{ overdueTasks.length }}个项目需反馈</p>
+          <p>{{ pendingFeedbacks.length }}个项目需反馈</p>
         </a>
         
         <a [routerLink]="['/designer/personal-board']" class="quick-access-item">

+ 187 - 6
src/app/pages/designer/dashboard/dashboard.scss

@@ -1,3 +1,5 @@
+@use 'sass:color';
+
 // 导入iOS主题变量
 @import '../ios-theme.scss';
 
@@ -24,7 +26,11 @@
   grid-template-columns: 1fr 1fr;
   gap: 24px;
   
-  .task-section {
+  .task-section,
+  .urgent-section,
+  .feedback-section,
+  .warning-section,
+  .quick-access-section {
     grid-column: 1 / -1;
   }
 }
@@ -118,6 +124,42 @@
         margin-left: 8px;
         border: 1px solid $ios-danger;
       }
+      
+      .countdown-badge {
+        background-color: color-mix(in srgb, $ios-warning 15%, transparent);
+        color: $ios-warning;
+        padding: 4px 10px;
+        border-radius: $ios-radius-full;
+        font-size: 12px;
+        margin-left: 8px;
+        border: 1px solid $ios-warning;
+      }
+    }
+    
+    .task-progress {
+      margin-top: 12px;
+      
+      .progress-bar {
+        height: 8px;
+        background-color: $ios-background-secondary;
+        border-radius: $ios-radius-full;
+        overflow: hidden;
+        border: 1px solid $ios-border;
+        
+        .progress-fill {
+          height: 100%;
+          background-color: $ios-primary;
+          transition: width 0.3s ease;
+          border-radius: $ios-radius-full;
+        }
+      }
+      
+      .progress-text {
+        font-size: 13px;
+        color: $ios-text-secondary;
+        margin: 6px 0 0 0;
+        text-align: right;
+      }
     }
   }
   
@@ -141,7 +183,7 @@
       color: white;
       
       &:hover {
-        background-color: darken($ios-success, 5%);
+        background-color: color.adjust($ios-success, $lightness: -5%);
         transform: translateY(-1px);
         box-shadow: $ios-shadow-md;
       }
@@ -168,6 +210,129 @@
   }
 }
 
+.urgent-section {
+  grid-column: 1 / -1;
+}
+
+.urgent-list {
+  display: grid;
+  gap: 16px;
+}
+
+.urgent-item {
+  background: color-mix(in srgb, $ios-danger 10%, transparent);
+  border: 1px solid $ios-danger;
+  border-radius: $ios-radius-lg;
+  padding: 16px 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  
+  .urgent-content {
+    .urgent-title {
+      font-size: 17px;
+      color: $ios-danger;
+      font-weight: $ios-font-weight-medium;
+      margin: 0 0 6px 0;
+      font-family: $ios-font-family;
+    }
+    
+    .urgent-detail {
+      font-size: 15px;
+      color: $ios-text-secondary;
+      margin: 0;
+    }
+  }
+  
+  .urgent-actions {
+    .btn-urgent-detail {
+      padding: 10px 18px;
+      border: none;
+      border-radius: $ios-radius-md;
+      background-color: $ios-danger;
+      color: white;
+      font-size: 15px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      font-weight: $ios-font-weight-medium;
+      font-family: $ios-font-family;
+      
+      &:hover {
+        background-color: color-mix(in srgb, $ios-danger 90%, black);
+        transform: translateY(-1px);
+        box-shadow: $ios-shadow-md;
+      }
+      
+      &:active {
+        transform: translateY(0);
+      }
+    }
+  }
+}
+
+.feedback-section {
+  grid-column: 1 / -1;
+}
+
+.feedback-list {
+  display: grid;
+  gap: 16px;
+}
+
+.feedback-item {
+  background: color-mix(in srgb, $ios-warning 10%, transparent);
+  border: 1px solid $ios-warning;
+  border-radius: $ios-radius-lg;
+  padding: 16px 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  
+  .feedback-content {
+    .feedback-title {
+      font-size: 17px;
+      color: $ios-warning;
+      font-weight: $ios-font-weight-medium;
+      margin: 0 0 6px 0;
+      font-family: $ios-font-family;
+    }
+    
+    .feedback-project,
+    .feedback-summary,
+    .feedback-time {
+      font-size: 14px;
+      color: $ios-text-secondary;
+      margin: 4px 0;
+    }
+  }
+  
+  .feedback-actions {
+    .btn-handle-feedback {
+      padding: 10px 18px;
+      border: 1px solid $ios-warning;
+      border-radius: $ios-radius-md;
+      background-color: $ios-card-background;
+      color: $ios-warning;
+      font-size: 15px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      font-weight: $ios-font-weight-medium;
+      font-family: $ios-font-family;
+      
+      &:hover {
+        background-color: $ios-warning;
+        color: white;
+        transform: translateY(-1px);
+        box-shadow: $ios-shadow-md;
+      }
+      
+      &:active {
+        transform: translateY(0);
+      }
+    }
+  }
+}
+
 .warning-section {
   grid-column: 1 / -1;
 }
@@ -249,12 +414,20 @@
   text-decoration: none;
   transition: all 0.3s ease;
   border: 1px solid $ios-border;
+  min-height: 120px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
   
   &:hover {
     box-shadow: $ios-shadow-md;
     transform: translateY(-2px);
   }
   
+  &.has-items {
+    border-left: 4px solid $ios-primary;
+  }
+  
   h3 {
     font-size: 20px;
     color: $ios-text-primary;
@@ -329,7 +502,7 @@
     font-family: $ios-font-family;
     
     &:hover {
-      background-color: darken($ios-primary, 5%);
+      background-color: color.adjust($ios-primary, $lightness: -5%);
       transform: translateY(-1px);
       box-shadow: $ios-shadow-md;
     }
@@ -353,16 +526,24 @@
     }
   }
   
-  .warning-item {
+  .urgent-item,
+  .warning-item,
+  .feedback-item {
     flex-direction: column;
     gap: 15px;
     
-    .warning-actions {
+    .urgent-actions,
+    .warning-actions,
+    .feedback-actions {
       width: 100%;
       
-      .btn-generate-reminder {
+      button {
         width: 100%;
       }
     }
   }
+  
+  .quick-access-grid {
+    grid-template-columns: 1fr;
+  }
 }

+ 128 - 4
src/app/pages/designer/dashboard/dashboard.ts

@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { RouterModule } from '@angular/router';
 import { ProjectService } from '../../../services/project.service';
-import { Task } from '../../../models/project.model';
+import { Task, RenderProgress, CustomerFeedback } from '../../../models/project.model';
 
 @Component({
   selector: 'app-dashboard',
@@ -13,8 +13,11 @@ import { Task } from '../../../models/project.model';
 export class Dashboard implements OnInit {
   tasks: Task[] = [];
   overdueTasks: Task[] = [];
+  urgentTasks: Task[] = [];
+  pendingFeedbacks: {task: Task, feedback: CustomerFeedback}[] = [];
   reminderMessage: string = '';
   feedbackProjectId: string = '';
+  countdowns: Map<string, string> = new Map();
 
   constructor(private projectService: ProjectService) {}
 
@@ -24,13 +27,13 @@ export class Dashboard implements OnInit {
 
   loadTasks(): void {
     this.projectService.getTasks().subscribe(tasks => {
+      // 按阶段优先级排序:建模 > 渲染 > 对图 > 反馈处理 > 后期 > 其他
       this.tasks = tasks.sort((a, b) => {
-        // 按阶段优先级排序:建模 > 对图 > 反馈处理 > 其他
         const stagePriority: Record<string, number> = {
-          '建模': 4,
+          '建模': 5,
+          '渲染': 4,
           '对图': 3,
           '反馈处理': 2,
-          '渲染': 1,
           '后期': 1,
           '完成': 0
         };
@@ -49,18 +52,139 @@ export class Dashboard implements OnInit {
       // 筛选超期任务
       this.overdueTasks = this.tasks.filter(task => task.isOverdue);
       
+      // 筛选紧急任务(渲染超时预警,交付前3小时/1小时)
+      this.urgentTasks = this.tasks.filter(task => {
+        const now = new Date();
+        const diffHours = (task.deadline.getTime() - now.getTime()) / (1000 * 60 * 60);
+        return diffHours <= 3 && diffHours > 0 && task.stage === '渲染';
+      });
+      
       // 设置反馈项目ID
       if (this.overdueTasks.length > 0) {
         this.feedbackProjectId = this.overdueTasks[0].projectId;
       }
+      
+      // 加载待处理反馈
+      this.loadPendingFeedbacks();
+      
+      // 启动倒计时
+      this.startCountdowns();
+    });
+  }
+  
+  loadPendingFeedbacks(): void {
+    this.pendingFeedbacks = [];
+    
+    // 模拟加载待处理反馈数据
+    this.tasks.forEach(task => {
+      // 使用模拟数据代替API调用
+      const mockFeedbacks = [
+        {
+          id: 'fb-' + task.id,
+          projectId: task.projectId,
+          content: '客户对色彩不满意,需要调整',
+          isSatisfied: false,
+          problemLocation: '色彩',
+          expectedEffect: '更明亮的色调',
+          referenceCase: '无',
+          status: '待处理' as const,
+          createdAt: new Date(Date.now() - 30 * 60 * 1000) // 30分钟前
+        },
+        {
+          id: 'fb-' + task.id + '-2',
+          projectId: task.projectId,
+          content: '家具款式需要调整',
+          isSatisfied: false,
+          problemLocation: '家具',
+          expectedEffect: '更现代的款式',
+          referenceCase: '无',
+          status: '待处理' as const,
+          createdAt: new Date(Date.now() - 45 * 60 * 1000) // 45分钟前
+        }
+      ];
+      
+      const pending = mockFeedbacks.filter(feedback => 
+        feedback.status === '待处理' && 
+        !feedback.isSatisfied
+      );
+      
+      if (pending.length > 0) {
+        this.pendingFeedbacks.push({task, feedback: pending[0]});
+      }
     });
   }
+  
+  startCountdowns(): void {
+    // 清除之前的定时器
+    this.countdowns.clear();
+    
+    // 为渲染任务启动倒计时
+    this.tasks.forEach(task => {
+      if (task.stage === '渲染') {
+        this.updateCountdown(task.id, task.deadline);
+      }
+    });
+    
+    // 定期更新倒计时
+    setInterval(() => {
+      this.tasks.forEach(task => {
+        if (task.stage === '渲染') {
+          this.updateCountdown(task.id, task.deadline);
+        }
+      });
+    }, 60000); // 每分钟更新一次
+  }
+  
+  updateCountdown(taskId: string, deadline: Date): void {
+    const now = new Date();
+    const diffMs = deadline.getTime() - now.getTime();
+    
+    if (diffMs <= 0) {
+      this.countdowns.set(taskId, '已超期');
+      return;
+    }
+    
+    const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
+    const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
+    
+    if (diffHours > 0) {
+      this.countdowns.set(taskId, `${diffHours}小时${diffMinutes}分钟`);
+    } else {
+      this.countdowns.set(taskId, `${diffMinutes}分钟`);
+    }
+  }
+  
+  getTaskCountdown(taskId: string): string {
+    return this.countdowns.get(taskId) || '';
+  }
+  
+  getTaskStageProgress(taskId: string): number {
+    // 在实际应用中,这里会从服务中获取真实的进度
+    const stageProgress: Record<string, number> = {
+      '建模': Math.floor(Math.random() * 100),
+      '渲染': Math.floor(Math.random() * 100),
+      '对图': Math.floor(Math.random() * 100),
+      '反馈处理': Math.floor(Math.random() * 100),
+      '后期': Math.floor(Math.random() * 100)
+    };
+    
+    const task = this.tasks.find(t => t.id === taskId);
+    return task ? (stageProgress[task.stage] || 0) : 0;
+  }
 
   markTaskAsCompleted(taskId: string): void {
     this.projectService.markTaskAsCompleted(taskId).subscribe(() => {
       this.loadTasks(); // 重新加载任务列表
     });
   }
+  
+  handleFeedback(taskId: string): void {
+    const task = this.tasks.find(t => t.id === taskId);
+    if (task) {
+      // 跳转到项目详情的反馈处理页面
+      window.location.href = `/designer/project-detail/${task.projectId}#feedback`;
+    }
+  }
 
   generateReminderMessage(): void {
     this.projectService.generateReminderMessage('overdue').subscribe(message => {

+ 1 - 0
src/app/pages/designer/ios-theme.scss

@@ -5,6 +5,7 @@ $ios-primary: #0047AB; // 克莱茵蓝 - iOS风格的蓝色
 $ios-primary-light: #4D91F7; // 较浅的蓝色
 $ios-background: #FFFFFF; // 白色背景
 $ios-background-secondary: #F2F2F7; // 浅灰色背景
+$ios-background-tertiary: #E5E5EA; // 三级背景色 - 浅灰色
 $ios-card-background: #FFFFFF; // 卡片背景色
 $ios-text-primary: #000000; // 主要文本色
 $ios-text-secondary: #8E8E93; // 次要文本色

+ 38 - 50
src/app/pages/designer/personal-board/personal-board.html

@@ -66,11 +66,13 @@
       <div class="settlement-summary">
         <div class="summary-item">
           <h3>总结算金额</h3>
-          <p class="amount total">{{ totalSettlementAmount | currency:'CNY':'symbol':'1.0-0' }}</p>
+          <p class="amount total" *ngIf="!isJuniorDesigner">{{ totalSettlementAmount | currency:'CNY':'symbol':'1.0-0' }}</p>
+          <p class="amount total" *ngIf="isJuniorDesigner">--</p>
         </div>
         <div class="summary-item">
           <h3>待结算金额</h3>
-          <p class="amount pending">{{ pendingSettlementAmount | currency:'CNY':'symbol':'1.0-0' }}</p>
+          <p class="amount pending" *ngIf="!isJuniorDesigner">{{ pendingSettlementAmount | currency:'CNY':'symbol':'1.0-0' }}</p>
+          <p class="amount pending" *ngIf="isJuniorDesigner">--</p>
         </div>
         <button (click)="viewSettlementDetails()" class="btn-view-details">
           查看明细
@@ -82,22 +84,26 @@
         <table class="settlement-table">
           <thead>
             <tr>
-              <th>阶段</th>
-              <th>金额</th>
+              <th>项目名称</th>
+              <th>结算阶段</th>
               <th>比例</th>
-              <th>状态</th>
+              <th *ngIf="!isJuniorDesigner">金额</th>
+              <th *ngIf="isJuniorDesigner">状态</th>
+              <th *ngIf="!isJuniorDesigner">状态</th>
               <th>创建日期</th>
             </tr>
           </thead>
           <tbody>
             <tr *ngIf="settlements.length === 0">
-              <td colspan="5" class="empty-row">暂无结算记录</td>
+              <td colspan="6" class="empty-row">暂无结算记录</td>
             </tr>
             <tr *ngFor="let settlement of settlements">
+              <td>{{ '项目' + settlement?.projectId }}</td>
               <td>{{ settlement.stage }}</td>
-              <td>{{ settlement.amount | currency:'CNY':'symbol':'1.0-0' }}</td>
               <td>{{ settlement.percentage }}%</td>
-              <td>
+              <td *ngIf="!isJuniorDesigner">{{ settlement.amount | currency:'CNY':'symbol':'1.0-0' }}</td>
+              <td *ngIf="isJuniorDesigner">{{ settlement.status }}</td>
+              <td *ngIf="!isJuniorDesigner">
                 <span class="status-badge" [class.settled]="settlement.status === '已结算'">
                   {{ settlement.status }}
                 </span>
@@ -107,6 +113,29 @@
           </tbody>
         </table>
       </div>
+      
+      <!-- 结算比例说明 -->
+      <div class="settlement-rules">
+        <h3>结算比例规则</h3>
+        <div class="rule-item">
+          <span class="stage-name">建模</span>
+          <div class="stage-bar">
+            <div class="stage-fill" style="width: 30%;">30%</div>
+          </div>
+        </div>
+        <div class="rule-item">
+          <span class="stage-name">渲染</span>
+          <div class="stage-bar">
+            <div class="stage-fill" style="width: 50%;">50%</div>
+          </div>
+        </div>
+        <div class="rule-item">
+          <span class="stage-name">后期</span>
+          <div class="stage-bar">
+            <div class="stage-fill" style="width: 20%;">20%</div>
+          </div>
+        </div>
+      </div>
     </section>
 
     <!-- 绩效趋势区域 -->
@@ -117,48 +146,7 @@
       </div>
       
       <div class="performance-chart">
-        <div class="chart-container">
-          <div class="chart-labels">
-            <span *ngFor="let data of performanceData">{{ data.month }}</span>
-          </div>
-          <div class="chart-bars">
-            <!-- 项目完成率 -->
-            <div class="bar-group">
-              <div *ngFor="let data of performanceData" class="bar completion-rate">
-                <div class="bar-fill" [style.height.%]="data.projectCompletionRate"></div>
-                <span class="bar-value">{{ data.projectCompletionRate }}%</span>
-              </div>
-            </div>
-            <!-- 客户满意度 -->
-            <div class="bar-group">
-              <div *ngFor="let data of performanceData" class="bar satisfaction-rate">
-                <div class="bar-fill" [style.height.%]="data.customerSatisfaction"></div>
-                <span class="bar-value">{{ data.customerSatisfaction }}%</span>
-              </div>
-            </div>
-            <!-- 交付准时率 -->
-            <div class="bar-group">
-              <div *ngFor="let data of performanceData" class="bar ontime-rate">
-                <div class="bar-fill" [style.height.%]="data.deliveryOnTimeRate"></div>
-                <span class="bar-value">{{ data.deliveryOnTimeRate }}%</span>
-              </div>
-            </div>
-          </div>
-        </div>
-        <div class="chart-legend">
-          <span class="legend-item">
-            <span class="legend-color completion-rate"></span>
-            项目完成率
-          </span>
-          <span class="legend-item">
-            <span class="legend-color satisfaction-rate"></span>
-            客户满意度
-          </span>
-          <span class="legend-item">
-            <span class="legend-color ontime-rate"></span>
-            交付准时率
-          </span>
-        </div>
+        <div #performanceChart style="width: 100%; height: 300px; position: relative;"></div>
       </div>
     </section>
   </main>

+ 75 - 0
src/app/pages/designer/personal-board/personal-board.scss

@@ -382,6 +382,81 @@
       }
     }
   }
+  
+  .status-badge {
+    display: inline-block;
+    padding: 4px 12px;
+    border-radius: $ios-radius-full;
+    font-size: 12px;
+    font-weight: $ios-font-weight-medium;
+    
+    &.settled {
+      background-color: rgba(52, 199, 89, 0.1);
+      color: $ios-success;
+    }
+    
+    &.pending {
+      background-color: rgba(0, 122, 255, 0.1);
+      color: $ios-primary;
+    }
+  }
+}
+
+/* 结算规则样式 */
+.settlement-rules {
+  margin-top: 24px;
+  padding: 20px;
+  background: $ios-card-background;
+  border-radius: $ios-radius-lg;
+  box-shadow: $ios-shadow-card;
+  border: 1px solid $ios-border;
+  
+  h3 {
+    font-size: 18px;
+    color: $ios-text-primary;
+    margin: 0 0 16px 0;
+    font-family: $ios-font-family;
+  }
+  
+  .rule-item {
+    display: flex;
+    align-items: center;
+    margin-bottom: 12px;
+    gap: 16px;
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+    
+    .stage-name {
+      font-size: 14px;
+      color: $ios-text-secondary;
+      min-width: 60px;
+      font-family: $ios-font-family;
+    }
+    
+    .stage-bar {
+      flex: 1;
+      height: 24px;
+      background: $ios-background-secondary;
+      border-radius: $ios-radius-full;
+      overflow: hidden;
+      position: relative;
+      
+      .stage-fill {
+        height: 100%;
+        background: $ios-primary;
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        padding-right: 12px;
+        color: white;
+        font-size: 12px;
+        font-weight: $ios-font-weight-medium;
+        font-family: $ios-font-family;
+      }
+    }
+  }
 }
 
 /* 绩效趋势样式 */

+ 256 - 6
src/app/pages/designer/personal-board/personal-board.ts

@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, AfterViewInit, ElementRef, ViewChild } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { RouterModule } from '@angular/router';
 import { ProjectService } from '../../../services/project.service';
@@ -6,7 +6,8 @@ import {
   SkillTag,
   PerformanceData,
   MatchingOrder,
-  Settlement
+  Settlement,
+  ProjectStage
 } from '../../../models/project.model';
 
 @Component({
@@ -15,20 +16,248 @@ import {
   templateUrl: './personal-board.html',
   styleUrl: './personal-board.scss'
 })
-export class PersonalBoard implements OnInit {
+export class PersonalBoard implements OnInit, AfterViewInit {
   skillTags: SkillTag[] = [];
   performanceData: PerformanceData[] = [];
   matchingOrders: MatchingOrder[] = [];
   settlements: Settlement[] = [];
   totalSettlementAmount: number = 0;
   pendingSettlementAmount: number = 0;
+  isJuniorDesigner: boolean = false; // 权限控制标记
+
+  @ViewChild('performanceChart') performanceChart!: ElementRef;
 
   constructor(private projectService: ProjectService) {}
 
   ngOnInit(): void {
+    this.checkDesignerLevel(); // 检查设计师级别
     this.loadData();
   }
 
+  ngAfterViewInit(): void {
+    // 数据加载完成后初始化图表
+    if (this.performanceData.length > 0 && this.performanceChart) {
+      this.initCustomChart();
+    }
+  }
+
+  private initCustomChart(): void {
+    try {
+      const chartContainer = this.performanceChart.nativeElement;
+      chartContainer.innerHTML = '';
+
+      // 准备图表数据
+      const months = this.performanceData.map(item => item.month);
+      const projectCompletionRate = this.performanceData.map(item => item.projectCompletionRate);
+      const customerSatisfaction = this.performanceData.map(item => item.customerSatisfaction);
+      const deliveryOnTimeRate = this.performanceData.map(item => item.deliveryOnTimeRate);
+
+      // 创建图表容器
+      const chartWrapper = document.createElement('div');
+      chartWrapper.className = 'custom-chart-wrapper';
+      chartWrapper.style.height = '100%';
+      chartWrapper.style.display = 'flex';
+      chartWrapper.style.flexDirection = 'column';
+
+      // 创建图例
+      const legend = document.createElement('div');
+      legend.className = 'custom-chart-legend';
+      legend.style.display = 'flex';
+      legend.style.justifyContent = 'center';
+      legend.style.marginBottom = '10px';
+      legend.innerHTML = `
+        <div class="legend-item"><span class="legend-color project-completion"></span>项目完成率</div>
+        <div class="legend-item"><span class="legend-color customer-satisfaction"></span>客户满意度</div>
+        <div class="legend-item"><span class="legend-color delivery-on-time"></span>交付准时率</div>
+      `;
+      chartWrapper.appendChild(legend);
+
+      // 创建图表主体
+      const chartBody = document.createElement('div');
+      chartBody.className = 'custom-chart-body';
+      chartBody.style.flex = '1';
+      chartBody.style.display = 'flex';
+      chartBody.style.alignItems = 'flex-end';
+      chartBody.style.justifyContent = 'space-around';
+      chartBody.style.padding = '20px 0';
+
+      // 创建Y轴刻度
+      const yAxis = document.createElement('div');
+      yAxis.className = 'custom-chart-yaxis';
+      yAxis.style.position = 'absolute';
+      yAxis.style.left = '0';
+      yAxis.style.height = '100%';
+      yAxis.style.display = 'flex';
+      yAxis.style.flexDirection = 'column-reverse';
+      yAxis.style.justifyContent = 'space-between';
+      yAxis.innerHTML = `
+        <div class="yaxis-label">100%</div>
+        <div class="yaxis-label">75%</div>
+        <div class="yaxis-label">50%</div>
+        <div class="yaxis-label">25%</div>
+        <div class="yaxis-label">0%</div>
+      `;
+      chartContainer.appendChild(yAxis);
+
+      // 为每个月份创建柱状图组
+      months.forEach((month, index) => {
+        const barGroup = document.createElement('div');
+        barGroup.className = 'custom-chart-bargroup';
+        barGroup.style.display = 'flex';
+        barGroup.style.alignItems = 'flex-end';
+        barGroup.style.width = '25%';
+        barGroup.style.position = 'relative';
+
+        // 项目完成率柱子
+        const projectBar = this.createBar(
+          projectCompletionRate[index], 
+          'project-completion', 
+          `${projectCompletionRate[index]}%`
+        );
+        barGroup.appendChild(projectBar);
+
+        // 客户满意度柱子
+        const satisfactionBar = this.createBar(
+          customerSatisfaction[index], 
+          'customer-satisfaction', 
+          `${customerSatisfaction[index]}%`
+        );
+        barGroup.appendChild(satisfactionBar);
+
+        // 交付准时率柱子
+        const ontimeBar = this.createBar(
+          deliveryOnTimeRate[index], 
+          'delivery-on-time', 
+          `${deliveryOnTimeRate[index]}%`
+        );
+        barGroup.appendChild(ontimeBar);
+
+        // 添加月份标签
+        const monthLabel = document.createElement('div');
+        monthLabel.className = 'custom-chart-month-label';
+        monthLabel.textContent = month;
+        monthLabel.style.position = 'absolute';
+        monthLabel.style.bottom = '-25px';
+        monthLabel.style.textAlign = 'center';
+        monthLabel.style.width = '100%';
+        barGroup.appendChild(monthLabel);
+
+        chartBody.appendChild(barGroup);
+      });
+
+      chartWrapper.appendChild(chartBody);
+      chartContainer.appendChild(chartWrapper);
+
+      // 动态添加样式
+      this.addChartStyles();
+    } catch (error) {
+      console.error('Failed to initialize custom chart:', error);
+    }
+  }
+
+  private createBar(value: number, className: string, tooltip: string): HTMLDivElement {
+    const bar = document.createElement('div');
+    bar.className = `custom-chart-bar ${className}`;
+    bar.style.height = `${value}%`;
+    bar.style.width = '20px';
+    bar.style.margin = '0 2px';
+    bar.style.position = 'relative';
+    bar.title = tooltip;
+
+    const barFill = document.createElement('div');
+    barFill.className = 'custom-chart-bar-fill';
+    barFill.style.height = '100%';
+    barFill.style.width = '100%';
+    barFill.style.borderRadius = '4px 4px 0 0';
+    bar.appendChild(barFill);
+
+    return bar;
+  }
+
+  private addChartStyles(): void {
+    // 检查是否已存在样式
+    let styleElement = document.getElementById('custom-chart-styles');
+    if (styleElement) return;
+
+    styleElement = document.createElement('style');
+    styleElement.id = 'custom-chart-styles';
+    styleElement.textContent = `
+      .custom-chart-wrapper {
+        position: relative;
+        width: 100%;
+        height: 100%;
+      }
+      .custom-chart-legend .legend-item {
+        display: flex;
+        align-items: center;
+        margin: 0 15px;
+        font-size: 12px;
+      }
+      .custom-chart-legend .legend-color {
+        display: inline-block;
+        width: 12px;
+        height: 12px;
+        margin-right: 5px;
+        border-radius: 2px;
+      }
+      .legend-color.project-completion {
+        background-color: #1890ff;
+      }
+      .legend-color.customer-satisfaction {
+        background-color: #52c41a;
+      }
+      .legend-color.delivery-on-time {
+        background-color: #fa8c16;
+      }
+      .custom-chart-body {
+        position: relative;
+        margin-left: 40px;
+      }
+      .custom-chart-bargroup {
+        height: 100%;
+      }
+      .custom-chart-bar {
+        cursor: pointer;
+        transition: opacity 0.3s;
+      }
+      .custom-chart-bar:hover {
+        opacity: 0.8;
+      }
+      .custom-chart-bar-fill {
+        transition: height 0.5s ease-out;
+      }
+      .project-completion .custom-chart-bar-fill {
+        background-color: #1890ff;
+      }
+      .customer-satisfaction .custom-chart-bar-fill {
+        background-color: #52c41a;
+      }
+      .delivery-on-time .custom-chart-bar-fill {
+        background-color: #fa8c16;
+      }
+      .custom-chart-month-label {
+        font-size: 12px;
+        color: #666;
+      }
+      .custom-chart-yaxis {
+        width: 40px;
+        font-size: 10px;
+        color: #999;
+      }
+      .yaxis-label {
+        text-align: right;
+        padding-right: 5px;
+      }
+    `;
+    document.head.appendChild(styleElement);
+  }
+
+  checkDesignerLevel(): void {
+    // 实际应用中应从用户服务获取级别信息
+    // 这里模拟初级设计师权限
+    this.isJuniorDesigner = false; // 临时设置为false,可根据实际需求调整
+  }
+
   loadData(): void {
     this.loadSkillTags();
     this.loadPerformanceData();
@@ -45,6 +274,12 @@ export class PersonalBoard implements OnInit {
   loadPerformanceData(): void {
     this.projectService.getPerformanceData().subscribe(data => {
       this.performanceData = data;
+      // 在数据加载完成后初始化图表(确保DOM已经渲染完成)
+      setTimeout(() => {
+        if (this.performanceChart) {
+          this.initCustomChart();
+        }
+      }, 0);
     });
   }
 
@@ -67,13 +302,28 @@ export class PersonalBoard implements OnInit {
   }
 
   acceptOrder(orderId: string): void {
-    // 在实际应用中,这里应该调用API来接受订单
+    // 接受订单功能
     console.log('接受订单:', orderId);
-    // 可以添加一些UI反馈,比如显示成功消息等
+    alert('订单接受成功!');
+    // 实际应用中应调用API接受订单
   }
 
   viewSettlementDetails(): void {
-    // 在实际应用中,这里可以跳转到结算详情页面
+    // 查看结算明细功能
     console.log('查看结算明细');
   }
+
+  // 根据项目阶段获取对应的结算比例
+  getStagePercentage(stage: ProjectStage): number {
+    const percentages: Record<ProjectStage, number> = {
+      '建模': 30,
+      '渲染': 50,
+      '后期': 20,
+      '前期沟通': 0, // 补充前期沟通阶段比例
+      '软装': 0, // 补充软装阶段比例
+      '完成': 0  // 补充完成阶段比例
+    };
+    return percentages[stage] || 0;
+  }
+
 }

+ 320 - 210
src/app/pages/designer/project-detail/project-detail.html

@@ -1,8 +1,38 @@
 <div class="project-detail-container">
   <!-- 项目标题栏 -->
-  <div class="project-header">
-    <h1>项目详情</h1>
-    <div class="project-id">项目ID: {{ projectId }}</div>
+  <div class="project-header card">
+    <div class="header-content">
+      <h1>项目详情</h1>
+      <div class="project-meta">
+        <span class="project-id">项目ID: {{ projectId }}</span>
+        <span class="project-status" *ngIf="project">{{ project.status }}</span>
+      </div>
+    </div>
+    <div class="header-actions">
+      <!-- 返回工作台按钮 -->
+      <button (click)="backToWorkbench()" class="back-btn">返回工作台</button>
+      
+      <!-- 切换项目下拉菜单 -->
+      <div class="project-switcher">
+        <button (click)="showDropdown = !showDropdown" class="switch-btn">切换项目</button>
+        <div *ngIf="showDropdown" class="switch-dropdown" (click)="$event.stopPropagation()">
+          <div *ngFor="let p of projects" 
+               (click)="switchProject(p.id); showDropdown = false" 
+               [class.active]="p.id === projectId" 
+               class="project-item">
+            <span class="project-name">{{ p.name }}</span>
+            <span class="project-status-badge" 
+                  [class.ongoing]="p.status === '进行中'" 
+                  [class.completed]="p.status === '已完成'" 
+                  [class.pending]="p.status === '待处理'">
+              {{ p.status }}
+            </span>
+          </div>
+        </div>
+      </div>
+      
+      <button (click)="generateReminderMessage()" class="stagnation-btn">设置停滞</button>
+    </div>
   </div>
 
   <!-- 提醒消息弹窗 -->
@@ -10,243 +40,323 @@
     {{ reminderMessage }}
   </div>
 
-  <!-- 项目基本信息 -->
-  <div class="project-info-section">
-    <h2>项目基本信息</h2>
-    <div class="info-grid">
-      <div class="info-item">
-        <label>项目名称:</label>
-        <span>{{ project?.name || '加载中...' }}</span>
-      </div>
-      <div class="info-item">
-        <label>客户姓名:</label>
-        <span>{{ project?.customerName || '加载中...' }}</span>
-      </div>
-      <div class="info-item">
-        <label>当前阶段:</label>
-        <span class="stage-tag" [class.stage-]="project?.currentStage">{{ project?.currentStage || '加载中...' }}</span>
-      </div>
-      <div class="info-item" *ngIf="project">
-        <label>预计交付日期:</label>
-        <span>{{ project.deadline | date:'yyyy-MM-dd' }}</span>
+  <!-- 主内容区 - 网格布局 -->
+  <div class="main-content-grid">
+    <!-- 项目基本信息卡片 -->
+    <div class="project-info-card card">
+      <h2>项目基本信息</h2>
+      <div class="info-grid">
+        <div class="info-item">
+          <label>项目名称</label>
+          <span>{{ project?.name || '加载中...' }}</span>
+        </div>
+        <div class="info-item">
+          <label>客户姓名</label>
+          <span>{{ project?.customerName || '加载中...' }}</span>
+        </div>
+        <div class="info-item">
+          <label>当前阶段</label>
+          <span class="stage-tag">{{ project?.currentStage || '加载中...' }}</span>
+        </div>
+        <div class="info-item" *ngIf="project">
+          <label>预计交付日期</label>
+          <span>{{ project.deadline | date:'yyyy-MM-dd' }}</span>
+        </div>
       </div>
     </div>
-  </div>
 
-  <!-- 客户画像与技能匹配度 -->
-  <div class="customer-profile-section">
-    <h2>客户画像</h2>
-    
-    <!-- 技能匹配度警告 -->
-    <div *ngIf="getSkillMismatchWarning()" class="skill-mismatch-warning">
-      {{ getSkillMismatchWarning() }}
-    </div>
+    <!-- 客户画像卡片 -->
+    <div class="customer-profile-card card">
+      <h2>客户画像</h2>
+      
+      <!-- 技能匹配度警告 -->
+      <div *ngIf="getSkillMismatchWarning()" class="warning-banner">
+        <div class="warning-content">
+          <span class="warning-icon">⚠️</span>
+          <span class="warning-text">{{ getSkillMismatchWarning() }}</span>
+        </div>
+        <button (click)="notifyTeamLeader('skill-mismatch')" class="contact-leader-btn">联系组长</button>
+      </div>
     
-    <div class="customer-tags" *ngIf="project">
-        <ng-container *ngIf="project.customerTags && project.customerTags.length > 0">
-          <div class="tag-group">
-            <label>需求类型:</label>
-            <span *ngIf="project.customerTags[0].needType" class="tag">
-              {{ project.customerTags[0].needType }}
-            </span>
+      <div *ngIf="project" class="tags-container">
+        <div class="tag-section">
+          <h3>客户偏好</h3>
+          <div class="tags-grid">
+            <ng-container *ngIf="project.customerTags && project.customerTags.length > 0">
+              <div class="tag-item">
+                <span class="tag-label">需求类型</span>
+                <span *ngIf="project.customerTags[0].needType" class="tag">
+                  {{ project.customerTags[0].needType }}
+                </span>
+              </div>
+              <div class="tag-item">
+                <span class="tag-label">设计风格</span>
+                <span *ngIf="project.customerTags[0].preference" class="tag">
+                  {{ project.customerTags[0].preference }}
+                </span>
+              </div>
+              <div class="tag-item">
+                <span class="tag-label">色彩氛围</span>
+                <span *ngIf="project.customerTags[0].colorAtmosphere" class="tag">
+                  {{ project.customerTags[0].colorAtmosphere }}
+                </span>
+              </div>
+            </ng-container>
           </div>
-          <div class="tag-group">
-            <label>设计风格:</label>
-            <span *ngIf="project.customerTags[0].preference" class="tag">
-              {{ project.customerTags[0].preference }}
-            </span>
-          </div>
-          <div class="tag-group">
-            <label>色彩氛围:</label>
-            <span *ngIf="project.customerTags[0].colorAtmosphere" class="tag">
-              {{ project.customerTags[0].colorAtmosphere }}
-            </span>
+        </div>
+        
+        <div class="tag-section">
+          <h3>项目要求</h3>
+          <div class="tags-flex">
+            <div class="tag-group">
+              <span class="group-label">高优先级需求</span>
+              <div class="tags">
+                <span *ngFor="let priority of project.highPriorityNeeds" class="priority-tag">
+                  {{ priority }}
+                </span>
+              </div>
+            </div>
+            <div class="tag-group">
+              <span class="group-label">擅长技能</span>
+              <div class="tags">
+                <span *ngFor="let skill of project.skillsRequired" class="skill-tag">
+                  {{ skill }}
+                </span>
+              </div>
+            </div>
           </div>
-        </ng-container>
-        <div class="tag-group">
-          <label>高优先级需求:</label>
-          <span *ngFor="let priority of project.highPriorityNeeds" class="priority-tag">
-            {{ priority }}
-          </span>
         </div>
       </div>
-  </div>
-
-  <!-- 前期沟通与需求对齐 -->
-  <div class="requirement-section">
-    <h2>前期沟通与需求对齐</h2>
-    <h3>需求确认清单</h3>
-    <div class="checklist">
-      <div *ngFor="let item of requirementChecklist" class="checklist-item">
-        <input type="checkbox" checked disabled>
-        <span>{{ item }}</span>
-      </div>
     </div>
-  </div>
 
-  <!-- 制作流程进度 -->
-  <div class="process-section">
-    <h2>制作流程进度</h2>
-    <div class="stage-progress">
-      <div class="stage" [class.completed]="project?.currentStage !== '建模'">
-        <div class="stage-icon">1</div>
-        <div class="stage-name">建模</div>
-        <div *ngIf="project?.currentStage === '建模'" class="stage-actions">
-          <button (click)="updateProjectStage('软装')" [disabled]="!areAllModelChecksPassed()">
-            {{ areAllModelChecksPassed() ? '完成建模' : '完成所有模型检查' }}
-          </button>
-        </div>
-      </div>
-      <div class="stage" [class.completed]="project?.currentStage !== '软装' && project?.currentStage !== '建模'">
-        <div class="stage-icon">2</div>
-        <div class="stage-name">软装</div>
-        <div *ngIf="project?.currentStage === '软装'" class="stage-actions">
-          <button (click)="updateProjectStage('渲染')">完成软装</button>
+    <!-- 制作流程进度卡片 -->
+    <div class="process-card card">
+      <h2>制作流程进度</h2>
+      <div class="stage-progress-container">
+        <div class="stage-progress">
+          <div class="stage" [class.completed]="project?.currentStage !== '建模'" [class.active]="project?.currentStage === '建模'">
+            <div class="stage-icon">
+              <span>{{ project?.currentStage !== '建模' ? '✓' : '1' }}</span>
+            </div>
+            <div class="stage-name">建模</div>
+          </div>
+          <div class="progress-line"></div>
+          <div class="stage" [class.completed]="project?.currentStage !== '软装' && project?.currentStage !== '建模'" [class.active]="project?.currentStage === '软装'">
+            <div class="stage-icon">
+              <span>{{ project?.currentStage !== '软装' && project?.currentStage !== '建模' ? '✓' : '2' }}</span>
+            </div>
+            <div class="stage-name">软装</div>
+          </div>
+          <div class="progress-line"></div>
+          <div class="stage" [class.completed]="project?.currentStage !== '渲染' && project?.currentStage !== '建模' && project?.currentStage !== '软装'" [class.active]="project?.currentStage === '渲染'">
+            <div class="stage-icon">
+              <span>{{ project?.currentStage !== '渲染' && project?.currentStage !== '建模' && project?.currentStage !== '软装' ? '✓' : '3' }}</span>
+            </div>
+            <div class="stage-name">渲染</div>
+          </div>
+          <div class="progress-line"></div>
+          <div class="stage" [class.completed]="project?.currentStage !== '后期' && project?.currentStage !== '建模' && project?.currentStage !== '软装' && project?.currentStage !== '渲染'" [class.active]="project?.currentStage === '后期'">
+            <div class="stage-icon">
+              <span>{{ project?.currentStage !== '后期' && project?.currentStage !== '建模' && project?.currentStage !== '软装' && project?.currentStage !== '渲染' ? '✓' : '4' }}</span>
+            </div>
+            <div class="stage-name">后期</div>
+          </div>
         </div>
       </div>
-      <div class="stage" [class.completed]="project?.currentStage !== '渲染' && project?.currentStage !== '建模' && project?.currentStage !== '软装'">
-        <div class="stage-icon">3</div>
-        <div class="stage-name">渲染</div>
-        <div *ngIf="project?.currentStage === '渲染'" class="stage-actions">
-          <button (click)="updateProjectStage('后期')">完成渲染</button>
+      
+      <!-- 当前阶段操作 -->
+      <div *ngIf="project" class="current-stage-actions">
+        <div class="current-stage-info">
+          <h3>当前阶段: <span class="stage-highlight">{{ project.currentStage }}</span></h3>
         </div>
-      </div>
-      <div class="stage" [class.completed]="project?.currentStage !== '后期' && project?.currentStage !== '建模' && project?.currentStage !== '软装' && project?.currentStage !== '渲染'">
-        <div class="stage-icon">4</div>
-        <div class="stage-name">后期</div>
-        <div *ngIf="project?.currentStage === '后期'" class="stage-actions">
-          <button (click)="updateProjectStage('完成')">完成后期</button>
+        <div class="stage-actions">
+          <button *ngIf="project.currentStage === '建模'" (click)="updateProjectStage('软装')" [disabled]="!areAllModelChecksPassed()" class="primary-btn">
+            {{ areAllModelChecksPassed() ? '完成建模' : '完成所有模型检查' }}
+          </button>
+          <button *ngIf="project.currentStage === '软装'" (click)="updateProjectStage('渲染')" class="primary-btn">完成软装</button>
+          <button *ngIf="project.currentStage === '渲染'" (click)="updateProjectStage('后期')" class="primary-btn">完成渲染</button>
+          <button *ngIf="project.currentStage === '后期'" (click)="updateProjectStage('完成')" class="primary-btn">完成后期</button>
         </div>
       </div>
     </div>
-  </div>
 
-  <!-- 渲染进度模块 -->
-  <div class="render-progress-section">
-    <h2>渲染进度</h2>
-    <div *ngIf="isLoadingRenderProgress" class="loading">
-      加载中...
-    </div>
-    <div *ngIf="errorLoadingRenderProgress" class="error">
-      加载失败
-      <button (click)="retryLoadRenderProgress()">点击重试</button>
-      <span>或联系组长协助排查</span>
-    </div>
-    <div *ngIf="renderProgress && !isLoadingRenderProgress && !errorLoadingRenderProgress" class="progress-content">
-      <div class="progress-bar">
-        <div class="progress-fill" [style.width.px]="renderProgress.completionRate + '%'"></div>
+    <!-- 渲染进度卡片 -->
+    <div class="render-progress-card card">
+      <h2>渲染进度</h2>
+      <div *ngIf="isLoadingRenderProgress" class="loading-state">
+        <div class="loading-spinner"></div>
+        <span>加载中...</span>
       </div>
-      <div class="progress-info">
-        <div>完成率: {{ renderProgress.completionRate }}%</div>
-        <div>预计剩余时间: {{ renderProgress.estimatedTimeRemaining }} 小时</div>
+      <div *ngIf="errorLoadingRenderProgress" class="error-state">
+        <span>加载失败</span>
+        <button (click)="retryLoadRenderProgress()" class="secondary-btn">点击重试</button>
+      </div>
+      <div *ngIf="renderProgress && !isLoadingRenderProgress && !errorLoadingRenderProgress" class="progress-content">
+        <!-- 渲染超时预警 -->
+        <div *ngIf="renderProgress.estimatedTimeRemaining <= 3" class="timeout-warning">
+          <div class="warning-icon">⚠️</div>
+          <div class="warning-text">
+            <span class="warning-title">渲染即将超时</span>
+            <span class="warning-time">预计剩余时间: {{ renderProgress.estimatedTimeRemaining }} 小时</span>
+          </div>
+        </div>
+        
+        <div class="progress-bar-container">
+          <div class="progress-bar">
+            <div class="progress-fill" [style.width.percent]="renderProgress.completionRate"></div>
+          </div>
+          <div class="progress-percentage">{{ renderProgress.completionRate }}%</div>
+        </div>
+        
+        <div class="progress-details">
+          <div class="progress-info">
+            <span class="info-label">预计剩余时间</span>
+            <span class="info-value">{{ renderProgress.estimatedTimeRemaining }} 小时</span>
+          </div>
+          <div class="progress-info">
+            <span class="info-label">当前状态</span>
+            <span class="info-value">{{ renderProgress.status }}</span>
+          </div>
+        </div>
       </div>
     </div>
-  </div>
 
-  <!-- 模型误差检查清单 -->
-  <div class="model-check-section">
-    <h2>模型误差检查清单</h2>
-    <div class="checklist">
-      <div *ngFor="let item of modelCheckItems" class="checklist-item">
-        <input type="checkbox" [checked]="item.isPassed" (change)="updateModelCheckItem(item.id, !item.isPassed)">
-        <span>{{ item.name }}</span>
-        <span class="check-status" [class.passed]="item.isPassed" [class.failed]="!item.isPassed">
-          {{ item.isPassed ? '通过' : '未通过' }}
-        </span>
+    <!-- 模型误差检查清单卡片 -->
+    <div class="model-check-card card">
+      <h2>模型误差检查清单</h2>
+      <div class="checklist">
+        <div *ngFor="let item of modelCheckItems" class="checklist-item">
+          <input type="checkbox" [checked]="item.isPassed" (change)="updateModelCheckItem(item.id, !item.isPassed)" class="custom-checkbox">
+          <span class="checklist-text">{{ item.name }}</span>
+          <span class="check-status" [class.passed]="item.isPassed" [class.failed]="!item.isPassed">
+            {{ item.isPassed ? '通过' : '未通过' }}
+          </span>
+        </div>
       </div>
     </div>
-  </div>
 
-  <!-- 客户反馈处理 -->
-  <div class="feedback-section">
-    <h2>客户反馈</h2>
-    <div *ngIf="feedbacks.length === 0" class="no-feedback">
-      暂无客户反馈
-    </div>
-    <div *ngFor="let feedback of feedbacks" class="feedback-item">
-      <div class="feedback-header">
-        <div class="feedback-customer">客户反馈</div>
-        <div class="feedback-date">{{ feedback.createdAt | date:'yyyy-MM-dd HH:mm' }}</div>
-      </div>
-      <div class="feedback-content">
-          <div class="feedback-status">状态: {{ feedback.status }}</div>
-          <div class="feedback-type">反馈类型: {{ feedback.isSatisfied ? '满意' : '不满意' }}</div>
-        <div class="feedback-details">
-          <p>修改部位: {{ feedback.problemLocation }}</p>
-          <p>期望效果: {{ feedback.expectedEffect }}</p>
-          <p>参考案例: {{ feedback.referenceCase }}</p>
-        </div>
-      </div>
-      <div class="feedback-actions">
-        <button (click)="updateFeedbackStatus(feedback.id, '处理中')" [disabled]="feedback.status === '处理中' || feedback.status === '已解决'">
-          标记为处理中
-        </button>
-        <button (click)="updateFeedbackStatus(feedback.id, '已解决')" [disabled]="feedback.status === '已解决'">
-          标记为已解决
-        </button>
+    <!-- 客户反馈卡片 -->
+    <div class="feedback-card card">
+      <h2>客户反馈</h2>
+      <div *ngIf="feedbacks.length === 0" class="empty-state">
+        <div class="empty-icon">📭</div>
+        <span>暂无客户反馈</span>
+      </div>
+      <div *ngFor="let feedback of feedbacks" class="feedback-item">
+        <div class="feedback-header">
+          <div class="feedback-meta">
+            <span class="feedback-type">{{ feedback.isSatisfied ? '满意反馈' : '不满意反馈' }}</span>
+            <span *ngIf="getFeedbackTag(feedback)" class="feedback-tag">{{ getFeedbackTag(feedback) }}</span>
+          </div>
+          <div class="feedback-date">{{ feedback.createdAt | date:'yyyy-MM-dd HH:mm' }}</div>
+        </div>
+        <div class="feedback-content">
+          <div class="feedback-status"><span class="status-label">状态:</span> <span class="status-value">{{ feedback.status }}</span></div>
+          <!-- 反馈倒计时 -->
+          <div *ngIf="feedback.status === '待处理' && feedbackTimeoutCountdown > 0" class="feedback-countdown">
+            <span class="countdown-icon">⏱️</span>
+            <span>响应倒计时: {{ formatCountdown(feedbackTimeoutCountdown) }}</span>
+          </div>
+          <div class="feedback-details">
+            <div class="detail-item">
+              <span class="detail-label">修改部位:</span>
+              <span class="detail-value">{{ feedback.problemLocation || '-' }}</span>
+            </div>
+            <div class="detail-item">
+              <span class="detail-label">期望效果:</span>
+              <span class="detail-value">{{ feedback.expectedEffect || '-' }}</span>
+            </div>
+            <div class="detail-item">
+              <span class="detail-label">参考案例:</span>
+              <span class="detail-value">{{ feedback.referenceCase || '-' }}</span>
+            </div>
+          </div>
+        </div>
+        <div class="feedback-actions">
+          <button (click)="updateFeedbackStatus(feedback.id, '处理中')" [disabled]="feedback.status === '处理中' || feedback.status === '已解决'" class="secondary-btn">
+            标记为处理中
+          </button>
+          <button (click)="updateFeedbackStatus(feedback.id, '已解决')" [disabled]="feedback.status === '已解决'" class="primary-btn">
+            标记为已解决
+          </button>
+        </div>
       </div>
     </div>
-  </div>
 
-  <!-- 设计师变更记录 -->
-  <div class="designer-change-section">
-    <h2>设计师变更记录</h2>
-    <div *ngIf="designerChanges.length === 0" class="no-changes">
-      暂无设计师变更记录
-    </div>
-    <div *ngFor="let change of designerChanges" class="change-item">
-      <div class="change-header">
-        <div class="change-time">{{ change.changeTime | date:'yyyy-MM-dd' }}</div>
-      </div>
-      <div class="change-details">
-        <div>原设计师: {{ change.oldDesignerName }}</div>
-        <div>新设计师: {{ change.newDesignerName }}</div>
-        <div>历史阶段成果:</div>
-        <ul>
-          <li *ngFor="let achievement of change.historicalAchievements">{{ achievement }}</li>
-        </ul>
-        <div>已完成工作量: {{ change.completedWorkload }}%</div>
-      </div>
-      <div class="change-status">
-        承接确认时间: {{ change.acceptanceTime | date:'yyyy-MM-dd' }}
+    <!-- 设计师变更记录卡片 -->
+    <div class="designer-change-card card">
+      <h2>设计师变更记录</h2>
+      <div class="change-actions">
+        <button (click)="initiateDesignerChange('技能不匹配')" class="secondary-btn">发起变更 - 技能不匹配</button>
+        <button (click)="initiateDesignerChange('休假')" class="secondary-btn">发起变更 - 休假</button>
+      </div>
+      <div *ngIf="designerChanges.length === 0" class="empty-state">
+        <div class="empty-icon">👤</div>
+        <span>暂无设计师变更记录</span>
+      </div>
+      <div *ngFor="let change of designerChanges" class="change-item">
+        <div class="change-header">
+          <div class="change-time">{{ change.changeTime | date:'yyyy-MM-dd' }}</div>
+          <button *ngIf="!change.acceptanceTime" (click)="acceptDesignerChange(change.id)" class="accept-change-btn primary-btn">
+            确认承接
+          </button>
+        </div>
+        <div class="change-details">
+          <div class="designer-change-info">
+            <div class="designer-change">
+              <span class="designer-label">原设计师:</span>
+              <span class="designer-name">{{ change.oldDesignerName }}</span>
+            </div>
+            <div class="designer-change">
+              <span class="designer-label">新设计师:</span>
+              <span class="designer-name">{{ change.newDesignerName }}</span>
+            </div>
+          </div>
+          <div class="workload-info">
+            <span>已完成工作量: <strong>{{ change.completedWorkload }}%</strong></span>
+          </div>
+          <div class="achievements">
+            <h4>历史阶段成果:</h4>
+            <ul>
+              <li *ngFor="let achievement of change.historicalAchievements">{{ achievement }}</li>
+            </ul>
+          </div>
+          <div *ngIf="change.acceptanceTime" class="change-status">
+            承接确认时间: {{ change.acceptanceTime | date:'yyyy-MM-dd' }}
+          </div>
+        </div>
       </div>
     </div>
-  </div>
 
-  <!-- 分阶段结算记录 -->
-  <div class="settlement-section">
-    <h2>分阶段结算记录</h2>
-    <div *ngIf="settlements.length === 0" class="no-settlements">
-      暂无结算记录
-    </div>
-    <div class="settlement-table">
-      <table>
-        <thead>
-          <tr>
-            <th>阶段</th>
-            <th>比例</th>
-            <th>金额(元)</th>
-            <th>状态</th>
-            <th>完成时间</th>
-          </tr>
-        </thead>
-        <tbody>
-          <tr *ngFor="let settlement of settlements">
-            <td>{{ settlement.stage }}</td>
-            <td>{{ settlement.percentage }}%</td>
-            <td>{{ settlement.amount }}</td>
-            <td>{{ settlement.status }}</td>
-            <td>{{ settlement.completionTime | date:'yyyy-MM-dd' }}</td>
-          </tr>
-        </tbody>
-      </table>
+    <!-- 分阶段结算记录卡片 -->
+    <div class="settlement-card card">
+      <h2>分阶段结算记录</h2>
+      <div *ngIf="settlements.length === 0" class="empty-state">
+        <div class="empty-icon">💰</div>
+        <span>暂无结算记录</span>
+      </div>
+      <div *ngIf="settlements.length > 0" class="settlement-table">
+        <table>
+          <thead>
+            <tr>
+              <th>阶段</th>
+              <th>比例</th>
+              <th>金额(元)</th>
+              <th>状态</th>
+              <th>完成时间</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr *ngFor="let settlement of settlements">
+              <td>{{ settlement.stage }}</td>
+              <td>{{ settlement.percentage }}%</td>
+              <td>{{ settlement.amount }}</td>
+              <td><span class="status-badge" [class.status-pending]="settlement.status === '待结算'" [class.status-settled]="settlement.status === '已结算'">{{ settlement.status }}</span></td>
+              <td>{{ settlement.completionTime | date:'yyyy-MM-dd' }}</td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
     </div>
   </div>
-
-  <!-- 停滞期设置 -->
-  <div class="stagnation-section">
-    <h2>停滞期管理</h2>
-    <button (click)="generateReminderMessage()">设置停滞</button>
-    <p>点击后将生成通知,提醒客户项目将暂时停滞</p>
-  </div>
 </div>

+ 191 - 823
src/app/pages/designer/project-detail/project-detail.scss

@@ -1,825 +1,193 @@
-// 导入iOS主题变量
 @import '../ios-theme.scss';
 
-.project-detail-container {
-  padding: $ios-spacing-xl;
-  max-width: 1200px;
-  margin: 0 auto;
-  font-family: $ios-font-family;
-  background-color: $ios-background-secondary;
-  min-height: 100vh;
-}
-
-/* 项目标题栏 */
-.project-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: $ios-spacing-xxl;
-  padding: $ios-spacing-xl;
-  background-color: $ios-background;
-  border-radius: $ios-radius-lg;
-  box-shadow: $ios-shadow-card;
-}
-
-.project-header h1 {
-  font-size: $ios-font-size-xl;
-  color: $ios-text-primary;
-  margin: 0;
-  font-weight: $ios-font-weight-semibold;
-}
-
-.project-id {
-  font-size: $ios-font-size-sm;
-  color: $ios-text-secondary;
-  background-color: $ios-background-secondary;
-  padding: $ios-spacing-xs $ios-spacing-md;
-  border-radius: $ios-radius-full;
-  font-weight: $ios-font-weight-medium;
-}
-
-/* 提醒消息弹窗 - iOS风格 */
-.reminder-popup {
-  position: fixed;
-  top: $ios-spacing-xl;
-  right: $ios-spacing-xl;
-  background-color: $ios-success;
-  color: white;
-  padding: $ios-spacing-md $ios-spacing-lg;
-  border-radius: $ios-radius-lg;
-  box-shadow: $ios-shadow-md;
-  z-index: 1000;
-  animation: slideInRight $ios-animation-fast $ios-animation-easing;
-  font-family: $ios-font-family;
-  font-size: $ios-font-size-base;
-  font-weight: $ios-font-weight-medium;
-}
-
-@keyframes slideInRight {
-  from {
-    transform: translateX(100%);
-    opacity: 0;
-  }
-  to {
-    transform: translateX(0);
-    opacity: 1;
-  }
-}
-
-/* 通用区域样式 - iOS卡片风格 */
-.project-info-section,
-.customer-profile-section,
-.requirement-section,
-.process-section,
-.render-progress-section,
-.model-check-section,
-.feedback-section,
-.designer-change-section,
-.settlement-section,
-.stagnation-section {
-  background-color: $ios-card-background;
-  border-radius: $ios-radius-lg;
-  padding: $ios-spacing-xl;
-  margin-bottom: $ios-spacing-xl;
-  box-shadow: $ios-shadow-card;
-  transition: $ios-feedback-hover;
-}
-
-.project-info-section:hover,
-.customer-profile-section:hover,
-.requirement-section:hover,
-.process-section:hover,
-.render-progress-section:hover,
-.model-check-section:hover,
-.feedback-section:hover,
-.designer-change-section:hover,
-.settlement-section:hover,
-.stagnation-section:hover {
-  box-shadow: $ios-shadow-md;
-}
-
-h2 {
-  font-size: $ios-font-size-lg;
-  color: $ios-text-primary;
-  margin-top: 0;
-  margin-bottom: $ios-spacing-xl;
-  padding-bottom: $ios-spacing-md;
-  border-bottom: 1px solid $ios-border;
-  font-weight: $ios-font-weight-semibold;
-}
-
-h3 {
-  font-size: $ios-font-size-base;
-  color: $ios-text-primary;
-  margin-top: 0;
-  margin-bottom: $ios-spacing-lg;
-  font-weight: $ios-font-weight-medium;
-}
-
-/* 项目基本信息 - iOS风格 */
-.info-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
-  gap: $ios-spacing-lg;
-}
-
-.info-item {
-  display: flex;
-  flex-direction: column;
-}
-
-.info-item label {
-  font-size: $ios-font-size-sm;
-  color: $ios-text-secondary;
-  margin-bottom: $ios-spacing-xs;
-  font-weight: $ios-font-weight-medium;
-}
-
-.info-item span {
-  font-size: $ios-font-size-base;
-  color: $ios-text-primary;
-  font-weight: $ios-font-weight-regular;
-}
-
-.stage-tag {
-  display: inline-block;
-  padding: $ios-spacing-xs $ios-spacing-lg;
-  border-radius: $ios-radius-full;
-  background-color: color-mix(in srgb, $ios-success 15%, transparent);
-  color: $ios-success;
-  font-size: $ios-font-size-sm;
-  font-weight: $ios-font-weight-medium;
-}
-
-/* 客户画像 - iOS风格 */
-.skill-mismatch-warning {
-  background-color: color-mix(in srgb, $ios-danger 10%, transparent);
-  color: $ios-danger;
-  padding: $ios-spacing-md $ios-spacing-lg;
-  border-radius: $ios-radius-md;
-  margin-bottom: $ios-spacing-xl;
-  border-left: 4px solid $ios-danger;
-  font-size: $ios-font-size-base;
-}
-
-.customer-tags {
-  display: flex;
-  flex-wrap: wrap;
-  gap: $ios-spacing-lg;
-}
-
-.tag-group {
-  display: flex;
-  flex-direction: column;
-  min-width: 150px;
-}
-
-.tag-group label {
-  font-size: $ios-font-size-sm;
-  color: $ios-text-secondary;
-  margin-bottom: $ios-spacing-sm;
-  font-weight: $ios-font-weight-medium;
-}
-
-.tag {
-  display: inline-block;
-  background-color: color-mix(in srgb, $ios-primary 15%, transparent);
-  color: $ios-primary;
-  padding: $ios-spacing-xs $ios-spacing-md;
-  border-radius: $ios-radius-full;
-  font-size: $ios-font-size-xs;
-  margin-right: $ios-spacing-xs;
-  margin-bottom: $ios-spacing-xs;
-  font-weight: $ios-font-weight-medium;
-}
-
-.priority-tag {
-  display: inline-block;
-  background-color: color-mix(in srgb, $ios-warning 15%, transparent);
-  color: $ios-warning;
-  padding: $ios-spacing-xs $ios-spacing-md;
-  border-radius: $ios-radius-full;
-  font-size: $ios-font-size-xs;
-  margin-right: $ios-spacing-xs;
-  margin-bottom: $ios-spacing-xs;
-  font-weight: $ios-font-weight-medium;
-}
-
-/* 需求确认清单 - iOS风格 */
-.checklist {
-  margin-top: $ios-spacing-lg;
-}
-
-.checklist-item {
-  display: flex;
-  align-items: center;
-  margin-bottom: $ios-spacing-md;
-  padding: $ios-spacing-md;
-  border-radius: $ios-radius-md;
-  background-color: $ios-background-secondary;
-  transition: $ios-feedback-hover;
-}
-
-.checklist-item:hover {
-  background-color: color-mix(in srgb, $ios-background-secondary 80%, white);
-}
-
-.checklist-item input[type="checkbox"] {
-  margin-right: $ios-spacing-md;
-  width: 20px;
-  height: 20px;
-  accent-color: $ios-primary;
-  border-radius: $ios-radius-sm;
-}
-
-.checklist-item span {
-  color: $ios-text-primary;
-  font-size: $ios-font-size-base;
-  flex: 1;
-}
-
-/* 制作流程进度 - iOS风格 */
-.stage-progress {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  position: relative;
-  padding: 0 $ios-spacing-md;
-}
-
-.stage-progress::before {
-  content: '';
-  position: absolute;
-  top: 50%;
-  left: 50px;
-  right: 50px;
-  height: 3px;
-  background-color: $ios-border;
-  transform: translateY(-50%);
-  z-index: 1;
-}
-
-.stage {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  z-index: 2;
-  position: relative;
-  transition: $ios-feedback-hover;
-}
-
-.stage-icon {
-  width: 44px;
-  height: 44px;
-  border-radius: 50%;
-  background-color: $ios-border;
-  color: $ios-text-secondary;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  font-weight: $ios-font-weight-semibold;
-  margin-bottom: $ios-spacing-md;
-  transition: all $ios-animation-normal $ios-animation-easing;
-  border: 3px solid $ios-background;
-  box-shadow: $ios-shadow-sm;
-}
-
-.stage-name {
-  font-size: $ios-font-size-sm;
-  color: $ios-text-secondary;
-  margin-bottom: $ios-spacing-md;
-  font-weight: $ios-font-weight-medium;
-}
-
-.stage.completed .stage-icon {
-  background-color: $ios-success;
-  color: white;
-  box-shadow: 0 0 0 3px color-mix(in srgb, $ios-success 20%, transparent);
-}
-
-.stage.completed .stage-name {
-  color: $ios-success;
-  font-weight: $ios-font-weight-semibold;
-}
-
-.stage-actions {
-  margin-top: $ios-spacing-md;
-}
-
-.stage-actions button {
-  background-color: $ios-primary;
-  color: white;
-  border: none;
-  padding: $ios-spacing-sm $ios-spacing-lg;
-  border-radius: $ios-radius-md;
-  font-size: $ios-font-size-base;
-  cursor: pointer;
-  transition: $ios-feedback-tap;
-  font-weight: $ios-font-weight-medium;
-  box-shadow: $ios-shadow-sm;
-}
-
-.stage-actions button:hover:not(:disabled) {
-  background-color: $ios-primary-light;
-  transform: translateY(-1px);
-  box-shadow: $ios-shadow-md;
-}
-
-.stage-actions button:active:not(:disabled) {
-  transform: translateY(0);
-  box-shadow: $ios-shadow-sm;
-}
-
-.stage-actions button:disabled {
-  cursor: not-allowed;
-  background-color: $ios-text-tertiary;
-  box-shadow: none;
-}
-
-/* 渲染进度 - iOS风格 */
-.loading {
-  text-align: center;
-  padding: $ios-spacing-xl;
-  color: $ios-text-secondary;
-  font-size: $ios-font-size-base;
-}
-
-.error {
-  text-align: center;
-  padding: $ios-spacing-xl;
-  color: $ios-danger;
-  background-color: color-mix(in srgb, $ios-danger 10%, transparent);
-  border-radius: $ios-radius-md;
-  font-size: $ios-font-size-base;
-}
-
-.error button {
-  background-color: $ios-danger;
-  color: white;
-  border: none;
-  padding: $ios-spacing-sm $ios-spacing-lg;
-  border-radius: $ios-radius-md;
-  font-size: $ios-font-size-base;
-  cursor: pointer;
-  margin: 0 $ios-spacing-md;
-  transition: $ios-feedback-tap;
-  font-weight: $ios-font-weight-medium;
-}
-
-.error button:hover {
-  background-color: color-mix(in srgb, $ios-danger 90%, black);
-  transform: translateY(-1px);
-}
-
-.progress-content {
-  padding: $ios-spacing-xl;
-}
-
-.progress-bar {
-  width: 100%;
-  height: 8px;
-  background-color: $ios-border;
-  border-radius: $ios-radius-full;
-  overflow: hidden;
-  margin-bottom: $ios-spacing-lg;
-  position: relative;
-}
-
-.progress-fill {
-  height: 100%;
-  background-color: $ios-primary;
-  transition: width $ios-animation-normal $ios-animation-easing;
-  border-radius: $ios-radius-full;
-  position: relative;
-  overflow: hidden;
-}
-
-.progress-fill::after {
-  content: '';
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: linear-gradient(
-    90deg,
-    transparent,
-    color-mix(in srgb, white 30%, transparent),
-    transparent
-  );
-  animation: shimmer 2s infinite;
-}
-
-@keyframes shimmer {
-  0% {
-    transform: translateX(-100%);
-  }
-  100% {
-    transform: translateX(100%);
-  }
-}
-
-.progress-info {
-  display: flex;
-  justify-content: space-between;
-  font-size: $ios-font-size-base;
-  color: $ios-text-primary;
-  font-weight: $ios-font-weight-medium;
-}
-
-/* 模型检查清单 - iOS风格 */
-.check-status {
-  margin-left: auto;
-  font-weight: $ios-font-weight-semibold;
-  font-size: $ios-font-size-sm;
-  padding: $ios-spacing-xs $ios-spacing-sm;
-  border-radius: $ios-radius-md;
-}
-
-.check-status.passed {
-  color: $ios-success;
-  background-color: color-mix(in srgb, $ios-success 10%, transparent);
-}
-
-.check-status.failed {
-  color: $ios-danger;
-  background-color: color-mix(in srgb, $ios-danger 10%, transparent);
-}
-
-/* 客户反馈 - iOS风格 */
-.no-feedback,
-.no-changes,
-.no-settlements {
-  text-align: center;
-  padding: $ios-spacing-xl;
-  color: $ios-text-secondary;
-  font-size: $ios-font-size-base;
-}
-
-.feedback-item {
-  border: 1px solid $ios-border;
-  border-radius: $ios-radius-lg;
-  padding: $ios-spacing-lg;
-  margin-bottom: $ios-spacing-lg;
-  background-color: $ios-background;
-  transition: $ios-feedback-hover;
-}
-
-.feedback-item:hover {
-  box-shadow: $ios-shadow-sm;
-}
-
-.feedback-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: $ios-spacing-md;
-  padding-bottom: $ios-spacing-md;
-  border-bottom: 1px solid $ios-border;
-}
-
-.feedback-customer {
-  font-weight: $ios-font-weight-semibold;
-  color: $ios-text-primary;
-  font-size: $ios-font-size-base;
-}
-
-.feedback-date {
-  font-size: $ios-font-size-xs;
-  color: $ios-text-tertiary;
-}
-
-.feedback-content {
-  margin-bottom: $ios-spacing-lg;
-}
-
-.feedback-status {
-  display: inline-block;
-  background-color: color-mix(in srgb, $ios-primary 15%, transparent);
-  color: $ios-primary;
-  padding: $ios-spacing-xs $ios-spacing-md;
-  border-radius: $ios-radius-full;
-  font-size: $ios-font-size-xs;
-  margin-bottom: $ios-spacing-md;
-  font-weight: $ios-font-weight-medium;
-}
-
-.feedback-type {
-  color: $ios-text-secondary;
-  margin-bottom: $ios-spacing-md;
-  font-size: $ios-font-size-sm;
-}
-
-.feedback-details {
-  background-color: $ios-background-secondary;
-  padding: $ios-spacing-md;
-  border-radius: $ios-radius-md;
-}
-
-.feedback-details p {
-  margin: $ios-spacing-xs 0;
-  font-size: $ios-font-size-sm;
-  color: $ios-text-primary;
-}
-
-.feedback-actions {
-  display: flex;
-  gap: $ios-spacing-md;
-}
-
-.feedback-actions button {
-  padding: $ios-spacing-sm $ios-spacing-lg;
-  border: none;
-  border-radius: $ios-radius-md;
-  font-size: $ios-font-size-base;
-  cursor: pointer;
-  transition: $ios-feedback-tap;
-  font-weight: $ios-font-weight-medium;
-  flex: 1;
-  box-shadow: $ios-shadow-sm;
-}
-
-.feedback-actions button:first-child {
-  background-color: $ios-primary;
-  color: white;
-}
-
-.feedback-actions button:first-child:hover:not(:disabled) {
-  background-color: $ios-primary-light;
-  transform: translateY(-1px);
-  box-shadow: $ios-shadow-md;
-}
-
-.feedback-actions button:first-child:active:not(:disabled) {
-  transform: translateY(0);
-  box-shadow: $ios-shadow-sm;
-}
-
-.feedback-actions button:last-child {
-  background-color: $ios-success;
-  color: white;
-}
-
-.feedback-actions button:last-child:hover:not(:disabled) {
-  background-color: color-mix(in srgb, $ios-success 90%, black);
-  transform: translateY(-1px);
-  box-shadow: $ios-shadow-md;
-}
-
-.feedback-actions button:last-child:active:not(:disabled) {
-  transform: translateY(0);
-  box-shadow: $ios-shadow-sm;
-}
-
-.feedback-actions button:disabled {
-  background-color: $ios-text-tertiary;
-  cursor: not-allowed;
-  box-shadow: none;
-}
-
-/* 设计师变更记录 - iOS风格 */
-.change-item {
-  border: 1px solid $ios-border;
-  border-radius: $ios-radius-lg;
-  padding: $ios-spacing-lg;
-  margin-bottom: $ios-spacing-lg;
-  background-color: $ios-background;
-  transition: $ios-feedback-hover;
-}
-
-.change-item:hover {
-  box-shadow: $ios-shadow-sm;
-}
-
-.change-header {
-  margin-bottom: $ios-spacing-lg;
-  padding-bottom: $ios-spacing-md;
-  border-bottom: 1px solid $ios-border;
-}
-
-.change-time {
-  font-size: $ios-font-size-sm;
-  color: $ios-text-secondary;
-  font-weight: $ios-font-weight-semibold;
-}
-
-.change-details {
-  margin-bottom: $ios-spacing-lg;
-}
-
-.change-details div {
-  margin-bottom: $ios-spacing-xs;
-  color: $ios-text-primary;
-  font-size: $ios-font-size-base;
-}
-
-.change-details ul {
-  margin: $ios-spacing-xs 0;
-  padding-left: $ios-spacing-xl;
-}
-
-.change-details li {
-  color: $ios-text-primary;
-  margin-bottom: $ios-spacing-xs;
-  font-size: $ios-font-size-sm;
-}
-
-.change-status {
-  font-size: $ios-font-size-sm;
-  color: $ios-text-secondary;
-  background-color: $ios-background-secondary;
-  padding: $ios-spacing-sm;
-  border-radius: $ios-radius-md;
-}
-
-/* 分阶段结算记录 - iOS风格 */
-.settlement-table {
-  overflow-x: auto;
-  border-radius: $ios-radius-lg;
-  background-color: $ios-background;
-  border: 1px solid $ios-border;
-}
-
-.settlement-table table {
-  width: 100%;
-  border-collapse: collapse;
-}
-
-.settlement-table th,
-.settlement-table td {
-  padding: $ios-spacing-lg;
-  text-align: left;
-  border-bottom: 1px solid $ios-border;
-  font-size: $ios-font-size-base;
-}
-
-.settlement-table th {
-  background-color: $ios-background-secondary;
-  font-weight: $ios-font-weight-semibold;
-  color: $ios-text-primary;
-  position: sticky;
-  top: 0;
-  z-index: 10;
-}
-
-.settlement-table td {
-  color: $ios-text-primary;
-  font-weight: $ios-font-weight-regular;
-}
-
-.settlement-table tr:last-child td {
-  border-bottom: none;
-}
-
-.settlement-table tr:hover td {
-  background-color: color-mix(in srgb, $ios-background-secondary 50%, white);
-}
-
-/* 停滞期管理 - iOS风格 */
-.stagnation-section button {
-  background-color: $ios-warning;
-  color: white;
-  border: none;
-  padding: $ios-spacing-md $ios-spacing-xl;
-  border-radius: $ios-radius-md;
-  font-size: $ios-font-size-base;
-  cursor: pointer;
-  transition: $ios-feedback-tap;
-  font-weight: $ios-font-weight-medium;
-  box-shadow: $ios-shadow-sm;
-}
-
-.stagnation-section button:hover {
-  background-color: color-mix(in srgb, $ios-warning 90%, black);
-  transform: translateY(-1px);
-  box-shadow: $ios-shadow-md;
-}
-
-.stagnation-section button:active {
-  transform: translateY(0);
-  box-shadow: $ios-shadow-sm;
-}
-
-.stagnation-section p {
-  margin-top: $ios-spacing-md;
-  color: $ios-text-secondary;
-  font-size: $ios-font-size-sm;
-}
-
-/* 响应式设计 - iOS风格 */
-@media (max-width: 768px) {
-  .project-detail-container {
-    padding: $ios-spacing-md;
-  }
-  
-  .project-header {
-    flex-direction: column;
-    align-items: flex-start;
-    gap: $ios-spacing-md;
-    padding: $ios-spacing-lg;
-  }
-  
-  .info-grid {
-    grid-template-columns: 1fr;
-    gap: $ios-spacing-md;
-  }
-  
-  .stage-progress {
-    flex-direction: column;
-    align-items: flex-start;
-    gap: $ios-spacing-xl;
-  }
-  
-  .stage-progress::before {
-    display: none;
-  }
-  
-  .stage {
-    flex-direction: row;
-    align-items: center;
-    gap: $ios-spacing-md;
-    width: 100%;
-    justify-content: flex-start;
-  }
-  
-  .stage-icon {
-    margin-bottom: 0;
-    width: 36px;
-    height: 36px;
-    font-size: $ios-font-size-sm;
-  }
-  
-  .feedback-actions {
-    flex-direction: column;
-  }
-  
-  .customer-tags {
-    flex-direction: column;
-    gap: $ios-spacing-md;
-  }
-  
-  .progress-info {
-    flex-direction: column;
-    gap: $ios-spacing-md;
-  }
-  
-  .checklist-item {
-    padding: $ios-spacing-sm;
-  }
-  
-  .settlement-table th,
-  .settlement-table td {
-    padding: $ios-spacing-sm;
-    font-size: $ios-font-size-sm;
-  }
-  
-  .stage-actions button {
-    padding: $ios-spacing-sm $ios-spacing-md;
-    font-size: $ios-font-size-sm;
-  }
-  
-  .feedback-actions button {
-    padding: $ios-spacing-sm $ios-spacing-md;
-    font-size: $ios-font-size-sm;
-  }
-  
-  .stagnation-section button {
-    padding: $ios-spacing-sm $ios-spacing-lg;
-    font-size: $ios-font-size-sm;
-    width: 100%;
-  }
-  
-  h2 {
-    font-size: $ios-font-size-base;
-    margin-bottom: $ios-spacing-lg;
-  }
-  
-  h3 {
-    font-size: $ios-font-size-sm;
-  }
-}
-
-/* iOS风格的滚动条 */
-::-webkit-scrollbar {
-  width: $ios-scrollbar-width;
-  height: $ios-scrollbar-width;
-}
-
-::-webkit-scrollbar-track {
-  background: $ios-scrollbar-track;
-  border-radius: $ios-radius-full;
-}
-
-::-webkit-scrollbar-thumb {
-  background: $ios-scrollbar-thumb;
-  border-radius: $ios-radius-full;
-}
-
-::-webkit-scrollbar-thumb:hover {
-  background: $ios-scrollbar-thumb-hover;
-}
-
-/* 平滑滚动 */
-html {
-  scroll-behavior: smooth;
-}
+/* 项目详情页主样式 */
+.project-detail-container{padding:$ios-spacing-xl;background-color:$ios-background;color:$ios-text-primary;min-height:100vh}
+.card{background-color:$ios-background-secondary;border-radius:$ios-radius-lg;padding:$ios-spacing-lg;margin-bottom:$ios-spacing-xl}
+.project-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:$ios-spacing-xl;background-color:$ios-background-secondary;padding:$ios-spacing-lg;border-radius:$ios-radius-lg}
+.header-content{display:flex;flex-direction:column;gap:$ios-spacing-sm}
+.project-header h1{font-size:$ios-font-size-xl;font-weight:$ios-font-weight-bold;color:$ios-text-primary;margin:0}
+.project-meta{display:flex;align-items:center;gap:$ios-spacing-lg;font-size:$ios-font-size-sm;color:$ios-text-secondary}
+.project-id{font-weight:$ios-font-weight-medium}
+.project-status{padding:$ios-spacing-xs $ios-spacing-md;border-radius:$ios-radius-full;background-color:#e8f0fe;color:$ios-primary}
+
+/* 按钮样式 */
+.header-actions{display:flex;gap:$ios-spacing-md;align-items:center}
+.back-btn{background-color:$ios-primary;color:white;border:none;padding:$ios-spacing-sm $ios-spacing-lg;border-radius:$ios-radius-md;font-size:$ios-font-size-sm;font-weight:$ios-font-weight-medium;cursor:pointer;white-space:nowrap}
+.back-btn:hover{background-color:#0056b3;transform:translateY(-1px)}
+.project-switcher{position:relative}
+.switch-btn{background-color:$ios-background-tertiary;color:$ios-text-primary;border:none;padding:$ios-spacing-sm $ios-spacing-lg;border-radius:$ios-radius-md;font-size:$ios-font-size-sm;font-weight:$ios-font-weight-medium;cursor:pointer;white-space:nowrap}
+.switch-btn:hover{background-color:#f0f0f0;transform:translateY(-1px)}
+.switch-dropdown{position:absolute;top:100%;right:0;margin-top:$ios-spacing-xs;background-color:$ios-background-secondary;border-radius:$ios-radius-md;overflow:hidden;min-width:200px;max-height:300px;overflow-y:auto;z-index:1000}
+.project-item{display:flex;justify-content:space-between;align-items:center;padding:$ios-spacing-md;cursor:pointer;border-bottom:1px solid $ios-border}
+.project-item:last-child{border-bottom:none}
+.project-item:hover{background-color:$ios-background}
+.project-item.active{background-color:#e8f0fe}
+.project-name{font-size:$ios-font-size-sm;color:$ios-text-primary;font-weight:$ios-font-weight-medium}
+.project-status-badge{font-size:$ios-font-size-xs;padding:$ios-spacing-xs $ios-spacing-sm;border-radius:$ios-radius-full;background-color:$ios-background-tertiary;color:$ios-text-secondary}
+.project-status-badge.ongoing{background-color:#e8f0fe;color:$ios-primary}
+.project-status-badge.completed{background-color:#e6f7e6;color:$ios-success}
+.project-status-badge.pending{background-color:#fff3cd;color:$ios-warning}
+
+.stagnation-btn{background-color:$ios-warning;color:white;border:none;padding:$ios-spacing-sm $ios-spacing-md;border-radius:$ios-radius-md;font-size:$ios-font-size-sm;cursor:pointer;font-weight:$ios-font-weight-medium}
+.stagnation-btn:hover{background-color:#d4a72c;transform:translateY(-1px)}
+
+/* 主内容区网格布局 */
+.main-content-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(320px, 1fr));gap:$ios-spacing-xl}
+
+/* 标题样式 */
+h2{font-size:$ios-font-size-lg;font-weight:$ios-font-weight-semibold;color:$ios-text-primary;margin-top:0;margin-bottom:$ios-spacing-lg;padding-bottom:$ios-spacing-sm;border-bottom:1px solid $ios-border}
+h3{font-size:$ios-font-size-base;font-weight:$ios-font-weight-medium;color:$ios-text-primary;margin-top:$ios-spacing-lg;margin-bottom:$ios-spacing-md}
+h4{font-size:$ios-font-size-sm;font-weight:$ios-font-weight-medium;color:$ios-text-primary;margin-top:$ios-spacing-md;margin-bottom:$ios-spacing-sm}
+
+/* 项目基本信息卡片 */
+.project-info-card .info-grid{display:grid;grid-template-columns:1fr 1fr;gap:$ios-spacing-md}
+.info-item{display:flex;flex-direction:column;gap:$ios-spacing-xs}
+.info-item label{font-size:$ios-font-size-sm;color:$ios-text-secondary;font-weight:$ios-font-weight-medium}
+.info-item span{font-size:$ios-font-size-base;color:$ios-text-primary;font-weight:$ios-font-weight-regular}
+.stage-tag{display:inline-block;padding:$ios-spacing-xs $ios-spacing-md;border-radius:$ios-radius-full;background-color:#e6f7e6;color:$ios-success;font-size:$ios-font-size-sm;font-weight:$ios-font-weight-medium}
+
+/* 客户画像卡片 */
+.warning-banner{background-color:#ffebee;color:$ios-danger;padding:$ios-spacing-md $ios-spacing-lg;border-radius:$ios-radius-md;margin-bottom:$ios-spacing-xl;border-left:4px solid $ios-danger;font-size:$ios-font-size-base;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:$ios-spacing-md}
+.warning-content{display:flex;align-items:center;gap:$ios-spacing-sm;flex:1}
+.warning-icon{font-size:$ios-font-size-lg}
+.contact-leader-btn{background-color:$ios-danger;color:white;border:none;padding:$ios-spacing-xs $ios-spacing-md;border-radius:$ios-radius-md;font-size:$ios-font-size-sm;cursor:pointer;font-weight:$ios-font-weight-medium}
+.contact-leader-btn:hover{background-color:#c62828;transform:translateY(-1px)}
+
+/* 标签样式 */
+.tags-container{display:flex;flex-direction:column;gap:$ios-spacing-lg}
+.tag-section{margin-bottom:$ios-spacing-md}
+.tag-section h3{margin-top:0;font-size:$ios-font-size-base}
+.tags-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(120px, 1fr));gap:$ios-spacing-md}
+.tag-item{display:flex;flex-direction:column;gap:$ios-spacing-xs}
+.tag-label{font-size:$ios-font-size-xs;color:$ios-text-secondary;font-weight:$ios-font-weight-medium}
+.tag{display:inline-block;padding:$ios-spacing-xs $ios-spacing-md;border-radius:$ios-radius-full;background-color:$ios-background-tertiary;color:$ios-text-primary;font-size:$ios-font-size-sm;font-weight:$ios-font-weight-medium;align-self:flex-start}
+.tags-flex{display:flex;flex-direction:column;gap:$ios-spacing-md}
+.tag-group{display:flex;flex-direction:column;gap:$ios-spacing-sm}
+.group-label{font-size:$ios-font-size-sm;color:$ios-text-secondary;font-weight:$ios-font-weight-medium}
+.tags{display:flex;flex-wrap:wrap;gap:$ios-spacing-sm}
+.priority-tag,.skill-tag{display:inline-block;padding:$ios-spacing-xs $ios-spacing-sm;border-radius:$ios-radius-full;background-color:$ios-background-tertiary;color:$ios-text-primary;font-size:$ios-font-size-xs;font-weight:$ios-font-weight-medium}
+.priority-tag{background-color:#fff3cd;color:$ios-warning}
+.skill-tag{background-color:#e8f0fe;color:$ios-primary}
+
+/* 制作流程进度卡片 */
+.stage-progress-container{margin-bottom:$ios-spacing-xl}
+.stage-progress{display:flex;justify-content:space-between;align-items:center;position:relative;padding:$ios-spacing-lg 0}
+.stage-progress::before{content:'';position:absolute;top:50%;left:$ios-spacing-xl;right:$ios-spacing-xl;height:3px;background-color:$ios-border;transform:translateY(-50%);z-index:1}
+.progress-line{position:absolute;top:50%;left:0;right:0;height:3px;background-color:$ios-success;transform:translateY(-50%);z-index:1}
+.stage{display:flex;flex-direction:column;align-items:center;z-index:2;position:relative}
+.stage-icon{width:44px;height:44px;border-radius:50%;background-color:$ios-border;color:$ios-text-secondary;display:flex;justify-content:center;align-items:center;font-weight:$ios-font-weight-semibold;margin-bottom:$ios-spacing-md}
+.stage.completed .stage-icon{background-color:$ios-success;color:white;box-shadow:0 0 0 4px #e6f7e6}
+.stage.active .stage-icon{background-color:$ios-primary;color:white;box-shadow:0 0 0 4px #e8f0fe}
+.stage-name{font-size:$ios-font-size-sm;color:$ios-text-secondary;text-align:center;white-space:nowrap;font-weight:$ios-font-weight-medium}
+.stage.completed .stage-name,.stage.active .stage-name{color:$ios-text-primary}
+.current-stage-actions{background-color:$ios-background-tertiary;padding:$ios-spacing-lg;border-radius:$ios-radius-md;text-align:center}
+.current-stage-info h3{margin-top:0;margin-bottom:$ios-spacing-md;font-size:$ios-font-size-base}
+.stage-highlight{color:$ios-primary;font-weight:$ios-font-weight-semibold}
+
+/* 按钮样式 */
+.primary-btn,.secondary-btn{border:none;padding:$ios-spacing-sm $ios-spacing-lg;border-radius:$ios-radius-md;font-size:$ios-font-size-sm;cursor:pointer;font-weight:$ios-font-weight-medium;white-space:nowrap}
+.primary-btn{background-color:$ios-primary;color:white}
+.primary-btn:hover{background-color:#0056b3;transform:translateY(-1px)}
+.primary-btn:disabled{background-color:$ios-border;color:$ios-text-secondary;cursor:not-allowed;transform:none}
+.secondary-btn{background-color:$ios-background-tertiary;color:$ios-text-primary}
+.secondary-btn:hover{background-color:#f0f0f0;transform:translateY(-1px)}
+
+/* 渲染进度卡片 */
+.loading-state,.error-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:$ios-spacing-xl;text-align:center;gap:$ios-spacing-md}
+.loading-spinner{width:40px;height:40px;border:3px solid $ios-border;border-top:3px solid $ios-primary;border-radius:50%;animation:spin 1s linear infinite}
+@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
+.timeout-warning{background-color:#fff3cd;color:$ios-warning;padding:$ios-spacing-md;border-radius:$ios-radius-md;margin-bottom:$ios-spacing-lg;display:flex;align-items:center;gap:$ios-spacing-sm;border-left:4px solid $ios-warning}
+.warning-title{font-weight:$ios-font-weight-medium;display:block}
+.warning-time{font-size:$ios-font-size-sm;display:block}
+.progress-bar-container{position:relative;margin-bottom:$ios-spacing-lg}
+.progress-bar{width:100%;height:12px;background-color:$ios-background-tertiary;border-radius:$ios-radius-full;overflow:hidden}
+.progress-fill{height:100%;background-color:$ios-primary;border-radius:$ios-radius-full}
+.progress-percentage{position:absolute;right:0;top:-$ios-spacing-md;font-size:$ios-font-size-sm;font-weight:$ios-font-weight-medium;color:$ios-text-secondary}
+.progress-details{display:grid;grid-template-columns:1fr 1fr;gap:$ios-spacing-md}
+.progress-info{display:flex;flex-direction:column;gap:$ios-spacing-xs}
+.info-label{font-size:$ios-font-size-xs;color:$ios-text-secondary}
+.info-value{font-size:$ios-font-size-base;color:$ios-text-primary;font-weight:$ios-font-weight-medium}
+
+/* 模型误差检查清单卡片 */
+.checklist{display:flex;flex-direction:column;gap:$ios-spacing-md}
+.checklist-item{display:flex;align-items:center;padding:$ios-spacing-md;border-radius:$ios-radius-md;background-color:$ios-background-tertiary}
+.custom-checkbox{margin-right:$ios-spacing-md;width:20px;height:20px;accent-color:$ios-primary;border-radius:$ios-radius-sm}
+.checklist-text{color:$ios-text-primary;font-size:$ios-font-size-base;flex:1}
+.check-status{font-size:$ios-font-size-sm;font-weight:$ios-font-weight-medium;padding:$ios-spacing-xs $ios-spacing-sm;border-radius:$ios-radius-full}
+.check-status.passed{background-color:#e6f7e6;color:$ios-success}
+.check-status.failed{background-color:#ffebee;color:$ios-danger}
+
+/* 客户反馈卡片 */
+.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:$ios-spacing-xl;text-align:center;gap:$ios-spacing-md;color:$ios-text-secondary}
+.empty-icon{font-size:$ios-font-size-xl}
+.feedback-item{background-color:$ios-background-tertiary;border-radius:$ios-radius-md;padding:$ios-spacing-lg;margin-bottom:$ios-spacing-md}
+.feedback-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:$ios-spacing-md}
+.feedback-meta{display:flex;align-items:center;gap:$ios-spacing-sm}
+.feedback-type{font-size:$ios-font-size-sm;font-weight:$ios-font-weight-medium;color:$ios-text-primary}
+.feedback-tag{font-size:$ios-font-size-xs;background-color:#e8f0fe;color:$ios-primary;padding:$ios-spacing-xs $ios-spacing-sm;border-radius:$ios-radius-full;font-weight:$ios-font-weight-medium}
+.feedback-date{font-size:$ios-font-size-xs;color:$ios-text-secondary}
+.feedback-content{margin-bottom:$ios-spacing-md}
+.feedback-status{display:flex;align-items:center;gap:$ios-spacing-xs;margin-bottom:$ios-spacing-sm;font-size:$ios-font-size-sm}
+.status-label{color:$ios-text-secondary}
+.status-value{color:$ios-text-primary;font-weight:$ios-font-weight-medium}
+.feedback-countdown{display:flex;align-items:center;gap:$ios-spacing-xs;padding:$ios-spacing-xs $ios-spacing-sm;background-color:#fff3cd;color:$ios-warning;border-radius:$ios-radius-full;font-size:$ios-font-size-xs;margin-bottom:$ios-spacing-md}
+.countdown-icon{font-size:$ios-font-size-sm}
+.feedback-details{display:flex;flex-direction:column;gap:$ios-spacing-xs}
+.detail-item{display:flex;gap:$ios-spacing-sm;font-size:$ios-font-size-sm}
+.detail-label{color:$ios-text-secondary;width:80px;flex-shrink:0}
+.detail-value{color:$ios-text-primary;flex:1}
+.feedback-actions{display:flex;gap:$ios-spacing-md;justify-content:flex-end}
+
+/* 设计师变更记录卡片 */
+.change-actions{display:flex;gap:$ios-spacing-md;margin-bottom:$ios-spacing-lg;flex-wrap:wrap}
+.change-item{background-color:$ios-background-tertiary;border-radius:$ios-radius-md;padding:$ios-spacing-lg;margin-bottom:$ios-spacing-md}
+.change-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:$ios-spacing-md}
+.change-time{font-size:$ios-font-size-sm;color:$ios-text-secondary;font-weight:$ios-font-weight-semibold}
+.accept-change-btn{background-color:$ios-success;color:white;border:none;padding:$ios-spacing-xs $ios-spacing-md;border-radius:$ios-radius-md;font-size:$ios-font-size-sm;cursor:pointer;font-weight:$ios-font-weight-medium}
+.accept-change-btn:hover{background-color:#2e7d32;transform:translateY(-1px)}
+.designer-change-info{display:grid;grid-template-columns:1fr;gap:$ios-spacing-sm;margin-bottom:$ios-spacing-md}
+.designer-change{display:flex;gap:$ios-spacing-sm;align-items:center}
+.designer-label{color:$ios-text-secondary;font-size:$ios-font-size-sm;width:80px;flex-shrink:0}
+.designer-name{color:$ios-text-primary;font-size:$ios-font-size-base;font-weight:$ios-font-weight-medium}
+.workload-info{font-size:$ios-font-size-sm;color:$ios-text-secondary;margin-bottom:$ios-spacing-md}
+.achievements{margin-bottom:$ios-spacing-md}
+.achievements ul{margin:0;padding-left:$ios-spacing-lg}
+.achievements li{color:$ios-text-primary;font-size:$ios-font-size-sm;margin-bottom:$ios-spacing-xs;position:relative}
+.change-status{font-size:$ios-font-size-sm;color:$ios-text-secondary;background-color:$ios-background;padding:$ios-spacing-sm;border-radius:$ios-radius-md}
+
+/* 分阶段结算记录卡片 */
+.settlement-table{overflow-x:auto;border-radius:$ios-radius-md;background-color:$ios-background;border:1px solid $ios-border}
+.settlement-table table{width:100%;border-collapse:collapse}
+.settlement-table th,.settlement-table td{padding:$ios-spacing-md;text-align:left;border-bottom:1px solid $ios-border;font-size:$ios-font-size-sm}
+.settlement-table th{background-color:$ios-background-secondary;font-weight:$ios-font-weight-semibold;color:$ios-text-primary;position:sticky;top:0;z-index:10}
+.settlement-table td{color:$ios-text-primary;font-weight:$ios-font-weight-regular}
+.settlement-table tr:last-child td{border-bottom:none}
+.settlement-table tr:hover td{background-color:#f5f5f5}
+.status-badge{padding:$ios-spacing-xs $ios-spacing-sm;border-radius:$ios-radius-full;font-size:$ios-font-size-xs;font-weight:$ios-font-weight-medium}
+.status-pending{background-color:#fff3cd;color:$ios-warning}
+.status-settled{background-color:#e6f7e6;color:$ios-success}
+
+/* 响应式设计 */
+@media (max-width: 768px){
+.project-detail-container{padding:$ios-spacing-md}
+.main-content-grid{grid-template-columns:1fr;gap:$ios-spacing-lg}
+.project-header{flex-direction:column;align-items:flex-start;gap:$ios-spacing-md}
+.header-actions{width:100%;justify-content:flex-end}
+.project-info-card .info-grid{grid-template-columns:1fr;gap:$ios-spacing-md}
+.stage-progress{flex-direction:column;gap:$ios-spacing-lg}
+.stage-progress::before{display:none}
+.stage{flex-direction:row;gap:$ios-spacing-md;width:100%;justify-content:flex-start}
+.stage-icon{margin-bottom:0;width:36px;height:36px;font-size:$ios-font-size-sm}
+.progress-details{grid-template-columns:1fr;gap:$ios-spacing-md}
+.feedback-actions,.change-actions{flex-direction:column}
+.feedback-actions button,.change-actions button{width:100%}
+.tags-grid{grid-template-columns:1fr}
+.settlement-table th,.settlement-table td{padding:$ios-spacing-sm;font-size:$ios-font-size-xs}
+.stage-tag{font-size:$ios-font-size-xs}
+}
+
+/* 滚动条样式 */
+::-webkit-scrollbar{width:8px;height:8px}
+::-webkit-scrollbar-track{background:$ios-scrollbar-track;border-radius:$ios-radius-full}
+::-webkit-scrollbar-thumb{background:$ios-scrollbar-thumb;border-radius:$ios-radius-full}
+::-webkit-scrollbar-thumb:hover{background:$ios-scrollbar-thumb-hover}

+ 169 - 4
src/app/pages/designer/project-detail/project-detail.ts

@@ -1,6 +1,6 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';
 import { CommonModule } from '@angular/common';
-import { ActivatedRoute } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
 import { ProjectService } from '../../../services/project.service';
 import {
   Project,
@@ -18,7 +18,7 @@ import {
   templateUrl: './project-detail.html',
   styleUrl: './project-detail.scss'
 })
-export class ProjectDetail implements OnInit {
+export class ProjectDetail implements OnInit, OnDestroy {
   projectId: string = '';
   project: Project | undefined;
   renderProgress: RenderProgress | undefined;
@@ -30,17 +30,53 @@ export class ProjectDetail implements OnInit {
   reminderMessage: string = '';
   isLoadingRenderProgress: boolean = false;
   errorLoadingRenderProgress: boolean = false;
+  feedbackTimeoutCountdown: number = 0;
+  private countdownInterval: any;
+  projects: {id: string, name: string, status: string}[] = [];
+  showDropdown: boolean = false;
 
   constructor(
     private route: ActivatedRoute,
-    private projectService: ProjectService
+    private projectService: ProjectService,
+    private router: Router
   ) {}
 
+  // 切换项目
+  switchProject(projectId: string): void {
+    this.projectId = projectId;
+    this.loadProjectData();
+    // 更新URL但不触发组件重载
+    this.router.navigate([], { relativeTo: this.route, queryParamsHandling: 'merge', queryParams: { id: projectId } });
+  }
+
+  // 返回工作台
+  backToWorkbench(): void {
+    this.router.navigate(['/designer/dashboard']);
+  }
+
   ngOnInit(): void {
     this.projectId = this.route.snapshot.paramMap.get('id') || '';
     this.loadProjectData();
+    
+    // 添加点击事件监听器,当点击页面其他位置时关闭下拉菜单
+    document.addEventListener('click', this.closeDropdownOnClickOutside);
   }
 
+  // 在组件销毁时移除事件监听器
+  ngOnDestroy(): void {
+    document.removeEventListener('click', this.closeDropdownOnClickOutside);
+  }
+
+  // 点击页面其他位置时关闭下拉菜单
+  private closeDropdownOnClickOutside = (event: MouseEvent): void => {
+    const targetElement = event.target as HTMLElement;
+    const projectSwitcher = targetElement.closest('.project-switcher');
+    
+    if (!projectSwitcher && this.showDropdown) {
+      this.showDropdown = false;
+    }
+  };
+
   loadProjectData(): void {
     if (this.projectId) {
       this.loadProjectDetails();
@@ -51,11 +87,21 @@ export class ProjectDetail implements OnInit {
       this.loadSettlements();
       this.loadRequirementChecklist();
     }
+    
+    // 初始化项目列表数据(模拟)
+    this.projects = [
+      { id: '1', name: '现代风格客厅设计', status: '进行中' },
+      { id: '2', name: '北欧风卧室装修', status: '已完成' },
+      { id: '3', name: '新中式书房改造', status: '进行中' },
+      { id: '4', name: '工业风餐厅设计', status: '待处理' }
+    ];
   }
 
   loadProjectDetails(): void {
     this.projectService.getProjectById(this.projectId).subscribe(project => {
       this.project = project;
+      // 检查技能匹配度
+      this.checkSkillMismatch();
     });
   }
 
@@ -72,6 +118,11 @@ export class ProjectDetail implements OnInit {
         // 模拟API加载失败的情况
         if (!progress) {
           this.errorLoadingRenderProgress = true;
+          // 通知技术组长
+          this.notifyTeamLeader('render-failed');
+        } else {
+          // 检查是否需要显示超时预警
+          this.checkRenderTimeout();
         }
       });
     }, 1000);
@@ -86,6 +137,10 @@ export class ProjectDetail implements OnInit {
   loadCustomerFeedbacks(): void {
     this.projectService.getCustomerFeedbacks().subscribe(feedbacks => {
       this.feedbacks = feedbacks.filter(f => f.projectId === this.projectId);
+      // 为反馈添加分类标签
+      this.tagCustomerFeedbacks();
+      // 检查是否有需要处理的反馈并启动倒计时
+      this.checkFeedbackTimeout();
     });
   }
 
@@ -129,6 +184,11 @@ export class ProjectDetail implements OnInit {
   updateFeedbackStatus(feedbackId: string, status: '处理中' | '已解决'): void {
     this.projectService.updateFeedbackStatus(feedbackId, status).subscribe(() => {
       this.loadCustomerFeedbacks(); // 重新加载反馈
+      // 清除倒计时
+      if (this.countdownInterval) {
+        clearInterval(this.countdownInterval);
+        this.feedbackTimeoutCountdown = 0;
+      }
     });
   }
 
@@ -176,4 +236,109 @@ export class ProjectDetail implements OnInit {
     
     return null;
   }
+
+  // 检查渲染是否超时
+  checkRenderTimeout(): void {
+    if (!this.renderProgress || !this.project) return;
+    
+    // 模拟交付前3小时预警
+    const deliveryTime = new Date(this.project.deadline);
+    const currentTime = new Date();
+    const timeDifference = deliveryTime.getTime() - currentTime.getTime();
+    const hoursRemaining = Math.floor(timeDifference / (1000 * 60 * 60));
+    
+    if (hoursRemaining <= 3 && hoursRemaining > 0) {
+      // 弹窗预警
+      alert('渲染进度预警:交付前3小时,请关注渲染进度');
+    }
+    
+    if (hoursRemaining <= 1 && hoursRemaining > 0) {
+      // 更严重的预警
+      alert('渲染进度严重预警:交付前1小时,渲染可能无法按时完成!');
+    }
+  }
+
+  // 为客户反馈添加分类标签
+  tagCustomerFeedbacks(): void {
+    this.feedbacks.forEach(feedback => {
+      // 添加分类标签
+      if (feedback.content.includes('色彩') || feedback.problemLocation?.includes('色彩')) {
+        (feedback as any).tag = '色彩问题';
+      } else if (feedback.content.includes('家具') || feedback.problemLocation?.includes('家具')) {
+        (feedback as any).tag = '家具款式问题';
+      } else if (feedback.content.includes('光线') || feedback.content.includes('照明')) {
+        (feedback as any).tag = '光线问题';
+      } else {
+        (feedback as any).tag = '其他问题';
+      }
+    });
+  }
+  
+  // 获取反馈标签的辅助方法
+  getFeedbackTag(feedback: CustomerFeedback): string {
+    return (feedback as any).tag || '';
+  }
+
+  // 检查反馈超时
+  checkFeedbackTimeout(): void {
+    const pendingFeedbacks = this.feedbacks.filter(f => f.status === '待处理');
+    if (pendingFeedbacks.length > 0) {
+      // 启动1小时倒计时
+      this.feedbackTimeoutCountdown = 3600; // 3600秒 = 1小时
+      this.startCountdown();
+    }
+  }
+
+  // 启动倒计时
+  startCountdown(): void {
+    this.countdownInterval = setInterval(() => {
+      if (this.feedbackTimeoutCountdown > 0) {
+        this.feedbackTimeoutCountdown--;
+      } else {
+        clearInterval(this.countdownInterval);
+        // 超时提醒
+        alert('客户反馈已超过1小时未响应,请立即处理!');
+        this.notifyTeamLeader('feedback-overdue');
+      }
+    }, 1000);
+  }
+
+  // 通知技术组长
+  notifyTeamLeader(type: 'render-failed' | 'feedback-overdue' | 'skill-mismatch'): void {
+    // 实际应用中应调用消息服务通知组长
+    console.log(`通知技术组长:${type} - 项目ID: ${this.projectId}`);
+  }
+
+  // 检查技能匹配度并提示
+  checkSkillMismatch(): void {
+    const warning = this.getSkillMismatchWarning();
+    if (warning) {
+      // 显示技能不匹配警告
+      if (confirm(`${warning}\n是否联系技术组长协调支持?`)) {
+        this.notifyTeamLeader('skill-mismatch');
+      }
+    }
+  }
+
+  // 发起设计师变更
+  initiateDesignerChange(reason: string): void {
+    // 实际应用中应调用API发起变更
+    console.log(`发起设计师变更,原因:${reason}`);
+    alert('已发起设计师变更申请,请等待新设计师承接');
+  }
+
+  // 确认承接变更项目
+  acceptDesignerChange(changeId: string): void {
+    // 实际应用中应调用API确认承接
+    console.log(`确认承接设计师变更:${changeId}`);
+    alert('已确认承接项目,系统已记录时间戳和责任人');
+  }
+
+  // 格式化倒计时显示
+  formatCountdown(seconds: number): string {
+    const hours = Math.floor(seconds / 3600);
+    const minutes = Math.floor((seconds % 3600) / 60);
+    const remainingSeconds = seconds % 60;
+    return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
+  }
 }