0235711 преди 2 дни
родител
ревизия
3ac836aad2

Файловите разлики са ограничени, защото са твърде много
+ 0 - 196
src/app/app.html


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

@@ -0,0 +1,126 @@
+// 项目模型
+export interface Project {
+  id: string;
+  name: string;
+  customerName: string;
+  customerTags: CustomerTag[];
+  highPriorityNeeds: string[];
+  status: ProjectStatus;
+  currentStage: ProjectStage;
+  createdAt: Date;
+  deadline: Date;
+  assigneeId: string;
+  assigneeName: string;
+  skillsRequired: string[];
+}
+
+// 客户标签
+export interface CustomerTag {
+  source: '朋友圈' | '信息流';
+  needType: '硬装' | '软装';
+  preference: '现代' | '宋式' | '欧式';
+  colorAtmosphere: string;
+}
+
+// 项目状态
+export type ProjectStatus = '进行中' | '已完成' | '已暂停' | '已延期';
+
+// 项目阶段
+export type ProjectStage = '前期沟通' | '建模' | '软装' | '渲染' | '后期' | '完成';
+
+// 任务模型
+export interface Task {
+  id: string;
+  projectId: string;
+  projectName: string;
+  title: string;
+  stage: ProjectStage;
+  deadline: Date;
+  isOverdue: boolean;
+  isCompleted: boolean;
+  completedAt?: Date;
+}
+
+// 渲染进度
+export interface RenderProgress {
+  id: string;
+  projectId: string;
+  completionRate: number; // 百分比
+  estimatedTimeRemaining: number; // 小时
+  status: '进行中' | '已完成' | '已失败';
+  updatedAt: Date;
+}
+
+// 模型误差检查项
+export interface ModelCheckItem {
+  id: string;
+  name: string;
+  isPassed: boolean;
+  notes?: string;
+}
+
+// 客户反馈
+export interface CustomerFeedback {
+  id: string;
+  projectId: string;
+  content: string;
+  isSatisfied: boolean;
+  problemLocation?: string;
+  expectedEffect?: string;
+  referenceCase?: string;
+  status: '待处理' | '处理中' | '已解决';
+  createdAt: Date;
+  updatedAt?: Date;
+}
+
+// 设计师变更记录
+export interface DesignerChange {
+  id: string;
+  projectId: string;
+  oldDesignerId: string;
+  oldDesignerName: string;
+  newDesignerId: string;
+  newDesignerName: string;
+  changeTime: Date;
+  acceptanceTime?: Date;
+  historicalAchievements: string[];
+  completedWorkload: number; // 百分比
+}
+
+// 结算记录
+export interface Settlement {
+  id: string;
+  projectId: string;
+  stage: ProjectStage;
+  amount: number;
+  percentage: number;
+  status: '待结算' | '已结算';
+  createdAt: Date;
+  settledAt?: Date;
+  completionTime?: Date;
+}
+
+// 技能标签
+export interface SkillTag {
+  id: string;
+  name: string;
+  level: number; // 1-5
+  count: number; // 完成项目数量
+}
+
+// 绩效数据
+export interface PerformanceData {
+  month: string;
+  projectCompletionRate: number;
+  customerSatisfaction: number;
+  deliveryOnTimeRate: number;
+}
+
+// 匹配订单
+export interface MatchingOrder {
+  id: string;
+  projectName: string;
+  requiredSkills: string[];
+  matchRate: number;
+  customerLevel: '优质' | '普通';
+}

+ 100 - 1
src/app/pages/designer/dashboard/dashboard.html

@@ -1 +1,100 @@
-<p>dashboard works!</p>
+<div class="dashboard-container">
+  <header class="dashboard-header">
+    <h1>设计师工作台</h1>
+  </header>
+
+  <main class="dashboard-main">
+    <!-- 待办任务区域 -->
+    <section class="task-section">
+      <div class="section-header">
+        <h2>待办任务</h2>
+      </div>
+      
+      <div class="task-list">
+        <div *ngIf="tasks.length === 0" class="empty-state">
+          暂无待办任务
+        </div>
+        
+        <div *ngFor="let task of tasks" class="task-item">
+          <div class="task-header">
+            <h3>{{ task.title }}</h3>
+            <span class="task-stage">{{ task.stage }}</span>
+          </div>
+          <div class="task-info">
+            <p class="project-name">项目: {{ task.projectName }}</p>
+            <p class="deadline" [class.overdue]="task.isOverdue">
+              截止日期: {{ task.deadline | date:'yyyy-MM-dd HH:mm' }}
+              <span *ngIf="task.isOverdue" class="overdue-badge">已超期</span>
+            </p>
+          </div>
+          <div class="task-actions">
+            <button *ngIf="!task.isCompleted" (click)="markTaskAsCompleted(task.id)" class="btn-complete">
+              标记完成
+            </button>
+            <button [routerLink]="['/designer/project-detail', task.projectId]" class="btn-detail">
+              查看详情
+            </button>
+          </div>
+        </div>
+      </div>
+    </section>
+
+    <!-- 时间预警区域 -->
+    <section class="warning-section">
+      <div class="section-header">
+        <h2>时间预警</h2>
+      </div>
+      
+      <div class="warning-list">
+        <div *ngIf="overdueTasks.length === 0" class="empty-state">
+          暂无超期风险任务
+        </div>
+        
+        <div *ngFor="let task of overdueTasks" class="warning-item">
+          <div class="warning-content">
+            <p class="warning-title">{{ task.title }} - 已超期</p>
+            <p class="warning-detail">项目: {{ task.projectName }},截止日期: {{ task.deadline | date:'yyyy-MM-dd HH:mm' }}</p>
+          </div>
+          <div class="warning-actions">
+            <button (click)="generateReminderMessage()" class="btn-generate-reminder">
+              生成提醒话术
+            </button>
+          </div>
+        </div>
+      </div>
+    </section>
+
+    <!-- 快速入口区域 -->
+    <section class="quick-access-section">
+      <div class="section-header">
+        <h2>快速入口</h2>
+      </div>
+      
+      <div class="quick-access-grid">
+        <a [routerLink]="['/designer/project-detail', tasks[0]?.projectId]" class="quick-access-item">
+          <h3>待确认项目</h3>
+          <p>{{ tasks.length }}个项目待处理</p>
+        </a>
+        
+        <a [routerLink]="['/designer/project-detail', feedbackProjectId]" class="quick-access-item">
+          <h3>待反馈项目</h3>
+          <p>{{ overdueTasks.length }}个项目需反馈</p>
+        </a>
+        
+        <a [routerLink]="['/designer/personal-board']" class="quick-access-item">
+          <h3>个人看板</h3>
+          <p>查看绩效与结算</p>
+        </a>
+      </div>
+    </section>
+  </main>
+
+  <!-- 提醒话术弹窗 -->
+  <div *ngIf="reminderMessage" class="reminder-modal">
+    <div class="modal-content">
+      <h3>提醒话术</h3>
+      <p>{{ reminderMessage }}</p>
+      <button (click)="clearReminder()" class="btn-close">关闭</button>
+    </div>
+  </div>
+</div>

+ 318 - 0
src/app/pages/designer/dashboard/dashboard.scss

