فهرست منبع

feet:customer8

徐福静0235668 2 روز پیش
والد
کامیت
03e6accd9b

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

@@ -14,7 +14,7 @@ import { CaseLibrary } from './pages/customer-service/case-library/case-library'
 import { ConsultationListComponent } from './pages/customer-service/dashboard/pages/consultation-list/consultation-list.component';
 import { AssignmentListComponent } from './pages/customer-service/dashboard/pages/assignment-list/assignment-list.component';
 import { ExceptionListComponent } from './pages/customer-service/dashboard/pages/exception-list/exception-list.component';
-import { RevenueDetailComponent } from './pages/customer-service/dashboard/pages/revenue-detail/revenue-detail.component';
+
 
 // 设计师页面
 import { Dashboard as DesignerDashboard } from './pages/designer/dashboard/dashboard';
@@ -73,7 +73,7 @@ export const routes: Routes = [
       { path: 'consultation-list', component: ConsultationListComponent, title: '咨询列表' },
       { path: 'assignment-list', component: AssignmentListComponent, title: '待派单列表' },
       { path: 'exception-list', component: ExceptionListComponent, title: '异常项目列表' },
-      { path: 'revenue-detail', component: RevenueDetailComponent, title: '今日成交详情' }
+    
     ]
   },
 

+ 136 - 24
src/app/pages/customer-service/consultation-order/consultation-order.html

@@ -1,17 +1,8 @@
 <div class="consultation-order-container">
-  <!-- 现代化页面头部 -->
+  <!-- Dashboard风格页面头部 -->
   <header class="page-header">
-    <div class="header-content">
-      <div class="header-icon">
-        <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-          <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="header-text">
-        <h1>客户咨询与下单</h1>
-        <p>记录客户需求,快速生成报价,一键创建项目</p>
-      </div>
-    </div>
+    <h1>创建订单</h1>
+    <div class="header-meta">通过小程序或人工方式创建新订单</div>
     
     <!-- 成功提示 -->
     <div *ngIf="showSuccessMessage()" class="success-toast">
@@ -25,6 +16,49 @@
     </div>
   </header>
 
+  <!-- 订单创建方式选择 -->
+  <section class="creation-method-section">
+    <div class="method-selector">
+      <div class="method-title">订单创建方式</div>
+      <div class="method-options">
+        <button 
+          class="method-option"
+          [class.active]="orderCreationMethod() === 'miniprogram'"
+          (click)="switchOrderCreationMethod('miniprogram')"
+        >
+          <div class="option-icon miniprogram-icon">
+            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
+              <line x1="8" y1="21" x2="16" y2="21"></line>
+              <line x1="12" y1="17" x2="12" y2="21"></line>
+            </svg>
+          </div>
+          <div class="option-content">
+            <div class="option-title">小程序创建</div>
+            <div class="option-desc">从小程序数据库同步信息</div>
+          </div>
+          <div class="option-badge">推荐</div>
+        </button>
+        
+        <button 
+          class="method-option"
+          [class.active]="orderCreationMethod() === 'manual'"
+          (click)="switchOrderCreationMethod('manual')"
+        >
+          <div class="option-icon manual-icon">
+            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
+            </svg>
+          </div>
+          <div class="option-content">
+            <div class="option-title">人工手动创建</div>
+            <div class="option-desc">手动填写客户和项目信息</div>
+          </div>
+        </button>
+      </div>
+    </div>
+  </section>
+
   <!-- 主要内容区域 -->
   <main class="main-content">
 
@@ -44,6 +78,24 @@
           </div>
         </div>
         <div class="header-actions">
+          <div class="order-time" *ngIf="orderTime()">
+            <span class="time-label">下单时间:</span>
+            <span class="time-value">{{ orderTime() }}</span>
+          </div>
+          <button 
+            *ngIf="orderCreationMethod() === 'miniprogram'" 
+            class="btn-primary btn-sm sync-btn" 
+            (click)="syncMiniprogramCustomerInfo()"
+            [disabled]="isSyncing()"
+          >
+            <mat-spinner *ngIf="isSyncing()" diameter="16" class="sync-spinner"></mat-spinner>
+            <svg *ngIf="!isSyncing()" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <polyline points="23 4 23 10 17 10"></polyline>
+              <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>
+            {{ isSyncing() ? '同步中...' : '同步客户信息' }}
+          </button>
           <button *ngIf="selectedCustomer()" class="btn-ghost btn-sm" (click)="clearSelectedCustomer()">
             <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
               <line x1="18" y1="6" x2="6" y2="18"></line>
@@ -56,7 +108,7 @@
       
       <div class="card-content">
         <!-- 客户搜索区域 -->
-        <div *ngIf="!selectedCustomer()" class="customer-search-section">
+        <div *ngIf="!selectedCustomer() && orderCreationMethod() === 'manual'" class="customer-search-section">
           <div class="search-container">
             <div class="search-input-wrapper">
               <svg class="search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -67,10 +119,26 @@
                 type="text" 
                 [ngModel]="searchKeyword()" 
                 (ngModelChange)="searchKeyword.set($event); searchCustomer()"
-                placeholder="搜索客户姓名或手机号..."
+                placeholder="输入客户姓名或手机号快速填写..."
                 class="search-input"
                 autocomplete="off"
+                (keyup.enter)="quickFillCustomerInfo(searchKeyword())"
               />
+              <button 
+                class="quick-fill-btn"
+                (click)="quickFillCustomerInfo(searchKeyword())"
+                [disabled]="!searchKeyword().trim() || isSyncing()"
+              >
+                <mat-spinner *ngIf="isSyncing()" diameter="16"></mat-spinner>
+                <span *ngIf="!isSyncing()">快速填写</span>
+              </button>
+            </div>
+            <div class="search-hint">
+              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <circle cx="12" cy="12" r="10"></circle>
+                <path d="m9 12 2 2 4-4"></path>
+              </svg>
+              <span>直接输入姓名/手机号,按回车或点击"快速填写"自动匹配客户信息</span>
             </div>
           </div>
           
@@ -119,7 +187,7 @@
                 <input type="tel" id="phone" formControlName="phone" placeholder="请输入手机号码" class="field-input">
               </div>
               <div class="form-field">
-                <label for="wechat" class="field-label">微信账号</label>
+                <label for="wechat" class="field-label">微信账号 <span class="optional">(可选)</span></label>
                 <input type="text" id="wechat" formControlName="wechat" placeholder="请输入微信账号" class="field-input">
               </div>
             </div>
@@ -127,10 +195,10 @@
           
           <!-- 分类信息组 -->
           <div class="form-section">
-            <h3 class="section-title">分类信息</h3>
+            <h3 class="section-title">分类信息 <span class="section-subtitle">(可选填写,便于客户管理)</span></h3>
             <div class="form-grid">
               <div class="form-field">
-                <label for="customerType" class="field-label">客户类型</label>
+                <label for="customerType" class="field-label">客户类型 <span class="optional">(可选)</span></label>
                 <select id="customerType" formControlName="customerType" class="field-select">
                   <option value="">请选择客户类型</option>
                   <option value="新客户">新客户</option>
@@ -139,18 +207,18 @@
                 </select>
               </div>
               <div class="form-field">
-                <label for="source" class="field-label">来源渠道</label>
+                <label for="source" class="field-label">来源渠道 <span class="optional">(可选)</span></label>
                 <input type="text" id="source" formControlName="source" placeholder="如:官网咨询、推荐介绍等" class="field-input">
               </div>
               <div class="form-field">
-                <label for="demandType" class="field-label">需求类型</label>
+                <label for="demandType" class="field-label">需求类型 <span class="optional">(可选)</span></label>
                 <select id="demandType" formControlName="demandType" class="field-select">
                   <option value="">请选择需求类型</option>
                   <option *ngFor="let type of demandTypes" [value]="type.value">{{ type.label }}</option>
                 </select>
               </div>
               <div class="form-field">
-                <label for="followUpStatus" class="field-label">跟进状态</label>
+                <label for="followUpStatus" class="field-label">跟进状态 <span class="optional">(可选)</span></label>
                 <select id="followUpStatus" formControlName="followUpStatus" class="field-select">
                   <option value="">请选择跟进状态</option>
                   <option *ngFor="let status of followUpStatus" [value]="status.value">{{ status.label }}</option>
@@ -161,7 +229,7 @@
           
           <!-- 偏好标签组 -->
           <div class="form-section">
-            <h3 class="section-title">偏好标签</h3>
+            <h3 class="section-title">偏好标签 <span class="section-subtitle">(可选,有助于个性化服务)</span></h3>
             <div class="tags-section">
               <div class="current-tags">
                 <mat-chip-grid #chipList aria-label="偏好标签">
@@ -210,7 +278,7 @@
           <!-- 备注信息组 -->
           <div class="form-section">
             <div class="form-field full-width">
-              <label for="remark" class="field-label">备注信息</label>
+              <label for="remark" class="field-label">备注信息 <span class="optional">(可选)</span></label>
               <textarea id="remark" formControlName="remark" rows="3" placeholder="请输入其他备注信息" class="field-textarea"></textarea>
             </div>
           </div>
@@ -232,10 +300,25 @@
             </svg>
           </div>
           <div class="header-text">
-            <h2>需求信息</h2>
-            <p>详细描述装修需求和偏好</p>
+            <h2>项目信息</h2>
+            <p>项目需求、风格、小组匹配等信息</p>
           </div>
         </div>
+        <div class="header-actions">
+          <button 
+            class="btn-primary btn-sm sync-btn" 
+            (click)="syncProjectInfo()"
+            [disabled]="isSyncing()"
+          >
+            <mat-spinner *ngIf="isSyncing()" diameter="16" class="sync-spinner"></mat-spinner>
+            <svg *ngIf="!isSyncing()" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <polyline points="23 4 23 10 17 10"></polyline>
+              <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>
+            {{ isSyncing() ? '同步中...' : '同步项目信息' }}
+          </button>
+        </div>
       </div>
       
       <div class="card-content">
@@ -297,6 +380,35 @@
             </div>
           </div>
           
