Browse Source

Merge branch 'master' of http://git.fmode.cn:3000/nkkj/yss-project

0235711 8 hours ago
parent
commit
5cecb23882

+ 1 - 104
src/app/app.html

@@ -1,112 +1,9 @@
 <div class="app-container">
-  <!-- 根据当前路由显示不同的导航 -->
-@if (isDesignerRoute) {
-  <!-- 设计师导航 -->
-  <app-designer-nav userName="设计师用户" [onlineHours]="4.8">
-    <router-outlet></router-outlet>
-  </app-designer-nav>
-} @else {
-  <!-- 客服导航 -->
-  <!-- 顶部导航栏 -->
-  <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/823/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">
+    <div class="content-wrapper">
       <router-outlet />
     </div>
   </main>
-}
 </div>

+ 1 - 1
src/app/app.routes.ts

@@ -106,4 +106,4 @@ export const routes: Routes = [
   // 默认路由重定向到客服工作台
   { path: '', redirectTo: '/customer-service/dashboard', pathMatch: 'full' },
   { path: '**', redirectTo: '/customer-service/dashboard' }
-];
+];

+ 4 - 32
src/app/app.ts

@@ -1,41 +1,13 @@
 import { Component, signal } from '@angular/core';
-import { OnInit } from '@angular/core';
-import { Router, RouterOutlet, RouterLinkActive } from '@angular/router';
-import { FormsModule } from '@angular/forms';
-import { DesignerNavComponent } from './shared/components/designer-nav/designer-nav';
+import { RouterOutlet } from '@angular/router';
 
 @Component({
   selector: 'app-root',
   standalone: true,
-  imports: [RouterOutlet, RouterLinkActive, FormsModule, DesignerNavComponent],
+  imports: [RouterOutlet],
   templateUrl: './app.html',
   styleUrl: './app.scss'
 })
-export class App implements OnInit {
+export class App {
   protected readonly title = signal('yss-project');
-  sidebarOpen = true;
-  searchTerm = '';
-  currentDate = new Date();
-  isDesignerRoute = false;
-
-  constructor(private router: Router) {}
-
-  ngOnInit() {
-    // 初始化时检查当前路由
-    this.checkIfDesignerRoute();
-    
-    // 监听路由变化
-    this.router.events.subscribe(() => {
-      this.checkIfDesignerRoute();
-    });
-  }
-
-  checkIfDesignerRoute() {
-    // 检查当前路径是否包含'designer'
-    this.isDesignerRoute = this.router.url.includes('designer');
-  }
-
-  toggleSidebar() {
-    this.sidebarOpen = !this.sidebarOpen;
-  }
-}
+}

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

@@ -305,4 +305,4 @@
       </div>
     </div>
   </div>
-</div>
+</div>

+ 40 - 28
src/app/pages/customer-service/case-library/case-library.ts

@@ -237,35 +237,35 @@ export class CaseLibrary implements OnInit {
     });
   }
   // 智能页码生成
-  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);
+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表示省略号
     }
-    
-    if (end < total) {
-      if (end < total - 1) {
-        pages.push(-1); // 用-1表示省略号
-      }
-      pages.push(total);
+  }
+  
+  for (let i = start; i <= end; i++) {
+    pages.push(i);
+  }
+  
+  if (end < total) {
+    if (end < total - 1) {
+      pages.push(-1); // 用-1表示省略号
     }
-    
-    return pages;
-  });
+    pages.push(total);
+  }
+  
+  return pages;
+});
   // 格式化样式显示的辅助方法
   getStyleDisplay(caseItem: CaseItem | null | undefined): string {
     if (!caseItem || !caseItem.style) {
@@ -279,6 +279,18 @@ export class CaseLibrary implements OnInit {
     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[];
@@ -294,4 +306,4 @@ export class CaseLibrary implements OnInit {
     
     this.filterForm.patchValue({ style: updatedStyles });
   }
-}
+}

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

@@ -271,4 +271,4 @@ export class ConsultationOrder {
     console.log('创建项目群');
     alert('项目群已创建,并邀请了相应技术组长!');
   }
-}
+}

+ 10 - 0
src/app/pages/customer-service/customer-service-layout/customer-service-layout.html

@@ -70,6 +70,16 @@
         </svg>
         <span>项目列表</span>
       </a>
+      <a href="/customer-service/project-detail/1" class="nav-item" routerLinkActive="active">
+        <svg width="20" height="20" 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>
+      </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>