@@ -0,0 +1,318 @@
+.dashboard-container {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 20px;
+}
+
+.dashboard-header {
+  margin-bottom: 30px;
+  h1 {
+    font-size: 28px;
+    color: #333;
+    font-weight: 600;
+  }
+}
+
+.dashboard-main {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 20px;
+  
+  .task-section {
+    grid-column: 1 / -1;
+  }
+}
+
+.section-header {
+  margin-bottom: 15px;
+  h2 {
+    font-size: 20px;
+    color: #555;
+    font-weight: 500;
+    display: flex;
+    align-items: center;
+    &::before {
+      content: '';
+      display: inline-block;
+      width: 4px;
+      height: 20px;
+      background-color: #1890ff;
+      margin-right: 8px;
+    }
+  }
+}
+
+.task-list {
+  display: grid;
+  gap: 15px;
+}
+
+.task-item {
+  background: #fff;
+  border-radius: 8px;
+  padding: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  transition: all 0.3s ease;
+  
+  &:hover {
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+    transform: translateY(-2px);
+  }
+  
+  .task-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 10px;
+    
+    h3 {
+      font-size: 16px;
+      color: #333;
+      margin: 0;
+    }
+    
+    .task-stage {
+      font-size: 14px;
+      padding: 4px 12px;
+      border-radius: 16px;
+      background-color: #e6f7ff;
+      color: #1890ff;
+    }
+  }
+  
+  .task-info {
+    margin-bottom: 15px;
+    
+    .project-name {
+      font-size: 14px;
+      color: #666;
+      margin: 5px 0;
+    }
+    
+    .deadline {
+      font-size: 14px;
+      color: #666;
+      margin: 5px 0;
+      
+      &.overdue {
+        color: #f5222d;
+      }
+      
+      .overdue-badge {
+        background-color: #ffccc7;
+        color: #f5222d;
+        padding: 2px 8px;
+        border-radius: 12px;
+        font-size: 12px;
+        margin-left: 8px;
+      }
+    }
+  }
+  
+  .task-actions {
+    display: flex;
+    gap: 10px;
+    
+    button {
+      padding: 8px 16px;
+      border: none;
+      border-radius: 4px;
+      font-size: 14px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+    }
+    
+    .btn-complete {
+      background-color: #52c41a;
+      color: white;
+      
+      &:hover {
+        background-color: #73d13d;
+      }
+    }
+    
+    .btn-detail {
+      background-color: #1890ff;
+      color: white;
+      
+      &:hover {
+        background-color: #40a9ff;
+      }
+    }
+  }
+}
+
+.warning-section {
+  grid-column: 1 / -1;
+}
+
+.warning-list {
+  display: grid;
+  gap: 15px;
+}
+
+.warning-item {
+  background: #fff7e6;
+  border: 1px solid #ffd591;
+  border-radius: 8px;
+  padding: 15px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  
+  .warning-content {
+    
+    .warning-title {
+      font-size: 16px;
+      color: #d46b08;
+      font-weight: 500;
+      margin: 0 0 5px 0;
+    }
+    
+    .warning-detail {
+      font-size: 14px;
+      color: #fa8c16;
+      margin: 0;
+    }
+  }
+  
+  .warning-actions {
+    
+    .btn-generate-reminder {
+      padding: 6px 16px;
+      border: 1px solid #fa8c16;
+      border-radius: 4px;
+      background-color: white;
+      color: #fa8c16;
+      font-size: 14px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      
+      &:hover {
+        background-color: #fa8c16;
+        color: white;
+      }
+    }
+  }
+}
+
+.quick-access-section {
+  grid-column: 1 / -1;
+}
+
+.quick-access-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+  gap: 15px;
+}
+
+.quick-access-item {
+  background: #fff;
+  border-radius: 8px;
+  padding: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  text-decoration: none;
+  transition: all 0.3s ease;
+  
+  &:hover {
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+    transform: translateY(-2px);
+  }
+  
+  h3 {
+    font-size: 18px;
+    color: #333;
+    margin: 0 0 10px 0;
+  }
+  
+  p {
+    font-size: 14px;
+    color: #666;
+    margin: 0;
+  }
+}
+
+.empty-state {
+  text-align: center;
+  padding: 40px 20px;
+  color: #999;
+  background-color: #fafafa;
+  border-radius: 8px;
+}
+
+.reminder-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1000;
+}
+
+.modal-content {
+  background: white;
+  border-radius: 8px;
+  padding: 20px;
+  max-width: 400px;
+  width: 90%;
+  text-align: center;
+  
+  h3 {
+    font-size: 18px;
+    color: #333;
+    margin: 0 0 15px 0;
+  }
+  
+  p {
+    font-size: 14px;
+    color: #666;
+    margin: 0 0 20px 0;
+    line-height: 1.5;
+  }
+  
+  .btn-close {
+    padding: 8px 20px;
+    border: none;
+    border-radius: 4px;
+    background-color: #1890ff;
+    color: white;
+    font-size: 14px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    
+    &:hover {
+      background-color: #40a9ff;
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .dashboard-main {
+    grid-template-columns: 1fr;
+  }
+  
+  .task-actions {
+    flex-direction: column;
+    
+    button {
+      width: 100%;
+    }
+  }
+  
+  .warning-item {
+    flex-direction: column;
+    gap: 15px;
+    
+    .warning-actions {
+      width: 100%;
+      
+      .btn-generate-reminder {
+        width: 100%;
+      }
+    }
+  }
+}

+ 66 - 3
src/app/pages/designer/dashboard/dashboard.ts

@@ -1,11 +1,74 @@
-import { Component } from '@angular/core';
+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';
 
 @Component({
   selector: 'app-dashboard',
-  imports: [],
+  imports: [CommonModule, RouterModule],
   templateUrl: './dashboard.html',
   styleUrl: './dashboard.scss'
 })
-export class Dashboard {
+export class Dashboard implements OnInit {
+  tasks: Task[] = [];
+  overdueTasks: Task[] = [];
+  reminderMessage: string = '';
+  feedbackProjectId: string = '';
 
+  constructor(private projectService: ProjectService) {}
+
+  ngOnInit(): void {
+    this.loadTasks();
+  }
+
+  loadTasks(): void {
+    this.projectService.getTasks().subscribe(tasks => {
+      this.tasks = tasks.sort((a, b) => {
+        // 按阶段优先级排序:建模 > 对图 > 反馈处理 > 其他
+        const stagePriority: Record<string, number> = {
+          '建模': 4,
+          '对图': 3,
+          '反馈处理': 2,
+          '渲染': 1,
+          '后期': 1,
+          '完成': 0
+        };
+        
+        const priorityA = stagePriority[a.stage] || 0;
+        const priorityB = stagePriority[b.stage] || 0;
+        
+        if (priorityA !== priorityB) {
+          return priorityB - priorityA;
+        }
+        
+        // 优先级相同时,按截止日期排序
+        return a.deadline.getTime() - b.deadline.getTime();
+      });
+      
+      // 筛选超期任务
+      this.overdueTasks = this.tasks.filter(task => task.isOverdue);
+      
+      // 设置反馈项目ID
+      if (this.overdueTasks.length > 0) {
+        this.feedbackProjectId = this.overdueTasks[0].projectId;
+      }
+    });
+  }
+
+  markTaskAsCompleted(taskId: string): void {
+    this.projectService.markTaskAsCompleted(taskId).subscribe(() => {
+      this.loadTasks(); // 重新加载任务列表
+    });
+  }
+
+  generateReminderMessage(): void {
+    this.projectService.generateReminderMessage('overdue').subscribe(message => {
+      this.reminderMessage = message;
+    });
+  }
+
+  clearReminder(): void {
+    this.reminderMessage = '';
+  }
 }

+ 164 - 1
src/app/pages/designer/personal-board/personal-board.html

@@ -1 +1,164 @@
-<p>personal-board works!</p>
+<div class="personal-board-container">
+  <header class="personal-board-header">
+    <h1>个人看板</h1>
+  </header>
+
+  <main class="personal-board-main">
+    <!-- 能力看板区域 -->
+    <section class="skill-board-section">
+      <div class="section-header">
+        <h2>能力看板</h2>
+        <p>根据项目经验自动更新的技能标签</p>
+      </div>
+      
+      <div class="skill-tags-container">
+        <div *ngFor="let tag of skillTags" class="skill-tag" [class.level-high]="tag.level >= 4" [class.level-medium]="tag.level >= 2 && tag.level < 4">
+          <span class="tag-name">{{ tag.name }}</span>
+          <div class="tag-level">
+            <div class="level-bar">
+              <div class="level-fill" [style.width.%]="(tag.level / 5) * 100"></div>
+            </div>
+            <span class="level-text">{{ tag.level }}/5</span>
+          </div>
+          <span class="tag-count">{{ tag.count }}个项目</span>
+        </div>
+      </div>
+
+      <!-- 匹配订单推荐 -->
+      <div class="matching-orders-section">
+        <h3>推荐匹配订单</h3>
+        <div class="order-list">
+          <div *ngIf="matchingOrders.length === 0" class="empty-state">
+            暂无匹配订单
+          </div>
+          
+          <div *ngFor="let order of matchingOrders" class="order-item">
+            <div class="order-header">
+              <h4>{{ order.projectName }}</h4>
+              <span class="match-rate" [class.high-match]="order.matchRate >= 80" [class.medium-match]="order.matchRate >= 60 && order.matchRate < 80">
+                匹配度 {{ order.matchRate }}%
+              </span>
+            </div>
+            <div class="order-info">
+              <div class="required-skills">
+                <span *ngFor="let skill of order.requiredSkills" class="skill-badge">{{ skill }}</span>
+              </div>
+              <span class="customer-level" [class.premium]="order.customerLevel === '优质'">
+                {{ order.customerLevel }}客户
+              </span>
+            </div>
+            <button (click)="acceptOrder(order.id)" class="btn-accept-order">
+              是否接单?
+            </button>
+          </div>
+        </div>
+      </div>
+    </section>
+
+    <!-- 结算统计区域 -->
+    <section class="settlement-section">
+      <div class="section-header">
+        <h2>结算统计</h2>
+        <p>按阶段计算的待结算金额</p>
+      </div>
+      
+      <div class="settlement-summary">
+        <div class="summary-item">
+          <h3>总结算金额</h3>
+          <p class="amount total">{{ totalSettlementAmount | currency:'CNY':'symbol':'1.0-0' }}</p>
+        </div>
+        <div class="summary-item">
+          <h3>待结算金额</h3>
+          <p class="amount pending">{{ pendingSettlementAmount | currency:'CNY':'symbol':'1.0-0' }}</p>
+        </div>
+        <button (click)="viewSettlementDetails()" class="btn-view-details">
+          查看明细
+        </button>
+      </div>
+      
+      <div class="settlement-details">
+        <h3>结算明细</h3>
+        <table class="settlement-table">
+          <thead>
+            <tr>
+              <th>阶段</th>
+              <th>金额</th>
+              <th>比例</th>
+              <th>状态</th>
+              <th>创建日期</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr *ngIf="settlements.length === 0">
+              <td colspan="5" class="empty-row">暂无结算记录</td>
+            </tr>
+            <tr *ngFor="let settlement of settlements">
+              <td>{{ settlement.stage }}</td>
+              <td>{{ settlement.amount | currency:'CNY':'symbol':'1.0-0' }}</td>
+              <td>{{ settlement.percentage }}%</td>
+              <td>
+                <span class="status-badge" [class.settled]="settlement.status === '已结算'">
+                  {{ settlement.status }}
+                </span>
+              </td>
+              <td>{{ settlement.createdAt | date:'yyyy-MM-dd' }}</td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </section>
+
+    <!-- 绩效趋势区域 -->
+    <section class="performance-section">
+      <div class="section-header">
+        <h2>绩效趋势</h2>
+        <p>项目完成率/客户满意度/交付准时率</p>
+      </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>
+    </section>
+  </main>
+</div>

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

@@ -0,0 +1,532 @@
+.personal-board-container {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 20px;
+}
+
+.personal-board-header {
+  margin-bottom: 30px;
+  h1 {
+    font-size: 28px;
+    color: #333;
+    font-weight: 600;
+  }
+}
+
+.personal-board-main {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 30px;
+  
+  .skill-board-section,
+  .settlement-section {
+    grid-column: 1 / -1;
+  }
+}
+
+.section-header {
+  margin-bottom: 20px;
+  h2 {
+    font-size: 20px;
+    color: #333;
+    font-weight: 500;
+    margin: 0 0 5px 0;
+  }
+  p {
+    font-size: 14px;
+    color: #666;
+    margin: 0;
+  }
+}
+
+/* 能力看板样式 */
+.skill-tags-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 15px;
+  margin-bottom: 30px;
+}
+
+.skill-tag {
+  background: #f0f2f5;
+  border-radius: 8px;
+  padding: 15px 20px;
+  min-width: 200px;
+  transition: all 0.3s ease;
+  
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  }
+  
+  &.level-high {
+    background: #e6f7ff;
+    border: 1px solid #91d5ff;
+  }
+  
+  &.level-medium {
+    background: #f6ffed;
+    border: 1px solid #b7eb8f;
+  }
+  
+  .tag-name {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333;
+    display: block;
+    margin-bottom: 10px;
+  }
+  
+  .tag-level {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    margin-bottom: 5px;
+    
+    .level-bar {
+      flex: 1;
+      height: 6px;
+      background: #d9d9d9;
+      border-radius: 3px;
+      overflow: hidden;
+      
+      .level-fill {
+        height: 100%;
+        background: #1890ff;
+        transition: width 0.3s ease;
+      }
+    }
+    
+    .level-text {
+      font-size: 12px;
+      color: #666;
+      min-width: 35px;
+    }
+  }
+  
+  .tag-count {
+    font-size: 12px;
+    color: #999;
+  }
+}
+
+.matching-orders-section {
+  
+  h3 {
+    font-size: 18px;
+    color: #333;
+    margin: 0 0 15px 0;
+  }
+  
+  .order-list {
+    display: grid;
+    gap: 15px;
+  }
+  
+  .order-item {
+    background: #fff;
+    border-radius: 8px;
+    padding: 20px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    transition: all 0.3s ease;
+    
+    &:hover {
+      box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+      transform: translateY(-2px);
+    }
+    
+    .order-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 10px;
+      
+      h4 {
+        font-size: 16px;
+        color: #333;
+        margin: 0;
+      }
+      
+      .match-rate {
+        font-size: 14px;
+        padding: 4px 12px;
+        border-radius: 16px;
+        background-color: #f5f5f5;
+        color: #666;
+        
+        &.high-match {
+          background-color: #f6ffed;
+          color: #52c41a;
+          border: 1px solid #b7eb8f;
+        }
+        
+        &.medium-match {
+          background-color: #fff7e6;
+          color: #fa8c16;
+          border: 1px solid #ffd591;
+        }
+      }
+    }
+    
+    .order-info {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 15px;
+      
+      .required-skills {
+        display: flex;
+        gap: 8px;
+        
+        .skill-badge {
+          font-size: 12px;
+          padding: 2px 8px;
+          background: #f0f2f5;
+          color: #666;
+          border-radius: 12px;
+        }
+      }
+      
+      .customer-level {
+        font-size: 14px;
+        color: #666;
+        
+        &.premium {
+          color: #fa8c16;
+          font-weight: 500;
+        }
+      }
+    }
+    
+    .btn-accept-order {
+      width: 100%;
+      padding: 10px;
+      border: 1px solid #1890ff;
+      border-radius: 4px;
+      background-color: white;
+      color: #1890ff;
+      font-size: 14px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      
+      &:hover {
+        background-color: #1890ff;
+        color: white;
+      }
+    }
+  }
+}
+
+/* 结算统计样式 */
+.settlement-summary {
+  background: #fff;
+  border-radius: 8px;
+  padding: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  margin-bottom: 20px;
+  display: grid;
+  grid-template-columns: 1fr 1fr auto;
+  gap: 30px;
+  align-items: center;
+  
+  .summary-item {
+    
+    h3 {
+      font-size: 16px;
+      color: #666;
+      margin: 0 0 5px 0;
+      font-weight: normal;
+    }
+    
+    .amount {
+      font-size: 24px;
+      font-weight: 600;
+      margin: 0;
+      
+      &.total {
+        color: #333;
+      }
+      
+      &.pending {
+        color: #1890ff;
+      }
+    }
+  }
+  
+  .btn-view-details {
+    padding: 10px 20px;
+    border: 1px solid #1890ff;
+    border-radius: 4px;
+    background-color: white;
+    color: #1890ff;
+    font-size: 14px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    white-space: nowrap;
+    
+    &:hover {
+      background-color: #1890ff;
+      color: white;
+    }
+  }
+}
+
+.settlement-details {
+  
+  h3 {
+    font-size: 18px;
+    color: #333;
+    margin: 0 0 15px 0;
+  }
+  
+  .settlement-table {
+    width: 100%;
+    border-collapse: collapse;
+    background: #fff;
+    border-radius: 8px;
+    overflow: hidden;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    
+    thead {
+      background: #fafafa;
+      
+      th {
+        padding: 12px 16px;
+        text-align: left;
+        font-size: 14px;
+        font-weight: 500;
+        color: #666;
+        border-bottom: 1px solid #f0f0f0;
+      }
+    }
+    
+    tbody {
+      
+      td {
+        padding: 12px 16px;
+        font-size: 14px;
+        color: #333;
+        border-bottom: 1px solid #f0f0f0;
+        
+        &:last-child {
+          color: #999;
+        }
+      }
+      
+      .empty-row {
+        text-align: center;
+        color: #999;
+        font-style: italic;
+        padding: 40px;
+      }
+      
+      .status-badge {
+        padding: 2px 8px;
+        border-radius: 12px;
+        font-size: 12px;
+        
+        &.settled {
+          background-color: #f6ffed;
+          color: #52c41a;
+          border: 1px solid #b7eb8f;
+        }
+        
+        &:not(.settled) {
+          background-color: #fff1f0;
+          color: #f5222d;
+          border: 1px solid #ffccc7;
+        }
+      }
+    }
+  }
+}
+
+/* 绩效趋势样式 */
+.performance-chart {
+  background: #fff;
+  border-radius: 8px;
+  padding: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  
+  .chart-container {
+    display: flex;
+    align-items: flex-end;
+    height: 300px;
+    margin-bottom: 20px;
+    
+    .chart-labels {
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      height: 100%;
+      margin-right: 20px;
+      
+      span {
+        font-size: 12px;
+        color: #999;
+        padding: 5px 0;
+      }
+    }
+    
+    .chart-bars {
+      flex: 1;
+      display: flex;
+      justify-content: space-around;
+      align-items: flex-end;
+      height: 100%;
+      
+      .bar-group {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 5px;
+        
+        .bar {
+          position: relative;
+          width: 40px;
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          transition: all 0.3s ease;
+          
+          &:hover {
+            transform: translateY(-5px);
+          }
+          
+          .bar-fill {
+            width: 100%;
+            border-radius: 4px 4px 0 0;
+            transition: height 0.3s ease;
+          }
+          
+          .bar-value {
+            position: absolute;
+            top: -25px;
+            font-size: 12px;
+            color: #666;
+            white-space: nowrap;
+          }
+          
+          &.completion-rate .bar-fill {
+            background: #1890ff;
+          }
+          
+          &.satisfaction-rate .bar-fill {
+            background: #52c41a;
+          }
+          
+          &.ontime-rate .bar-fill {
+            background: #fa8c16;
+          }
+        }
+      }
+    }
+  }
+  
+  .chart-legend {
+    display: flex;
+    justify-content: center;
+    gap: 30px;
+    
+    .legend-item {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      font-size: 14px;
+      color: #666;
+      
+      .legend-color {
+        width: 16px;
+        height: 4px;
+        border-radius: 2px;
+        
+        &.completion-rate {
+          background: #1890ff;
+        }
+        
+        &.satisfaction-rate {
+          background: #52c41a;
+        }
+        
+        &.ontime-rate {
+          background: #fa8c16;
+        }
+      }
+    }
+  }
+}
+
+.empty-state {
+  text-align: center;
+  padding: 40px 20px;
+  color: #999;
+  background-color: #fafafa;
+  border-radius: 8px;
+}
+
+@media (max-width: 768px) {
+  .personal-board-main {
+    grid-template-columns: 1fr;
+  }
+  
+  .skill-tags-container {
+    justify-content: center;
+  }
+  
+  .skill-tag {
+    min-width: 100%;
+  }
+  
+  .settlement-summary {
+    grid-template-columns: 1fr;
+    gap: 20px;
+    
+    .btn-view-details {
+      width: 100%;
+    }
+  }
+  
+  .performance-chart {
+    .chart-container {
+      flex-direction: column;
+      height: auto;
+      
+      .chart-labels {
+        flex-direction: row;
+        width: 100%;
+        margin-right: 0;
+        margin-bottom: 20px;
+        justify-content: space-around;
+      }
+      
+      .chart-bars {
+        flex-direction: column;
+        height: 300px;
+        
+        .bar-group {
+          flex-direction: row;
+          width: 100%;
+          gap: 10px;
+          justify-content: center;
+          
+          .bar {
+            height: 20px;
+            width: auto;
+            
+            .bar-fill {
+              border-radius: 0;
+              height: 100%;
+              width: 80px;
+            }
+            
+            .bar-value {
+              top: -25px;
+              left: 50%;
+              transform: translateX(-50%);
+            }
+          }
+        }
+      }
+    }
+  }
+}

