徐福静0235668 пре 1 дан
родитељ
комит
edfaace71e

+ 103 - 1
src/app/app.html

@@ -1 +1,103 @@
-<router-outlet />
+<div class="app-container">
+  <!-- 顶部导航栏 -->
+  <header class="top-navbar">
+    <div class="navbar-left">
+      <button class="menu-toggle" (click)="toggleSidebar()">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <line x1="3" y1="12" x2="21" y2="12"></line>
+          <line x1="3" y1="6" x2="21" y2="6"></line>
+          <line x1="3" y1="18" x2="21" y2="18"></line>
+        </svg>
+      </button>
+      <h1 class="app-title">客服工作台</h1>
+    </div>
+    
+    <div class="navbar-center">
+      <div class="search-container">
+        <input 
+          type="text" 
+          [(ngModel)]="searchTerm"
+          placeholder="搜索项目、客户或动态..." 
+          class="search-input"
+        />
+        <button class="search-button">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <circle cx="11" cy="11" r="8"></circle>
+            <path d="m21 21-4.35-4.35"></path>
+          </svg>
+        </button>
+      </div>
+    </div>
+    
+    <div class="navbar-right">
+      <button class="notification-btn">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
+        </svg>
+        <span class="notification-badge">3</span>
+      </button>
+      <div class="user-profile">
+        <img src="https://picsum.photos/id/64/40/40" alt="用户头像" class="user-avatar">
+        <span class="user-name">客服小李</span>
+      </div>
+    </div>
+  </header>
+
+  <!-- 主要内容区 -->
+  <main class="main-content">
+    <!-- 左侧侧边栏 -->
+    <aside class="sidebar" [class.collapsed]="!sidebarOpen">
+      <nav class="sidebar-nav">
+        <a href="/customer-service/dashboard" class="nav-item" routerLinkActive="active">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"></path>
+          </svg>
+          <span>工作台</span>
+        </a>
+        <a href="/customer-service/consultation-order" class="nav-item" routerLinkActive="active">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
+          </svg>
+          <span>客户咨询</span>
+        </a>
+        <a href="/customer-service/project-list" class="nav-item" routerLinkActive="active">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <line x1="8" y1="6" x2="21" y2="6"></line>
+            <line x1="8" y1="12" x2="21" y2="12"></line>
+            <line x1="8" y1="18" x2="21" y2="18"></line>
+            <line x1="3" y1="6" x2="3.01" y2="6"></line>
+            <line x1="3" y1="12" x2="3.01" y2="12"></line>
+            <line x1="3" y1="18" x2="3.01" y2="18"></line>
+          </svg>
+          <span>项目列表</span>
+        </a>
+        <a href="/customer-service/case-library" class="nav-item" routerLinkActive="active">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
+            <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
+          </svg>
+          <span>案例库</span>
+        </a>
+      </nav>
+      
+      <div class="sidebar-footer">
+        <div class="storage-info">
+          <span>在线时长: 4.5h</span>
+        </div>
+        <button class="logout-btn">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
+            <polyline points="16 17 21 12 16 7"></polyline>
+            <line x1="21" y1="12" x2="9" y2="12"></line>
+          </svg>
+          <span>退出登录</span>
+        </button>
+      </div>
+    </aside>
+
+    <!-- 中间内容区 -->
+    <div class="content-wrapper" [class.expanded]="!sidebarOpen">
+      <router-outlet />
+    </div>
+  </main>
+</div>

+ 331 - 0
src/app/app.scss

@@ -0,0 +1,331 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$primary-dark: #0E42CB;
+$secondary-color: #4E5BA6;
+$success-color: #00B42A;
+$warning-color: #FF7D00;
+$danger-color: #F53F3F;
+$text-primary: #1D2129;
+$text-secondary: #4E5969;
+$text-tertiary: #86909C;
+$border-color: #E5E6EB;
+$background-primary: #FFFFFF;
+$background-secondary: #F2F3F5;
+$background-tertiary: #F7F8FA;
+$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
+$shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.1);
+$border-radius: 8px;
+$transition: all 0.3s ease;
+
+// 全局样式重置
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+body {
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+  color: $text-primary;
+  background-color: $background-secondary;
+}
+
+// 主容器
+.app-container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  overflow: hidden;
+}
+
+// 顶部导航栏
+.top-navbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 24px;
+  height: 64px;
+  background-color: $background-primary;
+  border-bottom: 1px solid $border-color;
+  box-shadow: $shadow-sm;
+  position: sticky;
+  top: 0;
+  z-index: 1000;
+
+  .navbar-left {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+  }
+
+  .menu-toggle {
+    display: none;
+    background: none;
+    border: none;
+    cursor: pointer;
+    color: $text-primary;
+    padding: 8px;
+    transition: $transition;
+
+    &:hover {
+      color: $primary-color;
+    }
+  }
+
+  .app-title {
+    font-size: 20px;
+    font-weight: 600;
+    color: $text-primary;
+  }
+
+  .navbar-center {
+    flex: 1;
+    max-width: 500px;
+    margin: 0 20px;
+  }
+
+  .search-container {
+    display: flex;
+    align-items: center;
+    background-color: $background-tertiary;
+    border-radius: $border-radius;
+    padding: 6px 12px;
+    border: 1px solid $border-color;
+
+    .search-input {
+      flex: 1;
+      background: none;
+      border: none;
+      outline: none;
+      padding: 6px 8px;
+      font-size: 14px;
+      color: $text-primary;
+
+      &::placeholder {
+        color: $text-tertiary;
+      }
+    }
+
+    .search-button {
+      background: none;
+      border: none;
+      cursor: pointer;
+      color: $text-secondary;
+      padding: 4px;
+      transition: $transition;
+
+      &:hover {
+        color: $primary-color;
+      }
+    }
+  }
+
+  .navbar-right {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+  }
+
+  .notification-btn {
+    position: relative;
+    background: none;
+    border: none;
+    cursor: pointer;
+    color: $text-secondary;
+    padding: 8px;
+    transition: $transition;
+
+    &:hover {
+      color: $primary-color;
+    }
+
+    .notification-badge {
+      position: absolute;
+      top: 2px;
+      right: 2px;
+      background-color: $danger-color;
+      color: white;
+      font-size: 10px;
+      font-weight: 500;
+      padding: 2px 6px;
+      border-radius: 10px;
+      min-width: 18px;
+      text-align: center;
+    }
+  }
+
+  .user-profile {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+
+    .user-avatar {
+      width: 36px;
+      height: 36px;
+      border-radius: 50%;
+      object-fit: cover;
+    }
+
+    .user-name {
+      font-size: 14px;
+      font-weight: 500;
+      color: $text-primary;
+    }
+  }
+}
+
+// 主要内容区
+.main-content {
+  display: flex;
+  flex: 1;
+  overflow: hidden;
+}
+
+// 左侧侧边栏
+.sidebar {
+  width: 220px;
+  background-color: $background-primary;
+  border-right: 1px solid $border-color;
+  display: flex;
+  flex-direction: column;
+  transition: $transition;
+
+  .sidebar-nav {
+    flex: 1;
+    padding: 16px 0;
+
+    .nav-item {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 12px 24px;
+      color: $text-secondary;
+      text-decoration: none;
+      border-left: 3px solid transparent;
+      transition: $transition;
+
+      &:hover {
+        background-color: $background-tertiary;
+        color: $primary-color;
+      }
+
+      &.active {
+        color: $primary-color;
+        background-color: color-mix(in srgb, $primary-color 5%, transparent);
+        border-left-color: $primary-color;
+        font-weight: 500;
+      }
+    }
+  }
+
+  .sidebar-footer {
+    padding: 16px 24px;
+    border-top: 1px solid $border-color;
+
+    .storage-info {
+      margin-bottom: 16px;
+      font-size: 12px;
+      color: $text-tertiary;
+    }
+
+    .logout-btn {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      padding: 8px 12px;
+      background: none;
+      border: 1px solid $border-color;
+      border-radius: $border-radius;
+      color: $text-secondary;
+      cursor: pointer;
+      font-size: 14px;
+      transition: $transition;
+
+      &:hover {
+        background-color: $background-tertiary;
+        border-color: $danger-color;
+        color: $danger-color;
+      }
+    }
+  }
+
+  &.collapsed {
+    width: 0;
+    overflow: hidden;
+  }
+}
+
+// 中间内容区
+.content-wrapper {
+  flex: 1;
+  overflow-y: auto;
+  padding: 24px;
+  transition: $transition;
+
+  &.expanded {
+    width: 100%;
+  }
+}
+
+// 响应式设计
+@media (max-width: 1024px) {
+  .sidebar {
+    width: 200px;
+  }
+}
+
+@media (max-width: 768px) {
+  .top-navbar {
+    padding: 0 16px;
+
+    .menu-toggle {
+      display: block;
+    }
+
+    .app-title {
+      font-size: 18px;
+    }
+
+    .navbar-center {
+      display: none;
+    }
+  }
+
+  .sidebar {
+    position: fixed;
+    top: 64px;
+    left: 0;
+    height: calc(100vh - 64px);
+    z-index: 900;
+    transform: translateX(0);
+
+    &.collapsed {
+      transform: translateX(-100%);
+    }
+  }
+
+  .content-wrapper {
+    padding: 16px;
+    margin-left: 0;
+  }
+}
+
+@media (max-width: 480px) {
+  .top-navbar {
+    height: 56px;
+
+    .navbar-right {
+      gap: 12px;
+    }
+  }
+
+  .sidebar {
+    top: 56px;
+    height: calc(100vh - 56px);
+  }
+
+  .content-wrapper {
+    padding: 12px;
+  }
+}

+ 11 - 2
src/app/app.ts

@@ -1,12 +1,21 @@
 import { Component, signal } from '@angular/core';
-import { RouterOutlet } from '@angular/router';
+import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';
+import { FormsModule } from '@angular/forms';
 
 @Component({
   selector: 'app-root',
-  imports: [RouterOutlet],
+  standalone: true,
+  imports: [RouterOutlet, RouterLink, RouterLinkActive, FormsModule],
   templateUrl: './app.html',
   styleUrl: './app.scss'
 })
 export class App {
   protected readonly title = signal('yss-project');
+  sidebarOpen = true;
+  searchTerm = '';
+  currentDate = new Date();
+
+  toggleSidebar() {
+    this.sidebarOpen = !this.sidebarOpen;
+  }
 }

+ 41 - 1
src/app/models/project.model.ts

@@ -29,7 +29,11 @@ export type ProjectStatus = '进行中' | '已完成' | '已暂停' | '已延期
 export type ProjectStage = '前期沟通' | '建模' | '软装' | '渲染' | '后期' | '完成';
 
 // 任务模型