+ 1 - 1
src/app/pages/customer-service/dashboard/dashboard.ts

@@ -215,4 +215,4 @@ getUpdateStatus(update: Project | CustomerFeedback): string {
         return '';
     }
   }
-}
+}

+ 211 - 23
src/app/pages/customer-service/project-detail/project-detail.html

@@ -1,38 +1,132 @@
 <!-- 项目详情页面内容 -->
 <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>
+  <!-- 顶部导航/Header -->
+  <header class="project-header-blue">
+    <div class="header-content">
+      <div class="project-info">
+        <h1 class="project-title">{{ project()?.name || '现代简约风格三居室设计' }}</h1>
+        <div class="project-meta">
+          <span class="project-status {{ getProjectStatusClass(project()?.status) }}">
+            {{ project()?.status || '进行中' }}
+          </span>
+          <span class="project-stage">当前阶段:{{ project()?.currentStage || '方案修改与确认' }}</span>
+          <span class="project-date">最后更新:{{ formatDate(currentDate()) }}</span>
+        </div>
+      </div>
+      <div class="header-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>
+  </header>
+
+  <!-- 主要内容区域 -->
+  <div class="main-content-area">
+    <!-- 主内容区 (居中) -->
+    <div class="project-content-main">
+      <!-- 项目进度卡片 -->
+      <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="timeline-card">
+        <h3 class="card-title">项目阶段时间轴</h3>
+        <div class="project-timeline">
+          <div *ngFor="let stage of projectStages; index as i" class="timeline-item" [class.stage-completed]="stage.completed" [class.stage-in-progress]="stage.inProgress">
+            <div class="timeline-icon" [class.icon-completed]="stage.completed" [class.icon-in-progress]="stage.inProgress">
+              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                <circle cx="12" cy="12" r="9"></circle>
+                <path *ngIf="stage.completed" d="m5 12 5 5 10-10" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
+                <circle *ngIf="stage.inProgress" cx="12" cy="12" r="5"></circle>
+              </svg>
+            </div>
+            <div class="timeline-line" *ngIf="i < projectStages.length - 1" [class.line-completed]="stage.completed && projectStages[i+1].completed"></div>
+            <div class="timeline-content">
+              <div class="timeline-header">
+                <h4 class="stage-title">{{ stage.name }}</h4>
+                <span class="stage-status">
+                  {{ stage.completed ? '已完成' : stage.inProgress ? '进行中' : '未开始' }}
+                </span>
+              </div>
+              <div class="timeline-meta">
+                <div class="stage-dates">
+                  <span *ngIf="stage.startDate" class="date-item">开始:{{ formatDate(stage.startDate) }}</span>
+                  <span *ngIf="stage.endDate" class="date-item">完成:{{ formatDate(stage.endDate) }}</span>
+                </div>
+                <div class="stage-responsible">负责人:{{ stage.responsible || '未分配' }}</div>
+              </div>
+              <div class="stage-details" *ngIf="stage.details">
+                <p>{{ stage.details }}</p>
+              </div>
+            </div>
           </div>
         </div>
-        <div class="project-actions">
-          <button class="secondary-btn">
-            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+      </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>
-          <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>
+            <span>文件</span>
           </button>
         </div>
-      </div>
 
       <!-- 项目进度卡片 -->
       <div class="progress-card">
@@ -438,4 +532,98 @@
         </div>
       </div>
     </div>