+ 71 - 3
src/app/pages/designer/personal-board/personal-board.ts

@@ -1,11 +1,79 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { ProjectService } from '../../../services/project.service';
+import {
+  SkillTag,
+  PerformanceData,
+  MatchingOrder,
+  Settlement
+} from '../../../models/project.model';
 
 @Component({
   selector: 'app-personal-board',
-  imports: [],
+  imports: [CommonModule, RouterModule],
   templateUrl: './personal-board.html',
   styleUrl: './personal-board.scss'
 })
-export class PersonalBoard {
+export class PersonalBoard implements OnInit {
+  skillTags: SkillTag[] = [];
+  performanceData: PerformanceData[] = [];
+  matchingOrders: MatchingOrder[] = [];
+  settlements: Settlement[] = [];
+  totalSettlementAmount: number = 0;
+  pendingSettlementAmount: number = 0;
 
+  constructor(private projectService: ProjectService) {}
+
+  ngOnInit(): void {
+    this.loadData();
+  }
+
+  loadData(): void {
+    this.loadSkillTags();
+    this.loadPerformanceData();
+    this.loadMatchingOrders();
+    this.loadSettlements();
+  }
+
+  loadSkillTags(): void {
+    this.projectService.getSkillTags().subscribe(tags => {
+      this.skillTags = tags.sort((a, b) => b.level - a.level);
+    });
+  }
+
+  loadPerformanceData(): void {
+    this.projectService.getPerformanceData().subscribe(data => {
+      this.performanceData = data;
+    });
+  }
+
+  loadMatchingOrders(): void {
+    this.projectService.getMatchingOrders().subscribe(orders => {
+      this.matchingOrders = orders.sort((a, b) => b.matchRate - a.matchRate);
+    });
+  }
+
+  loadSettlements(): void {
+    this.projectService.getSettlements().subscribe(settlements => {
+      this.settlements = settlements;
+      
+      // 计算总金额和待结算金额
+      this.totalSettlementAmount = settlements.reduce((sum, s) => sum + s.amount, 0);
+      this.pendingSettlementAmount = settlements
+        .filter(s => s.status === '待结算')
+        .reduce((sum, s) => sum + s.amount, 0);
+    });
+  }
+
+  acceptOrder(orderId: string): void {
+    // 在实际应用中,这里应该调用API来接受订单
+    console.log('接受订单:', orderId);
+    // 可以添加一些UI反馈,比如显示成功消息等
+  }
+
+  viewSettlementDetails(): void {
+    // 在实际应用中,这里可以跳转到结算详情页面
+    console.log('查看结算明细');
+  }
 }

+ 252 - 1
src/app/pages/designer/project-detail/project-detail.html

@@ -1 +1,252 @@
-<p>project-detail works!</p>
+<div class="project-detail-container">
+  <!-- 项目标题栏 -->
+  <div class="project-header">
+    <h1>项目详情</h1>
+    <div class="project-id">项目ID: {{ projectId }}</div>
+  </div>
+
+  <!-- 提醒消息弹窗 -->
+  <div *ngIf="reminderMessage" class="reminder-popup">
+    {{ 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>
+    </div>
+  </div>
+
+  <!-- 客户画像与技能匹配度 -->
+  <div class="customer-profile-section">
+    <h2>客户画像</h2>
+    
+    <!-- 技能匹配度警告 -->
+    <div *ngIf="getSkillMismatchWarning()" class="skill-mismatch-warning">
+      {{ getSkillMismatchWarning() }}
+    </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>
+          <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>
+        </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>
+      </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>
+      </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>
+      </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>
+      <div class="progress-info">
+        <div>完成率: {{ renderProgress.completionRate }}%</div>
+        <div>预计剩余时间: {{ renderProgress.estimatedTimeRemaining }} 小时</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>
+    </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>
+    </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>
+    </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>
+  </div>
+
+  <!-- 停滞期设置 -->
+  <div class="stagnation-section">
+    <h2>停滞期管理</h2>
+    <button (click)="generateReminderMessage()">设置停滞</button>
+    <p>点击后将生成通知,提醒客户项目将暂时停滞</p>
+  </div>
+</div>

+ 595 - 0
src/app/pages/designer/project-detail/project-detail.scss

@@ -0,0 +1,595 @@
+.project-detail-container {
+  padding: 20px;
+  max-width: 1200px;
+  margin: 0 auto;
+  font-family: 'Arial', sans-serif;
+}
+
+/* 项目标题栏 */
+.project-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 30px;
+  padding-bottom: 15px;
+  border-bottom: 2px solid #eee;
+}
+
+.project-header h1 {
+  font-size: 28px;
+  color: #333;
+  margin: 0;
+}
+
+.project-id {
+  font-size: 14px;
+  color: #666;
+  background-color: #f5f5f5;
+  padding: 5px 10px;
+  border-radius: 4px;
+}
+
+/* 提醒消息弹窗 */
+.reminder-popup {
+  position: fixed;
+  top: 20px;
+  right: 20px;
+  background-color: #4CAF50;
+  color: white;
+  padding: 15px 20px;
+  border-radius: 4px;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+  z-index: 1000;
+  animation: slideInRight 0.3s ease-out;
+}
+
+@keyframes slideInRight {
+  from {
+    transform: translateX(100%);
+    opacity: 0;
+  }
+  to {
+    transform: translateX(0);
+    opacity: 1;
+  }
+}
+
+/* 通用区域样式 */
+.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: white;
+  border-radius: 8px;
+  padding: 20px;
+  margin-bottom: 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+h2 {
+  font-size: 20px;
+  color: #333;
+  margin-top: 0;
+  margin-bottom: 20px;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #eee;
+}
+
+h3 {
+  font-size: 16px;
+  color: #555;
+  margin-top: 0;
+  margin-bottom: 15px;
+}
+
+/* 项目基本信息 */
+.info-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+  gap: 15px;
+}
+
+.info-item {
+  display: flex;
+  flex-direction: column;
+}
+
+.info-item label {
+  font-size: 14px;
+  color: #666;
+  margin-bottom: 5px;
+  font-weight: bold;
+}
+
+.info-item span {
+  font-size: 16px;
+  color: #333;
+}
+
+.stage-tag {
+  display: inline-block;
+  padding: 5px 12px;
+  border-radius: 20px;
+  background-color: #e8f5e9;
+  color: #43a047;
+  font-size: 14px;
+}
+
+/* 客户画像 */
+.skill-mismatch-warning {
+  background-color: #ffebee;
+  color: #c62828;
+  padding: 10px 15px;
+  border-radius: 4px;
+  margin-bottom: 20px;
+  border-left: 4px solid #c62828;
+}
+
+.customer-tags {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 15px;
+}
+
+.tag-group {
+  display: flex;
+  flex-direction: column;
+  min-width: 150px;
+}
+
+.tag-group label {
+  font-size: 14px;
+  color: #666;
+  margin-bottom: 8px;
+  font-weight: bold;
+}
+
+.tag {
+  display: inline-block;
+  background-color: #e3f2fd;
+  color: #1976d2;
+  padding: 4px 10px;
+  border-radius: 15px;
+  font-size: 13px;
+  margin-right: 5px;
+  margin-bottom: 5px;
+}
+
+.priority-tag {
+  display: inline-block;
+  background-color: #fff3e0;
+  color: #f57c00;
+  padding: 4px 10px;
+  border-radius: 15px;
+  font-size: 13px;
+  margin-right: 5px;
+  margin-bottom: 5px;
+}
+
+/* 需求确认清单 */
+.checklist {
+  margin-top: 15px;
+}
+
+.checklist-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.checklist-item input[type="checkbox"] {
+  margin-right: 10px;
+  width: 18px;
+  height: 18px;
+}
+
+.checklist-item span {
+  color: #333;
+}
+
+/* 制作流程进度 */
+.stage-progress {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  position: relative;
+  padding: 0 10px;
+}
+
+.stage-progress::before {
+  content: '';
+  position: absolute;
+  top: 50%;
+  left: 50px;
+  right: 50px;
+  height: 2px;
+  background-color: #e0e0e0;
+  transform: translateY(-50%);
+  z-index: 1;
+}
+
+.stage {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  z-index: 2;
+  position: relative;
+}
+
+.stage-icon {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  background-color: #e0e0e0;
+  color: #666;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-weight: bold;
+  margin-bottom: 10px;
+  transition: all 0.3s ease;
+}
+
+.stage-name {
+  font-size: 14px;
+  color: #666;
+  margin-bottom: 10px;
+}
+
+.stage.completed .stage-icon {
+  background-color: #4caf50;
+  color: white;
+}
+
+.stage.completed .stage-name {
+  color: #4caf50;
+  font-weight: bold;
+}
+
+.stage-actions {
+  margin-top: 10px;
+}
+
+.stage-actions button {
+  background-color: #2196f3;
+  color: white;
+  border: none;
+  padding: 8px 16px;
+  border-radius: 4px;
+  font-size: 14px;
+  cursor: pointer;
+  transition: background-color 0.3s ease;
+}
+
+.stage-actions button:hover:not(:disabled) {
+  background-color: #1976d2;
+}
+
+.stage-actions button:disabled {
+  background-color: #ccc;
+  cursor: not-allowed;
+}
+
+/* 渲染进度 */
+.loading {
+  text-align: center;
+  padding: 20px;
+  color: #666;
+}
+
+.error {
+  text-align: center;
+  padding: 20px;
+  color: #c62828;
+  background-color: #ffebee;
+  border-radius: 4px;
+}
+
+.error button {
+  background-color: #c62828;
+  color: white;
+  border: none;
+  padding: 8px 16px;
+  border-radius: 4px;
+  font-size: 14px;
+  cursor: pointer;
+  margin: 0 10px;
+}
+
+.progress-content {
+  padding: 20px;
+}
+
+.progress-bar {
+  width: 100%;
+  height: 20px;
+  background-color: #e0e0e0;
+  border-radius: 10px;
+  overflow: hidden;
+  margin-bottom: 15px;
+}
+
+.progress-fill {
+  height: 100%;
+  background-color: #4caf50;
+  transition: width 0.3s ease;
+}
+
+.progress-info {
+  display: flex;
+  justify-content: space-between;
+  font-size: 16px;
+  color: #333;
+}
+
+/* 模型检查清单 */
+.check-status {
+  margin-left: auto;
+  font-weight: bold;
+}
+
+.check-status.passed {
+  color: #4caf50;
+}
+
+.check-status.failed {
+  color: #f44336;
+}
+
+/* 客户反馈 */
+.no-feedback,
+.no-changes,
+.no-settlements {
+  text-align: center;
+  padding: 20px;
+  color: #999;
+  font-style: italic;
+}
+
+.feedback-item {
+  border: 1px solid #eee;
+  border-radius: 8px;
+  padding: 15px;
+  margin-bottom: 15px;
+}
+
+.feedback-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.feedback-customer {
+  font-weight: bold;
+  color: #333;
+}
+
+.feedback-date {
+  font-size: 12px;
+  color: #999;
+}
+
+.feedback-content {
+  margin-bottom: 15px;
+}
+
+.feedback-status {
+  display: inline-block;
+  background-color: #e3f2fd;
+  color: #1976d2;
+  padding: 4px 10px;
+  border-radius: 15px;
+  font-size: 13px;
+  margin-bottom: 10px;
+}
+
+.feedback-type {
+  color: #666;
+  margin-bottom: 10px;
+  font-size: 14px;
+}
+
+.feedback-details {
+  background-color: #f5f5f5;
+  padding: 10px;
+  border-radius: 4px;
+}
+
+.feedback-details p {
+  margin: 5px 0;
+  font-size: 14px;
+  color: #333;
+}
+
+.feedback-actions {
+  display: flex;
+  gap: 10px;
+}
+
+.feedback-actions button {
+  padding: 8px 16px;
+  border: none;
+  border-radius: 4px;
+  font-size: 14px;
+  cursor: pointer;
+  transition: background-color 0.3s ease;
+}
+
+.feedback-actions button:first-child {
+  background-color: #2196f3;
+  color: white;
+}
+
+.feedback-actions button:first-child:hover:not(:disabled) {
+  background-color: #1976d2;
+}
+
+.feedback-actions button:last-child {
+  background-color: #4caf50;
+  color: white;
+}
+
+.feedback-actions button:last-child:hover:not(:disabled) {
+  background-color: #43a047;
+}
+
+.feedback-actions button:disabled {
+  background-color: #ccc;
+  cursor: not-allowed;
+}
+
+/* 设计师变更记录 */
+.change-item {
+  border: 1px solid #eee;
+  border-radius: 8px;
+  padding: 15px;
+  margin-bottom: 15px;
+}
+
+.change-header {
+  margin-bottom: 15px;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.change-time {
+  font-size: 14px;
+  color: #666;
+  font-weight: bold;
+}
+
+.change-details {
+  margin-bottom: 15px;
+}
+
+.change-details div {
+  margin-bottom: 8px;
+  color: #333;
+}
+
+.change-details ul {
+  margin: 8px 0;
+  padding-left: 20px;
+}
+
+.change-details li {
+  color: #333;
+  margin-bottom: 4px;
+}
+
+.change-status {
+  font-size: 14px;
+  color: #666;
+  background-color: #f5f5f5;
+  padding: 8px;
+  border-radius: 4px;
+}
+
+/* 分阶段结算记录 */
+.settlement-table {
+  overflow-x: auto;
+}
+
+.settlement-table table {
+  width: 100%;
+  border-collapse: collapse;
+}
+
+.settlement-table th,
+.settlement-table td {
+  padding: 12px;
+  text-align: left;
+  border-bottom: 1px solid #eee;
+}
+
+.settlement-table th {
+  background-color: #f5f5f5;
+  font-weight: bold;
+  color: #333;
+}
+
+.settlement-table td {
+  color: #666;
+}
+
+/* 停滞期管理 */
+.stagnation-section button {
+  background-color: #ff9800;
+  color: white;
+  border: none;
+  padding: 10px 20px;
+  border-radius: 4px;
+  font-size: 16px;
+  cursor: pointer;
+  transition: background-color 0.3s ease;
+}
+
+.stagnation-section button:hover {
+  background-color: #f57c00;
+}
+
+.stagnation-section p {
+  margin-top: 10px;
+  color: #666;
+  font-size: 14px;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .project-detail-container {
+    padding: 10px;
+  }
+  
+  .project-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 10px;
+  }
+  
+  .info-grid {
+    grid-template-columns: 1fr;
+  }
+  
+  .stage-progress {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 20px;
+  }
+  
+  .stage-progress::before {
+    display: none;
+  }
+  
+  .stage {
+    flex-direction: row;
+    align-items: center;
+    gap: 10px;
+  }
+  
+  .stage-icon {
+    margin-bottom: 0;
+  }
+  
+  .feedback-actions {
+    flex-direction: column;
+  }
+  
+  .customer-tags {
+    flex-direction: column;
+    gap: 10px;
+  }
+  
+  .progress-info {
+    flex-direction: column;
+    gap: 10px;
+  }
+}

+ 171 - 3
src/app/pages/designer/project-detail/project-detail.ts

@@ -1,11 +1,179 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { ActivatedRoute } from '@angular/router';
+import { ProjectService } from '../../../services/project.service';
+import {
+  Project,
+  RenderProgress,
+  ModelCheckItem,
+  CustomerFeedback,
+  DesignerChange,
+  Settlement,
+  ProjectStage
+} from '../../../models/project.model';
 
 @Component({
   selector: 'app-project-detail',
-  imports: [],
+  imports: [CommonModule],
   templateUrl: './project-detail.html',
   styleUrl: './project-detail.scss'
 })
-export class ProjectDetail {
+export class ProjectDetail implements OnInit {
+  projectId: string = '';
+  project: Project | undefined;
+  renderProgress: RenderProgress | undefined;
+  modelCheckItems: ModelCheckItem[] = [];
+  feedbacks: CustomerFeedback[] = [];
+  designerChanges: DesignerChange[] = [];
+  settlements: Settlement[] = [];
+  requirementChecklist: string[] = [];
+  reminderMessage: string = '';
+  isLoadingRenderProgress: boolean = false;
+  errorLoadingRenderProgress: boolean = false;
 
+  constructor(
+    private route: ActivatedRoute,
+    private projectService: ProjectService
+  ) {}
+
+  ngOnInit(): void {
+    this.projectId = this.route.snapshot.paramMap.get('id') || '';
+    this.loadProjectData();
+  }
+
+  loadProjectData(): void {
+    if (this.projectId) {
+      this.loadProjectDetails();
+      this.loadRenderProgress();
+      this.loadModelCheckItems();
+      this.loadCustomerFeedbacks();
+      this.loadDesignerChanges();
+      this.loadSettlements();
+      this.loadRequirementChecklist();
+    }
+  }
+
+  loadProjectDetails(): void {
+    this.projectService.getProjectById(this.projectId).subscribe(project => {
+      this.project = project;
+    });
+  }
+
+  loadRenderProgress(): void {
+    this.isLoadingRenderProgress = true;
+    this.errorLoadingRenderProgress = false;
+    
+    // 模拟API加载过程
+    setTimeout(() => {
+      this.projectService.getRenderProgress(this.projectId).subscribe(progress => {
+        this.renderProgress = progress;
+        this.isLoadingRenderProgress = false;
+        
+        // 模拟API加载失败的情况
+        if (!progress) {
+          this.errorLoadingRenderProgress = true;
+        }
+      });
+    }, 1000);
+  }
+
+  loadModelCheckItems(): void {
+    this.projectService.getModelCheckItems().subscribe(items => {
+      this.modelCheckItems = items;
+    });
+  }
+
+  loadCustomerFeedbacks(): void {
+    this.projectService.getCustomerFeedbacks().subscribe(feedbacks => {
+      this.feedbacks = feedbacks.filter(f => f.projectId === this.projectId);
+    });
+  }
+
+  loadDesignerChanges(): void {
+    // 在实际应用中,这里应该从服务中获取设计师变更记录
+    // 这里使用模拟数据
+    this.designerChanges = [
+      {
+        id: 'dc1',
+        projectId: this.projectId,
+        oldDesignerId: 'designer2',
+        oldDesignerName: '设计师B',
+        newDesignerId: 'designer1',
+        newDesignerName: '设计师A',
+        changeTime: new Date('2025-09-05'),
+        acceptanceTime: new Date('2025-09-05'),
+        historicalAchievements: ['完成初步建模', '确定色彩方案'],
+        completedWorkload: 30
+      }
+    ];
+  }
+
+  loadSettlements(): void {
+    this.projectService.getSettlements().subscribe(settlements => {
+      this.settlements = settlements.filter(s => s.projectId === this.projectId);
+    });
+  }
+
+  loadRequirementChecklist(): void {
+    this.projectService.generateRequirementChecklist(this.projectId).subscribe(checklist => {
+      this.requirementChecklist = checklist;
+    });
+  }
+
+  updateModelCheckItem(itemId: string, isPassed: boolean): void {
+    this.projectService.updateModelCheckItem(itemId, isPassed).subscribe(() => {
+      this.loadModelCheckItems(); // 重新加载检查项
+    });
+  }
+
+  updateFeedbackStatus(feedbackId: string, status: '处理中' | '已解决'): void {
+    this.projectService.updateFeedbackStatus(feedbackId, status).subscribe(() => {
+      this.loadCustomerFeedbacks(); // 重新加载反馈
+    });
+  }
+
+  updateProjectStage(stage: ProjectStage): void {
+    if (this.project) {
+      this.projectService.updateProjectStage(this.projectId, stage).subscribe(() => {
+        this.loadProjectDetails(); // 重新加载项目详情
+      });
+    }
+  }
+
+  generateReminderMessage(): void {
+    this.projectService.generateReminderMessage('stagnation').subscribe(message => {
+      this.reminderMessage = message;
+      
+      // 3秒后自动清除提醒
+      setTimeout(() => {
+        this.reminderMessage = '';
+      }, 3000);
+    });
+  }
+
+  retryLoadRenderProgress(): void {
+    this.loadRenderProgress();
+  }
+
+  // 检查是否所有模型检查项都已通过
+  areAllModelChecksPassed(): boolean {
+    return this.modelCheckItems.every(item => item.isPassed);
+  }
+
+  // 获取技能匹配度警告
+  getSkillMismatchWarning(): string | null {
+    if (!this.project) return null;
+    
+    // 模拟技能匹配度检查
+    const designerSkills = ['现代风格', '硬装'];
+    const requiredSkills = this.project.skillsRequired;
+    
+    const mismatchedSkills = requiredSkills.filter(skill => !designerSkills.includes(skill));
+    
+    if (mismatchedSkills.length > 0) {
+      return `警告:您不擅长${mismatchedSkills.join('、')},建议联系组长协调`;
+    }
+    
+    return null;
+  }
 }

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

@@ -0,0 +1,315 @@
+import { Injectable } from '@angular/core';
+import { Observable, of } from 'rxjs';
+import {
+  Project,
+  Task,
+  RenderProgress,
+  ModelCheckItem,
+  CustomerFeedback,
+  DesignerChange,
+  Settlement,
+  SkillTag,
+  PerformanceData,
+  MatchingOrder,
+  ProjectStage
+} from '../models/project.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ProjectService {
+  // 模拟数据 - 实际应用中应从API获取
+  private projects: Project[] = [
+    {
+      id: '1',
+      name: '现代风格客厅设计',
+      customerName: '张三',
+      customerTags: [
+        { source: '朋友圈', needType: '硬装', preference: '现代', colorAtmosphere: '简约明亮' }
+      ],
+      highPriorityNeeds: ['需家具购买建议'],
+      status: '进行中',
+      currentStage: '建模',
+      createdAt: new Date('2025-09-01'),
+      deadline: new Date('2025-09-15'),
+      assigneeId: 'designer1',
+      assigneeName: '设计师A',
+      skillsRequired: ['现代风格', '硬装']
+    },
+    {
+      id: '2',
+      name: '宋式风格卧室设计',
+      customerName: '李四',
+      customerTags: [
+        { source: '信息流', needType: '软装', preference: '宋式', colorAtmosphere: '典雅古朴' }
+      ],
+      highPriorityNeeds: [],
+      status: '进行中',
+      currentStage: '渲染',
+      createdAt: new Date('2025-09-02'),
+      deadline: new Date('2025-09-20'),
+      assigneeId: 'designer1',
+      assigneeName: '设计师A',
+      skillsRequired: ['宋式风格', '软装']
+    },
+    {
+      id: '3',
+      name: '欧式风格厨房设计',
+      customerName: '王五',
+      customerTags: [
+        { source: '朋友圈', needType: '硬装', preference: '欧式', colorAtmosphere: '豪华温馨' }
+      ],
+      highPriorityNeeds: ['需快速交付'],
+      status: '已完成',
+      currentStage: '完成',
+      createdAt: new Date('2025-08-20'),
+      deadline: new Date('2025-09-05'),
+      assigneeId: 'designer1',
+      assigneeName: '设计师A',
+      skillsRequired: ['欧式风格', '硬装']
+    }
+  ];
+
+  private tasks: Task[] = [
+    {
+      id: 't1',
+      projectId: '1',
+      projectName: '现代风格客厅设计',
+      title: '完成客厅建模',
+      stage: '建模',
+      deadline: new Date('2025-09-10'),
+      isOverdue: false,
+      isCompleted: false
+    },
+    {
+      id: 't2',
+      projectId: '2',
+      projectName: '宋式风格卧室设计',
+      title: '确认渲染结果',
+      stage: '渲染',
+      deadline: new Date('2025-09-12'),
+      isOverdue: false,
+      isCompleted: false
+    },
+    {
+      id: 't3',
+      projectId: '1',
+      projectName: '现代风格客厅设计',
+      title: '处理客户反馈',
+      stage: '后期',
+      deadline: new Date('2025-09-08'),
+      isOverdue: true,
+      isCompleted: false
+    }
+  ];
+
+  private renderProgresses: RenderProgress[] = [
+    {
+      id: 'rp1',
+      projectId: '2',
+      completionRate: 60,
+      estimatedTimeRemaining: 1,
+      status: '进行中',
+      updatedAt: new Date()
+    }
+  ];
+
+  private modelCheckItems: ModelCheckItem[] = [
+    { id: 'm1', name: '尺寸准确性', isPassed: true },
+    { id: 'm2', name: '比例协调性', isPassed: true },
+    { id: 'm3', name: '户型匹配度', isPassed: false, notes: '需调整沙发位置' }
+  ];
+
+  private feedbacks: CustomerFeedback[] = [
+    {
+      id: 'f1',
+      projectId: '1',
+      content: '客厅设计不太满意',
+      isSatisfied: false,
+      problemLocation: '沙发区域',
+      expectedEffect: '更宽敞舒适',
+      referenceCase: '提供了参考图片',
+      status: '待处理',
+      createdAt: new Date('2025-09-07')
+    }
+  ];
+
+  private settlements: Settlement[] = [
+    {
+      id: 's1',
+      projectId: '3',
+      stage: '建模',
+      amount: 3000,
+      percentage: 30,
+      status: '已结算',
+      createdAt: new Date('2025-08-25'),
+      settledAt: new Date('2025-09-01')
+    },
+    {
+      id: 's2',
+      projectId: '3',
+      stage: '渲染',
+      amount: 5000,
+      percentage: 50,
+      status: '已结算',
+      createdAt: new Date('2025-08-28'),
+      settledAt: new Date('2025-09-01')
+    },
+    {
+      id: 's3',
+      projectId: '3',
+      stage: '后期',
+      amount: 2000,
+      percentage: 20,
+      status: '已结算',
+      createdAt: new Date('2025-09-02'),
+      settledAt: new Date('2025-09-05')
+    },
+    {
+      id: 's4',
+      projectId: '1',
+      stage: '建模',
+      amount: 3000,
+      percentage: 30,
+      status: '待结算',
+      createdAt: new Date('2025-09-06')
+    }
+  ];
+
+  private skillTags: SkillTag[] = [
+    { id: 'sk1', name: '现代风格', level: 5, count: 10 },
+    { id: 'sk2', name: '宋式风格', level: 3, count: 5 },
+    { id: 'sk3', name: '硬装', level: 5, count: 8 },
+    { id: 'sk4', name: '软装', level: 3, count: 4 }
+  ];
+
+  private performanceData: PerformanceData[] = [
+    { month: '2025-08', projectCompletionRate: 90, customerSatisfaction: 95, deliveryOnTimeRate: 85 },
+    { month: '2025-09', projectCompletionRate: 85, customerSatisfaction: 90, deliveryOnTimeRate: 90 },
+    { month: '2025-10', projectCompletionRate: 95, customerSatisfaction: 92, deliveryOnTimeRate: 88 }
+  ];
+
+  private matchingOrders: MatchingOrder[] = [
+    {
+      id: 'mo1',
+      projectName: '新中式风格书房设计',
+      requiredSkills: ['宋式风格', '硬装'],
+      matchRate: 90,
+      customerLevel: '优质'
+    },
+    {
+      id: 'mo2',
+      projectName: '北欧风格餐厅设计',
+      requiredSkills: ['现代风格', '软装'],
+      matchRate: 80,
+      customerLevel: '普通'
+    }
+  ];
+
+  // 获取当前设计师的项目列表
+  getProjects(): Observable<Project[]> {
+    return of(this.projects);
+  }
+
+  // 获取项目详情
+  getProjectById(id: string): Observable<Project | undefined> {
+    return of(this.projects.find(project => project.id === id));
+  }
+
+  // 获取待办任务
+  getTasks(): Observable<Task[]> {
+    return of(this.tasks);
+  }
+
+  // 标记任务完成
+  markTaskAsCompleted(taskId: string): Observable<Task> {
+    const task = this.tasks.find(t => t.id === taskId);
+    if (task) {
+      task.isCompleted = true;
+      task.completedAt = new Date();
+    }
+    return of(task as Task);
+  }
+
+  // 获取渲染进度
+  getRenderProgress(projectId: string): Observable<RenderProgress | undefined> {
+    return of(this.renderProgresses.find(rp => rp.projectId === projectId));
+  }
+
+  // 获取模型检查清单
+  getModelCheckItems(): Observable<ModelCheckItem[]> {
+    return of(this.modelCheckItems);
+  }
+
+  // 更新模型检查项
+  updateModelCheckItem(itemId: string, isPassed: boolean): Observable<ModelCheckItem> {
+    const item = this.modelCheckItems.find(i => i.id === itemId);
+    if (item) {
+      item.isPassed = isPassed;
+    }
+    return of(item as ModelCheckItem);
+  }
+
+  // 获取客户反馈
+  getCustomerFeedbacks(): Observable<CustomerFeedback[]> {
+    return of(this.feedbacks);
+  }
+
+  // 更新反馈状态
+  updateFeedbackStatus(feedbackId: string, status: '处理中' | '已解决'): Observable<CustomerFeedback> {
+    const feedback = this.feedbacks.find(f => f.id === feedbackId);
+    if (feedback) {
+      feedback.status = status;
+      feedback.updatedAt = new Date();
+    }
+    return of(feedback as CustomerFeedback);
+  }
+
+  // 获取结算记录
+  getSettlements(): Observable<Settlement[]> {
+    return of(this.settlements);
+  }
+
+  // 获取技能标签
+  getSkillTags(): Observable<SkillTag[]> {
+    return of(this.skillTags);
+  }
+
+  // 获取绩效数据
+  getPerformanceData(): Observable<PerformanceData[]> {
+    return of(this.performanceData);
+  }
+
+  // 获取匹配订单
+  getMatchingOrders(): Observable<MatchingOrder[]> {
+    return of(this.matchingOrders);
+  }
+
+  // 生成需求确认清单
+  generateRequirementChecklist(projectId: string): Observable<string[]> {
+    return of([
+      '个性化需求已确认',
+      '色彩氛围已确认',
+      '硬装/软装范围已确认',
+      '资料提交截止时间已确认'
+    ]);
+  }
+
+  // 生成提醒话术
+  generateReminderMessage(type: 'overdue' | 'stagnation'): Observable<string> {
+    if (type === 'overdue') {
+      return of('当前处于对图期,需1小时内回复,若您需时间梳理需求,可约定XX时间沟通');
+    } else {
+      return of('接下来将推进新项目,若需修改请提前1天预约');
+    }
+  }
+
+  // 更新项目阶段
+  updateProjectStage(projectId: string, stage: ProjectStage): Observable<Project | undefined> {
+    const project = this.projects.find(p => p.id === projectId);
+    if (project) {
+      project.currentStage = stage;
+    }
+    return of(project);
+  }
+}

Някои файлове не бяха показани, защото твърде много файлове са промени