+// 修复 Task 接口,将 completedAt 改为 completedDate(与实际代码保持一致)
 export interface Task {
+  priority: any;
+  assignee: any;
+  description: any;
   id: string;
   projectId: string;
   projectName: string;
@@ -38,7 +42,39 @@ export interface Task {
   deadline: Date;
   isOverdue: boolean;
   isCompleted: boolean;
-  completedAt?: Date;
+  completedDate?: Date; // 修正为 completedDate
+}
+
+// 添加 Milestone 接口定义
+export interface Milestone {
+  id: string;
+  title: string;
+  description: string;
+  dueDate: Date;
+  completedDate?: Date | null;
+  isCompleted: boolean;
+}
+
+// 添加 Message 接口定义
+export interface Message {
+  id: string;
+  sender: string;
+  content: string;
+  timestamp: Date;
+  isRead: boolean;
+  type: 'text' | 'image' | 'file';
+}
+
+// 添加 FileItem 接口定义
+export interface FileItem {
+  id: string;
+  name: string;
+  type: 'document' | 'image' | 'spreadsheet' | 'other';
+  size: string;
+  url: string;
+  uploadedBy: string;
+  uploadedAt: Date;
+  downloadCount: number;
 }
 
 // 渲染进度
@@ -72,6 +108,10 @@ export interface CustomerFeedback {
   createdAt: Date;
   updatedAt?: Date;
   tag?: string;
+  // 添加模板中使用的属性
+  customerName?: string;
+  rating?: number;
+  response?: string;
 }
 
 // 设计师变更记录

+ 406 - 1
src/app/pages/customer-service/case-library/case-library.html

@@ -1 +1,406 @@
-<p>case-library works!</p>
+<div class="case-library-container">
+  <!-- 顶部导航栏 -->
+  <header class="top-navbar">
+    <div class="navbar-left">
+      <button class="menu-toggle">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <line x1="3" y1="12" x2="21" y2="12"></line>
+          <line x1="3" y1="6" x2="21" y2="6"></line>
+          <line x1="3" y1="18" x2="21" y2="18"></line>
+        </svg>
+      </button>
+      <h1 class="app-title">客服工作台</h1>
+    </div>
+    
+    <div class="navbar-center">
+      <div class="search-container">
+        <input 
+          type="text" 
+          [(ngModel)]="searchTerm" 
+          placeholder="搜索案例名称、设计师或标签..." 
+          class="search-input"
+          autocomplete="off"
+        />
+        <button class="search-button">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <circle cx="11" cy="11" r="8"></circle>
+            <path d="m21 21-4.35-4.35"></path>
+          </svg>
+        </button>
+      </div>
+    </div>
+    
+    <div class="navbar-right">
+      <button class="notification-btn">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
+        </svg>
+        <span class="notification-badge">3</span>
+      </button>
+      <div class="user-profile">
+        <img src="https://picsum.photos/id/64/40/40" alt="用户头像" class="user-avatar">
+        <span class="user-name">客服小李</span>
+      </div>
+    </div>
+  </header>
+
+  <!-- 主要内容区 -->
+  <main class="dashboard-content">
+    <!-- 左侧侧边栏 -->
+    <aside class="sidebar">
+      <nav class="sidebar-nav">
+        <a href="/customer-service/dashboard" class="nav-item">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"></path>
+          </svg>
+          <span>工作台</span>
+        </a>
+        <a href="/customer-service/consultation-order" class="nav-item">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
+          </svg>
+          <span>客户咨询</span>
+        </a>
+        <a href="/customer-service/project-list" class="nav-item">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <line x1="8" y1="6" x2="21" y2="6"></line>
+            <line x1="8" y1="12" x2="21" y2="12"></line>
+            <line x1="8" y1="18" x2="21" y2="18"></line>
+            <line x1="3" y1="6" x2="3.01" y2="6"></line>
+            <line x1="3" y1="12" x2="3.01" y2="12"></line>
+            <line x1="3" y1="18" x2="3.01" y2="18"></line>
+          </svg>
+          <span>项目列表</span>
+        </a>
+        <a href="/customer-service/case-library" class="nav-item active">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
+            <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
+          </svg>
+          <span>案例库</span>
+        </a>
+      </nav>
+      
+      <div class="sidebar-footer">
+        <div class="storage-info">
+          <span>在线时长: 4.5h</span>
+        </div>
+        <button class="logout-btn">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
+            <polyline points="16 17 21 12 16 7"></polyline>
+            <line x1="21" y1="12" x2="9" y2="12"></line>
+          </svg>
+          <span>退出登录</span>
+        </button>
+      </div>
+    </aside>
+
+    <!-- 中间内容区 -->
+    <div class="content-wrapper">
+      <!-- 欢迎区域 -->
+      <section class="welcome-section">
+        <h2>案例库</h2>
+        <p>今天是 {{ currentDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }) }},为客户提供最佳的设计参考</p>
+      </section>
+
+      <!-- 筛选区域 -->
+      <section class="filter-section">
+        <div class="filter-header">
+          <button class="filter-toggle-btn" (click)="toggleFilterPanel()">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <line x1="4" y1="10" x2="20" y2="10"></line>
+              <line x1="4" y1="14" x2="20" y2="14"></line>
+              <line x1="4" y1="18" x2="13" y2="18"></line>
+            </svg>
+            <span>高级筛选</span>
+          </button>
+          <button class="reset-filter-btn" (click)="resetFilters()">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <polyline points="23 4 23 10 17 10"></polyline>
+              <polyline points="1 20 1 14 7 14"></polyline>
+              <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
+            </svg>
+            <span>重置筛选</span>
+          </button>
+        </div>
+
+        <!-- 高级筛选面板 -->
+        <div class="filter-panel" [class.show]="showFilterPanel()">
+          <form [formGroup]="filterForm" class="filter-form">
+            <div class="filter-row">
+              <div class="filter-group">
+                <label>装修风格</label>
+                <div class="checkbox-group">
+                  <label *ngFor="let style of styleOptions" class="checkbox-item">
+                    <input 
+                      type="checkbox" 
+                      [value]="style"
+                      [checked]="filterForm.get('style')?.value?.includes(style) || false"
+                      (change)="onStyleChange(style, $event.target.checked)"
+                    />
+                    <span>{{ style }}</span>
+                  </label>
+                </div>
+              </div>
+              
+              <div class="filter-group">
+                <label>户型</label>
+                <select formControlName="houseType">
+                  <option value="">全部户型</option>
+                  <option *ngFor="let type of houseTypeOptions" [value]="type">{{ type }}</option>
+                </select>
+              </div>
+              
+              <div class="filter-group">
+                <label>楼盘</label>
+                <select formControlName="property">
+                  <option value="">全部楼盘</option>
+                  <option *ngFor="let property of propertyOptions" [value]="property">{{ property }}</option>
+                </select>
+              </div>
+            </div>
+            
+            <div class="filter-row">
+              <div class="filter-group">
+                <label>房屋面积</label>
+                <div class="range-inputs">
+                  <input 
+                    type="number" 
+                    formControlName="minArea"
+                    placeholder="最小面积"
+                    min="0"
+                  />
+                  <span>-</span>
+                  <input 
+                    type="number" 
+                    formControlName="maxArea"
+                    placeholder="最大面积"
+                    min="0"
+                  />
+                  <span>㎡</span>
+                </div>
+              </div>
+              
+              <div class="filter-group">
+                <label>收藏案例</label>
+                <label class="checkbox-item full-width">
+                  <input type="checkbox" formControlName="favorite" />
+                  <span>仅显示我收藏的案例</span>
+                </label>
+              </div>
+            </div>
+          </form>
+        </div>
+      </section>
+
+      <!-- 案例展示区域 -->
+      <section class="cases-section">
+        <div class="section-header">
+          <h3>精选案例 <span class="cases-count">({{ filteredCases().length }})</span></h3>
+        </div>
+        
+        <!-- 案例网格 -->
+        <div class="cases-grid">
+          <div *ngIf="filteredCases().length === 0" class="empty-state">
+            <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
+              <circle cx="8.5" cy="8.5" r="1.5"></circle>
+              <polyline points="21 15 16 10 5 21"></polyline>
+            </svg>
+            <p>未找到符合条件的案例</p>
+            <button class="btn-reset" (click)="resetFilters()">重置筛选条件</button>
+          </div>
+          
+          <div *ngFor="let caseItem of paginatedCases()" class="case-card" (click)="viewCaseDetails(caseItem)">
+            <div class="case-image-container">
+              <img [src]="caseItem.coverImage" [alt]="caseItem.name" class="case-image">
+              <div class="case-overlay">
+                <button class="favorite-btn" (click)="$event.stopPropagation(); toggleFavorite(caseItem.id)">
+                  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
+                  </svg>
+                  <span *ngIf="caseItem.isFavorite">已收藏</span>
+                  <span *ngIf="!caseItem.isFavorite">收藏</span>
+                </button>
+                <button class="share-btn" (click)="$event.stopPropagation(); shareCase(caseItem.id)">
+                  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path>
+                    <polyline points="16 6 12 2 8 6"></polyline>
+                    <line x1="12" y1="2" x2="12" y2="15"></line>
+                  </svg>
+                  <span>分享</span>
+                </button>
+              </div>
+            </div>
+            
+            <div class="case-info">
+              <h4 class="case-name">{{ caseItem.name }}</h4>
+              
+              <div class="case-meta">
+                <div class="meta-item">
+                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
+                    <circle cx="12" cy="7" r="4"></circle>
+                  </svg>
+                  <span>{{ caseItem.designer }}</span>
+                </div>
+                <div class="meta-item">
+                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
+                  </svg>
+                  <span>{{ caseItem.area }}㎡</span>
+                </div>
+                <div class="meta-item">
+                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <path d="M14 10h4.764a2 2 0 0 1 1.789 2.894l-3.5 7A2 2 0 0 1 15.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 0 0-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 0 1-2-2v-6a2 2 0 0 1 2-2h2.5"></path>
+                  </svg>
+                  <span>{{ caseItem.views }}浏览</span>
+                </div>
+              </div>
+              
+              <div class="case-tags">
+                <span *ngFor="let tag of caseItem.tags" class="tag">{{ tag }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 分页控件 -->
+        <div class="pagination" *ngIf="totalPages() > 1">
+          <button class="page-btn" (click)="prevPage()" [disabled]="currentPage() === 1">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <polyline points="15 18 9 12 15 6"></polyline>
+            </svg>
+          </button>
+          
+          <button 
+            *ngFor="let page of pageNumbers()"
+            class="page-btn"
+            [class.active]="page === currentPage()"
+            (click)="goToPage(page)"
+            [disabled]="page === -1"
+          >
+            {{ page === -1 ? '...' : page }}
+          </button>
+          
+          <button class="page-btn" (click)="nextPage()" [disabled]="currentPage() === totalPages()">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <polyline points="9 18 15 12 9 6"></polyline>
+            </svg>
+          </button>
+        </div>
+      </section>
+    </div>
+  </main>
+
+  <!-- 案例详情模态框 -->
+  <div class="case-modal" *ngIf="selectedCase()" (click)="closeCaseDetails()">
+    <div class="modal-content" (click)="$event.stopPropagation()">
+      <button class="close-btn" (click)="closeCaseDetails()">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <line x1="18" y1="6" x2="6" y2="18"></line>
+          <line x1="6" y1="6" x2="18" y2="18"></line>
+        </svg>
+      </button>
+      
+      <div class="case-detail-header">
+        <h2>{{ selectedCase()?.name }}</h2>
+        <div class="case-detail-meta">
+          <span>{{ selectedCase()?.designer }}</span>
+          <span>{{ selectedCase()?.area }}㎡</span>
+          <span>{{ formatDate(selectedCase()?.createdAt!) }}</span>
+        </div>
+      </div>
+      
+      <!-- 案例图片轮播 -->
+      <div class="case-image-gallery">
+        <div class="main-image">
+          <img [src]="selectedCase()?.coverImage" [alt]="selectedCase()?.name">
+        </div>
+        <div class="thumbnails">
+          <img 
+            *ngFor="let image of selectedCase()?.detailImages"
+            [src]="image"
+            [alt]="selectedCase()?.name"
+            class="thumbnail"
+          >
+        </div>
+      </div>
+      
+      <!-- 案例详情信息 -->
+      <div class="case-detail-info">
+        <div class="info-section">
+          <h3>案例详情</h3>
+          <p>{{ selectedCase()?.description }}</p>
+          <p>本案例采用了{{ selectedCase()?.style }}风格设计,为{{ selectedCase()?.houseType }}户型,面积{{ selectedCase()?.area }}平方米。设计师{{ selectedCase()?.designer }}根据客户需求,融合了现代美学与实用功能,打造了舒适且富有个性的居住空间。</p>
+        </div>
+        
+        <div class="info-section">
+          <h3>基本信息</h3>
+          <div class="info-grid">
+            <div class="info-item">
+              <label>风格</label>
+              <span>{{ getSelectedCaseStyle() }}</span>
+            </div>
+            <div class="info-item">
+              <label>户型</label>
+              <span>{{ selectedCase()?.houseType }}</span>
+            </div>
+            <div class="info-item">
+              <label>楼盘</label>
+              <span>{{ selectedCase()?.property }}</span>
+            </div>
+            <div class="info-item">
+              <label>面积</label>
+              <span>{{ selectedCase()?.area }}㎡</span>
+            </div>
+            <div class="info-item">
+              <label>分类</label>
+              <span>{{ selectedCase()?.category }}</span>
+            </div>
+            <div class="info-item">
+              <label>浏览量</label>
+              <span>{{ selectedCase()?.views }}</span>
+            </div>
+          </div>
+        </div>
+        
+        <div class="info-section">
+          <h3>标签</h3>
+          <div class="tags-container">
+            <span *ngFor="let tag of selectedCase()?.tags" class="tag">{{ tag }}</span>
+          </div>
+        </div>
+      </div>
+      
+      <!-- 操作按钮 -->
+      <div class="case-actions">
+        <button 
+          class="primary-btn"
+          (click)="toggleFavorite(selectedCase()?.id!)"
+        >
+          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
+          </svg>
+          <span *ngIf="selectedCase()?.isFavorite">已收藏</span>
+          <span *ngIf="!selectedCase()?.isFavorite">收藏案例</span>
+        </button>
+        <button class="secondary-btn" (click)="shareCase(selectedCase()?.id!)">
+          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path>
+            <polyline points="16 6 12 2 8 6"></polyline>
+            <line x1="12" y1="2" x2="12" y2="15"></line>
+          </svg>
+          <span>分享案例</span>
+        </button>
+        <button class="secondary-btn">
+          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
+          </svg>
+          <span>查看原图</span>
+        </button>
+      </div>
+    </div>
+  </div>
+</div>

+ 922 - 0
src/app/pages/customer-service/case-library/case-library.scss

@@ -0,0 +1,922 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$primary-dark: #0E42CB;
+$secondary-color: #4E5BA6;
+$success-color: #00B42A;
+$warning-color: #FF7D00;
+$danger-color: #F53F3F;
+$text-primary: #1D2129;
+$text-secondary: #4E5969;
+$text-tertiary: #86909C;
+$text-light: #C9CDD4; // 新增浅色文本变量
+$border-color: #E5E6EB;
+$background-primary: #FFFFFF;
+$background-secondary: #F2F3F5;
+$background-tertiary: #F7F8FA;
+$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
+$shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.1);
+$border-radius: 8px;
+$transition: all 0.3s ease;
+
+// 主容器
+.case-library-container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  background-color: $background-secondary;
+  color: $text-primary;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+}
+
+// 顶部导航栏
+.top-navbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 24px;
+  height: 64px;
+  background-color: $background-primary;
+  border-bottom: 1px solid $border-color;
+  box-shadow: $shadow-sm;
+  position: sticky;
+  top: 0;
+  z-index: 1000;
+
+  .navbar-left {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+  }
+
+  .menu-toggle {
+    display: none;
+    background: none;
+    border: none;
+    cursor: pointer;
+    color: $text-primary;
+    padding: 8px;
+    transition: $transition;
+
+    &:hover {
+      color: $primary-color;
+    }
+  }
+
+  .app-title {
+    font-size: 20px;
+    font-weight: 600;
+    color: $primary-color;
+  }
+
+  .navbar-center {
+    flex: 1;
+    max-width: 400px;
+    margin: 0 32px;
+  }
+
+  .search-container {
+    display: flex;
+    align-items: center;
+    background-color: $background-tertiary;
+    border-radius: 20px;
+    padding: 8px 16px;
+    height: 36px;
+
+    .search-input {
+      flex: 1;
+      border: none;
+      background: none;
+      outline: none;
+      font-size: 14px;
+      color: $text-primary;
+
+      &::placeholder {
+        color: $text-tertiary;
+      }
+    }
+
+    .search-button {
+      background: none;
+      border: none;
+      cursor: pointer;
+      color: $text-tertiary;
+      padding: 4px;
+      transition: $transition;
+
+      &:hover {
+        color: $primary-color;
+      }
+    }
+  }
+
+  .navbar-right {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+  }
+
+  .notification-btn {
+    position: relative;
+    background: none;
+    border: none;
+    cursor: pointer;
+    color: $text-secondary;
+    padding: 8px;
+    transition: $transition;
+
+    &:hover {
+      color: $primary-color;
+    }
+
+    .notification-badge {
+      position: absolute;
+      top: 0;
+      right: 0;
+      background-color: $danger-color;
+      color: white;
+      font-size: 10px;
+      padding: 2px 6px;
+      border-radius: 10px;
+      min-width: 16px;
+      text-align: center;
+    }
+  }
+// 在分页相关样式中添加省略号样式
+.pagination-ellipsis {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 36px;
+  height: 36px;
+  font-size: 14px;
+  color: $text-light;
+  cursor: default;
+}
+  .user-profile {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+
+    .user-avatar {
+      width: 36px;
+      height: 36px;
+      border-radius: 50%;
+      object-fit: cover;
+    }
+
+    .user-name {
+      font-size: 14px;
+      font-weight: 500;
+      color: $text-primary;
+    }
+  }
+}
+
+// 主要内容区
+.dashboard-content {
+  display: flex;
+  flex: 1;
+  overflow: hidden;
+}
+
+// 左侧侧边栏
+.sidebar {
+  width: 220px;
+  background-color: $background-primary;
+  border-right: 1px solid $border-color;
+  display: flex;
+  flex-direction: column;
+  transition: $transition;
+
+  .sidebar-nav {
+    flex: 1;
+    padding: 16px 0;
+
+    .nav-item {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 12px 24px;
+      color: $text-secondary;
+      text-decoration: none;
+      border-left: 3px solid transparent;
+      transition: $transition;
+
+      &:hover {
+        background-color: $background-tertiary;
+        color: $primary-color;
+      }
+
+      &.active {
+        color: $primary-color;
+        background-color: color-mix(in srgb, $primary-color 5%, transparent);
+        border-left-color: $primary-color;
+        font-weight: 500;
+      }
+    }
+  }
+
+  .sidebar-footer {
+    padding: 16px 24px;
+    border-top: 1px solid $border-color;
+
+    .storage-info {
+      margin-bottom: 16px;
+      font-size: 12px;
+      color: $text-tertiary;
+    }
+
+    .logout-btn {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      padding: 8px 12px;
+      background: none;
+      border: 1px solid $border-color;
+      border-radius: $border-radius;
+      color: $text-secondary;
+      cursor: pointer;
+      font-size: 14px;
+      transition: $transition;
+
+      &:hover {
+        background-color: $background-tertiary;
+        border-color: $danger-color;
+        color: $danger-color;
+      }
+    }
+  }
+}
+
+// 中间内容区
+.content-wrapper {
+  flex: 1;
+  overflow-y: auto;
+  padding: 24px;
+}
+
+// 欢迎区域
+.welcome-section {
+  margin-bottom: 24px;
+
+  h2 {
+    font-size: 24px;
+    font-weight: 600;
+    margin-bottom: 8px;
+    color: $text-primary;
+  }
+
+  p {
+    font-size: 14px;
+    color: $text-secondary;
+  }
+}
+
+// 筛选区域
+.filter-section {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  padding: 16px 20px;
+  box-shadow: $shadow-sm;
+  margin-bottom: 24px;
+}
+
+.filter-header {
+  display: flex;
+  gap: 12px;
+}
+
+.filter-toggle-btn,
+.reset-filter-btn {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 8px 16px;
+  background-color: $background-tertiary;
+  border: 1px solid $border-color;
+  border-radius: $border-radius;
+  color: $text-secondary;
+  cursor: pointer;
+  font-size: 14px;
+  transition: $transition;
+
+  &:hover {
+    background-color: $background-secondary;
+    border-color: $primary-color;
+    color: $primary-color;
+  }
+}
+
+.filter-panel {
+  margin-top: 16px;
+  padding-top: 16px;
+  border-top: 1px solid $border-color;
+  display: none;
+  animation: slideDown 0.3s ease;
+}
+
+.filter-panel.show {
+  display: block;
+}
+
+@keyframes slideDown {
+  from {
+    opacity: 0;
+    transform: translateY(-10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.filter-form {
+  .filter-row {
+    display: flex;
+    gap: 24px;
+    margin-bottom: 16px;
+    flex-wrap: wrap;
+  }
+  
+  .filter-group {
+    flex: 1;
+    min-width: 200px;
+    label {
+      display: block;
+      font-size: 14px;
+      font-weight: 500;
+      color: $text-primary;
+      margin-bottom: 8px;
+    }
+    select,
+    input[type="number"] {
+      width: 100%;
+      padding: 8px 12px;
+      border: 1px solid $border-color;
+      border-radius: $border-radius;
+      font-size: 14px;
+      transition: $transition;
+      &:focus {
+        outline: none;
+        border-color: $primary-color;
+        box-shadow: 0 0 0 3px color-mix(in srgb, $primary-color 10%, transparent);
+      }
+    }
+  }
+  
+  .checkbox-group {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 12px;
+  }
+  
+  .checkbox-item {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    font-size: 14px;
+    color: $text-secondary;
+    cursor: pointer;
+    input[type="checkbox"] {
+      width: auto;
+    }
+    &.full-width {
+      width: 100%;
+      justify-content: flex-start;
+    }
+  }
+  
+  .range-inputs {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    input {
+      flex: 1;
+    }
+    span {
+      font-size: 14px;
+      color: $text-secondary;
+    }
+  }
+}
+
+// 案例展示区域
+.cases-section {
+  .section-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 16px;
+    h3 {
+      font-size: 18px;
+      font-weight: 600;
+      color: $text-primary;
+      .cases-count {
+        font-size: 14px;
+        font-weight: normal;
+        color: $text-tertiary;
+      }
+    }
+  }
+}
+
+// 案例网格
+.cases-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+  gap: 20px;
+  margin-bottom: 24px;
+}
+
+// 案例卡片
+.case-card {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  overflow: hidden;
+  box-shadow: $shadow-sm;
+  cursor: pointer;
+  transition: $transition;
+  border: 2px solid transparent;
+  
+  &:hover {
+    border-color: $primary-color;
+    transform: translateY(-4px);
+    box-shadow: $shadow-md;
+  }
+}
+
+.case-image-container {
+  position: relative;
+  height: 200px;
+  overflow: hidden;
+}
+
+.case-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  transition: transform 0.5s ease;
+}
+
+.case-card:hover .case-image {
+  transform: scale(1.05);
+}
+
+.case-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: linear-gradient(to top, rgba(0, 0, 0, 0.6) 0%, transparent 50%);
+  opacity: 0;
+  transition: opacity 0.3s ease;
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-end;
+  padding: 16px;
+  gap: 8px;
+}
+
+.case-card:hover .case-overlay {
+  opacity: 1;
+}
+
+.favorite-btn,
+.share-btn {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 8px 12px;
+  background-color: white;
+  border: none;
+  border-radius: 20px;
+  color: $text-secondary;
+  cursor: pointer;
+  font-size: 12px;
+  font-weight: 500;
+  transition: $transition;
+  
+  &:hover {
+    background-color: $primary-color;
+    color: white;
+  }
+}
+
+.favorite-btn.favorited {
+  background-color: $danger-color;
+  color: white;
+}
+
+.case-info {
+  padding: 16px;
+}
+
+.case-name {
+  font-size: 16px;
+  font-weight: 600;
+  color: $text-primary;
+  margin-bottom: 8px;
+  display: -webkit-box;
+  -webkit-line-clamp: 1;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+
+.case-meta {
+  display: flex;
+  gap: 12px;
+  margin-bottom: 12px;
+}
+
+.meta-item {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 12px;
+  color: $text-tertiary;
+}
+
+.case-tags {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 6px;
+}
+
+.tag {
+  display: inline-block;
+  padding: 4px 10px;
+  background-color: $background-tertiary;
+  color: $text-secondary;
+  border-radius: 12px;
+  font-size: 11px;
+}
+
+// 空状态
+.empty-state {
+  grid-column: 1 / -1;
+  text-align: center;
+  padding: 60px 20px;
+  color: $text-tertiary;
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  border: 1px dashed $border-color;
+  
+  svg {
+    margin-bottom: 16px;
+  }
+  
+  p {
+    margin-bottom: 16px;
+    font-size: 14px;
+  }
+  
+  .btn-reset {
+    padding: 8px 20px;
+    background-color: $background-tertiary;
+    border: 1px solid $border-color;
+    border-radius: $border-radius;
+    color: $text-secondary;
+    cursor: pointer;
+    font-size: 14px;
+    transition: $transition;
+    
+    &:hover {
+      background-color: $background-secondary;
+      border-color: $primary-color;
+      color: $primary-color;
+    }
+  }
+}
+
+// 分页控件
+.pagination {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 4px;
+}
+
+.page-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 36px;
+  height: 36px;
+  background-color: $background-primary;
+  border: 1px solid $border-color;
+  border-radius: $border-radius;
+  color: $text-secondary;
+  cursor: pointer;
+  transition: $transition;
+  
+  &:hover:not(:disabled) {
+    background-color: $background-tertiary;
+    border-color: $primary-color;
+    color: $primary-color;
+  }
+  
+  &.active {
+    background-color: $primary-color;
+    border-color: $primary-color;
+    color: white;
+  }
+  
+  &:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+}
+
+// 案例详情模态框
+.case-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.7);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 2000;
+  padding: 20px;
+  animation: fadeIn 0.3s ease;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+.modal-content {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  max-width: 900px;
+  width: 100%;
+  max-height: 90vh;
+  overflow-y: auto;
+  position: relative;
+  animation: slideUp 0.3s ease;
+}
+
+@keyframes slideUp {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.close-btn {
+  position: absolute;
+  top: 16px;
+  right: 16px;
+  width: 32px;
+  height: 32px;
+  background-color: rgba(0, 0, 0, 0.1);
+  border: none;
+  border-radius: 50%;
+  color: $text-secondary;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: $transition;
+  z-index: 10;
+  
+  &:hover {
+    background-color: $danger-color;
+    color: white;
+  }
+}
+
+.case-detail-header {
+  padding: 24px 24px 0;
+  
+  h2 {
+    font-size: 24px;
+    font-weight: 600;
+    color: $text-primary;
+    margin-bottom: 8px;
+  }
+  
+  .case-detail-meta {
+    display: flex;
+    gap: 16px;
+    font-size: 14px;
+    color: $text-secondary;
+  }
+}
+
+.case-image-gallery {
+  margin: 20px 0;
+  
+  .main-image {
+    height: 400px;
+    overflow: hidden;
+    
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+    }
+  }
+  
+  .thumbnails {
+    display: flex;
+    gap: 8px;
+    padding: 12px 24px;
+    overflow-x: auto;
+    
+    .thumbnail {
+      width: 80px;
+      height: 60px;
+      object-fit: cover;
+      border-radius: 4px;
+      cursor: pointer;
+      border: 2px solid transparent;
+      transition: $transition;
+      
+      &:hover {
+        border-color: $primary-color;
+      }
+    }
+  }
+}
+
+.case-detail-info {
+  padding: 0 24px 24px;
+  
+  .info-section {
+    margin-bottom: 24px;
+    
+    h3 {
+      font-size: 18px;
+      font-weight: 600;
+      color: $text-primary;
+      margin-bottom: 12px;
+    }
+    
+    p {
+      font-size: 14px;
+      line-height: 1.6;
+      color: $text-secondary;
+      margin-bottom: 8px;
+    }
+  }
+  
+  .info-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+    gap: 16px;
+    
+    .info-item {
+      
+      label {
+        display: block;
+        font-size: 12px;
+        color: $text-tertiary;
+        margin-bottom: 4px;
+      }
+      
+      span {
+        font-size: 14px;
+        color: $text-primary;
+        font-weight: 500;
+      }
+    }
+  }
+  
+  .tags-container {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+  }
+}
+
+.case-actions {
+  display: flex;
+  gap: 12px;
+  padding: 20px 24px;
+  background-color: $background-tertiary;
+  border-top: 1px solid $border-color;
+  
+  .primary-btn,
+  .secondary-btn {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    padding: 10px 20px;
+    border-radius: $border-radius;
+    font-size: 14px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: $transition;
+    border: none;
+  }
+  
+  .primary-btn {
+    background-color: $primary-color;
+    color: white;
+    
+    &:hover {
+      background-color: $primary-dark;
+      transform: translateY(-1px);
+      box-shadow: $shadow-md;
+    }
+  }
+  
+  .secondary-btn {
+    background-color: $background-primary;
+    color: $text-secondary;
+    border: 1px solid $border-color;
+    
+    &:hover {
+      background-color: $background-secondary;
+      border-color: $primary-color;
+      color: $primary-color;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .cases-grid {
+    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+  }
+}
+
+@media (max-width: 768px) {
+  .dashboard-content {
+    flex-direction: column;
+  }
+  
+  .sidebar {
+    width: 100%;
+    height: auto;
+    border-right: none;
+    border-bottom: 1px solid $border-color;
+    
+    .sidebar-nav {
+      padding: 8px 0;
+      
+      .nav-item {
+        padding: 10px 16px;
+      }
+    }
+  }
+  
+  .content-wrapper {
+    padding: 16px;
+  }
+  
+  .menu-toggle {
+    display: block;
+  }
+  
+  .navbar-center {
+    margin: 0 16px;
+  }
+  
+  .cases-grid {
+    grid-template-columns: 1fr;
+  }
+  
+  .filter-form .filter-row {
+    flex-direction: column;
+    gap: 16px;
+  }
+  
+  .filter-form .filter-group {
+    min-width: 100%;
+  }
+  
+  .modal-content {
+    margin: 10px;
+    max-height: calc(100vh - 20px);
+  }
+  
+  .case-image-gallery .main-image {
+    height: 250px;
+  }
+  
+  .case-actions {
+    flex-direction: column;
+    
+    .primary-btn,
+    .secondary-btn {
+      width: 100%;
+      justify-content: center;
+    }
+  }
+}

+ 301 - 3
src/app/pages/customer-service/case-library/case-library.ts

@@ -1,11 +1,309 @@
-import { Component } from '@angular/core';
+import { Component, OnInit, signal, computed } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup } from '@angular/forms';
+import { RouterModule } from '@angular/router';
+
+// 定义案例接口
+interface CaseItem {
+  id: string;
+  name: string;
+  category: string;
+  style: string[];
+  houseType: string;
+  property: string;
+  designer: string;
+  area: number;
+  createdAt: Date;
+  coverImage: string;
+  detailImages: string[];
+  isFavorite: boolean;
+  tags: string[];
+  views: number;
+  description: string;
+}
 
 @Component({
   selector: 'app-case-library',
-  imports: [],
+  standalone: true,
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, RouterModule],
   templateUrl: './case-library.html',
   styleUrl: './case-library.scss'
 })