+          <!-- 项目管理组 -->
+          <div class="form-section">
+            <h3 class="section-title">项目管理</h3>
+            <div class="form-grid">
+              <div class="form-field">
+                <label for="projectGroup" class="field-label">项目小组 <span class="required">*</span></label>
+                <select id="projectGroup" formControlName="projectGroup" class="field-select">
+                  <option value="">请选择项目小组</option>
+                  <option *ngFor="let group of projectGroupOptions" [value]="group">{{ group }}</option>
+                </select>
+              </div>
+              <div class="form-field">
+                <label for="firstDraftDate" class="field-label">首稿时间 <span class="required">*</span></label>
+                <input type="date" id="firstDraftDate" formControlName="firstDraftDate" class="field-input">
+              </div>
+              <div class="form-field">
+                <label for="downPayment" class="field-label">首付款 <span class="required">*</span></label>
+                <div class="input-with-unit">
+                  <input type="number" id="downPayment" formControlName="downPayment" placeholder="请输入首付款金额" class="field-input">
+                  <span class="input-unit">元</span>
+                </div>
+              </div>
+            </div>
+            <div class="form-field full-width">
+              <label for="priceDetails" class="field-label">价格明细</label>
+              <textarea id="priceDetails" formControlName="priceDetails" rows="4" placeholder="可通过聊天或复制聊天记录提取价格明细信息" class="field-textarea"></textarea>
+            </div>
+          </div>
+
           <!-- 其他需求组 -->
           <div class="form-section">
             <h3 class="section-title">其他需求</h3>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 643 - 155
src/app/pages/customer-service/consultation-order/consultation-order.scss


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

@@ -100,6 +100,12 @@ export class ConsultationOrder {
   isSubmitting = signal(false);
   // 成功提示显示状态
   showSuccessMessage = signal(false);
+  // 订单创建方式
+  orderCreationMethod = signal<'miniprogram' | 'manual'>('miniprogram');
+  // 同步状态
+  isSyncing = signal(false);
+  // 下单时间(自动生成)
+  orderTime = signal<string>('');
 
   // 需求表单
   requirementForm: FormGroup;
@@ -121,6 +127,11 @@ export class ConsultationOrder {
     '全包', '半包', '清包', '旧房翻新', '局部改造'
   ];
 
+  // 项目小组选项
+  projectGroupOptions = [
+    '设计一组', '设计二组', '设计三组', '高端定制组', '软装设计组'
+  ];
+
   // 标签系统
   demandTypes = DEMAND_TYPES;
   followUpStatus = FOLLOW_UP_STATUS;
@@ -145,7 +156,12 @@ export class ConsultationOrder {
       decorationType: ['', Validators.required],
       preferredDesigner: [''],
       specialRequirements: [''],
-      referenceCases: [[]]
+      referenceCases: [[]],
+      // 新增字段
+      projectGroup: ['', Validators.required], // 项目小组
+      downPayment: ['', [Validators.required, Validators.min(0)]], // 首付款
+      priceDetails: [''], // 价格明细
+      firstDraftDate: ['', Validators.required] // 首稿时间
     });
 
     // 初始化客户表单
@@ -160,6 +176,16 @@ export class ConsultationOrder {
       followUpStatus: ['']
     });
 
+    // 自动生成下单时间
+    this.orderTime.set(new Date().toLocaleString('zh-CN', {
+      year: 'numeric',
+      month: '2-digit',
+      day: '2-digit',
+      hour: '2-digit',
+      minute: '2-digit',
+      second: '2-digit'
+    }));
+
     // 监听表单值变化,自动计算报价和匹配案例
     this.requirementForm.valueChanges.subscribe(() => {
       this.calculateEstimatedPrice();
@@ -441,4 +467,128 @@ export class ConsultationOrder {
       }
     });
   }
+
+  /**
+   * 切换订单创建方式
+   */
+  switchOrderCreationMethod(method: 'miniprogram' | 'manual') {
+    this.orderCreationMethod.set(method);
+    if (method === 'miniprogram') {
+      // 切换到小程序模式时,清空手动填写的信息
+      this.clearSelectedCustomer();
+    }
+  }
+
+  /**
+   * 同步小程序客户信息
+   */
+  syncMiniprogramCustomerInfo() {
+    this.isSyncing.set(true);
+    
+    // 模拟从小程序数据库同步客户信息
+    setTimeout(() => {
+      // 这里应该调用实际的API来获取小程序客户信息
+      const mockCustomerData = {
+        id: 'mp_' + Date.now(),
+        name: '张三',
+        phone: '13800138000',
+        wechat: 'zhangsan_wx',
+        customerType: '新客户',
+        source: '小程序注册',
+        avatar: '',
+        demandType: 'comprehensive',
+        preferenceTags: ['现代简约', '环保材料'],
+        followUpStatus: 'quotation'
+      };
+
+      this.selectedCustomer.set(mockCustomerData);
+      this.customerForm.patchValue({
+        name: mockCustomerData.name,
+        phone: mockCustomerData.phone,
+        wechat: mockCustomerData.wechat,
+        customerType: mockCustomerData.customerType,
+        source: mockCustomerData.source,
+        demandType: mockCustomerData.demandType,
+        followUpStatus: mockCustomerData.followUpStatus
+      });
+
+      this.preferenceTags = [...mockCustomerData.preferenceTags];
+      this.isSyncing.set(false);
+      this.snackBar.open('客户信息同步成功', '确定', { duration: 2000 });
+    }, 1500);
+  }
+
+  /**
+   * 同步项目信息
+   */
+  syncProjectInfo() {
+    this.isSyncing.set(true);
+    
+    // 模拟从数据库同步项目信息
+    setTimeout(() => {
+      // 这里应该调用实际的API来获取项目信息
+      const mockProjectData = {
+        style: '现代简约',
+        budget: '10-20万',
+        area: 120,
+        houseType: '三室两厅',
+        floor: 15,
+        decorationType: '全包',
+        preferredDesigner: '张设计师',
+        specialRequirements: '需要环保材料,注重收纳空间',
+        // 新增字段的模拟数据
+        projectGroup: '设计一组',
+        downPayment: 50000,
+        priceDetails: '设计费:15000元\n施工费:180000元\n主材费:120000元\n软装费:35000元',
+        firstDraftDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] // 14天后
+      };
+
+      this.requirementForm.patchValue(mockProjectData);
+      this.isSyncing.set(false);
+      this.snackBar.open('项目信息同步成功', '确定', { duration: 2000 });
+    }, 1500);
+  }
+
+  /**
+   * 快速填写客户信息(通过姓名或手机号)
+   */
+  quickFillCustomerInfo(keyword: string) {
+    if (!keyword.trim()) {
+      return;
+    }
+
+    this.isSyncing.set(true);
+    
+    // 模拟根据姓名或手机号查询客户信息
+    setTimeout(() => {
+      // 这里应该调用实际的API来查询客户信息
+      const mockCustomer = {
+        id: 'quick_' + Date.now(),
+        name: keyword.includes('1') ? '李四' : keyword,
+        phone: keyword.includes('1') ? keyword : '13900139000',
+        wechat: 'lisi_wx',
+        customerType: '老客户',
+        source: '推荐介绍',
+        avatar: '',
+        demandType: 'quality',
+        preferenceTags: ['北欧风格', '实木'],
+        followUpStatus: 'confirm'
+      };
+
+      this.selectedCustomer.set(mockCustomer);
+      this.customerForm.patchValue({
+        name: mockCustomer.name,
+        phone: mockCustomer.phone,
+        wechat: mockCustomer.wechat,
+        customerType: mockCustomer.customerType,
+        source: mockCustomer.source,
+        demandType: mockCustomer.demandType,
+        followUpStatus: mockCustomer.followUpStatus
+      });
+
+      this.preferenceTags = [...mockCustomer.preferenceTags];
+      this.isSyncing.set(false);
+      this.snackBar.open('客户信息填写完成', '确定', { duration: 2000 });
+    }, 1000);
+  }
 }

+ 2 - 2
src/app/pages/customer-service/customer-service-layout/customer-service-layout.html

@@ -86,9 +86,9 @@
       </a>
       <a routerLink="/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>
+          <path d="M16 3h5v5M8 3H3v5M12 19l-7-7 7-7M16 21h5v-5M8 21H3v-5"></path>
         </svg>
-        <span>客户咨询</span>
+        <span>创建订单</span>
       </a>
       <a routerLink="/customer-service/project-list" class="nav-item" routerLinkActive="active">
         <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">

+ 41 - 17
src/app/pages/customer-service/dashboard/dashboard.html

@@ -55,21 +55,7 @@
         </div>
       </div>
 
-      <div class="stat-card" (click)="handleTodayRevenueClick()" title="点击查看今日成交额详情">
-        <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>
 
     <!-- 新增:核心指标渐变数字卡片 -->
@@ -118,7 +104,19 @@
     <!-- 新客户触达 -->
     <div class="crm-card">
       <div class="crm-header">
-        <h3>新客户触达</h3>
+        <div class="crm-title-section">
+          <h3>新客户触达</h3>
+          <div class="crm-stats">
+            <div class="stat-item">
+              <span class="stat-number">{{ stats.newCustomerReachCount() }}</span>
+              <span class="stat-label">待触达</span>
+            </div>
+            <div class="stat-item">
+              <span class="stat-number success">{{ stats.newCustomerConversionRate() }}%</span>
+              <span class="stat-label">转化率</span>
+            </div>
+          </div>
+        </div>
         <a class="view-all-link" (click)="goToConsultationList()">查看全部</a>
       </div>
       <div class="crm-list">
@@ -128,9 +126,16 @@
             <div class="info">
               <div class="name">{{ c.name }}</div>
               <div class="meta">
+                <span class="tag" [class.value-tag]="c.customerTag === 'value-sensitive'" [class.price-tag]="c.customerTag === 'price-sensitive'">
+                  {{ c.customerTag === 'value-sensitive' ? '品质敏感' : '价格敏感' }}
+                </span>
                 <span class="tag">{{ c.demandType }}</span>
                 <span class="time">上次沟通:{{ formatDate(c.lastContactAt) }}</span>
               </div>
+              <div class="strategy-info">
+                <div class="recommended-phrase">{{ c.recommendedPhrase }}</div>
+                <div class="case-strategy">策略:{{ c.caseStrategy }}</div>
+              </div>
             </div>
           </div>
           <button class="ios-btn mini">触达</button>
@@ -142,7 +147,19 @@
     <!-- 老客户回访 -->
     <div class="crm-card">
       <div class="crm-header">
