Browse Source

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

0235711 2 weeks ago
parent
commit
1ffab3ec18
30 changed files with 3867 additions and 1707 deletions
  1. 2 2
      src/app/app.routes.ts
  2. 135 123
      src/app/pages/customer-service/consultation-order/consultation-order.html
  3. 607 183
      src/app/pages/customer-service/consultation-order/consultation-order.scss
  4. 206 25
      src/app/pages/customer-service/consultation-order/consultation-order.ts
  5. 9 14
      src/app/pages/customer-service/customer-service-layout/customer-service-layout.html
  6. 2 2
      src/app/pages/customer-service/customer-service-layout/customer-service-layout.ts
  7. 0 5
      src/app/pages/customer-service/customer-service.routes.ts
  8. 90 36
      src/app/pages/customer-service/dashboard/dashboard.html
  9. 0 6
      src/app/pages/customer-service/dashboard/dashboard.routes.ts
  10. 255 30
      src/app/pages/customer-service/dashboard/dashboard.scss
  11. 139 20
      src/app/pages/customer-service/dashboard/dashboard.ts
  12. 535 113
      src/app/pages/customer-service/dashboard/pages/consultation-list/consultation-list.component.scss
  13. 369 56
      src/app/pages/customer-service/dashboard/pages/consultation-list/consultation-list.component.ts
  14. 0 56
      src/app/pages/customer-service/dashboard/pages/revenue-detail/revenue-detail.component.html
  15. 0 161
      src/app/pages/customer-service/dashboard/pages/revenue-detail/revenue-detail.component.scss
  16. 0 120
      src/app/pages/customer-service/dashboard/pages/revenue-detail/revenue-detail.component.ts
  17. 197 295
      src/app/pages/customer-service/project-detail/project-detail.html
  18. 162 0
      src/app/pages/customer-service/project-detail/project-detail.scss
  19. 189 48
      src/app/pages/customer-service/project-detail/project-detail.ts
  20. 20 18
      src/app/pages/customer-service/project-detail/refund-request-dialog.ts
  21. 216 153
      src/app/pages/customer-service/project-list/project-list.html
  22. 352 0
      src/app/pages/customer-service/project-list/project-list.scss
  23. 208 98
      src/app/pages/customer-service/project-list/project-list.ts
  24. 2 2
      src/app/pages/hr/designer-profile/designer-profile.html
  25. 0 1
      src/app/pages/hr/designer-profile/designer-profile.ts
  26. 6 6
      src/app/pages/hr/employee-detail/employee-detail.html
  27. 45 36
      src/app/pages/hr/employee-detail/employee-detail.ts
  28. 33 10
      src/app/pages/hr/employee-records/employee-records.html
  29. 30 81
      src/app/pages/hr/employee-records/employee-records.scss
  30. 58 7
      src/app/pages/hr/employee-records/employee-records.ts

+ 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: '今日成交详情' }
+    
     ]
   },
 

+ 135 - 123
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,8 +16,51 @@
     </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">
+  <div class="main-content">
 
     <!-- 客户信息卡片 -->
     <section class="info-card customer-card">
@@ -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>
@@ -159,87 +227,47 @@
             </div>
           </div>
           
-          <!-- 偏好标签组 -->
-          <div class="form-section">
-            <h3 class="section-title">偏好标签</h3>
-            <div class="tags-section">
-              <div class="current-tags">
-                <mat-chip-grid #chipList aria-label="偏好标签">
-                  <mat-chip-row
-                    *ngFor="let tag of preferenceTags"
-                    [removable]="true"
-                    (removed)="removePreferenceTag(tag)"
-                    class="preference-chip"
-                  >
-                    {{ tag }}
-                    <button matChipRemove aria-label="移除 {{tag}}">
-                      <mat-icon>cancel</mat-icon>
-                    </button>
-                  </mat-chip-row>
-                  <input
-                    placeholder="添加自定义标签..."
-                    [matChipInputFor]="chipList"
-                    [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
-                    [matChipInputAddOnBlur]="addOnBlur"
-                    (matChipInputTokenEnd)="addPreferenceTag($event)"
-                    class="tag-input"
-                  />
-                </mat-chip-grid>
-              </div>
-              
-              <!-- 预设标签选项 -->
-              <div class="preset-tags">
-                <div class="preset-header">
-                  <span class="preset-label">快速选择</span>
-                </div>
-                <div class="preset-grid">
-                  <button
-                    *ngFor="let tag of preferenceTagOptions"
-                    type="button"
-                    class="preset-tag"
-                    (click)="addFromPreset(tag)"
-                    [class.selected]="preferenceTags.includes(tag)"
-                  >
-                    {{ tag }}
-                  </button>
-                </div>
-              </div>
-            </div>
-          </div>
-          
-          <!-- 备注信息组 -->
-          <div class="form-section">
-            <div class="form-field full-width">
-              <label for="remark" class="field-label">备注信息</label>
-              <textarea id="remark" formControlName="remark" rows="3" placeholder="请输入其他备注信息" class="field-textarea"></textarea>
-            </div>
-          </div>
         </form>
       </div>
     </section>
 
-    <!-- 需求信息卡片 -->
+    <!-- 项目需求卡片 -->
     <section class="info-card requirement-card">
       <div class="card-header">
         <div class="header-left">
           <div class="icon-wrapper requirement-icon">
             <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-              <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>
+              <rect x="3" y="4" width="18" height="14" rx="2" ry="2"></rect>
+              <line x1="8" y1="2" x2="16" y2="2"></line>
             </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" (click)="syncProjectInfo()" [disabled]="isSyncing()">
+            <mat-spinner *ngIf="isSyncing()" diameter="16"></mat-spinner>
+            <span *ngIf="!isSyncing()">从聊天记录提取</span>
+          </button>
+        </div>
       </div>
-      
+
+      <!-- Minimal 6 fields guide -->
       <div class="card-content">
-        <form [formGroup]="requirementForm" class="requirement-form">
+      <!-- Minimal 6 fields guide -->
+       <div class="minimal-guide">
+         <span class="guide-title">最小创建需6项:</span>
+         <span class="guide-item">客户姓名</span>
+         <span class="guide-item">手机</span>
+         <span class="guide-item">风格</span>
+         <span class="guide-item">项目小组</span>
+         <span class="guide-item">首付款</span>
+         <span class="guide-item">首稿时间</span>
+       </div>
+
+       <form [formGroup]="requirementForm" class="requirement-form">
           <!-- 基础需求组 -->
           <div class="form-section">
             <h3 class="section-title">基础需求</h3>
@@ -262,41 +290,39 @@
                   <option value="50万以上">50万以上</option>
                 </select>
               </div>
-              <div class="form-field">
-                <label for="area" class="field-label">房屋面积 <span class="required">*</span></label>
-                <div class="input-with-unit">
-                  <input type="number" id="area" formControlName="area" placeholder="请输入面积" class="field-input">
-                  <span class="input-unit">㎡</span>
-                </div>
-              </div>
             </div>
           </div>
           
-          <!-- 房屋信息组 -->
+          
+          <!-- 项目管理组 -->
           <div class="form-section">
-            <h3 class="section-title">房屋信息</h3>
+            <h3 class="section-title">项目管理</h3>
             <div class="form-grid">
               <div class="form-field">
-                <label for="houseType" class="field-label">户型 <span class="required">*</span></label>
-                <select id="houseType" formControlName="houseType" class="field-select">
-                  <option value="">请选择户型</option>
-                  <option *ngFor="let houseType of houseTypeOptions" [value]="houseType">{{ houseType }}</option>
+                <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="floor" class="field-label">楼层</label>
-                <input type="number" id="floor" formControlName="floor" placeholder="请输入楼层" class="field-input">
+                <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="decorationType" class="field-label">装修类型 <span class="required">*</span></label>
-                <select id="decorationType" formControlName="decorationType" class="field-select">
-                  <option value="">请选择装修类型</option>
-                  <option *ngFor="let type of decorationTypeOptions" [value]="type">{{ type }}</option>
-                </select>
+                <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>
@@ -411,7 +437,6 @@
       </div>
     </section>
 
-    <!-- 操作按钮区域 -->
     <section class="action-section">
       <div class="action-buttons">
         <button 
@@ -423,25 +448,12 @@
             <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"></path>
           </svg>
           <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" *ngIf="isSubmitting()" class="animate-spin">
-            <path d="M21 12a9 9 0 11-6.219-8.56"></path>
+            <path d="M21 12a9 9 0 1 1-6.219-8.56"></path>
           </svg>
           <span *ngIf="!isSubmitting()">创建项目</span>
           <span *ngIf="isSubmitting()">提交中...</span>
         </button>
-        <button 
-          class="btn-secondary btn-lg" 
-          (click)="createProjectGroup()"
-          [disabled]="isSubmitting() || !requirementForm.valid || !customerForm.valid"
-        >
-          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
-            <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
-            <circle cx="9" cy="7" r="4"></circle>
-            <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
-            <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
-          </svg>
-          一键拉群
-        </button>
       </div>
     </section>
-  </main>
+  </div>
 </div>

+ 607 - 183
src/app/pages/customer-service/consultation-order/consultation-order.scss

@@ -23,98 +23,354 @@ $heading-font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-ser
 $grid-gap: 16px;
 $card-padding: 16px;
 
-// 主容器样式
+// 主容器样式 - iOS风格白色背景
 .consultation-order-container {
-  background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
   min-height: 100vh;
-  padding: 0;
-  animation: fadeIn 0.5s ease-in-out;
+  background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
+  padding: 24px;
+  font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+  animation: fadeIn 0.5s ease-out;
+  
+  @media (max-width: 768px) {
+    padding: 16px;
+  }
+}
+
+// 主表单容器 - dashboard卡片风格
+.order-form {
+  background-color: #ffffff;
+  border-radius: 20px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  padding: 32px;
+  margin-bottom: 32px;
+  transition: all 0.3s ease;
+  animation: fadeIn 0.5s ease-out;
+  border: 1px solid rgba(0, 0, 0, 0.05);
+  position: relative;
+  overflow: hidden;
+  
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 1px;
+    background: linear-gradient(90deg, transparent 0%, rgba(0, 122, 255, 0.3) 50%, transparent 100%);
+  }
+  
+  &:hover {
+    box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
+    transform: translateY(-2px);
+  }
+  
+  @media (max-width: 768px) {
+    padding: 24px;
+    margin-bottom: 24px;
+    border-radius: 16px;
+  }
 }
 
 .main-content {
   max-width: 1200px;
   margin: 0 auto;
-  padding: 24px;
+  padding: 0;
   
   display: grid;
   grid-template-columns: 1fr;
-  gap: 32px;
+  gap: 40px;
   
   @media (max-width: 768px) {
-    padding: 16px;
-    gap: 20px;
+    gap: 24px;
   }
 }
 