-export class CaseLibrary {
+export class CaseLibrary implements OnInit {
+  // 当前日期
+  currentDate = new Date();
+  
+  // 搜索关键词
+  searchTerm = signal('');
+  
+  // 筛选表单
+  filterForm: FormGroup;
+  
+  // 案例列表
+  cases = signal<CaseItem[]>([]);
+  
+  // 筛选后的案例
+  filteredCases = computed(() => {
+    let result = [...this.cases()];
+    
+    // 应用搜索筛选
+    if (this.searchTerm()) {
+      const searchLower = this.searchTerm().toLowerCase();
+      result = result.filter(caseItem => 
+        caseItem.name.toLowerCase().includes(searchLower) ||
+        caseItem.designer.toLowerCase().includes(searchLower) ||
+        caseItem.description.toLowerCase().includes(searchLower) ||
+        caseItem.tags.some(tag => tag.toLowerCase().includes(searchLower))
+      );
+    }
+    
+    // 应用表单筛选
+    const filters = this.filterForm.value;
+    
+    if (filters.style && filters.style.length > 0) {
+      result = result.filter(caseItem => 
+        caseItem.style.some(style => filters.style.includes(style))
+      );
+    }
+    
+    if (filters.houseType) {
+      result = result.filter(caseItem => caseItem.houseType === filters.houseType);
+    }
+    
+    if (filters.property) {
+      result = result.filter(caseItem => caseItem.property === filters.property);
+    }
+    
+    if (filters.minArea) {
+      result = result.filter(caseItem => caseItem.area >= filters.minArea);
+    }
+    
+    if (filters.maxArea) {
+      result = result.filter(caseItem => caseItem.area <= filters.maxArea);
+    }
+    
+    if (filters.favorite) {
+      result = result.filter(caseItem => caseItem.isFavorite);
+    }
+    
+    return result;
+  });
+  
+  // 显示筛选面板
+  showFilterPanel = signal(false);
+  
+  // 当前查看的案例详情
+  selectedCase = signal<CaseItem | null>(null);
+  
+  // 分页信息
+  currentPage = signal(1);
+  itemsPerPage = signal(12);
+  
+  // 分页后的案例
+  paginatedCases = computed(() => {
+    const startIndex = (this.currentPage() - 1) * this.itemsPerPage();
+    return this.filteredCases().slice(startIndex, startIndex + this.itemsPerPage());
+  });
+  
+  // 总页数
+  totalPages = computed(() => {
+    return Math.ceil(this.filteredCases().length / this.itemsPerPage());
+  });
+  
+  // 筛选选项
+  styleOptions = ['现代简约', '北欧风', '工业风', '新中式', '法式轻奢', '日式', '美式', '混搭'];
+  houseTypeOptions = ['一室一厅', '两室一厅', '两室两厅', '三室一厅', '三室两厅', '四室两厅', '复式', '别墅', '其他'];
+  propertyOptions = ['万科', '绿城', '保利', '龙湖', '融创', '中海', '碧桂园', '其他'];
+  
+  constructor(private fb: FormBuilder) {
+    // 初始化筛选表单
+    this.filterForm = this.fb.group({
+      style: [[]],
+      houseType: [''],
+      property: [''],
+      minArea: [''],
+      maxArea: [''],
+      favorite: [false]
+    });
+  }
+  
+  ngOnInit(): void {
+    // 加载模拟案例数据
+    this.loadCases();
+  }
+  
+  // 加载案例数据
+  loadCases(): void {
+    // 模拟API请求获取案例数据
+    const mockCases: CaseItem[] = Array.from({ length: 24 }, (_, i) => ({
+      id: `case-${i + 1}`,
+      name: `${this.styleOptions[Math.floor(Math.random() * this.styleOptions.length)]}风格 ${this.houseTypeOptions[Math.floor(Math.random() * this.houseTypeOptions.length)]}设计`,
+      category: ['客厅', '卧室', '厨房', '浴室', '书房', '餐厅'][Math.floor(Math.random() * 6)],
+      style: [this.styleOptions[Math.floor(Math.random() * this.styleOptions.length)]],
+      houseType: this.houseTypeOptions[Math.floor(Math.random() * this.houseTypeOptions.length)],
+      property: this.propertyOptions[Math.floor(Math.random() * this.propertyOptions.length)],
+      designer: ['张设计', '李设计', '王设计', '赵设计', '陈设计'][Math.floor(Math.random() * 5)],
+      area: Math.floor(Math.random() * 100) + 50, // 50-150㎡
+      createdAt: new Date(Date.now() - Math.floor(Math.random() * 365) * 24 * 60 * 60 * 1000),
+      coverImage: `https://picsum.photos/id/${100 + i}/600/400`,
+      detailImages: Array.from({ length: 4 }, (_, j) => `https://picsum.photos/id/${130 + i + j}/800/600`),
+      isFavorite: Math.random() > 0.7,
+      tags: ['热门', '精选', '新上传', '高性价比', '业主好评'].filter(() => Math.random() > 0.5),
+      views: Math.floor(Math.random() * 1000) + 100,
+      description: '这是一个精美的' + ['现代简约', '北欧风', '新中式'][Math.floor(Math.random() * 3)] + '风格设计案例,融合了功能性与美学,为客户打造了舒适宜人的居住环境。'
+    }));
+    
+    this.cases.set(mockCases);
+  }
+  
+  // 切换收藏状态
+  toggleFavorite(caseId: string): void {
+    this.cases.set(
+      this.cases().map(caseItem => 
+        caseItem.id === caseId 
+          ? { ...caseItem, isFavorite: !caseItem.isFavorite }
+          : caseItem
+      )
+    );
+  }
+  
+  // 查看案例详情
+  viewCaseDetails(caseItem: CaseItem): void {
+    this.selectedCase.set(caseItem);
+    // 增加浏览量
+    this.cases.set(
+      this.cases().map(item => 
+        item.id === caseItem.id 
+          ? { ...item, views: item.views + 1 }
+          : item
+      )
+    );
+  }
+  
+  // 关闭案例详情
+  closeCaseDetails(): void {
+    this.selectedCase.set(null);
+  }
+  
+  // 分享案例
+  shareCase(caseId: string): void {
+    console.log('分享案例:', caseId);
+    // 模拟复制到剪贴板
+    alert('案例链接已复制到剪贴板!');
+  }
+  
+  // 重置筛选条件
+  resetFilters(): void {
+    this.filterForm.reset({
+      style: [],
+      houseType: '',
+      property: '',
+      minArea: '',
+      maxArea: '',
+      favorite: false
+    });
+    this.searchTerm.set('');
+    this.currentPage.set(1);
+  }
+  
+  // 切换筛选面板
+  toggleFilterPanel(): void {
+    this.showFilterPanel.set(!this.showFilterPanel());
+  }
+  
+  // 分页导航
+  goToPage(page: number): void {
+    if (page >= 1 && page <= this.totalPages()) {
+      this.currentPage.set(page);
+    }
+  }
+  
+  // 上一页
+  prevPage(): void {
+    this.goToPage(this.currentPage() - 1);
+  }
+  
+  // 下一页
+  nextPage(): void {
+    this.goToPage(this.currentPage() + 1);
+  }
+  
+  // 格式化日期
+  formatDate(date: Date): string {
+    return new Date(date).toLocaleDateString('zh-CN', {
+      month: '2-digit',
+      day: '2-digit',
+      year: 'numeric'
+    });
+  }
+  // 智能页码生成
+pageNumbers = computed(() => {
+  const pages = [];
+  const total = this.totalPages();
+  const current = this.currentPage();
+  
+  // 显示当前页及前后2页,加上第一页和最后一页
+  const start = Math.max(1, current - 2);
+  const end = Math.min(total, current + 2);
+  
+  if (start > 1) {
+    pages.push(1);
+    if (start > 2) {
+      pages.push(-1); // 用-1表示省略号
+    }
+  }
+  
+  for (let i = start; i <= end; i++) {
+    pages.push(i);
+  }
+  
+  if (end < total) {
+    if (end < total - 1) {
+      pages.push(-1); // 用-1表示省略号
+    }
+    pages.push(total);
+  }
+  
+  return pages;
+});
+  // 格式化样式显示的辅助方法
+  getStyleDisplay(caseItem: CaseItem | null | undefined): string {
+    if (!caseItem || !caseItem.style) {
+      return '';
+    }
+    return Array.isArray(caseItem.style) ? caseItem.style.join('、') : String(caseItem.style);
+  }
+
+  // 如果需要直接获取当前选中案例的样式显示
+  getSelectedCaseStyle(): string {
+    return this.getStyleDisplay(this.selectedCase());
+  }
 
+  // 删除重复的简单分页逻辑
+  // 保留智能分页逻辑(第251-270行)
+  // 移除下面这段代码:
+  //  // 在组件类中添加这个计算属性
+  //  pageNumbers = computed(() => {
+  //    const pages = [];
+  //    for (let i = 1; i <= this.totalPages(); i++) {
+  //      pages.push(i);
+  //    }
+  //    return pages;
+  //  });
+  
+  // 修复 onStyleChange 方法中的类型安全问题
+  onStyleChange(style: string, isChecked: boolean): void {
+    const currentStyles = (this.filterForm.get('style')?.value || []) as string[];
+    let updatedStyles: string[];
+    
+    if (isChecked) {
+      // 如果勾选,则添加风格(避免重复)
+      updatedStyles = [...new Set([...currentStyles, style])];
+    } else {
+      // 如果取消勾选,则移除风格
+      updatedStyles = currentStyles.filter(s => s !== style);
+    }
+    
+    this.filterForm.patchValue({ style: updatedStyles });
+  }
 }

+ 222 - 1
src/app/pages/customer-service/consultation-order/consultation-order.html

@@ -1 +1,222 @@
-<p>consultation-order works!</p>
+<div class="consultation-order-container">
+  <!-- 页面标题 -->
+  <div class="page-header">
+    <h1>客户咨询与下单</h1>
+    <p>记录客户需求,快速生成报价,一键创建项目</p>
+  </div>
+
+  <!-- 成功提示 -->
+  <div *ngIf="showSuccessMessage()" class="success-message">
+    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+      <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
+      <polyline points="22 4 12 14.01 9 11.01"></polyline>
+    </svg>
+    <span>表单提交成功!</span>
+  </div>
+
+  <!-- 客户信息区域 -->
+  <section class="customer-info-section">
+    <div class="section-header">
+      <h2>客户信息</h2>
+      <button *ngIf="selectedCustomer()" class="clear-customer-btn" (click)="clearSelectedCustomer()">
+        清除客户信息
+      </button>
+    </div>
+    
+    <!-- 客户搜索 -->
+    <div *ngIf="!selectedCustomer()" class="customer-search">
+      <div class="search-input-group">
+        <input 
+          type="text" 
+          [(ngModel)]="searchKeyword" 
+          (input)="searchCustomer()"
+          placeholder="搜索客户姓名或手机号..."
+          class="search-input"
+          autocomplete="off"
+        />
+        <button class="search-btn">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <circle cx="11" cy="11" r="8"></circle>
+            <path d="m21 21-4.35-4.35"></path>
+          </svg>
+        </button>
+      </div>
+      
+      <!-- 搜索结果 -->
+      <div *ngIf="searchResults().length > 0" class="search-results">
+        <div *ngFor="let customer of searchResults()" class="customer-item" (click)="selectCustomer(customer)">
+          <img [src]="customer.avatar" [alt]="customer.name" class="customer-avatar">
+          <div class="customer-details">
+            <div class="customer-name">{{ customer.name }}</div>
+            <div class="customer-phone">{{ customer.phone }}</div>
+            <div class="customer-tags">
+              <span *ngIf="customer.customerType" class="tag">{{ customer.customerType }}</span>
+              <span *ngIf="customer.source" class="tag source">{{ customer.source }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 客户信息表单 -->
+    <form [formGroup]="customerForm" class="customer-form">
+      <div class="form-row">
+        <div class="form-group">
+          <label for="name">客户姓名 <span class="required">*</span></label>
+          <input type="text" id="name" formControlName="name" placeholder="请输入客户姓名">
+        </div>
+        <div class="form-group">
+          <label for="phone">手机号码 <span class="required">*</span></label>
+          <input type="tel" id="phone" formControlName="phone" placeholder="请输入手机号码">
+        </div>
+        <div class="form-group">
+          <label for="wechat">微信</label>
+          <input type="text" id="wechat" formControlName="wechat" placeholder="请输入微信账号">
+        </div>
+      </div>
+      <div class="form-row">
+        <div class="form-group">
+          <label for="customerType">客户类型</label>
+          <select id="customerType" formControlName="customerType">
+            <option value="新客户">新客户</option>
+            <option value="老客户">老客户</option>
+            <option value="VIP客户">VIP客户</option>
+          </select>
+        </div>
+        <div class="form-group">
+          <label for="source">来源渠道</label>
+          <input type="text" id="source" formControlName="source" placeholder="如:官网咨询、推荐介绍等">
+        </div>
+      </div>
+      <div class="form-group full-width">
+        <label for="remark">备注</label>
+        <textarea id="remark" formControlName="remark" rows="2" placeholder="请输入其他备注信息"></textarea>
+      </div>
+    </form>
+  </section>
+
+  <!-- 需求信息区域 -->
+  <section class="requirement-section">
+    <div class="section-header">
+      <h2>需求信息</h2>
+    </div>
+    
+    <form [formGroup]="requirementForm" class="requirement-form">
+      <div class="form-row">
+        <div class="form-group">
+          <label for="style">装修风格 <span class="required">*</span></label>
+          <select id="style" formControlName="style">
+            <option value="">请选择装修风格</option>
+            <option *ngFor="let style of styleOptions" [value]="style">{{ style }}</option>
+          </select>
+        </div>
+        <div class="form-group">
+          <label for="budget">预算范围 <span class="required">*</span></label>
+          <select id="budget" formControlName="budget">
+            <option value="">请选择预算范围</option>
+            <option value="5-10万">5-10万</option>
+            <option value="10-20万">10-20万</option>
+            <option value="20-30万">20-30万</option>
+            <option value="30-50万">30-50万</option>
+            <option value="50万以上">50万以上</option>
+          </select>
+        </div>
+        <div class="form-group">
+          <label for="area">房屋面积 <span class="required">*</span></label>
+          <input type="number" id="area" formControlName="area" placeholder="请输入房屋面积(㎡)">
+        </div>
+      </div>
+      <div class="form-row">
+        <div class="form-group">
+          <label for="houseType">户型 <span class="required">*</span></label>
+          <select id="houseType" formControlName="houseType">
+            <option value="">请选择户型</option>
+            <option *ngFor="let houseType of houseTypeOptions" [value]="houseType">{{ houseType }}</option>
+          </select>
+        </div>
+        <div class="form-group">
+          <label for="floor">楼层</label>
+          <input type="number" id="floor" formControlName="floor" placeholder="请输入楼层">
+        </div>
+        <div class="form-group">
+          <label for="decorationType">装修类型 <span class="required">*</span></label>
+          <select id="decorationType" formControlName="decorationType">
+            <option value="">请选择装修类型</option>
+            <option *ngFor="let type of decorationTypeOptions" [value]="type">{{ type }}</option>
+          </select>
+        </div>
+      </div>
+      <div class="form-row">
+        <div class="form-group">
+          <label for="preferredDesigner">意向设计师</label>
+          <input type="text" id="preferredDesigner" formControlName="preferredDesigner" placeholder="请输入意向设计师姓名">
+        </div>
+      </div>
+      <div class="form-group full-width">
+        <label for="specialRequirements">特殊需求</label>
+        <textarea id="specialRequirements" formControlName="specialRequirements" rows="3" placeholder="请描述特殊需求或关注点"></textarea>
+      </div>
+    </form>
+  </section>
+
+  <!-- 报价与案例匹配区域 -->
+  <section class="price-match-section">
+    <div class="price-info">
+      <div class="price-label">预估报价范围</div>
+      <div class="price-value">{{ estimatedPriceRange() || '请填写需求信息以获取报价' }}</div>
+    </div>
+    
+    <!-- 匹配案例 -->
+    <div class="matched-cases" *ngIf="matchedCases().length > 0">
+      <h3>推荐案例(点击选择)</h3>
+      <div class="cases-grid">
+        <div *ngFor="let caseItem of matchedCases()" class="case-card" (click)="selectReferenceCase(caseItem)">
+          <img [src]="caseItem.imageUrl" [alt]="caseItem.name" class="case-image">
+          <div class="case-info">
+            <h4 class="case-name">{{ caseItem.name }}</h4>
+            <div class="case-details">
+              <span class="case-designer">{{ caseItem.designer }}</span>
+              <span class="case-area">{{ caseItem.area }}</span>
+            </div>
+            <div class="similarity-badge">匹配度 {{ caseItem.similarity }}%</div>
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 已选参考案例 -->
+    <div *ngIf="requirementForm.get('referenceCases')?.value.length > 0" class="selected-cases">
+      <h3>已选参考案例</h3>
+      <div class="selected-cases-list">
+        <div *ngFor="let caseId of requirementForm.get('referenceCases')?.value" class="selected-case-item">
+          <span>案例 #{{ caseId }}</span>
+          <button class="remove-case-btn" (click)="removeReferenceCase(caseId)">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <line x1="18" y1="6" x2="6" y2="18"></line>
+              <line x1="6" y1="6" x2="18" y2="18"></line>
+            </svg>
+          </button>
+        </div>
+      </div>
+    </div>
+  </section>
+
+  <!-- 操作按钮区域 -->
+  <section class="action-section">
+    <button 
+      class="primary-btn" 
+      (click)="submitForm()"
+      [disabled]="isSubmitting() || !requirementForm.valid || !customerForm.valid"
+    >
+      <span *ngIf="!isSubmitting()">创建项目</span>
+      <span *ngIf="isSubmitting()">提交中...</span>
+    </button>
+    <button 
+      class="secondary-btn" 
+      (click)="createProjectGroup()"
+      [disabled]="isSubmitting() || !requirementForm.valid || !customerForm.valid"
+    >
+      一键拉群
+    </button>
+  </section>
+</div>

+ 521 - 0
src/app/pages/customer-service/consultation-order/consultation-order.scss

@@ -0,0 +1,521 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$primary-dark: #0E42CB;
+$secondary-color: #4E5BA6;
+$success-color: #00B42A;
+$warning-color: #FF7D00;
+$danger-color: #F53F3F;
+$text-primary: #1D2129;
+$text-secondary: #4E5969;
+$text-tertiary: #86909C;
+$border-color: #E5E6EB;
+$background-primary: #FFFFFF;
+$background-secondary: #F2F3F5;
+$background-tertiary: #F7F8FA;
+$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
+$shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.1);
+$border-radius: 8px;
+$transition: all 0.3s ease;
+
+// 主容器
+.consultation-order-container {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 24px;
+  color: $text-primary;
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+}
+
+// 页面标题
+.page-header {
+  margin-bottom: 32px;
+  h1 {
+    font-size: 28px;
+    font-weight: 600;
+    margin-bottom: 8px;
+    color: $text-primary;
+  }
+  p {
+    font-size: 14px;
+    color: $text-secondary;
+  }
+}
+
+// 成功提示
+.success-message {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 12px 16px;
+  background-color: color-mix(in srgb, $success-color 10%, transparent);
+  color: $success-color;
+  border-radius: $border-radius;
+  border-left: 4px solid $success-color;
+  margin-bottom: 24px;
+  animation: slideIn 0.3s ease;
+}
+
+@keyframes slideIn {
+  from {
+    opacity: 0;
+    transform: translateY(-10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+// 通用区域样式
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  h2 {
+    font-size: 20px;
+    font-weight: 600;
+    color: $text-primary;
+  }
+}
+
+// 客户信息区域
+.customer-info-section {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  padding: 24px;
+  box-shadow: $shadow-sm;
+  margin-bottom: 24px;
+}
+
+// 客户搜索
+.customer-search {
+  margin-bottom: 24px;
+}
+
+.search-input-group {
+  display: flex;
+  align-items: center;
+  position: relative;
+  max-width: 400px;
+}
+
+.search-input {
+  flex: 1;
+  padding: 10px 12px;
+  border: 1px solid $border-color;
+  border-radius: $border-radius 0 0 $border-radius;
+  font-size: 14px;
+  transition: $transition;
+  &:focus {
+    outline: none;
+    border-color: $primary-color;
+    box-shadow: 0 0 0 3px color-mix(in srgb, $primary-color 10%, transparent);
+  }
+}
+
+.search-btn {
+  padding: 10px 16px;
+  background-color: $primary-color;
+  color: white;
+  border: none;
+  border-radius: 0 $border-radius $border-radius 0;
+  cursor: pointer;
+  transition: $transition;
+  &:hover {
+    background-color: $primary-dark;
+    transform: translateY(-1px);
+    box-shadow: $shadow-md;
+  }
+  &:active {
+    transform: translateY(0);
+  }
+}
+
+// 搜索结果
+.search-results {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  right: 0;
+  margin-top: 4px;
+  background-color: $background-primary;
+  border: 1px solid $border-color;
+  border-radius: $border-radius;
+  box-shadow: $shadow-lg;
+  z-index: 1000;
+  max-height: 300px;
+  overflow-y: auto;
+}
+
+.customer-item {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 12px 16px;
+  cursor: pointer;
+  transition: $transition;
+  &:hover {
+    background-color: $background-secondary;
+  }
+}
+
+.customer-avatar {
+  width: 36px;
+  height: 36px;
+  border-radius: 50%;
+  object-fit: cover;
+}
+
+.customer-details {
+  flex: 1;
+}
+
+.customer-name {
+  font-weight: 500;
+  color: $text-primary;
+  margin-bottom: 2px;
+}
+
+.customer-phone {
+  font-size: 12px;
+  color: $text-secondary;
+  margin-bottom: 4px;
+}
+
+.customer-tags {
+  display: flex;
+  gap: 6px;
+}
+
+.tag {
+  font-size: 11px;
+  padding: 2px 8px;
+  background-color: color-mix(in srgb, $primary-color 10%, transparent);
+  color: $primary-color;
+  border-radius: 12px;
+}
+
+.tag.source {
+  background-color: color-mix(in srgb, $secondary-color 10%, transparent);
+  color: $secondary-color;
+}
+
+// 表单样式
+.customer-form,
+.requirement-form {
+  .form-row {
+    display: flex;
+    gap: 16px;
+    margin-bottom: 16px;
+    flex-wrap: wrap;
+  }
+  
+  .form-group {
+    flex: 1;
+    min-width: 200px;
+    label {
+      display: block;
+      font-size: 14px;
+      font-weight: 500;
+      color: $text-primary;
+      margin-bottom: 6px;
+    }
+    input,
+    select,
+    textarea {
+      width: 100%;
+      padding: 10px 12px;
+      border: 1px solid $border-color;
+      border-radius: $border-radius;
+      font-size: 14px;
+      transition: $transition;
+      &:focus {
+        outline: none;
+        border-color: $primary-color;
+        box-shadow: 0 0 0 3px color-mix(in srgb, $primary-color 10%, transparent);
+      }
+    }
+    textarea {
+      resize: vertical;
+      min-height: 60px;
+    }
+  }
+  
+  .form-group.full-width {
+    min-width: 100%;
+  }
+}
+
+.required {
+  color: $danger-color;
+}
+
+// 清除客户按钮
+.clear-customer-btn {
+  padding: 6px 12px;
+  background-color: transparent;
+  color: $text-secondary;
+  border: 1px solid $border-color;
+  border-radius: $border-radius;
+  font-size: 14px;
+  cursor: pointer;
+  transition: $transition;
+  &:hover {
+    background-color: $background-secondary;
+    border-color: $danger-color;
+    color: $danger-color;
+  }
+}
+
+// 需求信息区域
+.requirement-section {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  padding: 24px;
+  box-shadow: $shadow-sm;
+  margin-bottom: 24px;
+}
+
+// 报价与案例匹配区域
+.price-match-section {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  padding: 24px;
+  box-shadow: $shadow-sm;
+  margin-bottom: 24px;
+}
+
+// 价格信息
+.price-info {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 20px;
+  background-color: color-mix(in srgb, $primary-color 5%, transparent);
+  border-radius: $border-radius;
+  margin-bottom: 24px;
+}
+
+.price-label {
+  font-size: 14px;
+  color: $text-secondary;
+}
+
+.price-value {
+  font-size: 20px;
+  font-weight: 600;
+  color: $primary-color;
+}
+
+// 匹配案例
+.matched-cases {
+  h3 {
+    font-size: 18px;
+    font-weight: 600;
+    color: $text-primary;
+    margin-bottom: 16px;
+  }
+}
+
+.cases-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+  gap: 16px;
+}
+
+.case-card {
+  background-color: $background-tertiary;
+  border: 2px solid transparent;
+  border-radius: $border-radius;
+  overflow: hidden;
+  cursor: pointer;
+  transition: $transition;
+  &:hover {
+    border-color: $primary-color;
+    transform: translateY(-2px);
+    box-shadow: $shadow-md;
+  }
+}
+
+.case-image {
+  width: 100%;
+  height: 180px;
+  object-fit: cover;
+}
+
+.case-info {
+  padding: 16px;
+}
+
+.case-name {
+  font-size: 16px;
+  font-weight: 500;
+  color: $text-primary;
+  margin-bottom: 8px;
+}
+
+.case-details {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+  .case-designer,
+  .case-area {
+    font-size: 13px;
+    color: $text-secondary;
+  }
+}
+
+.similarity-badge {
+  display: inline-block;
+  padding: 4px 12px;
+  background-color: color-mix(in srgb, $success-color 10%, transparent);
+  color: $success-color;
+  border-radius: 12px;
+  font-size: 12px;
+  font-weight: 500;
+}
+
+// 已选参考案例
+.selected-cases {
+  margin-top: 24px;
+  h3 {
+    font-size: 16px;
+    font-weight: 600;
+    color: $text-primary;
+    margin-bottom: 12px;
+  }
+}
+
+.selected-cases-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+}
+
+.selected-case-item {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 6px 12px;
+  background-color: color-mix(in srgb, $primary-color 10%, transparent);
+  color: $primary-color;
+  border-radius: 16px;
+  font-size: 13px;
+}
+
+.remove-case-btn {
+  background: none;
+  border: none;
+  color: $text-tertiary;
+  cursor: pointer;
+  padding: 2px;
+  transition: $transition;
+  &:hover {
+    color: $danger-color;
+  }
+}
+
+// 操作按钮区域
+.action-section {
+  display: flex;
+  gap: 12px;
+  justify-content: flex-end;
+}
+
+.primary-btn,
+.secondary-btn {
+  padding: 12px 24px;
+  border-radius: $border-radius;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: $transition;
+  border: none;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: 6px;
+}
+
+.primary-btn {
+  background-color: $primary-color;
+  color: white;
+  &:hover:not(:disabled) {
+    background-color: $primary-dark;
+    transform: translateY(-1px);
+    box-shadow: $shadow-md;
+  }
+  &:active:not(:disabled) {
+    transform: translateY(0);
+  }
+  &:disabled {
+    background-color: $text-tertiary;
+    cursor: not-allowed;
+  }
+}
+
+.secondary-btn {
+  background-color: $background-tertiary;
+  color: $text-primary;
+  border: 1px solid $border-color;
+  &:hover:not(:disabled) {
+    background-color: $background-secondary;
+    border-color: $primary-color;
+    color: $primary-color;
+    transform: translateY(-1px);
+    box-shadow: $shadow-md;
+  }
+  &:active:not(:disabled) {
+    transform: translateY(0);
+  }
+  &:disabled {
+    background-color: $background-tertiary;
+    color: $text-tertiary;
+    border-color: $border-color;
+    cursor: not-allowed;
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .consultation-order-container {
+    padding: 16px;
+  }
+  
+  .page-header {
+    h1 {
+      font-size: 24px;
+    }
+  }
+  
+  .form-row {
+    flex-direction: column;
+    gap: 12px;
+  }
+  
+  .form-group {
+    min-width: 100%;
+  }
+  
+  .cases-grid {
+    grid-template-columns: 1fr;
+  }
+  
+  .action-section {
+    flex-direction: column;
+  }
+  
+  .primary-btn,
+  .secondary-btn {
+    width: 100%;
+  }
+  
+  .section-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 8px;
+  }
+}

+ 265 - 2
src/app/pages/customer-service/consultation-order/consultation-order.ts

@@ -1,11 +1,274 @@
-import { Component } from '@angular/core';
+import { Component, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { RouterModule } from '@angular/router';
+import { ProjectService } from '../../../services/project.service';
+
+// 定义客户信息接口
+interface Customer {
+  id: string;
+  name: string;
+  phone: string;
+  wechat?: string;
+  avatar?: string;
+  customerType?: string; // 新客户/老客户/VIP客户
+  source?: string; // 来源渠道
+  remark?: string;
+}
+
+// 定义需求信息接口
+interface Requirement {
+  style: string;
+  budget: string;
+  area: number;
+  houseType: string;
+  floor: number;
+  decorationType: string;
+  preferredDesigner?: string;
+  specialRequirements?: string;
+  referenceCases?: string[];
+}
 
 @Component({
   selector: 'app-consultation-order',
-  imports: [],
+  standalone: true,
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, RouterModule],
   templateUrl: './consultation-order.html',
   styleUrl: './consultation-order.scss'
 })
 export class ConsultationOrder {
+  // 搜索客户关键词
+  searchKeyword = signal('');
+  // 搜索结果列表
+  searchResults = signal<Customer[]>([]);
+  // 选中的客户
+  selectedCustomer = signal<Customer | null>(null);
+  // 报价范围
+  estimatedPriceRange = signal<string>('');
+  // 匹配的案例
+  matchedCases = signal<any[]>([]);
+  // 表单提交状态
+  isSubmitting = signal(false);
+  // 成功提示显示状态
+  showSuccessMessage = signal(false);
+
+  // 需求表单
+  requirementForm: FormGroup;
+  // 客户表单
+  customerForm: FormGroup;
+
+  // 样式选项
+  styleOptions = [
+    '现代简约', '北欧风', '工业风', '新中式', '法式轻奢', '日式', '美式', '混搭'
+  ];
+  
+  // 户型选项
+  houseTypeOptions = [
+    '一室一厅', '两室一厅', '两室两厅', '三室一厅', '三室两厅', '四室两厅', '复式', '别墅', '其他'
+  ];
+  
+  // 装修类型选项
+  decorationTypeOptions = [
+    '全包', '半包', '清包', '旧房翻新', '局部改造'
+  ];
+
+  constructor(
+    private fb: FormBuilder,
+    private projectService: ProjectService
+  ) {
+    // 初始化需求表单
+    this.requirementForm = this.fb.group({
+      style: ['', Validators.required],
+      budget: ['', Validators.required],
+      area: ['', [Validators.required, Validators.min(1)]],
+      houseType: ['', Validators.required],
+      floor: ['', Validators.min(1)],
+      decorationType: ['', Validators.required],
+      preferredDesigner: [''],
+      specialRequirements: [''],
+      referenceCases: [[]]
+    });
+
+    // 初始化客户表单
+    this.customerForm = this.fb.group({
+      name: ['', Validators.required],
+      phone: ['', [Validators.required, Validators.pattern(/^1[3-9]\d{9}$/)]],
+      wechat: [''],
+      customerType: ['新客户'],
+      source: [''],
+      remark: ['']
+    });
+
+    // 监听表单值变化,自动计算报价和匹配案例
+    this.requirementForm.valueChanges.subscribe(() => {
+      this.calculateEstimatedPrice();
+      this.matchCases();
+    });
+  }
+
+  // 搜索客户
+  searchCustomer() {
+    if (this.searchKeyword().length >= 2) {
+      // 模拟搜索结果
+      this.searchResults.set([
+        {
+          id: '1',
+          name: '张先生',
+          phone: '138****5678',
+          customerType: '老客户',
+          source: '官网咨询',
+          avatar: 'https://picsum.photos/id/64/40/40'
+        },
+        {
+          id: '2',
+          name: '李女士',
+          phone: '139****1234',
+          customerType: 'VIP客户',
+          source: '推荐介绍',
+          avatar: 'https://picsum.photos/id/65/40/40'
+        }
+      ]);
+    }
+  }
+
+  // 选择客户
+  selectCustomer(customer: Customer) {
+    this.selectedCustomer.set(customer);
+    // 填充客户表单
+    this.customerForm.patchValue({
+      name: customer.name,
+      phone: customer.phone,
+      wechat: customer.wechat || '',
+      customerType: customer.customerType || '新客户',
+      source: customer.source || '',
+      remark: customer.remark || ''
+    });
+    // 清空搜索结果
+    this.searchResults.set([]);
+    this.searchKeyword.set('');
+  }
+
+  // 清除选中的客户
+  clearSelectedCustomer() {
+    this.selectedCustomer.set(null);
+    this.customerForm.reset({
+      customerType: '新客户'
+    });
+  }
+
+  // 计算预估报价
+  calculateEstimatedPrice() {
+    const { area, decorationType, style } = this.requirementForm.value;
+    if (area && decorationType) {
+      // 模拟报价计算逻辑
+      let basePrice = 0;
+      switch (decorationType) {
+        case '全包': 
+          basePrice = 1800;
+          break;
+        case '半包': 
+          basePrice = 1200;
+          break;
+        case '清包': 
+          basePrice = 800;
+          break;
+        case '旧房翻新': 
+          basePrice = 2000;
+          break;
+        case '局部改造': 
+          basePrice = 1500;
+          break;
+        default: 
+          basePrice = 1200;
+          break;
+      }
+
+      // 风格加价
+      const stylePremium = ['法式轻奢', '新中式', '日式'].includes(style) ? 0.2 : 0;
+      const totalPrice = area * basePrice * (1 + stylePremium);
+      const lowerBound = Math.floor(totalPrice * 0.9);
+      const upperBound = Math.ceil(totalPrice * 1.1);
+      
+      this.estimatedPriceRange.set(
+        `¥${lowerBound.toLocaleString()} - ¥${upperBound.toLocaleString()}`
+      );
+    }
+  }
+
+  // 匹配案例
+  matchCases() {
+    const { style, houseType, area } = this.requirementForm.value;
+    if (style && houseType && area) {
+      // 模拟匹配案例
+      this.matchedCases.set([
+        {
+          id: '101',
+          name: `${style}风格 ${houseType}设计`,
+          imageUrl: `https://picsum.photos/id/${30 + Math.floor(Math.random() * 10)}/300/200`,
+          designer: '王设计师',
+          area: area + '㎡',
+          similarity: 92
+        },
+        {
+          id: '102',
+          name: `${houseType} ${style}案例展示`,
+          imageUrl: `https://picsum.photos/id/${40 + Math.floor(Math.random() * 10)}/300/200`,
+          designer: '张设计师',
+          area: (area + 10) + '㎡',
+          similarity: 85
+        }
+      ]);
+    }
+  }
+
+  // 选择参考案例
+  selectReferenceCase(caseItem: any) {
+    const currentCases = this.requirementForm.get('referenceCases')?.value || [];
+    if (!currentCases.includes(caseItem.id)) {
+      this.requirementForm.patchValue({
+        referenceCases: [...currentCases, caseItem.id]
+      });
+    }
+  }
+
+  // 移除参考案例
+  removeReferenceCase(caseId: string) {
+    const currentCases = this.requirementForm.get('referenceCases')?.value || [];
+    this.requirementForm.patchValue({
+      referenceCases: currentCases.filter((id: string) => id !== caseId)
+    });
+  }
+
+  // 提交表单
+  submitForm() {
+    if (this.requirementForm.valid && this.customerForm.valid) {
+      this.isSubmitting.set(true);
+      
+      const formData = {
+        customerInfo: this.customerForm.value,
+        requirementInfo: this.requirementForm.value,
+        estimatedPriceRange: this.estimatedPriceRange(),
+        createdAt: new Date()
+      };
+
+      // 模拟提交请求
+      setTimeout(() => {
+        console.log('提交的表单数据:', formData);
+        this.isSubmitting.set(false);
+        this.showSuccessMessage.set(true);
+        
+        // 3秒后隐藏成功提示
+        setTimeout(() => {
+          this.showSuccessMessage.set(false);
+        }, 3000);
+      }, 1500);
+    }
+  }
 
+  // 一键拉群
+  createProjectGroup() {
+    // 模拟拉群功能
+    console.log('创建项目群');
+    alert('项目群已创建,并邀请了相应技术组长!');
+  }
 }

+ 169 - 1
src/app/pages/customer-service/dashboard/dashboard.html

@@ -1 +1,169 @@
-<p>dashboard works!</p>
+<!-- 欢迎区域 -->
+<section class="welcome-section">
+  <h2>您好,客服小李 👋</h2>
+  <p>今天是 {{ currentDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }) }},祝您工作顺利!</p>
+</section>
+
+<!-- 数据看板 -->
+<section class="stats-dashboard">
+  <div class="stats-grid">
+    <div class="stat-card">
+      <div class="stat-icon primary">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ stats.newConsultations() }}</div>
+        <div class="stat-label">新咨询数</div>
+      </div>
+      <div class="stat-trend positive">
+        <span>+12%</span>
+      </div>
+    </div>
+
+    <div class="stat-card">
+      <div class="stat-icon secondary">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
+          <polyline points="22 4 12 14.01 9 11.01"></polyline>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ stats.pendingAssignments() }}</div>
+        <div class="stat-label">待派单数</div>
+      </div>
+      <div class="stat-trend neutral">
+        <span>持平</span>
+      </div>
+    </div>
+
+    <div class="stat-card">
+      <div class="stat-icon warning">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
+          <line x1="12" y1="9" x2="12" y2="13"></line>
+          <line x1="12" y1="17" x2="12.01" y2="17"></line>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ stats.exceptionProjects() }}</div>
+        <div class="stat-label">异常项目</div>
+      </div>
+      <div class="stat-trend negative">
+        <span>+1</span>
+      </div>
+    </div>
+
+    <div class="stat-card">
+      <div class="stat-icon success">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <line x1="12" y1="1" x2="12" y2="23"></line>
+          <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">¥{{ stats.todayRevenue() }}</div>
+        <div class="stat-label">今日成交额</div>
+      </div>
+      <div class="stat-trend positive">
+        <span>+28%</span>
+      </div>
+    </div>
+  </div>
+</section>
+
+<!-- 紧急待办和项目动态流 -->
+<div class="content-grid">
+  <!-- 紧急待办列表 -->
+  <section class="urgent-tasks-section">
+    <div class="section-header">
+      <h3>紧急待办</h3>
+      <a href="/customer-service/project-list" class="view-all-link">查看全部</a>
+    </div>
+    
+    <div class="tasks-list">
+      <div *ngIf="urgentTasks().length === 0" class="empty-state">
+        <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <circle cx="12" cy="12" r="10"></circle>
+          <polyline points="12 6 12 12 16 14"></polyline>
+        </svg>
+        <p>暂无紧急待办事项</p>
+      </div>
+      
+      <div *ngFor="let task of urgentTasks()" class="task-item" [class.completed]="task.isCompleted" [class.overdue]="task.isOverdue">
+        <div class="task-checkbox">
+          <input type="checkbox" [checked]="task.isCompleted" (change)="markTaskAsCompleted(task.id)">
+        </div>
+        <div class="task-content">
+          <h4 class="task-title">{{ task.title || '未命名任务' }}</h4>
+          <p class="task-project">{{ task.projectName || '未知项目' }}</p>
+          <div class="task-meta">
+            <span class="task-time">{{ formatDate(task.deadline) }}</span>
+            <span class="task-status">{{ getTaskStatus(task) }}</span>
+          </div>
+        </div>
+        <div class="task-actions">
+          <button class="btn-primary" (click)="handleAssignment(task.projectId)">处理</button>
+        </div>
+      </div>
+    </div>
+  </section>
+
+  <!-- 项目动态流 -->
+  <section class="project-updates-section">
+    <div class="section-header">
+      <h3>项目动态</h3>
+      <div class="search-box">
+        <input 
+          type="text" 
+          [value]="searchTerm()"
+          (input)="searchTerm.set($event.target.value)"
+          placeholder="搜索动态..." 
+          class="search-input"
+        />
+      </div>
+    </div>
+    
+    <div class="updates-list">
+      <div *ngIf="filteredUpdates().length === 0" class="empty-state">
+        <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <circle cx="12" cy="12" r="10"></circle>
+          <line x1="2" y1="12" x2="22" y2="12"></line>
+          <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
+        </svg>
+        <p>暂无项目动态</p>
+      </div>
+      
+      <div *ngFor="let update of filteredUpdates()" class="update-item">
+        <div class="update-icon">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
+          </svg>
+        </div>
+        <div class="update-content">
+          <div class="update-title" *ngIf="'name' in update && update.name && 'status' in update && update.status">
+            项目 <strong>{{ update.name }}</strong> 状态更新为 {{ update.status }}
+          </div>
+          <div class="update-title" *ngIf="'content' in update">
+            <strong>{{ getCustomerName(update) }}</strong> 提交了反馈
+          </div>
+          <p class="update-text" *ngIf="'content' in update && update.content">{{ update.content }}</p>
+          <div class="update-meta">
+            <span class="update-time">{{ getFormattedDate(update) }}</span>
+            <span class="update-status {{ getUpdateStatusClass(update) }}">
+              {{ getUpdateStatus(update) }}
+            </span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </section>
+</div>
+
+<!-- 回到顶部按钮 -->
+<button class="back-to-top" (click)="scrollToTop()" [class.visible]="showBackToTop()">
+  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+    <polyline points="18 15 12 9 6 15"></polyline>
+  </svg>
+</button>