-        <h3>老客户回访</h3>
+        <div class="crm-title-section">
+          <h3>老客户回访</h3>
+          <div class="crm-stats">
+            <div class="stat-item">
+              <span class="stat-number">{{ stats.oldCustomerFollowUpCount() }}</span>
+              <span class="stat-label">待回访</span>
+            </div>
+            <div class="stat-item">
+              <span class="stat-number warning">{{ stats.oldCustomerRetentionRate() }}%</span>
+              <span class="stat-label">留存率</span>
+            </div>
+          </div>
+        </div>
         <a class="view-all-link" (click)="goToConsultationList()">查看全部</a>
       </div>
       <div class="crm-list">
@@ -152,9 +169,16 @@
             <div class="info">
               <div class="name">{{ c.name }}</div>
               <div class="meta">
+                <span class="tag" [class.value-tag]="c.customerTag === 'value-sensitive'" [class.price-tag]="c.customerTag === 'price-sensitive'">
+                  {{ c.customerTag === 'value-sensitive' ? '品质敏感' : '价格敏感' }}
+                </span>
                 <span class="tag">{{ c.demandType }}</span>
                 <span class="time">上次沟通:{{ formatDate(c.lastContactAt) }}</span>
               </div>
+              <div class="strategy-info">
+                <div class="recommended-phrase">{{ c.recommendedPhrase }}</div>
+                <div class="case-strategy">策略:{{ c.caseStrategy }}</div>
+              </div>
             </div>
           </div>
           <button class="ios-btn mini outline">回访</button>

+ 0 - 6
src/app/pages/customer-service/dashboard/dashboard.routes.ts

@@ -3,7 +3,6 @@ import { Dashboard } from './dashboard';
 import { ConsultationListComponent } from './pages/consultation-list/consultation-list.component';
 import { AssignmentListComponent } from './pages/assignment-list/assignment-list.component';
 import { ExceptionListComponent } from './pages/exception-list/exception-list.component';