-// 页面标题样式
+// iOS风格页面头部样式
 .page-header {
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-  color: white;
-  padding: 40px 0;
-  margin: -24px -24px 0 -24px;
-  border-radius: 0 0 24px 24px;
-  box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
+  text-align: center;
+  margin-bottom: 32px;
+  padding: 32px 0;
+  background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
+  border-radius: 16px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
+  position: relative;
+  overflow: hidden;
+  
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 2px;
+    background: linear-gradient(90deg, #007AFF 0%, #34C759 100%);
+  }
+  
+  h1 {
+    font-size: 36px;
+    font-weight: 800;
+    color: #1c1c1e;
+    margin: 0 0 12px;
+    line-height: 1.1;
+    letter-spacing: -0.6px;
+    font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'PingFang SC', 'Helvetica Neue', sans-serif;
+    background: linear-gradient(135deg, #1c1c1e 0%, #3c3c43 100%);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
+  }
+  
+  .header-meta {
+    font-size: 16px;
+    color: #6c757d;
+    margin: 0;
+    line-height: 1.5;
+    font-weight: 500;
+    font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'PingFang SC', 'Helvetica Neue', sans-serif;
+    opacity: 0.9;
+  }
+  
+  .subtitle {
+    font-size: 15px;
+    color: #8e8e93;
+    margin: 0;
+    line-height: 1.4;
+    font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'PingFang SC', 'Helvetica Neue', sans-serif;
+  }
   
   @media (max-width: 768px) {
-    margin: -16px -16px 0 -16px;
-    padding: 32px 0;
+    padding: 24px 16px;
+    margin-bottom: 24px;
+    
+    h1 {
+      font-size: 28px;
+      font-weight: 700;
+    }
+    
+    .header-meta {
+      font-size: 14px;
+    }
   }
+}
+
+// 订单创建方式选择样式
+.creation-method-section {
+  margin-bottom: 32px;
+  animation: fadeIn 0.6s ease-out;
+}
+
+.method-selector {
+  background: #ffffff;
+  border-radius: 16px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
+  padding: 24px;
+  border: 1px solid #f0f0f0;
   
-  .header-content {
-    max-width: 1200px;
-    margin: 0 auto;
-    padding: 0 24px;
+  .method-title {
+    font-size: 18px;
+    font-weight: 600;
+    color: #1c1c1e;
+    margin-bottom: 20px;
     text-align: center;
+    font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'PingFang SC', sans-serif;
+  }
+}
+
+.method-options {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 16px;
+  
+  @media (max-width: 768px) {
+    grid-template-columns: 1fr;
+    gap: 12px;
+  }
+}
+
+.method-option {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  padding: 20px;
+  background: #ffffff;
+  border: 2px solid #e5e5ea;
+  border-radius: 12px;
+  cursor: pointer;
+  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+  position: relative;
+  overflow: hidden;
+  
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 0;
+    height: 100%;
+    background: linear-gradient(135deg, rgba(0, 122, 255, 0.05) 0%, rgba(52, 199, 89, 0.05) 100%);
+    transition: width 0.3s ease;
+    z-index: 1;
+  }
+  
+  &:hover {
+    border-color: #007AFF;
+    box-shadow: 0 8px 25px rgba(0, 122, 255, 0.15);
+    transform: translateY(-2px);
     
-    @media (max-width: 768px) {
-      padding: 0 16px;
+    &::before {
+      width: 100%;
+    }
+    
+    .option-icon {
+      transform: scale(1.1);
+      box-shadow: 0 4px 12px rgba(0, 122, 255, 0.3);
+    }
+    
+    .option-title {
+      color: #007AFF;
     }
   }
   
-  h1 {
-    font-size: 2.5rem;
-    font-weight: 700;
-    margin: 0;
-    text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+  &.active {
+    border-color: #007AFF;
+    background: linear-gradient(135deg, #ffffff 0%, #f0f8ff 100%);
+    box-shadow: 0 6px 20px rgba(0, 122, 255, 0.2);
     
-    @media (max-width: 768px) {
-      font-size: 2rem;
+    &::before {
+      width: 100%;
+      background: linear-gradient(135deg, rgba(0, 122, 255, 0.08) 0%, rgba(52, 199, 89, 0.08) 100%);
+    }
+    
+    .option-icon {
+      background: linear-gradient(135deg, #007AFF 0%, #34C759 100%);
+      color: #ffffff;
+      box-shadow: 0 4px 12px rgba(0, 122, 255, 0.4);
+    }
+    
+    .option-title {
+      color: #007AFF;
+      font-weight: 600;
+    }
+    
+    .option-badge {
+      background: linear-gradient(135deg, #007AFF 0%, #34C759 100%);
+      color: #ffffff;
+      animation: pulse 2s infinite;
     }
   }
   
-  .subtitle {
-    font-size: 1.1rem;
-    margin-top: 8px;
-    font-weight: 400;
-    opacity: 0.9;
+  @media (max-width: 768px) {
+    padding: 16px;
+    gap: 12px;
+  }
+}
+
+.option-icon {
+  width: 48px;
+  height: 48px;
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #f2f2f7;
+  color: #8e8e93;
+  transition: all 0.3s ease;
+  position: relative;
+  z-index: 2;
+  
+  &.miniprogram-icon {
+    background: linear-gradient(135deg, #e3f2fd 0%, #f0f8ff 100%);
+    color: #1976d2;
+  }
+  
+  &.manual-icon {
+    background: linear-gradient(135deg, #fff3e0 0%, #fef7ed 100%);
+    color: #f57c00;
+  }
+  
+  svg {
+    width: 24px;
+    height: 24px;
+    transition: transform 0.3s ease;
+  }
+}
+
+.option-content {
+  flex: 1;
+  position: relative;
+  z-index: 2;
+}
+
+.option-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #1c1c1e;
+  margin: 0 0 4px;
+  transition: all 0.3s ease;
+  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'PingFang SC', sans-serif;
+}
+
+.option-desc {
+  font-size: 13px;
+  color: #8e8e93;
+  margin: 0;
+  line-height: 1.4;
+  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'PingFang SC', sans-serif;
+}
+
+.option-badge {
+  background: linear-gradient(135deg, #34C759 0%, #30D158 100%);
+  color: #ffffff;
+  padding: 4px 8px;
+  border-radius: 6px;
+  font-size: 11px;
+  font-weight: 600;
+  text-transform: uppercase;
+  letter-spacing: 0.5px;
+  position: relative;
+  z-index: 2;
+  transition: all 0.3s ease;
+  box-shadow: 0 2px 6px rgba(52, 199, 89, 0.3);
+}
+
+@keyframes pulse {
+  0%, 100% {
+    transform: scale(1);
+  }
+  50% {
+    transform: scale(1.05);
   }
 }
 
-// 成功提示样式
+// 成功提示样式 - dashboard设计
 .success-toast {
   position: fixed;
   top: 20px;
-  left: 50%;
-  transform: translateX(-50%);
-  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
-  color: white;
-  padding: 12px 24px;
-  border-radius: 50px;
-  box-shadow: 0 8px 32px rgba(16, 185, 129, 0.3);
-  text-align: center;
-  font-weight: 500;
-  animation: slideIn 0.5s ease-out;
-  border: 1px solid rgba(255, 255, 255, 0.2);
+  right: 20px;
+  background-color: #ffffff;
+  color: #1c1c1e;
+  padding: 16px 20px;
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+  border-left: 4px solid #34c759;
   z-index: 1000;
+  animation: slideInRight 0.3s ease-out;
+  min-width: 280px;
   
   .toast-content {
     display: flex;
     align-items: center;
-    gap: 8px;
-  }
-  
-  svg {
-    flex-shrink: 0;
+    gap: 12px;
+    
+    svg {
+      flex-shrink: 0;
+      color: #34c759;
+    }
+    
+    span {
+      font-weight: 500;
+      font-size: 16px;
+    }
   }
 }
 
@@ -138,7 +394,8 @@ $card-padding: 16px;
   }
 }
 
-@keyframes slideInUp {
+// 动画效果 - dashboard风格
+@keyframes fadeIn {
   from {
     opacity: 0;
     transform: translateY(20px);
@@ -149,89 +406,132 @@ $card-padding: 16px;
   }
 }
 
-// 现代化信息卡片样式
+@keyframes slideInUp {
+  from {
+    opacity: 0;
+    transform: translateY(30px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+// iOS风格信息卡片样式
 .info-card {
-  background: white;
-  border-radius: 20px;
-  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
-  border: 1px solid rgba(0, 0, 0, 0.05);
+  background: #ffffff;
+  border-radius: 16px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+  border: 1px solid rgba(0, 0, 0, 0.06);
   overflow: hidden;
-  transition: all 0.3s ease;
-  animation: slideInUp 0.5s ease-out;
-  animation-fill-mode: both;
-  
-  &:hover {
-    transform: translateY(-2px);
-    box-shadow: 0 12px 48px rgba(0, 0, 0, 0.12);
-  }
+  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+  animation: fadeIn 0.3s ease-out;
+  margin-bottom: 20px;
+  position: relative;
   
-  &:nth-child(1) {
-    animation-delay: 0.1s;
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 1px;
+    background: linear-gradient(90deg, transparent 0%, rgba(0, 122, 255, 0.2) 50%, transparent 100%);
+    opacity: 0;
+    transition: opacity 0.3s ease;
   }
   
-  &:nth-child(2) {
-    animation-delay: 0.2s;
+  &:hover {
+    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
+    transform: translateY(-2px);
+    border-color: rgba(0, 122, 255, 0.1);
+    
+    &::before {
+      opacity: 1;
+    }
   }
   
-  &:nth-child(3) {
-    animation-delay: 0.3s;
+  &:last-child {
+    margin-bottom: 0;
   }
 }
 
-// 卡片头部样式
+// iOS风格卡片头部样式
 .card-header {
-  padding: 24px 32px 20px;
-  border-bottom: 1px solid #f1f5f9;
-  background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+  padding: 20px 24px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.08);
+  background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
+  position: relative;
+  
+  &::after {
+    content: '';
+    position: absolute;
+    bottom: 0;
+    left: 24px;
+    right: 24px;
+    height: 1px;
+    background: linear-gradient(90deg, transparent 0%, rgba(0, 122, 255, 0.1) 50%, transparent 100%);
+  }
   
   @media (max-width: 768px) {
-    padding: 20px 24px 16px;
+    padding: 16px 20px;
+    
+    &::after {
+      left: 20px;
+      right: 20px;
+    }
   }
   
   .header-left {
     display: flex;
     align-items: center;
-    gap: 16px;
+    gap: 12px;
   }
   
   .icon-wrapper {
-    width: 48px;
-    height: 48px;
-    border-radius: 12px;
+    width: 32px;
+    height: 32px;
+    border-radius: 8px;
     display: flex;
     align-items: center;
     justify-content: center;
     flex-shrink: 0;
+    transition: all 0.2s ease;
     
     &.customer-icon {
-      background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
-      color: white;
+      background-color: #007AFF;
+      color: #ffffff;
     }
     
     &.requirement-icon {
-      background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
-      color: white;
+      background-color: #007AFF;
+      color: #ffffff;
     }
     
     &.price-icon {
-      background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
-      color: white;
+      background-color: #007AFF;
+      color: #ffffff;
+    }
+    
+    &.consultation-icon {
+      background-color: #007AFF;
+      color: #ffffff;
     }
   }
   
   .header-text {
     h2 {
-      font-size: 1.5rem;
-      font-weight: 700;
-      color: #1e293b;
+      font-size: 17px;
+      font-weight: 600;
+      color: #000000;
       margin: 0;
-      line-height: 1.2;
+      line-height: 1.3;
     }
     
     p {
-      font-size: 0.875rem;
-      color: #64748b;
-      margin: 4px 0 0;
+      font-size: 13px;
+      color: #8e8e93;
+      margin: 2px 0 0;
       line-height: 1.4;
     }
   }
@@ -239,112 +539,164 @@ $card-padding: 16px;
 
 // 卡片内容样式
 .card-content {
-  padding: 32px;
+  padding: 32px 24px;
+  background: #ffffff;
   
   @media (max-width: 768px) {
-    padding: 24px;
+    padding: 24px 20px;
   }
 }
 
-// 表单分组样式
+// 表单分组样式 - dashboard风格
 .form-section {
-  margin-bottom: 32px;
+  background-color: #ffffff;
+  border-radius: 18px;
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
+  padding: 28px;
+  margin-bottom: 28px;
+  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+  animation: fadeIn 0.5s ease-out;
+  border: 1px solid rgba(0, 0, 0, 0.04);
+  position: relative;
+  overflow: hidden;
+  
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 2px;
+    background: linear-gradient(90deg, #007AFF 0%, #34C759 50%, #FF9500 100%);
+    opacity: 0;
+    transition: opacity 0.3s ease;
+  }
+  
+  &:hover {
+    box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
+    transform: translateY(-3px);
+    
+    &::before {
+      opacity: 1;
+    }
+  }
   
   &:last-child {
     margin-bottom: 0;
   }
   
+  &:nth-child(1) {
+    animation-delay: 0s;
+  }
+  
+  &:nth-child(2) {
+    animation-delay: 0.1s;
+  }
+  
+  &:nth-child(3) {
+    animation-delay: 0.2s;
+  }
+  
+  &:nth-child(4) {
+    animation-delay: 0.3s;
+  }
+  
+  &:nth-child(5) {
+    animation-delay: 0.4s;
+  }
+  
+  @media (max-width: 768px) {
+    padding: 20px;
+    margin-bottom: 20px;
+    border-radius: 14px;
+  }
+  
   .section-title {
-    font-size: 1.125rem;
-    font-weight: 600;
-    color: #1e293b;
-    margin: 0 0 20px;
-    padding-bottom: 8px;
-    border-bottom: 2px solid #e2e8f0;
-    position: relative;
+    font-size: 22px;
+    font-weight: 700;
+    color: #1c1c1e;
+    margin: 0 0 16px 0;
+    display: flex;
+    align-items: center;
+    gap: 8px;
     
-    &::after {
-      content: '';
-      position: absolute;
-      bottom: -2px;
-      left: 0;
-      width: 40px;
-      height: 2px;
-      background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
-      border-radius: 1px;
+    .section-subtitle {
+      font-size: 14px;
+      font-weight: 400;
+      color: #8e8e93;
+      margin-left: 8px;
     }
   }
 }
 
-// 表单网格布局
+// 表单网格布局 - dashboard响应式网格
 .form-grid {
   display: grid;
   grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
-  gap: 24px;
+  gap: 20px;
   
   @media (max-width: 768px) {
     grid-template-columns: 1fr;
-    gap: 20px;
+    gap: 16px;
   }
 }
 
-// 表单字段样式
+// iOS风格表单字段样式
 .form-field {
   display: flex;
   flex-direction: column;
-  transition: all 0.3s ease;
+  margin-bottom: 16px;
   
   &.full-width {
     grid-column: 1 / -1;
   }
   
   .field-label {
-    font-size: 0.875rem;
-    font-weight: 600;
-    color: #374151;
+    font-size: 15px;
+    font-weight: 400;
+    color: #000000;
     margin-bottom: 8px;
-    transition: color 0.2s ease;
     
     .required {
-      color: #ef4444;
+      color: #ff3b30;
       margin-left: 2px;
     }
+    
+    .optional {
+      color: #8e8e93;
+      font-size: 13px;
+      font-weight: 300;
+      margin-left: 4px;
+    }
   }
   
   .field-input,
   .field-select,
   .field-textarea {
-    padding: 12px 16px;
-    border: 2px solid #e5e7eb;
-    border-radius: 12px;
-    font-size: 0.875rem;
-    transition: all 0.3s ease;
-    background: white;
-    
-    &:hover {
-      border-color: #d1d5db;
-    }
+    padding: 11px 16px;
+    border: 1px solid #d1d1d6;
+    border-radius: 10px;
+    font-size: 17px;
+    transition: border-color 0.2s ease;
+    background: #ffffff;
+    font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'PingFang SC', 'Helvetica Neue', sans-serif;
+    color: #000000;
     
     &:focus {
       outline: none;
-      border-color: #3b82f6;
-      box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+      border-color: #007aff;
     }
     
     &::placeholder {
-      color: #9ca3af;
-      transition: opacity 0.2s ease;
-    }
-    
-    &:focus::placeholder {
-      opacity: 0.7;
+      color: #8e8e93;
     }
   }
   
   .field-textarea {
     resize: vertical;
-    min-height: 100px;
+    min-height: 88px;
     font-family: inherit;
+    line-height: 1.4;
   }
 }
 
@@ -418,28 +770,32 @@ $card-padding: 16px;
     }
     
     .search-btn {
-      padding: 12px 24px;
-      background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
+      padding: 11px 20px;
+      background: #007aff;
       color: white;
       border: none;
-      border-radius: 12px;
+      border-radius: 10px;
       font-weight: 600;
+      font-size: 17px;
       cursor: pointer;
-      transition: all 0.2s ease;
+      transition: background-color 0.2s ease;
       display: flex;
       align-items: center;
       gap: 8px;
       white-space: nowrap;
       
       &:hover {
-        transform: translateY(-1px);
-        box-shadow: 0 4px 16px rgba(59, 130, 246, 0.3);
+        background: #0056cc;
+      }
+      
+      &:active {
+        background: #004499;
       }
       
       &:disabled {
         opacity: 0.6;
         cursor: not-allowed;
-        transform: none;
+        background: #007aff;
       }
     }
   }
@@ -525,6 +881,40 @@ $card-padding: 16px;
   }
 }
 
+// 搜索输入框样式 - dashboard设计
+.search-input-wrapper {
+  position: relative;
+  
+  .search-icon {
+    position: absolute;
+    left: 12px;
+    top: 50%;
+    transform: translateY(-50%);
+    color: #8e8e93;
+  }
+  
+  .search-input {
+    width: 100%;
+    padding: 12px 16px 12px 44px;
+    border: 1px solid #d1d1d6;
+    border-radius: 12px;
+    font-size: 16px;
+    transition: all 0.2s ease;
+    background-color: #ffffff;
+    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+    
+    &:focus {
+      outline: none;
+      border-color: #007aff;
+      box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1);
+    }
+    
+    &::placeholder {
+      color: #8e8e93;
+    }
+  }
+}
+
 .search-input-group {
   display: flex;
   align-items: center;
@@ -620,12 +1010,55 @@ $card-padding: 16px;
   gap: 6px;
 }
 
+// 标签样式 - dashboard设计
 .tag {
-  font-size: 11px;
-  padding: 2px 8px;
-  background-color: color-mix(in srgb, $primary-color 10%, transparent);
-  color: $primary-color;
-  border-radius: 12px;
+  display: inline-block;
+  padding: 6px 12px;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 500;
+  transition: all 0.2s ease;
+  
+  &.type-tag {
+    background-color: #e3f2fd;
+    color: #1565c0;
+    border: 1px solid #bbdefb;
+  }
+  
+  &.source-tag {
+    background-color: #e8f5e8;
+    color: #2e7d32;
+    border: 1px solid #c8e6c9;
+  }
+  
+  &:hover {
+    transform: translateY(-1px);
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  }
+}
+
+// 预设标签样式
+.preset-tag {
+  background-color: #f2f2f7;
+  color: #1c1c1e;
+  border: 1px solid #d1d1d6;
+  border-radius: 8px;
+  padding: 8px 16px;
+  font-size: 14px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  
+  &:hover {
+    background-color: #e5e5ea;
+    transform: translateY(-1px);
+  }
+  
+  &.selected {
+    background-color: #007aff;
+    color: white;
+    border-color: #007aff;
+  }
 }
 
 .tag.source {
@@ -1095,7 +1528,6 @@ $card-padding: 16px;
 // 操作按钮区域
 .action-section {
   margin-top: 40px;
-  
   .action-buttons {
     display: flex;
     gap: 16px;
@@ -1122,9 +1554,7 @@ $card-padding: 16px;
       background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
     }
   }
-  padding-top: 32px;
-  border-top: 1px solid #e2e8f0;
-  
+
   .action-buttons {
     display: flex;
     gap: 16px;
@@ -1238,57 +1668,51 @@ $card-padding: 16px;
 
 .primary-btn,
 .secondary-btn {
-  padding: 16px 32px; // 增大按钮尺寸
-  border-radius: $border-radius;
-  font-size: 18px; // 放大按钮字体
+  padding: 14px 24px;
+  border-radius: 10px;
+  font-size: 17px;
   font-weight: 600;
   cursor: pointer;
-  transition: $transition;
+  transition: background-color 0.2s ease;
   border: none;
   display: inline-flex;
   align-items: center;
   justify-content: center;
   gap: 8px;
-  min-width: 160px;
+  min-width: 120px;
   text-transform: none;
-  letter-spacing: 0.5px;
+  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'PingFang SC', 'Helvetica Neue', sans-serif;
 }
 
 .primary-btn {
-  background-color: $primary-color;
+  background-color: #007aff;
   color: white;
   &:hover:not(:disabled) {
-    background-color: $primary-dark;
-    transform: translateY(-2px);
-    box-shadow: $shadow-md;
+    background-color: #0056cc;
   }
   &:active:not(:disabled) {
-    transform: translateY(0);
+    background-color: #004499;
   }
   &:disabled {
-    background-color: $text-tertiary;
+    background-color: #8e8e93;
     cursor: not-allowed;
   }
 }
 
 .secondary-btn {
-  background-color: $background-tertiary;
-  color: $text-primary;
-  border: 2px solid $border-color;
+  background-color: #f2f2f7;
+  color: #007aff;
+  border: 1px solid #d1d1d6;
   &:hover:not(:disabled) {
-    background-color: $background-secondary;
-    border-color: $primary-color;
-    color: $primary-color;
-    transform: translateY(-2px);
-    box-shadow: $shadow-md;
+    background-color: #e5e5ea;
   }
   &:active:not(:disabled) {
-    transform: translateY(0);
+    background-color: #d1d1d6;
   }
   &:disabled {
-    background-color: $background-tertiary;
-    color: $text-tertiary;
-    border-color: $border-color;
+    background-color: #f2f2f7;
+    color: #8e8e93;
+    border-color: #d1d1d6;
     cursor: not-allowed;
   }
 }

+ 206 - 25
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();
@@ -356,37 +382,68 @@ export class ConsultationOrder {
     }
   }
 
-  // 创建项目
-  createProject() {
-    if (!this.selectedCustomer()) {
-      this.snackBar.open('请先选择客户', '关闭', { duration: 3000 });
-      return;
-    }
+  // 基于六项核心字段的最小创建可用性判断
+  minimalReady(): boolean {
+    const nameCtrl = this.customerForm.get('name');
+    const phoneCtrl = this.customerForm.get('phone');
+    const styleCtrl = this.requirementForm.get('style');
+    const groupCtrl = this.requirementForm.get('projectGroup');
+    const downPaymentCtrl = this.requirementForm.get('downPayment');
+    const firstDraftDateCtrl = this.requirementForm.get('firstDraftDate');
+
+    const nameOk = !!nameCtrl?.value && nameCtrl.valid;
+    const phoneOk = !!phoneCtrl?.value && phoneCtrl.valid;
+    const styleOk = !!styleCtrl?.value;
+    const groupOk = !!groupCtrl?.value;
+    const downPaymentOk = downPaymentCtrl != null && downPaymentCtrl.valid && downPaymentCtrl.value !== null && downPaymentCtrl.value !== '';
+    const firstDraftOk = !!firstDraftDateCtrl?.value;
+
+    return !!(nameOk && phoneOk && styleOk && groupOk && downPaymentOk && firstDraftOk);
+  }
 
-    if (this.requirementForm.invalid) {
-      this.snackBar.open('请完善需求信息', '关闭', { duration: 3000 });
+  // 创建项目(最小必填:姓名、手机、风格、项目组、首付款、首稿时间)
+  createProjectMinimal() {
+    const nameCtrl = this.customerForm.get('name');
+    const phoneCtrl = this.customerForm.get('phone');
+    const styleCtrl = this.requirementForm.get('style');
+    const groupCtrl = this.requirementForm.get('projectGroup');
+    const downPaymentCtrl = this.requirementForm.get('downPayment');
+    const firstDraftDateCtrl = this.requirementForm.get('firstDraftDate');
+
+    if (!nameCtrl?.value || !phoneCtrl?.value || !styleCtrl?.value || !groupCtrl?.value || downPaymentCtrl?.value === null || !firstDraftDateCtrl?.value) {
+      this.snackBar.open('请完整填写姓名、手机、风格、项目组、首付款、首稿时间', '关闭', { duration: 3000 });
       return;
     }
 
-    const selectedCustomer = this.selectedCustomer()!;
-    const projectData = {
-      customerId: selectedCustomer.id,
-      customerName: selectedCustomer.name,
-      requirement: this.requirementForm.value,
-      referenceCases: this.requirementForm.get('referenceCases')?.value || [],
-      tags: {
-        demandType: this.customerForm.get('demandType')?.value,
-        preferenceTags: this.preferenceTags,
-        followUpStatus: this.customerForm.get('followUpStatus')?.value
-      }
+    this.isSubmitting.set(true);
+
+    const payload = {
+      customerId: 'temp-' + Date.now(),
+      customerName: nameCtrl.value,
+      requirement: {
+        style: styleCtrl.value,
+        projectGroup: groupCtrl.value,
+        downPayment: Number(downPaymentCtrl?.value ?? 0),
+        firstDraftDate: firstDraftDateCtrl?.value
+      },
+      referenceCases: [],
+      tags: { followUpStatus: '待分配' }
     };
 
-    this.projectService.createProject(projectData).subscribe(
-      (response: any) => {
-        this.snackBar.open('项目创建成功', '关闭', { duration: 3000 });
+    this.projectService.createProject(payload).subscribe(
+      (res: any) => {
+        this.isSubmitting.set(false);
+        if (res?.success) {
+          this.showSuccessMessage.set(true);
+          this.snackBar.open('项目创建成功', '关闭', { duration: 2000 });
+          setTimeout(() => this.showSuccessMessage.set(false), 2500);
+        } else {
+          this.snackBar.open('创建失败,请稍后重试', '关闭', { duration: 3000 });
+        }
       },
-      (error: any) => {
-        this.snackBar.open('项目创建失败,请重试', '关闭', { duration: 3000 });
+      () => {
+        this.isSubmitting.set(false);
+        this.snackBar.open('创建失败,请稍后重试', '关闭', { duration: 3000 });
       }
     );
   }
@@ -441,4 +498,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);
+  }
 }

+ 9 - 14
src/app/pages/customer-service/customer-service-layout/customer-service-layout.html

@@ -38,7 +38,8 @@
     </button>
     
     <!-- 通知下拉菜单 -->
-    <div class="notification-dropdown" *ngIf="showNotifications()">
+    @if (showNotifications()) {
+    <div class="notification-dropdown">
       <div class="notification-header">
         <h3>消息通知</h3>
         <button class="close-btn" (click)="toggleNotifications()">
@@ -49,7 +50,8 @@
         </button>
       </div>
       <div class="notification-list">
-        <div *ngFor="let notification of notifications()" class="notification-item">
+        @for (notification of notifications(); track notification) {
+        <div class="notification-item">
           <div class="notification-icon">
             <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
               <circle cx="12" cy="12" r="10"></circle>
@@ -61,11 +63,13 @@
             <p>{{ notification }}</p>
           </div>
         </div>
+        }
       </div>
       <div class="notification-footer">
         <a href="#" class="view-all">查看全部</a>
       </div>
     </div>
+    }
     <div class="user-profile">
       <div style="width: 40px; height: 40px; background-color: #FFCCCC; color: #555555; display: flex; align-items: center; justify-content: center; font-size: 13.333333333333334px; font-weight: bold;" class="user-avatar" title="用户头像">CS</div>
       <span class="user-name">客服小李</span>
@@ -86,9 +90,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">
@@ -101,16 +105,7 @@
         </svg>
         <span>项目列表</span>
       </a>
-      <a routerLink="/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 routerLink="/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>

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

@@ -1,12 +1,12 @@
 import { Component, signal } from '@angular/core';
-import { CommonModule, NgIf, NgFor } from '@angular/common';
+import { CommonModule } from '@angular/common';
 import { Router, RouterOutlet, RouterLinkActive, RouterLink } from '@angular/router';
 import { FormsModule } from '@angular/forms';
 
 @Component({
   selector: 'app-customer-service-layout',
   standalone: true,
-  imports: [CommonModule, NgIf, NgFor, RouterOutlet, RouterLink, RouterLinkActive, FormsModule],
+  imports: [CommonModule, RouterOutlet, RouterLink, RouterLinkActive, FormsModule],
   templateUrl: './customer-service-layout.html',
   styleUrl: './customer-service-layout.scss'
 }) 

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

@@ -3,7 +3,6 @@ import { Dashboard } from './dashboard/dashboard';
 import { ConsultationListComponent } from './dashboard/pages/consultation-list/consultation-list.component';
 import { AssignmentListComponent } from './dashboard/pages/assignment-list/assignment-list.component';
 import { ExceptionListComponent } from './dashboard/pages/exception-list/exception-list.component';
-import { RevenueDetailComponent } from './dashboard/pages/revenue-detail/revenue-detail.component';
 
 export const CUSTOMER_SERVICE_ROUTES: Routes = [
   {
@@ -21,10 +20,6 @@ export const CUSTOMER_SERVICE_ROUTES: Routes = [
       {
         path: 'exception-list',
         component: ExceptionListComponent
-      },
-      {
-        path: 'revenue-detail',
-        component: RevenueDetailComponent
       }
     ]
   }

+ 90 - 36
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,52 +104,98 @@
     <!-- 新客户触达 -->
     <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">
-        <div class="crm-item" *ngFor="let c of newReachOutCustomers()" (click)="navigateToConsultation(c.name)">
+        @for (c of newReachOutCustomers(); track c.name) {
+        <div class="crm-item" (click)="navigateToConsultation(c.name)">
           <div class="crm-item-main">
             <div class="avatar small">{{ c.name.charAt(0) }}</div>
             <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>
         </div>
-        <div *ngIf="newReachOutCustomers().length === 0" class="empty-state small">暂无待触达客户</div>
+        }
+        @if (newReachOutCustomers().length === 0) {
+        <div class="empty-state small">暂无待触达客户</div>
+        }
       </div>
     </div>
 
     <!-- 老客户回访 -->
     <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">
-        <div class="crm-item" *ngFor="let c of oldCustomerFollowUps()" (click)="navigateToConsultation(c.name)">
+        @for (c of oldCustomerFollowUps(); track c.name) {
+        <div class="crm-item" (click)="navigateToConsultation(c.name)">
           <div class="crm-item-main">
             <div class="avatar small alt">{{ c.name.charAt(0) }}</div>
             <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>
         </div>
-        <div *ngIf="oldCustomerFollowUps().length === 0" class="empty-state small">暂无待回访客户</div>
+        }
+        @if (oldCustomerFollowUps().length === 0) {
+        <div class="empty-state small">暂无待回访客户</div>
+        }
       </div>
     </div>
-  </div>
-</section>
+    </div>
+  </section>
 
 <!-- 紧急待办和项目动态流 -->
 <div class="content-grid">
@@ -184,15 +216,18 @@
     </div>
     
     <div class="tasks-list">
-      <div *ngIf="urgentTasks().length === 0" class="empty-state">
+      @if (urgentTasks().length === 0) {
+      <div class="empty-state">
         <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <circle cx="12" cy="12" r="10"></circle>
           <polyline points="12 6 12 12 16 14"></polyline>
         </svg>
         <p>暂无紧急待办事项</p>
       </div>
+      }
       
-      <div *ngFor="let task of urgentTasks()" class="task-item" [class.completed]="task.isCompleted" [class.overdue]="task.isOverdue">
+      @for (task of urgentTasks(); track task.id) {
+      <div class="task-item" [class.completed]="task.isCompleted" [class.overdue]="task.isOverdue">
         <div class="task-checkbox">
           <input type="checkbox" [checked]="task.isCompleted" (change)="markTaskAsCompleted(task.id)">
         </div>
@@ -231,11 +266,13 @@
           </ng-template>
           </div>
       </div>
+      }
     </div>
   </section>
 
   <!-- iOS风格的添加紧急事项面板 -->
-  <div class="ios-modal-overlay" *ngIf="isTaskFormVisible()" (click)="hideTaskForm()">
+  @if (isTaskFormVisible()) {
+  <div class="ios-modal-overlay" (click)="hideTaskForm()">
     <div class="ios-panel" (click)="$event.stopPropagation()">
       <div class="ios-panel-header">
         <h3>添加紧急事项</h3>
@@ -304,12 +341,13 @@
             
             <!-- 预设时长下拉选择框 -->
             <div class="deadline-dropdown" [class.visible]="deadlineDropdownVisible">
+              @for (preset of timePresets; track preset.hours) {
               <div class="dropdown-option" 
-                   *ngFor="let preset of timePresets"
                    [class.selected]="selectedPreset === preset.hours.toString()"
                    (click)="handlePresetSelection(preset.hours.toString())">
                 {{ preset.label }}
               </div>
+              }
               
               <!-- 当天24:00前选项 -->
               <div class="dropdown-option" 
@@ -326,11 +364,14 @@
             </div>
             
             <!-- 错误提示信息 -->
-            <div class="error-message" *ngIf="deadlineError">{{ deadlineError }}</div>
+            @if (deadlineError) {
+            <div class="error-message">{{ deadlineError }}</div>
+            }
           </div>
           
           <!-- 自定义时间选择弹窗 -->
-          <div class="custom-time-modal" *ngIf="isCustomTimeVisible">
+          @if (isCustomTimeVisible) {
+          <div class="custom-time-modal">
             <div class="modal-backdrop"></div>
             <div class="modal-content">
               <div class="modal-header">
@@ -362,7 +403,9 @@
                 </div>
                 
                 <!-- 错误提示信息 -->
-                <div class="error-message" *ngIf="deadlineError">{{ deadlineError }}</div>
+                @if (deadlineError) {
+                <div class="error-message">{{ deadlineError }}</div>
+                }
               </div>
               
               <div class="modal-footer">
@@ -371,6 +414,7 @@
               </div>
             </div>
           </div>
+          }
           
           <div class="form-group">
             <label for="taskPriority">优先级</label>
@@ -406,6 +450,7 @@
       </div>
     </div>
   </div>
+  }
 
   <!-- 项目动态流 -->
   <section class="project-updates-section">
@@ -423,7 +468,8 @@
     </div>
     
     <div class="updates-list">
-      <div *ngIf="filteredUpdates().length === 0" class="empty-state">
+      @if (filteredUpdates().length === 0) {
+      <div class="empty-state">
         <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <circle cx="12" cy="12" r="10"></circle>
           <line x1="2" y1="12" x2="22" y2="12"></line>
@@ -431,21 +477,29 @@
         </svg>
         <p>暂无项目动态</p>
       </div>
+      }
       
-      <div *ngFor="let update of filteredUpdates()" class="update-item">
+      @for (update of filteredUpdates(); track update) {
+      <div class="update-item">
         <div class="update-icon">
           <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
             <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
           </svg>
         </div>
         <div class="update-content">
-          <div class="update-title" *ngIf="'name' in update && update.name && 'status' in update && update.status">
+          @if ('name' in update && update.name && 'status' in update && update.status) {
+          <div class="update-title">
             项目 <strong>{{ update.name }}</strong> 状态更新为 {{ update.status }}
           </div>
-          <div class="update-title" *ngIf="'content' in update">
+          }
+          @if ('content' in update) {
+          <div class="update-title">
             <strong>{{ getCustomerName(update) }}</strong> 提交了反馈
           </div>
-          <p class="update-text" *ngIf="'content' in update && update.content">{{ update.content }}</p>
+          }
+          @if ('content' in update && update.content) {
+          <p class="update-text">{{ update.content }}</p>
+          }
           <div class="update-meta">
             <span class="update-time">{{ getFormattedDate(update) }}</span>
             <span class="update-status {{ getUpdateStatusClass(update) }}">
@@ -454,9 +508,9 @@
           </div>
         </div>
       </div>
+      }
     </div>
   </section>
-</div>
 
 <!-- 回到顶部按钮 -->
 <button class="back-to-top" (click)="scrollToTop()" [class.visible]="showBackToTop()">

+ 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 - 30
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,40 @@ 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; }
-    .time { }
+    .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;
+      }
+    }
+    .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 +2210,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 +2288,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();
-  }
-}

+ 197 - 295
src/app/pages/customer-service/project-detail/project-detail.html

@@ -64,16 +64,18 @@
         <div class="record-section">
           <h4>过往咨询记录</h4>
           <div class="consultation-list">
-            <div class="consultation-item" *ngFor="let record of consultationRecords()">
-              <div class="consultation-date">{{ formatDate(record.date) }}</div>
-              <div class="consultation-content">{{ record.content }}</div>
-              <div class="consultation-status"
-                   [class.status-processed]="record.status === '已解决' || record.status === '成功'"
-                   [class.status-processing]="record.status === '处理中'"
-                   [class.status-pending]="record.status === '待处理'">
-                {{ record.status }}
+            @for (record of consultationRecords(); track $index) {
+              <div class="consultation-item">
+                <div class="consultation-date">{{ formatDate(record.date) }}</div>
+                <div class="consultation-content">{{ record.content }}</div>
+                <div class="consultation-status"
+                     [class.status-processed]="record.status === '已解决' || record.status === '成功'"
+                     [class.status-processing]="record.status === '处理中'"
+                     [class.status-pending]="record.status === '待处理'">
+                  {{ record.status }}
+                </div>
               </div>
-            </div>
+            }
           </div>
         </div>
         
@@ -81,12 +83,14 @@
         <div class="record-section">
           <h4>合作项目</h4>
           <div class="projects-list">
-            <div class="project-item" *ngFor="let proj of cooperationProjects()">
-              <div class="project-name">{{ proj.name }}</div>
-              <div class="project-period">{{ formatDate(proj.startDate) }} - {{ formatDate(proj.endDate) }}</div>
-              <div class="project-description">{{ proj.description }}</div>
-              <div class="project-status">{{ proj.status }}</div>
-            </div>
+            @for (proj of cooperationProjects(); track $index) {
+              <div class="project-item">
+                <div class="project-name">{{ proj.name }}</div>
+                <div class="project-period">{{ formatDate(proj.startDate) }} - {{ formatDate(proj.endDate) }}</div>
+                <div class="project-description">{{ proj.description }}</div>
+                <div class="project-status">{{ proj.status }}</div>
+              </div>
+            }
           </div>
         </div>
         
@@ -94,18 +98,24 @@
         <div class="record-section">
           <h4>历史反馈/评价</h4>
           <div class="feedback-list">
-            <div class="feedback-item" *ngFor="let feedback of historicalFeedbacks()">
-              <div class="feedback-date">{{ formatDate(feedback.date) }}</div>
-              <div class="feedback-rating">
-                <span *ngFor="let star of [1,2,3,4,5]">
-                  <i class="fa" [ngClass]="{ 'fa-star': star <= feedback.rating, 'fa-star-o': star > feedback.rating }"></i>
-                </span>
-              </div>
-              <div class="feedback-content">{{ feedback.content }}</div>
-              <div class="feedback-response" *ngIf="feedback.response">
-                <strong>回复:</strong>{{ feedback.response }}
+            @for (feedback of historicalFeedbacks(); track $index) {
+              <div class="feedback-item">
+                <div class="feedback-date">{{ formatDate(feedback.date) }}</div>
+                <div class="feedback-rating">
+                  @for (star of [1,2,3,4,5]; track $index) {
+                    <span>
+                      <i class="fa" [ngClass]="{ 'fa-star': star <= feedback.rating, 'fa-star-o': star > feedback.rating }"></i>
+                    </span>
+                  }
+                </div>
+                <div class="feedback-content">{{ feedback.content }}</div>
+                @if (feedback.response) {
+                  <div class="feedback-response">
+                    <strong>回复:</strong>{{ feedback.response }}
+                  </div>
+                }
               </div>
-            </div>
+            }
           </div>
         </div>
       </div>
@@ -114,34 +124,44 @@
       <div class="card 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>
+          @for (stage of projectStages; let i = $index; track $index) {
+            <div 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>
+                  @if (stage.completed) {
+                    <path d="m5 12 5 5 10-10" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
+                  }
+                  @if (stage.inProgress) {
+                    <circle cx="12" cy="12" r="5"></circle>
+                  }
+                </svg>
               </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>
+              @if (i < projectStages.length - 1) {
+                <div class="timeline-line" [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="stage-responsible">负责人:{{ stage.responsible || '未分配' }}</div>
-              </div>
-              <div class="stage-details" *ngIf="stage.details">
-                <p>{{ stage.details }}</p>
+                <div class="timeline-meta">
+                  <div class="stage-dates">
+                    @if (stage.startDate) { <span class="date-item">开始:{{ formatDate(stage.startDate) }}</span> }
+                    @if (stage.endDate) { <span class="date-item">完成:{{ formatDate(stage.endDate) }}</span> }
+                  </div>
+                  <div class="stage-responsible">负责人:{{ stage.responsible || '未分配' }}</div>
+                </div>
+                @if (stage.details) {
+                  <div class="stage-details">
+                    <p>{{ stage.details }}</p>
+                  </div>
+                }
               </div>
             </div>
-          </div>
+          }
         </div>
       </div>
 
@@ -182,262 +202,142 @@
             </svg>
             <span>文件</span>
           </button>
+          <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'members'" (click)="switchTab('members')">
+            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
+              <circle cx="9" cy="7" r="4"></circle>
+              <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
+              <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+            </svg>
+            <span>组员</span>
+          </button>
+          <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'requirements'" (click)="switchTab('requirements')">
+            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M9 11l3 3L22 4"></path>
+              <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
+            </svg>
+            <span>需求</span>
+          </button>
         </div>
 
-        <!-- 消息标签内容 -->
-        <div *ngIf="activeTab() === 'messages'" class="tab-content">
-          <div class="messages-container">
-            <div class="messages-list">
-              <div *ngFor="let message of messages()" class="message-item">
-                <div class="message-avatar">
-                  {{ message.sender.charAt(0) }}
-                </div>
-                <div class="message-content">
-                  <div class="message-header">
-                    <span class="message-sender">{{ message.sender }}</span>
-                    <span class="message-time">{{ formatDateTime(message.timestamp) }}</span>
-                  </div>
-                  <div class="message-text">{{ message.content }}</div>
-                </div>
-              </div>
+        @if (activeTab() === 'requirements') {
+          <div class="tab-content">
+            <div class="requirements-header" style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
+              <h3 style="margin:0;">需求补充</h3>
+              <button class="secondary-btn btn-hover-effect" (click)="saveRequirementDraft()">保存草稿</button>
             </div>
-            <div class="message-input-area">
-              <textarea 
-                [value]="newMessage()"
-                (input)="onMessageInput($event)"
-                placeholder="输入消息内容..."
-                rows="3"
-                (keydown.enter.shift)="$event.preventDefault()"
-                (keydown.enter)="sendMessage()"
-              ></textarea>
-              <div class="message-actions">
-                <button class="secondary-btn btn-hover-effect">
-                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                    <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
-                    <polyline points="14 2 14 8 20 8"></polyline>
-                  </svg>
-                  <span>上传文件</span>
-                </button>
-                <button class="primary-btn btn-hover-effect" (click)="sendMessage()" [disabled]="!newMessage().trim()">
-                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                    <line x1="22" y1="2" x2="11" y2="13"></line>
-                    <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
-                  </svg>
-                  <span>发送</span>
-                </button>
-              </div>
-            </div>
-          </div>
-        </div>
-
-        <!-- 概览标签内容 -->
-        <div *ngIf="activeTab() === 'overview'" class="tab-content">
-          <div class="overview-grid">
-            <!-- 客户信息卡片 -->
-            <div class="info-card">
-              <h4 class="card-title">客户信息</h4>
-              <div class="customer-info">
-                <div class="info-item">
-                  <label>客户姓名</label>
-                  <span>{{ project()?.customerName || '王先生' }}</span>
+            <div class="incomplete-hints" style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px;">
+              @if (!hasBlueprint()) {
+                <div class="hint-card" style="border:1px dashed #f39c12;background:#fffaf0;padding:10px 12px;border-radius:8px;display:flex;align-items:center;gap:8px;">
+                  <span style="color:#e67e22;">未上传施工图纸</span>
+                  <label class="primary-btn btn-hover-effect" style="margin-left:4px;">
+                    <input type="file" accept=".pdf,.png,.jpg,.jpeg" style="display:none" (change)="uploadBlueprint($event)">
+                    上传图纸
+                  </label>
                 </div>
-                <div class="info-item">
-                  <label>联系方式</label>
-                  <span>138****5678</span>
+              }
+              @if (!hasStylePreference()) {
+                <div class="hint-card" style="border:1px dashed #8e44ad;background:#faf5ff;padding:10px 12px;border-radius:8px;display:flex;align-items:center;gap:8px;">
+                  <span style="color:#8e44ad;">未填写风格偏好</span>
+                  <button class="secondary-btn btn-hover-effect" (click)="showStyleEdit.set(true); tempStylePreference = ''">去填写</button>
                 </div>
-                <div class="info-item">
-                  <label>标签</label>
-                  <div class="tag-container">
-                    <span class="tag">朋友圈</span>
-                    <span class="tag">软装</span>
-                    <span class="tag">现代风格</span>
-                  </div>
-                </div>
-                <div class="info-item">
-                  <label>高优先级需求</label>
-                  <ul class="need-list">
-                    <li *ngFor="let need of project()?.highPriorityNeeds || ['客厅光线充足', '储物空间充足', '环保材料']">
-                      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                        <polyline points="20 6 9 17 4 12"></polyline>
-                      </svg>
-                      {{ need }}
-                    </li>
-                  </ul>
-                </div>
-              </div>
+              }
             </div>
-
-            <!-- 项目团队卡片 -->
-            <div class="info-card">
-              <h4 class="card-title">项目团队</h4>
-              <div class="team-info">
-                <div class="team-member">
-                  <div class="member-avatar" title="客服小李">IMG</div>
-                  <div class="member-details">
-                    <div class="member-name">客服小李</div>
-                    <div class="member-role">客户经理</div>
-                  </div>
-                  <button class="message-btn">
-                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                      <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
-                    </svg>
-                  </button>
-                </div>
-                <div class="team-member">
-                  <div class="member-avatar" title="张设计师">IMG</div>
-                  <div class="member-details">
-                    <div class="member-name">张设计师</div>
-                    <div class="member-role">主设计师</div>
-                  </div>
-                  <button class="message-btn">
-                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                      <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
-                    </svg>
-                  </button>
+            @if (showStyleEdit()) {
+              <div class="style-editor" style="padding:12px;border:1px solid #eee;border-radius:8px;margin-bottom:16px;background:#fff;">
+                <div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
+                  <span>风格选择:</span>
+                  <select [(ngModel)]="tempStylePreference">
+                    <option value="" disabled selected>请选择风格</option>
+                    @for (opt of styleOptions; track $index) {
+                      <option [value]="opt">{{ opt }}</option>
+                    }
+                  </select>
+                  <button class="primary-btn btn-hover-effect" (click)="saveStylePreference()" [disabled]="!tempStylePreference">保存</button>
+                  <button class="secondary-btn btn-hover-effect" (click)="showStyleEdit.set(false)">取消</button>
                 </div>
               </div>
-            </div>
-
-            <!-- 最近反馈卡片 -->
-            <div class="info-card">
-              <h4 class="card-title">客户反馈</h4>
-              <div class="feedback-list">
-                <div *ngFor="let feedback of feedbacks()" class="feedback-item">
-                  <div class="feedback-item">
-                    <div class="feedback-header">
-                      <div class="feedback-author">{{ getFeedbackCustomerName(feedback) }}</div>
-                      <div class="feedback-rating">
-                        <span class="rating-stars">★★★★☆</span>
-                        <span class="rating-number">{{ getFeedbackRating(feedback) }}.0</span>
-                      </div>
-                    </div>
-                    <div class="feedback-content">{{ feedback?.content || '' }}</div>
-                    <div class="feedback-response" *ngIf="feedback?.response">
-                      <div class="response-label">客服回复:</div>
-                      <div class="response-text">{{ feedback.response }}</div>
-                    </div>
-                    <div class="feedback-meta">
-                      <span class="feedback-date">{{ formatDate(feedback?.createdAt) }}</span>
-                      <span class="feedback-status" [class.status-processed]="feedback?.status === '已解决'" [class.status-pending]="feedback?.status === '待处理'" [class.status-processing]="feedback?.status === '处理中'">
-                        {{ feedback?.status || '未知状态' }}
-                      </span>
-                    </div>
-                  </div>
-                </div>
-                <button class="view-all-btn btn-hover-effect" *ngIf="feedbacks().length > 0">查看全部反馈</button>
+            }
+            <div class="requirements-form">
+              <div class="form-row">
+                <label>需求摘要</label>
+                <textarea [formControl]="requirementForm.controls.summary" rows="3" placeholder="例如:偏好现代简约风,客厅以明亮色调为主…"></textarea>
               </div>
-            </div>
-          </div>
-        </div>
-
-        <!-- 里程碑标签内容 -->
-        <div *ngIf="activeTab() === 'milestones'" class="tab-content">
-          <div class="milestones-timeline">
-            <div *ngFor="let milestone of milestones(); index as i" class="milestone-item">
-              <div class="milestone-dot" [class.completed]="milestone.isCompleted"></div>
-              <div class="milestone-line" *ngIf="i < milestones().length - 1" [class.completed]="milestone.isCompleted && milestones()[i+1].isCompleted"></div>
-              <div class="milestone-content">
-                <div class="milestone-header">
-                  <h4 class="milestone-title">{{ milestone.title }}</h4>
-                  <span class="milestone-status" [class.status-completed]="milestone.isCompleted" [class.status-pending]="!milestone.isCompleted">
-                    {{ milestone.isCompleted ? '已完成' : '进行中' }}
-                  </span>
-                </div>
-                <p class="milestone-description">{{ milestone.description }}</p>
-                <div class="milestone-dates">
-                  <div class="date-item">
-                    <label>截止日期</label>
-                    <span>{{ formatDate(milestone.dueDate) }}</span>
-                  </div>
-                  <div class="date-item" *ngIf="milestone.completedDate">
-                    <label>完成日期</label>
-                    <span>{{ formatDate(milestone.completedDate) }}</span>
-                  </div>
-                </div>
-                <div class="milestone-actions" *ngIf="!milestone.isCompleted">
-                  <button class="primary-btn small btn-hover-effect" (click)="completeMilestone(milestone.id)">
-                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                      <polyline points="20 6 9 17 4 12"></polyline>
-                    </svg>
-                    <span>标记完成</span>
-                  </button>
-                </div>
+              <div class="form-row">
+                <label>重点诉求</label>
+                <textarea [formControl]="requirementForm.controls.priorityPoints" rows="3"></textarea>
               </div>
-            </div>
-          </div>
-        </div>
-
-        <!-- 任务标签内容 -->
-        <div *ngIf="activeTab() === 'tasks'" class="tab-content">
-          <div class="tasks-filter">
-            <div class="filter-options">
-              <button class="filter-btn active">全部任务</button>
-              <button class="filter-btn">进行中</button>
-              <button class="filter-btn">已完成</button>
-              <button class="filter-btn">逾期</button>
-            </div>
-            <div class="search-box">
-              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                <circle cx="11" cy="11" r="8"></circle>
-                <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
-              </svg>
-              <input type="text" placeholder="搜索任务...">
-            </div>
-          </div>
-          <div class="tasks-list">
-            <!-- 修复任务列表中的状态显示,确保安全访问 -->
-            <div *ngFor="let task of tasks()" class="task-item">
-              <div class="task-checkbox">
-                <input type="checkbox" [checked]="task.isCompleted" (change)="task.isCompleted ? null : completeTask(task.id)">
+              <div class="form-row">
+                <label>约束条件</label>
+                <textarea [formControl]="requirementForm.controls.constraints" rows="3"></textarea>
               </div>
-              <div class="task-content">
-                <h4 class="task-title" [class.completed]="task.isCompleted">{{ task.title || '未命名任务' }}</h4>
-                <p class="task-description">{{ task.description || '' }}</p>
-                <div class="task-meta">
-                  <span class="task-assignee">
-                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                      <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
-                      <circle cx="9" cy="7" r="4"></circle>
-                      <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
-                      <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
-                    </svg>
-                    {{ task.assignee || '未分配' }}
-                  </span>
-                  <span class="task-deadline" [class.overdue]="task.isOverdue">
-                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                      <circle cx="12" cy="12" r="10"></circle>
-                      <polyline points="12 6 12 12 16 14"></polyline>
-                    </svg>
-                    {{ formatDate(task.deadline) }}
-                  </span>
-                  <span class="task-priority" [class.priority-high]="task.priority === 'high'" [class.priority-medium]="task.priority === 'medium'" [class.priority-low]="task.priority === 'low'">
-                    {{ task.priority === 'high' ? '高' : task.priority === 'medium' ? '中' : '低' }}
-                  </span>
-                </div>
+              <div class="form-row">
+                <label>预算</label>
+                <input type="text" [formControl]="requirementForm.controls.budget" placeholder="如:20万以内">
+              </div>
+              <div class="form-actions">
+                <button class="primary-btn btn-hover-effect" (click)="submitRequirements()">提交需求</button>
               </div>
             </div>
           </div>
-        </div>
+        }
 
         <!-- 消息标签内容 -->
-        <div *ngIf="activeTab() === 'messages'" class="tab-content">
-          <div class="messages-container">
-            <div class="messages-list">
-              <div *ngFor="let message of messages()" class="message-item">
-                <div class="message-avatar">
-                  {{ message.sender.charAt(0) }}
-                </div>
-                <div class="message-content">
-                  <div class="message-header">
-                    <span class="message-sender">{{ message.sender }}</span>
-                    <span class="message-time">{{ formatDateTime(message.timestamp) }}</span>
+        @if (activeTab() === 'messages') {
+          <div class="tab-content">
+            <div class="messages-toolbar" style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;gap:8px;">
+              <div style="display:flex;align-items:center;gap:8px;">
+                <button class="primary-btn btn-hover-effect" (click)="createGroup()" [disabled]="creatingGroup() || !!chatGroup()">{{ creatingGroup() ? '拉群中…' : (chatGroup() ? '已创建群' : '拉群') }}</button>
+                @if (chatGroup()) { <a class="secondary-btn btn-hover-effect" [href]="chatGroup()!.link" target="_blank">群聊入口</a> }
+              </div>
+              <div class="tips" style="color:#888;font-size:12px;">在这里与客户与设计师进行项目沟通</div>
+            </div>
+            <div class="messages-container">
+              <div class="messages-list">
+                @for (message of messages(); track $index) {
+                  <div class="message-item">
+                    <div class="message-avatar">
+                      {{ message.sender.charAt(0) }}
+                    </div>
+                    <div class="message-content">
+                      <div class="message-header">
+                        <span class="message-sender">{{ message.sender }}</span>
+                        <span class="message-time">{{ formatDateTime(message.timestamp) }}</span>
+                      </div>
+                      <div class="message-text">{{ message.content }}</div>
+                    </div>
+                  </div>
+                }
+                <div class="message-input-area">
+                  <textarea 
+                    [value]="newMessage()"
+                    (input)="onMessageInput($event)"
+                    placeholder="输入消息内容..."
+                    rows="3"
+                    (keydown.enter.shift)="$event.preventDefault()"
+                    (keydown.enter)="sendMessage()"
+                  ></textarea>
+                  <div class="message-actions">
+                    <button class="secondary-btn btn-hover-effect">
+                      <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                        <path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
+                        <polyline points="14 2 14 8 20 8"></polyline>
+                      </svg>
+                      <span>上传文件</span>
+                    </button>
+                    <button class="primary-btn btn-hover-effect" (click)="sendMessage()" [disabled]="!newMessage().trim()">
+                      <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                        <line x1="22" y1="2" x2="11" y2="13"></line>
+                        <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
+                      </svg>
+                      <span>发送</span>
+                    </button>
                   </div>
-                  <div class="message-text">{{ message.content }}</div>
                 </div>
               </div>
             </div>
       </div>
-    </div>
+        }
 
     <!-- 右侧边栏 - 企业微信聊天集成 -->
     <div class="wechat-sidebar ios-sidebar">
@@ -461,18 +361,20 @@
       
       <!-- 聊天消息列表 -->
       <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>
+        @for (msg of wechatMessagesList; track $index) {
+          <div 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 class="message-text">{{ msg.content }}</div>
           </div>
-        </div>
+        }
       </div>
       
       <!-- 消息输入框 -->

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

@@ -19,6 +19,10 @@ $shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
 $border-radius: 12px;
 $transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
 
+// 兼容背景变量(别名)
+$bg-light: $background-secondary;
+$bg-white: $background-primary;
+
 // iOS风格卡片
 .card {
   background: $background-secondary;
@@ -33,6 +37,164 @@ $transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
   }
 }
 
+// Members & Requirements — iOS styled details and micro-interactions
+.project-detail-container {
+  // Members tab
+  .members-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin: 12px 0 8px;
+
+    h4 {
+      margin: 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: $text-primary;
+    }
+
+    .actions {
+      display: flex;
+      gap: 8px;
+
+      .primary-btn,
+      .secondary-btn {
+        height: 34px;
+        padding: 0 14px;
+        border-radius: 10px;
+      }
+    }
+  }
+
+  .members-list {
+    background-color: $bg-white;
+    border: 1px solid $border-color;
+    border-radius: 12px;
+    box-shadow: $shadow-sm;
+    padding: 8px;
+  }
+
+  .member-row {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 12px 10px;
+    border-bottom: 1px dashed color-mix(in srgb, $border-color 70%, transparent);
+    transition: $transition;
+
+    &:last-child { border-bottom: none; }
+
+    .role {
+      flex: 0 0 120px;
+      font-size: 13px;
+      color: $text-secondary;
+    }
+
+    .name { flex: 1; }
+
+    &.editable:hover { background-color: $bg-light; }
+  }
+
+  .member-input {
+    width: 100%;
+    background: $bg-light;
+    border: 1px solid $border-color;
+    border-radius: 10px;
+    padding: 8px 12px;
+    font-size: 14px;
+    color: $text-primary;
+    outline: none;
+    transition: $transition;
+
+    &::placeholder { color: $text-tertiary; }
+
+    &:focus {
+      background: $bg-white;
+      border-color: $primary-color;
+      box-shadow: 0 0 0 3px color-mix(in srgb, $primary-color 15%, transparent);
+      transform: translateY(-1px);
+    }
+  }
+
+  // Requirements tab
+  .requirements-header {
+    margin: 12px 0 8px;
+
+    h4 {
+      margin: 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: $text-primary;
+    }
+  }
+
+  .requirements-form {
+    background: $bg-white;
+    border: 1px solid $border-color;
+    border-radius: 12px;
+    box-shadow: $shadow-sm;
+    padding: 12px;
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+
+    .form-row {
+      display: flex;
+      flex-direction: column;
+      gap: 6px;
+
+      label {
+        font-size: 13px;
+        color: $text-secondary;
+      }
+
+      .form-input,
+      .form-textarea {
+        background: $bg-light;
+        border: 1px solid $border-color;
+        border-radius: 12px;
+        padding: 10px 12px;
+        font-size: 14px;
+        color: $text-primary;
+        outline: none;
+        transition: $transition;
+
+        &::placeholder { color: $text-tertiary; }
+
+        &:focus {
+          background: $bg-white;
+          border-color: $primary-color;
+          box-shadow: 0 0 0 3px color-mix(in srgb, $primary-color 15%, transparent);
+          transform: translateY(-1px);
+        }
+      }
+
+      .form-textarea { resize: vertical; min-height: 84px; }
+    }
+
+    .form-actions {
+      display: flex;
+      justify-content: flex-end;
+      gap: 10px;
+      padding-top: 8px;
+      border-top: 1px solid color-mix(in srgb, $border-color 70%, transparent);
+    }
+  }
+}
+
+// Responsive fine-tuning
+@media (max-width: 768px) {
+  .project-detail-container {
+    .members-list { padding: 6px; }
+    .member-row {
+      padding: 10px 8px;
+      .role { flex-basis: 88px; }
+    }
+
+    .requirements-form { padding: 10px; }
+  }
+}
+
 // iOS风格按钮
 .button {
   border-radius: $border-radius;

+ 189 - 48
src/app/pages/customer-service/project-detail/project-detail.ts

@@ -1,10 +1,10 @@
 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 { FormsModule, ReactiveFormsModule, FormGroup, FormControl } from '@angular/forms';
+import { RouterModule, ActivatedRoute, Router } from '@angular/router';
 import { MatDialog, MatDialogModule } from '@angular/material/dialog';
 import { ProjectService } from '../../../services/project.service';
-import { Project, Task, Message, FileItem, CustomerFeedback, Milestone } from '../../../models/project.model';
+import { Project, Task, Message, FileItem, CustomerFeedback, Milestone, CustomerTag } from '../../../models/project.model';
 import { ModificationRequestDialog } from './modification-request-dialog';
 import { ComplaintWarningDialog } from './complaint-warning-dialog';
 import { RefundRequestDialog } from './refund-request-dialog';
@@ -93,9 +93,49 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
   // 当前激活的标签页
   activeTab = signal('overview');
   
+  // 允许的标签集合
+  private readonly allowedTabs = new Set(['overview','milestones','tasks','messages','files','members','requirements'])
+  
+  // 群聊相关状态
+  chatGroup = signal<{ name: string; link: string; createdAt: Date } | null>(null)
+  creatingGroup = signal(false)
+
+  // 需求补充相关(风格偏好与施工图纸)
+  styleOptions: string[] = ['现代简约','北欧','新中式','美式','工业风','法式','日式','混搭']
+  tempStylePreference: string = ''
+  showStyleEdit = signal(false)
+
+  hasStylePreference = computed(() => {
+    const p = this.project();
+    if (!p || !p.customerTags || p.customerTags.length === 0) return false
+    return p.customerTags.some(t => !!t.preference)
+  })
+
+  hasBlueprint = computed(() => {
+    return (this.files() || []).some(f => /施工图|blueprint|图纸/i.test(f.name))
+  })
   // 新消息内容
   newMessage = signal('');
   
+  // 组员管理
+  teamRoles: string[] = ['订单客服','主设计师','协助设计','渲染设计师']
+  teamMembers = signal<{ role: string; name: string }[]>([
+    { role: '订单客服', name: '客服小李' },
+    { role: '主设计师', name: '张设计师' },
+    { role: '协助设计', name: '王设计师' },
+    { role: '渲染设计师', name: '李设计师' }
+  ])
+  isEditingMembers = signal(false)
+  editedMembers = signal<{ role: string; name: string }[]>([])
+
+  // 需求补充表单(Reactive Forms)
+  requirementForm = new FormGroup({
+    summary: new FormControl<string>('', { nonNullable: true }),
+    priorityPoints: new FormControl<string>('', { nonNullable: true }),
+    constraints: new FormControl<string>('', { nonNullable: true }),
+    budget: new FormControl<string>('', { nonNullable: true })
+  })
+  
   // 项目阶段数据 - 进度时间轴
   projectStages: ProjectStage[] = [
     {      
@@ -149,23 +189,23 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
       responsible: '客服小李',
       details: '项目验收、交付和投诉处理'
     }
-  ];
+  ]
   
   // 企业微信聊天相关
-  @ViewChild('wechatMessages') wechatMessagesContainer: any;
-  wechatMessagesList: WechatMessage[] = [];
-  wechatInput = '';
-  scrollToBottom = false;
+  @ViewChild('wechatMessages') wechatMessagesContainer: any
+  wechatMessagesList: WechatMessage[] = []
+  wechatInput = ''
+  scrollToBottom = false
   
   // 历史服务记录相关
-  consultationRecords = signal<ConsultationRecord[]>([]);
-  cooperationProjects = signal<CooperationProject[]>([]);
-  historicalFeedbacks = signal<HistoricalFeedback[]>([]);
+  consultationRecords = signal<ConsultationRecord[]>([])
+  cooperationProjects = signal<CooperationProject[]>([])
+  historicalFeedbacks = signal<HistoricalFeedback[]>([])
   
   // 售后处理弹窗状态
-  showModificationRequest = false;
-  showComplaintWarning = false;
-  showRefundRequest = false;
+  showModificationRequest = false
+  showComplaintWarning = false
+  showRefundRequest = false
   
   // 渲染图只读预览弹窗状态与数据
   showRenderPreviewModal = false;
@@ -178,28 +218,37 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
     '已完成': 'success',
     '已暂停': 'secondary',
     '已取消': 'danger'
-  };
+  }
   
   // 计算完成进度
   completionProgress = computed(() => {
-    if (!this.tasks().length) return 0;
-    const completedTasks = this.tasks().filter(task => task.isCompleted).length;
-    return Math.round((completedTasks / this.tasks().length) * 100);
-  });
+    if (!this.tasks().length) return 0
+    const completedTasks = this.tasks().filter(task => task.isCompleted).length
+    return Math.round((completedTasks / this.tasks().length) * 100)
+  })
   
   constructor(
     private route: ActivatedRoute,
     private projectService: ProjectService,
-    private dialog: MatDialog
+    private dialog: MatDialog,
+    private router: Router
   ) {
     // 获取路由参数中的项目ID
     this.route.paramMap.subscribe(params => {
-      this.projectId = params.get('id') || '';
-    });
+      this.projectId = params.get('id') || ''
+    })
   }
   
   ngOnInit(): void {
-    this.loadProjectDetails();
+    // 解析 queryParams 中的 activeTab
+    this.route.queryParamMap.subscribe(q => {
+      const tab = (q.get('activeTab') || '').trim()
+      if (tab && this.allowedTabs.has(tab)) {
+        this.activeTab.set(tab)
+      }
+    })
+
+    this.loadProjectDetails()
   }
   
   // 加载项目详情
@@ -207,22 +256,22 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
     // 模拟从服务获取项目数据
     this.projectService.getProjectById(this.projectId).subscribe(project => {
       if (project) {
-        this.project.set(project);
+        this.project.set(project)
       }
-    });
+    })
     
     // 加载模拟数据
-    this.loadMockData();
+    this.loadMockData()
   }
   
   // 加载模拟数据
   // 修复 loadMockData 方法中的任务对象,确保类型一致性
   loadMockData(): void {
     // 初始化企业微信聊天
-    this.initWechatMessages();
+    this.initWechatMessages()
     
     // 初始化历史服务记录
-    this.initHistoricalServiceRecords();
+    this.initHistoricalServiceRecords()
     
     // 模拟里程碑数据
     this.milestones.set([
@@ -266,7 +315,7 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
         completedDate: undefined,
         isCompleted: false
       }
-    ]);
+    ])
     
     // 模拟任务数据 - 确保所有任务对象都有完整的必填属性
     this.tasks.set([
@@ -340,7 +389,7 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
         priority: 'medium',
         stage: '渲染'
       }
-    ]);
+    ])
     
     // 模拟消息数据
     this.messages.set([
@@ -384,7 +433,7 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
         isRead: true,
         type: 'text'
       }
-    ]);
+    ])
     
     // 模拟文件数据
     this.files.set([
@@ -438,7 +487,7 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
         uploadedAt: new Date('2023-06-04'),
         downloadCount: 7
       }
-    ]);
+    ])
     
     // 模拟客户反馈
     this.feedbacks.set([
@@ -458,17 +507,57 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
   
   // 切换标签页
   switchTab(tab: string): void {
-    this.activeTab.set(tab);
+    this.activeTab.set(tab)
+    // 同步到 URL,便于分享/刷新保持状态
+    this.router.navigate([], {
+      relativeTo: this.route,
+      queryParams: { activeTab: tab },
+      queryParamsHandling: 'merge'
+    })
+  }
+
+  // 组员编辑相关
+  startEditMembers(): void {
+    this.editedMembers.set(this.teamMembers().map(m => ({ ...m })))
+    this.isEditingMembers.set(true)
+  }
+  cancelEditMembers(): void {
+    this.isEditingMembers.set(false)
+  }
+  saveMembers(): void {
+    this.teamMembers.set(this.editedMembers().map(m => ({ ...m })))
+    this.isEditingMembers.set(false)
+  }
+
+  // 组员编辑:模板辅助方法
+  getEditedMemberName(role: string): string {
+    const found = this.editedMembers().find(m => m.role === role)
+    return found?.name || ''
+  }
+  updateEditedMember(role: string, name: string): void {
+    const others = this.editedMembers().filter(m => m.role !== role)
+    this.editedMembers.set([...others, { role, name }])
+  }
+  
+  // 需求表单相关
+  saveRequirementDraft(): void {
+    console.log('保存草稿:', this.requirementForm.getRawValue())
+  }
+  submitRequirements(): void {
+    const data = this.requirementForm.getRawValue()
+    console.log('提交需求:', data)
+    // 简单反馈
+    alert('需求已提交,客服与设计师会尽快处理。')
   }
   
   // 增强版发送消息功能
   sendMessage(): void {
     if (this.newMessage().trim()) {
       // 添加发送动画效果
-      const sendBtn = document.querySelector('.message-actions .primary-btn');
+      const sendBtn = document.querySelector('.message-actions .primary-btn')
       if (sendBtn) {
-        sendBtn.classList.add('sending');
-        setTimeout(() => sendBtn.classList.remove('sending'), 300);
+        sendBtn.classList.add('sending')
+        setTimeout(() => sendBtn.classList.remove('sending'), 300)
       }
 
       const newMsg: Message = {
@@ -478,28 +567,28 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
         timestamp: new Date(),
         isRead: true,
         type: 'text'
-      };
+      }
       
-      this.messages.set([...this.messages(), newMsg]);
-      this.newMessage.set('');
+      this.messages.set([...this.messages(), newMsg])
+      this.newMessage.set('')
       
       // 自动滚动到底部
       setTimeout(() => {
-        const container = document.querySelector('.messages-list');
+        const container = document.querySelector('.messages-list') as HTMLElement | null
         if (container) {
-          container.scrollTop = container.scrollHeight;
+          container.scrollTop = container.scrollHeight
         }
-      }, 100);
+      }, 100)
     }
   }
   
   // 增强版完成任务功能
   completeTask(taskId: string): void {
     // 添加完成动画效果
-    const taskElement = document.querySelector(`.task-item[data-id="${taskId}"]`);
+    const taskElement = document.querySelector(`.task-item[data-id="${taskId}"]`)
     if (taskElement) {
-      taskElement.classList.add('completing');
-      setTimeout(() => taskElement.classList.remove('completing'), 500);
+      taskElement.classList.add('completing')
+      setTimeout(() => taskElement.classList.remove('completing'), 500)
     }
 
     this.tasks.set(
@@ -508,10 +597,10 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
           ? { ...task, isCompleted: true, completedDate: new Date(), isOverdue: false }
           : task
       )
-    );
+    )
     
     // 播放完成音效