+
+    <!-- 右侧边栏 - 企业微信聊天集成 -->
+    <div class="wechat-sidebar">
+      <div class="wechat-header">
+        <h3>项目群聊</h3>
+        <div class="wechat-actions">
+          <button class="search-btn">
+            <svg width="16" height="16" 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>
+          <button class="settings-btn">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <circle cx="12" cy="12" r="3"></circle>
+              <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
+            </svg>
+          </button>
+        </div>
+      </div>
+      
+      <!-- 聊天消息列表 -->
+      <div class="wechat-messages" #wechatMessages>
+        <div *ngFor="let msg of wechatMessagesList" class="wechat-message-item">
+          <div class="message-avatar">
+            {{ msg.sender.charAt(0) }}
+          </div>
+          <div class="message-content">
+            <div class="message-header">
+              <span class="message-sender">{{ msg.sender }}</span>
+              <span class="message-time">{{ formatTime(msg.timestamp) }}</span>
+            </div>
+            <div class="message-text">{{ msg.content }}</div>
+          </div>
+        </div>
+      </div>
+      
+      <!-- 消息输入框 -->
+      <div class="wechat-input-area">
+        <div class="input-actions">
+          <button class="action-btn">
+            <svg width="18" height="18" 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>
+          </button>
+          <button class="action-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>
+          </button>
+        </div>
+        <input 
+          type="text" 
+          [(ngModel)]="wechatInput" 
+          placeholder="输入消息..." 
+          class="wechat-input"
+          (keydown.enter)="sendWechatMessage()"
+        />
+        <button class="send-btn" (click)="sendWechatMessage()" [disabled]="!wechatInput.trim()">
+          发送
+        </button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 售后处理入口 (固定在底部) -->
+  <div class="after-sales-actions">
+    <div class="actions-container">
+      <button class="action-btn primary" (click)="openModificationRequest()">
+        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <path d="M12 20h9"></path>
+          <path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
+        </svg>
+        <span>申请修改</span>
+      </button>
+      <button class="action-btn warning" (click)="openComplaintWarning()">
+        <svg width="18" height="18" 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>
+        <span>投诉预警</span>
+      </button>
+      <button class="action-btn secondary" (click)="openRefundRequest()">
+        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
+        </svg>
+        <span>申请退款</span>
+      </button>
+    </div>
+  </div>
 </div>

+ 610 - 16
src/app/pages/customer-service/project-detail/project-detail.scss

@@ -1,16 +1,20 @@
 // 全局变量
-$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);
+$primary-color: #165DFF;
+$primary-dark: #0d2f5e;
+$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;
 
 // 主容器
@@ -19,9 +23,599 @@ $transition: all 0.3s ease;
   flex-direction: column;
   height: 100vh;
   overflow: hidden;
-  background-color: $bg-light;
 }
 