-import { RevenueDetailComponent } from './pages/revenue-detail/revenue-detail.component';
 
 export const DASHBOARD_ROUTES: Routes = [
   { 
@@ -20,10 +19,5 @@ export const DASHBOARD_ROUTES: Routes = [
     path: 'exception-list', 
     component: ExceptionListComponent,
     data: { title: '异常项目' }
-  },
-  { 
-    path: 'revenue-detail', 
-    component: RevenueDetailComponent,
-    data: { title: '成交详情' }
   }
 ];

+ 255 - 29
src/app/pages/customer-service/dashboard/dashboard.scss

@@ -38,6 +38,54 @@ $ios-radius-md: 12px;
 $ios-radius-lg: 16px;
 $ios-radius-xl: 22px;
 
+/* 企业微信提示动画 */
+@keyframes slideInRight {
+  from {
+    transform: translateX(100%);
+    opacity: 0;
+  }
+  to {
+    transform: translateX(0);
+    opacity: 1;
+  }
+}
+
+@keyframes slideOutRight {
+  from {
+    transform: translateX(0);
+    opacity: 1;
+  }
+  to {
+    transform: translateX(100%);
+    opacity: 0;
+  }
+}
+
+/* 企业微信提示样式 */
+.wechat-work-tip {
+  .tip-content {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+  }
+  
+  .tip-icon {
+    font-size: 20px;
+    text-align: center;
+  }
+  
+  .tip-text {
+    font-weight: 600;
+    line-height: 1.4;
+  }
+  
+  .tip-subtext {
+    font-size: 12px;
+    opacity: 0.9;
+    line-height: 1.3;
+  }
+}
+
 /* iOS风格的模态框覆盖层 */
 .ios-modal-overlay {
   position: fixed;
@@ -465,8 +513,9 @@ $ios-radius-xl: 22px;
 
 .stats-grid {
   display: grid;
-  grid-template-columns: repeat(4, 1fr);
-  gap: 20px;
+  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+  gap: 16px;
+  margin-bottom: 20px;
 }
 
 .stat-card {
@@ -531,7 +580,8 @@ $ios-radius-xl: 22px;
 .content-grid {
   display: grid;
   grid-template-columns: 1fr 1fr;
-  gap: 24px;
+  gap: 20px;
+  margin-top: 16px;
 }
 
 /* 任务列表区块 */
@@ -989,6 +1039,28 @@ $ios-radius-xl: 22px;
     grid-template-columns: repeat(2, 1fr);
   }
 
+/* 欢迎区域 */
+.welcome-section {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  padding: 24px 32px;
+  border-radius: $border-radius;
+  margin-bottom: 24px;
+  box-shadow: $shadow-lg;
+}
+
+.welcome-section h2 {
+  font-size: 24px;
+  font-weight: 700;
+  margin: 0 0 6px 0;
+}
+
+.welcome-section p {
+  font-size: 14px;
+  opacity: 0.9;
+  margin: 0;
+}
+
 @media (max-width: 768px) {
   .welcome-section {
     margin-bottom: 16px;
@@ -1442,13 +1514,13 @@ input[type="datetime-local"]::-webkit-calendar-picker-indicator {
 
 /* CRM队列 */
 .crm-queues {
-  margin: 20px 0 8px;
+  margin-bottom: 24px;
 }
 
 .crm-grid {
   display: grid;
   grid-template-columns: 1fr 1fr;
-  gap: 16px;
+  gap: 20px;
 }
 
 .crm-card {
@@ -1461,12 +1533,72 @@ input[type="datetime-local"]::-webkit-calendar-picker-indicator {
 
 .crm-header {
   display: flex;
-  align-items: center;
+  align-items: flex-start;
   justify-content: space-between;
-  margin-bottom: 12px;
+  margin-bottom: 16px;
+  gap: 16px;
 
-  h3 { margin: 0; font-size: 16px; font-weight: 600; }
-  .view-all-link { color: $primary-color; font-size: 13px; cursor: pointer; }
+  .crm-title-section {
+    flex: 1;
+    
+    h3 { 
+      margin: 0 0 8px 0; 
+      font-size: 16px; 
+      font-weight: 600; 
+      color: $ios-text;
+    }
+  }
+
+  .crm-stats {
+    display: flex;
+    gap: 16px;
+    margin-top: 4px;
+    
+    .stat-item {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      gap: 2px;
+      
+      .stat-number {
+        font-size: 18px;
+        font-weight: 700;
+        color: $ios-text;
+        
+        &.success {
+          color: $ios-success;
+        }
+        
+        &.warning {
+          color: $ios-warning;
+        }
+        
+        &.danger {
+          color: $ios-danger;
+        }
+      }
+      
+      .stat-label {
+        font-size: 11px;
+        color: $ios-text-secondary;
+        font-weight: 500;
+        text-transform: uppercase;
+        letter-spacing: 0.5px;
+      }
+    }
+  }
+  
+  .view-all-link { 
+    color: $primary-color; 
+    font-size: 13px; 
+    cursor: pointer;
+    white-space: nowrap;
+    margin-top: 4px;
+    
+    &:hover {
+      color: $ios-primary-dark;
+    }
+  }
 }
 
 .crm-list {
@@ -1477,7 +1609,7 @@ input[type="datetime-local"]::-webkit-calendar-picker-indicator {
 
 .crm-item {
   display: flex;
-  align-items: center;
+  align-items: flex-start;
   justify-content: space-between;
   gap: 12px;
   padding: 10px 12px;
@@ -1488,17 +1620,41 @@ input[type="datetime-local"]::-webkit-calendar-picker-indicator {
 
   &:hover { background: #f7f7fb; }
 
-  .crm-item-main { display: flex; align-items: center; gap: 12px; }
+  .crm-item-main { display: flex; align-items: flex-start; gap: 12px; }
   .avatar.small {
     width: 28px; height: 28px; border-radius: 50%;
     background: $primary-color; color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700;
+    flex-shrink: 0;
     &.alt { background: $ios-secondary; }
   }
   .info {
     .name { font-weight: 600; color: $text-primary-dark; }
-    .meta { color: $text-tertiary-dark; font-size: 12px; display: flex; gap: 8px; }
-    .tag { background: rgba(0,0,0,0.05); padding: 2px 6px; border-radius: 6px; }
+    .meta { color: $text-tertiary-dark; font-size: 12px; display: flex; gap: 8px; margin-bottom: 8px; }
+    .tag { 
+      background: rgba(0,0,0,0.05); padding: 2px 6px; border-radius: 6px;
+      &.value-tag {
+        background: #e8f5e8;
+        color: #2e7d32;
+      }
+      &.price-tag {
+        background: #fff3e0;
+        color: #f57c00;
+      }
+    }
     .time { }
+    .strategy-info {
+      font-size: 12px;
+      line-height: 1.4;
+      .recommended-phrase {
+        color: #555;
+        margin-bottom: 4px;
+        font-style: italic;
+      }
+      .case-strategy {
+        color: #666;
+        font-size: 11px;
+      }
+    }
   }
 
   .ios-btn.mini { padding: 6px 10px; font-size: 12px; border-radius: 8px; }
@@ -2055,31 +2211,73 @@ input[type="datetime-local"]::-webkit-calendar-picker-indicator {
 }
 
 /* 响应式设计 */
-@media (max-width: 768px) {
-  .content-grid {
-    grid-template-columns: 1fr;
-    gap: 20px;
-  }
-  
+@media (max-width: 1024px) {
   .stats-grid {
-    grid-template-columns: 1fr;
-    gap: 16px;
+    grid-template-columns: repeat(2, 1fr);
   }
   
+  .core-metrics-grid {
+    grid-template-columns: repeat(2, 1fr);
+  }
+}
+
+@media (max-width: 768px) {
   .welcome-section {
-    padding: 20px;
+    padding: 16px 20px;
+    margin-bottom: 16px;
   }
-  
+
   .welcome-section h2 {
-    font-size: 24px;
+    font-size: 20px;
   }
-  
+
   .welcome-section p {
-    font-size: 14px;
+    font-size: 13px;
   }
-  
-  .section-header h3 {
-    font-size: 18px;
+
+  .stats-grid {
+    grid-template-columns: 1fr;
+    gap: 12px;
+  }
+
+  .content-grid {
+    grid-template-columns: 1fr;
+    gap: 16px;
+    margin-top: 12px;
+  }
+
+  .crm-grid {
+    grid-template-columns: 1fr;
+    gap: 12px;
+  }
+
+  .core-metrics-grid {
+    grid-template-columns: 1fr;
+    gap: 10px;
+  }
+
+  .crm-item {
+    padding: 10px 0;
+    
+    .strategy-info {
+      .recommended-phrase {
+        font-size: 11px;
+      }
+      
+      .case-strategy {
+        font-size: 10px;
+      }
+    }
+  }
+
+  .section-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 12px;
+    
+    h3 {
+      font-size: 18px;
+    }
   }
   
   .task-title,
@@ -2091,4 +2289,32 @@ input[type="datetime-local"]::-webkit-calendar-picker-indicator {
   .update-text {
     font-size: 14px !important;
   }
+}
+
+@media (max-width: 480px) {
+  .welcome-section {
+    padding: 12px 16px;
+  }
+
+  .stats-dashboard,
+  .crm-queues {
+    margin-bottom: 16px;
+  }
+
+  .crm-item {
+    .avatar.small {
+      width: 24px;
+      height: 24px;
+      font-size: 11px;
+    }
+    
+    .info .name {
+      font-size: 14px;
+    }
+    
+    .info .meta {
+      font-size: 11px;
+      gap: 6px;
+    }
+  }
 }

+ 139 - 20
src/app/pages/customer-service/dashboard/dashboard.ts

@@ -20,16 +20,35 @@ export class Dashboard implements OnInit, OnDestroy {
     newConsultations: signal(12),
     pendingAssignments: signal(5),
     exceptionProjects: signal(2),
-    todayRevenue: signal(28500),
     // 新增核心指标
     conversionRateToday: signal(36), // 当日成交率(%)
     pendingComplaints: signal(3),   // 待处理投诉数
-    unRepliedConsultations: signal(7) // 未回复咨询数
+    unRepliedConsultations: signal(7), // 未回复咨询数
+    // 新客户触达统计
+    newCustomerReachCount: signal(8), // 新客户待触达数量
+    newCustomerConversionRate: signal(24), // 新客户转化率(%)
+    // 老客户回访统计
+    oldCustomerFollowUpCount: signal(6), // 老客户待回访数量
+    oldCustomerRetentionRate: signal(78) // 老客户留存率(%)
   };
 
-  // 新增:新客户触达/老客户回访列表
-  newReachOutCustomers = signal<Array<{ name: string; demandType: string; lastContactAt: Date }>>([]);
-  oldCustomerFollowUps = signal<Array<{ name: string; demandType: string; lastContactAt: Date }>>([]);
+  // 新增:新客户触达/老客户回访列表(增强版本,包含客户标签和策略)
+  newReachOutCustomers = signal<Array<{ 
+    name: string; 
+    demandType: string; 
+    lastContactAt: Date;
+    customerTag: 'value-sensitive' | 'price-sensitive';
+    recommendedPhrase: string;
+    caseStrategy: string;
+  }>>([]);
+  oldCustomerFollowUps = signal<Array<{ 
+    name: string; 
+    demandType: string; 
+    lastContactAt: Date;
+    customerTag: 'value-sensitive' | 'price-sensitive';
+    recommendedPhrase: string;
+    caseStrategy: string;
+  }>>([]);
   urgentTasks = signal<Task[]>([]);
 
   // 任务处理状态
@@ -180,23 +199,129 @@ export class Dashboard implements OnInit, OnDestroy {
   private loadCRMQueues(): void {
     const now = new Date();
     this.newReachOutCustomers.set([
-      { name: '陈女士', demandType: '全屋定制', lastContactAt: new Date(now.getTime() - 2 * 60 * 60 * 1000) },
-      { name: '赵先生', demandType: '厨房改造', lastContactAt: new Date(now.getTime() - 26 * 60 * 60 * 1000) },
-      { name: '吴先生', demandType: '客厅软装', lastContactAt: new Date(now.getTime() - 5 * 60 * 60 * 1000) }
+      { 
+        name: '陈女士', 
+        demandType: '全屋定制', 
+        lastContactAt: new Date(now.getTime() - 2 * 60 * 60 * 1000),
+        customerTag: 'value-sensitive',
+        recommendedPhrase: '我们的全屋定制方案注重品质与设计的完美结合,为您打造独一无二的家居空间',
+        caseStrategy: '推荐高端别墅案例,强调设计理念和材料品质'
+      },
+      { 
+        name: '赵先生', 
+        demandType: '厨房改造', 
+        lastContactAt: new Date(now.getTime() - 26 * 60 * 60 * 1000),
+        customerTag: 'price-sensitive',
+        recommendedPhrase: '我们的厨房改造方案性价比极高,在预算范围内实现最大化的功能提升',
+        caseStrategy: '推荐经济实用案例,突出成本控制和实用功能'
+      },
+      { 
+        name: '吴先生', 
+        demandType: '客厅软装', 
+        lastContactAt: new Date(now.getTime() - 5 * 60 * 60 * 1000),
+        customerTag: 'value-sensitive',
+        recommendedPhrase: '我们的软装设计师将为您量身定制,打造有品味的生活空间',
+        caseStrategy: '推荐精品软装案例,强调设计师专业度和美学价值'
+      }
     ]);
 
     this.oldCustomerFollowUps.set([
-      { name: '王女士', demandType: '别墅整装', lastContactAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) },
-      { name: '李先生', demandType: '卧室升级', lastContactAt: new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000) },
-      { name: '孙女士', demandType: '卫生间翻新', lastContactAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000) }
+      { 
+        name: '王女士', 
+        demandType: '别墅整装', 
+        lastContactAt: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000),
+        customerTag: 'value-sensitive',
+        recommendedPhrase: '感谢您对我们的信任,我们将继续为您提供高品质的服务体验',
+        caseStrategy: '展示同档次别墅案例,强调服务品质和后续保障'
+      },
+      { 
+        name: '李先生', 
+        demandType: '卧室升级', 
+        lastContactAt: new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000),
+        customerTag: 'price-sensitive',
+        recommendedPhrase: '我们为老客户准备了特别优惠,让您以更实惠的价格享受升级服务',
+        caseStrategy: '推荐性价比升级方案,提供老客户专属优惠'
+      },
+      { 
+        name: '孙女士', 
+        demandType: '卫生间翻新', 
+        lastContactAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000),
+        customerTag: 'value-sensitive',
+        recommendedPhrase: '基于您之前的项目经验,我们为您推荐更加精致的翻新方案',
+        caseStrategy: '展示精品卫生间案例,强调细节工艺和材料升级'
+      }
     ]);
   }
 
-  // 便捷跳转到咨询列表并带上客户名称搜索
+  // 便捷跳转到企业微信聊天窗口或咨询列表
   navigateToConsultation(keyword: string): void {
+    // 尝试打开企业微信聊天窗口
+    this.openWeChatWorkChat(keyword);
+    
+    // 同时导航到咨询列表作为备选方案
     this.router.navigate(['/customer-service/consultation-list'], { queryParams: { q: keyword } });
   }
 
+  // 打开企业微信聊天窗口
+  private openWeChatWorkChat(customerName: string): void {
+    try {
+      // 企业微信 Web 版聊天链接格式
+      const weChatWorkUrl = `https://work.weixin.qq.com/wework_admin/frame#/chat/${encodeURIComponent(customerName)}`;
+      
+      // 尝试在新窗口中打开企业微信聊天
+      const chatWindow = window.open(weChatWorkUrl, '_blank', 'width=800,height=600,scrollbars=yes,resizable=yes');
+      
+      if (!chatWindow) {
+        console.warn('无法打开企业微信聊天窗口,可能被浏览器阻止弹窗');
+        // 如果弹窗被阻止,显示提示信息
+        this.showWeChatWorkTip(customerName);
+      }
+    } catch (error) {
+      console.error('打开企业微信聊天窗口失败:', error);
+      this.showWeChatWorkTip(customerName);
+    }
+  }
+
+  // 显示企业微信提示信息
+  private showWeChatWorkTip(customerName: string): void {
+    // 创建临时提示元素
+    const tip = document.createElement('div');
+    tip.className = 'wechat-work-tip';
+    tip.innerHTML = `
+      <div class="tip-content">
+        <div class="tip-icon">💬</div>
+        <div class="tip-text">正在为您打开与 ${customerName} 的企业微信聊天</div>
+        <div class="tip-subtext">如未自动打开,请手动前往企业微信查找该客户</div>
+      </div>
+    `;
+    
+    // 添加样式
+    tip.style.cssText = `
+      position: fixed;
+      top: 20px;
+      right: 20px;
+      background: linear-gradient(135deg, #1890ff, #36cfc9);
+      color: white;
+      padding: 16px 20px;
+      border-radius: 12px;
+      box-shadow: 0 8px 32px rgba(24, 144, 255, 0.3);
+      z-index: 10000;
+      font-size: 14px;
+      max-width: 320px;
+      animation: slideInRight 0.3s ease-out;
+    `;
+    
+    document.body.appendChild(tip);
+    
+    // 3秒后自动移除提示
+    setTimeout(() => {
+      if (tip.parentNode) {
+        tip.style.animation = 'slideOutRight 0.3s ease-in';
+        setTimeout(() => tip.remove(), 300);
+      }
+    }, 3000);
+  }
+
   // 查看全部咨询列表
   goToConsultationList(): void {
     this.router.navigate(['/customer-service/consultation-list']);
@@ -499,18 +624,12 @@ export class Dashboard implements OnInit, OnDestroy {
     this.navigateToDetail('exceptions');
   }
 
-  // 今日成交额图标点击处理
-  handleTodayRevenueClick(): void {
-    this.navigateToDetail('revenue');
-  }
-
   // 导航到详情页
-  private navigateToDetail(type: 'consultations' | 'assignments' | 'exceptions' | 'revenue'): void {
+  private navigateToDetail(type: 'consultations' | 'assignments' | 'exceptions'): void {
     const routeMap = {
       consultations: '/customer-service/consultation-list',
       assignments: '/customer-service/assignment-list',
-      exceptions: '/customer-service/exception-list',
-      revenue: '/customer-service/revenue-detail'
+      exceptions: '/customer-service/exception-list'
     };
     
     console.log('导航到:', routeMap[type]);

+ 535 - 113
src/app/pages/customer-service/dashboard/pages/consultation-list/consultation-list.component.scss

@@ -1,166 +1,588 @@
 @import "../../../customer-service-styles.scss";
 
-.ios-container {
-  max-width: 100%;
-  height: 100vh;
-  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
-  background-color: $background-tertiary;
+.consultation-container {
+  min-height: 100vh;
+  background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 }
 
-.ios-header {
+// 页面头部
+.page-header {
+  background: #ffffff;
+  border-bottom: 1px solid #e2e8f0;
+  padding: 20px 24px;
   display: flex;
   align-items: center;
-  padding: 16px;
-  background-color: $background-primary;
-  border-bottom: 1px solid $border-color;
+  justify-content: space-between;
   position: sticky;
   top: 0;
-  z-index: 10;
+  z-index: 100;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
 
-  h1 {
-    font-size: 20px;
-    font-weight: 600;
-    margin: 0 auto;
+  .header-left {
+    display: flex;
+    align-items: center;
+    gap: 16px;
   }
-}
 
-.ios-back-btn {
-  background: none;
-  border: none;
-  padding: 8px;
-  z-index: 11;
-}
+  .back-btn {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 40px;
+    height: 40px;
+    border: none;
+    background: #f8fafc;
+    border-radius: 10px;
+    color: #64748b;
+    cursor: pointer;
+    transition: all 0.2s ease;
 
-.ios-content {
-  padding: 16px;
-}
+    &:hover {
+      background: #e2e8f0;
+      color: #475569;
+      transform: translateX(-2px);
+    }
 
-.search-bar {
-  position: relative;
-  margin-bottom: 16px;
+    &:active {
+      transform: translateX(-1px);
+    }
+  }
 
-  input {
-    width: 100%;
-    padding: 12px 16px 12px 40px;
-    border-radius: 10px;
-    border: 1px solid $border-color;
-    background-color: $background-secondary;
-    font-size: 16px;
+  .header-title {
+    h1 {
+      font-size: 24px;
+      font-weight: 700;
+      color: #1e293b;
+      margin: 0;
+      line-height: 1.2;
+    }
 
-    &::placeholder {
-      color: $text-tertiary;
+    .subtitle {
+      font-size: 14px;
+      color: #64748b;
+      margin: 4px 0 0;
+      line-height: 1.4;
     }
   }
 
-  svg {
-    position: absolute;
-    left: 12px;
-    top: 50%;
-    transform: translateY(-50%);
-    color: $text-tertiary;
+  .header-actions {
+    .refresh-btn {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      padding: 10px 16px;
+      border: 1px solid #e2e8f0;
+      background: #ffffff;
+      border-radius: 8px;
+      color: #475569;
+      font-size: 14px;
+      font-weight: 500;
+      cursor: pointer;
+      transition: all 0.2s ease;
+
+      &:hover:not(:disabled) {
+        background: #f8fafc;
+        border-color: #cbd5e1;
+      }
+
+      &:disabled {
+        opacity: 0.6;
+        cursor: not-allowed;
+      }
+
+      svg.spinning {
+        animation: spin 1s linear infinite;
+      }
+    }
   }
 }
 
-.consultation-list {
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-}
+// 搜索和筛选区域
+.search-filter-section {
+  padding: 24px;
+  background: #ffffff;
+  border-bottom: 1px solid #e2e8f0;
+
+  .search-container {
+    margin-bottom: 20px;
 
-.consultation-card {
-  background-color: $background-primary;
-  border-radius: 12px;
-  padding: 16px;
-  box-shadow: $shadow-sm;
-  transition: transform 0.2s ease;
+    .search-input-wrapper {
+      position: relative;
+      max-width: 500px;
 
-  &:active {
-    transform: scale(0.98);
+      .search-icon {
+        position: absolute;
+        left: 16px;
+        top: 50%;
+        transform: translateY(-50%);
+        color: #94a3b8;
+        z-index: 2;
+      }
+
+      .search-input {
+        width: 100%;
+        padding: 14px 16px 14px 48px;
+        border: 2px solid #e2e8f0;
+        border-radius: 12px;
+        font-size: 16px;
+        background: #f8fafc;
+        transition: all 0.2s ease;
+        outline: none;
+
+        &::placeholder {
+          color: #94a3b8;
+        }
+
+        &:focus {
+          border-color: #3b82f6;
+          background: #ffffff;
+          box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+        }
+      }
+
+      .clear-search {
+        position: absolute;
+        right: 12px;
+        top: 50%;
+        transform: translateY(-50%);
+        width: 24px;
+        height: 24px;
+        border: none;
+        background: #e2e8f0;
+        border-radius: 50%;
+        color: #64748b;
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        transition: all 0.2s ease;
+
+        &:hover {
+          background: #cbd5e1;
+          color: #475569;
+        }
+      }
+    }
   }
-}
 
-.card-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 12px;
+  .filter-chips {
+    display: flex;
+    gap: 12px;
+    flex-wrap: wrap;
+
+    .filter-chip {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      padding: 10px 16px;
+      border: 2px solid #e2e8f0;
+      background: #ffffff;
+      border-radius: 20px;
+      color: #64748b;
+      font-size: 14px;
+      font-weight: 500;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      position: relative;
+
+      &:hover {
+        border-color: #cbd5e1;
+        background: #f8fafc;
+      }
+
+      &.active {
+        border-color: #3b82f6;
+        background: #3b82f6;
+        color: #ffffff;
+
+        .chip-count {
+          background: rgba(255, 255, 255, 0.2);
+          color: #ffffff;
+        }
+      }
+
+      .chip-count {
+        background: #e2e8f0;
+        color: #64748b;
+        padding: 2px 8px;
+        border-radius: 10px;
+        font-size: 12px;
+        font-weight: 600;
+        min-width: 20px;
+        text-align: center;
+      }
+    }
+  }
 }
 
-.customer-info {
-  display: flex;
-  align-items: center;
-  gap: 12px;
+// 咨询内容区域
+.consultation-content {
+  padding: 24px;
+  min-height: 400px;
 
-  .avatar {
-    width: 40px;
-    height: 40px;
-    border-radius: 20px;
-    background-color: $primary-color;
-    color: white;
+  .loading-state {
     display: flex;
+    flex-direction: column;
     align-items: center;
     justify-content: center;
-    font-weight: 600;
+    padding: 60px 20px;
+    color: #64748b;
+
+    .loading-spinner {
+      width: 40px;
+      height: 40px;
+      border: 3px solid #e2e8f0;
+      border-top: 3px solid #3b82f6;
+      border-radius: 50%;
+      animation: spin 1s linear infinite;
+      margin-bottom: 16px;
+    }
+
+    p {
+      font-size: 16px;
+      margin: 0;
+    }
   }
 
-  h3 {
-    font-size: 16px;
-    margin: 0;
-    color: $text-primary;
+  .empty-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 60px 20px;
+    text-align: center;
+    color: #64748b;
+
+    svg {
+      margin-bottom: 20px;
+      opacity: 0.5;
+    }
+
+    h3 {
+      font-size: 20px;
+      font-weight: 600;
+      color: #475569;
+      margin: 0 0 8px;
+    }
+
+    p {
+      font-size: 16px;
+      margin: 0;
+      line-height: 1.5;
+    }
   }
 
-  .time {
-    font-size: 14px;
-    color: $text-tertiary;
-    margin: 4px 0 0;
+  .consultation-list {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
   }
 }
 
-.status-badge {
-  padding: 4px 8px;
-  border-radius: 4px;
-  font-size: 12px;
-  font-weight: 500;
+// 咨询条目
+.consultation-item {
+  background: #ffffff;
+  border: 1px solid #e2e8f0;
+  border-radius: 16px;
+  padding: 20px;
+  transition: all 0.2s ease;
+  position: relative;
+  overflow: hidden;
+
+  &:hover {
+    border-color: #cbd5e1;
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+    transform: translateY(-1px);
+  }
 
   &.urgent {
-    background-color: rgba($danger-color, 0.1);
-    color: $danger-color;
+    border-left: 4px solid #ef4444;
+    background: linear-gradient(135deg, #fef2f2 0%, #ffffff 100%);
+  }
+
+  &.sensitive {
+    border-left: 4px solid #f59e0b;
+    background: linear-gradient(135deg, #fffbeb 0%, #ffffff 100%);
+  }
+
+  &.overdue {
+    border-left: 4px solid #dc2626;
+    background: linear-gradient(135deg, #fef2f2 0%, #ffffff 100%);
+  }
+
+  .item-header {
+    display: flex;
+    align-items: flex-start;
+    justify-content: space-between;
+    margin-bottom: 16px;
+
+    .customer-section {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      flex: 1;
+
+      .customer-avatar {
+        width: 48px;
+        height: 48px;
+        border-radius: 12px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        color: #ffffff;
+        font-size: 18px;
+        font-weight: 600;
+        flex-shrink: 0;
+      }
+
+      .customer-details {
+        flex: 1;
+
+        .customer-name {
+          font-size: 18px;
+          font-weight: 600;
+          color: #1e293b;
+          margin: 0 0 4px;
+          line-height: 1.3;
+        }
+
+        .customer-meta {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+          font-size: 14px;
+          color: #64748b;
+
+          .separator {
+            color: #cbd5e1;
+          }
+
+          .reply-status {
+            &.overdue {
+              color: #dc2626;
+              font-weight: 500;
+            }
+          }
+        }
+      }
+    }
+
+    .priority-badges {
+      display: flex;
+      gap: 8px;
+      flex-shrink: 0;
+
+      .priority-badge {
+        display: flex;
+        align-items: center;
+        gap: 4px;
+        padding: 6px 10px;
+        border-radius: 12px;
+        font-size: 12px;
+        font-weight: 600;
+        text-transform: uppercase;
+        letter-spacing: 0.5px;
+
+        &.urgent {
+          background: #fef2f2;
+          color: #dc2626;
+          border: 1px solid #fecaca;
+        }
+
+        &.sensitive {
+          background: #fffbeb;
+          color: #d97706;
+          border: 1px solid #fed7aa;
+        }
+      }
+    }
   }
 
-  &:not(.urgent) {
-    background-color: rgba($success-color, 0.1);
-    color: $success-color;
+  .consultation-content-text {
+    margin-bottom: 20px;
+
+    p {
+      font-size: 16px;
+      line-height: 1.6;
+      color: #374151;
+      margin: 0;
+
+      :global(mark) {
+        background: #fef3c7;
+        color: #92400e;
+        padding: 2px 4px;
+        border-radius: 4px;
+        font-weight: 500;
+      }
+    }
+  }
+
+  .item-actions {
+    display: flex;
+    gap: 12px;
+    flex-wrap: wrap;
+
+    .action-btn {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      padding: 10px 16px;
+      border-radius: 8px;
+      font-size: 14px;
+      font-weight: 500;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      border: none;
+      outline: none;
+
+      &:disabled {
+        opacity: 0.6;
+        cursor: not-allowed;
+      }
+
+      &.primary {
+        background: #3b82f6;
+        color: #ffffff;
+
+        &:hover:not(:disabled) {
+          background: #2563eb;
+          transform: translateY(-1px);
+          box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
+        }
+      }
+
+      &.secondary {
+        background: #f8fafc;
+        color: #475569;
+        border: 1px solid #e2e8f0;
+
+        &:hover {
+          background: #f1f5f9;
+          border-color: #cbd5e1;
+        }
+      }
+
+      &.tertiary {
+        background: #f0fdf4;
+        color: #16a34a;
+        border: 1px solid #bbf7d0;
+
+        &:hover {
+          background: #dcfce7;
+          border-color: #86efac;
+        }
+      }
+    }
   }
 }
 
-.content {
-  color: $text-secondary;
-  margin: 0 0 16px;
-  line-height: 1.5;
+// 动画
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
 }
 
-.card-footer {
-  display: flex;
-  gap: 8px;
-
-  .ios-btn {
-    flex: 1;
-    padding: 10px;
-    border-radius: 8px;
-    font-size: 14px;
-    font-weight: 500;
-    text-align: center;
-    transition: all 0.2s ease;
+// 响应式设计
+@media (max-width: 768px) {
+  .page-header {
+    padding: 16px 20px;
+
+    .header-title h1 {
+      font-size: 20px;
+    }
+
+    .header-actions .refresh-btn {
+      padding: 8px 12px;
+      font-size: 13px;
+    }
+  }
+
+  .search-filter-section {
+    padding: 20px;
+
+    .search-container .search-input-wrapper .search-input {
+      font-size: 16px; // 防止iOS缩放
+    }
+
+    .filter-chips {
+      gap: 8px;
+
+      .filter-chip {
+        padding: 8px 12px;
+        font-size: 13px;
+      }
+    }
+  }
+
+  .consultation-content {
+    padding: 20px;
+
+    .consultation-item {
+      padding: 16px;
+
+      .item-header {
+        .customer-section {
+          .customer-avatar {
+            width: 40px;
+            height: 40px;
+            font-size: 16px;
+          }
+
+          .customer-details .customer-name {
+            font-size: 16px;
+          }
+        }
+      }
+
+      .consultation-content-text p {
+        font-size: 15px;
+      }
+
+      .item-actions {
+        gap: 8px;
+
+        .action-btn {
+          padding: 8px 12px;
+          font-size: 13px;
+        }
+      }
+    }
+  }
+}
+
+@media (max-width: 480px) {
+  .page-header {
+    .header-left {
+      gap: 12px;
+    }
+
+    .header-title h1 {
+      font-size: 18px;
+    }
+  }
+
+  .consultation-item {
+    .item-header {
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 12px;
 
-    &.outline {
-      background-color: transparent;
-      border: 1px solid $primary-color;
-      color: $primary-color;
+      .priority-badges {
+        align-self: flex-end;
+      }
     }
 
-    &:not(.outline) {
-      background-color: $primary-color;
-      color: white;
+    .item-actions {
+      .action-btn {
+        flex: 1;
+        justify-content: center;
+        min-width: 0;
+      }
     }
   }
 }

+ 369 - 56
src/app/pages/customer-service/dashboard/pages/consultation-list/consultation-list.component.ts

@@ -8,53 +8,179 @@ import { FormsModule } from '@angular/forms';
   standalone: true,
   imports: [CommonModule, RouterModule, FormsModule],
   template: `
-    <div class="ios-container">
-      <header class="ios-header">
-        <button class="ios-back-btn" (click)="goBack()">
-          <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <path d="M19 12H5M12 19l-7-7 7-7"/>
-          </svg>
-        </button>
-        <h1>客户咨询记录</h1>
+    <div class="consultation-container">
+      <!-- 页面头部 -->
+      <header class="page-header">
+        <div class="header-left">
+          <button class="back-btn" (click)="goBack()">
+            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <path d="M19 12H5M12 19l-7-7 7-7"/>
+            </svg>
+          </button>
+          <div class="header-title">
+            <h1>客户咨询记录</h1>
+            <p class="subtitle">共 {{ filteredConsultations.length }} 条咨询记录</p>
+          </div>
+        </div>
+        <div class="header-actions">
+          <button class="refresh-btn" (click)="refreshData()" [disabled]="isLoading">
+            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" [class.spinning]="isLoading">
+              <polyline points="23 4 23 10 17 10"></polyline>
+              <polyline points="1 20 1 14 7 14"></polyline>
+              <path d="m20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path>
+            </svg>
+            刷新
+          </button>
+        </div>
       </header>
 
-      <div class="ios-content">
-        <div class="toolbar">
-          <div class="search-bar">
-            <input type="text" placeholder="搜索咨询记录..." [(ngModel)]="keyword" (input)="applyFilters()">
-            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-              <circle cx="11" cy="11" r="8"/>
-              <line x1="21" y1="21" x2="16.65" y2="16.65"/>
+      <!-- 搜索和筛选区域 -->
+      <div class="search-filter-section">
+        <div class="search-container">
+          <div class="search-input-wrapper">
+            <svg class="search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <circle cx="11" cy="11" r="8"></circle>
+              <path d="m21 21-4.35-4.35"></path>
             </svg>
+            <input 
+              type="text" 
+              placeholder="搜索客户姓名、咨询内容..." 
+              [(ngModel)]="keyword" 
+              (input)="applyFilters()"
+              class="search-input"
+            >
+            <button *ngIf="keyword" class="clear-search" (click)="clearSearch()">
+              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <line x1="18" y1="6" x2="6" y2="18"></line>
+                <line x1="6" y1="6" x2="18" y2="18"></line>
+              </svg>
+            </button>
           </div>
-          <div class="quick-filters">
-            <label>
-              <input type="checkbox" [(ngModel)]="filterUnrepliedOver1h" (change)="applyFilters()"> 未回复超1小时
-            </label>
-            <label>
-              <input type="checkbox" [(ngModel)]="filterSensitive" (change)="applyFilters()"> 含敏感词
-            </label>
-          </div>
+        </div>
+        
+        <div class="filter-chips">
+          <button 
+            class="filter-chip" 
+            [class.active]="filterUnrepliedOver1h" 
+            (click)="toggleFilter('unreplied')"
+          >
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <circle cx="12" cy="12" r="10"></circle>
+              <polyline points="12 6 12 12 16 14"></polyline>
+            </svg>
+            未回复超1小时
+            <span *ngIf="filterUnrepliedOver1h" class="chip-count">{{ getUnrepliedCount() }}</span>
+          </button>
+          <button 
+            class="filter-chip" 
+            [class.active]="filterSensitive" 
+            (click)="toggleFilter('sensitive')"
+          >
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
+            </svg>
+            含敏感词
+            <span *ngIf="filterSensitive" class="chip-count">{{ getSensitiveCount() }}</span>
+          </button>
+          <button 
+            class="filter-chip" 
+            [class.active]="filterHighPriority" 
+            (click)="toggleFilter('priority')"
+          >
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <path d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"></path>
+            </svg>
+            紧急咨询
+            <span *ngIf="filterHighPriority" class="chip-count">{{ getHighPriorityCount() }}</span>
+          </button>
+        </div>
+      </div>
+
+      <!-- 咨询列表 -->
+      <div class="consultation-content">
+        <div *ngIf="isLoading" class="loading-state">
+          <div class="loading-spinner"></div>
+          <p>加载中...</p>
         </div>
 
-        <div class="consultation-list">
-          <div class="consultation-card" *ngFor="let item of filteredConsultations">
-            <div class="card-header">
-              <div class="customer-info">
-                <div class="avatar">{{item.customer.charAt(0)}}</div>
-                <div>
-                  <h3>{{item.customer}}</h3>
-                  <p class="time">{{item.time}}</p>
+        <div *ngIf="!isLoading && filteredConsultations.length === 0" class="empty-state">
+          <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1">
+            <circle cx="11" cy="11" r="8"></circle>
+            <path d="m21 21-4.35-4.35"></path>
+          </svg>
+          <h3>暂无咨询记录</h3>
+          <p>{{ keyword ? '没有找到匹配的咨询记录' : '还没有客户咨询记录' }}</p>
+        </div>
+
+        <div *ngIf="!isLoading && filteredConsultations.length > 0" class="consultation-list">
+          <div 
+            class="consultation-item" 
+            *ngFor="let item of filteredConsultations; trackBy: trackByConsultation"
+            [class.urgent]="item.priority === 'high'"
+            [class.sensitive]="containsSensitiveWords(item.content)"
+            [class.overdue]="isOverdue(item)"
+          >
+            <div class="item-header">
+              <div class="customer-section">
+                <div class="customer-avatar" [style.background-color]="getAvatarColor(item.customer)">
+                  {{ item.customer.charAt(0) }}
+                </div>
+                <div class="customer-details">
+                  <h3 class="customer-name">{{ item.customer }}</h3>
+                  <div class="customer-meta">
+                    <span class="consultation-time">{{ formatTime(item.time) }}</span>
+                    <span class="separator">•</span>
+                    <span class="reply-status" [class.overdue]="isOverdue(item)">
+                      {{ getReplyStatus(item) }}
+                    </span>
+                  </div>
                 </div>
               </div>
-              <span class="status-badge" [class.urgent]="item.priority === 'high'">
-                {{item.priority === 'high' ? '紧急' : '普通'}}
-              </span>
+              <div class="priority-badges">
+                <span *ngIf="item.priority === 'high'" class="priority-badge urgent">
+                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <path d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"></path>
+                  </svg>
+                  紧急
+                </span>
+                <span *ngIf="containsSensitiveWords(item.content)" class="priority-badge sensitive">
+                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
+                  </svg>
+                  敏感词
+                </span>
+              </div>
+            </div>
+            
+            <div class="consultation-content-text">
+              <p [innerHTML]="highlightKeyword(item.content)"></p>
             </div>
-            <p class="content">{{item.content}}</p>
-            <div class="card-footer">
-              <button class="ios-btn" (click)="openWeCom(item)" title="跳转企业微信">同步到企微</button>
-              <button class="ios-btn outline">查看详情</button>
+            
+            <div class="item-actions">
+              <button 
+                class="action-btn primary" 
+                (click)="syncToWeChat(item)"
+                [disabled]="item.syncing"
+              >
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <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 *ngIf="!item.syncing">同步到企微</span>
+                <span *ngIf="item.syncing">同步中...</span>
+              </button>
+              <button class="action-btn secondary" (click)="viewDetails(item)">
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
+                  <circle cx="12" cy="12" r="3"></circle>
+                </svg>
+                查看详情
+              </button>
+              <button class="action-btn tertiary" (click)="markAsReplied(item)">
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polyline points="20 6 9 17 4 12"></polyline>
+                </svg>
+                标记已回复
+              </button>
             </div>
           </div>
         </div>
@@ -74,13 +200,67 @@ export class ConsultationListComponent implements OnInit {
   keyword = '';
   filterUnrepliedOver1h = false;
   filterSensitive = false;
+  filterHighPriority = false;
+  isLoading = false;
 
-  private sensitivePatterns = /(发手机|发邮箱|找不到人)/;
+  private sensitivePatterns = /(发手机|发邮箱|找不到人|微信|QQ|电话|联系方式)/;
 
   consultations = [
-    {customer: '张先生', time: '10:30', content: '咨询关于厨房改造的预算和工期', priority: 'normal', lastReplyMinutes: 30, weComUserId: 'zhangxiansheng'},
-    {customer: '李女士', time: '11:45', content: '询问客厅设计风格建议,发手机给你', priority: 'high', lastReplyMinutes: 120, weComUserId: 'linnvshi'},
-    {customer: '王先生', time: '14:20', content: '需要全屋设计方案咨询,发邮箱吧', priority: 'normal', lastReplyMinutes: 75, weComUserId: 'wangxiansheng'}
+    {
+      id: '1',
+      customer: '张先生', 
+      time: '10:30', 
+      content: '咨询关于厨房改造的预算和工期,希望能在下个月开始施工', 
+      priority: 'normal', 
+      lastReplyMinutes: 30, 
+      weComUserId: 'zhangxiansheng',
+      syncing: false,
+      replied: false
+    },
+    {
+      id: '2',
+      customer: '李女士', 
+      time: '11:45', 
+      content: '询问客厅设计风格建议,发手机给你,希望尽快联系', 
+      priority: 'high', 
+      lastReplyMinutes: 120, 
+      weComUserId: 'linnvshi',
+      syncing: false,
+      replied: false
+    },
+    {
+      id: '3',
+      customer: '王先生', 
+      time: '14:20', 
+      content: '需要全屋设计方案咨询,发邮箱吧,预算在20万左右', 
+      priority: 'normal', 
+      lastReplyMinutes: 75, 
+      weComUserId: 'wangxiansheng',
+      syncing: false,
+      replied: false
+    },
+    {
+      id: '4',
+      customer: '陈女士', 
+      time: '09:15', 
+      content: '想了解卧室装修的具体流程和注意事项', 
+      priority: 'normal', 
+      lastReplyMinutes: 15, 
+      weComUserId: 'chennvshi',
+      syncing: false,
+      replied: true
+    },
+    {
+      id: '5',
+      customer: '刘先生', 
+      time: '16:30', 
+      content: '紧急!明天要看房,需要设计师现场指导,找不到人联系', 
+      priority: 'high', 
+      lastReplyMinutes: 180, 
+      weComUserId: 'liuxiansheng',
+      syncing: false,
+      replied: false
+    }
   ];
 
   filteredConsultations = [...this.consultations];
@@ -98,30 +278,163 @@ export class ConsultationListComponent implements OnInit {
   }
 
   applyFilters(): void {
-    const kw = this.keyword.trim().toLowerCase();
     this.filteredConsultations = this.consultations.filter(item => {
-      const matchKw = !kw || item.customer.toLowerCase().includes(kw) || item.content.toLowerCase().includes(kw);
-      const matchUnreplied = !this.filterUnrepliedOver1h || (item.lastReplyMinutes ?? 0) > 60;
-      const matchSensitive = !this.filterSensitive || this.sensitivePatterns.test(item.content);
-      return matchKw && matchUnreplied && matchSensitive;
+      const matchesKeyword = !this.keyword || 
+        item.customer.toLowerCase().includes(this.keyword.toLowerCase()) || 
+        item.content.toLowerCase().includes(this.keyword.toLowerCase());
+      const matchesUnreplied = !this.filterUnrepliedOver1h || item.lastReplyMinutes > 60;
+      const matchesSensitive = !this.filterSensitive || this.containsSensitiveWords(item.content);
+      const matchesPriority = !this.filterHighPriority || item.priority === 'high';
+      return matchesKeyword && matchesUnreplied && matchesSensitive && matchesPriority;
     });
   }
 
-  openWeCom(item: any): void {
-    const userId = item.weComUserId || encodeURIComponent(item.customer);
-    const wecomUrl = `wecom://message?username=${userId}`;
-    const webUrl = `https://work.weixin.qq.com/wework_admin/frame#contacts`;
+  toggleFilter(type: string): void {
+    switch (type) {
+      case 'unreplied':
+        this.filterUnrepliedOver1h = !this.filterUnrepliedOver1h;
+        break;
+      case 'sensitive':
+        this.filterSensitive = !this.filterSensitive;
+        break;
+      case 'priority':
+        this.filterHighPriority = !this.filterHighPriority;
+        break;
+    }
+    this.applyFilters();
+  }
+
+  clearSearch(): void {
+    this.keyword = '';
+    this.applyFilters();
+  }
+
+  refreshData(): void {
+    this.isLoading = true;
+    // 模拟数据刷新
+    setTimeout(() => {
+      this.isLoading = false;
+      this.applyFilters();
+    }, 1000);
+  }
+
+  containsSensitiveWords(content: string): boolean {
+    return this.sensitivePatterns.test(content);
+  }
+
+  isOverdue(item: any): boolean {
+    return item.lastReplyMinutes > 60 && !item.replied;
+  }
+
+  getReplyStatus(item: any): string {
+    if (item.replied) {
+      return '已回复';
+    }
+    if (item.lastReplyMinutes > 60) {
+      return `超时 ${Math.floor(item.lastReplyMinutes / 60)} 小时`;
+    }
+    return `${item.lastReplyMinutes} 分钟前`;
+  }
+
+  formatTime(time: string): string {
+    return `今天 ${time}`;
+  }
+
+  getAvatarColor(name: string): string {
+    const colors = ['#007AFF', '#34C759', '#FF9500', '#FF3B30', '#AF52DE', '#5AC8FA'];
+    const index = name.charCodeAt(0) % colors.length;
+    return colors[index];
+  }
+
+  highlightKeyword(content: string): string {
+    if (!this.keyword) return content;
+    const regex = new RegExp(`(${this.keyword})`, 'gi');
+    return content.replace(regex, '<mark>$1</mark>');
+  }
+
+  trackByConsultation(index: number, item: any): string {
+    return item.id;
+  }
+
+  getUnrepliedCount(): number {
+    return this.consultations.filter(item => item.lastReplyMinutes > 60 && !item.replied).length;
+  }
+
+  getSensitiveCount(): number {
+    return this.consultations.filter(item => this.containsSensitiveWords(item.content)).length;
+  }
+
+  getHighPriorityCount(): number {
+    return this.consultations.filter(item => item.priority === 'high').length;
+  }
+
+  syncToWeChat(item: any): void {
+    item.syncing = true;
+    
     try {
-      window.location.href = wecomUrl;
+      // 尝试打开企业微信应用
+      const wecomUrl = `wxwork://message/?username=${item.weComUserId}`;
+      window.open(wecomUrl, '_blank');
+      
+      // 模拟同步过程
+      setTimeout(() => {
+        item.syncing = false;
+        // 显示成功提示
+        this.showToast('已成功同步到企业微信');
+      }, 2000);
+      
+      // 备选方案:打开企业微信网页版
       setTimeout(() => {
+        const webUrl = `https://work.weixin.qq.com/wework_admin/frame#index`;
         window.open(webUrl, '_blank');
-      }, 800);
-    } catch (_) {
-      window.open(webUrl, '_blank');
+      }, 1000);
+    } catch (error) {
+      console.error('同步到企业微信失败:', error);
+      item.syncing = false;
+      this.showToast('同步失败,请检查企业微信客户端', 'error');
     }
   }
 
+  viewDetails(item: any): void {
+    // 这里可以导航到详情页面或打开模态框
+    console.log('查看详情:', item);
+    this.showToast(`正在查看 ${item.customer} 的咨询详情`);
+  }
+
+  markAsReplied(item: any): void {
+    item.replied = true;
+    this.showToast(`已标记 ${item.customer} 的咨询为已回复`);
+    this.applyFilters();
+  }
+
+  private showToast(message: string, type: 'success' | 'error' = 'success'): void {
+    // 简单的提示实现,实际项目中可以使用更完善的提示组件
+    const toast = document.createElement('div');
+    toast.textContent = message;
+    toast.style.cssText = `
+      position: fixed;
+      top: 20px;
+      right: 20px;
+      background: ${type === 'success' ? '#34C759' : '#FF3B30'};
+      color: white;
+      padding: 12px 20px;
+      border-radius: 8px;
+      z-index: 10000;
+      font-size: 14px;
+      box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+    `;
+    document.body.appendChild(toast);
+    
+    setTimeout(() => {
+      document.body.removeChild(toast);
+    }, 3000);
+  }
+
+  openWeCom(item: any): void {
+    this.syncToWeChat(item);
+  }
+
   goBack() {
-    history.back();
+    window.history.back();
   }
 }

+ 0 - 56
src/app/pages/customer-service/dashboard/pages/revenue-detail/revenue-detail.component.html

@@ -1,56 +0,0 @@
-<div class="ios-container">
-  <header class="ios-header">
-    <button class="ios-back-btn" (click)="goBack()">
-      <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-        <path d="M19 12H5M12 19l-7-7 7-7"/>
-      </svg>
-    </button>
-    <h1>成交详情</h1>
-    <button class="ios-share-btn">
-      <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-        <circle cx="18" cy="5" r="3"/>
-        <circle cx="6" cy="12" r="3"/>
-        <circle cx="18" cy="19" r="3"/>
-        <line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>
-        <line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
-      </svg>
-    </button>
-  </header>
-
-  <div class="ios-content">
-    <div class="summary-card">
-      <div class="amount">¥{{totalRevenue}}</div>
-      <div class="period">今日总成交额</div>
-      <div class="stats">
-        <div class="stat-item">
-          <div class="value">{{transactions.length}}</div>
-          <div class="label">成交项目</div>
-        </div>
-        <div class="stat-item">
-          <div class="value positive">+{{growthRate}}%</div>
-          <div class="label">环比增长</div>
-        </div>
-      </div>
-    </div>
-
-    <div class="transaction-list">
-      <div class="section-header">
-        <h2>交易记录</h2>
-        <button class="filter-btn">筛选</button>
-      </div>
-
-      <div class="transaction-item" *ngFor="let item of transactions">
-        <div class="icon">
-          <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <path d="M12 1v3M12 20v3M5 12H2M22 12h-3M6.2 6.2l-1.4 1.4M19.2 19.2l-1.4 1.4M6.2 17.8l-1.4-1.4M19.2 4.8l-1.4-1.4"/>
-          </svg>
-        </div>
-        <div class="details">
-          <div class="project">{{item.project}}</div>
-          <div class="customer">{{item.customer}}</div>
-        </div>
-        <div class="amount">¥{{item.amount}}</div>
-      </div>
-    </div>
-  </div>
-</div>

+ 0 - 161
src/app/pages/customer-service/dashboard/pages/revenue-detail/revenue-detail.component.scss

@@ -1,161 +0,0 @@
-@import "../../../customer-service-styles.scss";
-
-.ios-container {
-  max-width: 100%;
-  height: 100vh;
-  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
-  background-color: $background-tertiary;
-}
-
-.ios-header {
-  display: flex;
-  align-items: center;
-  padding: 16px;
-  background-color: $background-primary;
-  border-bottom: 1px solid $border-color;
-  position: sticky;
-  top: 0;
-  z-index: 10;
-
-  h1 {
-    font-size: 20px;
-    font-weight: 600;
-    margin: 0 auto;
-  }
-}
-
-.ios-back-btn {
-  background: none;
-  border: none;
-  padding: 8px;
-  z-index: 11;
-}
-
-.ios-share-btn {
-  background: none;
-  border: none;
-  padding: 8px;
-  z-index: 11;
-}
-
-.ios-content {
-  padding: 16px;
-}
-
-.summary-card {
-  background: linear-gradient(135deg, $primary-color, $primary-dark);
-  border-radius: 16px;
-  padding: 24px;
-  margin-bottom: 16px;
-  color: white;
-  box-shadow: $shadow-md;
-}
-
-.amount {
-  font-size: 32px;
-  font-weight: 700;
-  margin-bottom: 8px;
-}
-
-.period {
-  font-size: 16px;
-  opacity: 0.9;
-  margin-bottom: 24px;
-}
-
-.stats {
-  display: flex;
-  justify-content: space-between;
-}
-
-.stat-item {
-  text-align: center;
-
-  .value {
-    font-size: 20px;
-    font-weight: 600;
-    margin-bottom: 4px;
-
-    &.positive {
-      color: $success-color;
-    }
-  }
-
-  .label {
-    font-size: 14px;
-    opacity: 0.8;
-  }
-}
-
-.transaction-list {
-  background-color: $background-primary;
-  border-radius: 16px;
-  padding: 16px;
-  box-shadow: $shadow-sm;
-}
-
-.section-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16px;
-
-  h2 {
-    font-size: 18px;
-    margin: 0;
-    color: $text-primary;
-  }
-}
-
-.filter-btn {
-  background: none;
-  border: none;
-  color: $primary-color;
-  font-size: 14px;
-  font-weight: 500;
-}
-
-.transaction-item {
-  display: flex;
-  align-items: center;
-  padding: 12px 0;
-  border-bottom: 1px solid $border-color;
-
-  &:last-child {
-    border-bottom: none;
-  }
-}
-
-.icon {
-  width: 40px;
-  height: 40px;
-  border-radius: 20px;
-  background-color: rgba($primary-color, 0.1);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 12px;
-  color: $primary-color;
-}
-
-.details {
-  flex: 1;
-
-  .project {
-    font-size: 16px;
-    font-weight: 500;
-    color: $text-primary;
-    margin-bottom: 4px;
-  }
-
-  .customer {
-    font-size: 14px;
-    color: $text-tertiary;
-  }
-}
-
-.amount {
-  font-size: 16px;
-  font-weight: 600;
-  color: $success-color;
-}

+ 0 - 120
src/app/pages/customer-service/dashboard/pages/revenue-detail/revenue-detail.component.ts

@@ -1,120 +0,0 @@
-import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { RouterModule } from '@angular/router';
-
-@Component({
-  selector: 'app-revenue-detail',
-  standalone: true,
-  imports: [CommonModule, RouterModule],
-  template: `
-    <div class="ios-container">
-      <header class="ios-header">
-        <h1>今日成交详情</h1>
-        <button class="ios-back-btn" (click)="goBack()">
-          <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <path d="M19 12H5M12 19l-7-7 7-7"/>
-          </svg>
-        </button>
-      </header>
-      
-      <div class="ios-content">
-        <div class="ios-summary-card">
-          <h2>¥28,500</h2>
-          <p>今日总成交额</p>
-          <div class="ios-stats">
-            <div class="ios-stat-item">
-              <span>5</span>
-              <p>成交项目</p>
-            </div>
-            <div class="ios-stat-item">
-              <span>+28%</span>
-              <p>环比增长</p>
-            </div>
-          </div>
-        </div>
-        
-        <div class="ios-card" *ngFor="let item of transactions">
-          <div class="ios-card-header">
-            <h3>{{item.project}}</h3>
-            <span class="ios-amount">¥{{item.amount}}</span>
-          </div>
-          <p>{{item.customer}} · {{item.time}}</p>
-        </div>
-      </div>
-    </div>
-  `,
-  styles: [`
-    .ios-container {
-      max-width: 800px;
-      margin: 0 auto;
-      font-family: -apple-system, BlinkMacSystemFont, sans-serif;
-    }
-    
-    .ios-header {
-      display: flex;
-      align-items: center;
-      padding: 16px;
-      background: #f2f2f7;
-      border-bottom: 1px solid #d1d1d6;
-    }
-    
-    .ios-back-btn {
-      background: none;
-      border: none;
-      margin-right: 16px;
-    }
-    
-    .ios-content {
-      padding: 16px;
-    }
-    
-    .ios-summary-card {
-      background: linear-gradient(135deg, #007aff, #0040dd);
-      color: white;
-      border-radius: 12px;
-      padding: 24px;
-      margin-bottom: 16px;
-      text-align: center;
-    }
-    
-    .ios-stats {
-      display: flex;
-      justify-content: space-around;
-      margin-top: 16px;
-    }
-    
-    .ios-stat-item {
-      text-align: center;
-    }
-    
-    .ios-card {
-      background: white;
-      border-radius: 12px;
-      padding: 16px;
-      margin-bottom: 16px;
-      box-shadow: 0 1px 3px rgba(0,0,0,0.1);
-    }
-    
-    .ios-card-header {
-      display: flex;
-      justify-content: space-between;
-      margin-bottom: 8px;
-    }
-    
-    .ios-amount {
-      color: #34c759;
-      font-weight: bold;
-    }
-  `]
-})
-export class RevenueDetailComponent {
-  transactions = [
-    {project: '现代简约客厅设计', amount: '12,000', customer: '张先生', time: '10:30'},
-    {project: '欧式厨房改造', amount: '8,500', customer: '李女士', time: '11:45'},
-    {project: '三居室全屋设计', amount: '8,000', customer: '王先生', time: '14:20'}
-  ];
-  
-  goBack() {
-    history.back();
-  }
-}

+ 6 - 6
src/app/pages/hr/employee-detail/employee-detail.html

@@ -258,7 +258,7 @@
 </div>
 
 <!-- iOS风格审核对话框 -->
-<div class="ios-modal" [class.show]="showReviewDialog">
+<div class="ios-modal" [class.show]="showReviewDialog()">
   <div class="modal-backdrop" (click)="closeReviewDialog()"></div>
   <div class="modal-content">
     <div class="modal-header">
@@ -273,19 +273,19 @@
           <h4>审核结果</h4>
         </div>
         <div class="ios-list">
-          <div class="list-item clickable" [class.selected]="reviewResult === 'approved'" (click)="reviewResult = 'approved'">
+          <div class="list-item clickable" [class.selected]="reviewResult() === 'approved'" (click)="reviewResult.set('approved')">
             <div class="item-content">
               <span class="item-label">通过</span>
             </div>
-            <div class="item-action" *ngIf="reviewResult === 'approved'">
+            <div class="item-action" *ngIf="reviewResult() === 'approved'">
               <mat-icon>check</mat-icon>
             </div>
           </div>
-          <div class="list-item clickable" [class.selected]="reviewResult === 'rejected'" (click)="reviewResult = 'rejected'">
+          <div class="list-item clickable" [class.selected]="reviewResult() === 'rejected'" (click)="reviewResult.set('rejected')">
             <div class="item-content">
               <span class="item-label">拒绝</span>
             </div>
-            <div class="item-action" *ngIf="reviewResult === 'rejected'">
+            <div class="item-action" *ngIf="reviewResult() === 'rejected'">
               <mat-icon>check</mat-icon>
             </div>
           </div>
@@ -296,7 +296,7 @@
           <h4>审核备注</h4>
         </div>
         <div class="ios-textarea">
-          <textarea [(ngModel)]="reviewComment" rows="4" placeholder="请输入审核意见..."></textarea>
+          <textarea [ngModel]="reviewComment()" (ngModelChange)="reviewComment.set($event)" rows="4" placeholder="请输入审核意见..."></textarea>
         </div>
       </div>
     </div>

+ 26 - 0
src/app/pages/hr/employee-detail/employee-detail.ts

@@ -48,6 +48,11 @@ export class EmployeeDetailComponent implements OnInit {
 
   employee = signal<Employee | null>(null);
   screeningForm: FormGroup;
+  
+  // 审核相关属性
+  showReviewDialog = signal(false);
+  reviewComment = signal('');
+  reviewResult = signal<'approved' | 'rejected' | ''>('');
 
   // 模拟员工数据
   private mockEmployees: Employee[] = [
@@ -317,4 +322,25 @@ export class EmployeeDetailComponent implements OnInit {
       verticalPosition: 'top'
     });
   }
+
+  // 审核对话框方法
+  openReviewDialog() {
+    this.showReviewDialog.set(true);
+    this.reviewComment.set('');
+  }
+
+  closeReviewDialog() {
+    this.showReviewDialog.set(false);
+    this.reviewComment.set('');
+  }
+
+  submitReview() {
+    if (this.reviewComment().trim()) {
+      // 这里可以添加提交审核的逻辑
+      this.showSnackBar('审核意见已提交');
+      this.closeReviewDialog();
+    } else {
+      this.showSnackBar('请填写审核意见');
+    }
+  }
 }

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است