-    this.playSound('complete');
+    this.playSound('complete')
   }
   
   // 修复 completeMilestone 方法中的类型问题
@@ -522,7 +611,7 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
           ? { ...milestone, isCompleted: true, completedDate: new Date() }
           : milestone
       )
-    );
+    )
   }
   
   // 增强 formatDate 和 formatDateTime 方法的类型安全
@@ -848,4 +937,56 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
       }
     });
   }
+  
+  // 创建项目群聊(模拟)
+  createGroup(): void {
+    if (this.chatGroup()) return
+    this.creatingGroup.set(true)
+    const name = `项目群 - ${this.project()?.name || '未命名项目'}`
+    // 模拟异步创建
+    setTimeout(() => {
+      const link = `https://work.weixin.qq.com/grouplink/${this.projectId}-${Math.random().toString(36).slice(2, 8)}`
+      this.chatGroup.set({ name, link, createdAt: new Date() })
+      // 推送系统消息
+      this.wechatMessagesList.push({ sender: '系统', content: `已创建群聊「${name}」,群聊入口已生成。`, timestamp: new Date() })
+      this.creatingGroup.set(false)
+    }, 600)
+  }
+
+  // 上传施工图纸(模拟)
+  uploadBlueprint(event: Event): void {
+    const input = event.target as HTMLInputElement
+    const file = input?.files && input.files[0]
+    if (!file) return
+    const newItem: FileItem = {
+      id: 'bp-' + Date.now(),
+      name: file.name || `施工图-${Date.now()}.pdf`,
+      type: 'document',
+      size: `${Math.max(1, Math.round((file.size || 500000) / 1024))} KB`,
+      url: URL.createObjectURL(file),
+      uploadedBy: '客服',
+      uploadedAt: new Date(),
+      downloadCount: 0
+    }
+    const arr = [...this.files()]
+    arr.unshift(newItem)
+    this.files.set(arr)
+  }
+
+  // 保存风格偏好(写入 Project.customerTags)
+  saveStylePreference(): void {
+    const pref = (this.tempStylePreference || '').trim()
+    if (!pref) return
+    const p = this.project()
+    if (!p) return
+    const tags = [...(p.customerTags || [])]
+    if (tags.length === 0) {
+      tags.push({ source: '朋友圈', needType: '软装', preference: pref as CustomerTag['preference'], colorAtmosphere: '' })
+    } else {
+      // 替换第一个标签的偏好
+      tags[0] = { ...tags[0], preference: pref as CustomerTag['preference'] }
+    }
+    this.project.set({ ...p, customerTags: tags })
+    this.showStyleEdit.set(false)
+  }
 }