+// 顶部导航/Header
+.project-header-blue {
+  background-color: $primary-dark;
+  color: white;
+  padding: 16px 24px;
+  box-shadow: $shadow-md;
+  z-index: 100;
+
+  .header-content {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    max-width: 1400px;
+    margin: 0 auto;
+  }
+
+  .project-info {
+    flex: 1;
+  }
+
+  .project-title {
+    font-size: 24px;
+    font-weight: 600;
+    margin-bottom: 8px;
+  }
+
+  .project-meta {
+    display: flex;
+    gap: 16px;
+    font-size: 14px;
+  }
+
+  .project-status {
+    padding: 4px 8px;
+    border-radius: 4px;
+    font-weight: 500;
+    background-color: rgba(255, 255, 255, 0.2);
+  }
+
+  .project-stage,
+  .project-date {
+    opacity: 0.9;
+  }
+
+  .header-actions {
+    display: flex;
+    gap: 12px;
+  }
+
+  .primary-btn,
+  .secondary-btn {
+    color: white;
+    background-color: rgba(255, 255, 255, 0.2);
+    border: 1px solid rgba(255, 255, 255, 0.3);
+    padding: 8px 16px;
+    border-radius: $border-radius;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    transition: $transition;
+
+    &:hover {
+      background-color: rgba(255, 255, 255, 0.3);
+    }
+  }
+
+  .primary-btn {
+    background-color: $primary-color;
+    border-color: $primary-color;
+
+    &:hover {
+      background-color: #0E42CB;
+    }
+  }
+}
+
+// 主要内容区域
+.main-content-area {
+  display: flex;
+  flex: 1;
+  overflow: hidden;
+}
+
+// 主内容区 (居中)
+.project-content-main {
+  flex: 1;
+  overflow-y: auto;
+  padding: 24px;
+  background-color: $background-secondary;
+}
+
+// 右侧边栏 - 企业微信聊天集成
+.wechat-sidebar {
+  width: 350px;
+  background-color: $background-tertiary;
+  border-left: 1px solid $border-color;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.wechat-header {
+  padding: 16px 20px;
+  border-bottom: 1px solid $border-color;
+  background-color: $background-primary;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.wechat-header h3 {
+  font-size: 16px;
+  font-weight: 600;
+  color: $text-primary;
+}
+
+.wechat-actions {
+  display: flex;
+  gap: 8px;
+}
+
+.search-btn,
+.settings-btn {
+  background: none;
+  border: none;
+  padding: 6px;
+  border-radius: 4px;
+  cursor: pointer;
+  color: $text-secondary;
+  transition: $transition;
+
+  &:hover {
+    background-color: $background-tertiary;
+    color: $primary-color;
+  }
+}
+
+.wechat-messages {
+  flex: 1;
+  overflow-y: auto;
+  padding: 16px;
+}
+
+.wechat-message-item {
+  display: flex;
+  gap: 12px;
+  margin-bottom: 16px;
+}
+
+.message-avatar {
+  width: 36px;
+  height: 36px;
+  border-radius: 50%;
+  background-color: $primary-color;
+  color: white;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-weight: 600;
+  font-size: 14px;
+  flex-shrink: 0;
+}
+
+.message-content {
+  flex: 1;
+}
+
+.message-header {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 4px;
+  font-size: 12px;
+}
+
+.message-sender {
+  font-weight: 600;
+  color: $text-primary;
+}
+
+.message-time {
+  color: $text-tertiary;
+}
+
+.message-text {
+  background-color: $background-primary;
+  padding: 8px 12px;
+  border-radius: $border-radius;
+  color: $text-primary;
+  line-height: 1.5;
+}
+
+.wechat-input-area {
+  padding: 16px;
+  border-top: 1px solid $border-color;
+  background-color: $background-primary;
+  display: flex;
+  gap: 8px;
+  align-items: flex-end;
+}
+
+.input-actions {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.action-btn {
+  background: none;
+  border: none;
+  padding: 8px;
+  border-radius: 4px;
+  cursor: pointer;
+  color: $text-secondary;
+  transition: $transition;
+
+  &:hover {
+    background-color: $background-tertiary;
+    color: $primary-color;
+  }
+}
+
+.wechat-input {
+  flex: 1;
+  padding: 10px 12px;
+  border: 1px solid $border-color;
+  border-radius: $border-radius;
+  outline: none;
+  font-size: 14px;
+  transition: $transition;
+
+  &:focus {
+    border-color: $primary-color;
+  }
+}
+
+.send-btn {
+  padding: 10px 16px;
+  background-color: $primary-color;
+  color: white;
+  border: none;
+  border-radius: $border-radius;
+  cursor: pointer;
+  font-size: 14px;
+  transition: $transition;
+
+  &:hover:not(:disabled) {
+    background-color: $primary-dark;
+  }
+
+  &:disabled {
+    background-color: $text-tertiary;
+    cursor: not-allowed;
+  }
+}
+
+// 售后处理入口 (固定在底部)
+.after-sales-actions {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 350px; /* 与右侧边栏宽度一致 */
+  background-color: $background-primary;
+  border-top: 1px solid $border-color;
+  padding: 16px 24px;
+  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
+  z-index: 100;
+}
+
+.actions-container {
+  max-width: 1000px;
+  margin: 0 auto;
+  display: flex;
+  justify-content: center;
+  gap: 16px;
+}
+
+.action-btn {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 10px 20px;
+  border-radius: $border-radius;
+  border: none;
+  cursor: pointer;
+  font-size: 14px;
+  font-weight: 500;
+  transition: $transition;
+}
+
+.action-btn.primary {
+  background-color: $primary-dark;
+  color: white;
+
+  &:hover {
+    background-color: #0a2340;
+  }
+}
+
+.action-btn.warning {
+  background-color: $warning-color;
+  color: white;
+
+  &:hover {
+    background-color: #e57000;
+  }
+}
+
+.action-btn.secondary {
+  background-color: $background-tertiary;
+  color: $text-primary;
+  border: 1px solid $border-color;
+
+  &:hover {
+    background-color: $background-secondary;
+  }
+}
+
+// 进度卡片
+.progress-card {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  padding: 20px;
+  margin-bottom: 24px;
+  box-shadow: $shadow-sm;
+}
+
+.progress-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12px;
+}
+
+.progress-header h3 {
+  font-size: 16px;
+  font-weight: 600;
+  color: $text-primary;
+}
+
+.progress-percentage {
+  font-size: 24px;
+  font-weight: 600;
+  color: $primary-color;
+}
+
+.progress-bar {
+  height: 8px;
+  background-color: $background-tertiary;
+  border-radius: 4px;
+  overflow: hidden;
+  margin-bottom: 12px;
+}
+
+.progress-fill {
+  height: 100%;
+  background-color: $primary-color;
+  transition: width 0.3s ease;
+}
+
+.progress-meta {
+  display: flex;
+  justify-content: space-between;
+  font-size: 12px;
+  color: $text-tertiary;
+}
+
+// 进度时间轴卡片
+.timeline-card {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  padding: 20px;
+  margin-bottom: 24px;
+  box-shadow: $shadow-sm;
+}
+
+.card-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: $text-primary;
+  margin-bottom: 20px;
+}
+
+.project-timeline {
+  position: relative;
+  padding-left: 30px;
+}
+
+.timeline-item {
+  position: relative;
+  padding-bottom: 24px;
+  transition: $transition;
+
+  &:hover {
+    background-color: $background-tertiary;
+    border-radius: $border-radius;
+  }
+}
+
+.timeline-icon {
+  position: absolute;
+  left: -30px;
+  top: 4px;
+  width: 24px;
+  height: 24px;
+  border-radius: 50%;
+  background-color: $background-tertiary;
+  border: 2px solid $border-color;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: $transition;
+}
+
+.timeline-icon.icon-completed {
+  background-color: $success-color;
+  border-color: $success-color;
+  color: white;
+}
+
+.timeline-icon.icon-in-progress {
+  background-color: $primary-color;
+  border-color: $primary-color;
+  color: white;
+  animation: pulse 2s infinite;
+}
+
+.timeline-line {
+  position: absolute;
+  left: -24px;
+  top: 28px;
+  width: 2px;
+  height: calc(100% + 4px);
+  background-color: $border-color;
+}
+
+.timeline-line.line-completed {
+  background-color: $success-color;
+}
+
+.timeline-content {
+  padding: 8px 16px;
+}
+
+.timeline-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.stage-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: $text-primary;
+}
+
+.stage-status {
+  font-size: 12px;
+  padding: 2px 8px;
+  border-radius: 12px;
+  background-color: $background-tertiary;
+  color: $text-secondary;
+}
+
+.stage-item.stage-completed .stage-status {
+  background-color: color-mix(in srgb, $success-color 10%, transparent);
+  color: $success-color;
+}
+
+.stage-item.stage-in-progress .stage-status {
+  background-color: color-mix(in srgb, $primary-color 10%, transparent);
+  color: $primary-color;
+}
+
+.timeline-meta {
+  font-size: 12px;
+  color: $text-tertiary;
+  margin-bottom: 8px;
+}
+
+.stage-dates {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 4px;
+}
+
+.stage-details {
+  font-size: 13px;
+  color: $text-secondary;
+  line-height: 1.4;
+}
+
+// 标签页样式
+.project-tabs {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  box-shadow: $shadow-sm;
+  overflow: hidden;
+}
+
+.tab-header {
+  display: flex;
+  border-bottom: 1px solid $border-color;
+  background-color: $background-tertiary;
+}
+
+.tab-btn {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 14px 20px;
+  border: none;
+  background: none;
+  cursor: pointer;
+  font-size: 14px;
+  color: $text-secondary;
+  transition: $transition;
+
+  &:hover {
+    color: $primary-color;
+    background-color: color-mix(in srgb, $primary-color 5%, transparent);
+  }
+
+  &.active {
+    color: $primary-color;
+    background-color: $background-primary;
+    border-bottom: 2px solid $primary-color;
+  }
+}
+
+.tab-content {
+  padding: 20px;
+}
+
+// 动画效果
+@keyframes pulse {
+  0% {
+    box-shadow: 0 0 0 0 rgba(22, 93, 255, 0.4);
+  }
+  70% {
+    box-shadow: 0 0 0 6px rgba(22, 93, 255, 0);
+  }
+  100% {
+    box-shadow: 0 0 0 0 rgba(22, 93, 255, 0);
+  }
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .wechat-sidebar {
+    width: 300px;
+  }
+  
+  .after-sales-actions {
+    right: 300px;
+  }
+}
+
+@media (max-width: 768px) {
+  .main-content-area {
+    flex-direction: column;
+  }
+  
+  .wechat-sidebar {
+    width: 100%;
+    height: 0;
+    border-left: none;
+    border-top: 1px solid $border-color;
+  }
+  
+  .wechat-sidebar.show {
+    height: 400px;
+  }
+  
+  .after-sales-actions {
+    right: 0;
+    bottom: 0;
+  }
+  
+  .actions-container {
+    flex-wrap: wrap;
+  }
+}
+
+// 原有样式变量兼容
+$bg-light: $background-secondary;
+$bg-white: $background-primary;
+$box-shadow: $shadow-md;
+$text-light: $text-tertiary;
+
 // 顶部导航栏
 .top-navbar {
   display: flex;
@@ -268,7 +862,7 @@ $transition: all 0.3s ease;
         }
 
         &:disabled {
-          background-color: $text-light;
+          background-color: $text-tertiary;
           cursor: not-allowed;
         }
       }