+ 542 - 0
src/app/pages/customer-service/dashboard/dashboard.scss

@@ -0,0 +1,542 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$primary-dark: #0E42CB;
+$secondary-color: #4E5BA6;
+$success-color: #00B42A;
+$warning-color: #FF7D00;
+$danger-color: #F53F3F;
+$text-primary: #1D2129;
+$text-secondary: #4E5969;
+$text-tertiary: #86909C;
+$border-color: #E5E6EB;
+$background-primary: #FFFFFF;
+$background-secondary: #F2F3F5;
+$background-tertiary: #F7F8FA;
+$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
+$shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.1);
+$border-radius: 8px;
+$transition: all 0.3s ease;
+
+// 欢迎区域
+.welcome-section {
+  margin-bottom: 24px;
+
+  h2 {
+    font-size: 24px;
+    font-weight: 600;
+    margin-bottom: 8px;
+    color: $text-primary;
+  }
+
+  p {
+    font-size: 14px;
+    color: $text-secondary;
+  }
+}
+
+// 数据看板
+.stats-dashboard {
+  margin-bottom: 24px;
+
+  .stats-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+    gap: 16px;
+  }
+
+  .stat-card {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    padding: 20px;
+    background-color: $background-primary;
+    border-radius: $border-radius;
+    box-shadow: $shadow-sm;
+    transition: $transition;
+
+    &:hover {
+      box-shadow: $shadow-md;
+      transform: translateY(-2px);
+    }
+
+    .stat-icon {
+      width: 48px;
+      height: 48px;
+      border-radius: 12px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      color: white;
+
+      &.primary {
+        background-color: $primary-color;
+      }
+
+      &.secondary {
+        background-color: $secondary-color;
+      }
+
+      &.warning {
+        background-color: $warning-color;
+      }
+
+      &.success {
+        background-color: $success-color;
+      }
+    }
+
+    .stat-content {
+      flex: 1;
+
+      .stat-value {
+        font-size: 28px;
+        font-weight: 600;
+        color: $text-primary;
+        margin-bottom: 4px;
+      }
+
+      .stat-label {
+        font-size: 14px;
+        color: $text-secondary;
+      }
+    }
+
+    .stat-trend {
+      padding: 4px 10px;
+      border-radius: 12px;
+      font-size: 12px;
+      font-weight: 500;
+
+      &.positive {
+        background-color: color-mix(in srgb, $success-color 10%, transparent);
+        color: $success-color;
+      }
+
+      &.negative {
+        background-color: color-mix(in srgb, $danger-color 10%, transparent);
+        color: $danger-color;
+      }
+
+      &.neutral {
+        background-color: color-mix(in srgb, $text-tertiary 10%, transparent);
+        color: $text-tertiary;
+      }
+    }
+  }
+}
+
+// 内容网格
+.content-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 24px;
+  margin-bottom: 24px;
+}
+
+// 通用区块头部
+.section-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 16px;
+
+  h3 {
+    font-size: 18px;
+    font-weight: 600;
+    color: $text-primary;
+  }
+
+  .view-all-link {
+    color: $primary-color;
+    text-decoration: none;
+    font-size: 14px;
+    transition: $transition;
+
+    &:hover {
+      text-decoration: underline;
+    }
+  }
+
+  .search-box {
+    max-width: 200px;
+
+    .search-input {
+      width: 100%;
+      padding: 6px 12px;
+      border: 1px solid $border-color;
+      border-radius: $border-radius;
+      font-size: 14px;
+      background-color: $background-primary;
+      color: $text-primary;
+
+      &::placeholder {
+        color: $text-tertiary;
+      }
+
+      &:focus {
+        outline: none;
+        border-color: $primary-color;
+        box-shadow: 0 0 0 2px color-mix(in srgb, $primary-color 20%, transparent);
+      }
+    }
+  }
+}
+
+// 紧急待办列表
+.urgent-tasks-section {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  padding: 24px;
+  box-shadow: $shadow-sm;
+
+  .tasks-list {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+  }
+
+  .empty-state {
+    text-align: center;
+    padding: 40px 20px;
+    color: $text-tertiary;
+
+    svg {
+      margin-bottom: 16px;
+      opacity: 0.5;
+    }
+
+    p {
+      font-size: 14px;
+    }
+  }
+
+  .task-item {
+    display: flex;
+    align-items: flex-start;
+    gap: 12px;
+    padding: 16px;
+    border: 1px solid $border-color;
+    border-radius: $border-radius;
+    transition: $transition;
+
+    &:hover {
+      border-color: $primary-color;
+      box-shadow: 0 2px 8px color-mix(in srgb, $primary-color 10%, transparent);
+    }
+
+    &.completed {
+      opacity: 0.6;
+      background-color: $background-tertiary;
+    }
+
+    &.overdue {
+      border-color: $danger-color;
+      background-color: color-mix(in srgb, $danger-color 5%, transparent);
+    }
+
+    .task-checkbox {
+      margin-top: 2px;
+
+      input[type="checkbox"] {
+        width: 18px;
+        height: 18px;
+        cursor: pointer;
+      }
+    }
+
+    .task-content {
+      flex: 1;
+
+      .task-title {
+        font-size: 16px;
+        font-weight: 500;
+        color: $text-primary;
+        margin-bottom: 4px;
+      }
+
+      .task-project {
+        font-size: 13px;
+        color: $text-secondary;
+        margin-bottom: 8px;
+      }
+
+      .task-meta {
+        display: flex;
+        gap: 16px;
+        font-size: 12px;
+        color: $text-tertiary;
+      }
+    }
+
+    .task-actions {
+      display: flex;
+      align-items: flex-start;
+    }
+  }
+}
+
+// 项目动态流
+.project-updates-section {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  padding: 24px;
+  box-shadow: $shadow-sm;
+
+  .updates-list {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+  }
+
+  .empty-state {
+    text-align: center;
+    padding: 40px 20px;
+    color: $text-tertiary;
+
+    svg {
+      margin-bottom: 16px;
+      opacity: 0.5;
+    }
+
+    p {
+      font-size: 14px;
+    }
+  }
+
+  .update-item {
+    display: flex;
+    gap: 12px;
+    padding: 16px;
+    border: 1px solid $border-color;
+    border-radius: $border-radius;
+    transition: $transition;
+
+    &:hover {
+      border-color: $primary-color;
+      box-shadow: 0 2px 8px color-mix(in srgb, $primary-color 10%, transparent);
+    }
+
+    .update-icon {
+      width: 40px;
+      height: 40px;
+      border-radius: 10px;
+      background-color: color-mix(in srgb, $primary-color 10%, transparent);
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      color: $primary-color;
+      flex-shrink: 0;
+    }
+
+    .update-content {
+      flex: 1;
+
+      .update-title {
+        font-size: 15px;
+        font-weight: 500;
+        color: $text-primary;
+        margin-bottom: 4px;
+      }
+
+      .update-text {
+        font-size: 14px;
+        color: $text-secondary;
+        margin-bottom: 8px;
+        line-height: 1.5;
+      }
+
+      .update-meta {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+
+        .update-time {
+          font-size: 12px;
+          color: $text-tertiary;
+        }
+
+        .update-status {
+          font-size: 12px;
+          font-weight: 500;
+          padding: 2px 8px;
+          border-radius: 10px;
+
+          &.completed {
+            background-color: color-mix(in srgb, $success-color 10%, transparent);
+            color: $success-color;
+          }
+
+          &.pending {
+            background-color: color-mix(in srgb, $primary-color 10%, transparent);
+            color: $primary-color;
+          }
+
+          &.exception {
+            background-color: color-mix(in srgb, $danger-color 10%, transparent);
+            color: $danger-color;
+          }
+        }
+      }
+    }
+  }
+}
+
+// 按钮样式
+.btn-primary {
+  padding: 8px 16px;
+  background-color: $primary-color;
+  color: white;
+  border: none;
+  border-radius: $border-radius;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: $transition;
+
+  &:hover {
+    background-color: $primary-dark;
+    transform: translateY(-1px);
+    box-shadow: $shadow-md;
+  }
+
+  &:active {
+    transform: translateY(0);
+  }
+
+  &:disabled {
+    background-color: $text-tertiary;
+    cursor: not-allowed;
+    transform: none;
+    box-shadow: none;
+  }
+}
+
+// 回到顶部按钮
+.back-to-top {
+  position: fixed;
+  bottom: 20px;
+  right: 20px;
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  background-color: #1976d2;
+  color: white;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  opacity: 0;
+  visibility: hidden;
+  transition: opacity 0.3s, visibility 0.3s;
+  border: none;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+}
+
+.back-to-top.visible {
+  opacity: 1;
+  visibility: visible;
+}
+
+.back-to-top:hover {
+  background-color: #1565c0;
+}
+
+// 响应式设计
+@media (max-width: 1024px) {
+  .content-grid {
+    grid-template-columns: 1fr;
+  }
+
+  .stats-dashboard .stats-grid {
+    grid-template-columns: repeat(2, 1fr);
+  }
+}
+
+@media (max-width: 768px) {
+  .welcome-section {
+    margin-bottom: 16px;
+
+    h2 {
+      font-size: 20px;
+    }
+  }
+
+  .stats-dashboard {
+    margin-bottom: 16px;
+
+    .stats-grid {
+      grid-template-columns: 1fr;
+      gap: 12px;
+    }
+
+    .stat-card {
+      padding: 16px;
+
+      .stat-icon {
+        width: 40px;
+        height: 40px;
+      }
+
+      .stat-value {
+        font-size: 24px;
+      }
+    }
+  }
+
+  .content-grid {
+    gap: 16px;
+    margin-bottom: 16px;
+  }
+
+  .section-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 12px;
+
+    h3 {
+      font-size: 16px;
+    }
+
+    .search-box {
+      max-width: 100%;
+      width: 100%;
+    }
+  }
+
+  .urgent-tasks-section,
+  .project-updates-section {
+    padding: 16px;
+  }
+
+  .task-item,
+  .update-item {
+    padding: 12px;
+  }
+
+  .back-to-top {
+    bottom: 16px;
+    right: 16px;
+    width: 36px;
+    height: 36px;
+  }
+}
+
+@media (max-width: 480px) {
+  .task-item {
+    flex-direction: column;
+    align-items: flex-start;
+
+    .task-actions {
+      margin-top: 12px;
+      align-self: flex-end;
+    }
+  }
+
+  .update-item {
+    .update-icon {
+      width: 32px;
+      height: 32px;
+    }
+  }
+}

+ 211 - 4
src/app/pages/customer-service/dashboard/dashboard.ts