+ 20 - 18
src/app/pages/customer-service/project-detail/refund-request-dialog.ts

@@ -46,24 +46,26 @@ interface RefundRequestData {
           </mat-form-field>
         </div>
         
-        <div class="form-group" *ngIf="refundType === 'partial'">
-          <label for="refund-amount">退款金额 *</label>
-          <mat-form-field appearance="outline" class="form-field">
-            <input 
-              matInput 
-              [(ngModel)]="refundAmount" 
-              id="refund-amount" 
-              name="refundAmount" 
-              type="number" 
-              min="0" 
-              [max]="data.projectAmount"
-              step="0.01" 
-              placeholder="请输入退款金额"
-              required
-            >
-            <span matTextSuffix>¥</span>
-          </mat-form-field>
-        </div>
+        @if (refundType === 'partial') {
+          <div class="form-group">
+            <label for="refund-amount">退款金额 *</label>
+            <mat-form-field appearance="outline" class="form-field">
+              <input 
+                matInput 
+                [(ngModel)]="refundAmount" 
+                id="refund-amount" 
+                name="refundAmount" 
+                type="number" 
+                min="0" 
+                [max]="data.projectAmount"
+                step="0.01" 
+                placeholder="请输入退款金额"
+                required
+              >
+              <span matTextSuffix>¥</span>
+            </mat-form-field>
+          </div>
+        }
         
         <div class="form-group">
           <label for="refund-reason">退款原因 *</label>

+ 216 - 153
src/app/pages/customer-service/project-list/project-list.html

@@ -3,7 +3,7 @@
   <div class="project-content">
       <!-- 页面标题和操作 -->
       <div class="page-header">
-        <h2>项目列表</h2>
+        <h2>项目看板</h2>
         <div class="header-actions">
           <div class="search-container">
             <div class="search-box">
@@ -26,6 +26,17 @@
               </button>
             </div>
           </div>
+          <div class="view-toggle">
+            <button [class.active]="viewMode() === 'card'" (click)="toggleView('card')">卡片</button>
+            <button [class.active]="viewMode() === 'list'" (click)="toggleView('list')">列表</button>
+          </div>
+          <button class="add-project-btn" (click)="openCreateProjectModal()">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <line x1="12" y1="5" x2="12" y2="19"></line>
+              <line x1="5" y1="12" x2="19" y2="12"></line>
+            </svg>
+            添加项目
+          </button>
         </div>
       </div>
 
@@ -34,187 +45,239 @@
         <div class="filter-group">
           <label>状态筛选</label>
           <select (change)="onStatusChange($event)" [value]="statusFilter()">
-            <option *ngFor="let option of statusOptions" [value]="option.value">
-              {{ option.label }}
-            </option>
+            @for (option of statusOptions; track option.value) {
+              <option [value]="option.value">{{ option.label }}</option>
+            }
           </select>
         </div>
-        
         <div class="filter-group">
           <label>阶段筛选</label>
           <select (change)="onStageChange($event)" [value]="stageFilter()">
-            <option *ngFor="let option of stageOptions" [value]="option.value">
-              {{ option.label }}
-            </option>
+            @for (option of stageOptions; track option.value) {
+              <option [value]="option.value">{{ option.label }}</option>
+            }
           </select>
-        </div>  
-        
+        </div>
         <div class="filter-group">
           <label>排序方式</label>
           <select (change)="onSortChange($event)" [value]="sortBy()">
-            <option *ngFor="let option of sortOptions" [value]="option.value">
-              {{ option.label }}
-            </option>
+            @for (option of sortOptions; track option.value) {
+              <option [value]="option.value">{{ option.label }}</option>
+            }
           </select>
         </div>
-        
         <div class="filter-results">
           <span>共 {{ projects().length }} 个项目</span>
         </div>
       </div>
 
-      <!-- 项目列表 -->
-      <div class="project-grid">
-        <div *ngFor="let project of paginatedProjects()" class="project-card">
-          <div class="card-header">
-            <div class="card-title-section">
-              <h3 class="project-name">{{ project.name }}</h3>
-              <span class="project-id">#{{ project.id }}</span>
+      <!-- 视图:卡片模式(看板) -->
+      @if (viewMode() === 'card') {
+        <div class="kanban-container">
+          <div class="kanban-scroll">
+            <!-- 列头 -->
+            <div class="kanban-header">
+              @for (col of columns; track col.id) {
+                <div class="kanban-column-header" [attr.data-col]="col.id">
+                  <h3 class="column-title">{{ col.name }}</h3>
+                  <span class="stage-count">{{ getProjectsByColumn(col.id).length }}</span>
+                </div>
+              }
             </div>
-            <div class="card-tags">
-              <span class="project-tag">{{ project.tagDisplayText }}</span>
-              <span *ngIf="project.isUrgent" class="urgent-tag">紧急</span>
+            <!-- 列体 -->
+            <div class="kanban-body">
+              @for (col of columns; track col.id) {
+                <div class="kanban-column" [attr.data-col]="col.id">
+                  @for (project of getProjectsByColumn(col.id); track project.id) {
+                    <div class="kanban-card" (click)="navigateToProject(project, col.id)">
+                      <div class="kanban-card-header">
+                        <div class="left">
+                          <h4 class="project-name">{{ project.name }}</h4>
+                          <span class="project-id">#{{ project.id }}</span>
+                        </div>
+                        <div class="right">
+                          @if (col.id === 'pending') {
+                            <span class="pending-badge">待分配</span>
+                          }
+                          <span class="project-tag">{{ project.tagDisplayText }}</span>
+                          @if (project.isUrgent) {
+                            <span class="urgent-tag">紧急</span>
+                          }
+                        </div>
+                      </div>
+                      <div class="kanban-card-content">
+                        <p class="customer">客户:{{ project.customerName }}</p>
+                        <p class="assignee">设计师:{{ project.assigneeName || '未分配' }}</p>
+                        <p class="stage">阶段:<span class="stage-badge" [class]="getStageClass(project.currentStage)">{{ project.currentStage }}</span></p>
+                        <div class="progress-line">
+                          <div class="progress-bar">
+                            <div class="progress-fill" [style.width.percent]="project.progress"></div>
+                          </div>
+                          <span class="progress-text">{{ project.progress }}%</span>
+                        </div>
+                        <p class="deadline" [class.overdue]="project.daysUntilDeadline < 0" [class.urgent]="project.isUrgent">
+                          截止:{{ formatDate(project.deadline) }}
+                        </p>
+                      </div>
+                      <div class="kanban-card-footer">
+                        <button class="btn-link" (click)="$event.stopPropagation(); navigateToProject(project, col.id)">进入</button>
+                        <button class="btn-link" (click)="$event.stopPropagation(); navigateToMessages(project)">沟通管理</button>
+                      </div>
+                    </div>
+                  }
+                   @if (getProjectsByColumn(col.id).length === 0) {
+                     <div class="empty-column">
+                       <span class="empty-icon">📦</span>
+                       <p>暂无项目</p>
+                     </div>
+                   }
+                 </div>
+               }
             </div>
           </div>
-          
-          <div class="card-content">
-            <!-- 客户信息 -->
-            <div class="info-item">
-              <span class="info-label">客户</span>
-              <span class="info-value">{{ project.customerName }}</span>
-            </div>
-            
-            <!-- 设计师信息 -->
-            <div class="info-item">
-              <span class="info-label">设计师</span>
-              <span class="info-value">{{ project.assigneeName }}</span>
-            </div>
-            
-            <!-- 项目状态 -->
-            <div class="info-item">
-              <span class="info-label">状态</span>
-              <span class="info-value status-badge" [class]="getStatusClass(project.status)">
-                {{ project.status }}
-              </span>
-            </div>
-            
-            <!-- 当前阶段 -->
-            <div class="info-item">
-              <span class="info-label">阶段</span>
-              <span class="info-value stage-badge" [class]="getStageClass(project.currentStage)">
-                {{ project.currentStage }}
-              </span>
-            </div>
-            
-            <!-- 项目进度 -->
-            <div class="progress-section">
-              <div class="progress-header">
-                <span class="progress-label">进度</span>
-                <span class="progress-percentage">{{ project.progress }}%</span>
-              </div>
-              <div class="progress-bar">
-                <div 
-                  class="progress-fill" 
-                  [style.width.percent]="project.progress"
-                  [style.backgroundColor]="project.status === '进行中' ? '#1976d2' : '#757575'"
-                ></div>
-              </div>
-            </div>
-            
-            <!-- 时间信息 -->
-            <div class="timeline-info">
-              <div class="time-item">
-                <span class="time-label">创建时间</span>
-                <span class="time-value">{{ formatDate(project.createdAt) }}</span>
+        </div>
+      }
+
+      <!-- 视图:列表模式(沿用原卡片列表 + 分页) -->
+      @if (viewMode() === 'list') {
+        <div class="project-grid">
+          @for (project of paginatedProjects(); track project.id) {
+            <div class="project-card">
+              <div class="card-header">
+                <div class="card-title-section">
+                  <h3 class="project-name">{{ project.name }}</h3>
+                  <span class="project-id">#{{ project.id }}</span>
+                </div>
+                <div class="card-tags">
+                  <span class="project-tag">{{ project.tagDisplayText }}</span>
+                  @if (project.isUrgent) { <span class="urgent-tag">紧急</span> }
+                </div>
               </div>
-              <div class="time-item">
-                <span class="time-label">截止日期</span>
-                <span 
-                  class="time-value deadline"
-                  [class.overdue]="project.daysUntilDeadline < 0"
-                  [class.urgent]="project.isUrgent"
-                >
-                  {{ formatDate(project.deadline) }} ({{ project.daysUntilDeadline >= 0 ? '还有' + project.daysUntilDeadline + '天' : '已逾期' + getAbsValue(project.daysUntilDeadline) + '天' }})
-                </span>
+              <div class="card-content">
+                <div class="info-item">
+                  <span class="info-label">客户</span>
+                  <span class="info-value">{{ project.customerName }}</span>
+                </div>
+                <div class="info-item">
+                  <span class="info-label">设计师</span>
+                  <span class="info-value">{{ project.assigneeName || '未分配' }}</span>
+                </div>
+                <div class="info-item">
+                  <span class="info-label">状态</span>
+                  <span class="info-value status-badge" [class]="getStatusClass(project.status)">{{ project.status }}</span>
+                </div>
+                <div class="info-item">
+                  <span class="info-label">阶段</span>
+                  <span class="info-value stage-badge" [class]="getStageClass(project.currentStage)">{{ project.currentStage }}</span>
+                </div>
+                <div class="progress-section">
+                  <div class="progress-header">
+                    <span class="progress-label">进度</span>
+                    <span class="progress-percentage">{{ project.progress }}%</span>
+                  </div>
+                  <div class="progress-bar">
+                    <div class="progress-fill" [style.width.percent]="project.progress" [style.backgroundColor]="project.status === '进行中' ? '#1976d2' : '#757575'"></div>
+                  </div>
+                </div>
+                <div class="timeline-info">
+                  <div class="time-item">
+                    <span class="time-label">创建时间</span>
+                    <span class="time-value">{{ formatDate(project.createdAt) }}</span>
+                  </div>
+                  <div class="time-item">
+                    <span class="time-label">截止日期</span>
+                    <span class="time-value deadline" [class.overdue]="project.daysUntilDeadline < 0" [class.urgent]="project.isUrgent">
+                      {{ formatDate(project.deadline) }} ({{ project.daysUntilDeadline >= 0 ? '还有' + project.daysUntilDeadline + '天' : '已逾期' + getAbsValue(project.daysUntilDeadline) + '天' }})
+                    </span>
+                  </div>
+                </div>
+                @if (project.highPriorityNeeds && project.highPriorityNeeds.length > 0) {
+                  <div class="needs-section">
+                    <span class="needs-label">高优先级需求:</span>
+                    <ul class="needs-list">
+                      @for (need of project.highPriorityNeeds; track $index) {
+                        <li>
+                          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                            <circle cx="12" cy="12" r="10"></circle>
+                            <line x1="12" y1="8" x2="12" y2="12"></line>
+                            <line x1="12" y1="16" x2="12.01" y2="16"></line>
+                          </svg>
+                          {{ need }}
+                        </li>
+                      }
+                    </ul>
+                  </div>
+                }
               </div>
-            </div>
-            
-            <!-- 高优先级需求 -->
-            <div *ngIf="project.highPriorityNeeds && project.highPriorityNeeds.length > 0" class="needs-section">
-              <span class="needs-label">高优先级需求:</span>
-              <ul class="needs-list">
-                <li *ngFor="let need of project.highPriorityNeeds">
-                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                    <circle cx="12" cy="12" r="10"></circle>
-                    <line x1="12" y1="8" x2="12" y2="12"></line>
-                    <line x1="12" y1="16" x2="12.01" y2="16"></line>
+              <div class="card-footer">
+                <button class="secondary-btn card-action" (click)="navigateToMessages(project)">
+                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
+                  </svg>
+                  <span>沟通管理</span>
+                </button>
+                <button class="primary-btn card-action" (click)="navigateToProject(project, getColumnIdForProject(project))">
+                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+                    <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
                   </svg>
-                  {{ need }}
-                </li>
-              </ul>
+                  <span>查看详情</span>
+                </button>
+              </div>
             </div>
-          </div>
-          
-          <div class="card-footer">
-            <button class="secondary-btn card-action">
+          }
+        </div>
+
+        <!-- 分页控件 -->
+        @if (totalPages() > 1) {
+          <div class="pagination">
+            <button class="pagination-btn" (click)="prevPage()" [disabled]="currentPage() === 1">
               <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
+                <line x1="15" y1="18" x2="9" y2="12"></line>
+                <line x1="9" y1="18" x2="15" y2="12"></line>
               </svg>
-              <span>联系</span>
             </button>
-            <a [routerLink]="['/customer-service/project-detail', project.id]" class="primary-btn card-action">
+            @for (page of pageNumbers(); track page) {
+              <button class="pagination-btn" [class.active]="page === currentPage()" (click)="goToPage(page)">{{ page }}</button>
+            }
+            @if (totalPages() > 5) { <span class="pagination-ellipsis">...</span> }
+            @if (totalPages() > 5) {
+              <button class="pagination-btn" (click)="goToPage(totalPages())">{{ totalPages() }}</button>
+            }
+            <button class="pagination-btn" (click)="nextPage()" [disabled]="currentPage() === totalPages()">
               <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-                <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>
+                <line x1="9" y1="18" x2="15" y2="12"></line>
+                <line x1="15" y1="6" x2="9" y2="12"></line>
               </svg>
-              <span>查看详情</span>
-            </a>
+            </button>
           </div>
-        </div>
-      </div>
+        }
+      }
 
-      <!-- 分页控件 -->
-      <div class="pagination" *ngIf="totalPages() > 1">
-        <button 
-          class="pagination-btn" 
-          (click)="prevPage()" 
-          [disabled]="currentPage() === 1"
-        >
-          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <line x1="15" y1="18" x2="9" y2="12"></line>
-            <line x1="9" y1="18" x2="15" y2="12"></line>
-          </svg>
-        </button>
-        
-        <button 
-          *ngFor="let page of pageNumbers()"
-          class="pagination-btn" 
-          [class.active]="page === currentPage()"
-          (click)="goToPage(page)"
-        >
-          {{ page }}
-        </button>
-        
-        <!-- 省略号 -->
-        <span *ngIf="totalPages() > 5" class="pagination-ellipsis">...</span>
-        
-        <button 
-          *ngIf="totalPages() > 5" 
-          class="pagination-btn" 
-          (click)="goToPage(totalPages())"
-        >
-          {{ totalPages() }}
-        </button>
-        
-        <button 
-          class="pagination-btn" 
-          (click)="nextPage()" 
-          [disabled]="currentPage() === totalPages()"
-        >
-          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <line x1="9" y1="18" x2="15" y2="12"></line>
-            <line x1="15" y1="6" x2="9" y2="12"></line>
-          </svg>
-        </button>
-      </div>
+      <!-- 添加项目弹窗 -->
+      @if (createModalVisible()) {
+        <div class="modal-backdrop" (click)="cancelCreateProject()"></div>
+        <div class="modal">
+          <div class="modal-header">
+            <h3>添加项目</h3>
+            <button class="close-btn" (click)="cancelCreateProject()">✖</button>
+          </div>
+          <div class="modal-body">
+            <div class="form-item">
+              <label>客户名称</label>
+              <input type="text" [value]="newCustomerName()" (input)="newCustomerName.set($any($event.target).value)" placeholder="请输入客户名称" />
+            </div>
+            <div class="form-item">
+              <label>核心需求</label>
+              <textarea rows="4" [value]="newRequirement()" (input)="newRequirement.set($any($event.target).value)" placeholder="请输入客户核心需求...">
+              </textarea>
+            </div>
+            <div class="tip">仅需填写以上两项,后续信息在详情页补充</div>
+          </div>
+          <div class="modal-footer">
+            <button class="btn-secondary" (click)="cancelCreateProject()">取消</button>
+            <button class="btn-primary" (click)="submitCreateProject()">提交创建</button>
+          </div>
+        </div>
+      }
     </div>
 </div>

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

@@ -806,4 +806,356 @@ $transition: all 0.3s ease;
 
 .project-card:nth-child(4n) {
   animation-delay: 0.3s;
+}
+
+/* --- Kanban, toolbar, and modal styles (scoped to customer-service project list) --- */
+
+/* Header actions layout */
+.project-content .page-header .header-actions {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+/* View toggle buttons */
+.project-content .page-header .header-actions .view-toggle {
+  display: inline-flex;
+  border: 1px solid $border-color;
+  background-color: $bg-white;
+  border-radius: 6px;
+  overflow: hidden;
+}
+
+.project-content .page-header .header-actions .view-toggle button {
+  padding: 8px 12px;
+  border: none;
+  background: transparent;
+  color: $text-secondary;
+  font-size: 14px;
+  cursor: pointer;
+  transition: $transition;
+}
+
+.project-content .page-header .header-actions .view-toggle button:hover:not(.active) {
+  background-color: $bg-light;
+  color: $text-primary;
+}
+
+.project-content .page-header .header-actions .view-toggle button.active {
+  background-color: $primary-light;
+  color: $primary-color;
+  font-weight: 600;
+}
+
+/* Add project button */
+.project-content .page-header .header-actions .add-project-btn {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  padding: 8px 12px;
+  border: none;
+  border-radius: 6px;
+  color: #fff;
+  background-color: $primary-color;
+  cursor: pointer;
+  transition: $transition;
+}
+
+.project-content .page-header .header-actions .add-project-btn:hover {
+  background-color: #1565c0;
+}
+
+.project-content .page-header .header-actions .add-project-btn:active {
+  background-color: #0E42CB;
+}
+
+/* Kanban layout */
+.project-content .kanban-container {
+  background-color: transparent;
+}
+
+.project-content .kanban-scroll {
+  overflow-x: auto;
+  padding-bottom: 8px;
+}
+
+.project-content .kanban-header,
+.project-content .kanban-body {
+  display: grid;
+  grid-auto-flow: column;
+  grid-auto-columns: 320px;
+  gap: 16px;
+}
+
+/* Column header */
+.project-content .kanban-header .kanban-column-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px;
+  background-color: $bg-white;
+  border: 1px solid $border-color;
+  border-radius: 8px;
+  box-shadow: $box-shadow;
+}
+
+.project-content .kanban-header .kanban-column-header .column-title {
+  margin: 0;
+  font-size: 14px;
+  font-weight: 600;
+  color: $text-primary;
+}
+
+.project-content .kanban-header .kanban-column-header .stage-count {
+  font-size: 12px;
+  color: $text-light;
+  background-color: $bg-light;
+  border-radius: 12px;
+  padding: 2px 8px;
+}
+
+/* Column body */
+.project-content .kanban-body .kanban-column {
+  min-height: 420px;
+  padding: 12px;
+  background-color: $bg-white;
+  border: 1px dashed $border-color;
+  border-radius: 8px;
+  box-shadow: $box-shadow;
+}
+
+/* Card */
+.project-content .kanban-card {
+  background-color: $bg-white;
+  border: 1px solid $border-color;
+  border-radius: 8px;
+  box-shadow: $box-shadow;
+  padding: 12px;
+  margin-bottom: 12px;
+  cursor: pointer;
+  transition: transform 0.2s ease, box-shadow 0.2s ease;
+}
+
+.project-content .kanban-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
+}
+
+.project-content .kanban-card-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 8px;
+}
+
+.project-content .kanban-card-header .left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.project-content .kanban-card-header .left .project-name {
+  margin: 0;
+  font-size: 16px;
+  color: $text-primary;
+}
+
+.project-content .kanban-card-header .left .project-id {
+  font-size: 12px;
+  color: $text-light;
+}
+
+.project-content .kanban-card-header .right {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+/* Pending badge */
+.project-content .pending-badge {
+  color: $warning-color;
+  background-color: #fff8e1;
+  border: 1px solid #ffe0b2;
+  padding: 2px 8px;
+  border-radius: 12px;
+  font-size: 12px;
+  font-weight: 600;
+}
+
+.project-content .kanban-card-content {
+  font-size: 13px;
+  color: $text-secondary;
+}
+
+.project-content .kanban-card-content .stage-badge {
+  padding: 2px 8px;
+  border-radius: 12px;
+  font-size: 12px;
+}
+
+.project-content .kanban-card-content .progress-line {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.project-content .kanban-card-content .progress-line .progress-bar {
+  flex: 1;
+  height: 6px;
+  background-color: $bg-light;
+  border-radius: 3px;
+  overflow: hidden;
+}
+
+.project-content .kanban-card-content .progress-line .progress-fill {
+  height: 100%;
+  background-color: $primary-color;
+}
+
+.project-content .kanban-card-content .progress-line .progress-text {
+  font-size: 12px;
+  color: $text-secondary;
+}
+
+.project-content .kanban-card-footer {
+  display: flex;
+  justify-content: flex-end;
+}
+
+.project-content .kanban-card-footer .btn-link {
+  border: none;
+  background: none;
+  color: $primary-color;
+  cursor: pointer;
+  padding: 6px 8px;
+}
+
+.project-content .kanban-card-footer .btn-link:hover {
+  text-decoration: underline;
+}
+
+/* Modal */
+.modal-backdrop {
+  position: fixed;
+  inset: 0;
+  background: rgba(0, 0, 0, 0.4);
+  z-index: 1000;
+}
+
+.modal {
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  width: 480px;
+  max-width: 90vw;
+  background: $bg-white;
+  border-radius: 8px;
+  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.2);
+  z-index: 1001;
+  overflow: hidden;
+}
+
+.modal .modal-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  border-bottom: 1px solid $border-color;
+}
+
+.modal .modal-header h3 {
+  margin: 0;
+  font-size: 18px;
+  color: $text-primary;
+}
+
+.modal .modal-header .close-btn {
+  border: none;
+  background: none;
+  font-size: 16px;
+  color: $text-secondary;
+  cursor: pointer;
+}
+
+.modal .modal-header .close-btn:hover {
+  color: $text-primary;
+}
+
+.modal .modal-body {
+  padding: 16px;
+}
+
+.modal .modal-body .form-item {
+  margin-bottom: 12px;
+}
+
+.modal .modal-body .form-item label {
+  display: block;
+  font-size: 13px;
+  color: $text-secondary;
+  margin-bottom: 6px;
+}
+
+.modal .modal-body .form-item input,
+.modal .modal-body .form-item textarea {
+  width: 100%;
+  border: 1px solid $border-color;
+  border-radius: 6px;
+  padding: 8px 10px;
+  font-size: 14px;
+  background: $bg-white;
+  outline: none;
+}
+
+.modal .modal-body .form-item input:focus,
+.modal .modal-body .form-item textarea:focus {
+  border-color: $primary-color;
+  box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.1);
+}
+
+.modal .modal-body .tip {
+  font-size: 12px;
+  color: $text-light;
+  margin-top: 8px;
+}
+
+.modal .modal-footer {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  gap: 12px;
+  padding: 12px 16px;
+  border-top: 1px solid $border-color;
+}
+
+.modal .modal-footer .btn-secondary,
+.modal .modal-footer .btn-primary {
+  border: none;
+  border-radius: 6px;
+  padding: 8px 16px;
+  font-size: 14px;
+  cursor: pointer;
+}
+
+.modal .modal-footer .btn-secondary {
+  background: $bg-light;
+  color: $text-primary;
+}
+
+.modal .modal-footer .btn-secondary:hover {
+  background: $border-color;
+}
+
+.modal .modal-footer .btn-primary {
+  background: $primary-color;
+  color: #fff;
+}
+
+.modal .modal-footer .btn-primary:hover {
+  background: #1565c0;
+}
+
+.modal .modal-footer .btn-primary:active {
+  background: #0E42CB;
 }

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