@@ -337,7 +931,7 @@ $transition: all 0.3s ease;
       display: flex;
       justify-content: space-between;
       font-size: 12px;
-      color: $text-light;
+      color: $text-tertiary;
     }
   }
 
@@ -418,7 +1012,7 @@ $transition: all 0.3s ease;
             display: block;
             font-size: 12px;
             font-weight: 500;
-            color: $text-light;
+            color: $text-tertiary;
             margin-bottom: 4px;
           }
 

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

@@ -1,12 +1,28 @@
-// 添加 toggleSidebar 方法到 ProjectDetail 类中
-// 在第20行左右添加:
-import { Component, OnInit, signal, computed } from '@angular/core';
+import { Component, OnInit, signal, computed, ViewChild, AfterViewChecked } 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';
 
+// 定义项目阶段接口
+interface ProjectStage {
+  name: string;
+  completed: boolean;
+  inProgress: boolean;
+  startDate?: Date;
+  endDate?: Date;
+  responsible?: string;
+  details?: string;
+}
+
+// 定义企业微信消息接口
+interface WechatMessage {
+  sender: string;
+  content: string;
+  timestamp: Date;
+}
+
 @Component({
   selector: 'app-project-detail',
   standalone: true,
@@ -14,7 +30,7 @@ import { Project, Task, Message, FileItem, CustomerFeedback, Milestone } from '.
   templateUrl: './project-detail.html',
   styleUrls: ['./project-detail.scss', '../customer-service-styles.scss']
 })