@@ -1,11 +1,218 @@
-import { Component } from '@angular/core';
+// 修复 OnDestroy 导入和使用
+import { Component, OnInit, OnDestroy, signal, computed } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { RouterModule } from '@angular/router';
+import { ProjectService } from '../../../services/project.service';
+import { Project, Task, CustomerFeedback } from '../../../models/project.model';
 
 @Component({
   selector: 'app-dashboard',
-  imports: [],
+  standalone: true,
+  imports: [CommonModule, FormsModule, RouterModule],
   templateUrl: './dashboard.html',
   styleUrl: './dashboard.scss'
-})
-export class Dashboard {
+}) 
+export class Dashboard implements OnInit, OnDestroy {
+  // 数据看板统计
+  stats = {
+    newConsultations: signal(12),
+    pendingAssignments: signal(5),
+    exceptionProjects: signal(2),
+    todayRevenue: signal(28500)
+  };
 
+  // 紧急待办列表
+  urgentTasks = signal<Task[]>([]);
+  
+  // 项目动态流
+  projectUpdates = signal<(Project | CustomerFeedback)[]>([]);
+  
+  // 搜索关键词
+  searchTerm = signal('');
+  
+  // 筛选后的项目更新
+  filteredUpdates = computed(() => {
+    if (!this.searchTerm()) return this.projectUpdates();
+    
+    return this.projectUpdates().filter(item => {
+      if ('name' in item) {
+        // 项目
+        return item.name.toLowerCase().includes(this.searchTerm().toLowerCase()) ||
+               item.customerName.toLowerCase().includes(this.searchTerm().toLowerCase()) ||
+               item.status.toLowerCase().includes(this.searchTerm().toLowerCase());
+      } else {
+        // 反馈
+        return 'content' in item && item.content.toLowerCase().includes(this.searchTerm().toLowerCase()) ||
+               'status' in item && item.status.toLowerCase().includes(this.searchTerm().toLowerCase());
+      }
+    });
+  });
+
+  currentDate = new Date();
+  
+  // 回到顶部按钮可见性信号
+  showBackToTopSignal = signal(false);
+
+  constructor(private projectService: ProjectService) {}
+
+  ngOnInit(): void {
+    this.loadUrgentTasks();
+    this.loadProjectUpdates();
+    
+    // 添加滚动事件监听
+    window.addEventListener('scroll', this.onScroll.bind(this));
+  }
+
+  // 添加滚动事件处理方法
+  private onScroll(): void {
+    this.showBackToTopSignal.set(window.scrollY > 300);
+  }
+  
+  // 添加显示回到顶部按钮的计算属性
+  showBackToTop = computed(() => this.showBackToTopSignal());
+
+  // 清理事件监听器
+  ngOnDestroy(): void {
+    window.removeEventListener('scroll', this.onScroll.bind(this));
+  }
+
+  // 添加scrollToTop方法
+  scrollToTop(): void {
+    window.scrollTo({
+      top: 0,
+      behavior: 'smooth'
+    });
+  }
+  
+  // 修改loadUrgentTasks方法,添加status属性
+  loadUrgentTasks(): void {
+    // 从服务获取任务数据,筛选出紧急任务
+    this.projectService.getTasks().subscribe(tasks => {
+      const filteredTasks = tasks.map(task => ({...task, status: task.isOverdue ? '已逾期' : task.isCompleted ? '已完成' : '进行中'}))
+        .filter(task => task.isOverdue || task.deadline.toDateString() === new Date().toDateString());
+      
+      this.urgentTasks.set(filteredTasks.sort((a, b) => {
+        // 按紧急程度排序
+        if (a.isOverdue && !b.isOverdue) return -1;
+        if (!a.isOverdue && b.isOverdue) return 1;
+        return a.deadline.getTime() - b.deadline.getTime();
+      }));
+    });
+  }
+
+  loadProjectUpdates(): void {
+    // 模拟项目更新数据
+    this.projectService.getProjects().subscribe(projects => {
+      this.projectService.getCustomerFeedbacks().subscribe(feedbacks => {
+        // 合并项目和反馈,按时间倒序排序
+        const updates: (Project | CustomerFeedback)[] = [
+          ...projects,
+          ...feedbacks
+        ].sort((a, b) => {
+          const dateA = 'createdAt' in a ? a.createdAt : new Date(a['updatedAt'] || a['deadline']);
+          const dateB = 'createdAt' in b ? b.createdAt : new Date(b['updatedAt'] || b['deadline']);
+          return dateB.getTime() - dateA.getTime();
+        }).slice(0, 20); // 限制显示20条
+        
+        this.projectUpdates.set(updates);
+      });
+    });
+  }
+
+  // 处理任务完成
+  markTaskAsCompleted(taskId: string): void {
+    this.urgentTasks.set(
+      this.urgentTasks().map(task => 
+        task.id === taskId ? { ...task, isCompleted: true, status: '已完成' } : task
+      )
+    );
+  }
+
+  // 处理派单操作
+  handleAssignment(projectId: string): void {
+    // 在实际应用中,这里会调用API处理派单
+    console.log('处理派单:', projectId);
+    // 更新统计数据
+    this.stats.pendingAssignments.set(this.stats.pendingAssignments() - 1);
+  }
+
+  // 格式化日期
+  formatDate(date: Date | string): string {
+    if (!date) return '';
+    try {
+      return new Date(date).toLocaleString('zh-CN', {
+        month: '2-digit',
+        day: '2-digit',
+        hour: '2-digit',
+        minute: '2-digit'
+      });
+    } catch (error) {
+      console.error('日期格式化错误:', error);
+      return '';
+    }
+  }
+
+  // 添加安全获取客户名称的方法
+getCustomerName(update: Project | CustomerFeedback): string {
+  if ('customerName' in update && update.customerName) {
+    return update.customerName;
+  } else if ('projectId' in update) {
+    // 查找相关项目获取客户名称
+    return '客户反馈';
+  }
+  return '未知客户';
+}
+
+  // 优化的日期格式化方法
+  getFormattedDate(update: Project | CustomerFeedback): string {
+    if (!update) return '';
+    
+    if ('createdAt' in update && update.createdAt) {
+      return this.formatDate(update.createdAt);
+    } else if ('updatedAt' in update && update.updatedAt) {
+      return this.formatDate(update.updatedAt);
+    } else if ('deadline' in update && update.deadline) {
+      return this.formatDate(update.deadline);
+    }
+    return '';
+  }
+
+  // 添加获取状态的安全方法
+getUpdateStatus(update: Project | CustomerFeedback): string {
+  if ('status' in update && update.status) {
+    return update.status;
+  }
+  return '已更新';
+}
+
+  // 添加getTaskStatus方法的正确实现
+  getTaskStatus(task: Task): string {
+    if (!task) return '未知状态';
+    if (task.isCompleted) return '已完成';
+    if (task.isOverdue) return '已逾期';
+    return '进行中';
+  }
+
+  // 添加getUpdateStatusClass方法的正确实现
+  getUpdateStatusClass(update: Project | CustomerFeedback): string {
+    if (!update || !('status' in update) || !update.status) return '';
+    
+    switch (update.status) {
+      case '进行中':
+        return 'status-active';
+      case '已完成':
+        return 'status-completed';
+      case '已延期':
+      case '已暂停':
+        return 'status-warning';
+      case '已解决':
+        return 'status-success';
+      case '待处理':
+      case '处理中':
+        return 'status-info';
+      default:
+        return '';
+    }
+  }
 }

+ 441 - 1
src/app/pages/customer-service/project-detail/project-detail.html

@@ -1 +1,441 @@
-<p>project-detail works!</p>
+<!-- 项目详情页面内容 -->
+<div class="project-detail-container">
+  <!-- 右侧主要内容 -->
+  <div class="project-content">
+      <!-- 项目头部信息 -->
+      <div class="project-header">
+        <div class="project-title-section">
+          <h2 class="project-title">{{ project()?.name || '现代简约风格三居室设计' }}</h2>
+          <div class="project-meta">
+            <span class="project-status {{ getProjectStatusClass(project()?.status) }}">
+              {{ project()?.status || '进行中' }}
+            </span>
+            <span class="project-stage">当前阶段:{{ project()?.currentStage || '方案修改与确认' }}</span>
+          </div>
+        </div>
+        <div class="project-actions">
+          <button class="secondary-btn">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+              <polyline points="14 2 14 8 20 8"></polyline>
+              <line x1="16" y1="13" x2="8" y2="13"></line>
+              <line x1="16" y1="17" x2="8" y2="17"></line>
+              <polyline points="10 9 9 9 8 9"></polyline>
+            </svg>
+            <span>导出报告</span>
+          </button>
+          <button class="primary-btn">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
+              <polyline points="22 4 12 14.01 9 11.01"></polyline>
+            </svg>
+            <span>联系客户</span>
+          </button>
+        </div>
+      </div>
+
+      <!-- 项目进度卡片 -->
+      <div class="progress-card">
+        <div class="progress-header">
+          <h3>项目进度</h3>
+          <span class="progress-percentage">{{ completionProgress() }}%</span>
+        </div>
+        <div class="progress-bar">
+          <div class="progress-fill" [style.width]="progressFillWidth()"></div>
+        </div>
+        <div class="progress-meta">
+          <span>开始时间:{{ formatDate(project()?.createdAt || '2023-06-01') }}</span>
+          <span>预计完成:{{ formatDate(project()?.deadline || '2023-07-15') }}</span>
+        </div>
+      </div>
+
+      <!-- 项目详情标签页 -->
+      <div class="project-tabs">
+        <div class="tab-header">
+          <button class="tab-btn" [class.active]="activeTab() === 'overview'" (click)="switchTab('overview')">
+            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M5 12h14M12 5l7 7-7 7"></path>
+            </svg>
+            <span>概览</span>
+          </button>
+          <button class="tab-btn" [class.active]="activeTab() === 'milestones'" (click)="switchTab('milestones')">
+            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
+            </svg>
+            <span>里程碑</span>
+          </button>
+          <button class="tab-btn" [class.active]="activeTab() === 'tasks'" (click)="switchTab('tasks')">
+            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
+            </svg>
+            <span>任务</span>
+          </button>
+          <button class="tab-btn" [class.active]="activeTab() === 'messages'" (click)="switchTab('messages')">
+            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
+            </svg>
+            <span>消息</span>
+          </button>
+          <button class="tab-btn" [class.active]="activeTab() === 'files'" (click)="switchTab('files')">
+            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+              <polyline points="14 2 14 8 20 8"></polyline>
+              <line x1="16" y1="13" x2="8" y2="13"></line>
+              <line x1="16" y1="17" x2="8" y2="17"></line>
+              <polyline points="10 9 9 9 8 9"></polyline>
+            </svg>
+            <span>文件</span>
+          </button>
+        </div>
+
+        <!-- 消息标签内容 - 只保留一个消息输入区域 -->
+        <div *ngIf="activeTab() === 'messages'" class="tab-content">
+          <div class="messages-container">
+            <div class="messages-list">
+              <!-- 消息列表内容保持不变 -->
+            </div>
+            <div class="message-input-area">
+              <!-- 只使用单向绑定 + input事件,移除双向绑定 -->
+              <textarea 
+                [value]="newMessage()"
+                (input)="onMessageInput($event)"
+                placeholder="输入消息内容..."
+                rows="3"
+                (keydown.enter.shift)="$event.preventDefault()"
+                (keydown.enter)="sendMessage()"
+              ></textarea>
+              <div class="message-actions">
+                <button class="secondary-btn">
+                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
+                    <polyline points="14 2 14 8 20 8"></polyline>
+                  </svg>
+                  <span>上传文件</span>
+                </button>
+                <button class="primary-btn" (click)="sendMessage()" [disabled]="!newMessage().trim()">
+                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <line x1="22" y1="2" x2="11" y2="13"></line>
+                    <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
+                  </svg>
+                  <span>发送</span>
+                </button>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 概览标签内容 -->
+        <div *ngIf="activeTab() === 'overview'" class="tab-content">
+          <div class="overview-grid">
+            <!-- 客户信息卡片 -->
+            <div class="info-card">
+              <h4 class="card-title">客户信息</h4>
+              <div class="customer-info">
+                <div class="info-item">
+                  <label>客户姓名</label>
+                  <span>{{ project()?.customerName || '王先生' }}</span>
+                </div>
+                <div class="info-item">
+                  <label>联系方式</label>
+                  <span>138****5678</span>
+                </div>
+                <div class="info-item">
+                  <label>标签</label>
+                  <div class="tag-container">
+                    <span class="tag">朋友圈</span>
+                    <span class="tag">软装</span>
+                    <span class="tag">现代风格</span>
+                  </div>
+                </div>
+                <div class="info-item">
+                  <label>高优先级需求</label>
+                  <ul class="need-list">
+                    <li *ngFor="let need of project()?.highPriorityNeeds || ['客厅光线充足', '储物空间充足', '环保材料']">
+                      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                        <polyline points="20 6 9 17 4 12"></polyline>
+                      </svg>
+                      {{ need }}
+                    </li>
+                  </ul>
+                </div>
+              </div>
+            </div>
+
+            <!-- 项目团队卡片 -->
+            <div class="info-card">
+              <h4 class="card-title">项目团队</h4>
+              <div class="team-info">
+                <div class="team-member">
+                  <img src="https://picsum.photos/id/64/48/48" alt="客服小李" class="member-avatar">
+                  <div class="member-details">
+                    <div class="member-name">客服小李</div>
+                    <div class="member-role">客户经理</div>
+                  </div>
+                  <button class="message-btn">
+                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                      <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
+                    </svg>
+                  </button>
+                </div>
+                <div class="team-member">
+                  <img src="https://picsum.photos/id/91/48/48" alt="张设计师" class="member-avatar">
+                  <div class="member-details">
+                    <div class="member-name">张设计师</div>
+                    <div class="member-role">主设计师</div>
+                  </div>
+                  <button class="message-btn">
+                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                      <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
+                    </svg>
+                  </button>
+                </div>
+              </div>
+            </div>
+
+            <!-- 最近反馈卡片 -->
+            <div class="info-card">
+              <h4 class="card-title">客户反馈</h4>
+              <div class="feedback-list">
+                <div *ngFor="let feedback of feedbacks()" class="feedback-item">
+                  <!-- 修改前 -->
+                  <div class="feedback-item">
+                    <div class="feedback-header">
+                      <div class="feedback-author">{{ feedback?.customerName || '未知客户' }}</div>
+                      <div class="feedback-rating">
+                        <span class="rating-stars">★★★★☆</span>
+                        <span class="rating-number">{{ feedback?.rating || 0 }}.0</span>
+                      </div>
+                    </div>
+                    <div class="feedback-content">{{ feedback?.content || '' }}</div>
+                    <div class="feedback-response" *ngIf="feedback?.response">
+                      <div class="response-label">客服回复:</div>
+                      <div class="response-text">{{ feedback.response }}</div>
+                    </div>
+                    <div class="feedback-meta">
+                      <span class="feedback-date">{{ formatDate(feedback?.createdAt) }}</span>
+                      <span class="feedback-status" [class.status-processed]="feedback?.status === '已解决'" [class.status-pending]="feedback?.status === '待处理'">
+                        {{ feedback?.status || '未知状态' }}
+                      </span>
+                    </div>
+                  </div>
+                  
+                  <!-- 修改后 -->
+                  <div class="feedback-item">
+                    <div class="feedback-header">
+                      <div class="feedback-author">{{ getFeedbackCustomerName(feedback) }}</div>
+                      <div class="feedback-rating">
+                        <span class="rating-stars">★★★★☆</span>
+                        <span class="rating-number">{{ getFeedbackRating(feedback) }}.0</span>
+                      </div>
+                    </div>
+                    <div class="feedback-content">{{ feedback?.content || '' }}</div>
+                    <div class="feedback-response" *ngIf="feedback?.response">
+                      <div class="response-label">客服回复:</div>
+                      <div class="response-text">{{ feedback.response }}</div>
+                    </div>
+                    <div class="feedback-meta">
+                      <span class="feedback-date">{{ formatDate(feedback?.createdAt) }}</span>
+                      <span class="feedback-status" [class.status-processed]="feedback?.status === '已解决'" [class.status-pending]="feedback?.status === '待处理'" [class.status-processing]="feedback?.status === '处理中'">
+                        {{ feedback?.status || '未知状态' }}
+                      </span>
+                    </div>
+                  </div>
+                  <button class="view-all-btn" *ngIf="feedbacks().length > 0">查看全部反馈</button>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 里程碑标签内容 -->
+        <div *ngIf="activeTab() === 'milestones'" class="tab-content">
+          <div class="milestones-timeline">
+            <div *ngFor="let milestone of milestones(); index as i" class="milestone-item">
+              <div class="milestone-dot" [class.completed]="milestone.isCompleted"></div>
+              <div class="milestone-line" *ngIf="i < milestones().length - 1" [class.completed]="milestone.isCompleted && milestones()[i+1].isCompleted"></div>
+              <div class="milestone-content">
+                <div class="milestone-header">
+                  <h4 class="milestone-title">{{ milestone.title }}</h4>
+                  <span class="milestone-status" [class.status-completed]="milestone.isCompleted" [class.status-pending]="!milestone.isCompleted">
+                    {{ milestone.isCompleted ? '已完成' : '进行中' }}
+                  </span>
+                </div>
+                <p class="milestone-description">{{ milestone.description }}</p>
+                <div class="milestone-dates">
+                  <div class="date-item">
+                    <label>截止日期</label>
+                    <span>{{ formatDate(milestone.dueDate) }}</span>
+                  </div>
+                  <div class="date-item" *ngIf="milestone.completedDate">
+                    <label>完成日期</label>
+                    <span>{{ formatDate(milestone.completedDate) }}</span>
+                  </div>
+                </div>
+                <div class="milestone-actions" *ngIf="!milestone.isCompleted">
+                  <button class="primary-btn small" (click)="completeMilestone(milestone.id)">
+                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                      <polyline points="20 6 9 17 4 12"></polyline>
+                    </svg>
+                    <span>标记完成</span>
+                  </button>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 任务标签内容 -->
+        <div *ngIf="activeTab() === 'tasks'" class="tab-content">
+          <div class="tasks-filter">
+            <div class="filter-options">
+              <button class="filter-btn active">全部任务</button>
+              <button class="filter-btn">进行中</button>
+              <button class="filter-btn">已完成</button>
+              <button class="filter-btn">逾期</button>
+            </div>
+            <div class="search-box">
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                <circle cx="11" cy="11" r="8"></circle>
+                <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+              </svg>
+              <input type="text" placeholder="搜索任务...">
+            </div>
+          </div>
+          <div class="tasks-list">
+            <!-- 修复任务列表中的状态显示,确保安全访问 -->
+            <div *ngFor="let task of tasks()" class="task-item">
+              <div class="task-checkbox">
+                <input type="checkbox" [checked]="task.isCompleted" (change)="task.isCompleted ? null : completeTask(task.id)">
+              </div>
+              <div class="task-content">
+                <h4 class="task-title" [class.completed]="task.isCompleted">{{ task.title || '未命名任务' }}</h4>
+                <p class="task-description">{{ task.description || '' }}</p>
+                <div class="task-meta">
+                  <span class="task-assignee">
+                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                      <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
+                      <circle cx="9" cy="7" r="4"></circle>
+                      <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
+                      <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+                    </svg>
+                    {{ task.assignee || '未分配' }}
+                  </span>
+                  <span class="task-deadline" [class.overdue]="task.isOverdue">
+                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                      <circle cx="12" cy="12" r="10"></circle>
+                      <polyline points="12 6 12 12 16 14"></polyline>
+                    </svg>
+                    {{ formatDate(task.deadline) }}
+                  </span>
+                  <span class="task-priority" [class.priority-high]="task.priority === 'high'" [class.priority-medium]="task.priority === 'medium'" [class.priority-low]="task.priority === 'low'">
+                    {{ task.priority === 'high' ? '高' : task.priority === 'medium' ? '中' : '低' }}
+                  </span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 消息标签内容 -->
+        <div *ngIf="activeTab() === 'messages'" class="tab-content">
+          <div class="messages-container">
+            <div class="messages-list">
+              <div *ngFor="let message of messages()" class="message-item">
+                <div class="message-avatar">
+                  {{ message.sender.charAt(0) }}
+                </div>
+                <div class="message-content">
+                  <div class="message-header">
+                    <span class="message-sender">{{ message.sender }}</span>
+                    <span class="message-time">{{ formatDateTime(message.timestamp) }}</span>
+                  </div>
+                  <div class="message-text">{{ message.content }}</div>
+                </div>
+              </div>
+            </div>
+            <!-- 修复消息输入区域,避免重复定义 -->
+            <div class="message-input-area">
+            <!-- 只使用单向绑定 + input事件 -->
+            <textarea 
+              [value]="newMessage()"
+              (input)="onMessageInput($event)"
+              placeholder="输入消息内容..."
+              rows="3"
+              (keydown.enter.shift)="$event.preventDefault()"
+              (keydown.enter)="sendMessage()"
+            ></textarea>
+            <div class="message-actions">
+              <button class="secondary-btn">
+                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                  <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
+                  <polyline points="14 2 14 8 20 8"></polyline>
+                </svg>
+                <span>上传文件</span>
+              </button>
+              <button class="primary-btn" (click)="sendMessage()" [disabled]="!newMessage().trim()">
+                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                  <line x1="22" y1="2" x2="11" y2="13"></line>
+                  <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
+                </svg>
+                <span>发送</span>
+              </button>
+            </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 文件标签内容 -->
+        <div *ngIf="activeTab() === 'files'" class="tab-content">
+          <div class="files-filter">
+            <div class="filter-options">
+              <button class="filter-btn active">全部文件</button>
+              <button class="filter-btn">文档</button>
+              <button class="filter-btn">图片</button>
+              <button class="filter-btn">表格</button>
+            </div>
+            <div class="search-box">
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                <circle cx="11" cy="11" r="8"></circle>
+                <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+              </svg>
+              <input type="text" placeholder="搜索文件...">
+            </div>
+          </div>
+          <div class="files-list">
+            <div *ngFor="let file of files()" class="file-item">
+              <div class="file-icon" [class.type-document]="file.type === 'document'" [class.type-image]="file.type === 'image'" [class.type-spreadsheet]="file.type === 'spreadsheet'">
+                <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                  <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+                  <polyline points="14 2 14 8 20 8"></polyline>
+                  <line x1="16" y1="13" x2="8" y2="13"></line>
+                  <line x1="16" y1="17" x2="8" y2="17"></line>
+                  <polyline points="10 9 9 9 8 9"></polyline>
+                </svg>
+              </div>
+              <div class="file-info">
+                <h4 class="file-name">{{ file.name }}</h4>
+                <div class="file-meta">
+                  <span class="file-uploader">上传者:{{ file.uploadedBy }}</span>
+                  <span class="file-date">{{ formatDate(file.uploadedAt) }}</span>
+                  <span class="file-size">{{ file.size }}</span>
+                </div>
+              </div>
+              <div class="file-actions">
+                <button class="action-btn" (click)="previewFile(file)" title="预览">
+                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
+                  </svg>
+                </button>
+                <button class="action-btn" (click)="downloadFile(file)" title="下载">
+                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
+                    <polyline points="7 10 12 15 17 10"></polyline>
+                    <line x1="12" y1="15" x2="12" y2="3"></line>
+                  </svg>
+                </button>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+</div>

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