@@ -1,7 +1,7 @@
 import { Component, OnInit, signal, computed } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
-import { RouterModule } from '@angular/router';
+import { Router, RouterModule } from '@angular/router';
 import { ProjectService } from '../../../services/project.service';
 import { Project, ProjectStatus, ProjectStage } from '../../../models/project.model';
 
@@ -27,6 +27,25 @@ export class ProjectList implements OnInit {
   // 原始项目数据(用于筛选)
   allProjects = signal<Project[]>([]);
 
+  // 视图模式:卡片 / 列表(默认卡片)
+  viewMode = signal<'card' | 'list'>('card');
+
+  // 看板列配置
+  columns = [
+    { id: 'pending', name: '待分配' },
+    { id: 'req', name: '需求深化' },
+    { id: 'delivery', name: '交付中' },
+    { id: 'done', name: '已完成' }
+  ] as const;
+
+  // 创建项目弹窗
+  createModalVisible = signal(false);
+  newCustomerName = signal('');
+  newRequirement = signal('');
+
+  // 基础项目集合(服务端返回 + 本地生成),用于二次处理
+  private baseProjects: Project[] = [];
+
   // 添加toggleSidebar方法
   toggleSidebar(): void {
     // 侧边栏切换逻辑
@@ -45,7 +64,7 @@ export class ProjectList implements OnInit {
   // 每页显示数量
   pageSize = 8;
   
-  // 分页后的项目列表
+  // 分页后的项目列表(列表模式下可用)
   paginatedProjects = computed(() => {
     const filteredProjects = this.projects();
     const startIndex = (this.currentPage() - 1) * this.pageSize;
@@ -59,11 +78,11 @@ export class ProjectList implements OnInit {
   
   // 筛选和排序选项
   statusOptions = [
-    { value: 'all', label: '全部状态' },
-    { value: '进行中', label: '进行中' },
-    { value: '已完成', label: '已完成' },
-    { value: '已暂停', label: '已暂停' },
-    { value: '已延期', label: '已延期' }
+    { value: 'all', label: '全部' },
+    { value: 'pending', label: '待分配' },
+    { value: 'req', label: '需求深化' },
+    { value: 'delivery', label: '交付中' },
+    { value: 'done', label: '已完成' }
   ];
   
   stageOptions = [
@@ -82,19 +101,32 @@ export class ProjectList implements OnInit {
     { value: 'name', label: '项目名称' }
   ];
   
-  constructor(private projectService: ProjectService) {}
+  constructor(private projectService: ProjectService, private router: Router) {}
   
   ngOnInit(): void {
+    // 读取上次的视图记忆
+    const saved = localStorage.getItem('cs.viewMode');
+    if (saved === 'card' || saved === 'list') {
+      this.viewMode.set(saved);
+    }
     this.loadProjects();
   }
   
+  // 视图切换
+  toggleView(mode: 'card' | 'list') {
+    if (this.viewMode() !== mode) {
+      this.viewMode.set(mode);
+      localStorage.setItem('cs.viewMode', mode);
+    }
+  }
+
   // 加载项目列表
   loadProjects(): void {
     this.projectService.getProjects().subscribe(projects => {
       this.allProjects.set(projects);
-      // 添加模拟数据以丰富列表
-      const enrichedProjects = [...projects, ...this.generateMockProjects()];
-      this.processProjects(enrichedProjects);
+      // 生成基础列表(服务返回 + 模拟)
+      this.baseProjects = [...projects, ...this.generateMockProjects()];
+      this.processProjects(this.baseProjects);
     });
   }
   
@@ -138,10 +170,11 @@ export class ProjectList implements OnInit {
       );
     }
     
-    // 状态筛选
+    // 状态筛选(按看板列映射)
     if (this.statusFilter() !== 'all') {
+      const col = this.statusFilter() as 'pending' | 'req' | 'delivery' | 'done';
       filteredProjects = filteredProjects.filter(project => 
-        project.status === this.statusFilter()
+        this.getColumnIdForProject(project) === col
       );
     }
     
@@ -248,8 +281,8 @@ export class ProjectList implements OnInit {
         currentStage: stage,
         createdAt: createdDate,
         deadline: deadlineDate,
-        assigneeId: `designer${i % 3 + 1}`,
-        assigneeName: `设计师${String.fromCharCode(64 + (i % 3 + 1))}`,
+        assigneeId: i % 4 === 0 ? '' : `designer${i % 3 + 1}`,
+        assigneeName: i % 4 === 0 ? '' : `设计师${String.fromCharCode(64 + (i % 3 + 1))}`,
         skillsRequired: [preference + '风格', needType]
       });
     }
@@ -257,126 +290,203 @@ export class ProjectList implements OnInit {
     return mockProjects;
   }
   
-  // 处理搜索
+  // 列表/筛选交互(保留已有实现)
   onSearch(): void {
-    this.currentPage.set(1);
-    this.processProjects(this.allProjects());
+    // 搜索后重算
+    this.processProjects(this.baseProjects);
   }
-  
-  // 处理状态筛选
+
   onStatusChange(event: Event): void {
-    const selectElement = event.target as HTMLSelectElement;
-    this.statusFilter.set(selectElement.value);
-    this.currentPage.set(1);
-    this.processProjects(this.allProjects());
+    const value = (event.target as HTMLSelectElement).value;
+    this.statusFilter.set(value);
+    this.processProjects(this.baseProjects);
   }
-  
-  // 处理阶段筛选
+
   onStageChange(event: Event): void {
-    const selectElement = event.target as HTMLSelectElement;
-    this.stageFilter.set(selectElement.value);
-    this.currentPage.set(1);
-    this.processProjects(this.allProjects());
+    const value = (event.target as HTMLSelectElement).value;
+    this.stageFilter.set(value);
+    this.processProjects(this.baseProjects);
   }
-  
-  // 处理排序变化
+
   onSortChange(event: Event): void {
-    const selectElement = event.target as HTMLSelectElement;
-    this.sortBy.set(selectElement.value);
-    this.processProjects(this.allProjects());
+    const value = (event.target as HTMLSelectElement).value;
+    this.sortBy.set(value);
+    this.processProjects(this.baseProjects);
   }
-  
-  // 分页导航
+
   goToPage(page: number): void {
-    if (page >= 1 && page <= this.totalPages() && page !== this.currentPage()) {
+    if (page >= 1 && page <= this.totalPages()) {
       this.currentPage.set(page);
-      // 不需要重新加载整个项目列表,currentPage变更后computed属性会自动更新
     }
   }
 
   prevPage(): void {
     if (this.currentPage() > 1) {
-      this.goToPage(this.currentPage() - 1);
+      this.currentPage.update(v => v - 1);
     }
   }
 
   nextPage(): void {
     if (this.currentPage() < this.totalPages()) {
-      this.goToPage(this.currentPage() + 1);
+      this.currentPage.update(v => v + 1);
     }
   }
 
-  // 计算当前显示的页码数组
   pageNumbers = computed(() => {
-    const maxVisible = 5;
     const total = this.totalPages();
-    const current = this.currentPage();
     const pages: number[] = [];
-
-    if (total <= maxVisible) {
-      // 如果总页数不超过最大可见页数,显示所有页码
-      for (let i = 1; i <= total; i++) {
-        pages.push(i);
-      }
-    } else {
-      // 处理中间页码显示
-      if (current <= Math.ceil(maxVisible / 2)) {
-        // 当前页在前面部分
-        for (let i = 1; i <= maxVisible; i++) {
-          pages.push(i);
-        }
-      } else if (current >= total - Math.floor(maxVisible / 2)) {
-        // 当前页在后面部分
-        for (let i = total - (maxVisible - 1); i <= total; i++) {
-          pages.push(i);
-        }
-      } else {
-        // 当前页在中间部分
-        for (let i = current - Math.floor(maxVisible / 2); i <= current + Math.floor(maxVisible / 2); i++) {
-          pages.push(i);
-        }
-      }
-    }
+    const maxToShow = Math.min(total, 5);
+    for (let i = 1; i <= maxToShow; i++) pages.push(i);
     return pages;
   });
 
-  // 计算绝对值的辅助方法(用于模板中)
   getAbsValue(value: number): number {
     return Math.abs(value);
   }
-  
-  // 格式化日期
+
   formatDate(date: Date): string {
-    return new Date(date).toLocaleDateString('zh-CN', {
-      year: 'numeric',
-      month: '2-digit',
-      day: '2-digit'
-    });
+    const d = new Date(date);
+    const y = d.getFullYear();
+    const m = String(d.getMonth() + 1).padStart(2, '0');
+    const day = String(d.getDate()).padStart(2, '0');
+    return `${y}-${m}-${day}`;
   }
-  
-  // 获取状态样式类
+
   getStatusClass(status: string): string {
-    const statusClasses: Record<string, string> = {
-      '进行中': 'status-active',
-      '已完成': 'status-completed',
-      '已暂停': 'status-paused',
-      '已延期': 'status-overdue'
-    };
-    
-    return statusClasses[status] || '';
+    switch (status) {
+      case '进行中': return 'status-in-progress';
+      case '已完成': return 'status-completed';
+      case '已暂停': return 'status-paused';
+      case '已延期': return 'status-overdue';
+      default: return '';
+    }
   }
-  
-  // 获取阶段样式类
+
   getStageClass(stage: string): string {
-    const stageClasses: Record<string, string> = {
-      '需求沟通': 'stage-communication',
-      '建模': 'stage-modeling',
-      '软装': 'stage-decoration',
-      '渲染': 'stage-rendering',
-      '后期': 'stage-postproduction',
-      '投诉处理': 'stage-completed'
+    switch (stage) {
+      case '需求沟通': return 'stage-requirement';
+      case '建模': return 'stage-modeling';
+      case '软装': return 'stage-soft';
+      case '渲染': return 'stage-render';
+      case '后期': return 'stage-post';
+      case '投诉处理': return 'stage-issue';
+      default: return '';
+    }
+  }
+
+  // 看板分组逻辑
+  private isPendingAssignment(p: Project): boolean {
+    return !p.assigneeId || p.assigneeId.trim() === '';
+  }
+
+  private isRequirementElaboration(p: Project): boolean {
+    // 已分配但仍在需求沟通阶段
+    return !this.isCompleted(p) && !this.isPendingAssignment(p) && p.currentStage === '需求沟通';
+  }
+
+  private isInDelivery(p: Project): boolean {
+    const deliveryStages: ProjectStage[] = ['建模', '软装', '渲染', '后期'];
+    return !this.isCompleted(p) && !this.isPendingAssignment(p) && deliveryStages.includes(p.currentStage);
+  }
+
+  private isCompleted(p: Project): boolean {
+    return p.status === '已完成';
+  }
+
+  getProjectsByColumn(columnId: 'pending' | 'req' | 'delivery' | 'done'): ProjectListItem[] {
+    const list = this.projects();
+    switch (columnId) {
+      case 'pending':
+        return list.filter(p => this.isPendingAssignment(p));
+      case 'req':
+        return list.filter(p => this.isRequirementElaboration(p));
+      case 'delivery':
+        return list.filter(p => this.isInDelivery(p));
+      case 'done':
+        return list.filter(p => this.isCompleted(p));
+    }
+  }
+
+  // 新增:根据项目状态与阶段推断所在看板列
+  getColumnIdForProject(project: ProjectListItem): 'pending' | 'req' | 'delivery' | 'done' {
+    if (this.isPendingAssignment(project)) return 'pending';
+    if (this.isRequirementElaboration(project)) return 'req';
+    if (this.isInDelivery(project)) return 'delivery';
+    if (this.isCompleted(project)) return 'done';
+    return 'req';
+  }
+
+  // 详情跳转(附带角色与模块)
+  navigateToProject(project: ProjectListItem, columnId: 'pending' | 'req' | 'delivery' | 'done') {
+    const tab = columnId === 'pending' ? 'members' : (columnId === 'req' ? 'requirements' : 'overview');
+    this.router.navigate(['/customer-service/project-detail', project.id], {
+      queryParams: { role: 'customer_service', activeTab: tab }
+    });
+  }
+
+  // 新增:直接进入沟通管理(消息)标签
+  navigateToMessages(project: ProjectListItem) {
+    this.router.navigate(['/customer-service/project-detail', project.id], {
+      queryParams: { role: 'customer_service', activeTab: 'messages' }
+    });
+  }
+  
+  // 打开/关闭创建项目弹窗
+  openCreateProjectModal() {
+    this.newCustomerName.set('');
+    this.newRequirement.set('');
+    this.createModalVisible.set(true);
+  }
+
+  cancelCreateProject() {
+    this.createModalVisible.set(false);
+  }
+
+  // 提交创建项目(最小必填:客户名称 + 核心需求)
+  submitCreateProject() {
+    const customerName = this.newCustomerName().trim();
+    const requirementText = this.newRequirement().trim();
+    if (!customerName || !requirementText) {
+      alert('请填写客户名称和核心需求');
+      return;
+    }
+
+    const payload = {
+      customerId: 'temp-' + Date.now(),
+      customerName,
+      requirement: requirementText,
+      referenceCases: [],
+      tags: { followUpStatus: '待分配' }
     };
-    
-    return stageClasses[stage] || '';
+
+    this.projectService.createProject(payload).subscribe(res => {
+      if (res.success) {
+        // 组装前端项目对象(默认待分配:无assignee)
+        const now = new Date();
+        const deadline = new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000);
+        const newProject: Project = {
+          id: res.projectId,
+          name: `${customerName} 项目`,
+          customerName,
+          customerTags: [],
+          highPriorityNeeds: [],
+          status: '进行中',
+          currentStage: '需求沟通',
+          createdAt: now,
+          deadline: deadline,
+          assigneeId: '',
+          assigneeName: '',
+          skillsRequired: []
+        };
+        this.baseProjects = [newProject, ...this.baseProjects];
+        this.processProjects(this.baseProjects);
+        this.createModalVisible.set(false);
+        // 新建后滚动到“待分配”列顶部
+        setTimeout(() => {
+          const el = document.querySelector('.kanban-column[data-col="pending"]');
+          el?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
+        }, 0);
+      }
+    });
   }
 }

+ 2 - 2
src/app/pages/hr/designer-profile/designer-profile.html

@@ -100,7 +100,7 @@
                 <div class="section-title mt">核心能力</div>
                 <div class="kv-list">
                   <div class="kv-item full">
-                    <span class="k">技能匹配度</span>
+                   
                     <span class="v">
                       @if (d.screening?.core?.skills?.length) {
                         @for (s of d.screening?.core?.skills || []; track s) {
@@ -112,7 +112,7 @@
                     </span>
                   </div>
                   <div class="kv-item"><span class="k">相关项目经验</span><span class="v">{{ d.screening?.core?.projects || '-' }}</span></div>
-                  <div class="kv-item"><span class="k">证书资质</span><span class="v">{{ d.screening?.core?.certs || '-' }}</span></div>
+                 
                 </div>
 
                 <div class="section-title mt">求职意向</div>

+ 0 - 1
src/app/pages/hr/designer-profile/designer-profile.ts

@@ -377,7 +377,6 @@ export class DesignerProfile implements OnInit {
         screening: {
           basic: { age: 30, education: '本科', years: '7年', targetRole: '高级效果图设计师' },
           core: {
-            skills: ['3D建模', '渲染', '后期处理'],
             projects: '住宅/商业空间等10+项目,擅长现代与新中式风格',
             certs: '3D效果图高级设计师认证'
           },

+ 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>

+ 45 - 36
src/app/pages/hr/employee-detail/employee-detail.ts

@@ -48,10 +48,11 @@ export class EmployeeDetailComponent implements OnInit {
 
   employee = signal<Employee | null>(null);
   screeningForm: FormGroup;
-  // 新增:审核弹窗相关状态
-  showReviewDialog = false;
-  reviewResult: 'approved' | 'rejected' | null = null;
-  reviewComment = '';
+
+  // 审核弹窗状态(Signals)
+  showReviewDialog = signal(false);
+  reviewComment = signal('');
+  reviewResult = signal<'approved' | 'rejected' | ''>('');
 
   // 模拟员工数据
   private mockEmployees: Employee[] = [
@@ -270,17 +271,17 @@ export class EmployeeDetailComponent implements OnInit {
   }
 
   approveScreening() {
-    // 改为打开审核弹窗
-    this.reviewResult = 'approved';
-    this.reviewComment = '';
-    this.showReviewDialog = true;
+    // 打开审核弹窗(Signals)
+    this.reviewResult.set('approved');
+    this.reviewComment.set('');
+    this.showReviewDialog.set(true);
   }
 
   rejectScreening() {
-    // 改为打开审核弹窗
-    this.reviewResult = 'rejected';
-    this.reviewComment = '';
-    this.showReviewDialog = true;
+    // 打开审核弹窗(Signals)
+    this.reviewResult.set('rejected');
+    this.reviewComment.set('');
+    this.showReviewDialog.set(true);
   }
 
   private openScreeningDialog() {
@@ -308,30 +309,6 @@ export class EmployeeDetailComponent implements OnInit {
     }
   }
 
-  // 新增:模板绑定的方法
-  closeReviewDialog() {
-    this.showReviewDialog = false;
-  }
-
-  submitReview() {
-    if (!this.reviewResult) {
-      this.showSnackBar('请选择审核结果');
-      return;
-    }
-    const currentEmployee = this.employee();
-    if (currentEmployee) {
-      const updatedEmployee: Employee = {
-        ...currentEmployee,
-        screeningStatus: this.reviewResult,
-        screeningComment: this.reviewComment,
-        screeningTime: new Date()
-      } as Employee;
-      this.employee.set(updatedEmployee);
-      this.showSnackBar('审核结果已保存');
-    }
-    this.showReviewDialog = false;
-    this.reviewComment = '';
-  }
 
   viewAttendance() {
     this.router.navigate(['/hr/attendance'], { 
@@ -350,4 +327,36 @@ export class EmployeeDetailComponent implements OnInit {
       verticalPosition: 'top'
     });
   }
+
+  // 审核对话框方法(Signals)
+  openReviewDialog() {
+    this.showReviewDialog.set(true);
+    this.reviewComment.set('');
+    this.reviewResult.set('');
+  }
+
+  closeReviewDialog() {
+    this.showReviewDialog.set(false);
+    this.reviewComment.set('');
+    this.reviewResult.set('');
+  }
+
+  submitReview() {
+    const currentEmployee = this.employee();
+    if (!this.reviewResult()) {
+      this.showSnackBar('请选择审核结果');
+      return;
+    }
+    if (currentEmployee) {
+      const updatedEmployee: Employee = {
+        ...currentEmployee,
+        screeningStatus: this.reviewResult(),
+        screeningComment: this.reviewComment(),
+        screeningTime: new Date()
+      } as Employee;
+      this.employee.set(updatedEmployee);
+      this.showSnackBar('审核结果已保存');
+    }
+    this.closeReviewDialog();
+  }
 }

+ 33 - 10
src/app/pages/hr/employee-records/employee-records.html

@@ -1,4 +1,4 @@
-<div class="employee-records-container">
+<div class="employee-records-container" [class.sensitive-expanded]="isAnySensitiveExpanded()">
   <!-- 页面标题和操作栏 -->
   <div class="page-header">
     <div class="title-section">
@@ -117,6 +117,34 @@
         <td mat-cell *matCellDef="let employee">{{employee.phone}}</td>
       </ng-container>
 
+      <!-- 新增:身份证号列,仅在在职员工显示,初始脱敏 -->
+      <ng-container matColumnDef="idCard">
+        <th mat-header-cell *matHeaderCellDef>身份证号</th>
+        <td mat-cell *matCellDef="let employee">
+          @if (employee.status === '在职') {
+            <span class="idcard" [class.expanded]="isSensitiveExpanded(employee.id)" [matTooltip]="employee.idCard || ''">
+              {{ isSensitiveExpanded(employee.id) ? (employee.idCard || '') : maskIdCard(employee.idCard || '') }}
+            </span>
+          } @else {
+            <span class="muted">-</span>
+          }
+        </td>
+      </ng-container>
+
+      <!-- 新增:银行卡号列,仅在在职员工显示,初始脱敏 -->
+      <ng-container matColumnDef="bankCard">
+        <th mat-header-cell *matHeaderCellDef>银行卡号</th>
+        <td mat-cell *matCellDef="let employee">
+          @if (employee.status === '在职') {
+            <span class="bankcard" [class.expanded]="isSensitiveExpanded(employee.id)" [matTooltip]="formatBankCard(employee.bankCard || '')">
+              {{ isSensitiveExpanded(employee.id) ? formatBankCard(employee.bankCard || '') : maskBankCard(employee.bankCard || '') }}
+            </span>
+          } @else {
+            <span class="muted">-</span>
+          }
+        </td>
+      </ng-container>
+
       <!-- 入职日期列 -->
       <ng-container matColumnDef="hireDate">
         <th mat-header-cell *matHeaderCellDef>入职日期</th>
@@ -135,22 +163,17 @@
         </td>
       </ng-container>
 
-      <!-- 操作列 -->
+      <!-- 操作列:去掉“查看详情”按钮,改为眼睛图标控制敏感信息显示 -->
       <ng-container matColumnDef="actions">
         <th mat-header-cell *matHeaderCellDef>操作</th>
         <td mat-cell *matCellDef="let employee" class="actions-cell">
-          <mat-chip color="primary" selected class="view-detail-chip" (click)="goToDetails(employee)" matTooltip="查看该员工详情">
-            <mat-icon>visibility</mat-icon>
-            查看详情
-          </mat-chip>
+          <button mat-icon-button color="primary" (click)="toggleSensitive(employee.id)" [matTooltip]="isSensitiveExpanded(employee.id) ? '隐藏敏感信息' : '查看敏感信息'">
+            <mat-icon>{{ isSensitiveExpanded(employee.id) ? 'visibility_off' : 'visibility' }}</mat-icon>
+          </button>
           <button mat-icon-button [matMenuTriggerFor]="actionMenu" aria-label="操作菜单">
             <mat-icon>more_vert</mat-icon>
           </button>
           <mat-menu #actionMenu="matMenu">
-            <button mat-menu-item (click)="goToDetails(employee)">
-              <mat-icon>visibility</mat-icon>
-              <span>查看详情</span>
-            </button>
             <button mat-menu-item (click)="openEditEmployeeDialog(employee)">
               <mat-icon>edit</mat-icon>
               <span>编辑</span>

+ 30 - 81
src/app/pages/hr/employee-records/employee-records.scss

@@ -272,94 +272,43 @@
 }
 
 // 修复表格行高度对齐问题
+.employee-records-container {
+  // 当有敏感信息展开时,轻微扩大表格容器可视宽度(通过阴影与过渡体现)
+  &.sensitive-expanded .employee-table-container {
+    box-shadow: 0 12px 30px rgba(22,93,255,0.18);
+    transition: box-shadow 0.25s ease;
+  }
+}
+
 .employee-table-container {
-  background-color: #fff;
-  border-radius: 12px;
-  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.06);
-  margin-bottom: 24px;
-  overflow: auto;
-  
   .employee-table {
-    width: 100%;
-    
-    th.mat-header-cell {
-      background: linear-gradient(180deg, #f7f9fc 0%, #eef3ff 100%);
-      color: #1a3a6e;
-      font-weight: 600;
-      padding: 12px 16px;
-      min-height: 56px; // 统一表头高度
-      vertical-align: middle;
-    }
-    
-    td.mat-cell {
-      padding: 12px 16px;
-      min-height: 56px; // 统一单元格高度
-      vertical-align: middle;
-      border-bottom: 1px solid #f0f0f0; // 确保分割线清晰
-    }
-    
-    tr.mat-row {
-      min-height: 56px; // 统一行高度
-      
-      &:hover {
-        background-color: #f8f9fa;
+    .idcard,
+    .bankcard {
+      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+      letter-spacing: 0.3px;
+      white-space: nowrap;
+      max-width: 160px;
+      display: inline-block;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      transition: max-width 0.25s ease;
+
+      &.expanded {
+        max-width: 360px; // 展开后显示更长内容
       }
     }
-    
-    // 确保所有单元格内容垂直居中
-    .mat-cell, .mat-header-cell {
-      display: flex;
-      align-items: center;
+
+    .muted {
+      color: #bdbdbd;
     }
-    
-    .status-badge {
-      display: inline-flex;
-      align-items: center;
-      justify-content: center;
-      padding: 6px 12px;
-      border-radius: 12px;
-      font-size: 12px;
-      font-weight: 500;
-      text-align: center;
-      min-width: 60px;
-      height: 24px; // 固定高度
-      
-      &.status-active {
-        background-color: #e6f7ee;
-        color: #00a854;
+
+    td.mat-cell {
+      .mat-icon {
+        color: #5a6cf3;
       }
-      
-      &.status-probation {
-        background-color: #fff7e6;
-        color: #fa8c16;
+      button.mat-icon-button:hover .mat-icon {
+        color: #3f51b5;
       }
-      
-      &.status-resigned {
-        background-color: #f5f5f5;
-        color: #999;
-      }
-    }
-  }
-  
-  .empty-state {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    padding: 48px 24px;
-    text-align: center;
-    
-    mat-icon {
-      font-size: 48px;
-      height: 48px;
-      width: 48px;
-      color: #ccc;
-      margin-bottom: 16px;
-    }
-    
-    p {
-      color: #666;
-      margin-bottom: 16px;
     }
   }
 }

+ 58 - 7
src/app/pages/hr/employee-records/employee-records.ts

@@ -530,7 +530,9 @@ export class EmployeeRecords implements OnInit {
       gender: '男',
       birthDate: new Date('1990-01-01'),
       hireDate: new Date('2022-01-15'),
-      status: '在职'
+      status: '在职',
+      idCard: '110105199001011234',
+      bankCard: '6222021234567890'
     },
     {
       id: '2',
@@ -543,7 +545,9 @@ export class EmployeeRecords implements OnInit {
       gender: '女',
       birthDate: new Date('1992-05-20'),
       hireDate: new Date('2022-03-10'),
-      status: '在职'
+      status: '在职',
+      idCard: '110105199205201256',
+      bankCard: '6216619876543210'
     },
     {
       id: '3',
@@ -556,7 +560,9 @@ export class EmployeeRecords implements OnInit {
       gender: '男',
       birthDate: new Date('1988-11-15'),
       hireDate: new Date('2023-01-05'),
-      status: '试用期'
+      status: '试用期',
+      idCard: '110105198811151278',
+      bankCard: '6225880011223344'
     },
     {
       id: '4',
@@ -569,7 +575,9 @@ export class EmployeeRecords implements OnInit {
       gender: '男',
       birthDate: new Date('1985-07-22'),
       hireDate: new Date('2021-06-18'),
-      status: '在职'
+      status: '在职',
+      idCard: '110105198507221299',
+      bankCard: '6217009988776655'
     },
     {
       id: '5',
@@ -582,7 +590,9 @@ export class EmployeeRecords implements OnInit {
       gender: '男',
       birthDate: new Date('1983-03-30'),
       hireDate: new Date('2020-09-01'),
-      status: '在职'
+      status: '在职',
+      idCard: '110105198303301233',
+      bankCard: '6222035566778899'
     }
   ]);
   
@@ -593,11 +603,17 @@ export class EmployeeRecords implements OnInit {
   filterStatus = signal('');
   
   // 表格列定义
-  displayedColumns = ['select', 'name', 'employeeId', 'department', 'position', 'phone', 'hireDate', 'status', 'actions'];
+  displayedColumns = ['select', 'name', 'employeeId', 'department', 'position', 'phone', 'idCard', 'bankCard', 'hireDate', 'status', 'actions'];
   
   // 选中的员工
   selectedEmployees = signal<string[]>([]);
+
+  // 展示敏感信息的行(按员工id)
+  sensitiveExpandedIds = signal<string[]>([]);
   
+  // 是否有任意行展开敏感信息
+  isAnySensitiveExpanded = computed(() => this.sensitiveExpandedIds().length > 0);
+
   // 部门和职位数据
   departments = [
     { id: '1', name: '设计部', employeeCount: 25 },
@@ -635,7 +651,42 @@ export class EmployeeRecords implements OnInit {
   ) {}
   
   ngOnInit() {}
-  
+
+  // 掩码与格式化工具
+  maskIdCard(id: string): string {
+    if (!id) return '';
+    if (id.length >= 18) return `${id.slice(0, 6)}********${id.slice(-4)}`;
+    if (id.length > 6) return `${id.slice(0, 3)}****${id.slice(-2)}`;
+    return id;
+  }
+
+  maskBankCard(card: string): string {
+    if (!card) return '';
+    const compact = card.replace(/\s+/g, '');
+    if (compact.length <= 8) return compact;
+    const first4 = compact.slice(0, 4);
+    const last4 = compact.slice(-4);
+    return `${first4} **** **** ${last4}`;
+  }
+
+  formatBankCard(card: string): string {
+    if (!card) return '';
+    return card.replace(/\s+/g, '').replace(/(\d{4})(?=\d)/g, '$1 ').trim();
+  }
+
+  isSensitiveExpanded(id: string): boolean {
+    return this.sensitiveExpandedIds().includes(id);
+  }
+
+  toggleSensitive(id: string) {
+    const list = this.sensitiveExpandedIds();
+    if (list.includes(id)) {
+      this.sensitiveExpandedIds.set(list.filter(x => x !== id));
+    } else {
+      this.sensitiveExpandedIds.set([...list, id]);
+    }
+  }
+
   // 打开新增员工对话框
   openAddEmployeeDialog() {
     const dialogRef = this.dialog.open(AddEmployeeDialog, {