-export class ProjectDetail implements OnInit {
+export class ProjectDetail implements OnInit, AfterViewChecked {
   // 项目ID
   projectId = '';
   
@@ -49,6 +65,72 @@ export class ProjectDetail implements OnInit {
   // 新消息内容
   newMessage = signal('');
   
+  // 项目阶段数据 - 进度时间轴
+  projectStages: ProjectStage[] = [
+    {
+      name: '前期沟通',
+      completed: true,
+      inProgress: false,
+      startDate: new Date('2023-06-01'),
+      endDate: new Date('2023-06-08'),
+      responsible: '客服小李',
+      details: '完成客户需求确认、现场量房和初步方案讨论'
+    },
+    {
+      name: '建模',
+      completed: true,
+      inProgress: false,
+      startDate: new Date('2023-06-09'),
+      endDate: new Date('2023-06-18'),
+      responsible: '张设计师',
+      details: '创建3D模型和基础渲染'
+    },
+    {
+      name: '软装',
+      completed: false,
+      inProgress: true,
+      startDate: new Date('2023-06-19'),
+      responsible: '张设计师',
+      details: '选择和搭配家具、灯具和装饰品'
+    },
+    {
+      name: '渲染',
+      completed: false,
+      inProgress: false,
+      startDate: new Date('2023-06-26'),
+      responsible: '张设计师',
+      details: '生成最终渲染图'
+    },
+    {
+      name: '后期',
+      completed: false,
+      inProgress: false,
+      startDate: new Date('2023-07-03'),
+      responsible: '张设计师',
+      details: '后期图像处理和优化'
+    },
+    {
+      name: '完成',
+      completed: false,
+      inProgress: false,
+      startDate: new Date('2023-07-10'),
+      endDate: new Date('2023-07-15'),
+      responsible: '客服小李',
+      details: '项目验收和交付'
+    }
+  ];
+  
+  // 企业微信聊天相关
+  @ViewChild('wechatMessages') wechatMessagesContainer: any;
+  wechatMessagesList: WechatMessage[] = [];
+  wechatInput = '';
+  scrollToBottom = false;
+  
+  // 售后处理弹窗状态
+  showModificationRequest = false;
+  showComplaintWarning = false;
+  showRefundRequest = false;
+  
   // 项目状态颜色映射
   statusColors = {
     '进行中': 'primary',
@@ -95,6 +177,9 @@ export class ProjectDetail implements OnInit {
   // 加载模拟数据
   // 修复 loadMockData 方法中的任务对象,确保类型一致性
   loadMockData(): void {
+    // 初始化企业微信聊天
+    this.initWechatMessages();
+    
     // 模拟里程碑数据
     this.milestones.set([
       { 
@@ -452,4 +537,120 @@ export class ProjectDetail implements OnInit {
   progressFillWidth = computed(() => {
     return `${this.completionProgress()}%`;
   });
-}
+  
+  // AfterViewChecked 接口实现,用于滚动到聊天窗口底部
+  ngAfterViewChecked(): void {
+    if (this.scrollToBottom && this.wechatMessagesContainer) {
+      this.wechatMessagesContainer.nativeElement.scrollTop = 
+        this.wechatMessagesContainer.nativeElement.scrollHeight;
+      this.scrollToBottom = false;
+    }
+  }
+  
+  // 初始化企业微信聊天
+  initWechatMessages(): void {
+    // 模拟企业微信聊天消息
+    this.wechatMessagesList = [
+      {
+        sender: '客服小李',
+        content: '您好,张先生,我们已经收到您的需求,正在为您制定设计方案。',
+        timestamp: new Date('2023-06-01T10:30:00')
+      },
+      {
+        sender: '张先生',
+        content: '好的,我希望客厅光线充足,储物空间充足,并且使用环保材料。',
+        timestamp: new Date('2023-06-01T10:35:00')
+      },
+      {
+        sender: '客服小李',
+        content: '明白了,我们会重点考虑这些需求。预计3天内可以完成初步方案。',
+        timestamp: new Date('2023-06-01T10:40:00')
+      },
+      {
+        sender: '张设计师',
+        content: '您好,我是负责您项目的设计师小张。今天下午我会去现场量房,请问您方便吗?',
+        timestamp: new Date('2023-06-02T14:00:00')
+      },
+      {
+        sender: '张先生',
+        content: '下午好的,我在小区门口等您。',
+        timestamp: new Date('2023-06-02T14:05:00')
+      },
+      {
+        sender: '张设计师',
+        content: '已完成量房,正在制作初步设计方案。',
+        timestamp: new Date('2023-06-05T18:30:00')
+      },
+      {
+        sender: '客服小李',
+        content: '张先生,初步设计方案已完成,您什么时候方便查看一下?',
+        timestamp: new Date('2023-06-08T10:00:00')
+      }
+    ];
+    
+    // 确保滚动到底部
+    setTimeout(() => {
+      this.scrollToBottom = true;
+    }, 100);
+  }
+  
+  // 发送企业微信消息
+  sendWechatMessage(): void {
+    if (!this.wechatInput.trim()) return;
+    
+    const newMessage: WechatMessage = {
+      sender: '客服小李',
+      content: this.wechatInput.trim(),
+      timestamp: new Date()
+    };
+    
+    this.wechatMessagesList = [...this.wechatMessagesList, newMessage];
+    this.wechatInput = '';
+    this.scrollToBottom = true;
+    
+    // 模拟对方回复
+    setTimeout(() => {
+      const replyMessage: WechatMessage = {
+        sender: '张先生',
+        content: '收到,我稍后查看。',
+        timestamp: new Date()
+      };
+      
+      this.wechatMessagesList = [...this.wechatMessagesList, replyMessage];
+      this.scrollToBottom = true;
+    }, 2000);
+  }
+  
+  // 格式化为时间显示
+  formatTime(date: Date): string {
+    if (!date) return '';
+    return new Date(date).toLocaleTimeString('zh-CN', { 
+      hour: '2-digit', 
+      minute: '2-digit' 
+    });
+  }
+  
+  // 获取当前日期的计算属性
+  currentDate = computed(() => {
+    return new Date();
+  });
+  
+  // 售后处理入口方法
+  openModificationRequest(): void {
+    this.showModificationRequest = true;
+    // 实际项目中这里可以打开申请修改的模态框或抽屉组件
+    console.log('打开申请修改弹窗');
+  }
+  
+  openComplaintWarning(): void {
+    this.showComplaintWarning = true;
+    // 实际项目中这里可以打开投诉预警的模态框或抽屉组件
+    console.log('打开投诉预警弹窗');
+  }
+  
+  openRefundRequest(): void {
+    this.showRefundRequest = true;
+    // 实际项目中这里可以打开申请退款的模态框或抽屉组件
+    console.log('打开申请退款弹窗');
+  }
+}

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

@@ -375,4 +375,4 @@ export class ProjectList implements OnInit {
     
     return stageClasses[stage] || '';
   }
-}
+}