@@ -0,0 +1,1192 @@
+// 全局变量
+$primary-color: #1e88e5;
+$secondary-color: #6c757d;
+$success-color: #4caf50;
+$warning-color: #ff9800;
+$danger-color: #f44336;
+$text-primary: #333;
+$text-secondary: #666;
+$text-light: #999;
+$bg-light: #f5f7fa;
+$bg-white: #fff;
+$border-color: #e1e5e9;
+$box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+$transition: all 0.3s ease;
+
+// 主容器
+.project-detail-container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  overflow: hidden;
+  background-color: $bg-light;
+}
+
+// 顶部导航栏
+.top-navbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 24px;
+  height: 64px;
+  background-color: $bg-white;
+  border-bottom: 1px solid $border-color;
+  box-shadow: $box-shadow;
+
+  .navbar-left {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+
+    .menu-toggle {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 40px;
+      height: 40px;
+      border: none;
+      background: none;
+      color: $text-primary;
+      cursor: pointer;
+      transition: $transition;
+
+      &:hover {
+        color: $primary-color;
+        background-color: $bg-light;
+        border-radius: 4px;
+      }
+    }
+
+    .app-title {
+      font-size: 18px;
+      font-weight: 600;
+      color: $text-primary;
+      margin: 0;
+    }
+  }
+
+  .navbar-right {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+
+    .notification-btn {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 40px;
+      height: 40px;
+      border: none;
+      background: none;
+      color: $text-primary;
+      cursor: pointer;
+      position: relative;
+      transition: $transition;
+
+      &:hover {
+        color: $primary-color;
+        background-color: $bg-light;
+        border-radius: 4px;
+      }
+
+      .notification-badge {
+        position: absolute;
+        top: 8px;
+        right: 8px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 16px;
+        height: 16px;
+        font-size: 10px;
+        font-weight: 600;
+        color: white;
+        background-color: $danger-color;
+        border-radius: 50%;
+      }
+    }
+
+    .user-profile {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      padding: 8px 12px;
+      border-radius: 6px;
+      transition: $transition;
+
+      &:hover {
+        background-color: $bg-light;
+      }
+
+      .user-avatar {
+        width: 32px;
+        height: 32px;
+        border-radius: 50%;
+        object-fit: cover;
+      }
+
+      .user-name {
+        font-size: 14px;
+        font-weight: 500;
+        color: $text-primary;
+      }
+    }
+  }
+}
+
+// 主要内容区
+.dashboard-content {
+  display: flex;
+  flex: 1;
+  overflow: hidden;
+}
+
+// 左侧侧边栏
+.sidebar {
+  width: 240px;
+  background-color: $bg-white;
+  border-right: 1px solid $border-color;
+  overflow-y: auto;
+  flex-shrink: 0;
+
+  .sidebar-nav {
+    padding: 16px 0;
+
+    .nav-item {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 12px 24px;
+      color: $text-secondary;
+      text-decoration: none;
+      transition: $transition;
+
+      &:hover {
+        color: $primary-color;
+        background-color: $bg-light;
+      }
+
+      &.active {
+        color: $primary-color;
+        background-color: #e3f2fd;
+        border-left: 3px solid $primary-color;
+        padding-left: 21px;
+      }
+
+      svg {
+        flex-shrink: 0;
+      }
+
+      span {
+        font-size: 14px;
+        font-weight: 500;
+      }
+    }
+  }
+}
+
+// 右侧主要内容
+.project-content {
+  flex: 1;
+  overflow-y: auto;
+  padding: 24px;
+
+  // 项目头部信息
+  .project-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    margin-bottom: 24px;
+
+    .project-title-section {
+      .project-title {
+        font-size: 24px;
+        font-weight: 600;
+        color: $text-primary;
+        margin: 0 0 8px 0;
+      }
+
+      .project-meta {
+        display: flex;
+        align-items: center;
+        gap: 16px;
+
+        .project-status {
+          padding: 4px 12px;
+          border-radius: 16px;
+          font-size: 12px;
+          font-weight: 500;
+          text-align: center;
+
+          &.status-active {
+            color: $primary-color;
+            background-color: #e3f2fd;
+          }
+
+          &.status-pending {
+            color: $warning-color;
+            background-color: #fff3e0;
+          }
+
+          &.status-completed {
+            color: $success-color;
+            background-color: #e8f5e9;
+          }
+        }
+
+        .project-stage {
+          font-size: 14px;
+          color: $text-secondary;
+        }
+      }
+    }
+
+    .project-actions {
+      display: flex;
+      gap: 12px;
+
+      .primary-btn,
+      .secondary-btn {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        padding: 8px 16px;
+        border: none;
+        border-radius: 6px;
+        font-size: 14px;
+        font-weight: 500;
+        cursor: pointer;
+        transition: $transition;
+      }
+
+      .primary-btn {
+        color: white;
+        background-color: $primary-color;
+
+        &:hover {
+          background-color: #1565c0;
+        }
+
+        &:disabled {
+          background-color: $text-light;
+          cursor: not-allowed;
+        }
+      }
+
+      .secondary-btn {
+        color: $text-primary;
+        background-color: $bg-white;
+        border: 1px solid $border-color;
+
+        &:hover {
+          background-color: $bg-light;
+        }
+      }
+
+      .primary-btn.small {
+        padding: 4px 12px;
+        font-size: 12px;
+      }
+    }
+  }
+
+  // 项目进度卡片
+  .progress-card {
+    padding: 20px;
+    margin-bottom: 24px;
+    background-color: $bg-white;
+    border-radius: 8px;
+    box-shadow: $box-shadow;
+
+    .progress-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 12px;
+
+      h3 {
+        font-size: 16px;
+        font-weight: 600;
+        color: $text-primary;
+        margin: 0;
+      }
+
+      .progress-percentage {
+        font-size: 20px;
+        font-weight: 700;
+        color: $primary-color;
+      }
+    }
+
+    .progress-bar {
+      height: 8px;
+      margin-bottom: 12px;
+      background-color: $bg-light;
+      border-radius: 4px;
+      overflow: hidden;
+
+      .progress-fill {
+        height: 100%;
+        background-color: $primary-color;
+        border-radius: 4px;
+        transition: $transition;
+      }
+    }
+
+    .progress-meta {
+      display: flex;
+      justify-content: space-between;
+      font-size: 12px;
+      color: $text-light;
+    }
+  }
+
+  // 项目详情标签页
+  .project-tabs {
+    background-color: $bg-white;
+    border-radius: 8px;
+    box-shadow: $box-shadow;
+    overflow: hidden;
+
+    // 标签页头部
+    .tab-header {
+      display: flex;
+      border-bottom: 1px solid $border-color;
+      background-color: #f8f9fa;
+
+      .tab-btn {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        padding: 16px 24px;
+        border: none;
+        background: none;
+        font-size: 14px;
+        font-weight: 500;
+        color: $text-secondary;
+        cursor: pointer;
+        transition: $transition;
+        border-bottom: 2px solid transparent;
+
+        &:hover {
+          color: $primary-color;
+          background-color: $bg-light;
+        }
+
+        &.active {
+          color: $primary-color;
+          background-color: $bg-white;
+          border-bottom-color: $primary-color;
+        }
+      }
+    }
+
+    // 标签页内容
+    .tab-content {
+      padding: 24px;
+      min-height: 400px;
+    }
+  }
+
+  // 概览网格布局
+  .overview-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
+    gap: 24px;
+
+    // 信息卡片
+    .info-card {
+      padding: 20px;
+      background-color: $bg-light;
+      border-radius: 8px;
+
+      .card-title {
+        font-size: 16px;
+        font-weight: 600;
+        color: $text-primary;
+        margin: 0 0 16px 0;
+        padding-bottom: 12px;
+        border-bottom: 1px solid $border-color;
+      }
+
+      // 客户信息
+      .customer-info {
+        .info-item {
+          margin-bottom: 16px;
+
+          label {
+            display: block;
+            font-size: 12px;
+            font-weight: 500;
+            color: $text-light;
+            margin-bottom: 4px;
+          }
+
+          span {
+            font-size: 14px;
+            color: $text-primary;
+          }
+
+          .tag-container {
+            display: flex;
+            gap: 8px;
+            flex-wrap: wrap;
+
+            .tag {
+              padding: 4px 12px;
+              font-size: 12px;
+              color: $primary-color;
+              background-color: #e3f2fd;
+              border-radius: 16px;
+            }
+          }
+
+          .need-list {
+            margin: 0;
+            padding-left: 20px;
+
+            li {
+              display: flex;
+              align-items: flex-start;
+              gap: 8px;
+              font-size: 14px;
+              color: $text-primary;
+              margin-bottom: 4px;
+
+              svg {
+                flex-shrink: 0;
+                margin-top: 2px;
+                color: $success-color;
+              }
+            }
+          }
+        }
+      }
+
+      // 团队信息
+      .team-info {
+        .team-member {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          padding: 12px 0;
+          border-bottom: 1px solid $border-color;
+
+          &:last-child {
+            border-bottom: none;
+          }
+
+          .member-avatar {
+            width: 48px;
+            height: 48px;
+            border-radius: 50%;
+            object-fit: cover;
+          }
+
+          .member-details {
+            flex: 1;
+            margin-left: 12px;
+
+            .member-name {
+              font-size: 14px;
+              font-weight: 500;
+              color: $text-primary;
+              margin-bottom: 2px;
+            }
+
+            .member-role {
+              font-size: 12px;
+              color: $text-light;
+            }
+          }
+
+          .message-btn {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 36px;
+            height: 36px;
+            border: none;
+            color: $primary-color;
+            background-color: #e3f2fd;
+            border-radius: 50%;
+            cursor: pointer;
+            transition: $transition;
+
+            &:hover {
+              background-color: $primary-color;
+              color: white;
+            }
+          }
+        }
+      }
+
+      // 反馈信息
+      .feedback-list {
+        .feedback-item {
+          padding: 16px;
+          background-color: $bg-white;
+          border-radius: 6px;
+          margin-bottom: 12px;
+
+          .feedback-header {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: 8px;
+
+            .feedback-author {
+              font-size: 14px;
+              font-weight: 500;
+              color: $text-primary;
+            }
+
+            .feedback-rating {
+              display: flex;
+              align-items: center;
+              gap: 8px;
+
+              .rating-stars {
+                font-size: 12px;
+                color: $warning-color;
+              }
+
+              .rating-number {
+                font-size: 14px;
+                font-weight: 600;
+                color: $text-primary;
+              }
+            }
+          }
+
+          .feedback-content {
+            font-size: 14px;
+            color: $text-secondary;
+            line-height: 1.5;
+            margin-bottom: 8px;
+          }
+
+          .feedback-response {
+            padding: 12px;
+            background-color: $bg-light;
+            border-radius: 4px;
+            margin-bottom: 8px;
+
+            .response-label {
+              font-size: 12px;
+              font-weight: 500;
+              color: $text-light;
+              margin-bottom: 4px;
+            }
+
+            .response-text {
+              font-size: 13px;
+              color: $text-secondary;
+            }
+          }
+
+          .feedback-meta {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+
+            .feedback-date {
+              font-size: 12px;
+              color: $text-light;
+            }
+
+            .feedback-status {
+              padding: 2px 8px;
+              font-size: 11px;
+              font-weight: 500;
+              border-radius: 12px;
+
+              &.status-processed {
+                color: $success-color;
+                background-color: #e8f5e9;
+              }
+
+              &.status-pending {
+                color: $warning-color;
+                background-color: #fff3e0;
+              }
+            }
+          }
+        }
+
+        .view-all-btn {
+          display: block;
+          width: 100%;
+          padding: 8px 16px;
+          border: 1px solid $border-color;
+          background-color: $bg-white;
+          color: $text-secondary;
+          font-size: 13px;
+          text-align: center;
+          cursor: pointer;
+          border-radius: 4px;
+          transition: $transition;
+
+          &:hover {
+            color: $primary-color;
+            border-color: $primary-color;
+          }
+        }
+      }
+    }
+  }
+
+  // 里程碑时间线
+  .milestones-timeline {
+    position: relative;
+    padding-left: 32px;
+    margin-left: 16px;
+
+    .milestone-item {
+      position: relative;
+      margin-bottom: 24px;
+
+      .milestone-dot {
+        position: absolute;
+        left: -48px;
+        top: 4px;
+        width: 16px;
+        height: 16px;
+        background-color: $text-light;
+        border-radius: 50%;
+        border: 2px solid white;
+        box-shadow: 0 0 0 2px $border-color;
+        transition: $transition;
+
+        &.completed {
+          background-color: $success-color;
+          box-shadow: 0 0 0 2px #e8f5e9;
+        }
+      }
+
+      .milestone-line {
+        position: absolute;
+        left: -40px;
+        top: 20px;
+        bottom: -24px;
+        width: 2px;
+        background-color: $border-color;
+        transition: $transition;
+
+        &.completed {
+          background-color: $success-color;
+        }
+      }
+
+      .milestone-content {
+        padding: 16px;
+        background-color: $bg-white;
+        border-radius: 8px;
+        border-left: 3px solid $border-color;
+        transition: $transition;
+
+        &:hover {
+          box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+        }
+      }
+
+      .milestone-header {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 8px;
+
+        .milestone-title {
+          font-size: 16px;
+          font-weight: 600;
+          color: $text-primary;
+          margin: 0;
+        }
+
+        .milestone-status {
+          padding: 4px 12px;
+          border-radius: 16px;
+          font-size: 12px;
+          font-weight: 500;
+
+          &.status-completed {
+            color: $success-color;
+            background-color: #e8f5e9;
+          }
+
+          &.status-pending {
+            color: $warning-color;
+            background-color: #fff3e0;
+          }
+        }
+      }
+
+      .milestone-description {
+        font-size: 14px;
+        color: $text-secondary;
+        line-height: 1.5;
+        margin: 0 0 12px 0;
+      }
+
+      .milestone-dates {
+        display: flex;
+        gap: 24px;
+        margin-bottom: 12px;
+
+        .date-item {
+          display: flex;
+          flex-direction: column;
+
+          label {
+            font-size: 11px;
+            color: $text-light;
+            margin-bottom: 2px;
+          }
+
+          span {
+            font-size: 13px;
+            color: $text-primary;
+            font-weight: 500;
+          }
+        }
+      }
+
+      .milestone-actions {
+        display: flex;
+        gap: 8px;
+      }
+    }
+
+    .milestone-item:last-child {
+      margin-bottom: 0;
+
+      .milestone-line {
+        display: none;
+      }
+    }
+  }
+
+  // 任务过滤和搜索
+  .tasks-filter,
+  .files-filter {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 24px;
+
+    .filter-options {
+      display: flex;
+      gap: 8px;
+
+      .filter-btn {
+        padding: 6px 16px;
+        border: 1px solid $border-color;
+        background-color: $bg-white;
+        color: $text-secondary;
+        font-size: 13px;
+        font-weight: 500;
+        cursor: pointer;
+        border-radius: 4px;
+        transition: $transition;
+
+        &:hover {
+          color: $primary-color;
+          border-color: $primary-color;
+        }
+
+        &.active {
+          color: white;
+          background-color: $primary-color;
+          border-color: $primary-color;
+        }
+      }
+    }
+
+    .search-box {
+      position: relative;
+      width: 240px;
+
+      svg {
+        position: absolute;
+        left: 12px;
+        top: 50%;
+        transform: translateY(-50%);
+        color: $text-light;
+      }
+
+      input {
+        width: 100%;
+        padding: 8px 12px 8px 40px;
+        border: 1px solid $border-color;
+        border-radius: 4px;
+        font-size: 14px;
+        color: $text-primary;
+        transition: $transition;
+
+        &:focus {
+          outline: none;
+          border-color: $primary-color;
+          box-shadow: 0 0 0 2px rgba(30, 136, 229, 0.1);
+        }
+
+        &::placeholder {
+          color: $text-light;
+        }
+      }
+    }
+  }
+
+  // 任务列表
+  .tasks-list {
+    .task-item {
+      display: flex;
+      gap: 16px;
+      padding: 16px;
+      margin-bottom: 12px;
+      background-color: $bg-white;
+      border-radius: 8px;
+      border-left: 3px solid $border-color;
+      transition: $transition;
+
+      &:hover {
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+      }
+
+      .task-checkbox {
+        flex-shrink: 0;
+
+        input[type="checkbox"] {
+          width: 18px;
+          height: 18px;
+          cursor: pointer;
+        }
+      }
+
+      .task-content {
+        flex: 1;
+
+        .task-title {
+          font-size: 16px;
+          font-weight: 600;
+          color: $text-primary;
+          margin: 0 0 8px 0;
+          transition: $transition;
+
+          &.completed {
+            color: $text-light;
+            text-decoration: line-through;
+          }
+        }
+
+        .task-description {
+          font-size: 14px;
+          color: $text-secondary;
+          line-height: 1.5;
+          margin: 0 0 12px 0;
+        }
+
+        .task-meta {
+          display: flex;
+          gap: 24px;
+
+          span {
+            display: flex;
+            align-items: center;
+            gap: 6px;
+            font-size: 13px;
+            color: $text-secondary;
+          }
+
+          .task-deadline {
+            &.overdue {
+              color: $danger-color;
+            }
+          }
+
+          .task-priority {
+            padding: 2px 8px;
+            border-radius: 12px;
+            font-size: 11px;
+            font-weight: 500;
+
+            &.priority-high {
+              color: $danger-color;
+              background-color: #ffebee;
+            }
+
+            &.priority-medium {
+              color: $warning-color;
+              background-color: #fff3e0;
+            }
+
+            &.priority-low {
+              color: $success-color;
+              background-color: #e8f5e9;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 消息容器
+  .messages-container {
+    display: flex;
+    flex-direction: column;
+    height: 500px;
+
+    .messages-list {
+      flex: 1;
+      overflow-y: auto;
+      padding-right: 8px;
+      margin-bottom: 16px;
+
+      .message-item {
+        display: flex;
+        gap: 12px;
+        margin-bottom: 24px;
+
+        .message-avatar {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          width: 36px;
+          height: 36px;
+          font-size: 14px;
+          font-weight: 600;
+          color: white;
+          background-color: $primary-color;
+          border-radius: 50%;
+          flex-shrink: 0;
+        }
+
+        .message-content {
+          max-width: 70%;
+
+          .message-header {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: 4px;
+
+            .message-sender {
+              font-size: 13px;
+              font-weight: 600;
+              color: $text-primary;
+            }
+
+            .message-time {
+              font-size: 11px;
+              color: $text-light;
+            }
+          }
+
+          .message-text {
+            font-size: 14px;
+            color: $text-secondary;
+            line-height: 1.5;
+            padding: 12px;
+            background-color: $bg-light;
+            border-radius: 12px;
+          }
+        }
+      }
+    }
+
+    .message-input-area {
+      .message-actions {
+        display: flex;
+        justify-content: flex-end;
+        gap: 12px;
+        margin-top: 12px;
+      }
+
+      textarea {
+        width: 100%;
+        padding: 12px;
+        border: 1px solid $border-color;
+        border-radius: 6px;
+        font-size: 14px;
+        color: $text-primary;
+        resize: vertical;
+        transition: $transition;
+
+        &:focus {
+          outline: none;
+          border-color: $primary-color;
+          box-shadow: 0 0 0 2px rgba(30, 136, 229, 0.1);
+        }
+
+        &::placeholder {
+          color: $text-light;
+        }
+      }
+    }
+  }
+
+  // 文件列表
+  .files-list {
+    .file-item {
+      display: flex;
+      align-items: center;
+      gap: 16px;
+      padding: 16px;
+      margin-bottom: 12px;
+      background-color: $bg-white;
+      border-radius: 8px;
+      transition: $transition;
+
+      &:hover {
+        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+      }
+
+      .file-icon {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 48px;
+        height: 48px;
+        border-radius: 8px;
+        flex-shrink: 0;
+
+        &.type-document {
+          color: $primary-color;
+          background-color: #e3f2fd;
+        }
+
+        &.type-image {
+          color: $success-color;
+          background-color: #e8f5e9;
+        }
+
+        &.type-spreadsheet {
+          color: $warning-color;
+          background-color: #fff3e0;
+        }
+      }
+
+      .file-info {
+        flex: 1;
+
+        .file-name {
+          font-size: 14px;
+          font-weight: 500;
+          color: $text-primary;
+          margin: 0 0 8px 0;
+        }
+
+        .file-meta {
+          display: flex;
+          gap: 16px;
+
+          span {
+            font-size: 12px;
+            color: $text-light;
+          }
+        }
+      }
+
+      .file-actions {
+        display: flex;
+        gap: 8px;
+
+        .action-btn {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          width: 36px;
+          height: 36px;
+          border: 1px solid $border-color;
+          background-color: $bg-white;
+          color: $text-secondary;
+          border-radius: 6px;
+          cursor: pointer;
+          transition: $transition;
+
+          &:hover {
+            color: $primary-color;
+            border-color: $primary-color;
+          }
+        }
+      }
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 1024px) {
+  .overview-grid {
+    grid-template-columns: 1fr;
+  }
+
+  .project-content {
+    padding: 16px;
+  }
+
+  .sidebar {
+    width: 200px;
+  }
+}
+
+@media (max-width: 768px) {
+  .top-navbar {
+    padding: 0 16px;
+
+    .navbar-right {
+      gap: 8px;
+    }
+
+    .user-name {
+      display: none;
+    }
+  }
+
+  .sidebar {
+    display: none;
+  }
+
+  .project-header {
+    flex-direction: column;
+    gap: 16px;
+    align-items: flex-start !important;
+  }
+
+  .project-actions {
+    width: 100%;
+    justify-content: flex-start;
+  }
+
+  .tab-header {
+    flex-wrap: wrap;
+
+    .tab-btn {
+      flex: 1;
+      min-width: 120px;
+    }
+  }
+
+  .tasks-filter,
+  .files-filter {
+    flex-direction: column;
+    gap: 16px;
+    align-items: flex-start !important;
+
+    .search-box {
+      width: 100%;
+    }
+  }
+
+  .task-meta,
+  .file-meta {
+    flex-wrap: wrap;
+    gap: 12px !important;
+  }
+
+  .messages-container {
+    height: 400px;
+  }
+
+  .message-content {
+    max-width: 85% !important;
+  }
+}

+ 450 - 5
src/app/pages/customer-service/project-detail/project-detail.ts

@@ -1,11 +1,456 @@
-import { Component } from '@angular/core';
+// 添加 toggleSidebar 方法到 ProjectDetail 类中
+// 在第20行左右添加:
+import { Component, OnInit, signal, computed } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { RouterModule, ActivatedRoute } from '@angular/router';
+import { ProjectService } from '../../../services/project.service';
+import { Project, Task, Message, FileItem, CustomerFeedback, Milestone } from '../../../models/project.model';
 
+// 删除错误的代码块
 @Component({
   selector: 'app-project-detail',
-  imports: [],
+  standalone: true,
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, RouterModule],
   templateUrl: './project-detail.html',
   styleUrl: './project-detail.scss'
-})
-export class ProjectDetail {
-
+}) 
+export class ProjectDetail implements OnInit {
+  // 项目ID
+  projectId = '';
+  
+  // 项目详情
+  project = signal<Project | null>(null);
+  
+  // 添加 toggleSidebar 方法
+  toggleSidebar(): void {
+    // 这里可以实现侧边栏切换逻辑
+    // 如果需要与全局状态交互,可以注入相应的服务
+    console.log('Toggle sidebar');
+  }
+  
+  // 项目里程碑
+  milestones = signal<Milestone[]>([]);
+  
+  // 项目任务
+  tasks = signal<Task[]>([]);
+  
+  // 项目消息
+  messages = signal<Message[]>([]);
+  
+  // 项目文件
+  files = signal<FileItem[]>([]);
+  
+  // 客户反馈
+  feedbacks = signal<CustomerFeedback[]>([]);
+  
+  // 当前激活的标签页
+  activeTab = signal('overview');
+  
+  // 新消息内容
+  newMessage = signal('');
+  
+  // 项目状态颜色映射
+  statusColors = {
+    '进行中': 'primary',
+    '待确认': 'warning',
+    '已完成': 'success',
+    '已暂停': 'secondary',
+    '已取消': 'danger'
+  };
+  
+  // 计算完成进度
+  completionProgress = computed(() => {
+    if (!this.tasks().length) return 0;
+    const completedTasks = this.tasks().filter(task => task.isCompleted).length;
+    return Math.round((completedTasks / this.tasks().length) * 100);
+  });
+  
+  constructor(
+    private route: ActivatedRoute,
+    private projectService: ProjectService
+  ) {
+    // 获取路由参数中的项目ID
+    this.route.paramMap.subscribe(params => {
+      this.projectId = params.get('id') || '';
+    });
+  }
+  
+  ngOnInit(): void {
+    this.loadProjectDetails();
+  }
+  
+  // 加载项目详情
+  loadProjectDetails(): void {
+    // 模拟从服务获取项目数据
+    this.projectService.getProjectById(this.projectId).subscribe(project => {
+      if (project) {
+        this.project.set(project);
+      }
+    });
+    
+    // 加载模拟数据
+    this.loadMockData();
+  }
+  
+  // 加载模拟数据
+  // 修复 loadMockData 方法中的任务对象,确保类型一致性
+  loadMockData(): void {
+    // 模拟里程碑数据
+    this.milestones.set([
+      { 
+        id: 'm1',
+        title: '客户需求确认',
+        description: '确认客户的装修需求和风格偏好',
+        dueDate: new Date('2023-06-10'),
+        completedDate: new Date('2023-06-08'),
+        isCompleted: true
+      },
+      {
+        id: 'm2',
+        title: '设计方案提交',
+        description: '提交初步设计方案供客户审阅',
+        dueDate: new Date('2023-06-20'),
+        completedDate: new Date('2023-06-18'),
+        isCompleted: true
+      },
+      {
+        id: 'm3',
+        title: '方案修改与确认',
+        description: '根据客户反馈修改并最终确认方案',
+        dueDate: new Date('2023-06-25'),
+        completedDate: undefined,
+        isCompleted: false
+      },
+      {
+        id: 'm4',
+        title: '施工图制作',
+        description: '制作详细的施工图纸',
+        dueDate: new Date('2023-07-05'),
+        completedDate: undefined,
+        isCompleted: false
+      },
+      {
+        id: 'm5',
+        title: '项目验收',
+        description: '客户验收最终成果',
+        dueDate: new Date('2023-07-15'),
+        completedDate: undefined,
+        isCompleted: false
+      }
+    ]);
+    
+    // 模拟任务数据 - 确保所有任务对象都有完整的必填属性
+    this.tasks.set([
+      { 
+        id: 't1',
+        title: '现场量房',
+        description: '前往客户现场进行房屋测量',
+        projectId: this.projectId,
+        projectName: '现代简约风格三居室设计',
+        assignee: '张设计师',
+        deadline: new Date('2023-06-05'),
+        completedDate: new Date('2023-06-04'),
+        isCompleted: true,
+        isOverdue: false,
+        priority: 'high',
+        stage: '前期沟通' // 添加 stage 属性,确保符合 Task 接口
+      },
+      {
+        id: 't2',
+        title: '设计初稿',
+        description: '完成设计方案初稿',
+        projectId: this.projectId,
+        projectName: '现代简约风格三居室设计',
+        assignee: '张设计师',
+        deadline: new Date('2023-06-15'),
+        completedDate: new Date('2023-06-14'),
+        isCompleted: true,
+        isOverdue: false,
+        priority: 'high',
+        stage: '建模'
+      },
+      {
+        id: 't3',
+        title: '客户沟通',
+        description: '与客户沟通设计方案反馈',
+        projectId: this.projectId,
+        projectName: '现代简约风格三居室设计',
+        assignee: '客服小李',
+        deadline: new Date('2023-06-22'),
+        completedDate: undefined,
+        isCompleted: false,
+        isOverdue: false,
+        priority: 'medium',
+        stage: '前期沟通'
+      },
+      {
+        id: 't4',
+        title: '方案修改',
+        description: '根据客户反馈修改设计方案',
+        projectId: this.projectId,
+        projectName: '现代简约风格三居室设计',
+        assignee: '张设计师',
+        deadline: new Date('2023-06-28'),
+        completedDate: undefined,
+        isCompleted: false,
+        isOverdue: false,
+        priority: 'high',
+        stage: '软装'
+      },
+      {
+        id: 't5',
+        title: '施工图绘制',
+        description: '绘制详细的施工图纸',
+        projectId: this.projectId,
+        projectName: '现代简约风格三居室设计',
+        assignee: '王设计师',
+        deadline: new Date('2023-07-10'),
+        completedDate: undefined,
+        isCompleted: false,
+        isOverdue: false,
+        priority: 'medium',
+        stage: '渲染'
+      }
+    ]);
+    
+    // 模拟消息数据
+    this.messages.set([
+      {
+        id: 'msg1',
+        sender: '客户王先生',
+        content: '设计方案收到了,整体很满意,客厅的颜色可以再调整一下吗?',
+        timestamp: new Date('2023-06-19T10:30:00'),
+        isRead: true,
+        type: 'text'
+      },
+      {
+        id: 'msg2',
+        sender: '客服小李',
+        content: '好的,我们会根据您的需求调整客厅颜色方案,稍后给您发送修改后的效果图。',
+        timestamp: new Date('2023-06-19T10:45:00'),
+        isRead: true,
+        type: 'text'
+      },
+      {
+        id: 'msg3',
+        sender: '张设计师',
+        content: '修改后的客厅效果图已上传,请查收。',
+        timestamp: new Date('2023-06-19T14:20:00'),
+        isRead: true,
+        type: 'text'
+      },
+      {
+        id: 'msg4',
+        sender: '客户王先生',
+        content: '这个效果很好,就按照这个方案进行吧!',
+        timestamp: new Date('2023-06-20T09:15:00'),
+        isRead: true,
+        type: 'text'
+      },
+      {
+        id: 'msg5',
+        sender: '客服小李',
+        content: '收到,我们会尽快开始下一阶段的工作。',
+        timestamp: new Date('2023-06-20T09:30:00'),
+        isRead: true,
+        type: 'text'
+      }
+    ]);
+    
+    // 模拟文件数据
+    this.files.set([
+      {
+        id: 'file1',
+        name: '设计方案初稿.pdf',
+        type: 'document',
+        size: '2.5MB',
+        url: 'https://example.com/files/design-proposal.pdf',
+        uploadedBy: '张设计师',
+        uploadedAt: new Date('2023-06-15'),
+        downloadCount: 12
+      },
+      {
+        id: 'file2',
+        name: '客厅效果图.png',
+        type: 'image',
+        size: '1.8MB',
+        url: 'https://example.com/files/living-room.png',
+        uploadedBy: '张设计师',
+        uploadedAt: new Date('2023-06-18'),
+        downloadCount: 8
+      },
+      {
+        id: 'file3',
+        name: '修改后客厅效果图.png',
+        type: 'image',
+        size: '2.1MB',
+        url: 'https://example.com/files/living-room-revised.png',
+        uploadedBy: '张设计师',
+        uploadedAt: new Date('2023-06-19'),
+        downloadCount: 5
+      },
+      {
+        id: 'file4',
+        name: '客户需求文档.docx',
+        type: 'document',
+        size: '1.2MB',
+        url: 'https://example.com/files/requirements.docx',
+        uploadedBy: '客服小李',
+        uploadedAt: new Date('2023-06-05'),
+        downloadCount: 10
+      },
+      {
+        id: 'file5',
+        name: '房屋测量数据.xlsx',
+        type: 'spreadsheet',
+        size: '0.8MB',
+        url: 'https://example.com/files/measurement.xlsx',
+        uploadedBy: '张设计师',
+        uploadedAt: new Date('2023-06-04'),
+        downloadCount: 7
+      }
+    ]);
+    
+    // 模拟客户反馈
+    this.feedbacks.set([
+      {
+        id: 'fb1',
+        projectId: this.projectId,
+        customerName: '王先生',
+        content: '对设计方案整体满意,但希望客厅颜色能更柔和一些。',
+        rating: 4,
+        createdAt: new Date('2023-06-19'),
+        status: '已解决',
+        response: '已根据您的需求调整了客厅颜色方案,详见最新上传的效果图。',
+        isSatisfied: true
+      }
+    ]);
+  }
+  
+  // 切换标签页
+  switchTab(tab: string): void {
+    this.activeTab.set(tab);
+  }
+  
+  // 发送消息
+  sendMessage(): void {
+    if (this.newMessage().trim()) {
+      const newMsg: Message = {
+        id: `msg${Date.now()}`,
+        sender: '客服小李',
+        content: this.newMessage().trim(),
+        timestamp: new Date(),
+        isRead: true,
+        type: 'text'
+      };
+      
+      this.messages.set([...this.messages(), newMsg]);
+      this.newMessage.set('');
+    }
+  }
+  
+  // 完成任务
+  // 修复 completeTask 方法中的类型问题
+  completeTask(taskId: string): void {
+    this.tasks.set(
+      this.tasks().map(task => 
+        task.id === taskId 
+          ? { ...task, isCompleted: true, completedDate: new Date(), isOverdue: false } // 添加 isOverdue 确保类型安全
+          : task
+      )
+    );
+  }
+  
+  // 修复 completeMilestone 方法中的类型问题
+  completeMilestone(milestoneId: string): void {
+    this.milestones.set(
+      this.milestones().map(milestone => 
+        milestone.id === milestoneId 
+          ? { ...milestone, isCompleted: true, completedDate: new Date() }
+          : milestone
+      )
+    );
+  }
+  
+  // 增强 formatDate 和 formatDateTime 方法的类型安全
+  formatDate(date: Date | string | null | undefined): string {
+    if (!date) return '-';
+    try {
+      return new Date(date).toLocaleDateString('zh-CN', {
+        year: 'numeric',
+        month: '2-digit',
+        day: '2-digit'
+      });
+    } catch (error) {
+      console.error('日期格式化错误:', error);
+      return '-';
+    }
+  }
+  
+  formatDateTime(date: Date | string | null | undefined): string {
+    if (!date) return '-';
+    try {
+      return new Date(date).toLocaleString('zh-CN', {
+        year: 'numeric',
+        month: '2-digit',
+        day: '2-digit',
+        hour: '2-digit',
+        minute: '2-digit'
+      });
+    } catch (error) {
+      console.error('日期时间格式化错误:', error);
+      return '-';
+    }
+  }
+  
+  // 下载文件
+  downloadFile(file: FileItem): void {
+    console.log('下载文件:', file.name);
+    // 实际应用中,这里会处理文件下载逻辑
+  }
+  
+  // 查看文件预览
+  previewFile(file: FileItem): void {
+    console.log('预览文件:', file.name);
+    // 实际应用中,这里会打开文件预览
+  }
+  
+  // 获取项目状态的CSS类名
+  getProjectStatusClass(status: string | null | undefined): string {
+    if (!status) return 'status-default';
+    switch (status) {
+      case '进行中':
+        return 'status-active';
+      case '待确认':
+        return 'status-pending';
+      case '已完成':
+        return 'status-completed';
+      default:
+        return 'status-default';
+    }
+  }
+  
+  // 获取反馈客户名称(带安全检查)
+  getFeedbackCustomerName(feedback: CustomerFeedback | undefined | null): string {
+    if (!feedback) return '未知客户';
+    return feedback.customerName || '未知客户';
+  }
+  
+  // 获取反馈评分(带安全检查)
+  getFeedbackRating(feedback: CustomerFeedback | undefined | null): number {
+    if (!feedback) return 0;
+    return feedback.rating || 0;
+  }
+  
+  // 消息输入事件处理
+  onMessageInput(event: Event): void {
+    const target = event.target as HTMLTextAreaElement;
+    this.newMessage.set(target.value);
+  }
+  
+  // 添加正确的progressFillWidth计算属性
+  progressFillWidth = computed(() => {
+    return `${this.completionProgress()}%`;
+  });
 }

+ 281 - 1
src/app/pages/customer-service/project-list/project-list.html

@@ -1 +1,281 @@
-<p>project-list works!</p>
+<div class="project-list-container">
+  <!-- 顶部导航栏 -->
+  <header class="top-navbar">
+    <div class="navbar-left">
+      <button class="menu-toggle" (click)="toggleSidebar()">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <line x1="3" y1="12" x2="21" y2="12"></line>
+          <line x1="3" y1="6" x2="21" y2="6"></line>
+          <line x1="3" y1="18" x2="21" y2="18"></line>
+        </svg>
+      </button>
+      <h1 class="app-title">客服工作台</h1>
+    </div>
+    
+    <div class="navbar-right">
+      <button class="notification-btn">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
+        </svg>
+        <span class="notification-badge">3</span>
+      </button>
+      <div class="user-profile">
+        <img src="https://picsum.photos/id/64/40/40" alt="用户头像" class="user-avatar">
+        <span class="user-name">客服小李</span>
+      </div>
+    </div>
+  </header>
+
+  <!-- 主要内容区 -->
+  <main class="dashboard-content">
+    <!-- 左侧侧边栏 -->
+    <aside class="sidebar">
+      <nav class="sidebar-nav">
+        <a href="/customer-service/dashboard" class="nav-item">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"></path>
+          </svg>
+          <span>工作台</span>
+        </a>
+        <a href="/customer-service/consultation-order" class="nav-item">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
+          </svg>
+          <span>客户咨询</span>
+        </a>
+        <a href="/customer-service/project-list" class="nav-item active">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M3 6l3 1m0 0l-3 9a5.002 5.002 0 006.001 0M6 7l3 9M6 7l6-2m6 2l3-1m-3 1l-3 9a5.002 5.002 0 006.001 0M18 7l3 9m-3-9l-6-2m0-2v2m0 16V5m0 16H9m3 0h3"></path>
+          </svg>
+          <span>项目列表</span>
+        </a>
+        <a href="/customer-service/case-library" class="nav-item">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <rect x="2" y="7" width="20" height="14" rx="2" ry="2"></rect>
+            <path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path>
+          </svg>
+          <span>案例库</span>
+        </a>
+      </nav>
+    </aside>
+
+    <!-- 右侧主要内容 -->
+    <div class="project-content">
+      <!-- 页面标题和操作 -->
+      <div class="page-header">
+        <h2>项目列表</h2>
+        <div class="header-actions">
+          <div class="search-container">
+            <div class="search-box">
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                <circle cx="11" cy="11" r="8"></circle>
+                <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+              </svg>
+              <input 
+                type="text" 
+                  placeholder="搜索项目名称或客户..."
+                  [value]="searchTerm()"
+                  (input)="searchTerm.set($any($event.target).value)"
+                  (keyup.enter)="onSearch()"
+              >
+              <button class="search-btn" (click)="onSearch()">
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                  <line x1="5" y1="12" x2="19" y2="12"></line>
+                  <polyline points="12 5 19 12 12 19"></polyline>
+                </svg>
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 筛选和排序工具栏 -->
+      <div class="filter-toolbar">
+        <div class="filter-group">
+          <label>状态筛选</label>
+          <select (change)="onStatusChange($event)" [value]="statusFilter()">
+            <option *ngFor="let option of statusOptions" [value]="option.value">
+              {{ option.label }}
+            </option>
+          </select>
+        </div>
+        
+        <div class="filter-group">
+          <label>阶段筛选</label>
+          <select (change)="onStageChange($event)" [value]="stageFilter()">
+            <option *ngFor="let option of stageOptions" [value]="option.value">
+              {{ option.label }}
+            </option>
+          </select>
+        </div>
+        
+        <div class="filter-group">
+          <label>排序方式</label>
+          <select (change)="onSortChange($event)" [value]="sortBy()">
+            <option *ngFor="let option of sortOptions" [value]="option.value">
+              {{ option.label }}
+            </option>
+          </select>
+        </div>
+        
+        <div class="filter-results">
+          <span>共 {{ projects().length }} 个项目</span>
+        </div>
+      </div>
+
+      <!-- 项目列表 -->
+      <div class="project-grid">
+        <div *ngFor="let project of paginatedProjects()" class="project-card">
+          <div class="card-header">
+            <div class="card-title-section">
+              <h3 class="project-name">{{ project.name }}</h3>
+              <span class="project-id">#{{ project.id }}</span>
+            </div>
+            <div class="card-tags">
+              <span class="project-tag">{{ project.tagDisplayText }}</span>
+              <span *ngIf="project.isUrgent" class="urgent-tag">紧急</span>
+            </div>
+          </div>
+          
+          <div class="card-content">
+            <!-- 客户信息 -->
+            <div class="info-item">
+              <span class="info-label">客户</span>
+              <span class="info-value">{{ project.customerName }}</span>
+            </div>
+            
+            <!-- 设计师信息 -->
+            <div class="info-item">
+              <span class="info-label">设计师</span>
+              <span class="info-value">{{ project.assigneeName }}</span>
+            </div>
+            
+            <!-- 项目状态 -->
+            <div class="info-item">
+              <span class="info-label">状态</span>
+              <span class="info-value status-badge" [class]="getStatusClass(project.status)">
+                {{ project.status }}
+              </span>
+            </div>
+            
+            <!-- 当前阶段 -->
+            <div class="info-item">
+              <span class="info-label">阶段</span>
+              <span class="info-value stage-badge" [class]="getStageClass(project.currentStage)">
+                {{ project.currentStage }}
+              </span>
+            </div>
+            
+            <!-- 项目进度 -->
+            <div class="progress-section">
+              <div class="progress-header">
+                <span class="progress-label">进度</span>
+                <span class="progress-percentage">{{ project.progress }}%</span>
+              </div>
+              <div class="progress-bar">
+                <div 
+                  class="progress-fill" 
+                  [style.width.percent]="project.progress"
+                  [style.backgroundColor]="project.status === '进行中' ? '#1976d2' : '#757575'"
+                ></div>
+              </div>
+            </div>
+            
+            <!-- 时间信息 -->
+            <div class="timeline-info">
+              <div class="time-item">
+                <span class="time-label">创建时间</span>
+                <span class="time-value">{{ formatDate(project.createdAt) }}</span>
+              </div>
+              <div class="time-item">
+                <span class="time-label">截止日期</span>
+                <span 
+                  class="time-value deadline"
+                  [class.overdue]="project.daysUntilDeadline < 0"
+                  [class.urgent]="project.isUrgent"
+                >
+                  {{ formatDate(project.deadline) }} ({{ project.daysUntilDeadline >= 0 ? '还有' + project.daysUntilDeadline + '天' : '已逾期' + getAbsValue(project.daysUntilDeadline) + '天' }})
+                </span>
+              </div>
+            </div>
+            
+            <!-- 高优先级需求 -->
+            <div *ngIf="project.highPriorityNeeds && project.highPriorityNeeds.length > 0" class="needs-section">
+              <span class="needs-label">高优先级需求:</span>
+              <ul class="needs-list">
+                <li *ngFor="let need of project.highPriorityNeeds">
+                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <circle cx="12" cy="12" r="10"></circle>
+                    <line x1="12" y1="8" x2="12" y2="12"></line>
+                    <line x1="12" y1="16" x2="12.01" y2="16"></line>
+                  </svg>
+                  {{ need }}
+                </li>
+              </ul>
+            </div>
+          </div>
+          
+          <div class="card-footer">
+            <button class="secondary-btn card-action">
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
+              </svg>
+              <span>联系</span>
+            </button>
+            <a [routerLink]="['/customer-service/project-detail', project.id]" class="primary-btn card-action">
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
+              </svg>
+              <span>查看详情</span>
+            </a>
+          </div>
+        </div>
+      </div>
+
+      <!-- 分页控件 -->
+      <div class="pagination" *ngIf="totalPages() > 1">
+        <button 
+          class="pagination-btn" 
+          (click)="prevPage()" 
+          [disabled]="currentPage() === 1"
+        >
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <line x1="15" y1="18" x2="9" y2="12"></line>
+            <line x1="9" y1="18" x2="15" y2="12"></line>
+          </svg>
+        </button>
+        
+        <button 
+          *ngFor="let page of pageNumbers()"
+          class="pagination-btn" 
+          [class.active]="page === currentPage()"
+          (click)="goToPage(page)"
+        >
+          {{ page }}
+        </button>
+        
+        <!-- 省略号 -->
+        <span *ngIf="totalPages() > 5" class="pagination-ellipsis">...</span>
+        
+        <button 
+          *ngIf="totalPages() > 5" 
+          class="pagination-btn" 
+          (click)="goToPage(totalPages())"
+        >
+          {{ totalPages() }}
+        </button>
+        
+        <button 
+          class="pagination-btn" 
+          (click)="nextPage()" 
+          [disabled]="currentPage() === totalPages()"
+        >
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <line x1="9" y1="18" x2="15" y2="12"></line>
+            <line x1="15" y1="6" x2="9" y2="12"></line>
+          </svg>
+        </button>
+      </div>
+    </div>
+  </main>
+</div>

+ 793 - 0
src/app/pages/customer-service/project-list/project-list.scss

@@ -0,0 +1,793 @@
+// 全局变量
+$primary-color: #1976d2;
+$primary-light: #e3f2fd;
+$secondary-color: #6c757d;
+$success-color: #4caf50;
+$warning-color: #ff9800;
+$danger-color: #f44336;
+$text-primary: #333;
+$text-secondary: #666;
+$text-light: #999;
+$bg-light: #f5f7fa;
+$bg-white: #fff;
+$border-color: #e1e5e9;
+$box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+$transition: all 0.3s ease;
+
+// 主容器
+.project-list-container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  overflow: hidden;
+  background-color: $bg-light;
+}
+
+// 顶部导航栏
+.top-navbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 24px;
+  height: 64px;
+  background-color: $bg-white;
+  border-bottom: 1px solid $border-color;
+  box-shadow: $box-shadow;
+
+  .navbar-left {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+
+    .menu-toggle {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 40px;
+      height: 40px;
+      border: none;
+      background: none;
+      color: $text-primary;
+      cursor: pointer;
+      transition: $transition;
+
+      &:hover {
+        color: $primary-color;
+        background-color: $bg-light;
+        border-radius: 4px;
+      }
+    }
+
+    .app-title {
+      font-size: 18px;
+      font-weight: 600;
+      color: $text-primary;
+      margin: 0;
+    }
+  }
+
+  .navbar-right {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+
+    .notification-btn {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 40px;
+      height: 40px;
+      border: none;
+      background: none;
+      color: $text-primary;
+      cursor: pointer;
+      position: relative;
+      transition: $transition;
+
+      &:hover {
+        color: $primary-color;
+        background-color: $bg-light;
+        border-radius: 4px;
+      }
+
+      .notification-badge {
+        position: absolute;
+        top: 8px;
+        right: 8px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 16px;
+        height: 16px;
+        font-size: 10px;
+        font-weight: 600;
+        color: white;
+        background-color: $danger-color;
+        border-radius: 50%;
+      }
+    }
+
+    .user-profile {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      padding: 8px 12px;
+      border-radius: 6px;
+      transition: $transition;
+
+      &:hover {
+        background-color: $bg-light;
+      }
+
+      .user-avatar {
+        width: 32px;
+        height: 32px;
+        border-radius: 50%;
+        object-fit: cover;
+      }
+
+      .user-name {
+        font-size: 14px;
+        font-weight: 500;
+        color: $text-primary;
+      }
+    }
+  }
+}
+
+// 主要内容区
+.dashboard-content {
+  display: flex;
+  flex: 1;
+  overflow: hidden;
+}
+
+// 左侧侧边栏
+.sidebar {
+  width: 240px;
+  background-color: $bg-white;
+  border-right: 1px solid $border-color;
+  overflow-y: auto;
+  flex-shrink: 0;
+
+  .sidebar-nav {
+    padding: 16px 0;
+
+    .nav-item {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 12px 24px;
+      color: $text-secondary;
+      text-decoration: none;
+      transition: $transition;
+
+      &:hover {
+        color: $primary-color;
+        background-color: $bg-light;
+      }
+
+      &.active {
+        color: $primary-color;
+        background-color: #e3f2fd;
+        border-left: 3px solid $primary-color;
+        padding-left: 21px;
+      }
+
+      svg {
+        flex-shrink: 0;
+      }
+
+      span {
+        font-size: 14px;
+        font-weight: 500;
+      }
+    }
+  }
+}
+
+// 右侧主要内容
+.project-content {
+  flex: 1;
+  overflow-y: auto;
+  padding: 24px;
+
+  // 页面标题和操作
+  .page-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 24px;
+
+    h2 {
+      font-size: 24px;
+      font-weight: 600;
+      color: $text-primary;
+      margin: 0;
+    }
+
+    .header-actions {
+      .search-container {
+        .search-box {
+          display: flex;
+          align-items: center;
+          width: 320px;
+          border: 1px solid $border-color;
+          border-radius: 6px;
+          overflow: hidden;
+          transition: $transition;
+
+          &:focus-within {
+            border-color: $primary-color;
+            box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.1);
+          }
+
+          svg:first-child {
+            margin-left: 12px;
+            color: $text-light;
+          }
+
+          input {
+            flex: 1;
+            padding: 8px 12px;
+            border: none;
+            outline: none;
+            font-size: 14px;
+            color: $text-primary;
+            background-color: $bg-white;
+          }
+
+          .search-btn {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 40px;
+            height: 100%;
+            border: none;
+            color: $primary-color;
+            background-color: $primary-light;
+            cursor: pointer;
+            transition: $transition;
+
+            &:hover {
+              background-color: $primary-color;
+              color: white;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // 筛选和排序工具栏
+  .filter-toolbar {
+    display: flex;
+    align-items: center;
+    gap: 24px;
+    padding: 16px 20px;
+    margin-bottom: 24px;
+    background-color: $bg-white;
+    border-radius: 8px;
+    box-shadow: $box-shadow;
+
+    .filter-group {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      label {
+        font-size: 14px;
+        font-weight: 500;
+        color: $text-secondary;
+        white-space: nowrap;
+      }
+
+      select {
+        padding: 6px 12px;
+        border: 1px solid $border-color;
+        border-radius: 4px;
+        font-size: 14px;
+        color: $text-primary;
+        background-color: $bg-white;
+        cursor: pointer;
+        transition: $transition;
+
+        &:focus {
+          outline: none;
+          border-color: $primary-color;
+          box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.1);
+        }
+
+        &:hover {
+          border-color: $primary-color;
+        }
+      }
+    }
+
+    .filter-results {
+      margin-left: auto;
+      font-size: 14px;
+      color: $text-secondary;
+    }
+  }
+
+  // 项目网格布局
+  .project-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
+    gap: 20px;
+    margin-bottom: 24px;
+
+    // 项目卡片
+    .project-card {
+      display: flex;
+      flex-direction: column;
+      padding: 20px;
+      background-color: $bg-white;
+      border-radius: 8px;
+      box-shadow: $box-shadow;
+      transition: $transition;
+      border: 1px solid transparent;
+
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
+        border-color: $primary-light;
+      }
+
+      // 卡片头部
+      .card-header {
+        margin-bottom: 16px;
+
+        .card-title-section {
+          display: flex;
+          justify-content: space-between;
+          align-items: flex-start;
+          margin-bottom: 8px;
+
+          .project-name {
+            font-size: 16px;
+            font-weight: 600;
+            color: $text-primary;
+            margin: 0;
+            flex: 1;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+          }
+
+          .project-id {
+            font-size: 12px;
+            color: $text-light;
+            margin-left: 8px;
+          }
+        }
+
+        .card-tags {
+          display: flex;
+          gap: 8px;
+          flex-wrap: wrap;
+
+          .project-tag {
+            padding: 4px 12px;
+            font-size: 12px;
+            font-weight: 500;
+            color: $primary-color;
+            background-color: $primary-light;
+            border-radius: 16px;
+          }
+
+          .urgent-tag {
+            padding: 4px 12px;
+            font-size: 12px;
+            font-weight: 500;
+            color: $danger-color;
+            background-color: #ffebee;
+            border-radius: 16px;
+          }
+        }
+      }
+
+      // 卡片内容
+      .card-content {
+        flex: 1;
+
+        // 信息项
+        .info-item {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          margin-bottom: 12px;
+
+          .info-label {
+            font-size: 13px;
+            color: $text-light;
+          }
+
+          .info-value {
+            font-size: 14px;
+            font-weight: 500;
+            color: $text-primary;
+          }
+
+          .status-badge {
+            padding: 4px 12px;
+            border-radius: 16px;
+            font-size: 12px;
+            font-weight: 500;
+          }
+
+          .stage-badge {
+            padding: 4px 12px;
+            border-radius: 16px;
+            font-size: 12px;
+            font-weight: 500;
+          }
+
+          // 状态样式
+          .status-active {
+            color: $primary-color;
+            background-color: $primary-light;
+          }
+
+          .status-completed {
+            color: $success-color;
+            background-color: #e8f5e9;
+          }
+
+          .status-paused {
+            color: $secondary-color;
+            background-color: #f5f5f5;
+          }
+
+          .status-overdue {
+            color: $danger-color;
+            background-color: #ffebee;
+          }
+
+          // 阶段样式
+          .stage-communication {
+            color: #9c27b0;
+            background-color: #f3e5f5;
+          }
+
+          .stage-modeling {
+            color: #2196f3;
+            background-color: #e3f2fd;
+          }
+
+          .stage-decoration {
+            color: #009688;
+            background-color: #e0f2f1;
+          }
+
+          .stage-rendering {
+            color: #ff5722;
+            background-color: #fbe9e7;
+          }
+
+          .stage-postproduction {
+            color: #ff9800;
+            background-color: #fff3e0;
+          }
+
+          .stage-completed {
+            color: $success-color;
+            background-color: #e8f5e9;
+          }
+        }
+
+        // 进度部分
+        .progress-section {
+          margin-bottom: 16px;
+
+          .progress-header {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: 8px;
+
+            .progress-label {
+              font-size: 13px;
+              color: $text-light;
+            }
+
+            .progress-percentage {
+              font-size: 14px;
+              font-weight: 600;
+              color: $primary-color;
+            }
+          }
+
+          .progress-bar {
+            height: 6px;
+            background-color: $bg-light;
+            border-radius: 3px;
+            overflow: hidden;
+
+            .progress-fill {
+              height: 100%;
+              border-radius: 3px;
+              transition: width 0.6s ease;
+            }
+          }
+        }
+
+        // 时间信息
+        .timeline-info {
+          margin-bottom: 16px;
+
+          .time-item {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: 8px;
+
+            .time-label {
+              font-size: 13px;
+              color: $text-light;
+            }
+
+            .time-value {
+              font-size: 13px;
+              color: $text-secondary;
+            }
+
+            .deadline {
+              &.overdue {
+                color: $danger-color;
+                font-weight: 500;
+              }
+
+              &.urgent {
+                color: $warning-color;
+                font-weight: 500;
+              }
+            }
+          }
+        }
+
+        // 高优先级需求
+        .needs-section {
+          padding-top: 12px;
+          border-top: 1px solid $border-color;
+
+          .needs-label {
+            display: block;
+            font-size: 13px;
+            color: $text-light;
+            margin-bottom: 8px;
+          }
+
+          .needs-list {
+            margin: 0;
+            padding: 0;
+            list-style: none;
+
+            li {
+              display: flex;
+              align-items: center;
+              gap: 6px;
+              font-size: 13px;
+              color: $warning-color;
+              margin-bottom: 4px;
+
+              svg {
+                flex-shrink: 0;
+                color: $warning-color;
+              }
+            }
+          }
+        }
+      }
+
+      // 卡片底部
+      .card-footer {
+        display: flex;
+        gap: 12px;
+        margin-top: 16px;
+        padding-top: 16px;
+        border-top: 1px solid $border-color;
+
+        .secondary-btn,
+        .primary-btn {
+          display: flex;
+          align-items: center;
+          gap: 6px;
+          padding: 8px 16px;
+          border: none;
+          border-radius: 6px;
+          font-size: 14px;
+          font-weight: 500;
+          cursor: pointer;
+          transition: $transition;
+        }
+
+        .primary-btn {
+          flex: 1;
+          color: white;
+          background-color: $primary-color;
+
+          &:hover {
+            background-color: #1565c0;
+          }
+        }
+
+        .secondary-btn {
+          flex: 1;
+          color: $text-primary;
+          background-color: $bg-light;
+
+          &:hover {
+            background-color: $border-color;
+          }
+        }
+
+        .card-action {
+          justify-content: center;
+        }
+      }
+    }
+  }
+
+  // 分页控件
+  .pagination {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    gap: 8px;
+    padding: 20px;
+    background-color: $bg-white;
+    border-radius: 8px;
+    box-shadow: $box-shadow;
+
+    .pagination-btn {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 36px;
+      height: 36px;
+      border: 1px solid $border-color;
+      background-color: $bg-white;
+      color: $text-secondary;
+      border-radius: 4px;
+      cursor: pointer;
+      transition: $transition;
+      font-size: 14px;
+      font-weight: 500;
+
+      &:hover:not(:disabled) {
+        color: $primary-color;
+        border-color: $primary-color;
+      }
+
+      &.active {
+        color: white;
+        background-color: $primary-color;
+        border-color: $primary-color;
+      }
+
+      &:disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+      }
+    }
+
+    .pagination-ellipsis {
+      font-size: 14px;
+      color: $text-light;
+      padding: 0 8px;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .project-grid {
+    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)) !important;
+  }
+}
+
+@media (max-width: 1024px) {
+  .sidebar {
+    width: 200px;
+  }
+
+  .project-content {
+    padding: 16px;
+  }
+
+  .filter-toolbar {
+    flex-wrap: wrap;
+  }
+
+  .page-header {
+    flex-direction: column;
+    align-items: flex-start !important;
+    gap: 16px;
+  }
+
+  .header-actions {
+    width: 100%;
+  }
+
+  .search-box {
+    width: 100% !important;
+  }
+}
+
+@media (max-width: 768px) {
+  .top-navbar {
+    padding: 0 16px;
+
+    .navbar-right {
+      gap: 8px;
+    }
+
+    .user-name {
+      display: none;
+    }
+  }
+
+  .sidebar {
+    display: none;
+  }
+
+  .filter-toolbar {
+    gap: 16px;
+    padding: 12px 16px;
+
+    .filter-group {
+      flex: 1 1 calc(50% - 8px);
+    }
+
+    .filter-results {
+      width: 100%;
+      margin-left: 0;
+      text-align: center;
+    }
+  }
+
+  .project-grid {
+    grid-template-columns: 1fr !important;
+    gap: 16px !important;
+  }
+
+  .pagination {
+    padding: 16px;
+  }
+
+  .pagination-btn {
+    width: 32px !important;
+    height: 32px !important;
+    font-size: 13px !important;
+  }
+}
+
+// 动画效果
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.project-card {
+  animation: fadeIn 0.4s ease-out;
+}
+
+.project-card:nth-child(2n) {
+  animation-delay: 0.1s;
+}
+
+.project-card:nth-child(3n) {
+  animation-delay: 0.2s;
+}
+
+.project-card:nth-child(4n) {
+  animation-delay: 0.3s;
+}

+ 370 - 3
src/app/pages/customer-service/project-list/project-list.ts

@@ -1,11 +1,378 @@
-import { Component } from '@angular/core';
+import { Component, OnInit, signal, computed } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { RouterModule } from '@angular/router';
+import { ProjectService } from '../../../services/project.service';
+import { Project, ProjectStatus, ProjectStage } from '../../../models/project.model';
+
+// 定义项目列表项接口,包含计算后的属性
+interface ProjectListItem extends Project {
+  progress: number;
+  daysUntilDeadline: number;
+  isUrgent: boolean;
+  tagDisplayText: string;
+}
 
 @Component({
   selector: 'app-project-list',
-  imports: [],
+  standalone: true,
+  imports: [CommonModule, FormsModule, RouterModule],
   templateUrl: './project-list.html',
   styleUrl: './project-list.scss'
 })
-export class ProjectList {
+export class ProjectList implements OnInit {
+  // 项目列表数据
+  projects = signal<ProjectListItem[]>([]);
+  
+  // 原始项目数据(用于筛选)
+  allProjects = signal<Project[]>([]);
+
+  // 添加toggleSidebar方法
+  toggleSidebar(): void {
+    // 侧边栏切换逻辑
+    console.log('Toggle sidebar');
+  }
+  
+  // 筛选和排序状态
+  searchTerm = signal('');
+  statusFilter = signal<string>('all');
+  stageFilter = signal<string>('all');
+  sortBy = signal<string>('deadline');
+  
+  // 当前页码
+  currentPage = signal(1);
+  
+  // 每页显示数量
+  pageSize = 8;
+  
+  // 分页后的项目列表
+  paginatedProjects = computed(() => {
+    const filteredProjects = this.projects();
+    const startIndex = (this.currentPage() - 1) * this.pageSize;
+    return filteredProjects.slice(startIndex, startIndex + this.pageSize);
+  });
+  
+  // 总页数
+  totalPages = computed(() => {
+    return Math.ceil(this.projects().length / this.pageSize);
+  });
+  
+  // 筛选和排序选项
+  statusOptions = [
+    { value: 'all', label: '全部状态' },
+    { value: '进行中', label: '进行中' },
+    { value: '已完成', label: '已完成' },
+    { value: '已暂停', label: '已暂停' },
+    { value: '已延期', label: '已延期' }
+  ];
+  
+  stageOptions = [
+    { value: 'all', label: '全部阶段' },
+    { value: '前期沟通', label: '前期沟通' },
+    { value: '建模', label: '建模' },
+    { value: '软装', label: '软装' },
+    { value: '渲染', label: '渲染' },
+    { value: '后期', label: '后期' },
+    { value: '完成', label: '完成' }
+  ];
+  
+  sortOptions = [
+    { value: 'deadline', label: '截止日期' },
+    { value: 'createdAt', label: '创建时间' },
+    { value: 'name', label: '项目名称' }
+  ];
+  
+  constructor(private projectService: ProjectService) {}
+  
+  ngOnInit(): void {
+    this.loadProjects();
+  }
+  
+  // 加载项目列表
+  loadProjects(): void {
+    this.projectService.getProjects().subscribe(projects => {
+      this.allProjects.set(projects);
+      // 添加模拟数据以丰富列表
+      const enrichedProjects = [...projects, ...this.generateMockProjects()];
+      this.processProjects(enrichedProjects);
+    });
+  }
+  
+  // 处理项目数据,添加计算属性
+  processProjects(projects: Project[]): void {
+    const processedProjects = projects.map(project => {
+      // 计算项目进度(模拟)
+      const progress = this.calculateProjectProgress(project);
+      
+      // 计算距离截止日期的天数
+      const daysUntilDeadline = this.calculateDaysUntilDeadline(project.deadline);
+      
+      // 判断是否紧急(截止日期前3天或已逾期)
+      const isUrgent = daysUntilDeadline <= 3 && project.status === '进行中';
+      
+      // 生成标签显示文本
+      const tagDisplayText = this.generateTagDisplayText(project);
+      
+      return {
+        ...project,
+        progress,
+        daysUntilDeadline,
+        isUrgent,
+        tagDisplayText
+      };
+    });
+    
+    this.projects.set(this.applyFiltersAndSorting(processedProjects));
+  }
+  
+  // 应用筛选和排序
+  applyFiltersAndSorting(projects: ProjectListItem[]): ProjectListItem[] {
+    let filteredProjects = [...projects];
+    
+    // 搜索筛选
+    if (this.searchTerm().trim()) {
+      const searchLower = this.searchTerm().toLowerCase().trim();
+      filteredProjects = filteredProjects.filter(project => 
+        project.name.toLowerCase().includes(searchLower) ||
+        project.customerName.toLowerCase().includes(searchLower)
+      );
+    }
+    
+    // 状态筛选
+    if (this.statusFilter() !== 'all') {
+      filteredProjects = filteredProjects.filter(project => 
+        project.status === this.statusFilter()
+      );
+    }
+    
+    // 阶段筛选
+    if (this.stageFilter() !== 'all') {
+      filteredProjects = filteredProjects.filter(project => 
+        project.currentStage === this.stageFilter()
+      );
+    }
+    
+    // 排序
+    filteredProjects.sort((a, b) => {
+      switch (this.sortBy()) {
+        case 'deadline':
+          return new Date(a.deadline).getTime() - new Date(b.deadline).getTime();
+        case 'createdAt':
+          return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
+        case 'name':
+          return a.name.localeCompare(b.name);
+        default:
+          return 0;
+      }
+    });
+    
+    return filteredProjects;
+  }
+  
+  // 生成标签显示文本
+  generateTagDisplayText(project: Project): string {
+    if (!project.customerTags || project.customerTags.length === 0) {
+      return '普通项目';
+    }
+    
+    const tag = project.customerTags[0];
+    return `${tag.preference}${tag.needType}`;
+  }
+  
+  // 计算项目进度(模拟)
+  calculateProjectProgress(project: Project): number {
+    if (project.status === '已完成') return 100;
+    if (project.status === '已暂停' || project.status === '已延期') return 0;
+    
+    // 基于当前阶段计算进度
+    const stageProgress: Record<ProjectStage, number> = {
+      '前期沟通': 20,
+      '建模': 40,
+      '软装': 60,
+      '渲染': 80,
+      '后期': 90,
+      '完成': 100
+    };
+    
+    return stageProgress[project.currentStage] || 0;
+  }
+  
+  // 计算距离截止日期的天数
+  calculateDaysUntilDeadline(deadline: Date): number {
+    const now = new Date();
+    const deadlineDate = new Date(deadline);
+    const diffTime = deadlineDate.getTime() - now.getTime();
+    return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+  }
+  
+  // 生成模拟项目数据
+  generateMockProjects(): Project[] {
+    const statuses: ProjectStatus[] = ['进行中', '已完成', '已暂停', '已延期'];
+    const stages: ProjectStage[] = ['前期沟通', '建模', '软装', '渲染', '后期', '完成'];
+    const preferences: ('现代' | '宋式' | '欧式')[] = ['现代', '宋式', '欧式'];
+    const needTypes: ('硬装' | '软装')[] = ['硬装', '软装'];
+    const sources: ('朋友圈' | '信息流')[] = ['朋友圈', '信息流'];
+    
+    const mockProjects: Project[] = [];
+    
+    for (let i = 1; i <= 12; i++) {
+      const baseDate = new Date();
+      const createdDate = new Date(baseDate.setDate(baseDate.getDate() - Math.floor(Math.random() * 30)));
+      baseDate.setDate(baseDate.getDate() + Math.floor(Math.random() * 15) + 5);
+      const deadlineDate = new Date(baseDate);
+      
+      const preference = preferences[Math.floor(Math.random() * preferences.length)];
+      const needType = needTypes[Math.floor(Math.random() * needTypes.length)];
+      const source = sources[Math.floor(Math.random() * sources.length)];
+      const stage = stages[Math.floor(Math.random() * stages.length)];
+      const status = stage === '完成' ? '已完成' : statuses[Math.floor(Math.random() * 3)];
+      
+      mockProjects.push({
+        id: `mock-${i}`,
+        name: `${preference}风格${needType === '硬装' ? '全屋' : '客厅'}设计`,
+        customerName: `客户${String.fromCharCode(64 + i)}`,
+        customerTags: [
+          {
+            source: source,
+            needType: needType,
+            preference: preference,
+            colorAtmosphere: i % 2 === 0 ? '简约明亮' : '温馨舒适'
+          }
+        ],
+        highPriorityNeeds: i % 3 === 0 ? ['需快速交付', '重要客户'] : [],
+        status: status,
+        currentStage: stage,
+        createdAt: createdDate,
+        deadline: deadlineDate,
+        assigneeId: `designer${i % 3 + 1}`,
+        assigneeName: `设计师${String.fromCharCode(64 + (i % 3 + 1))}`,
+        skillsRequired: [preference + '风格', needType]
+      });
+    }
+    
+    return mockProjects;
+  }
+  
+  // 处理搜索
+  onSearch(): void {
+    this.currentPage.set(1);
+    this.processProjects(this.allProjects());
+  }
+  
+  // 处理状态筛选
+  onStatusChange(event: Event): void {
+    const selectElement = event.target as HTMLSelectElement;
+    this.statusFilter.set(selectElement.value);
+    this.currentPage.set(1);
+    this.processProjects(this.allProjects());
+  }
+  
+  // 处理阶段筛选
+  onStageChange(event: Event): void {
+    const selectElement = event.target as HTMLSelectElement;
+    this.stageFilter.set(selectElement.value);
+    this.currentPage.set(1);
+    this.processProjects(this.allProjects());
+  }
+  
+  // 处理排序变化
+  onSortChange(event: Event): void {
+    const selectElement = event.target as HTMLSelectElement;
+    this.sortBy.set(selectElement.value);
+    this.processProjects(this.allProjects());
+  }
+  
+  // 分页导航
+  goToPage(page: number): void {
+    if (page >= 1 && page <= this.totalPages() && page !== this.currentPage()) {
+      this.currentPage.set(page);
+      this.loadProjects();
+    }
+  }
+
+  prevPage(): void {
+    if (this.currentPage() > 1) {
+      this.goToPage(this.currentPage() - 1);
+    }
+  }
+
+  nextPage(): void {
+    if (this.currentPage() < this.totalPages()) {
+      this.goToPage(this.currentPage() + 1);
+    }
+  }
+
+  // 计算当前显示的页码数组
+  pageNumbers = computed(() => {
+    const maxVisible = 5;
+    const total = this.totalPages();
+    const current = this.currentPage();
+    const pages: number[] = [];
+
+    if (total <= maxVisible) {
+      // 如果总页数不超过最大可见页数,显示所有页码
+      for (let i = 1; i <= total; i++) {
+        pages.push(i);
+      }
+    } else {
+      // 处理中间页码显示
+      if (current <= Math.ceil(maxVisible / 2)) {
+        // 当前页在前面部分
+        for (let i = 1; i <= maxVisible; i++) {
+          pages.push(i);
+        }
+      } else if (current >= total - Math.floor(maxVisible / 2)) {
+        // 当前页在后面部分
+        for (let i = total - (maxVisible - 1); i <= total; i++) {
+          pages.push(i);
+        }
+      } else {
+        // 当前页在中间部分
+        for (let i = current - Math.floor(maxVisible / 2); i <= current + Math.floor(maxVisible / 2); i++) {
+          pages.push(i);
+        }
+      }
+    }
+    return pages;
+  });
 
+  // 计算绝对值的辅助方法(用于模板中)
+  getAbsValue(value: number): number {
+    return Math.abs(value);
+  }
+  
+  // 格式化日期
+  formatDate(date: Date): string {
+    return new Date(date).toLocaleDateString('zh-CN', {
+      year: 'numeric',
+      month: '2-digit',
+      day: '2-digit'
+    });
+  }
+  
+  // 获取状态样式类
+  getStatusClass(status: string): string {
+    const statusClasses: Record<string, string> = {
+      '进行中': 'status-active',
+      '已完成': 'status-completed',
+      '已暂停': 'status-paused',
+      '已延期': 'status-overdue'
+    };
+    
+    return statusClasses[status] || '';
+  }
+  
+  // 获取阶段样式类
+  getStageClass(stage: string): string {
+    const stageClasses: Record<string, string> = {
+      '前期沟通': 'stage-communication',
+      '建模': 'stage-modeling',
+      '软装': 'stage-decoration',
+      '渲染': 'stage-rendering',
+      '后期': 'stage-postproduction',
+      '完成': 'stage-completed'
+    };
+    
+    return stageClasses[stage] || '';
+  }
 }

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

@@ -79,7 +79,10 @@ export class ProjectService {
       stage: '建模',
       deadline: new Date('2025-09-10'),
       isOverdue: false,
-      isCompleted: false
+      isCompleted: false,
+      priority: 'high',
+      assignee: '设计师A',
+      description: '根据客户需求完成客厅3D模型构建'
     },
     {
       id: 't2',
@@ -89,7 +92,10 @@ export class ProjectService {
       stage: '渲染',
       deadline: new Date('2025-09-12'),
       isOverdue: false,
-      isCompleted: false
+      isCompleted: false,
+      priority: 'medium',
+      assignee: '设计师B',
+      description: '审核渲染效果并与客户确认'
     },
     {
       id: 't3',
@@ -99,7 +105,10 @@ export class ProjectService {
       stage: '后期',
       deadline: new Date('2025-09-08'),
       isOverdue: true,
-      isCompleted: false
+      isCompleted: false,
+      priority: 'high',
+      assignee: '客服',
+      description: '整理客户反馈意见并传达给设计团队'
     }
   ];
 
@@ -226,7 +235,7 @@ export class ProjectService {
     const task = this.tasks.find(t => t.id === taskId);
     if (task) {
       task.isCompleted = true;
-      task.completedAt = new Date();
+      task.completedDate = new Date();
     }
     return of(task as Task);
   }