浏览代码

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

0235711 3 天之前
父节点
当前提交
5ea45f33da
共有 52 个文件被更改,包括 8251 次插入1272 次删除
  1. 6 1
      angular.json
  2. 7 0
      package-lock.json
  3. 1 0
      package.json
  4. 20 1
      src/app/app.routes.ts
  5. 8 8
      src/app/pages/admin/admin-layout/admin-layout.html
  6. 130 0
      src/app/pages/admin/customers/customers.html
  7. 74 0
      src/app/pages/admin/customers/customers.scss
  8. 185 0
      src/app/pages/admin/customers/customers.ts
  9. 73 10
      src/app/pages/admin/dashboard/dashboard.html
  10. 575 129
      src/app/pages/admin/dashboard/dashboard.scss
  11. 480 76
      src/app/pages/admin/dashboard/dashboard.ts
  12. 234 0
      src/app/pages/admin/designers/designers.html
  13. 28 0
      src/app/pages/admin/designers/designers.scss
  14. 340 0
      src/app/pages/admin/designers/designers.ts
  15. 68 0
      src/app/pages/admin/finance/finance.html
  16. 7 0
      src/app/pages/admin/finance/finance.scss
  17. 90 0
      src/app/pages/admin/finance/finance.ts
  18. 75 42
      src/app/pages/admin/system-settings/system-settings.html
  19. 208 0
      src/app/pages/admin/system-settings/system-settings.scss
  20. 286 32
      src/app/pages/admin/system-settings/system-settings.ts
  21. 422 199
      src/app/pages/customer-service/consultation-order/consultation-order.html
  22. 823 94
      src/app/pages/customer-service/consultation-order/consultation-order.scss
  23. 165 8
      src/app/pages/customer-service/consultation-order/consultation-order.ts
  24. 155 0
      src/app/pages/customer-service/consultation-order/project-group-dialog.component.ts
  25. 4 4
      src/app/pages/customer-service/customer-service-layout/customer-service-layout.scss
  26. 31 0
      src/app/pages/customer-service/customer-service.routes.ts
  27. 174 1
      src/app/pages/customer-service/dashboard/dashboard.html
  28. 29 0
      src/app/pages/customer-service/dashboard/dashboard.routes.ts
  29. 889 436
      src/app/pages/customer-service/dashboard/dashboard.scss
  30. 308 28
      src/app/pages/customer-service/dashboard/dashboard.ts
  31. 42 0
      src/app/pages/customer-service/dashboard/pages/assignment-list/assignment-list.component.html
  32. 148 0
      src/app/pages/customer-service/dashboard/pages/assignment-list/assignment-list.component.scss
  33. 105 0
      src/app/pages/customer-service/dashboard/pages/assignment-list/assignment-list.component.ts
  34. 42 0
      src/app/pages/customer-service/dashboard/pages/consultation-list/consultation-list.component.html
  35. 166 0
      src/app/pages/customer-service/dashboard/pages/consultation-list/consultation-list.component.scss
  36. 90 0
      src/app/pages/customer-service/dashboard/pages/consultation-list/consultation-list.component.ts
  37. 38 0
      src/app/pages/customer-service/dashboard/pages/exception-list/exception-list.component.html
  38. 158 0
      src/app/pages/customer-service/dashboard/pages/exception-list/exception-list.component.scss
  39. 110 0
      src/app/pages/customer-service/dashboard/pages/exception-list/exception-list.component.ts
  40. 56 0
      src/app/pages/customer-service/dashboard/pages/revenue-detail/revenue-detail.component.html
  41. 161 0
      src/app/pages/customer-service/dashboard/pages/revenue-detail/revenue-detail.component.scss
  42. 120 0
      src/app/pages/customer-service/dashboard/pages/revenue-detail/revenue-detail.component.ts
  43. 170 0
      src/app/pages/customer-service/project-detail/complaint-warning-dialog.ts
  44. 162 0
      src/app/pages/customer-service/project-detail/modification-request-dialog.ts
  45. 121 136
      src/app/pages/customer-service/project-detail/project-detail.html
  46. 201 23
      src/app/pages/customer-service/project-detail/project-detail.scss
  47. 197 16
      src/app/pages/customer-service/project-detail/project-detail.ts
  48. 185 0
      src/app/pages/customer-service/project-detail/refund-request-dialog.ts
  49. 42 26
      src/app/pages/customer-service/project-list/project-list.scss
  50. 1 1
      src/app/pages/customer-service/project-list/project-list.ts
  51. 34 0
      src/app/services/project.service.ts
  52. 7 1
      src/styles.scss

+ 6 - 1
angular.json

@@ -47,7 +47,12 @@
                   "maximumError": "100kB"
                 }
               ],
-              "outputHashing": "all"
+              "outputHashing": "all",
+              "optimization": {
+                "fonts": {
+                  "inline": false
+                }
+              }
             },
             "development": {
               "optimization": false,

+ 7 - 0
package-lock.json

@@ -18,6 +18,7 @@
         "@angular/router": "^20.1.0",
         "chart.js": "^4.5.0",
         "echarts": "^6.0.0",
+        "roboto-fontface": "^0.10.0",
         "rxjs": "~7.8.0",
         "tslib": "^2.3.0",
         "zone.js": "~0.15.0"
@@ -10260,6 +10261,12 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/roboto-fontface": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz",
+      "integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g==",
+      "license": "Apache-2.0"
+    },
     "node_modules/rolldown": {
       "version": "1.0.0-beta.32",
       "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.32.tgz",

+ 1 - 0
package.json

@@ -31,6 +31,7 @@
     "@angular/router": "^20.1.0",
     "chart.js": "^4.5.0",
     "echarts": "^6.0.0",
+    "roboto-fontface": "^0.10.0",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",
     "zone.js": "~0.15.0"

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

@@ -8,6 +8,11 @@ import { ConsultationOrder } from './pages/customer-service/consultation-order/c
 import { ProjectList } from './pages/customer-service/project-list/project-list';
 import { ProjectDetail } from './pages/customer-service/project-detail/project-detail';
 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';
@@ -41,6 +46,10 @@ import { UserManagement } from './pages/admin/user-management/user-management';
 import { SystemSettings } from './pages/admin/system-settings/system-settings';
 import { Logs } from './pages/admin/logs/logs';
 import { ApiIntegrations } from './pages/admin/api-integrations/api-integrations';
+// 新增:管理员子模块页面
+import { Designers } from './pages/admin/designers/designers';
+import { Customers } from './pages/admin/customers/customers';
+import { FinancePage } from './pages/admin/finance/finance';
 
 export const routes: Routes = [
   // 客服路由
@@ -53,7 +62,12 @@ export const routes: Routes = [
       { path: 'consultation-order', component: ConsultationOrder, title: '客户咨询与下单' },
       { path: 'project-list', component: ProjectList, title: '项目列表' },
       { path: 'project-detail/:id', component: ProjectDetail, title: '项目详情' },
-      { path: 'case-library', component: CaseLibrary, title: '案例库' }
+      { path: 'case-library', component: CaseLibrary, title: '案例库' },
+      // 工作台子页面路由
+      { path: 'consultation-list', component: ConsultationListComponent, title: '咨询列表' },
+      { path: 'assignment-list', component: AssignmentListComponent, title: '待派单列表' },
+      { path: 'exception-list', component: ExceptionListComponent, title: '异常项目列表' },
+      { path: 'revenue-detail', component: RevenueDetailComponent, title: '今日成交详情' }
     ]
   },
 
@@ -118,6 +132,11 @@ export const routes: Routes = [
       // 用户与角色管理相关路由
       { path: 'user-management', component: UserManagement, title: '用户与角色管理' },
       
+      // 新增:设计师、客户、财务管理
+      { path: 'designers', component: Designers, title: '设计师管理' },
+      { path: 'customers', component: Customers, title: '客户管理' },
+      { path: 'finance', component: FinancePage, title: '财务管理' },
+      
       // 系统设置相关路由
       { path: 'system-settings', component: SystemSettings, title: '系统设置' },
       

+ 8 - 8
src/app/pages/admin/admin-layout/admin-layout.html

@@ -28,13 +28,13 @@
       <nav class="sidebar-nav">
         <div class="nav-section">
           <h3 class="section-title">核心功能</h3>
-          <a href="/admin/dashboard" class="nav-item" routerLinkActive="active">
+          <a routerLink="/admin/dashboard" class="nav-item" routerLinkActive="active">
             <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
               <path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"></path>
             </svg>
             <span>总览看板</span>
           </a>
-          <a href="/admin/project-management" class="nav-item" routerLinkActive="active">
+          <a routerLink="/admin/project-management" class="nav-item" routerLinkActive="active">
             <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
               <line x1="8" y1="6" x2="21" y2="6"></line>
               <line x1="8" y1="12" x2="21" y2="12"></line>
@@ -45,7 +45,7 @@
             </svg>
             <span>项目管理</span>
           </a>
-          <a href="/admin/designers" class="nav-item" routerLinkActive="active">
+          <a routerLink="/admin/designers" class="nav-item" routerLinkActive="active">
             <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
               <path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
               <circle cx="9" cy="7" r="4"></circle>
@@ -54,14 +54,14 @@
             </svg>
             <span>设计师管理</span>
           </a>
-          <a href="/admin/customers" class="nav-item" routerLinkActive="active">
+          <a routerLink="/admin/customers" class="nav-item" routerLinkActive="active">
             <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
               <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
               <circle cx="12" cy="7" r="4"></circle>
             </svg>
             <span>客户管理</span>
           </a>
-          <a href="/admin/finance" class="nav-item" routerLinkActive="active">
+          <a routerLink="/admin/finance" class="nav-item" routerLinkActive="active">
             <svg width="20" height="20" 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>
@@ -72,14 +72,14 @@
         
         <div class="nav-section">
           <h3 class="section-title">系统设置</h3>
-          <a href="/admin/system-settings" class="nav-item" routerLinkActive="active">
+          <a routerLink="/admin/system-settings" class="nav-item" routerLinkActive="active">
             <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
               <circle cx="12" cy="12" r="3"></circle>
               <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
             </svg>
             <span>系统设置</span>
           </a>
-          <a href="/admin/logs" class="nav-item" routerLinkActive="active">
+          <a routerLink="/admin/logs" 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>
@@ -89,7 +89,7 @@
             </svg>
             <span>系统日志</span>
           </a>
-          <a href="/admin/api-integrations" class="nav-item" routerLinkActive="active">
+          <a routerLink="/admin/api-integrations" class="nav-item" routerLinkActive="active">
             <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
               <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
               <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>

+ 130 - 0
src/app/pages/admin/customers/customers.html

@@ -0,0 +1,130 @@
+<div class="customers-page">
+  <div class="page-header">
+    <div class="header-left">
+      <h2 class="page-title">客户管理</h2>
+      <p class="page-description">管理客户档案、分级与成交数据</p>
+    </div>
+    <div class="header-actions">
+      <button class="btn primary" (click)="addCustomer()">+ 新建客户</button>
+      <button class="btn" (click)="exportCustomers()">导出</button>
+    </div>
+  </div>
+
+  <div class="stats-cards">
+    <div class="stat-card">
+      <div class="stat-label">客户总数</div>
+      <div class="stat-value">{{ total() }}</div>
+    </div>
+    <div class="stat-card">
+      <div class="stat-label">活跃客户</div>
+      <div class="stat-value">{{ active() }}</div>
+    </div>
+    <div class="stat-card">
+      <div class="stat-label">VIP客户</div>
+      <div class="stat-value">{{ vip() }}</div>
+    </div>
+    <div class="stat-card">
+      <div class="stat-label">累计成交额</div>
+      <div class="stat-value">{{ formatAmount(amount()) }}</div>
+    </div>
+  </div>
+
+  <div class="toolbar">
+    <div class="search">
+      <input placeholder="搜索客户名称/联系人/电话" (input)="keyword.set($any($event.target).value)" [value]="keyword()"/>
+    </div>
+    <div class="filters">
+      <select (change)="status.set($any($event.target).value)">
+        <option value="all">全部状态</option>
+        <option value="active">活跃</option>
+        <option value="inactive">沉默</option>
+      </select>
+      <select (change)="level.set($any($event.target).value)">
+        <option value="all">全部等级</option>
+        <option value="normal">普通</option>
+        <option value="vip">VIP</option>
+        <option value="svip">SVIP</option>
+      </select>
+      <button class="btn" (click)="resetFilters()">重置</button>
+    </div>
+  </div>
+
+  <div class="table-card">
+    <div class="table header">
+      <div>客户名称</div>
+      <div>联系人</div>
+      <div>电话</div>
+      <div>等级</div>
+      <div>状态</div>
+      <div>项目数</div>
+      <div>累计成交</div>
+      <div>加入日期</div>
+      <div>操作</div>
+    </div>
+    <div class="table row" *ngFor="let c of filtered">
+      <div class="name">
+        <div class="title">{{ c.name }}</div>
+      </div>
+      <div>{{ c.contact }}</div>
+      <div>{{ c.phone }}</div>
+      <div>
+        <span class="tag" [class.vip]="c.level==='vip'" [class.svip]="c.level==='svip'">{{ c.level.toUpperCase() }}</span>
+      </div>
+      <div>
+        <span class="dot" [class.green]="c.status==='active'" [class.gray]="c.status==='inactive'"></span>
+        {{ c.status==='active' ? '活跃' : '沉默' }}
+      </div>
+      <div>{{ c.projects }}</div>
+      <div>{{ formatAmount(c.totalAmount) }}</div>
+      <div>{{ c.joinDate }}</div>
+      <div class="actions">
+        <button class="icon" (click)="viewCustomer(c)">详</button>
+        <button class="icon" (click)="editCustomer(c)">编</button>
+        <button class="icon danger">删</button>
+      </div>
+    </div>
+    <div class="empty" *ngIf="filtered.length === 0">暂无数据</div>
+  </div>
+
+  <!-- 侧边面板 -->
+  <div class="panel-overlay" *ngIf="showPanel" (click)="closePanel()">
+    <div class="side-panel" (click)="$event.stopPropagation()">
+      <div class="panel-header">
+        <h3>{{ panelMode==='add' ? '新建客户' : panelMode==='detail' ? '客户详情' : '编辑客户' }}</h3>
+        <button class="close-btn" (click)="closePanel()">×</button>
+      </div>
+
+      <!-- 详情 -->
+      <div class="panel-content" *ngIf="panelMode==='detail' && currentCustomer">
+        <div class="detail-section"><label>客户名称</label><span>{{ currentCustomer.name }}</span></div>
+        <div class="detail-section"><label>联系人</label><span>{{ currentCustomer.contact }}</span></div>
+        <div class="detail-section"><label>电话</label><span>{{ currentCustomer.phone }}</span></div>
+        <div class="detail-section"><label>等级</label><span class="tag" [class.vip]="currentCustomer.level==='vip'" [class.svip]="currentCustomer.level==='svip'">{{ currentCustomer.level.toUpperCase() }}</span></div>
+        <div class="detail-section"><label>状态</label><span>{{ currentCustomer.status==='active' ? '活跃' : '沉默' }}</span></div>
+        <div class="detail-section"><label>项目数</label><span>{{ currentCustomer.projects }}</span></div>
+        <div class="detail-section"><label>累计成交</label><span>{{ formatAmount(currentCustomer.totalAmount) }}</span></div>
+        <div class="detail-section"><label>加入日期</label><span>{{ currentCustomer.joinDate }}</span></div>
+      </div>
+
+      <!-- 新增/编辑表单 -->
+      <div class="panel-content" *ngIf="panelMode==='add' || panelMode==='edit'">
+        <form class="customer-form">
+          <div class="form-group"><label>客户名称 *</label><input type="text" [(ngModel)]="formModel.name" name="name" placeholder="请输入客户名称" required></div>
+          <div class="form-group"><label>联系人</label><input type="text" [(ngModel)]="formModel.contact" name="contact" placeholder="请输入联系人"></div>
+          <div class="form-group"><label>电话</label><input type="text" [(ngModel)]="formModel.phone" name="phone" placeholder="请输入电话"></div>
+          <div class="form-group"><label>等级</label><select [(ngModel)]="formModel.level" name="level"><option value="normal">普通</option><option value="vip">VIP</option><option value="svip">SVIP</option></select></div>
+          <div class="form-group"><label>状态</label><select [(ngModel)]="formModel.status" name="status"><option value="active">活跃</option><option value="inactive">沉默</option></select></div>
+          <div class="form-group"><label>项目数</label><input type="number" [(ngModel)]="formModel.projects" name="projects" min="0" placeholder="0"></div>
+          <div class="form-group"><label>累计成交(¥)</label><input type="number" [(ngModel)]="formModel.totalAmount" name="totalAmount" min="0" step="100" placeholder="0"></div>
+          <div class="form-group"><label>加入日期</label><input type="date" [(ngModel)]="formModel.joinDate" name="joinDate"></div>
+        </form>
+      </div>
+
+      <div class="panel-footer">
+        <button class="btn" (click)="closePanel()">取消</button>
+        <button class="btn primary" *ngIf="panelMode==='add'" (click)="saveCustomer()">保存</button>
+        <button class="btn primary" *ngIf="panelMode==='edit'" (click)="updateCustomer()">更新</button>
+      </div>
+    </div>
+  </div>
+</div>

+ 74 - 0
src/app/pages/admin/customers/customers.scss

@@ -0,0 +1,74 @@
+.customers-page{padding:24px}
+.page-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.page-title{font-size:20px;margin:0 0 6px}.page-description{color:#64748b;margin:0}.btn{padding:8px 12px;border-radius:8px;border:1px solid #e5e7eb;background:#fff;cursor:pointer}.btn.primary{background:#165DFF;color:#fff;border-color:#165DFF}
+.stats-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px}.stat-card{background:#fff;border-radius:12px;box-shadow:0 1px 2px rgba(0,0,0,.06);padding:16px}.stat-label{color:#64748b;font-size:12px}.stat-value{font-size:22px;font-weight:700;margin-top:6px}
+.toolbar{display:flex;justify-content:space-between;align-items:center;background:#fff;padding:12px 16px;border-radius:12px;box-shadow:0 1px 2px rgba(0,0,0,.06);margin-bottom:12px}.search input{width:320px;padding:8px 10px;border:1px solid #e5e7eb;border-radius:8px}.filters{display:flex;gap:8px;align-items:center}.filters select{padding:8px;border:1px solid #e5e7eb;border-radius:8px}
+.table-card{background:#fff;border-radius:12px;box-shadow:0 1px 2px rgba(0,0,0,.06)}.table{display:grid;grid-template-columns:2.2fr 1.1fr 1.4fr .8fr .9fr .8fr 1.2fr 1.1fr 1fr;align-items:center;padding:12px 16px;border-bottom:1px solid #f1f5f9}.table.header{color:#64748b;font-weight:600;background:#f8fafc;border-top-left-radius:12px;border-top-right-radius:12px}.table.row{background:#fff}.table .name .title{font-weight:600}.tag{display:inline-block;padding:2px 8px;border-radius:999px;background:#eef2ff;color:#4f46e5;font-weight:600;font-size:12px}.tag.vip{background:#fff7ed;color:#f97316}.tag.svip{background:#fff1f2;color:#ef4444}.dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px;background:#94a3b8}.dot.green{background:#10b981}.dot.gray{background:#94a3b8}.actions{display:flex;gap:6px;justify-content:flex-end}.icon{border:1px solid #e5e7eb;background:#fff;border-radius:8px;padding:6px 8px;cursor:pointer}.icon.danger{color:#ef4444;border-color:#fecaca}
+.empty{padding:24px;text-align:center;color:#94a3b8}
+@media (max-width: 992px){.stats-cards{grid-template-columns:repeat(2,1fr)}.search input{width:100%}.toolbar{flex-direction:column;gap:10px;align-items:flex-start}}
+
+/* 侧边面板通用样式(与设计师页面保持一致) */
+.panel-overlay {
+  position: fixed;
+  inset: 0;
+  background: rgba(0, 0, 0, 0.45);
+  display: flex;
+  justify-content: flex-end;
+  z-index: 1000;
+}
+
+.side-panel {
+  width: 560px;
+  height: 100%;
+  background: #fff;
+  box-shadow: -4px 0 16px rgba(0,0,0,0.08);
+  display: flex;
+  flex-direction: column;
+  animation: slideIn .2s ease;
+}
+
+@keyframes slideIn {
+  from { transform: translateX(24px); opacity: 0; }
+  to { transform: translateX(0); opacity: 1; }
+}
+
+.panel-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 20px;
+  border-bottom: 1px solid #f0f0f0;
+
+  h3 { margin: 0; font-size: 18px; font-weight: 600; }
+  .close-btn { border: none; background: transparent; font-size: 20px; cursor: pointer; }
+}
+
+.panel-content {
+  padding: 16px 20px;
+  overflow: auto;
+  flex: 1;
+
+  .detail-section {
+    display: flex;
+    margin-bottom: 12px;
+    label { width: 92px; color: #888; }
+    span { color: #333; }
+  }
+
+  .customer-form {
+    .form-group { margin-bottom: 12px; display: flex; flex-direction: column; }
+    label { margin-bottom: 6px; color: #666; }
+    input, select, textarea { padding: 8px 10px; border: 1px solid #e5e6eb; border-radius: 6px; }
+    textarea { min-height: 88px; resize: vertical; }
+  }
+}
+
+.panel-footer {
+  padding: 12px 20px;
+  border-top: 1px solid #f0f0f0;
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+
+  .btn { padding: 8px 16px; border-radius: 6px; border: 1px solid #e5e6eb; background: #fff; cursor: pointer; }
+  .btn.primary { background: #3a7afe; color: #fff; border-color: #3a7afe; }
+}

+ 185 - 0
src/app/pages/admin/customers/customers.ts

@@ -0,0 +1,185 @@
+import { Component, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+interface Customer {
+  id: string;
+  name: string;
+  contact: string;
+  phone: string;
+  level: 'normal' | 'vip' | 'svip';
+  status: 'active' | 'inactive';
+  projects: number;
+  totalAmount: number;
+  joinDate: string; // yyyy-mm-dd
+}
+
+@Component({
+  selector: 'app-admin-customers',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './customers.html',
+  styleUrl: './customers.scss'
+})
+export class Customers {
+  // 统计
+  total = signal(356);
+  active = signal(298);
+  vip = signal(42);
+  amount = signal(1118000);
+
+  // 数据
+  customers = signal<Customer[]>([
+    { id: 'c001', name: '杭州 | 岚光科技', contact: '刘先生', phone: '138****2011', level: 'vip', status: 'active', projects: 6, totalAmount: 120000, joinDate: '2025-03-18' },
+    { id: 'c002', name: '苏州 | 宏图建设', contact: '王女士', phone: '137****8890', level: 'normal', status: 'active', projects: 3, totalAmount: 60000, joinDate: '2024-12-05' },
+    { id: 'c003', name: '宁波 | 海纳传媒', contact: '周先生', phone: '139****7621', level: 'svip', status: 'active', projects: 12, totalAmount: 380000, joinDate: '2023-10-01' },
+    { id: 'c004', name: '嘉兴 | 岛屿设计', contact: '陈女士', phone: '136****5532', level: 'normal', status: 'inactive', projects: 1, totalAmount: 8000, joinDate: '2024-02-20' }
+  ]);
+
+  // 筛选
+  keyword = signal('');
+  status = signal<'all' | 'active' | 'inactive'>('all');
+  level = signal<'all' | 'normal' | 'vip' | 'svip'>('all');
+
+  // 面板状态
+  showPanel = false;
+  panelMode: 'add' | 'detail' | 'edit' = 'add';
+  currentCustomer: Customer | null = null;
+  formModel: Partial<Customer> = {};
+
+  get filtered() {
+    const kw = this.keyword().trim().toLowerCase();
+    const st = this.status();
+    const lv = this.level();
+    return this.customers().filter(c => {
+      const m1 = !kw || c.name.toLowerCase().includes(kw) || c.contact.toLowerCase().includes(kw) || c.phone.includes(kw);
+      const m2 = st === 'all' || c.status === st;
+      const m3 = lv === 'all' || c.level === lv;
+      return m1 && m2 && m3;
+    });
+  }
+
+  resetFilters() {
+    this.keyword.set('');
+    this.status.set('all');
+    this.level.set('all');
+  }
+
+  formatAmount(n: number) {
+    return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY', maximumFractionDigits: 0 }).format(n);
+  }
+
+  // 新建客户 -> 打开侧边面板填写
+  addCustomer() {
+    this.formModel = {
+      name: '',
+      contact: '',
+      phone: '',
+      level: 'normal',
+      status: 'active',
+      projects: 0,
+      totalAmount: 0,
+      joinDate: new Date().toISOString().slice(0, 10)
+    } as Partial<Customer>;
+    this.currentCustomer = null;
+    this.panelMode = 'add';
+    this.showPanel = true;
+  }
+
+  // 导出当前筛选客户为 CSV
+  exportCustomers() {
+    const header = ['客户名称','联系人','电话','等级','状态','项目数','累计成交','加入日期'];
+    const rows = this.filtered.map(c => [
+      c.name,
+      c.contact,
+      c.phone,
+      c.level.toUpperCase(),
+      c.status === 'active' ? '活跃' : '沉默',
+      String(c.projects),
+      String(c.totalAmount),
+      c.joinDate
+    ]);
+    this.downloadCSV('客户列表.csv', [header, ...rows]);
+  }
+
+  private downloadCSV(filename: string, rows: (string | number)[][]) {
+    const escape = (val: string | number) => {
+      const s = String(val ?? '');
+      if (/[",\n]/.test(s)) return '"' + s.replace(/"/g, '""') + '"';
+      return s;
+    };
+    const csv = rows.map(r => r.map(escape).join(',')).join('\n');
+    const blob = new Blob(['\ufeff', csv], { type: 'text/csv;charset=utf-8;' });
+    const url = URL.createObjectURL(blob);
+    const a = document.createElement('a');
+    a.href = url;
+    a.download = filename;
+    a.click();
+    URL.revokeObjectURL(url);
+  }
+
+  // 查看详情
+  viewCustomer(c: Customer) {
+    this.currentCustomer = c;
+    this.panelMode = 'detail';
+    this.showPanel = true;
+  }
+
+  // 编辑
+  editCustomer(c: Customer) {
+    this.currentCustomer = c;
+    this.formModel = { ...c };
+    this.panelMode = 'edit';
+    this.showPanel = true;
+  }
+
+  // 关闭
+  closePanel() {
+    this.showPanel = false;
+    this.panelMode = 'add';
+    this.currentCustomer = null;
+    this.formModel = {};
+  }
+
+  // 保存
+  saveCustomer() {
+    const name = (this.formModel.name || '').trim();
+    if (!name) { alert('请输入客户名称'); return; }
+    const item: Customer = {
+      id: Date.now().toString(),
+      name,
+      contact: this.formModel.contact || '',
+      phone: this.formModel.phone || '',
+      level: (this.formModel.level as Customer['level']) || 'normal',
+      status: (this.formModel.status as Customer['status']) || 'active',
+      projects: Number(this.formModel.projects || 0),
+      totalAmount: Number(this.formModel.totalAmount || 0),
+      joinDate: this.formModel.joinDate || new Date().toISOString().slice(0,10)
+    };
+    this.customers.set([item, ...this.customers()]);
+    this.total.set(this.total() + 1);
+    if (item.status === 'active') this.active.set(this.active() + 1);
+    if (item.level === 'vip') this.vip.set(this.vip() + 1);
+    this.amount.set(this.amount() + item.totalAmount);
+    this.closePanel();
+  }
+
+  // 更新
+  updateCustomer() {
+    if (!this.currentCustomer) return;
+    const updated: Customer = {
+      ...this.currentCustomer,
+      name: this.formModel.name ?? this.currentCustomer.name,
+      contact: this.formModel.contact ?? this.currentCustomer.contact,
+      phone: this.formModel.phone ?? this.currentCustomer.phone,
+      level: (this.formModel.level as Customer['level']) ?? this.currentCustomer.level,
+      status: (this.formModel.status as Customer['status']) ?? this.currentCustomer.status,
+      projects: Number(this.formModel.projects ?? this.currentCustomer.projects),
+      totalAmount: Number(this.formModel.totalAmount ?? this.currentCustomer.totalAmount),
+      joinDate: this.formModel.joinDate ?? this.currentCustomer.joinDate
+    };
+    // 简化:不做复杂统计差异,直接替换
+    this.customers.set(this.customers().map(c => c.id === updated.id ? updated : c));
+    this.closePanel();
+  }
+}

+ 73 - 10
src/app/pages/admin/dashboard/dashboard.html

@@ -8,7 +8,7 @@
   <!-- 统计卡片区域 -->
   <div class="stats-grid">
     <!-- 总项目数 -->
-    <div class="stat-card">
+    <div class="stat-card clickable" (click)="showPanel('totalProjects')">
       <div class="stat-icon primary">
         <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <line x1="8" y1="6" x2="21" y2="6"></line>
@@ -29,7 +29,7 @@
     </div>
 
     <!-- 进行中项目 -->
-    <div class="stat-card">
+    <div class="stat-card clickable" (click)="showPanel('active')">
       <div class="stat-icon secondary">
         <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
@@ -45,7 +45,7 @@
     </div>
 
     <!-- 已完成项目 -->
-    <div class="stat-card">
+    <div class="stat-card clickable" (click)="showPanel('completed')">
       <div class="stat-icon success">
         <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <polyline points="20 6 9 17 4 12"></polyline>
@@ -61,7 +61,7 @@
     </div>
 
     <!-- 设计师总数 -->
-    <div class="stat-card">
+    <div class="stat-card clickable" (click)="showPanel('designers')">
       <div class="stat-icon primary">
         <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
@@ -80,7 +80,7 @@
     </div>
 
     <!-- 客户总数 -->
-    <div class="stat-card">
+    <div class="stat-card clickable" (click)="showPanel('customers')">
       <div class="stat-icon secondary">
         <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
@@ -97,7 +97,7 @@
     </div>
 
     <!-- 总收入 -->
-    <div class="stat-card">
+    <div class="stat-card clickable" (click)="showPanel('revenue')">
       <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>
@@ -121,8 +121,8 @@
       <div class="chart-header">
         <h3>项目数量趋势</h3>
         <div class="chart-period">
-          <button class="period-btn active">近6个月</button>
-          <button class="period-btn">近12个月</button>
+          <button class="period-btn" [class.active]="projectPeriod() === '6m'" (click)="setProjectPeriod('6m')">近6个月</button>
+          <button class="period-btn" [class.active]="projectPeriod() === '12m'" (click)="setProjectPeriod('12m')">近12个月</button>
         </div>
       </div>
       <div id="projectTrendChart" class="chart-container"></div>
@@ -133,8 +133,8 @@
       <div class="chart-header">
         <h3>季度收入统计</h3>
         <div class="chart-period">
-          <button class="period-btn active">本季度</button>
-          <button class="period-btn">全年</button>
+          <button class="period-btn" [class.active]="revenuePeriod() === 'quarter'" (click)="setRevenuePeriod('quarter')">本季度</button>
+          <button class="period-btn" [class.active]="revenuePeriod() === 'year'" (click)="setRevenuePeriod('year')">全年</button>
         </div>
       </div>
       <div id="revenueChart" class="chart-container"></div>
@@ -208,4 +208,67 @@
       </div>
     </div>
   </div>
+
+  <!-- 详情抽屉 -->
+  <div class="drawer" *ngIf="detailOpen()" (click)="closeDetailPanel()">
+    <div class="drawer-panel" (click)="$event.stopPropagation()">
+      <div class="drawer-header">
+        <h3 class="drawer-title">{{ detailTitle() }}</h3>
+        <button class="drawer-close" (click)="closeDetailPanel()">✕</button>
+      </div>
+      <div class="drawer-body">
+        <div id="detailChart" class="chart-container"></div>
+    
+        <div class="drawer-toolbar">
+          <div class="filters">
+            <input type="text" placeholder="关键字搜索"
+                   [value]="keyword()"
+                   (input)="setKeyword($any($event.target).value)" />
+    
+            <select [value]="statusFilter()" (change)="setStatus($any($event.target).value)">
+              <option *ngFor="let opt of getStatusOptions()" [value]="opt.value">{{ opt.label }}</option>
+            </select>
+    
+            <input type="date" [value]="dateFrom() || ''" (change)="setDateFrom($any($event.target).value)" />
+            <span class="date-sep">-</span>
+            <input type="date" [value]="dateTo() || ''" (change)="setDateTo($any($event.target).value)" />
+    
+            <button class="btn reset" (click)="resetFilters()">重置</button>
+          </div>
+          <div class="actions">
+            <button class="btn export" (click)="exportCSV()">导出 CSV</button>
+          </div>
+        </div>
+    
+        <div class="detail-table-wrapper">
+          <table class="detail-table">
+            <thead>
+              <tr>
+                <th *ngFor="let col of getColumns()">{{ col.label }}</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr *ngFor="let row of pagedData(); let i = index">
+                <td *ngFor="let col of getColumns()">{{ col.formatter ? col.formatter(row[col.field]) : row[col.field] }}</td>
+              </tr>
+              <tr *ngIf="pagedData().length === 0">
+                <td class="empty" [attr.colspan]="getColumns().length">暂无数据</td>
+              </tr>
+            </tbody>
+          </table>
+    
+          <div class="pagination">
+            <div class="info">共 {{ totalItems() }} 条 • 第 {{ pageIndex() }} / {{ totalPages }} 页</div>
+            <div class="pager">
+              <button (click)="prevPage()" [disabled]="pageIndex() <= 1">上一页</button>
+              <ng-container *ngFor="let n of getPages()">
+                <button class="num" [class.active]="pageIndex() === n" (click)="goToPage(n)">{{ n }}</button>
+              </ng-container>
+              <button (click)="nextPage()" [disabled]="pageIndex() >= totalPages">下一页</button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
 </div>

+ 575 - 129
src/app/pages/admin/dashboard/dashboard.scss

@@ -12,87 +12,273 @@ $border-color: #E5E6EB;
 $background-primary: #FFFFFF;
 $background-secondary: #F2F3F5;
 $background-tertiary: #F7F8FA;
-$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
-$shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
-$shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.1);
-$border-radius: 8px;
-$transition: all 0.3s ease;
+$shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.06);
+$shadow-md: 0 8px 24px rgba(0, 0, 0, 0.12);
+$shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.16);
+$border-radius: 12px;
+$transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
 
 // 主容器
 .admin-dashboard {
-  padding: 20px 0;
+  padding: 32px 0;
+  background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
+  min-height: 100vh;
+}
+
+// 编写按钮样式
+.write-button {
+  position: fixed;
+  bottom: 32px;
+  right: 32px;
+  width: 64px;
+  height: 64px;
+  background: linear-gradient(135deg, $primary-color, #7c3aed);
+  border: none;
+  border-radius: 50%;
+  box-shadow: 0 8px 24px rgba(22, 93, 255, 0.3);
+  cursor: pointer;
+  transition: $transition;
+  z-index: 1000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  
+  &:hover {
+    transform: translateY(-4px) scale(1.05);
+    box-shadow: 0 12px 32px rgba(22, 93, 255, 0.4);
+  }
+  
+  &:active {
+    transform: translateY(-2px) scale(1.02);
+  }
+  
+  svg {
+    width: 28px;
+    height: 28px;
+    fill: white;
+  }
+}
+
+// 模态框样式
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  backdrop-filter: blur(8px);
+  z-index: 2000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+  animation: fadeIn 0.3s ease;
+}
+
+.modal-content {
+  background: $background-primary;
+  border-radius: 16px;
+  width: 100%;
+  max-width: 600px;
+  max-height: 90vh;
+  overflow-y: auto;
+  box-shadow: 0 24px 48px rgba(0, 0, 0, 0.2);
+  animation: slideUp 0.3s ease;
+  
+  .modal-header {
+    padding: 32px 32px 0;
+    border-bottom: 1px solid $border-color;
+    
+    .modal-title {
+      font-size: 24px;
+      font-weight: 700;
+      color: $text-primary;
+      margin: 0 0 16px 0;
+      background: linear-gradient(135deg, $primary-color, #7c3aed);
+      -webkit-background-clip: text;
+      -webkit-text-fill-color: transparent;
+      background-clip: text;
+    }
+    
+    .modal-subtitle {
+      font-size: 16px;
+      color: $text-secondary;
+      margin: 0 0 24px 0;
+    }
+    
+    .close-button {
+      position: absolute;
+      top: 24px;
+      right: 24px;
+      width: 40px;
+      height: 40px;
+      border: none;
+      background: rgba(0, 0, 0, 0.05);
+      border-radius: 50%;
+      cursor: pointer;
+      transition: $transition;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      
+      &:hover {
+        background: rgba(0, 0, 0, 0.1);
+        transform: scale(1.1);
+      }
+      
+      svg {
+        width: 20px;
+        height: 20px;
+        fill: $text-secondary;
+      }
+    }
+  }
+  
+  .modal-body {
+    padding: 32px;
+  }
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+@keyframes slideUp {
+  from {
+    opacity: 0;
+    transform: translateY(40px) scale(0.95);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0) scale(1);
+  }
 }
 
 // 页面标题
 .page-header {
-  margin-bottom: 24px;
+  margin-bottom: 32px;
+  text-align: center;
 
   .page-title {
-    font-size: 28px;
-    font-weight: 600;
+    font-size: 32px;
+    font-weight: 700;
     color: $text-primary;
-    margin: 0 0 8px 0;
+    margin: 0 0 12px 0;
+    background: linear-gradient(135deg, $primary-color, #7c3aed);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
   }
 
   .page-description {
-    font-size: 16px;
+    font-size: 18px;
     color: $text-secondary;
     margin: 0;
+    font-weight: 400;
   }
 }
 
 // 统计卡片网格
 .stats-grid {
   display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
-  gap: 20px;
-  margin-bottom: 32px;
+  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+  gap: 24px;
+  margin-bottom: 40px;
 }
 
 // 统计卡片
 .stat-card {
-  background-color: $background-primary;
+  background: $background-primary;
   border-radius: $border-radius;
-  padding: 20px;
+  padding: 28px;
   box-shadow: $shadow-sm;
   display: flex;
   align-items: center;
-  gap: 16px;
+  gap: 20px;
   transition: $transition;
-  border: 1px solid $border-color;
+  border: 1px solid rgba(255, 255, 255, 0.8);
+  position: relative;
+  overflow: hidden;
+  
+  &.clickable {
+    cursor: pointer;
+    
+    &:hover {
+      box-shadow: $shadow-lg;
+      transform: translateY(-6px);
+      border-color: rgba($primary-color, 0.3);
+    }
+    
+    &:active {
+      transform: translateY(-2px);
+    }
+  }
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 4px;
+    background: linear-gradient(90deg, $primary-color, $success-color);
+    transform: scaleX(0);
+    transition: $transition;
+  }
 
   &:hover {
     box-shadow: $shadow-md;
-    transform: translateY(-2px);
+    transform: translateY(-4px);
+    
+    &::before {
+      transform: scaleX(1);
+    }
   }
 
   .stat-icon {
-    width: 48px;
-    height: 48px;
-    border-radius: 50%;
+    width: 56px;
+    height: 56px;
+    border-radius: 16px;
     display: flex;
     align-items: center;
     justify-content: center;
     color: white;
+    position: relative;
+
+    &::before {
+      content: '';
+      position: absolute;
+      inset: 0;
+      border-radius: inherit;
+      background: inherit;
+      opacity: 0.1;
+      transform: scale(1.3);
+    }
 
     &.primary {
-      background-color: $primary-color;
+      background: linear-gradient(135deg, $primary-color, #4c9aff);
     }
 
     &.secondary {
-      background-color: $secondary-color;
+      background: linear-gradient(135deg, $secondary-color, #6366f1);
     }
 
     &.success {
-      background-color: $success-color;
+      background: linear-gradient(135deg, $success-color, #52c41a);
     }
 
     &.warning {
-      background-color: $warning-color;
+      background: linear-gradient(135deg, $warning-color, #ffa940);
     }
 
     &.danger {
-      background-color: $danger-color;
+      background: linear-gradient(135deg, $danger-color, #ff7875);
     }
   }
 
@@ -100,37 +286,43 @@ $transition: all 0.3s ease;
     flex: 1;
 
     .stat-value {
-      font-size: 28px;
-      font-weight: 600;
+      font-size: 32px;
+      font-weight: 800;
       color: $text-primary;
-      margin-bottom: 4px;
+      margin-bottom: 6px;
+      line-height: 1;
+      letter-spacing: -0.02em;
     }
 
     .stat-label {
-      font-size: 14px;
+      font-size: 15px;
       color: $text-secondary;
+      font-weight: 500;
     }
   }
 
   .stat-trend {
-    font-size: 12px;
-    font-weight: 500;
-    padding: 4px 8px;
-    border-radius: 4px;
+    font-size: 13px;
+    font-weight: 600;
+    padding: 6px 10px;
+    border-radius: 8px;
     white-space: nowrap;
+    display: flex;
+    align-items: center;
+    gap: 4px;
 
     &.positive {
-      background-color: color-mix(in srgb, $success-color 5%, transparent);
+      background: rgba(0, 180, 42, 0.1);
       color: $success-color;
     }
 
     &.negative {
-      background-color: color-mix(in srgb, $danger-color 5%, transparent);
+      background: rgba(245, 63, 63, 0.1);
       color: $danger-color;
     }
 
     &.neutral {
-      background-color: $background-tertiary;
+      background: $background-tertiary;
       color: $text-secondary;
     }
   }
@@ -139,150 +331,329 @@ $transition: all 0.3s ease;
 // 图表网格
 .charts-grid {
   display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
-  gap: 20px;
-  margin-bottom: 32px;
+  grid-template-columns: 2fr 1fr;
+  gap: 32px;
+  margin-bottom: 40px;
 }
 
 // 图表卡片
 .chart-card {
-  background-color: $background-primary;
+  background: $background-primary;
   border-radius: $border-radius;
   box-shadow: $shadow-sm;
   overflow: hidden;
-  border: 1px solid $border-color;
+  border: 1px solid rgba(255, 255, 255, 0.8);
+  transition: $transition;
+
+  &:hover {
+    box-shadow: $shadow-md;
+    transform: translateY(-2px);
+  }
 
   .chart-header {
     display: flex;
     justify-content: space-between;
     align-items: center;
-    padding: 20px;
+    padding: 24px;
     border-bottom: 1px solid $border-color;
+    background: linear-gradient(135deg, rgba(22, 93, 255, 0.02), rgba(124, 58, 237, 0.02));
 
     h3 {
-      font-size: 16px;
-      font-weight: 600;
+      font-size: 18px;
+      font-weight: 700;
       color: $text-primary;
       margin: 0;
     }
 
     .chart-period {
-      display: flex;
+      display: inline-flex;
       gap: 8px;
-
       .period-btn {
         padding: 6px 12px;
-        border: 1px solid $border-color;
-        background-color: $background-primary;
-        border-radius: 4px;
+        border-radius: 8px;
+        border: 1px solid rgba(0,0,0,0.06);
+        background: #fff;
+        color: #334155;
         font-size: 12px;
-        color: $text-secondary;
-        cursor: pointer;
-        transition: $transition;
-
-        &:hover {
-          background-color: $background-tertiary;
+        &.active {
+          background: #2563eb;
+          color: #fff;
+          border-color: #2563eb;
+          box-shadow: 0 6px 14px rgba(37, 99, 235, 0.25);
         }
+      }
+    }
+  }
+  .chart-container {
+    width: 100%;
+    height: 320px;
+  }
+}
 
-        &.active {
-          background-color: $primary-color;
-          color: white;
-          border-color: $primary-color;
+/* 抽屉样式 */
+.drawer {
+  position: fixed;
+  inset: 0;
+  background: rgba(15, 23, 42, 0.45);
+  backdrop-filter: blur(2px);
+  display: flex;
+  justify-content: flex-end;
+  z-index: 1000;
+  .drawer-panel {
+    width: min(720px, 92vw);
+    height: 100%;
+    background: #fff;
+    border-top-left-radius: 16px;
+    border-bottom-left-radius: 16px;
+    box-shadow: -12px 0 30px rgba(0,0,0,0.12);
+    display: flex;
+    flex-direction: column;
+    animation: slideIn 0.2s ease;
+  }
+  .drawer-header {
+    padding: 16px 20px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    border-bottom: 1px solid rgba(0,0,0,0.06);
+    .drawer-title { font-size: 16px; font-weight: 600; color: #0f172a; }
+    .drawer-close {
+      border: none; background: transparent; font-size: 18px; cursor: pointer; color: #64748b;
+      &:hover { color: #0f172a; }
+    }
+  }
+  .drawer-body {
+    padding: 16px 20px 24px;
+    flex: 1;
+    overflow: auto;
+    #detailChart { width: 100%; height: 360px; }
+    .drawer-toolbar {
+      margin-top: 16px;
+      padding: 12px 14px;
+      background: #f8fafc;
+      border-radius: 10px;
+      border: 1px solid rgba(0,0,0,0.06);
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      gap: 12px;
+      flex-wrap: wrap;
+  
+      .filters {
+        display: flex;
+        gap: 10px;
+        align-items: center;
+        flex-wrap: wrap;
+  
+        input[type="text"], input[type="date"], select {
+          height: 34px;
+          padding: 6px 10px;
+          border-radius: 8px;
+          border: 1px solid $border-color;
+          outline: none;
+          background: #fff;
+          color: $text-primary;
+          font-size: 13px;
+          &:focus { box-shadow: 0 0 0 3px rgba(22, 93, 255, 0.15); border-color: $primary-color; }
+        }
+        .date-sep { color: $text-secondary; }
+        .btn.reset {
+          height: 34px; padding: 6px 10px; border-radius: 8px; border: 1px solid $border-color; background: #fff; cursor: pointer; color: #0f172a; font-size: 13px;
+          &:hover { background: #f1f5f9; }
+        }
+      }
+  
+      .actions {
+        .btn.export {
+          height: 34px; padding: 6px 12px; border-radius: 8px; border: 1px solid #2563eb; background: #2563eb; color: #fff; font-weight: 600; cursor: pointer;
+          box-shadow: 0 6px 14px rgba(37,99,235,0.25);
+          &:hover { filter: brightness(1.05); }
         }
       }
     }
+  
+    .detail-table-wrapper {
+      margin-top: 12px;
+      background: #fff;
+      border: 1px solid rgba(0,0,0,0.06);
+      border-radius: 12px;
+      overflow: hidden;
+  
+      .detail-table {
+        width: 100%;
+        border-collapse: collapse;
+        thead th {
+          background: #f1f5f9;
+          color: #0f172a;
+          text-align: left;
+          font-size: 13px;
+          font-weight: 700;
+          padding: 10px 12px;
+          border-bottom: 1px solid rgba(0,0,0,0.06);
+          white-space: nowrap;
+        }
+        tbody td {
+          padding: 10px 12px;
+          font-size: 13px;
+          color: #334155;
+          border-bottom: 1px solid rgba(0,0,0,0.04);
+        }
+        tbody tr:hover td { background: rgba(37,99,235,0.03); }
+        .empty { text-align: center; color: $text-secondary; padding: 24px 0; }
+      }
+  
+      .pagination {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: 10px 12px;
+        background: #fafafa;
+        .info { font-size: 12px; color: #64748b; }
+        .pager { display: inline-flex; gap: 6px; align-items: center; }
+        .pager button { min-width: 30px; height: 30px; border-radius: 8px; border: 1px solid $border-color; background: #fff; font-size: 12px; cursor: pointer; }
+        .pager button.num.active { background: #2563eb; color: #fff; border-color: #2563eb; box-shadow: 0 6px 14px rgba(37,99,235,0.25); }
+        .pager button:disabled { opacity: 0.5; cursor: not-allowed; }
+      }
+    }
   }
+}
 
-  .chart-container {
-    height: 300px;
-    padding: 20px;
+@media (max-width: 768px) {
+  .drawer .drawer-body {
+    .drawer-toolbar { flex-direction: column; align-items: stretch; }
+    .pagination { flex-direction: column; gap: 8px; align-items: flex-start; }
   }
 }
 
+@keyframes slideIn {
+  from { transform: translateX(12px); opacity: 0; }
+  to { transform: translateX(0); opacity: 1; }
+}
+
 // 最近活动区域
 .recent-activities {
-  background-color: $background-primary;
+  background: $background-primary;
   border-radius: $border-radius;
+  padding: 32px;
   box-shadow: $shadow-sm;
-  border: 1px solid $border-color;
-
-  .section-header {
+  border: 1px solid rgba(255, 255, 255, 0.8);
+  transition: $transition;
+  
+  &:hover {
+    box-shadow: $shadow-md;
+  }
+  
+  .activities-header {
     display: flex;
-    justify-content: space-between;
     align-items: center;
-    padding: 20px;
-    border-bottom: 1px solid $border-color;
-
+    justify-content: space-between;
+    margin-bottom: 24px;
+    padding-bottom: 16px;
+    border-bottom: 2px solid $border-color;
+    
     h3 {
-      font-size: 16px;
-      font-weight: 600;
+      font-size: 20px;
+      font-weight: 700;
       color: $text-primary;
       margin: 0;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      
+      &::before {
+        content: '';
+        width: 4px;
+        height: 20px;
+        background: linear-gradient(135deg, $primary-color, $success-color);
+        border-radius: 2px;
+      }
     }
-
-    .view-all-link {
+    
+    .view-all {
       font-size: 14px;
       color: $primary-color;
       text-decoration: none;
+      font-weight: 600;
+      padding: 8px 16px;
+      border-radius: 8px;
+      background: rgba(22, 93, 255, 0.1);
       transition: $transition;
-
+      
       &:hover {
-        text-decoration: underline;
+        background: rgba(22, 93, 255, 0.2);
+        transform: translateY(-1px);
       }
     }
   }
-
-  .activities-list {
-    padding: 0 20px 20px 20px;
-
+  
+  .activity-list {
     .activity-item {
       display: flex;
-      align-items: flex-start;
-      gap: 12px;
+      align-items: center;
       padding: 16px 0;
-      border-bottom: 1px solid $border-color;
-
+      border-bottom: 1px solid rgba(229, 230, 235, 0.5);
+      transition: $transition;
+      
       &:last-child {
         border-bottom: none;
       }
-
+      
+      &:hover {
+        background: rgba(22, 93, 255, 0.02);
+        border-radius: 8px;
+        margin: 0 -16px;
+        padding: 16px;
+      }
+      
       .activity-icon {
-        width: 32px;
-        height: 32px;
-        border-radius: 50%;
-        background-color: $background-tertiary;
+        width: 40px;
+        height: 40px;
+        border-radius: 12px;
+        background: linear-gradient(135deg, rgba(22, 93, 255, 0.1), rgba(124, 58, 237, 0.1));
         display: flex;
         align-items: center;
         justify-content: center;
-        color: $primary-color;
-        flex-shrink: 0;
+        margin-right: 16px;
+        
+        svg {
+          width: 20px;
+          height: 20px;
+          fill: $primary-color;
+        }
       }
-
+      
       .activity-content {
         flex: 1;
-
+        
         .activity-text {
-          font-size: 14px;
+          font-size: 15px;
           color: $text-primary;
-          line-height: 1.5;
-          margin-bottom: 4px;
+          margin-bottom: 6px;
+          line-height: 1.4;
+          
+          .user {
+            font-weight: 700;
+            color: $primary-color;
+          }
+          
+          .action {
+            color: $text-secondary;
+            font-weight: 500;
+          }
+          
+          .target {
+            color: $text-primary;
+            font-weight: 600;
+            background: rgba(22, 93, 255, 0.1);
+            padding: 2px 6px;
+            border-radius: 4px;
+          }
         }
-
+        
         .activity-time {
-          font-size: 12px;
-          color: $text-tertiary;
-        }
-
-        .activity-user {
-          font-weight: 600;
-          color: $text-primary;
-        }
-
-        .activity-project,
-        .activity-task,
-        .activity-customer {
-          font-weight: 600;
-          color: $primary-color;
+          font-size: 13px;
+          color: $text-secondary;
+          font-weight: 500;
         }
       }
     }
@@ -290,63 +661,138 @@ $transition: all 0.3s ease;
 }
 
 // 响应式设计
-@media (max-width: 1200px) {
-  .stats-grid {
-    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+@media (max-width: 1400px) {
+  .admin-dashboard {
+    .charts-grid {
+      grid-template-columns: 1fr;
+      gap: 24px;
+    }
+    
+    .stats-grid {
+      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+    }
   }
+}
 
-  .charts-grid {
-    grid-template-columns: 1fr;
+@media (max-width: 1024px) {
+  .admin-dashboard {
+    padding: 24px 0;
+    
+    .stats-grid {
+      grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+      gap: 20px;
+    }
+    
+    .stat-card {
+      padding: 24px;
+    }
   }
 }
 
 @media (max-width: 768px) {
+  .admin-dashboard {
+    padding: 20px 0;
+  }
+
   .page-title {
-    font-size: 24px !important;
+    font-size: 28px !important;
+  }
+
+  .page-description {
+    font-size: 15px !important;
   }
 
   .stats-grid {
     grid-template-columns: 1fr;
+    gap: 16px;
   }
 
+  .stat-card {
+    padding: 20px;
+    .stat-card.clickable {
+      cursor: pointer;
+      transition: transform 0.15s ease, box-shadow 0.15s ease;
+      &:hover {
+        transform: translateY(-2px);
+        box-shadow: 0 10px 20px rgba(0,0,0,0.08);
+      }
+    }
+    .stat-icon {
+      width: 44px;
+      height: 44px;
+    }
+    .stat-value {
+      font-size: 28px !important;
+    }
+  }
+  // removed duplicated .stat-icon/.stat-value block here
   .chart-container {
-    height: 250px !important;
+    height: 280px !important;
   }
-
   .chart-header {
     flex-direction: column;
     gap: 12px;
     align-items: flex-start !important;
   }
-
   .recent-activities {
     margin-top: 20px;
+    padding: 24px;
   }
 }
 
 @media (max-width: 480px) {
   .admin-dashboard {
-    padding: 12px 0;
+    padding: 16px 0;
   }
 
   .page-title {
-    font-size: 20px !important;
+    font-size: 24px !important;
   }
 
   .page-description {
     font-size: 14px !important;
   }
 
-  .chart-container {
-    height: 200px !important;
-    padding: 12px !important;
+  .stats-grid {
+    gap: 12px;
   }
 
   .stat-card {
     padding: 16px;
+    
+    .stat-icon {
+      width: 40px;
+      height: 40px;
+    }
+    
+    .stat-value {
+      font-size: 24px !important;
+    }
+    
+    .stat-label {
+      font-size: 14px;
+    }
   }
 
-  .stat-value {
-    font-size: 24px !important;
+  .chart-container {
+    height: 240px !important;
+    padding: 12px !important;
+  }
+
+  .recent-activities {
+    padding: 20px;
+    
+    .activity-item {
+      .activity-icon {
+        width: 36px;
+        height: 36px;
+      }
+      
+      .activity-content {
+        .activity-text {
+          font-size: 14px;
+        }
+      }
+    }
   }
 }

+ 480 - 76
src/app/pages/admin/dashboard/dashboard.ts

@@ -1,13 +1,10 @@
 import { CommonModule } from '@angular/common';
 import { RouterModule } from '@angular/router';
 import { Subscription } from 'rxjs';
-import { signal } from '@angular/core';
+import { signal, Component, OnInit, AfterViewInit, OnDestroy, computed } from '@angular/core';
 import { AdminDashboardService } from './dashboard.service';
 import * as echarts from 'echarts';
 
-// 确保@Component装饰器存在
-import { Component, OnInit, AfterViewInit, OnDestroy } from '@angular/core';
-
 @Component({
   selector: 'app-admin-dashboard',
   standalone: true,
@@ -26,9 +23,104 @@ export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
     totalRevenue: signal(1258000)
   };
 
+  // 图表周期切换
+  projectPeriod = signal<'6m' | '12m'>('6m');
+  revenuePeriod = signal<'quarter' | 'year'>('quarter');
+
+  // 详情面板
+  detailOpen = signal(false);
+  detailType = signal<'totalProjects' | 'active' | 'completed' | 'designers' | 'customers' | 'revenue' | null>(null);
+  detailTitle = computed(() => {
+    switch (this.detailType()) {
+      case 'totalProjects': return '项目总览';
+      case 'active': return '进行中项目详情';
+      case 'completed': return '已完成项目详情';
+      case 'designers': return '设计师统计详情';
+      case 'customers': return '客户统计详情';
+      case 'revenue': return '收入统计详情';
+      default: return '';
+    }
+  });
+
+  // 明细数据与筛选/分页状态
+  detailData = signal<any[]>([]);
+  keyword = signal('');
+  statusFilter = signal('all');
+  dateFrom = signal<string | null>(null);
+  dateTo = signal<string | null>(null);
+  pageIndex = signal(1);
+  pageSize = signal(10);
+
+  // 过滤后的数据
+  filteredData = computed(() => {
+    const type = this.detailType();
+    let data = this.detailData();
+    const kw = this.keyword().trim().toLowerCase();
+    const status = this.statusFilter();
+    const from = this.dateFrom() ? new Date(this.dateFrom() as string).getTime() : null;
+    const to = this.dateTo() ? new Date(this.dateTo() as string).getTime() : null;
+
+    // 关键词过滤(对常见字段做并集匹配)
+    if (kw) {
+      data = data.filter((it: any) => {
+        const text = [it.name, it.projectName, it.customer, it.owner, it.status, it.level, it.invoiceNo]
+          .filter(Boolean)
+          .join(' ')
+          .toLowerCase();
+        return text.includes(kw);
+      });
+    }
+
+    // 状态过滤(不同类型对应不同字段)
+    if (status && status !== 'all') {
+      data = data.filter((it: any) => {
+        switch (type) {
+          case 'active':
+          case 'completed':
+          case 'totalProjects':
+            return (it.status || '').toLowerCase() === status.toLowerCase();
+          case 'designers':
+            return (it.level || '').toLowerCase() === status.toLowerCase();
+          case 'customers':
+            return (it.status || '').toLowerCase() === status.toLowerCase();
+          case 'revenue':
+            return (it.type || '').toLowerCase() === status.toLowerCase();
+          default:
+            return true;
+        }
+      });
+    }
+
+    // 时间范围过滤:尝试使用 date/endDate/startDate 三者之一
+    if (from || to) {
+      data = data.filter((it: any) => {
+        const d = it.date || it.endDate || it.startDate;
+        if (!d) return false;
+        const t = new Date(d).getTime();
+        if (from && t < from) return false;
+        if (to && t > to) return false;
+        return true;
+      });
+    }
+
+    return data;
+  });
+
+  // 分页后的数据
+  pagedData = computed(() => {
+    const size = this.pageSize();
+    const idx = this.pageIndex();
+    const start = (idx - 1) * size;
+    return this.filteredData().slice(start, start + size);
+  });
+
+  totalItems = computed(() => this.filteredData().length);
+  totalPagesComputed = computed(() => Math.max(1, Math.ceil(this.totalItems() / this.pageSize())));
+
   private subscriptions: Subscription = new Subscription();
   private projectChart: any | null = null;
   private revenueChart: any | null = null;
+  private detailChart: any | null = null;
 
   constructor(private dashboardService: AdminDashboardService) {}
 
@@ -44,94 +136,406 @@ export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
   ngOnDestroy(): void {
     this.subscriptions.unsubscribe();
     window.removeEventListener('resize', this.handleResize);
-    if (this.projectChart) {
-      this.projectChart.dispose();
-    }
-    if (this.revenueChart) {
-      this.revenueChart.dispose();
-    }
+    this.disposeCharts();
+  }
+
+  private disposeCharts(): void {
+    if (this.projectChart) { this.projectChart.dispose(); this.projectChart = null; }
+    if (this.revenueChart) { this.revenueChart.dispose(); this.revenueChart = null; }
+    if (this.detailChart) { this.detailChart.dispose(); this.detailChart = null; }
   }
 
   loadDashboardData(): void {
-    // 在实际应用中,这里会从服务加载数据
-    // 由于是模拟环境,我们使用模拟数据
+    // 模拟调用服务
     this.subscriptions.add(
-      this.dashboardService.getDashboardStats().subscribe(data => {
-        // 更新统计数据
-        // this.stats.totalProjects.set(data.totalProjects);
-        // 其他数据更新...
+      this.dashboardService.getDashboardStats().subscribe(() => {
+        // 使用默认模拟数据,必要时可在此更新 signals
       })
     );
   }
 
+  // ====== 顶部两张主图表 ======
   initCharts(): void {
-    // 初始化项目趋势图
-    const projectChartDom = document.getElementById('projectTrendChart');
-    if (projectChartDom) {
-      this.projectChart = echarts.init(projectChartDom);
-      if (this.projectChart) {
-        this.projectChart.setOption({
-          title: { text: '项目数量趋势', left: 'center', textStyle: { fontSize: 16 } },
-          tooltip: { trigger: 'axis' },
-          xAxis: { type: 'category', data: ['1月', '2月', '3月', '4月', '5月', '6月'] },
-          yAxis: { type: 'value' },
-          series: [{
-            name: '新项目',
-            type: 'line',
-            data: [18, 25, 32, 28, 42, 38],
-            lineStyle: { color: '#165DFF' },
-            itemStyle: { color: '#165DFF' }
-          }, {
-            name: '完成项目',
-            type: 'line',
-            data: [15, 20, 25, 22, 35, 30],
-            lineStyle: { color: '#00B42A' },
-            itemStyle: { color: '#00B42A' }
-          }]
-        });
-      }
-    }
-
-    // 初始化收入统计图
-    const revenueChartDom = document.getElementById('revenueChart');
-    if (revenueChartDom) {
-      this.revenueChart = echarts.init(revenueChartDom);
-      if (this.revenueChart) {
-        this.revenueChart.setOption({
-          title: { text: '季度收入统计', left: 'center', textStyle: { fontSize: 16 } },
-          tooltip: { trigger: 'item' },
-          series: [{
-            name: '收入分布',
-            type: 'pie',
-            radius: '65%',
-            data: [
-              { value: 350000, name: '第一季度' },
-              { value: 420000, name: '第二季度' },
-              { value: 488000, name: '第三季度' }
-            ],
-            emphasis: {
-              itemStyle: {
-                shadowBlur: 10,
-                shadowOffsetX: 0,
-                shadowColor: 'rgba(0, 0, 0, 0.5)'
-              }
-            }
-          }]
-        });
-      }
+    this.initProjectChart();
+    this.initRevenueChart();
+  }
+
+  private initProjectChart(): void {
+    const el = document.getElementById('projectTrendChart');
+    if (!el) return;
+    this.projectChart?.dispose();
+    this.projectChart = echarts.init(el);
+
+    const { x, newProjects, completed } = this.prepareProjectSeries(this.projectPeriod());
+    this.projectChart.setOption({
+      title: { text: '项目数量趋势', left: 'center', textStyle: { fontSize: 16 } },
+      tooltip: { trigger: 'axis' },
+      legend: { data: ['新项目', '完成项目'] },
+      xAxis: { type: 'category', data: x },
+      yAxis: { type: 'value' },
+      series: [
+        { name: '新项目', type: 'line', data: newProjects, lineStyle: { color: '#165DFF' }, itemStyle: { color: '#165DFF' }, smooth: true },
+        { name: '完成项目', type: 'line', data: completed, lineStyle: { color: '#00B42A' }, itemStyle: { color: '#00B42A' }, smooth: true }
+      ]
+    });
+  }
+
+  private initRevenueChart(): void {
+    const el = document.getElementById('revenueChart');
+    if (!el) return;
+    this.revenueChart?.dispose();
+    this.revenueChart = echarts.init(el);
+
+    if (this.revenuePeriod() === 'quarter') {
+      this.revenueChart.setOption({
+        title: { text: '季度收入统计', left: 'center', textStyle: { fontSize: 16 } },
+        tooltip: { trigger: 'item' },
+        series: [{
+          type: 'pie', radius: '65%',
+          data: [
+            { value: 350000, name: '第一季度' },
+            { value: 420000, name: '第二季度' },
+            { value: 488000, name: '第三季度' }
+          ],
+          emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.5)' } }
+        }]
+      });
+    } else {
+      // 全年:使用柱状图展示 12 个月收入
+      const months = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'];
+      const revenue = [120, 140, 160, 155, 180, 210, 230, 220, 240, 260, 280, 300].map(v => v * 1000);
+      this.revenueChart.setOption({
+        title: { text: '全年收入统计', left: 'center', textStyle: { fontSize: 16 } },
+        tooltip: { trigger: 'axis' },
+        xAxis: { type: 'category', data: months },
+        yAxis: { type: 'value' },
+        series: [{ type: 'bar', data: revenue, itemStyle: { color: '#165DFF' } }]
+      });
     }
   }
 
-  private handleResize = (): void => {
-    if (this.projectChart) {
-      this.projectChart.resize();
+  private prepareProjectSeries(period: '6m' | '12m') {
+    if (period === '6m') {
+      return {
+        x: ['1月','2月','3月','4月','5月','6月'],
+        newProjects: [18, 25, 32, 28, 42, 38],
+        completed:   [15, 20, 25, 22, 35, 30]
+      };
     }
-    if (this.revenueChart) {
-      this.revenueChart.resize();
+    // 12个月数据(构造平滑趋势)
+    return {
+      x: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'],
+      newProjects: [12,18,22,26,30,34,36,38,40,42,44,46],
+      completed:   [10,14,18,20,24,28,30,31,33,35,37,39]
+    };
+  }
+
+  setProjectPeriod(p: '6m' | '12m') { 
+    if (this.projectPeriod() !== p) {
+      this.projectPeriod.set(p);
+      this.initProjectChart();
     }
+  }
+
+  setRevenuePeriod(p: 'quarter' | 'year') {
+    if (this.revenuePeriod() !== p) {
+      this.revenuePeriod.set(p);
+      this.initRevenueChart();
+    }
+  }
+
+  // ====== 详情面板 ======
+  showPanel(type: 'totalProjects' | 'active' | 'completed' | 'designers' | 'customers' | 'revenue') {
+    this.detailType.set(type);
+    // 重置筛选与分页
+    this.keyword.set('');
+    this.statusFilter.set('all');
+    this.dateFrom.set(null);
+    this.dateTo.set(null);
+    this.pageIndex.set(1);
+
+    // 加载本次类型的明细数据
+    this.loadDetailData(type);
+
+    // 打开抽屉并初始化图表
+    this.detailOpen.set(true);
+    setTimeout(() => this.initDetailChart(), 0);
+    document.body.style.overflow = 'hidden';
+  }
+
+  closeDetailPanel() {
+    this.detailOpen.set(false);
+    this.detailType.set(null);
+    this.detailChart?.dispose();
+    this.detailChart = null;
+    document.body.style.overflow = 'auto';
+  }
+
+  private initDetailChart() {
+    const el = document.getElementById('detailChart');
+    if (!el) return;
+    this.detailChart?.dispose();
+    this.detailChart = echarts.init(el);
+    const type = this.detailType();
+
+    if (type === 'totalProjects' || type === 'active' || type === 'completed') {
+      const { x, newProjects, completed } = this.prepareProjectSeries('12m');
+      this.detailChart.setOption({
+        title: { text: '项目趋势详情(12个月)', left: 'center' },
+        tooltip: { trigger: 'axis' },
+        legend: { data: ['新项目','完成项目'] },
+        xAxis: { type: 'category', data: x },
+        yAxis: { type: 'value' },
+        series: [
+          { name: '新项目', type: 'line', data: newProjects, smooth: true, lineStyle: { color: '#165DFF' } },
+          { name: '完成项目', type: 'line', data: completed, smooth: true, lineStyle: { color: '#00B42A' } }
+        ]
+      });
+      return;
+    }
+
+    if (type === 'designers') {
+      this.detailChart.setOption({
+        title: { text: '设计师完成量对比', left: 'center' },
+        tooltip: { trigger: 'axis' },
+        legend: { data: ['完成','进行中'] },
+        xAxis: { type: 'category', data: ['张','李','王','赵','陈'] },
+        yAxis: { type: 'value' },
+        series: [
+          { name: '完成', type: 'bar', data: [18,15,12,10,9], itemStyle: { color: '#00B42A' } },
+          { name: '进行中', type: 'bar', data: [8,6,5,4,3], itemStyle: { color: '#165DFF' } }
+        ]
+      });
+      return;
+    }
+
+    if (type === 'customers') {
+      this.detailChart.setOption({
+        title: { text: '客户增长趋势', left: 'center' },
+        tooltip: { trigger: 'axis' },
+        xAxis: { type: 'category', data: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'] },
+        yAxis: { type: 'value' },
+        series: [{ name: '客户数', type: 'line', data: [280,300,310,320,330,340,345,350,355,360,368,380], itemStyle: { color: '#4E5BA6' }, smooth: true }]
+      });
+      return;
+    }
+
+    // revenue
+    this.detailChart.setOption({
+      title: { text: '收入构成(年度)', left: 'center' },
+      tooltip: { trigger: 'item' },
+      series: [{
+        type: 'pie', radius: ['35%','65%'],
+        data: [
+          { value: 520000, name: '设计服务' },
+          { value: 360000, name: '材料供应' },
+          { value: 180000, name: '售后与增值' },
+          { value: 198000, name: '其他' }
+        ]
+      }]
+    });
+  }
+
+  private handleResize = (): void => {
+    this.projectChart?.resize();
+    this.revenueChart?.resize();
+    this.detailChart?.resize();
   };
 
   formatCurrency(amount: number): string {
     return '¥' + amount.toLocaleString('zh-CN');
   }
-}
+
+  // 兼容旧模板调用(已调整为 showPanel)
+  showProjectDetails(status: 'active' | 'completed'): void {
+    this.showPanel(status);
+  }
+  showCustomersDetails(): void { this.showPanel('customers'); }
+  showFinanceDetails(): void { this.showPanel('revenue'); }
+
+  // ====== 明细数据:加载、列配置、导出与分页 ======
+  private loadDetailData(type: 'totalProjects' | 'active' | 'completed' | 'designers' | 'customers' | 'revenue') {
+    // 构造模拟数据(足量便于分页演示)
+    const now = new Date();
+    const addDays = (base: Date, days: number) => new Date(base.getTime() + days * 86400000);
+
+    if (type === 'totalProjects' || type === 'active' || type === 'completed') {
+      const status = type === 'active' ? '进行中' : (type === 'completed' ? '已完成' : undefined);
+      const items = Array.from({ length: 42 }).map((_, i) => ({
+        id: 'P' + String(1000 + i),
+        name: `项目 ${i + 1}`,
+        owner: ['张三','李四','王五','赵六'][i % 4],
+        status: status || (i % 3 === 0 ? '进行中' : (i % 3 === 1 ? '已完成' : '待启动')),
+        startDate: addDays(now, -60 + i).toISOString().slice(0,10),
+        endDate: addDays(now, -30 + i).toISOString().slice(0,10)
+      }));
+      this.detailData.set(items);
+      return;
+    }
+
+    if (type === 'designers') {
+      const items = Array.from({ length: 36 }).map((_, i) => ({
+        id: 'D' + String(200 + i),
+        name: ['张一','李二','王三','赵四','陈五','刘六'][i % 6],
+        level: ['junior','mid','senior'][i % 3],
+        completed: 10 + (i % 15),
+        inProgress: 1 + (i % 6),
+        avgCycle: 7 + (i % 10),
+        date: addDays(now, -i).toISOString().slice(0,10)
+      }));
+      this.detailData.set(items);
+      return;
+    }
+
+    if (type === 'customers') {
+      const items = Array.from({ length: 28 }).map((_, i) => ({
+        id: 'C' + String(300 + i),
+        name: ['王先生','李女士','赵先生','陈女士'][i % 4],
+        projects: 1 + (i % 5),
+        lastContact: addDays(now, -i * 2).toISOString().slice(0,10),
+        status: ['潜在','跟进中','已签约'][i % 3],
+        date: addDays(now, -i * 2).toISOString().slice(0,10)
+      }));
+      this.detailData.set(items);
+      return;
+    }
+
+    // revenue
+    const items = Array.from({ length: 34 }).map((_, i) => ({
+      invoiceNo: 'INV-' + String(10000 + i),
+      customer: ['华夏地产','远景家装','绿洲装饰','宏图设计'][i % 4],
+      amount: 5000 + (i % 12) * 1500,
+      type: ['service','material','addon'][i % 3],
+      date: addDays(now, -i).toISOString().slice(0,10)
+    }));
+    this.detailData.set(items);
+  }
+
+  getColumns(): { label: string; field: string; formatter?: (v: any) => string }[] {
+    const type = this.detailType();
+    if (type === 'totalProjects' || type === 'active' || type === 'completed') {
+      return [
+        { label: '项目编号', field: 'id' },
+        { label: '项目名称', field: 'name' },
+        { label: '负责人', field: 'owner' },
+        { label: '状态', field: 'status' },
+        { label: '开始日期', field: 'startDate' },
+        { label: '结束日期', field: 'endDate' }
+      ];
+    }
+    if (type === 'designers') {
+      return [
+        { label: '设计师', field: 'name' },
+        { label: '级别', field: 'level' },
+        { label: '完成量', field: 'completed' },
+        { label: '进行中', field: 'inProgress' },
+        { label: '平均周期(天)', field: 'avgCycle' },
+        { label: '统计日期', field: 'date' }
+      ];
+    }
+    if (type === 'customers') {
+      return [
+        { label: '客户名', field: 'name' },
+        { label: '项目数', field: 'projects' },
+        { label: '最后联系', field: 'lastContact' },
+        { label: '状态', field: 'status' }
+      ];
+    }
+    // revenue
+    return [
+      { label: '发票号', field: 'invoiceNo' },
+      { label: '客户', field: 'customer' },
+      { label: '金额', field: 'amount', formatter: (v: any) => this.formatCurrency(Number(v)) },
+      { label: '类型', field: 'type' },
+      { label: '日期', field: 'date' }
+    ];
+  }
+
+  // 状态选项(随类型变化)
+  getStatusOptions(): { label: string; value: string }[] {
+    const type = this.detailType();
+    if (type === 'totalProjects' || type === 'active' || type === 'completed') {
+      return [
+        { label: '全部状态', value: 'all' },
+        { label: '进行中', value: '进行中' },
+        { label: '已完成', value: '已完成' },
+        { label: '待启动', value: '待启动' }
+      ];
+    }
+    if (type === 'designers') {
+      return [
+        { label: '全部级别', value: 'all' },
+        { label: 'junior', value: 'junior' },
+        { label: 'mid', value: 'mid' },
+        { label: 'senior', value: 'senior' }
+      ];
+    }
+    if (type === 'customers') {
+      return [
+        { label: '全部状态', value: 'all' },
+        { label: '潜在', value: '潜在' },
+        { label: '跟进中', value: '跟进中' },
+        { label: '已签约', value: '已签约' }
+      ];
+    }
+    return [
+      { label: '全部类型', value: 'all' },
+      { label: 'service', value: 'service' },
+      { label: 'material', value: 'material' },
+      { label: 'addon', value: 'addon' }
+    ];
+  }
+
+  // 交互:筛选与分页
+  setKeyword(v: string) { this.keyword.set(v); this.pageIndex.set(1); }
+  setStatus(v: string) { this.statusFilter.set(v); this.pageIndex.set(1); }
+  setDateFrom(v: string) { this.dateFrom.set(v || null); this.pageIndex.set(1); }
+  setDateTo(v: string) { this.dateTo.set(v || null); this.pageIndex.set(1); }
+  resetFilters() {
+    this.keyword.set('');
+    this.statusFilter.set('all');
+    this.dateFrom.set(null);
+    this.dateTo.set(null);
+    this.pageIndex.set(1);
+  }
+
+  get totalPages() { return this.totalPagesComputed(); }
+  goToPage(n: number) { const tp = this.totalPagesComputed(); if (n >= 1 && n <= tp) this.pageIndex.set(n); }
+  prevPage() { this.goToPage(this.pageIndex() - 1); }
+  nextPage() { this.goToPage(this.pageIndex() + 1); }
+
+  // 生成页码列表(最多展示 5 个,居中当前页)
+  getPages(): number[] {
+    const total = this.totalPagesComputed();
+    const current = this.pageIndex();
+    const max = 5;
+    let start = Math.max(1, current - Math.floor(max / 2));
+    let end = Math.min(total, start + max - 1);
+    start = Math.max(1, end - max + 1);
+    const pages: number[] = [];
+    for (let i = start; i <= end; i++) pages.push(i);
+    return pages;
+  }
+
+  // 导出当前过滤结果为 CSV
+  exportCSV() {
+    const cols = this.getColumns();
+    const rows = this.filteredData();
+    const header = cols.map(c => c.label).join(',');
+    const escape = (val: any) => {
+      if (val === undefined || val === null) return '';
+      const s = String(val).replace(/"/g, '""');
+      return /[",\n]/.test(s) ? `"${s}"` : s;
+    };
+    const lines = rows.map(r => cols.map(c => escape(c.formatter ? c.formatter((r as any)[c.field]) : (r as any)[c.field])).join(','));
+    const csv = [header, ...lines].join('\n');
+    const blob = new Blob(["\ufeff" + csv], { type: 'text/csv;charset=utf-8;' });
+    const url = URL.createObjectURL(blob);
+    const a = document.createElement('a');
+    a.href = url;
+    const filenameMap: any = { totalProjects: '项目总览', active: '进行中项目', completed: '已完成项目', designers: '设计师统计', customers: '客户统计', revenue: '收入统计' };
+    a.download = `${filenameMap[this.detailType() || 'totalProjects']}-明细.csv`;
+    a.click();
+    URL.revokeObjectURL(url);
+  }
+}

+ 234 - 0
src/app/pages/admin/designers/designers.html

@@ -0,0 +1,234 @@
+<div class="designers-page">
+  <!-- 页面标题和操作按钮 -->
+  <div class="page-header">
+    <div class="header-left">
+      <h2 class="page-title">设计师管理</h2>
+      <p class="page-description">管理设计师档案、技能等级与项目绩效</p>
+    </div>
+    <div class="header-actions">
+      <button class="btn primary" (click)="addDesigner()">+ 新建设计师</button>
+      <button class="btn" (click)="exportDesigners()">导出</button>
+    </div>
+  </div>
+
+  <!-- 统计卡片 -->
+  <div class="stats-cards">
+    <div class="stat-card">
+      <div class="stat-label">设计师总数</div>
+      <div class="stat-value">{{ totalDesigners() }}</div>
+    </div>
+    <div class="stat-card green">
+      <div class="stat-label">在线</div>
+      <div class="stat-value">{{ activeDesigners() }}</div>
+    </div>
+    <div class="stat-card orange">
+      <div class="stat-label">忙碌</div>
+      <div class="stat-value">{{ busyDesigners() }}</div>
+    </div>
+    <div class="stat-card gray">
+      <div class="stat-label">离线</div>
+      <div class="stat-value">{{ inactiveDesigners() }}</div>
+    </div>
+  </div>
+
+  <!-- 工具栏:搜索与筛选 -->
+  <div class="toolbar">
+    <div class="search">
+      <input placeholder="搜索姓名/邮箱" (input)="searchTerm.set($any($event.target).value)" [value]="searchTerm()" />
+    </div>
+    <div class="filters">
+      <select (change)="selectedDepartment.set($any($event.target).value)">
+        <option value="all">全部部门</option>
+        <option *ngFor="let d of departments" [value]="d">{{ d }}</option>
+      </select>
+      <select (change)="selectedLevel.set($any($event.target).value)">
+        <option value="all">全部级别</option>
+        <option value="junior">初级</option>
+        <option value="intermediate">中级</option>
+        <option value="senior">高级</option>
+        <option value="expert">专家</option>
+      </select>
+      <select (change)="selectedStatus.set($any($event.target).value)">
+        <option value="all">全部状态</option>
+        <option value="active">在线</option>
+        <option value="busy">忙碌</option>
+        <option value="inactive">离线</option>
+      </select>
+      <button class="btn" (click)="resetFilters()">重置</button>
+    </div>
+  </div>
+
+  <!-- 表格 -->
+  <div class="table-card">
+    <div class="table header">
+      <div class="th name">设计师</div>
+      <div class="th dept">部门</div>
+      <div class="th level">级别</div>
+      <div class="th status">状态</div>
+      <div class="th projects">项目数</div>
+      <div class="th rate">完工率</div>
+      <div class="th score">满意度</div>
+      <div class="th join">入职日期</div>
+      <div class="th active">最近活跃</div>
+      <div class="th actions">操作</div>
+    </div>
+
+    <div class="table row" *ngFor="let d of filteredDesigners">
+      <div class="cell name">
+        <img [src]="d.avatar" class="avatar" alt="头像" />
+        <div class="info">
+          <div class="title">{{ d.name }}</div>
+          <div class="sub">{{ d.email }} · {{ d.phone }}</div>
+        </div>
+      </div>
+      <div class="cell">{{ d.department }}</div>
+      <div class="cell"><span class="tag">{{ getLevelText(d.level) }}</span></div>
+      <div class="cell">
+        <span class="status-dot" [class.online]="d.status==='active'" [class.busy]="d.status==='busy'" [class.offline]="d.status==='inactive'"></span>
+        {{ getStatusText(d.status) }}
+      </div>
+      <div class="cell">{{ d.projectCount }}</div>
+      <div class="cell">{{ d.completionRate }}%</div>
+      <div class="cell">{{ d.satisfactionScore }}</div>
+      <div class="cell">{{ d.joinDate | date:'yyyy-MM-dd' }}</div>
+      <div class="cell">{{ d.lastActiveDate | date:'yyyy-MM-dd' }}</div>
+      <div class="cell actions">
+        <button class="icon" title="查看" (click)="viewDesigner(d)">详</button>
+        <button class="icon" title="编辑" (click)="editDesigner(d)">编</button>
+        <button class="icon danger" title="删除" (click)="deleteDesigner(d)">删</button>
+      </div>
+    </div>
+
+    <div class="empty" *ngIf="filteredDesigners.length === 0">暂无符合条件的数据</div>
+  </div>
+
+  <!-- 侧边面板 -->
+  <div class="panel-overlay" *ngIf="showPanel" (click)="closePanel()">
+    <div class="side-panel" (click)="$event.stopPropagation()">
+      <!-- 面板标题 -->
+      <div class="panel-header">
+        <h3>
+          {{ panelMode === 'add' ? '新建设计师' : panelMode === 'detail' ? '设计师详情' : '编辑设计师' }}
+        </h3>
+        <button class="close-btn" (click)="closePanel()">×</button>
+      </div>
+
+      <!-- 详情模式 -->
+      <div class="panel-content" *ngIf="panelMode === 'detail' && currentDesigner">
+        <div class="detail-card">
+          <div class="detail-avatar">
+            <img [src]="currentDesigner.avatar" alt="头像" />
+          </div>
+          <div class="detail-info">
+            <h4>{{ currentDesigner.name }}</h4>
+            <p class="detail-email">{{ currentDesigner.email }}</p>
+            <p class="detail-phone">{{ currentDesigner.phone }}</p>
+          </div>
+        </div>
+        <div class="detail-section">
+          <label>部门</label>
+          <span>{{ currentDesigner.department }}</span>
+        </div>
+        <div class="detail-section">
+          <label>级别</label>
+          <span class="tag">{{ getLevelText(currentDesigner.level) }}</span>
+        </div>
+        <div class="detail-section">
+          <label>状态</label>
+          <span class="status-dot" [class.online]="currentDesigner.status==='active'" [class.busy]="currentDesigner.status==='busy'" [class.offline]="currentDesigner.status==='inactive'"></span>
+          {{ getStatusText(currentDesigner.status) }}
+        </div>
+        <div class="detail-section">
+          <label>技能</label>
+          <div class="skills-tags">
+            <span class="skill-tag" *ngFor="let skill of currentDesigner.skills">{{ skill }}</span>
+          </div>
+        </div>
+        <div class="detail-section">
+          <label>项目数</label>
+          <span>{{ currentDesigner.projectCount }}</span>
+        </div>
+        <div class="detail-section">
+          <label>完成率</label>
+          <span>{{ currentDesigner.completionRate }}%</span>
+        </div>
+        <div class="detail-section">
+          <label>满意度</label>
+          <span>{{ currentDesigner.satisfactionScore }}/5</span>
+        </div>
+        <div class="detail-section">
+          <label>入职日期</label>
+          <span>{{ currentDesigner.joinDate | date:'yyyy-MM-dd' }}</span>
+        </div>
+        <div class="detail-section">
+          <label>最近活跃</label>
+          <span>{{ currentDesigner.lastActiveDate | date:'yyyy-MM-dd HH:mm' }}</span>
+        </div>
+      </div>
+
+      <!-- 新增/编辑模式 -->
+      <div class="panel-content" *ngIf="panelMode === 'add' || panelMode === 'edit'">
+        <form class="designer-form">
+          <div class="form-group">
+            <label>姓名 *</label>
+            <input type="text" [(ngModel)]="formModel.name" name="name" placeholder="请输入姓名" required>
+          </div>
+          <div class="form-group">
+            <label>邮箱</label>
+            <input type="email" [(ngModel)]="formModel.email" name="email" placeholder="请输入邮箱">
+          </div>
+          <div class="form-group">
+            <label>电话</label>
+            <input type="text" [(ngModel)]="formModel.phone" name="phone" placeholder="请输入电话">
+          </div>
+          <div class="form-group">
+            <label>部门</label>
+            <select [(ngModel)]="formModel.department" name="department">
+              <option *ngFor="let d of departments" [value]="d">{{ d }}</option>
+            </select>
+          </div>
+          <div class="form-group">
+            <label>级别</label>
+            <select [(ngModel)]="formModel.level" name="level">
+              <option value="junior">初级</option>
+              <option value="intermediate">中级</option>
+              <option value="senior">高级</option>
+              <option value="expert">专家</option>
+            </select>
+          </div>
+          <div class="form-group">
+            <label>状态</label>
+            <select [(ngModel)]="formModel.status" name="status">
+              <option value="active">在线</option>
+              <option value="busy">忙碌</option>
+              <option value="inactive">离线</option>
+            </select>
+          </div>
+          <div class="form-group">
+            <label>技能</label>
+            <input type="text" [(ngModel)]="formModel.skillsInput" name="skillsInput" placeholder="请输入技能,用逗号分隔">
+          </div>
+          <div class="form-group">
+            <label>项目数</label>
+            <input type="number" [(ngModel)]="formModel.projectCount" name="projectCount" placeholder="0" min="0">
+          </div>
+          <div class="form-group">
+            <label>完成率(%)</label>
+            <input type="number" [(ngModel)]="formModel.completionRate" name="completionRate" placeholder="0" min="0" max="100">
+          </div>
+          <div class="form-group">
+            <label>满意度(1-5)</label>
+            <input type="number" [(ngModel)]="formModel.satisfactionScore" name="satisfactionScore" placeholder="5" min="1" max="5" step="0.1">
+          </div>
+        </form>
+      </div>
+
+      <!-- 面板操作按钮 -->
+      <div class="panel-footer">
+        <button class="btn" (click)="closePanel()">取消</button>
+        <button class="btn primary" *ngIf="panelMode === 'add'" (click)="saveDesigner()">保存</button>
+        <button class="btn primary" *ngIf="panelMode === 'edit'" (click)="updateDesigner()">更新</button>
+      </div>
+    </div>
+  </div>
+</div>

+ 28 - 0
src/app/pages/admin/designers/designers.scss

@@ -0,0 +1,28 @@
+.designers-page{padding:24px}
+.page-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.page-title{font-size:20px;margin:0 0 6px}.page-description{color:#64748b;margin:0}.btn{padding:8px 12px;border-radius:8px;border:1px solid #e5e7eb;background:#fff;cursor:pointer}.btn.primary{background:#165DFF;color:#fff;border-color:#165DFF}
+.stats-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:16px}.stat-card{background:#fff;border-radius:12px;box-shadow:0 1px 2px rgba(0,0,0,.06);padding:16px}.stat-card.green .stat-value{color:#10b981}.stat-card.orange .stat-value{color:#f59e0b}.stat-card.gray .stat-value{color:#94a3b8}.stat-label{color:#64748b;font-size:12px}.stat-value{font-size:22px;font-weight:700;margin-top:6px}
+.toolbar{display:flex;justify-content:space-between;align-items:center;background:#fff;padding:12px 16px;border-radius:12px;box-shadow:0 1px 2px rgba(0,0,0,.06);margin-bottom:12px}.search input{width:320px;padding:8px 10px;border:1px solid #e5e7eb;border-radius:8px}.filters{display:flex;gap:8px;align-items:center}.filters select{padding:8px;border:1px solid #e5e7eb;border-radius:8px}
+.table-card{background:#fff;border-radius:12px;box-shadow:0 1px 2px rgba(0,0,0,.06)}.table{display:grid;grid-template-columns:2.2fr 1.2fr .9fr 1fr .8fr .9fr .8fr 1.1fr 1.1fr 1fr;align-items:center;padding:12px 16px;border-bottom:1px solid #f1f5f9}.table.header{color:#64748b;font-weight:600;background:#f8fafc;border-top-left-radius:12px;border-top-right-radius:12px}.table.row{background:#fff}
+.cell.name{display:flex;align-items:center;gap:10px}.avatar{width:36px;height:36px;border-radius:50%}.info .title{font-weight:600}.info .sub{color:#94a3b8;font-size:12px}.tag{display:inline-block;padding:2px 8px;border-radius:999px;background:#eef2ff;color:#4f46e5;font-weight:600;font-size:12px}.status-dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px;background:#94a3b8}.status-dot.online{background:#10b981}.status-dot.busy{background:#f59e0b}.status-dot.offline{background:#94a3b8}.actions{display:flex;gap:6px;justify-content:flex-end}.icon{border:1px solid #e5e7eb;background:#fff;border-radius:8px;padding:6px 8px;cursor:pointer}.icon.danger{color:#ef4444;border-color:#fecaca}
+.empty{padding:24px;text-align:center;color:#94a3b8}
+@media (max-width: 1200px){.table{grid-template-columns:2fr 1fr .9fr 1fr .8fr .9fr .8fr 1fr 1fr 1fr}.search input{width:100%}.toolbar{flex-direction:column;gap:10px;align-items:flex-start}}
+/* 侧边面板样式 */
+.panel-overlay{position:fixed;inset:0;background:rgba(15,23,42,.35);backdrop-filter:saturate(120%) blur(2px);display:flex;justify-content:flex-end;z-index:1000}
+.side-panel{width:420px;max-width:90vw;height:100%;background:#fff;border-left:1px solid #e5e7eb;box-shadow:-6px 0 16px rgba(0,0,0,.06);display:flex;flex-direction:column}
+.panel-header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid #f1f5f9}
+.panel-header h3{margin:0;font-size:16px}
+.close-btn{border:none;background:transparent;font-size:22px;line-height:1;cursor:pointer;color:#94a3b8}
+.panel-content{padding:14px 16px;overflow:auto}
+.detail-card{display:flex;gap:12px;align-items:center;margin-bottom:12px}
+.detail-avatar img{width:48px;height:48px;border-radius:50%;border:1px solid #e5e7eb}
+.detail-info h4{margin:0 0 4px}
+.detail-email,.detail-phone{margin:0;color:#64748b}
+.detail-section{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px dashed #f1f5f9}
+.skills-tags{display:flex;flex-wrap:wrap;gap:6px}
+.skill-tag{background:#f1f5f9;color:#334155;border-radius:999px;padding:2px 8px;font-size:12px}
+.designer-form{display:grid;grid-template-columns:1fr;gap:10px}
+.form-group{display:flex;flex-direction:column;gap:6px}
+.form-group input,.form-group select{padding:8px 10px;border:1px solid #e5e7eb;border-radius:8px}
+.panel-footer{padding:12px 16px;border-top:1px solid #f1f5f9;display:flex;justify-content:flex-end;gap:8px}
+.panel-footer .btn{padding:8px 12px;border-radius:8px;border:1px solid #e5e7eb;background:#fff}
+.panel-footer .btn.primary{background:#165DFF;color:#fff;border-color:#165DFF}

+ 340 - 0
src/app/pages/admin/designers/designers.ts

@@ -0,0 +1,340 @@
+import { Component, OnInit, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { FormsModule } from '@angular/forms';
+
+// 设计师接口定义
+interface Designer {
+  id: string;
+  name: string;
+  avatar: string;
+  email: string;
+  phone: string;
+  department: string;
+  level: 'junior' | 'intermediate' | 'senior' | 'expert';
+  skills: string[];
+  status: 'active' | 'inactive' | 'busy';
+  projectCount: number;
+  completionRate: number;
+  satisfactionScore: number;
+  joinDate: Date;
+  lastActiveDate: Date;
+}
+
+@Component({
+  selector: 'app-designers',
+  standalone: true,
+  imports: [CommonModule, RouterModule, FormsModule],
+  templateUrl: './designers.html',
+  styleUrl: './designers.scss'
+})
+export class Designers implements OnInit {
+  // 设计师数据
+  designers = signal<Designer[]>([
+    {
+      id: '1',
+      name: '张小美',
+      avatar: 'data:image/svg+xml,%3Csvg width="40" height="40" xmlns="http://www.w3.org/2000/svg"%3E%3Crect width="100%25" height="100%25" fill="%23FFE6CC"/%3E%3Ctext x="50%25" y="50%25" font-family="Arial" font-size="14" font-weight="bold" text-anchor="middle" fill="%23FF7D00" dy="0.3em"%3E张%3C/text%3E%3C/svg%3E',
+      email: 'zhang.xiaomei@company.com',
+      phone: '138****1234',
+      department: '室内设计部',
+      level: 'senior',
+      skills: ['室内设计', '3D建模', 'AutoCAD', 'SketchUp'],
+      status: 'active',
+      projectCount: 15,
+      completionRate: 95,
+      satisfactionScore: 4.8,
+      joinDate: new Date('2022-03-15'),
+      lastActiveDate: new Date()
+    },
+    {
+      id: '2',
+      name: '李设计',
+      avatar: 'data:image/svg+xml,%3Csvg width="40" height="40" xmlns="http://www.w3.org/2000/svg"%3E%3Crect width="100%25" height="100%25" fill="%23E6F7FF"/%3E%3Ctext x="50%25" y="50%25" font-family="Arial" font-size="14" font-weight="bold" text-anchor="middle" fill="%23165DFF" dy="0.3em"%3E李%3C/text%3E%3C/svg%3E',
+      email: 'li.sheji@company.com',
+      phone: '139****5678',
+      department: '建筑设计部',
+      level: 'expert',
+      skills: ['建筑设计', 'Revit', 'Rhino', '参数化设计'],
+      status: 'busy',
+      projectCount: 22,
+      completionRate: 98,
+      satisfactionScore: 4.9,
+      joinDate: new Date('2021-08-20'),
+      lastActiveDate: new Date()
+    },
+    {
+      id: '3',
+      name: '王创意',
+      avatar: 'data:image/svg+xml,%3Csvg width="40" height="40" xmlns="http://www.w3.org/2000/svg"%3E%3Crect width="100%25" height="100%25" fill="%23F0F9FF"/%3E%3Ctext x="50%25" y="50%25" font-family="Arial" font-size="14" font-weight="bold" text-anchor="middle" fill="%234E5BA6" dy="0.3em"%3E王%3C/text%3E%3C/svg%3E',
+      email: 'wang.chuangyi@company.com',
+      phone: '137****9012',
+      department: '景观设计部',
+      level: 'intermediate',
+      skills: ['景观设计', 'Lumion', 'Photoshop', '手绘'],
+      status: 'active',
+      projectCount: 8,
+      completionRate: 92,
+      satisfactionScore: 4.6,
+      joinDate: new Date('2023-01-10'),
+      lastActiveDate: new Date()
+    }
+  ]);
+
+  // 筛选和搜索
+  searchTerm = signal('');
+  selectedDepartment = signal('all');
+  selectedLevel = signal('all');
+  selectedStatus = signal('all');
+
+  // 统计数据
+  totalDesigners = signal(24);
+  activeDesigners = signal(18);
+  busyDesigners = signal(4);
+  inactiveDesigners = signal(2);
+
+  // 部门列表
+  departments = ['室内设计部', '建筑设计部', '景观设计部', '平面设计部'];
+
+  // 侧边面板状态
+  showPanel: boolean = false;
+  panelMode: 'add' | 'detail' | 'edit' = 'add';
+  currentDesigner: Designer | null = null;
+  formModel: (Partial<Designer> & { skillsInput?: string }) = {};
+
+  ngOnInit(): void {
+    this.loadDesigners();
+  }
+
+  loadDesigners(): void {
+    // 在实际应用中,这里会从服务加载数据
+    console.log('加载设计师数据');
+  }
+
+  // 筛选设计师
+  get filteredDesigners() {
+    return this.designers().filter(designer => {
+      const matchesSearch = designer.name.toLowerCase().includes(this.searchTerm().toLowerCase()) ||
+                           designer.email.toLowerCase().includes(this.searchTerm().toLowerCase());
+      const matchesDepartment = this.selectedDepartment() === 'all' || designer.department === this.selectedDepartment();
+      const matchesLevel = this.selectedLevel() === 'all' || designer.level === this.selectedLevel();
+      const matchesStatus = this.selectedStatus() === 'all' || designer.status === this.selectedStatus();
+      
+      return matchesSearch && matchesDepartment && matchesLevel && matchesStatus;
+    });
+  }
+
+  // 获取等级显示文本
+  getLevelText(level: string): string {
+    const levelMap: { [key: string]: string } = {
+      'junior': '初级',
+      'intermediate': '中级',
+      'senior': '高级',
+      'expert': '专家'
+    };
+    return levelMap[level] || level;
+  }
+
+  // 获取状态显示文本
+  getStatusText(status: string): string {
+    const statusMap: { [key: string]: string } = {
+      'active': '在线',
+      'inactive': '离线',
+      'busy': '忙碌'
+    };
+    return statusMap[status] || status;
+  }
+
+  // 获取状态样式类
+  getStatusClass(status: string): string {
+    return `status-${status}`;
+  }
+
+  // 查看设计师详情
+  viewDesigner(designer: Designer): void {
+    this.currentDesigner = designer;
+    this.panelMode = 'detail';
+    this.showPanel = true;
+  }
+
+  // 编辑设计师
+  editDesigner(designer: Designer): void {
+    this.currentDesigner = designer;
+    this.formModel = { ...designer };
+    this.panelMode = 'edit';
+    this.showPanel = true;
+  }
+
+  // 关闭面板
+  closePanel(): void {
+    this.showPanel = false;
+    this.panelMode = 'add';
+    this.currentDesigner = null;
+    this.formModel = {};
+  }
+
+  // 保存新增
+  saveDesigner(): void {
+    const name = (this.formModel.name || '').trim();
+    if (!name) {
+      alert('请输入姓名');
+      return;
+    }
+    const newDesigner: Designer = {
+      id: Date.now().toString(),
+      name,
+      avatar:
+        `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40"><rect width="100%" height="100%" fill="%23F0F9FF"/><text x="50%" y="50%" font-family="Arial" font-size="14" font-weight="bold" text-anchor="middle" fill="%234E5BA6" dy="0.35em">${name.charAt(0)}</text></svg>`,
+      email: this.formModel.email || '',
+      phone: this.formModel.phone || '',
+      department: (this.formModel.department as Designer['department']) || '室内设计部',
+      level: (this.formModel.level as Designer['level']) || 'junior',
+      skills: Array.isArray(this.formModel.skills) ? (this.formModel.skills as string[]) : ((this.formModel as any).skillsInput ? String((this.formModel as any).skillsInput).split(',').map(s=>s.trim()).filter(Boolean) : []),
+      status: (this.formModel.status as Designer['status']) || 'active',
+      projectCount: Number(this.formModel.projectCount || 0),
+      completionRate: Number(this.formModel.completionRate || 0),
+      satisfactionScore: Number(this.formModel.satisfactionScore || 5),
+      joinDate: this.formModel.joinDate instanceof Date ? this.formModel.joinDate : new Date(),
+      lastActiveDate: new Date()
+    };
+    this.designers.set([newDesigner, ...this.designers()]);
+    this.totalDesigners.set(this.totalDesigners() + 1);
+    if (newDesigner.status === 'active') this.activeDesigners.set(this.activeDesigners() + 1);
+    if (newDesigner.status === 'busy') this.busyDesigners.set(this.busyDesigners() + 1);
+    if (newDesigner.status === 'inactive') this.inactiveDesigners.set(this.inactiveDesigners() + 1);
+    this.closePanel();
+  }
+
+  // 提交编辑
+  updateDesigner(): void {
+    if (!this.currentDesigner) return;
+    const updated: Designer = {
+      ...this.currentDesigner,
+      name: (this.formModel.name ?? this.currentDesigner.name) as string,
+      email: (this.formModel.email ?? this.currentDesigner.email) as string,
+      phone: (this.formModel.phone ?? this.currentDesigner.phone) as string,
+      department: (this.formModel.department ?? this.currentDesigner.department) as Designer['department'],
+      level: (this.formModel.level ?? this.currentDesigner.level) as Designer['level'],
+      status: (this.formModel.status ?? this.currentDesigner.status) as Designer['status'],
+      skills: Array.isArray(this.formModel.skills)
+        ? (this.formModel.skills as string[])
+        : ((this.formModel as any).skillsInput
+            ? String((this.formModel as any).skillsInput).split(',').map(s=>s.trim()).filter(Boolean)
+            : this.currentDesigner.skills),
+      projectCount: Number(this.formModel.projectCount ?? this.currentDesigner.projectCount),
+      completionRate: Number(this.formModel.completionRate ?? this.currentDesigner.completionRate),
+      satisfactionScore: Number(this.formModel.satisfactionScore ?? this.currentDesigner.satisfactionScore),
+      joinDate: this.formModel.joinDate instanceof Date ? this.formModel.joinDate : this.currentDesigner.joinDate,
+      lastActiveDate: this.currentDesigner.lastActiveDate
+    };
+
+    // 若状态变更,更新统计
+    if (updated.status !== this.currentDesigner.status) {
+      const dec = (s: 'active'|'busy'|'inactive') => {
+        if (s==='active') this.activeDesigners.set(this.activeDesigners()-1);
+        if (s==='busy') this.busyDesigners.set(this.busyDesigners()-1);
+        if (s==='inactive') this.inactiveDesigners.set(this.inactiveDesigners()-1);
+      };
+      const inc = (s: 'active'|'busy'|'inactive') => {
+        if (s==='active') this.activeDesigners.set(this.activeDesigners()+1);
+        if (s==='busy') this.busyDesigners.set(this.busyDesigners()+1);
+        if (s==='inactive') this.inactiveDesigners.set(this.inactiveDesigners()+1);
+      };
+      dec(this.currentDesigner.status);
+      inc(updated.status);
+    }
+
+    this.designers.set(this.designers().map(d => d.id === updated.id ? updated : d));
+    this.closePanel();
+  }
+
+  // 添加新设计师 -> 打开面板
+  addDesigner(): void {
+    this.formModel = {
+      name: '',
+      email: '',
+      phone: '',
+      department: '室内设计部',
+      level: 'junior',
+      status: 'active',
+      projectCount: 0,
+      completionRate: 0,
+      satisfactionScore: 5,
+      joinDate: new Date(),
+      skillsInput: ''
+    };
+    this.currentDesigner = null;
+    this.panelMode = 'add';
+    this.showPanel = true;
+  }
+
+  // 删除设计师
+  deleteDesigner(designer: Designer): void {
+    if (confirm(`确定要删除设计师 ${designer.name} 吗?`)) {
+      const updatedDesigners = this.designers().filter(d => d.id !== designer.id);
+      this.designers.set(updatedDesigners);
+      console.log('删除设计师:', designer);
+    }
+  }
+
+  // (已移除重复的新增方法,保留通过侧边面板新增的流程)
+
+  // 导出设计师数据
+  exportDesigners(): void {
+    const header = [
+      '姓名',
+      '邮箱',
+      '电话',
+      '部门',
+      '级别',
+      '状态',
+      '项目数',
+      '完工率(%)',
+      '满意度',
+      '入职日期',
+      '最近活跃'
+    ];
+
+    const rows = this.filteredDesigners.map(d => [
+      d.name,
+      d.email,
+      d.phone,
+      d.department,
+      this.getLevelText(d.level),
+      this.getStatusText(d.status),
+      String(d.projectCount),
+      String(d.completionRate),
+      String(d.satisfactionScore),
+      d.joinDate instanceof Date ? d.joinDate.toISOString().slice(0, 10) : String(d.joinDate),
+      d.lastActiveDate instanceof Date ? d.lastActiveDate.toISOString().slice(0, 10) : String(d.lastActiveDate)
+    ]);
+
+    this.downloadCSV('设计师列表.csv', [header, ...rows]);
+  }
+
+  private downloadCSV(filename: string, rows: (string | number)[][]) {
+    const escape = (val: string | number) => {
+      const s = String(val ?? '');
+      if (/[",\n]/.test(s)) {
+        return '"' + s.replace(/"/g, '""') + '"';
+      }
+      return s;
+    };
+    const csv = rows.map(r => r.map(escape).join(',')).join('\n');
+    const blob = new Blob(['\ufeff', csv], { type: 'text/csv;charset=utf-8;' });
+    const url = URL.createObjectURL(blob);
+    const a = document.createElement('a');
+    a.href = url;
+    a.download = filename;
+    a.click();
+    URL.revokeObjectURL(url);
+  }
+
+  // 重置筛选条件
+  resetFilters(): void {
+    this.searchTerm.set('');
+    this.selectedDepartment.set('all');
+    this.selectedLevel.set('all');
+    this.selectedStatus.set('all');
+  }
+}

+ 68 - 0
src/app/pages/admin/finance/finance.html

@@ -0,0 +1,68 @@
+<div class="finance-page">
+  <div class="page-header">
+    <div class="header-left">
+      <h2 class="page-title">财务管理</h2>
+      <p class="page-description">统计收支、查看项目流水并导出数据</p>
+    </div>
+    <div class="header-actions">
+      <button class="btn primary" (click)="exportReport()">导出报表</button>
+    </div>
+  </div>
+
+  <div class="stats-cards">
+    <div class="stat-card income">
+      <div class="stat-label">本月收入</div>
+      <div class="stat-value">{{ formatAmount(income()) }}</div>
+    </div>
+    <div class="stat-card expense">
+      <div class="stat-label">本月支出</div>
+      <div class="stat-value">{{ formatAmount(expense()) }}</div>
+    </div>
+    <div class="stat-card profit">
+      <div class="stat-label">本月利润</div>
+      <div class="stat-value">{{ formatAmount(profit()) }}</div>
+    </div>
+  </div>
+
+  <div class="toolbar">
+    <div class="search"><input placeholder="搜索项目/客户" (input)="keyword.set($any($event.target).value)" [value]="keyword()"/></div>
+    <div class="filters">
+      <select (change)="type.set($any($event.target).value)">
+        <option value="all">全部类型</option>
+        <option value="income">收入</option>
+        <option value="expense">支出</option>
+      </select>
+      <select (change)="status.set($any($event.target).value)">
+        <option value="all">全部状态</option>
+        <option value="done">已入账</option>
+        <option value="pending">待入账</option>
+      </select>
+      <button class="btn" (click)="resetFilters()">重置</button>
+    </div>
+  </div>
+
+  <div class="table-card">
+    <div class="table header">
+      <div>日期</div>
+      <div>类型</div>
+      <div>项目</div>
+      <div>客户</div>
+      <div>金额</div>
+      <div>状态</div>
+    </div>
+    <div class="table row" *ngFor="let i of filtered">
+      <div>{{ i.date }}</div>
+      <div>
+        <span class="chip" [class.green]="i.type==='income'" [class.red]="i.type==='expense'">{{ i.type==='income' ? '收入' : '支出' }}</span>
+      </div>
+      <div>{{ i.project }}</div>
+      <div>{{ i.customer }}</div>
+      <div>{{ formatAmount(i.amount) }}</div>
+      <div>
+        <span class="dot" [class.green]="i.status==='done'" [class.orange]="i.status==='pending'"></span>
+        {{ i.status==='done' ? '已入账' : '待入账' }}
+      </div>
+    </div>
+    <div class="empty" *ngIf="filtered.length===0">暂无数据</div>
+  </div>
+</div>

+ 7 - 0
src/app/pages/admin/finance/finance.scss

@@ -0,0 +1,7 @@
+.finance-page{padding:24px}
+.page-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.page-title{font-size:20px;margin:0 0 6px}.page-description{color:#64748b;margin:0}.btn{padding:8px 12px;border-radius:8px;border:1px solid #e5e7eb;background:#fff;cursor:pointer}.btn.primary{background:#165DFF;color:#fff;border-color:#165DFF}
+.stats-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:16px}.stat-card{background:#fff;border-radius:12px;box-shadow:0 1px 2px rgba(0,0,0,.06);padding:16px}.stat-card.income .stat-value{color:#10b981}.stat-card.expense .stat-value{color:#ef4444}.stat-card.profit .stat-value{color:#0ea5e9}.stat-label{color:#64748b;font-size:12px}.stat-value{font-size:22px;font-weight:700;margin-top:6px}
+.toolbar{display:flex;justify-content:space-between;align-items:center;background:#fff;padding:12px 16px;border-radius:12px;box-shadow:0 1px 2px rgba(0,0,0,.06);margin-bottom:12px}.search input{width:320px;padding:8px 10px;border:1px solid #e5e7eb;border-radius:8px}.filters{display:flex;gap:8px;align-items:center}.filters select{padding:8px;border:1px solid #e5e7eb;border-radius:8px}
+.table-card{background:#fff;border-radius:12px;box-shadow:0 1px 2px rgba(0,0,0,.06)}.table{display:grid;grid-template-columns:1fr .8fr 2fr 1.2fr 1.2fr 1fr;align-items:center;padding:12px 16px;border-bottom:1px solid #f1f5f9}.table.header{color:#64748b;font-weight:600;background:#f8fafc;border-top-left-radius:12px;border-top-right-radius:12px}.chip{display:inline-block;padding:2px 8px;border-radius:999px;background:#eef2ff;color:#4f46e5;font-weight:600;font-size:12px}.chip.green{background:#ecfdf5;color:#10b981}.chip.red{background:#fef2f2;color:#ef4444}.dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px;background:#94a3b8}.dot.green{background:#10b981}.dot.orange{background:#fb923c}
+.empty{padding:24px;text-align:center;color:#94a3b8}
+@media (max-width: 992px){.stats-cards{grid-template-columns:1fr}.search input{width:100%}.toolbar{flex-direction:column;gap:10px;align-items:flex-start}}

+ 90 - 0
src/app/pages/admin/finance/finance.ts

@@ -0,0 +1,90 @@
+import { Component, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+interface RecordItem {
+  id: string;
+  date: string; // yyyy-mm-dd
+  type: 'income' | 'expense';
+  project: string;
+  customer: string;
+  amount: number;
+  status: 'pending' | 'done';
+}
+
+@Component({
+  selector: 'app-admin-finance',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './finance.html',
+  styleUrl: './finance.scss'
+})
+export class FinancePage {
+  // 统计
+  income = signal(511800);
+  expense = signal(98000);
+  profit = signal(this.income() - this.expense());
+
+  // 数据
+  list = signal<RecordItem[]>([
+    { id: 'r001', date: '2025-09-01', type: 'income', project: '极越办公公装设计', customer: '岚小牛', amount: 60000, status: 'done' },
+    { id: 'r002', date: '2025-09-05', type: 'income', project: '山水别墅景观', customer: '王先生', amount: 85000, status: 'done' },
+    { id: 'r003', date: '2025-09-08', type: 'expense', project: '材料采购', customer: '—', amount: 12000, status: 'done' },
+    { id: 'r004', date: '2025-09-09', type: 'income', project: '工业风办公室', customer: '赵女士', amount: 95000, status: 'pending' }
+  ]);
+
+  // 筛选
+  keyword = signal('');
+  type = signal<'all' | 'income' | 'expense'>('all');
+  status = signal<'all' | 'pending' | 'done'>('all');
+
+  get filtered() {
+    const kw = this.keyword().toLowerCase();
+    return this.list().filter(i => {
+      const m1 = !kw || i.project.toLowerCase().includes(kw) || i.customer.toLowerCase().includes(kw);
+      const m2 = this.type() === 'all' || i.type === this.type();
+      const m3 = this.status() === 'all' || i.status === this.status();
+      return m1 && m2 && m3;
+    });
+  }
+
+  formatAmount(n: number) {
+    return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY', maximumFractionDigits: 0 }).format(n);
+  }
+
+  resetFilters() {
+    this.keyword.set('');
+    this.type.set('all');
+    this.status.set('all');
+  }
+
+  // 导出当前筛选流水为 CSV
+  exportReport() {
+    const header = ['日期','类型','项目','客户','金额','状态'];
+    const rows = this.filtered.map(i => [
+      i.date,
+      i.type === 'income' ? '收入' : '支出',
+      i.project,
+      i.customer,
+      i.amount,
+      i.status === 'done' ? '已入账' : '待入账'
+    ]);
+    this.downloadCSV('财务流水.csv', [header, ...rows]);
+  }
+
+  private downloadCSV(filename: string, rows: (string | number)[][]) {
+    const escape = (val: string | number) => {
+      const s = String(val ?? '');
+      if (/[",\n]/.test(s)) return '"' + s.replace(/"/g, '""') + '"';
+      return s;
+    };
+    const csv = rows.map(r => r.map(escape).join(',')).join('\n');
+    const blob = new Blob(['\ufeff', csv], { type: 'text/csv;charset=utf-8;' });
+    const url = URL.createObjectURL(blob);
+    const a = document.createElement('a');
+    a.href = url;
+    a.download = filename;
+    a.click();
+    URL.revokeObjectURL(url);
+  }
+}

+ 75 - 42
src/app/pages/admin/system-settings/system-settings.html

@@ -138,8 +138,8 @@
 
   <!-- 项目状态流标签内容 -->
   <div *ngIf="activeTab === 'workflow'" class="tab-content">
-    <!-- 搜索区域 -->
-    <div class="search-section">
+    <!-- 搜索区域:左侧搜索 + 右侧悬浮按钮 -->
+    <div class="search-section workflow-search">
       <div class="search-input-wrapper">
         <svg class="search-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
           <circle cx="11" cy="11" r="8"></circle>
@@ -156,61 +156,93 @@
           </svg>
         </button>
       </div>
-      <button mat-raised-button color="primary" class="create-btn"
-              (click)="openWorkflowDialog()">
+  
+      <!-- 悬浮 + 按钮,hover 展开“添加阶段/批量操作” -->
+      <button mat-fab color="primary" class="fab-add" [matMenuTriggerFor]="fabMenu" #fabTrigger="matMenuTrigger"
+              (mouseenter)="fabTrigger.openMenu()">
         <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
           <line x1="12" y1="5" x2="12" y2="19"></line>
           <line x1="5" y1="12" x2="19" y2="12"></line>
         </svg>
-        添加阶段
       </button>
+      <mat-menu #fabMenu="matMenu" xPosition="before">
+        <button mat-menu-item (click)="openWorkflowDialog()">添加阶段</button>
+        <button mat-menu-item (click)="onBulkAction()">批量操作</button>
+      </mat-menu>
     </div>
-
-    <!-- 阶段列表 -->
-    <div class="workflow-stages-list">
-      <div *ngFor="let stage of filteredWorkflowStages; let i = index" class="stage-item" [class.disabled]="!stage.isActive">
-        <div class="stage-header">
-          <div class="stage-order">{{ stage.order }}</div>
-          <div class="stage-info">
-            <h3 class="stage-name">{{ stage.name }}</h3>
-            <p class="stage-description">{{ stage.description }}</p>
-          </div>
-          <div class="stage-controls">
-            <mat-slide-toggle
-              [checked]="stage.isActive"
-              (change)="toggleActive('workflow', stage.id, $event.checked)"
-              class="stage-toggle">
-              {{ stage.isActive ? '启用' : '禁用' }}
-            </mat-slide-toggle>
-            <div class="action-buttons">
-              <button mat-icon-button class="action-btn" color="primary"
-                      title="编辑阶段"
-                      (click)="openWorkflowDialog(stage)">
-                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+  
+    <!-- 批量操作工具条 -->
+    <div class="bulk-toolbar" *ngIf="isBulkMode">
+      <div class="bulk-left">
+        <mat-checkbox (change)="($event.checked ? selectAllVisibleWorkflow() : clearSelectionWorkflow())"></mat-checkbox>
+        <span>已选 {{ bulkSelectedCount }} 个核心阶段</span>
+      </div>
+      <div class="bulk-actions">
+        <button mat-stroked-button color="primary" (click)="bulkEnableWorkflow(true)">启用</button>
+        <button mat-stroked-button color="primary" (click)="bulkEnableWorkflow(false)">禁用</button>
+        <button mat-stroked-button (click)="bulkCopyWorkflow()">复制</button>
+        <button mat-stroked-button (click)="bulkSortWorkflow()">排序</button>
+        <button mat-stroked-button (click)="bulkExportWorkflow()">导出</button>
+        <button mat-stroked-button color="warn" (click)="bulkDeleteWorkflow()">删除</button>
+        <button mat-button (click)="exitBulkMode()">退出选择模式</button>
+      </div>
+    </div>
+  
+    <!-- 横向时间轴(展示核心 1~4 阶段) -->
+    <div class="workflow-timeline" *ngIf="coreWorkflowStages?.length">
+      <div class="timeline-step" *ngFor="let stage of coreWorkflowStages; let last = last" [class.last]="last" [attr.title]="stage.order + '. ' + stage.name">
+        <div class="node">
+          <span class="order">{{ stage.order }}</span>
+        </div>
+        <div class="label">{{ getStageAbbr(stage.name) }}</div>
+      </div>
+    </div>
+  
+    <!-- 栅格卡片:每个阶段两列卡片 -->
+    <div class="timeline-stage-cards" *ngIf="coreWorkflowStages?.length">
+      <div class="stage-cards-row" *ngFor="let stage of coreWorkflowStages">
+        <div class="stage-title">{{ stage.name }}</div>
+        <div class="stage-grid">
+          <!-- 信息卡片 -->
+          <div class="stage-card" [class.disabled]="!stage.isActive">
+            <div class="bulk-check" *ngIf="isBulkMode">
+              <mat-checkbox [checked]="isSelectedWorkflow(stage.id)" (change)="toggleSelectWorkflow(stage.id, $event.checked)"></mat-checkbox>
+            </div>
+            <div class="card-actions">
+              <button mat-icon-button class="action-btn" color="primary" title="编辑阶段" (click)="openWorkflowDialog(stage)">
+                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                   <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
                   <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
                 </svg>
               </button>
-              <button mat-icon-button class="action-btn" color="warn"
-                      title="删除阶段"
-                      (click)="deleteSetting('workflow', stage.id)">
-                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+              <button mat-icon-button class="action-btn" color="warn" title="删除阶段" (click)="deleteSetting('workflow', stage.id)">
+                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                   <polyline points="3,6 5,6 21,6"></polyline>
                   <path d="M19,6v14a2,2,0,0,1-2,2H7a2,2,0,0,1-2-2V6m3,0V4a2,2,0,0,1,2-2h4a2,2,0,0,1,2,2V6"></path>
                 </svg>
               </button>
             </div>
+            <div class="stage-name">{{ stage.name }}</div>
+            <div class="stage-desc">{{ stage.description }}</div>
+            <div class="stage-ops">
+              <mat-slide-toggle [checked]="stage.isActive" (change)="toggleActive('workflow', stage.id, $event.checked)" class="stage-toggle">
+                {{ stage.isActive ? '启用' : '禁用' }}
+              </mat-slide-toggle>
+            </div>
+          </div>
+          <!-- 占位:操作/说明卡片,可扩展为表单或统计 -->
+          <div class="stage-card muted">
+            <div class="hint-title">配置建议</div>
+            <ul class="hint-list">
+              <li>明确阶段产出与验收节点</li>
+              <li>约定跨阶段交接标准</li>
+              <li>必要时在此卡片扩展清单/表单</li>
+            </ul>
           </div>
-        </div>
-        <div class="stage-divider" *ngIf="i < filteredWorkflowStages.length - 1">
-          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ccc" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
-            <polyline points="22 12 13.5 12 13.5 2"></polyline>
-            <polyline points="1 12 8.5 12 8.5 22"></polyline>
-          </svg>
         </div>
       </div>
     </div>
-
+  
     <!-- 无数据状态 -->
     <div *ngIf="filteredWorkflowStages.length === 0" class="empty-state">
       <svg width="80" height="80" viewBox="0 0 24 24" fill="none" stroke="#ccc" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
@@ -260,6 +292,7 @@
           <div class="template-info">
             <div class="template-title-row">
               <h3 class="template-name">{{ template.name }}</h3>
+              <span class="rule-metric" *ngIf="template.category">{{ template.category }}</span>
             </div>
             <p class="template-description">{{ template.description }}</p>
           </div>
@@ -382,10 +415,10 @@
           </div>
         </div>
         <div class="rule-formula">
-          <div class="formula-label">条件:</div>
-          <div class="formula-content">{{ rule.condition }}</div>
-          <div class="formula-label">价格:</div>
-          <div class="formula-content">{{ rule.price }} 元</div>
+          <div class="formula-label">分类:</div>
+          <div class="formula-content">{{ rule.category }}</div>
+          <div class="formula-label">计算公式:</div>
+          <div class="formula-content">{{ rule.formula }}</div>
         </div>
       </div>
     </div>

+ 208 - 0
src/app/pages/admin/system-settings/system-settings.scss

@@ -757,4 +757,212 @@ $shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.
     padding: 8px 16px !important;
     font-size: 13px !important;
   }
+}
+
+// 顶部:工作流搜索 + 右侧悬浮按钮
+.workflow-search {
+  position: relative;
+  align-items: center;
+
+  .fab-add {
+    position: absolute;
+    right: 0;
+    top: 50%;
+    transform: translateY(-50%);
+    z-index: 2;
+    box-shadow: $shadow-md;
+
+    &:hover {
+      box-shadow: $shadow-lg;
+      transform: translateY(-50%) scale(1.03);
+    }
+  }
+}
+
+// 横向时间轴
+.workflow-timeline {
+  display: grid;
+  grid-auto-flow: column;
+  grid-auto-columns: 1fr;
+  align-items: center;
+  gap: 24px;
+  padding: 16px 20px;
+  background: $bg-white;
+  border-radius: 12px;
+  box-shadow: $shadow-sm;
+  margin-bottom: 20px;
+  position: relative;
+
+  &::before {
+    content: '';
+    position: absolute;
+    left: 40px;
+    right: 40px;
+    top: 50%;
+    height: 2px;
+    background: $border-color;
+    z-index: 0;
+  }
+
+  .timeline-step {
+    position: relative;
+    text-align: center;
+
+    .node {
+      width: 32px;
+      height: 32px;
+      border-radius: 50%;
+      background: $primary-color;
+      color: $bg-white;
+      display: inline-flex;
+      align-items: center;
+      justify-content: center;
+      box-shadow: $shadow-sm;
+      position: relative;
+      z-index: 1;
+    }
+
+    .label {
+      margin-top: 8px;
+      font-size: 14px;
+      color: $text-primary;
+      font-weight: 600;
+    }
+
+    &.last::after {
+      display: none;
+    }
+  }
+}
+
+// 两列卡片
+.timeline-stage-cards {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+
+  .stage-cards-row {
+    background: $bg-white;
+    border-radius: 12px;
+    box-shadow: $shadow-sm;
+    padding: 16px 16px 8px;
+
+    .stage-title {
+      font-size: 16px;
+      font-weight: 700;
+      color: $primary-color;
+      margin: 0 0 12px;
+    }
+
+    .stage-grid {
+      display: grid;
+      grid-template-columns: repeat(2, minmax(320px, 1fr));
+      gap: 16px;
+
+      @media (max-width: 1024px) {
+        grid-template-columns: 1fr;
+      }
+
+      .stage-card {
+        position: relative;
+        background: $bg-white;
+        border-radius: 12px;
+        box-shadow: $shadow-sm;
+        padding: 16px;
+        transition: transform .2s ease, box-shadow .2s ease;
+        overflow: hidden;
+
+        &:hover {
+          transform: translateY(-2px) scale(1.01);
+          box-shadow: $shadow-md;
+        }
+
+        &.muted {
+          background: $bg-light;
+        }
+
+        &.disabled { opacity: .6; }
+
+        .card-actions {
+          position: absolute;
+          top: 8px;
+          right: 8px;
+          display: flex;
+          gap: 4px;
+          opacity: 0;
+          transition: opacity .2s ease;
+        }
+
+        &:hover .card-actions { opacity: 1; }
+
+        .stage-name {
+          font-size: 16px;
+          font-weight: 700;
+          color: $primary-color;
+          margin-bottom: 6px;
+        }
+
+        .stage-desc {
+          font-size: 14px;
+          color: $text-secondary;
+          line-height: 1.6;
+          margin-bottom: 12px;
+        }
+
+        .stage-ops {
+          .stage-toggle {
+            font-size: 12px;
+          }
+        }
+
+        .hint-title {
+          font-size: 14px;
+          font-weight: 600;
+          color: $text-secondary;
+          margin-bottom: 8px;
+        }
+        .hint-list { margin: 0; padding-left: 18px; color: $text-tertiary; }
+      }
+    }
+  }
+}
+
+.bulk-toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 16px;
+  background: $bg-white;
+  border: 1px solid $border-color;
+  border-radius: 10px;
+  padding: 10px 14px;
+  margin: 8px 0 16px;
+  box-shadow: $shadow-sm;
+
+  .bulk-left {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    color: $text-secondary;
+    font-size: 14px;
+  }
+  .bulk-actions {
+    display: flex;
+    gap: 8px;
+    flex-wrap: wrap;
+  }
+}
+
+.timeline-stage-cards .stage-card {
+  position: relative;
+  .bulk-check {
+    position: absolute;
+    left: 12px;
+    top: 12px;
+    z-index: 2;
+  }
+}
+
+.workflow-timeline .timeline-step .label {
+  letter-spacing: 0.5px;
 }

+ 286 - 32
src/app/pages/admin/system-settings/system-settings.ts

@@ -1,6 +1,10 @@
 import { Component, OnInit, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
+<<<<<<< HEAD
 import * as echarts from 'echarts';
 import { CommonModule } from '@angular/common';
+=======
+import { CommonModule, NgIf, NgForOf } from '@angular/common';
+>>>>>>> bad0e181d3082e479a9e3e3e6feaccaa13118175
 import { RouterModule } from '@angular/router';
 import { FormsModule } from '@angular/forms';
 import { SettingDialogComponent } from './setting-dialog/setting-dialog'; // @ts-ignore: Component used in code but not in template
@@ -8,6 +12,11 @@ import { MatDialog } from '@angular/material/dialog';
 import { MatSelectModule } from '@angular/material/select';
 import { MatFormFieldModule } from '@angular/material/form-field';
 import { MatSlideToggleModule } from '@angular/material/slide-toggle';
+import { MatDialogModule } from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { MatInputModule } from '@angular/material/input';
+import { MatMenuModule } from '@angular/material/menu';
+import { MatCheckboxModule } from '@angular/material/checkbox';
 
 // 定义工作流阶段接口
 interface WorkflowStage {
@@ -24,6 +33,8 @@ interface SOPTemplate {
   name: string;
   description: string;
   steps: string[];
+  // 新增:模板分类,供对话框使用
+  category?: string;
   isActive: boolean;
 }
 
@@ -32,8 +43,9 @@ interface PricingRule {
   id: string;
   name: string;
   description: string;
-  condition: string;
-  price: number;
+  // 调整为与对话框一致的字段
+  formula: string;
+  category: string;
   isActive: boolean;
 }
 
@@ -53,11 +65,18 @@ interface PerformanceRule {
   standalone: true,
   imports: [
     CommonModule,
+    NgIf,
+    NgForOf,
     RouterModule,
     FormsModule,
     MatFormFieldModule,
     MatSelectModule,
-    MatSlideToggleModule
+    MatSlideToggleModule,
+    MatDialogModule,
+    MatButtonModule,
+    MatInputModule,
+    MatMenuModule,
+    MatCheckboxModule
   ],
   templateUrl: './system-settings.html'
 })
@@ -83,8 +102,19 @@ export class SystemSettings implements OnInit {
   filteredPricingRules: PricingRule[] = [];
   filteredPerformanceRules: PerformanceRule[] = [];
   
+  // 核心阶段(用于横向时间轴显示 1~4 阶段)
+  coreWorkflowStages: WorkflowStage[] = [];
+  
   // 常用指标
   performanceMetrics = ['项目完成率', '客户满意度', '按时交付率', '产值'];
+  // 新增:分类选项
+  sopCategories: string[] = ['客户接入', '项目开发', '运维保障', '质量流程'];
+  pricingCategories: string[] = ['设计', '开发', '服务', '其他'];
+  
+  // 批量模式状态与选择集(用于模板绑定)
+  isBulkMode: boolean = false;
+  bulkSelectedIds: Set<string> = new Set<string>();
+  get bulkSelectedCount(): number { return this.bulkSelectedIds.size; }
   
   // 图表实例引用
   @ViewChild('dataUsageChart') private dataUsageChart!: ElementRef;
@@ -216,6 +246,10 @@ export class SystemSettings implements OnInit {
       }
     ];
     this.onWorkflowSearch();
+    // 计算核心阶段(1~4)用于时间轴
+    this.coreWorkflowStages = this.workflowStages
+      .filter(s => s.order >= 1 && s.order <= 4)
+      .sort((a, b) => a.order - b.order);
   }
   
   // 加载SOP模板数据
@@ -233,6 +267,7 @@ export class SystemSettings implements OnInit {
           '客户信息录入系统',
           '启动项目'
         ],
+        category: '客户接入',
         isActive: true
       },
       {
@@ -249,20 +284,21 @@ export class SystemSettings implements OnInit {
           '用户验收测试',
           '上线部署'
         ],
+        category: '项目开发',
         isActive: true
       },
       {
         id: '3',
-        name: '问题处理流程',
-        description: '处理客户反馈问题的标准流程',
+        name: '上线运维流程',
+        description: '项目上线后的运维与监控流程',
         steps: [
-          '问题记录与分类',
-          '问题评估与优先级确定',
-          '问题分析与解决',
-          '验证解决方案',
-          '更新文档',
-          '反馈给客户'
+          '上线准备',
+          '监控配置',
+          '告警设置',
+          '备份策略',
+          '应急预案'
         ],
+        category: '运维保障',
         isActive: true
       }
     ];
@@ -274,26 +310,26 @@ export class SystemSettings implements OnInit {
     this.pricingRules = [
       {
         id: '1',
-        name: '基础开发服务',
-        description: '标准软件开发服务报价',
-        condition: '基础功能开发',
-        price: 12000,
+        name: '基础设计报价',
+        description: '按面积与复杂度计算设计报价',
+        category: '设计',
+        formula: '基础费 + 面积 * 设计单价 * 复杂度系数',
         isActive: true
       },
       {
         id: '2',
-        name: '高级开发服务',
-        description: '包含复杂功能的开发服务报价',
-        condition: '高级功能开发',
-        price: 25000,
+        name: '开发功能报价',
+        description: '按功能点与工期评估开发报价',
+        category: '开发',
+        formula: '功能点数 * 功能单价 + 预估工期 * 人日单价',
         isActive: true
       },
       {
         id: '3',
-        name: '维护服务',
-        description: '系统上线后的维护服务报价',
-        condition: '系统维护',
-        price: 5000,
+        name: '维保服务报价',
+        description: '按服务等级与响应时间计算报价',
+        category: '服务',
+        formula: '服务等级系数 * 基础费 + 响应时间系数 * 附加费',
         isActive: true
       }
     ];
@@ -342,26 +378,243 @@ export class SystemSettings implements OnInit {
   
   // 打开工作流阶段对话框
   openWorkflowDialog(stage?: WorkflowStage): void {
-    // 打开对话框的逻辑
-    console.log('打开工作流阶段对话框', stage);
+    const dialogRef = this.dialog.open(SettingDialogComponent, {
+      width: '720px',
+      data: {
+        type: 'workflow',
+        item: stage ? { ...stage } : {
+          id: '',
+          name: '',
+          order: this.workflowStages.length + 1,
+          description: '',
+          isActive: true
+        }
+      }
+    });
+
+    dialogRef.afterClosed().subscribe((result: WorkflowStage | undefined) => {
+      if (result) {
+        this.saveSetting('workflow', result);
+      }
+    });
   }
   
   // 打开SOP模板对话框
   openSOPDialog(template?: SOPTemplate): void {
-    // 打开对话框的逻辑
-    console.log('打开SOP模板对话框', template);
+    const dialogRef = this.dialog.open(SettingDialogComponent, {
+      width: '880px',
+      data: {
+        type: 'sop',
+        categories: this.sopCategories,
+        item: template ? { ...template } : {
+          id: '',
+          name: '',
+          description: '',
+          steps: [],
+          category: this.sopCategories[0],
+          isActive: true
+        }
+      }
+    });
+
+    dialogRef.afterClosed().subscribe((result: SOPTemplate | undefined) => {
+      if (result) {
+        this.saveSetting('sop', result);
+      }
+    });
   }
   
   // 打开报价规则对话框
   openPricingDialog(rule?: PricingRule): void {
-    // 打开对话框的逻辑
-    console.log('打开报价规则对话框', rule);
+    const dialogRef = this.dialog.open(SettingDialogComponent, {
+      width: '720px',
+      data: {
+        type: 'pricing',
+        categories: this.pricingCategories,
+        item: rule ? { ...rule } : {
+          id: '',
+          name: '',
+          description: '',
+          category: this.pricingCategories[0],
+          formula: '',
+          isActive: true
+        }
+      }
+    });
+
+    dialogRef.afterClosed().subscribe((result: PricingRule | undefined) => {
+      if (result) {
+        this.saveSetting('pricing', result);
+      }
+    });
   }
   
   // 打开绩效规则对话框
   openPerformanceDialog(rule?: PerformanceRule): void {
-    // 打开对话框的逻辑
-    console.log('打开绩效规则对话框', rule);
+    const dialogRef = this.dialog.open(SettingDialogComponent, {
+      width: '720px',
+      data: {
+        type: 'performance',
+        metrics: this.performanceMetrics,
+        item: rule ? { ...rule } : {
+          id: '',
+          name: '',
+          description: '',
+          metric: this.performanceMetrics[0],
+          threshold: 80,
+          reward: '基础绩效 * 1.2',
+          isActive: true
+        }
+      }
+    });
+
+    dialogRef.afterClosed().subscribe((result: PerformanceRule | undefined) => {
+      if (result) {
+        this.saveSetting('performance', result);
+      }
+    });
+  }
+  
+  // 顶部批量操作菜单入口
+  onBulkAction(): void {
+    this.enterBulkMode();
+  }
+  
+  // 进入/退出批量模式
+  enterBulkMode(): void {
+    this.isBulkMode = true;
+    this.bulkSelectedIds.clear();
+  }
+  exitBulkMode(): void {
+    this.isBulkMode = false;
+    this.bulkSelectedIds.clear();
+  }
+
+  // 选择与全选
+  toggleSelectWorkflow(id: string, checked: boolean): void {
+    if (checked) {
+      this.bulkSelectedIds.add(id);
+    } else {
+      this.bulkSelectedIds.delete(id);
+    }
+  }
+  isSelectedWorkflow(id: string): boolean {
+    return this.bulkSelectedIds.has(id);
+  }
+  selectAllVisibleWorkflow(): void {
+    // 当前 UI 显示为核心阶段 1~4
+    this.coreWorkflowStages.forEach(s => this.bulkSelectedIds.add(s.id));
+  }
+  clearSelectionWorkflow(): void {
+    this.bulkSelectedIds.clear();
+  }
+
+  // 批量启用/禁用
+  bulkEnableWorkflow(enable: boolean): void {
+    if (this.bulkSelectedIds.size === 0) return;
+    const set = this.bulkSelectedIds;
+    this.workflowStages = this.workflowStages.map(s =>
+      set.has(s.id) ? { ...s, isActive: enable } : s
+    );
+    this.onWorkflowSearch();
+    this.recalcCoreWorkflowStages();
+    console.log(`批量${enable ? '启用' : '禁用'}完成:${set.size} 项`);
+  }
+
+  // 批量删除
+  bulkDeleteWorkflow(): void {
+    if (this.bulkSelectedIds.size === 0) return;
+    const names = this.workflowStages.filter(s => this.bulkSelectedIds.has(s.id)).map(s => s.name);
+    const preview = names.slice(0, 5).join('、') + (names.length > 5 ? ` 等 ${names.length} 项` : '');
+    if (confirm(`确认删除以下阶段?\n${preview}`)) {
+      this.workflowStages = this.workflowStages.filter(s => !this.bulkSelectedIds.has(s.id));
+      this.onWorkflowSearch();
+      this.recalcCoreWorkflowStages();
+      this.clearSelectionWorkflow();
+    }
+  }
+
+  // 批量复制
+  bulkCopyWorkflow(): void {
+    if (this.bulkSelectedIds.size === 0) return;
+    const selected = this.workflowStages.filter(s => this.bulkSelectedIds.has(s.id)).sort((a,b)=>a.order-b.order);
+    const maxId = this.workflowStages.reduce((m, s) => Math.max(m, parseInt(s.id, 10) || 0), 0);
+    let nextId = maxId + 1;
+    const maxOrder = this.workflowStages.reduce((m, s) => Math.max(m, s.order), 0);
+    let nextOrder = maxOrder + 1;
+    const clones: WorkflowStage[] = selected.map(s => ({
+      ...s,
+      id: String(nextId++),
+      name: `${s.name} (副本)`,
+      order: nextOrder++,
+    }));
+    this.workflowStages = [...this.workflowStages, ...clones];
+    this.onWorkflowSearch();
+    this.recalcCoreWorkflowStages();
+    console.log(`已复制 ${clones.length} 项`);
+  }
+
+  // 批量排序:输入起始序号,按当前显示顺序依次编号
+  bulkSortWorkflow(): void {
+    if (this.bulkSelectedIds.size === 0) return;
+    const selected = this.workflowStages.filter(s => this.bulkSelectedIds.has(s.id)).sort((a,b)=>a.order-b.order);
+    const minOrder = selected.reduce((m,s)=>Math.min(m,s.order), selected[0].order);
+    const input = prompt(`请输入起始序号(默认 ${minOrder})`, String(minOrder));
+    const start = input ? parseInt(input, 10) : minOrder;
+    if (!Number.isFinite(start)) return;
+    let current = start;
+    const idSet = new Set(selected.map(s=>s.id));
+    this.workflowStages = this.workflowStages.map(s => {
+      if (idSet.has(s.id)) {
+        return { ...s, order: current++ };
+      }
+      return s;
+    });
+    // 二次排序保证整体序号稳定
+    this.workflowStages.sort((a,b)=>a.order-b.order);
+    this.onWorkflowSearch();
+    this.recalcCoreWorkflowStages();
+  }
+
+  // 批量导出 JSON
+  bulkExportWorkflow(): void {
+    if (this.bulkSelectedIds.size === 0) return;
+    const data = this.workflowStages.filter(s => this.bulkSelectedIds.has(s.id));
+    const json = JSON.stringify(data, null, 2);
+    const blob = new Blob([json], { type: 'application/json' });
+    const url = URL.createObjectURL(blob);
+    const a = document.createElement('a');
+    a.href = url;
+    a.download = 'workflow-stages-export.json';
+    document.body.appendChild(a);
+    a.click();
+    document.body.removeChild(a);
+    URL.revokeObjectURL(url);
+  }
+
+  // 计算核心阶段(1~4)
+  recalcCoreWorkflowStages(): void {
+    this.coreWorkflowStages = this.workflowStages
+      .filter(s => s.order >= 1 && s.order <= 4)
+      .sort((a, b) => a.order - b.order);
+  }
+
+  // 名称缩写:用于时间轴标签
+  getStageAbbr(name: string): string {
+    const map: Record<string, string> = {
+      '需求沟通': '需求',
+      '方案设计': '方设',
+      '开发实现': '开实',
+      '测试验证': '测验',
+      '部署上线': '部署',
+      '运维维护': '运维'
+    };
+    if (map[name]) return map[name];
+    const ascii = name.match(/[A-Za-z]+/g);
+    if (ascii && ascii.length) {
+      return ascii.map(s => s[0]).join('').slice(0, 3).toUpperCase();
+    }
+    return name.slice(0, 2);
   }
   
   // 保存设置项
@@ -512,7 +765,8 @@ export class SystemSettings implements OnInit {
       this.filteredPricingRules = this.pricingRules.filter(
         rule => rule.name.toLowerCase().includes(searchTerm) || 
                 rule.description.toLowerCase().includes(searchTerm) ||
-                rule.condition.toLowerCase().includes(searchTerm)
+                rule.formula.toLowerCase().includes(searchTerm) ||
+                rule.category.toLowerCase().includes(searchTerm)
       );
     }
   }

+ 422 - 199
src/app/pages/customer-service/consultation-order/consultation-order.html

@@ -1,222 +1,445 @@
 <div class="consultation-order-container">
-  <!-- 页面标题 -->
-  <div class="page-header">
-    <h1>客户咨询与下单</h1>
-    <p>记录客户需求,快速生成报价,一键创建项目</p>
-  </div>
-
-  <!-- 成功提示 -->
-  <div *ngIf="showSuccessMessage()" class="success-message">
-    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-      <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
-      <polyline points="22 4 12 14.01 9 11.01"></polyline>
-    </svg>
-    <span>表单提交成功!</span>
-  </div>
-
-  <!-- 客户信息区域 -->
-  <section class="customer-info-section">
-    <div class="section-header">
-      <h2>客户信息</h2>
-      <button *ngIf="selectedCustomer()" class="clear-customer-btn" (click)="clearSelectedCustomer()">
-        清除客户信息
-      </button>
-    </div>
-    
-    <!-- 客户搜索 -->
-    <div *ngIf="!selectedCustomer()" class="customer-search">
-      <div class="search-input-group">
-        <input 
-          type="text" 
-          [(ngModel)]="searchKeyword" 
-          (input)="searchCustomer()"
-          placeholder="搜索客户姓名或手机号..."
-          class="search-input"
-          autocomplete="off"
-        />
-        <button class="search-btn">
-          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <circle cx="11" cy="11" r="8"></circle>
-            <path d="m21 21-4.35-4.35"></path>
-          </svg>
-        </button>
+  <!-- 现代化页面头部 -->
+  <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 *ngIf="searchResults().length > 0" class="search-results">
-        <div *ngFor="let customer of searchResults()" class="customer-item" (click)="selectCustomer(customer)">
-          <img [src]="customer.avatar" [alt]="customer.name" class="customer-avatar">
-          <div class="customer-details">
-            <div class="customer-name">{{ customer.name }}</div>
-            <div class="customer-phone">{{ customer.phone }}</div>
-            <div class="customer-tags">
-              <span *ngIf="customer.customerType" class="tag">{{ customer.customerType }}</span>
-              <span *ngIf="customer.source" class="tag source">{{ customer.source }}</span>
-            </div>
-          </div>
-        </div>
+      <div class="header-text">
+        <h1>客户咨询与下单</h1>
+        <p>记录客户需求,快速生成报价,一键创建项目</p>
       </div>
     </div>
     
-    <!-- 客户信息表单 -->
-    <form [formGroup]="customerForm" class="customer-form">
-      <div class="form-row">
-        <div class="form-group">
-          <label for="name">客户姓名 <span class="required">*</span></label>
-          <input type="text" id="name" formControlName="name" placeholder="请输入客户姓名">
-        </div>
-        <div class="form-group">
-          <label for="phone">手机号码 <span class="required">*</span></label>
-          <input type="tel" id="phone" formControlName="phone" placeholder="请输入手机号码">
-        </div>
-        <div class="form-group">
-          <label for="wechat">微信</label>
-          <input type="text" id="wechat" formControlName="wechat" placeholder="请输入微信账号">
-        </div>
-      </div>
-      <div class="form-row">
-        <div class="form-group">
-          <label for="customerType">客户类型</label>
-          <select id="customerType" formControlName="customerType">
-            <option value="新客户">新客户</option>
-            <option value="老客户">老客户</option>
-            <option value="VIP客户">VIP客户</option>
-          </select>
-        </div>
-        <div class="form-group">
-          <label for="source">来源渠道</label>
-          <input type="text" id="source" formControlName="source" placeholder="如:官网咨询、推荐介绍等">
-        </div>
-      </div>
-      <div class="form-group full-width">
-        <label for="remark">备注</label>
-        <textarea id="remark" formControlName="remark" rows="2" placeholder="请输入其他备注信息"></textarea>
+    <!-- 成功提示 -->
+    <div *ngIf="showSuccessMessage()" class="success-toast">
+      <div class="toast-content">
+        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+          <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
+          <polyline points="22 4 12 14.01 9 11.01"></polyline>
+        </svg>
+        <span>表单提交成功!</span>
       </div>
-    </form>
-  </section>
-
-  <!-- 需求信息区域 -->
-  <section class="requirement-section">
-    <div class="section-header">
-      <h2>需求信息</h2>
     </div>
-    
-    <form [formGroup]="requirementForm" class="requirement-form">
-      <div class="form-row">
-        <div class="form-group">
-          <label for="style">装修风格 <span class="required">*</span></label>
-          <select id="style" formControlName="style">
-            <option value="">请选择装修风格</option>
-            <option *ngFor="let style of styleOptions" [value]="style">{{ style }}</option>
-          </select>
-        </div>
-        <div class="form-group">
-          <label for="budget">预算范围 <span class="required">*</span></label>
-          <select id="budget" formControlName="budget">
-            <option value="">请选择预算范围</option>
-            <option value="5-10万">5-10万</option>
-            <option value="10-20万">10-20万</option>
-            <option value="20-30万">20-30万</option>
-            <option value="30-50万">30-50万</option>
-            <option value="50万以上">50万以上</option>
-          </select>
+  </header>
+
+  <!-- 主要内容区域 -->
+  <main class="main-content">
+
+    <!-- 客户信息卡片 -->
+    <section class="info-card customer-card">
+      <div class="card-header">
+        <div class="header-left">
+          <div class="icon-wrapper customer-icon">
+            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
+              <circle cx="12" cy="7" r="4"></circle>
+            </svg>
+          </div>
+          <div class="header-text">
+            <h2>客户信息</h2>
+            <p>搜索或新建客户档案</p>
+          </div>
         </div>
-        <div class="form-group">
-          <label for="area">房屋面积 <span class="required">*</span></label>
-          <input type="number" id="area" formControlName="area" placeholder="请输入房屋面积(㎡)">
+        <div class="header-actions">
+          <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>
+              <line x1="6" y1="6" x2="18" y2="18"></line>
+            </svg>
+            清除
+          </button>
         </div>
       </div>
-      <div class="form-row">
-        <div class="form-group">
-          <label for="houseType">户型 <span class="required">*</span></label>
-          <select id="houseType" formControlName="houseType">
-            <option value="">请选择户型</option>
-            <option *ngFor="let houseType of houseTypeOptions" [value]="houseType">{{ houseType }}</option>
-          </select>
-        </div>
-        <div class="form-group">
-          <label for="floor">楼层</label>
-          <input type="number" id="floor" formControlName="floor" placeholder="请输入楼层">
-        </div>
-        <div class="form-group">
-          <label for="decorationType">装修类型 <span class="required">*</span></label>
-          <select id="decorationType" formControlName="decorationType">
-            <option value="">请选择装修类型</option>
-            <option *ngFor="let type of decorationTypeOptions" [value]="type">{{ type }}</option>
-          </select>
+      
+      <div class="card-content">
+        <!-- 客户搜索区域 -->
+        <div *ngIf="!selectedCustomer()" 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">
+                <circle cx="11" cy="11" r="8"></circle>
+                <path d="m21 21-4.35-4.35"></path>
+              </svg>
+              <input 
+                type="text" 
+                [(ngModel)]="searchKeyword" 
+                (input)="searchCustomer()"
+                placeholder="搜索客户姓名或手机号..."
+                class="search-input"
+                autocomplete="off"
+              />
+            </div>
+          </div>
+          
+          <!-- 搜索结果 -->
+          <div *ngIf="searchResults().length > 0" class="search-results">
+            <div class="results-header">
+              <span class="results-count">找到 {{ searchResults().length }} 位客户</span>
+            </div>
+            <div class="customer-list">
+              <div *ngFor="let customer of searchResults()" class="customer-item" (click)="selectCustomer(customer)">
+                <div class="customer-avatar">
+                  <img [src]="customer.avatar" [alt]="customer.name" *ngIf="customer.avatar">
+                  <div *ngIf="!customer.avatar" class="avatar-placeholder">
+                    {{ customer.name.charAt(0) }}
+                  </div>
+                </div>
+                <div class="customer-info">
+                  <div class="customer-name">{{ customer.name }}</div>
+                  <div class="customer-phone">{{ customer.phone }}</div>
+                  <div class="customer-tags" *ngIf="customer.customerType || customer.source">
+                    <span *ngIf="customer.customerType" class="tag type-tag">{{ customer.customerType }}</span>
+                    <span *ngIf="customer.source" class="tag source-tag">{{ customer.source }}</span>
+                  </div>
+                </div>
+                <div class="select-indicator">
+                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                    <polyline points="9 18 15 12 9 6"></polyline>
+                  </svg>
+                </div>
+              </div>
+            </div>
+          </div>
         </div>
+        <!-- 客户信息表单 -->
+        <form [formGroup]="customerForm" class="customer-form">
+          <!-- 基本信息组 -->
+          <div class="form-section">
+            <h3 class="section-title">基本信息</h3>
+            <div class="form-grid">
+              <div class="form-field">
+                <label for="name" class="field-label">客户姓名 <span class="required">*</span></label>
+                <input type="text" id="name" formControlName="name" placeholder="请输入客户姓名" class="field-input">
+              </div>
+              <div class="form-field">
+                <label for="phone" class="field-label">手机号码 <span class="required">*</span></label>
+                <input type="tel" id="phone" formControlName="phone" placeholder="请输入手机号码" class="field-input">
+              </div>
+              <div class="form-field">
+                <label for="wechat" class="field-label">微信账号</label>
+                <input type="text" id="wechat" formControlName="wechat" placeholder="请输入微信账号" class="field-input">
+              </div>
+            </div>
+          </div>
+          
+          <!-- 分类信息组 -->
+          <div class="form-section">
+            <h3 class="section-title">分类信息</h3>
+            <div class="form-grid">
+              <div class="form-field">
+                <label for="customerType" class="field-label">客户类型</label>
+                <select id="customerType" formControlName="customerType" class="field-select">
+                  <option value="">请选择客户类型</option>
+                  <option value="新客户">新客户</option>
+                  <option value="老客户">老客户</option>
+                  <option value="VIP客户">VIP客户</option>
+                </select>
+              </div>
+              <div class="form-field">
+                <label for="source" class="field-label">来源渠道</label>
+                <input type="text" id="source" formControlName="source" placeholder="如:官网咨询、推荐介绍等" class="field-input">
+              </div>
+              <div class="form-field">
+                <label for="demandType" class="field-label">需求类型</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>
+                <select id="followUpStatus" formControlName="followUpStatus" class="field-select">
+                  <option value="">请选择跟进状态</option>
+                  <option *ngFor="let status of followUpStatus" [value]="status.value">{{ status.label }}</option>
+                </select>
+              </div>
+            </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
+                    *ngFor="let tag of preferenceTags"
+                    removable
+                    (removed)="removePreferenceTag(tag)"
+                    class="preference-chip"
+                  >
+                    {{ tag }}
+                    <mat-icon matChipRemove>cancel</mat-icon>
+                  </mat-chip>
+                  <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>
-      <div class="form-row">
-        <div class="form-group">
-          <label for="preferredDesigner">意向设计师</label>
-          <input type="text" id="preferredDesigner" formControlName="preferredDesigner" placeholder="请输入意向设计师姓名">
+    </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>
+            </svg>
+          </div>
+          <div class="header-text">
+            <h2>需求信息</h2>
+            <p>详细描述装修需求和偏好</p>
+          </div>
         </div>
       </div>
-      <div class="form-group full-width">
-        <label for="specialRequirements">特殊需求</label>
-        <textarea id="specialRequirements" formControlName="specialRequirements" rows="3" placeholder="请描述特殊需求或关注点"></textarea>
+      
+      <div class="card-content">
+        <form [formGroup]="requirementForm" class="requirement-form">
+          <!-- 基础需求组 -->
+          <div class="form-section">
+            <h3 class="section-title">基础需求</h3>
+            <div class="form-grid">
+              <div class="form-field">
+                <label for="style" class="field-label">装修风格 <span class="required">*</span></label>
+                <select id="style" formControlName="style" class="field-select">
+                  <option value="">请选择装修风格</option>
+                  <option *ngFor="let style of styleOptions" [value]="style">{{ style }}</option>
+                </select>
+              </div>
+              <div class="form-field">
+                <label for="budget" class="field-label">预算范围 <span class="required">*</span></label>
+                <select id="budget" formControlName="budget" class="field-select">
+                  <option value="">请选择预算范围</option>
+                  <option value="5-10万">5-10万</option>
+                  <option value="10-20万">10-20万</option>
+                  <option value="20-30万">20-30万</option>
+                  <option value="30-50万">30-50万</option>
+                  <option value="50万以上">50万以上</option>
+                </select>
+              </div>
+              <div class="form-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>
+            <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>
+                </select>
+              </div>
+              <div class="form-field">
+                <label for="floor" class="field-label">楼层</label>
+                <input type="number" id="floor" formControlName="floor" placeholder="请输入楼层" 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>
+              </div>
+            </div>
+          </div>
+          
+          <!-- 其他需求组 -->
+          <div class="form-section">
+            <h3 class="section-title">其他需求</h3>
+            <div class="form-grid">
+              <div class="form-field">
+                <label for="preferredDesigner" class="field-label">意向设计师</label>
+                <input type="text" id="preferredDesigner" formControlName="preferredDesigner" placeholder="请输入意向设计师姓名" class="field-input">
+              </div>
+            </div>
+            <div class="form-field full-width">
+              <label for="specialRequirements" class="field-label">特殊需求</label>
+              <textarea id="specialRequirements" formControlName="specialRequirements" rows="4" placeholder="请详细描述特殊需求或关注点" class="field-textarea"></textarea>
+            </div>
+          </div>
+        </form>
       </div>
-    </form>
-  </section>
+    </section>
 
-  <!-- 报价与案例匹配区域 -->
-  <section class="price-match-section">
-    <div class="price-info">
-      <div class="price-label">预估报价范围</div>
-      <div class="price-value">{{ estimatedPriceRange() || '请填写需求信息以获取报价' }}</div>
-    </div>
-    
-    <!-- 匹配案例 -->
-    <div class="matched-cases" *ngIf="matchedCases().length > 0">
-      <h3>推荐案例(点击选择)</h3>
-      <div class="cases-grid">
-        <div *ngFor="let caseItem of matchedCases()" class="case-card" (click)="selectReferenceCase(caseItem)">
-          <img [src]="caseItem.imageUrl" [alt]="caseItem.name" class="case-image">
-          <div class="case-info">
-            <h4 class="case-name">{{ caseItem.name }}</h4>
-            <div class="case-details">
-              <span class="case-designer">{{ caseItem.designer }}</span>
-              <span class="case-area">{{ caseItem.area }}</span>
-            </div>
-            <div class="similarity-badge">匹配度 {{ caseItem.similarity }}%</div>
+    <!-- 报价与案例匹配卡片 -->
+    <section class="info-card price-match-card">
+      <div class="card-header">
+        <div class="header-left">
+          <div class="icon-wrapper price-icon">
+            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <rect x="2" y="6" width="20" height="12" rx="2"></rect>
+              <path d="M12 6v4"></path>
+              <path d="M2 10h20"></path>
+            </svg>
+          </div>
+          <div class="header-text">
+            <h2>报价与案例匹配</h2>
+            <p>智能匹配相似案例和预估报价</p>
           </div>
         </div>
       </div>
-    </div>
-    
-    <!-- 已选参考案例 -->
-    <div *ngIf="requirementForm.get('referenceCases')?.value.length > 0" class="selected-cases">
-      <h3>已选参考案例</h3>
-      <div class="selected-cases-list">
-        <div *ngFor="let caseId of requirementForm.get('referenceCases')?.value" class="selected-case-item">
-          <span>案例 #{{ caseId }}</span>
-          <button class="remove-case-btn" (click)="removeReferenceCase(caseId)">
-            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-              <line x1="18" y1="6" x2="6" y2="18"></line>
-              <line x1="6" y1="6" x2="18" y2="18"></line>
-            </svg>
-          </button>
+      
+      <div class="card-content">
+        <!-- 预估报价区域 -->
+        <div class="price-section">
+          <div class="price-card">
+            <div class="price-header">
+              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <circle cx="12" cy="12" r="10"></circle>
+                <path d="M12 6v6l4 2"></path>
+              </svg>
+              <span>预估报价范围</span>
+            </div>
+            <div class="price-value">
+              {{ estimatedPriceRange() || '请填写需求信息以获取报价' }}
+            </div>
+          </div>
+        </div>
+        
+        <!-- 匹配案例区域 -->
+        <div class="cases-section" *ngIf="matchedCases().length > 0">
+          <div class="section-header">
+            <h3>推荐案例</h3>
+            <span class="section-subtitle">点击选择参考案例</span>
+          </div>
+          <div class="cases-grid">
+            <div *ngFor="let caseItem of matchedCases()" class="case-card" (click)="selectReferenceCase(caseItem)">
+              <div class="case-image-wrapper">
+                <img [src]="caseItem.imageUrl" [alt]="caseItem.name" class="case-image">
+                <div class="similarity-badge">{{ caseItem.similarity }}%</div>
+              </div>
+              <div class="case-content">
+                <h4 class="case-name">{{ caseItem.name }}</h4>
+                <div class="case-meta">
+                  <span class="case-designer">
+                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                      <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
+                      <circle cx="12" cy="7" r="4"></circle>
+                    </svg>
+                    {{ caseItem.designer }}
+                  </span>
+                  <span class="case-area">
+                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                      <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
+                    </svg>
+                    {{ caseItem.area }}
+                  </span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 已选案例区域 -->
+        <div *ngIf="requirementForm.get('referenceCases')?.value.length > 0" class="selected-cases-section">
+          <div class="section-header">
+            <h3>已选参考案例</h3>
+            <span class="section-subtitle">{{ requirementForm.get('referenceCases')?.value.length }} 个案例</span>
+          </div>
+          <div class="selected-cases-list">
+            <div *ngFor="let caseId of requirementForm.get('referenceCases')?.value" class="selected-case-item">
+              <div class="case-info">
+                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <path d="M9 11l3 3 8-8"></path>
+                  <path d="M21 12c0 4.97-4.03 9-9 9s-9-4.03-9-9 4.03-9 9-9c1.51 0 2.93.37 4.18 1.03"></path>
+                </svg>
+                <span>案例 #{{ caseId }}</span>
+              </div>
+              <button class="remove-case-btn" (click)="removeReferenceCase(caseId)">
+                <svg width="14" height="14" 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>
         </div>
       </div>
-    </div>
-  </section>
+    </section>
 
-  <!-- 操作按钮区域 -->
-  <section class="action-section">
-    <button 
-      class="primary-btn" 
-      (click)="submitForm()"
-      [disabled]="isSubmitting() || !requirementForm.valid || !customerForm.valid"
-    >
-      <span *ngIf="!isSubmitting()">创建项目</span>
-      <span *ngIf="isSubmitting()">提交中...</span>
-    </button>
-    <button 
-      class="secondary-btn" 
-      (click)="createProjectGroup()"
-      [disabled]="isSubmitting() || !requirementForm.valid || !customerForm.valid"
-    >
-      一键拉群
-    </button>
-  </section>
+    <!-- 操作按钮区域 -->
+    <section class="action-section">
+      <div class="action-buttons">
+        <button 
+          class="btn-primary btn-lg" 
+          (click)="submitForm()"
+          [disabled]="isSubmitting() || !requirementForm.valid || !customerForm.valid"
+        >
+          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" *ngIf="!isSubmitting()">
+            <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>
+          </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>

文件差异内容过多而无法显示
+ 823 - 94
src/app/pages/customer-service/consultation-order/consultation-order.scss


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

@@ -1,8 +1,15 @@
-import { Component, signal } from '@angular/core';
+import { Component, signal, Inject } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { RouterModule } from '@angular/router';
 import { ProjectService } from '../../../services/project.service';
+import { MatChipInputEvent } from '@angular/material/chips';
+import { COMMA, ENTER } from '@angular/cdk/keycodes';
+import { MatChipsModule } from '@angular/material/chips';
+import { MatIconModule } from '@angular/material/icon';
+import { MatSnackBar } from '@angular/material/snack-bar';
+import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { ProjectGroupDialog } from './project-group-dialog.component';
 
 // 定义客户信息接口
 interface Customer {
@@ -14,6 +21,10 @@ interface Customer {
   customerType?: string; // 新客户/老客户/VIP客户
   source?: string; // 来源渠道
   remark?: string;
+  // 客户标签信息
+  demandType?: string;
+  preferenceTags?: string[];
+  followUpStatus?: string;
 }
 
 // 定义需求信息接口
@@ -29,10 +40,35 @@ interface Requirement {
   referenceCases?: string[];
 }
 
+// 标签选项定义
+const DEMAND_TYPES = [
+  { value: 'price', label: '价格敏感' },
+  { value: 'quality', label: '质量敏感' },
+  { value: 'comprehensive', label: '综合要求' }
+];
+
+const FOLLOW_UP_STATUS = [
+  { value: 'quotation', label: '待报价' },
+  { value: 'confirm', label: '待确认需求' },
+  { value: 'lost', label: '已失联' }
+];
+
+// 预设的偏好标签选项
+const PREFERENCE_TAG_OPTIONS = [
+  // 颜色偏好
+  '柔和色系', '明亮色系', '深色系', '中性色系',
+  // 材质偏好
+  '环保材料', '实木', '大理石', '瓷砖', '地板', '墙纸',
+  // 风格偏好
+  '现代简约', '北欧风格', '中式风格', '美式风格', '工业风',
+  // 其他偏好
+  '智能家电', '收纳空间', '开放式厨房', '大窗户'
+];
+
 @Component({
   selector: 'app-consultation-order',
   standalone: true,
-  imports: [CommonModule, FormsModule, ReactiveFormsModule, RouterModule],
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, RouterModule, MatChipsModule, MatIconModule],
   templateUrl: './consultation-order.html',
   styleUrls: ['./consultation-order.scss', '../customer-service-styles.scss']
 })
@@ -72,9 +108,19 @@ export class ConsultationOrder {
     '全包', '半包', '清包', '旧房翻新', '局部改造'
   ];
 
+  // 标签系统
+  demandTypes = DEMAND_TYPES;
+  followUpStatus = FOLLOW_UP_STATUS;
+  preferenceTagOptions = PREFERENCE_TAG_OPTIONS;
+  addOnBlur = true;
+  readonly separatorKeysCodes = [ENTER, COMMA] as const;
+  preferenceTags: string[] = [];
+
   constructor(
     private fb: FormBuilder,
-    private projectService: ProjectService
+    private projectService: ProjectService,
+    private snackBar: MatSnackBar,
+    private dialog: MatDialog
   ) {
     // 初始化需求表单
     this.requirementForm = this.fb.group({
@@ -96,7 +142,9 @@ export class ConsultationOrder {
       wechat: [''],
       customerType: ['新客户'],
       source: [''],
-      remark: ['']
+      remark: [''],
+      demandType: [''],
+      followUpStatus: ['']
     });
 
     // 监听表单值变化,自动计算报价和匹配案例
@@ -106,6 +154,36 @@ export class ConsultationOrder {
     });
   }
 
+  // 添加偏好标签
+  addPreferenceTag(event: MatChipInputEvent): void {
+    const value = (event.value || '').trim();
+
+    // 添加标签,如果它不是空的,并且不在已有标签中
+    if (value && !this.preferenceTags.includes(value)) {
+      this.preferenceTags.push(value);
+    }
+
+    // 清空输入框
+    if (event.input) {
+      event.input.value = '';
+    }
+  }
+
+  // 删除偏好标签
+  removePreferenceTag(tag: string): void {
+    const index = this.preferenceTags.indexOf(tag);
+    if (index >= 0) {
+      this.preferenceTags.splice(index, 1);
+    }
+  }
+
+  // 从预设选项中添加标签
+  addFromPreset(tag: string): void {
+    if (!this.preferenceTags.includes(tag)) {
+      this.preferenceTags.push(tag);
+    }
+  }
+
   // 搜索客户
   searchCustomer() {
     if (this.searchKeyword().length >= 2) {
@@ -265,10 +343,89 @@ export class ConsultationOrder {
     }
   }
 
-  // 一键拉群
+  // 创建项目
+  createProject() {
+    if (!this.selectedCustomer()) {
+      this.snackBar.open('请先选择客户', '关闭', { duration: 3000 });
+      return;
+    }
+
+    if (this.requirementForm.invalid) {
+      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.projectService.createProject(projectData).subscribe(
+      (response: any) => {
+        this.snackBar.open('项目创建成功', '关闭', { duration: 3000 });
+      },
+      (error: any) => {
+        this.snackBar.open('项目创建失败,请重试', '关闭', { duration: 3000 });
+      }
+    );
+  }
+
+  /**
+   * 创建项目群
+   */
   createProjectGroup() {
-    // 模拟拉群功能
-    console.log('创建项目群');
-    alert('项目群已创建,并邀请了相应技术组长!');
+    // 先检查是否选择了客户
+    if (!this.selectedCustomer()) {
+      this.snackBar.open('请先选择客户', '确定', { duration: 2000 });
+      return;
+    }
+    
+    // 显示弹窗
+    this.dialog.open(ProjectGroupDialog, {
+      width: '500px',
+      data: {
+        selectedCustomer: this.selectedCustomer(),
+        demandType: this.customerForm.get('demandType')?.value,
+        preferenceTags: this.preferenceTags,
+        followUpStatus: this.customerForm.get('followUpStatus')?.value
+      }
+    }).afterClosed().subscribe(result => {
+      if (result && result.confirm) {
+
+        const tags = {
+          demandType: result.demandType,
+          preferenceTags: result.preferenceTags,
+          followUpStatus: result.followUpStatus
+        };
+
+        this.isSubmitting.set(true);
+        this.projectService.createProjectGroup({
+          customerId: result.customerId,
+          customerName: result.customerName,
+          tags
+        }).subscribe(
+          (response: any) => {
+            if (response.success) {
+              this.snackBar.open(`项目群创建成功,群ID: ${response.groupId}`, '确定', { duration: 3000 });
+            } else {
+              this.snackBar.open('项目群创建失败,请稍后重试', '确定', { duration: 2000 });
+            }
+            this.isSubmitting.set(false);
+          },
+          (error: any) => {
+            this.snackBar.open('创建项目群时出错,请稍后重试', '确定', { duration: 2000 });
+            this.isSubmitting.set(false);
+          }
+        );
+      }
+    });
   }
 }

+ 155 - 0
src/app/pages/customer-service/consultation-order/project-group-dialog.component.ts

@@ -0,0 +1,155 @@
+import { Component, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { CommonModule } from '@angular/common';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule } from '@angular/material/dialog';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { FormsModule } from '@angular/forms';
+import { MatChipGrid, MatChipsModule } from '@angular/material/chips';
+import { MatIconModule } from '@angular/material/icon';
+
+@Component({
+  selector: 'app-project-group-dialog',
+  standalone: true,
+  imports: [
+    CommonModule,
+    MatButtonModule,
+    MatDialogModule,
+    MatFormFieldModule,
+    MatInputModule,
+    FormsModule,
+    MatChipsModule,
+    MatIconModule
+  ],
+  template: `
+    <h2 mat-dialog-title>创建项目群</h2>
+    <mat-dialog-content>
+      <div class="dialog-content">
+        <div class="customer-info" *ngIf="data.selectedCustomer">
+          <h3>客户信息</h3>
+          <p><strong>姓名:</strong> {{ data.selectedCustomer.name }}</p>
+          <p><strong>电话:</strong> {{ data.selectedCustomer.phone }}</p>
+          <p><strong>地址:</strong> {{ data.selectedCustomer.address }}</p>
+        </div>
+        <div class="no-customer" *ngIf="!data.selectedCustomer">
+          <p class="warning-message">请先选择客户</p>
+        </div>
+        
+        <div class="group-details" *ngIf="data.selectedCustomer">
+          <h3>项目群信息</h3>
+          <div class="form-group">
+            <label>需求类型</label>
+            <p>{{ data.demandType || '未设置' }}</p>
+          </div>
+          
+          <div class="form-group" *ngIf="data.preferenceTags && data.preferenceTags.length > 0">
+            <label>偏好标签</label>
+            <mat-chip-grid>
+              <mat-chip *ngFor="let tag of data.preferenceTags">{{ tag }}</mat-chip>
+            </mat-chip-grid>
+          </div>
+          
+          <div class="form-group">
+            <label>跟进状态</label>
+            <p>{{ data.followUpStatus || '未设置' }}</p>
+          </div>
+          
+          <div class="form-group">
+            <label>备注</label>
+            <textarea [(ngModel)]="remarks" rows="3" placeholder="输入群聊备注..."></textarea>
+          </div>
+        </div>
+      </div>
+    </mat-dialog-content>
+    <mat-dialog-actions align="end">
+      <button mat-button (click)="onCancel()">取消</button>
+      <button mat-button color="primary" (click)="onConfirm()" [disabled]="!data.selectedCustomer">
+        确认创建
+      </button>
+    </mat-dialog-actions>
+  `,
+  styles: [`
+    .dialog-content {
+      padding: 10px 0;
+    }
+    
+    h3 {
+      margin: 16px 0 12px;
+      font-size: 16px;
+      font-weight: 600;
+    }
+    
+    .customer-info {
+      padding: 12px;
+      background-color: #f5f5f5;
+      border-radius: 8px;
+      margin-bottom: 16px;
+    }
+    
+    .no-customer {
+      padding: 20px;
+      text-align: center;
+    }
+    
+    .warning-message {
+      color: #f44336;
+      font-size: 16px;
+      margin: 0;
+    }
+    
+    .form-group {
+      margin-bottom: 16px;
+    }
+    
+    label {
+      display: block;
+      margin-bottom: 4px;
+      font-weight: 500;
+      color: rgba(0, 0, 0, 0.87);
+    }
+    
+    p {
+      margin: 0;
+      color: rgba(0, 0, 0, 0.6);
+    }
+    
+    textarea {
+      width: 100%;
+      padding: 8px;
+      border: 1px solid #e0e0e0;
+      border-radius: 4px;
+      resize: vertical;
+      font-family: inherit;
+      font-size: 14px;
+    }
+    
+    mat-chip {
+      margin: 4px;
+    }
+  `]
+})
+export class ProjectGroupDialog {
+  remarks: string = '';
+
+  constructor(
+    public dialogRef: MatDialogRef<ProjectGroupDialog>,
+    @Inject(MAT_DIALOG_DATA) public data: any
+  ) {}
+
+  onCancel(): void {
+    this.dialogRef.close();
+  }
+
+  onConfirm(): void {
+    this.dialogRef.close({
+      confirm: true,
+      customerId: this.data.selectedCustomer?.id,
+      customerName: this.data.selectedCustomer?.name,
+      demandType: this.data.demandType,
+      preferenceTags: this.data.preferenceTags,
+      followUpStatus: this.data.followUpStatus,
+      remarks: this.remarks
+    });
+  }
+}

+ 4 - 4
src/app/pages/customer-service/customer-service-layout/customer-service-layout.scss

@@ -24,10 +24,10 @@ $transition: all 0.3s ease;
   align-items: center;
   justify-content: space-between;
   padding: 0 24px;
-  height: 64px;
+  height: 72px; // 扩大导航栏高度
   background-color: $background-primary;
   border-bottom: 1px solid $border-color;
-  box-shadow: $shadow-sm;
+  box-shadow: $shadow-md; // 增加阴影效果
   position: sticky;
   top: 0;
   z-index: 1000;
@@ -53,8 +53,8 @@ $transition: all 0.3s ease;
   }
 
   .app-title {
-    font-size: 20px;
-    font-weight: 600;
+    font-size: 24px; // 放大标题字体
+    font-weight: 700;
     color: $text-primary;
   }
 

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

@@ -0,0 +1,31 @@
+import { Routes } from '@angular/router';
+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 = [
+  {
+    path: '',
+    component: Dashboard,
+    children: [
+      {
+        path: 'consultation-list',
+        component: ConsultationListComponent
+      },
+      {
+        path: 'assignment-list',
+        component: AssignmentListComponent
+      },
+      {
+        path: 'exception-list',
+        component: ExceptionListComponent
+      },
+      {
+        path: 'revenue-detail',
+        component: RevenueDetailComponent
+      }
+    ]
+  }
+];

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

@@ -82,7 +82,7 @@
       <div style="display: flex; gap: 12px; align-items: center;">
         <button 
           class="btn-primary"
-          (click)="addUrgentTask()"
+          (click)="showTaskForm()"
           style="font-size: 14px; padding: 6px 16px;"
         >
           添加紧急事项
@@ -135,6 +135,179 @@
     </div>
   </section>
 
+  <!-- iOS风格的添加紧急事项面板 -->
+  <div class="ios-modal-overlay" *ngIf="isTaskFormVisible()" (click)="hideTaskForm()">
+    <div class="ios-panel" (click)="$event.stopPropagation()">
+      <div class="ios-panel-header">
+        <h3>添加紧急事项</h3>
+        <button class="ios-close-button" (click)="hideTaskForm()">
+          <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <line x1="18" y1="6" x2="6" y2="18"></line>
+            <line x1="6" y1="6" x2="18" y2="18"></line>
+          </svg>
+        </button>
+      </div>
+      
+      <div class="ios-panel-content">
+        <form (ngSubmit)="handleAddTaskSubmit()">
+          <div class="form-group">
+            <label for="taskTitle">任务标题 *</label>
+            <input 
+              type="text" 
+              id="taskTitle" 
+              [(ngModel)]="newTask.title" 
+              [ngModelOptions]="{standalone: true}"
+              placeholder="请输入任务标题"
+              required
+              class="ios-input"
+            >
+          </div>
+          
+          <div class="form-group">
+            <label for="projectName">项目名称 *</label>
+            <input 
+              type="text" 
+              id="projectName" 
+              [(ngModel)]="newTask.projectName" 
+              [ngModelOptions]="{standalone: true}"
+              placeholder="请输入项目名称"
+              required
+              class="ios-input"
+            >
+          </div>
+          
+          <div class="form-group">
+            <label for="projectStage">项目阶段 *</label>
+            <select 
+              id="projectStage" 
+              [(ngModel)]="newTask.stage" 
+              [ngModelOptions]="{standalone: true}"
+              required
+              class="ios-select"
+            >
+              <option value="前期沟通">前期沟通</option>
+              <option value="建模">建模</option>
+              <option value="软装">软装</option>
+              <option value="渲染">渲染</option>
+              <option value="后期">后期</option>
+              <option value="完成">完成</option>
+            </select>
+          </div>
+          
+          <div class="form-group">
+            <label for="taskDeadline">截止时间 *</label>
+            
+            <!-- 显示当前选择的截止时间 -->
+            <div class="deadline-display ios-input" (click)="deadlineDropdownVisible = !deadlineDropdownVisible">
+              {{ getDisplayDeadline() || '请选择截止时间' }}
+              <span class="dropdown-arrow" [class.rotate]="deadlineDropdownVisible">▼</span>
+            </div>
+            
+            <!-- 预设时长下拉选择框 -->
+            <div class="deadline-dropdown" [class.visible]="deadlineDropdownVisible">
+              <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" 
+                   [class.selected]="selectedPreset === 'today'"
+                   (click)="handlePresetSelection('today')">
+                今日24:00前
+              </div>
+              
+              <!-- 自定义时间选项 -->
+              <div class="dropdown-divider"></div>
+              <div class="dropdown-option custom-option" (click)="handlePresetSelection('custom')">
+                自定义时间
+              </div>
+            </div>
+            
+            <!-- 错误提示信息 -->
+            <div class="error-message" *ngIf="deadlineError">{{ deadlineError }}</div>
+          </div>
+          
+          <!-- 自定义时间选择弹窗 -->
+          <div class="custom-time-modal" *ngIf="isCustomTimeVisible">
+            <div class="modal-backdrop"></div>
+            <div class="modal-content">
+              <div class="modal-header">
+                <h3>选择自定义时间</h3>
+                <button class="close-button" (click)="closeCustomTimePicker()">×</button>
+              </div>
+              
+              <div class="modal-body">
+                <!-- 日期选择 -->
+                <div class="date-picker">
+                  <label>日期</label>
+                  <input
+                    type="date"
+                    [(ngModel)]="customDate"
+                    min="{{ todayDate }}"
+                    max="{{ sevenDaysLaterDate }}"
+                    class="ios-input"
+                  />
+                </div>
+                
+                <!-- 时间选择 -->
+                <div class="time-picker">
+                  <label>时间</label>
+                  <input
+                    type="time"
+                    [(ngModel)]="customTime"
+                    class="ios-input"
+                  />
+                </div>
+                
+                <!-- 错误提示信息 -->
+                <div class="error-message" *ngIf="deadlineError">{{ deadlineError }}</div>
+              </div>
+              
+              <div class="modal-footer">
+                <button class="cancel-button" (click)="closeCustomTimePicker()">取消</button>
+                <button class="confirm-button" (click)="handleCustomTimeSelection()">确定</button>
+              </div>
+            </div>
+          </div>
+          
+          <div class="form-group">
+            <label for="taskPriority">优先级</label>
+            <select 
+              id="taskPriority" 
+              [(ngModel)]="newTask.priority" 
+              [ngModelOptions]="{standalone: true}"
+              class="ios-select"
+            >
+              <option value="high">高</option>
+              <option value="medium">中</option>
+              <option value="low">低</option>
+            </select>
+          </div>
+          
+          <div class="form-group">
+            <label for="taskDescription">任务描述</label>
+            <textarea 
+              id="taskDescription" 
+              [(ngModel)]="newTask.description" 
+              [ngModelOptions]="{standalone: true}"
+              placeholder="请输入任务描述"
+              rows="3"
+              class="ios-textarea"
+            ></textarea>
+          </div>
+        </form>
+      </div>
+      
+      <div class="ios-panel-footer">
+        <button type="button" class="ios-cancel-button" (click)="hideTaskForm()">取消</button>
+        <button type="button" class="ios-submit-button" (click)="handleAddTaskSubmit()" [disabled]="isSubmitDisabled">确定</button>
+      </div>
+    </div>
+  </div>
+
   <!-- 项目动态流 -->
   <section class="project-updates-section">
     <div class="section-header">

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

@@ -0,0 +1,29 @@
+import { Routes } from '@angular/router';
+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 = [
+  { 
+    path: 'consultation-list', 
+    component: ConsultationListComponent,
+    data: { title: '咨询列表' }
+  },
+  { 
+    path: 'assignment-list', 
+    component: AssignmentListComponent,
+    data: { title: '派单列表' }
+  },
+  { 
+    path: 'exception-list', 
+    component: ExceptionListComponent,
+    data: { title: '异常项目' }
+  },
+  { 
+    path: 'revenue-detail', 
+    component: RevenueDetailComponent,
+    data: { title: '成交详情' }
+  }
+];

文件差异内容过多而无法显示
+ 889 - 436
src/app/pages/customer-service/dashboard/dashboard.scss


+ 308 - 28
src/app/pages/customer-service/dashboard/dashboard.ts

@@ -2,7 +2,7 @@
 import { Component, OnInit, OnDestroy, signal, computed } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
-import { RouterModule } from '@angular/router';
+import { RouterModule, Router, ActivatedRoute } from '@angular/router';
 import { ProjectService } from '../../../services/project.service';
 import { Project, Task, CustomerFeedback } from '../../../models/project.model';
 
@@ -11,7 +11,8 @@ import { Project, Task, CustomerFeedback } from '../../../models/project.model';
   standalone: true,
   imports: [CommonModule, FormsModule, RouterModule],
   templateUrl: './dashboard.html',
-  styleUrls: ['./dashboard.scss', '../customer-service-styles.scss']
+  styleUrls: ['./dashboard.scss', '../customer-service-styles.scss'],
+  providers: [ProjectService]
 }) 
 export class Dashboard implements OnInit, OnDestroy {
   // 数据看板统计
@@ -57,7 +58,71 @@ export class Dashboard implements OnInit, OnDestroy {
   // 回到顶部按钮可见性信号
   showBackToTopSignal = signal(false);
 
-  constructor(private projectService: ProjectService) {}
+  // 任务表单可见性
+  isTaskFormVisible = signal(false);
+  
+  // 新任务数据
+  newTask: Task = {
+    id: '',
+    projectId: '',
+    projectName: '',
+    title: '',
+    stage: '需求沟通',
+    deadline: new Date(),
+    isOverdue: false,
+    isCompleted: false,
+    priority: 'high',
+    assignee: '当前用户',
+    description: ''
+  };
+  
+  // 用于日期时间输入的属性
+  deadlineInput = '';
+  
+  // 预设快捷时长选项
+  timePresets = [
+    { label: '1小时内', hours: 1 },
+    { label: '3小时内', hours: 3 },
+    { label: '6小时内', hours: 6 },
+    { label: '12小时内', hours: 12 },
+    { label: '24小时内', hours: 24 }
+  ];
+  
+  // 选中的预设时长
+  selectedPreset = '';
+  
+  // 自定义时间弹窗可见性
+  isCustomTimeVisible = false;
+  
+  // 自定义选择的日期和时间
+  customDate = new Date();
+  customTime = '';
+  
+  // 错误提示信息
+  deadlineError = '';
+  
+  // 提交按钮是否禁用
+  isSubmitDisabled = false;
+  
+  // 下拉框可见性
+  deadlineDropdownVisible = false;
+  
+  // 日期范围限制
+  get todayDate(): string {
+    return new Date().toISOString().split('T')[0];
+  }
+  
+  get sevenDaysLaterDate(): string {
+    const date = new Date();
+    date.setDate(date.getDate() + 7);
+    return date.toISOString().split('T')[0];
+  }
+
+  constructor(
+    private projectService: ProjectService,
+    private router: Router,
+    private activatedRoute: ActivatedRoute
+  ) {}
 
   ngOnInit(): void {
     this.loadUrgentTasks();
@@ -175,52 +240,267 @@ export class Dashboard implements OnInit, OnDestroy {
     this.stats.pendingAssignments.set(this.stats.pendingAssignments() - 1);
   }
 
-  // 添加新的紧急事项
-  addUrgentTask(): void {
-    // 在实际应用中,这里可能会打开一个表单模态框
-    // 这里使用模拟数据直接添加
-    const newTask: Task = {
-      id: `task-${Date.now()}`,
-      projectId: `project-${Math.floor(Math.random() * 1000)}`,
-      title: '新增紧急任务',
-      projectName: '新项目',
-      stage: '需求沟通', // 设置一个有效的ProjectStage值
+  // 显示任务表单
+  showTaskForm(): void {
+    // 重置表单数据
+    this.newTask = {
+      id: '',
+      projectId: '',
+      projectName: '',
+      title: '',
+      stage: '需求沟通',
       deadline: new Date(),
       isOverdue: false,
       isCompleted: false,
-      // Task接口中没有status属性,移除它
-      // 为了符合Task接口要求,添加required的stage字段
-      priority: 'high', // 模拟值
-      assignee: '当前用户', // 模拟值
-      description: '紧急任务描述' // 模拟值
+      priority: 'high',
+      assignee: '当前用户',
+      description: ''
     };
-
-    this.urgentTasks.set([newTask, ...this.urgentTasks()]);
+    
+    // 重置相关状态
+    this.deadlineError = '';
+    this.isSubmitDisabled = false;
+    
+    // 计算并设置默认预设时长
+    this.setDefaultPreset();
+    
+    // 显示表单
+    this.isTaskFormVisible.set(true);
+    
+    // 添加iOS风格的面板显示动画
+    setTimeout(() => {
+      document.querySelector('.ios-panel')?.classList.add('ios-panel-visible');
+    }, 10);
+  }
+  
+  // 设置默认预设时长
+  private setDefaultPreset(): void {
+    const now = new Date();
+    const todayEnd = new Date(now);
+    todayEnd.setHours(23, 59, 59, 999);
+    
+    // 检查3小时后是否超过当天24:00
+    const threeHoursLater = new Date(now.getTime() + 3 * 60 * 60 * 1000);
+    
+    if (threeHoursLater <= todayEnd) {
+      // 3小时后未超过当天24:00,默认选中3小时内
+      this.selectedPreset = '3';
+      this.updatePresetDeadline(3);
+    } else {
+      // 3小时后超过当天24:00,默认选中当天24:00前
+      this.selectedPreset = 'today';
+      this.deadlineInput = todayEnd.toISOString().slice(0, 16);
+      this.newTask.deadline = todayEnd;
+    }
+  }
+  
+  // 处理预设时长选择
+  handlePresetSelection(preset: string): void {
+    this.selectedPreset = preset;
+    this.deadlineError = '';
+    
+    if (preset === 'custom') {
+      // 打开自定义时间选择器
+      this.openCustomTimePicker();
+    } else if (preset === 'today') {
+      // 设置为当天24:00前
+      const now = new Date();
+      const todayEnd = new Date(now);
+      todayEnd.setHours(23, 59, 59, 999);
+      
+      this.deadlineInput = todayEnd.toISOString().slice(0, 16);
+      this.newTask.deadline = todayEnd;
+    } else {
+      // 计算预设时长的截止时间
+      const hours = parseInt(preset);
+      this.updatePresetDeadline(hours);
+    }
+  }
+  
+  // 更新预设时长的截止时间
+  private updatePresetDeadline(hours: number): void {
+    const now = new Date();
+    const deadline = new Date(now.getTime() + hours * 60 * 60 * 1000);
+    
+    this.deadlineInput = deadline.toISOString().slice(0, 16);
+    this.newTask.deadline = deadline;
+  }
+  
+  // 打开自定义时间选择器
+  openCustomTimePicker(): void {
+    // 重置自定义时间
+    this.customDate = new Date();
+    const now = new Date();
+    this.customTime = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
+    
+    // 显示自定义时间弹窗
+    this.isCustomTimeVisible = true;
+    
+    // 添加iOS风格的弹窗动画
+    setTimeout(() => {
+      document.querySelector('.custom-time-modal')?.classList.add('modal-visible');
+    }, 10);
+  }
+  
+  // 关闭自定义时间选择器
+  closeCustomTimePicker(): void {
+    // 添加iOS风格的弹窗关闭动画
+    const modal = document.querySelector('.custom-time-modal');
+    if (modal) {
+      modal.classList.remove('modal-visible');
+      setTimeout(() => {
+        this.isCustomTimeVisible = false;
+      }, 300);
+    } else {
+      this.isCustomTimeVisible = false;
+    }
+  }
+  
+  // 处理自定义时间选择
+  handleCustomTimeSelection(): void {
+    const [hours, minutes] = this.customTime.split(':').map(Number);
+    const selectedDateTime = new Date(this.customDate);
+    selectedDateTime.setHours(hours, minutes, 0, 0);
+    
+    // 验证选择的时间是否有效
+    if (this.validateDeadline(selectedDateTime)) {
+      this.deadlineInput = selectedDateTime.toISOString().slice(0, 16);
+      this.newTask.deadline = selectedDateTime;
+      this.closeCustomTimePicker();
+    }
+  }
+  
+  // 验证截止时间是否有效
+  validateDeadline(deadline: Date): boolean {
+    const now = new Date();
+    
+    if (deadline < now) {
+      this.deadlineError = '截止时间不能早于当前时间,请重新选择';
+      this.isSubmitDisabled = true;
+      return false;
+    }
+    
+    this.deadlineError = '';
+    this.isSubmitDisabled = false;
+    return true;
+  }
+  
+  // 获取显示的截止时间文本
+  getDisplayDeadline(): string {
+    if (!this.deadlineInput) return '';
+    
+    try {
+      const date = new Date(this.deadlineInput);
+      return date.toLocaleString('zh-CN', {
+        year: 'numeric',
+        month: '2-digit',
+        day: '2-digit',
+        hour: '2-digit',
+        minute: '2-digit'
+      });
+    } catch (error) {
+      return '';
+    }
+  }
+  
+  // 隐藏任务表单
+  hideTaskForm(): void {
+    // 添加iOS风格的面板隐藏动画
+    const panel = document.querySelector('.ios-panel');
+    if (panel) {
+      panel.classList.remove('ios-panel-visible');
+      setTimeout(() => {
+        this.isTaskFormVisible.set(false);
+      }, 300);
+    } else {
+      this.isTaskFormVisible.set(false);
+    }
+  }
+  
+  // 处理添加任务表单提交
+  handleAddTaskSubmit(): void {
+    // 验证表单数据
+    if (!this.newTask.title.trim() || !this.newTask.projectName.trim() || !this.deadlineInput || this.isSubmitDisabled) {
+      // 在实际应用中,这里应该显示错误提示
+      alert('请填写必填字段(任务标题、项目名称、截止时间)');
+      return;
+    }
+    
+    // 创建新任务
+    const taskToAdd: Task = {
+      ...this.newTask,
+      id: `task-${Date.now()}`,
+      projectId: `project-${Math.floor(Math.random() * 1000)}`,
+      deadline: new Date(this.deadlineInput),
+      isOverdue: new Date(this.deadlineInput) < new Date()
+    };
+    
+    // 添加到任务列表
+    this.urgentTasks.set([taskToAdd, ...this.urgentTasks()]);
+    
+    // 更新统计数据
     this.stats.pendingAssignments.set(this.stats.pendingAssignments() + 1);
+    
+    // 隐藏表单
+    this.hideTaskForm();
+  }
+  
+  // 添加新的紧急事项
+  addUrgentTask(): void {
+    // 调用显示表单方法
+    this.showTaskForm();
   }
 
   // 新咨询数图标点击处理
   handleNewConsultationsClick(): void {
-    console.log('点击查看新咨询详情');
-    // 在实际应用中,这里会跳转到新咨询列表页面或打开新咨询模态框
+    this.navigateToDetail('consultations');
   }
 
   // 待派单数图标点击处理
   handlePendingAssignmentsClick(): void {
-    console.log('点击查看待派单详情');
-    // 在实际应用中,这里会跳转到待派单列表页面或打开待派单模态框
+    this.navigateToDetail('assignments');
   }
 
   // 异常项目图标点击处理
   handleExceptionProjectsClick(): void {
-    console.log('点击查看异常项目详情');
-    // 在实际应用中,这里会跳转到异常项目列表页面或打开异常项目模态框
+    this.navigateToDetail('exceptions');
   }
 
   // 今日成交额图标点击处理
   handleTodayRevenueClick(): void {
-    console.log('点击查看今日成交额详情');
-    // 在实际应用中,这里会跳转到今日成交额详情页面或打开今日成交额模态框
+    this.navigateToDetail('revenue');
+  }
+
+  // 导航到详情页
+  private navigateToDetail(type: 'consultations' | 'assignments' | 'exceptions' | 'revenue'): void {
+    const routeMap = {
+      consultations: '/customer-service/consultation-list',
+      assignments: '/customer-service/assignment-list',
+      exceptions: '/customer-service/exception-list',
+      revenue: '/customer-service/revenue-detail'
+    };
+    
+    console.log('导航到:', routeMap[type]);
+    console.log('当前路由:', this.router.url);
+    
+    // 添加iOS风格页面过渡动画
+    document.body.classList.add('ios-page-transition');
+    setTimeout(() => {
+      this.router.navigateByUrl(routeMap[type])
+        .then(navResult => {
+          console.log('导航结果:', navResult);
+          if (!navResult) {
+            console.error('导航失败,检查路由配置');
+          }
+        })
+        .catch(err => {
+          console.error('导航错误:', err);
+        });
+      
+      setTimeout(() => {
+        document.body.classList.remove('ios-page-transition');
+      }, 300);
+    }, 100);
   }
 
   // 格式化日期

+ 42 - 0
src/app/pages/customer-service/dashboard/pages/assignment-list/assignment-list.component.html

@@ -0,0 +1,42 @@
+<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>
+  </header>
+
+  <div class="ios-content">
+    <div class="stats-bar">
+      <div class="stat-item">
+        <span class="value">{{assignments.length}}</span>
+        <span class="label">总任务数</span>
+      </div>
+      <div class="stat-item">
+        <span class="value">{{getUrgentCount()}}</span>
+        <span class="label">紧急任务</span>
+      </div>
+    </div>
+
+    <div class="assignment-list">
+      <div class="assignment-card" *ngFor="let item of assignments">
+        <div class="card-header">
+          <h3>{{item.project}}</h3>
+          <span class="priority-badge" [class.high]="item.priority === 'high'">
+            {{item.priority === 'high' ? '高优先级' : '普通'}}
+          </span>
+        </div>
+        <p class="description">{{item.description}}</p>
+        <div class="card-footer">
+          <div class="meta">
+            <span class="customer"><svg><use xlink:href="#user-icon"></use></svg> {{item.customer}}</span>
+            <span class="deadline"><svg><use xlink:href="#clock-icon"></use></svg> {{item.deadline}}</span>
+          </div>
+          <button class="ios-btn">立即派单</button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 148 - 0
src/app/pages/customer-service/dashboard/pages/assignment-list/assignment-list.component.scss

@@ -0,0 +1,148 @@
+@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-content {
+  padding: 16px;
+}
+
+.stats-bar {
+  display: flex;
+  justify-content: space-around;
+  background-color: $background-primary;
+  border-radius: 12px;
+  padding: 16px;
+  margin-bottom: 16px;
+  box-shadow: $shadow-sm;
+}
+
+.stat-item {
+  text-align: center;
+
+  .value {
+    display: block;
+    font-size: 24px;
+    font-weight: 700;
+    color: $primary-color;
+    margin-bottom: 4px;
+  }
+
+  .label {
+    font-size: 14px;
+    color: $text-tertiary;
+  }
+}
+
+.assignment-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.assignment-card {
+  background-color: $background-primary;
+  border-radius: 12px;
+  padding: 16px;
+  box-shadow: $shadow-sm;
+  transition: transform 0.2s ease;
+
+  &:active {
+    transform: scale(0.98);
+  }
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12px;
+
+  h3 {
+    font-size: 16px;
+    margin: 0;
+    color: $text-primary;
+  }
+}
+
+.priority-badge {
+  padding: 4px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+  font-weight: 500;
+
+  &.high {
+    background-color: rgba($danger-color, 0.1);
+    color: $danger-color;
+  }
+
+  &:not(.high) {
+    background-color: rgba($success-color, 0.1);
+    color: $success-color;
+  }
+}
+
+.description {
+  color: $text-secondary;
+  margin: 0 0 16px;
+  line-height: 1.5;
+}
+
+.card-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+
+  .meta {
+    display: flex;
+    gap: 12px;
+    font-size: 14px;
+    color: $text-tertiary;
+
+    svg {
+      width: 14px;
+      height: 14px;
+      margin-right: 4px;
+      vertical-align: middle;
+    }
+  }
+}
+
+.ios-btn {
+  padding: 8px 16px;
+  border-radius: 8px;
+  font-size: 14px;
+  font-weight: 500;
+  background-color: $primary-color;
+  color: white;
+  border: none;
+  transition: all 0.2s ease;
+}

+ 105 - 0
src/app/pages/customer-service/dashboard/pages/assignment-list/assignment-list.component.ts

@@ -0,0 +1,105 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+
+@Component({
+  selector: 'app-assignment-list',
+  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-card" *ngFor="let item of assignments">
+          <div class="ios-card-header">
+            <h3>{{item.project}}</h3>
+            <span class="ios-badge" [class.urgent]="item.priority === 'high'">
+              {{item.priority === 'high' ? '紧急' : '普通'}}
+            </span>
+          </div>
+          <p>{{item.description}}</p>
+          <button class="ios-btn">派单处理</button>
+        </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-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-badge {
+      background: #34c759;
+      color: white;
+      padding: 4px 8px;
+      border-radius: 10px;
+      font-size: 12px;
+      
+      &.urgent {
+        background: #ff3b30;
+      }
+    }
+    
+    .ios-btn {
+      background: #007aff;
+      color: white;
+      border: none;
+      padding: 8px 16px;
+      border-radius: 8px;
+      margin-top: 12px;
+    }
+  `]
+})
+export class AssignmentListComponent {
+  assignments = [
+    {project: '现代简约客厅设计', priority: 'high', description: '客户急需设计方案确认'},
+    {project: '欧式厨房改造', priority: 'normal', description: '等待设计师确认方案'},
+    {project: '三居室全屋设计', priority: 'normal', description: '等待客户提供户型图'}
+  ];
+  
+  goBack() {
+    history.back();
+  }
+}

+ 42 - 0
src/app/pages/customer-service/dashboard/pages/consultation-list/consultation-list.component.html

@@ -0,0 +1,42 @@
+<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>
+  </header>
+
+  <div class="ios-content">
+    <div class="search-bar">
+      <input type="text" placeholder="搜索咨询记录...">
+      <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"/>
+      </svg>
+    </div>
+
+    <div class="consultation-list">
+      <div class="consultation-card" *ngFor="let item of consultations">
+        <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>
+          </div>
+          <span class="status-badge" [class.urgent]="item.priority === 'high'">
+            {{item.priority === 'high' ? '紧急' : '普通'}}
+          </span>
+        </div>
+        <p class="content">{{item.content}}</p>
+        <div class="card-footer">
+          <button class="ios-btn">查看详情</button>
+          <button class="ios-btn outline">分配处理</button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 166 - 0
src/app/pages/customer-service/dashboard/pages/consultation-list/consultation-list.component.scss

@@ -0,0 +1,166 @@
+@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-content {
+  padding: 16px;
+}
+
+.search-bar {
+  position: relative;
+  margin-bottom: 16px;
+
+  input {
+    width: 100%;
+    padding: 12px 16px 12px 40px;
+    border-radius: 10px;
+    border: 1px solid $border-color;
+    background-color: $background-secondary;
+    font-size: 16px;
+
+    &::placeholder {
+      color: $text-tertiary;
+    }
+  }
+
+  svg {
+    position: absolute;
+    left: 12px;
+    top: 50%;
+    transform: translateY(-50%);
+    color: $text-tertiary;
+  }
+}
+
+.consultation-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.consultation-card {
+  background-color: $background-primary;
+  border-radius: 12px;
+  padding: 16px;
+  box-shadow: $shadow-sm;
+  transition: transform 0.2s ease;
+
+  &:active {
+    transform: scale(0.98);
+  }
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12px;
+}
+
+.customer-info {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+
+  .avatar {
+    width: 40px;
+    height: 40px;
+    border-radius: 20px;
+    background-color: $primary-color;
+    color: white;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-weight: 600;
+  }
+
+  h3 {
+    font-size: 16px;
+    margin: 0;
+    color: $text-primary;
+  }
+
+  .time {
+    font-size: 14px;
+    color: $text-tertiary;
+    margin: 4px 0 0;
+  }
+}
+
+.status-badge {
+  padding: 4px 8px;
+  border-radius: 4px;
+  font-size: 12px;
+  font-weight: 500;
+
+  &.urgent {
+    background-color: rgba($danger-color, 0.1);
+    color: $danger-color;
+  }
+
+  &:not(.urgent) {
+    background-color: rgba($success-color, 0.1);
+    color: $success-color;
+  }
+}
+
+.content {
+  color: $text-secondary;
+  margin: 0 0 16px;
+  line-height: 1.5;
+}
+
+.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;
+
+    &.outline {
+      background-color: transparent;
+      border: 1px solid $primary-color;
+      color: $primary-color;
+    }
+
+    &:not(.outline) {
+      background-color: $primary-color;
+      color: white;
+    }
+  }
+}

+ 90 - 0
src/app/pages/customer-service/dashboard/pages/consultation-list/consultation-list.component.ts

@@ -0,0 +1,90 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+
+@Component({
+  selector: 'app-consultation-list',
+  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-card" *ngFor="let item of consultations">
+          <div class="ios-card-header">
+            <h3>{{item.customer}}</h3>
+            <span class="ios-badge">{{item.time}}</span>
+          </div>
+          <p>{{item.content}}</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-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-badge {
+      background: #007aff;
+      color: white;
+      padding: 4px 8px;
+      border-radius: 10px;
+      font-size: 12px;
+    }
+  `]
+})
+export class ConsultationListComponent {
+  consultations = [
+    {customer: '张先生', time: '10:30', content: '咨询关于厨房改造的预算和工期'},
+    {customer: '李女士', time: '11:45', content: '询问客厅设计风格建议'},
+    {customer: '王先生', time: '14:20', content: '需要全屋设计方案咨询'}
+  ];
+  
+  goBack() {
+    history.back();
+  }
+}

+ 38 - 0
src/app/pages/customer-service/dashboard/pages/exception-list/exception-list.component.html

@@ -0,0 +1,38 @@
+<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>
+  </header>
+
+  <div class="ios-content">
+    <div class="filter-bar">
+      <button class="filter-btn active">全部</button>
+      <button class="filter-btn">未处理</button>
+      <button class="filter-btn">处理中</button>
+    </div>
+
+    <div class="exception-list">
+      <div class="exception-card" *ngFor="let item of exceptions">
+        <div class="card-header">
+          <div class="project-info">
+            <div class="status-indicator" [class]="item.status"></div>
+            <h3>{{item.project}}</h3>
+          </div>
+          <span class="date">{{item.date}}</span>
+        </div>
+        <div class="card-body">
+          <p class="issue"><strong>问题描述:</strong> {{item.issue}}</p>
+          <p class="action" *ngIf="item.action"><strong>处理方案:</strong> {{item.action}}</p>
+        </div>
+        <div class="card-footer">
+          <button class="ios-btn warning">{{item.status === 'resolved' ? '查看详情' : '处理异常'}}</button>
+          <button class="ios-btn">联系客户</button>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 158 - 0
src/app/pages/customer-service/dashboard/pages/exception-list/exception-list.component.scss

@@ -0,0 +1,158 @@
+@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-content {
+  padding: 16px;
+}
+
+.filter-bar {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 16px;
+}
+
+.filter-btn {
+  flex: 1;
+  padding: 8px;
+  border-radius: 8px;
+  border: none;
+  background-color: $background-secondary;
+  color: $text-secondary;
+  font-size: 14px;
+  transition: all 0.2s ease;
+
+  &.active {
+    background-color: $primary-color;
+    color: white;
+  }
+}
+
+.exception-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.exception-card {
+  background-color: $background-primary;
+  border-radius: 12px;
+  padding: 16px;
+  box-shadow: $shadow-sm;
+  transition: transform 0.2s ease;
+
+  &:active {
+    transform: scale(0.98);
+  }
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12px;
+}
+
+.project-info {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+
+  .status-indicator {
+    width: 12px;
+    height: 12px;
+    border-radius: 6px;
+
+    &.pending {
+      background-color: $warning-color;
+    }
+
+    &.processing {
+      background-color: $primary-color;
+    }
+
+    &.resolved {
+      background-color: $success-color;
+    }
+  }
+
+  h3 {
+    font-size: 16px;
+    margin: 0;
+    color: $text-primary;
+  }
+}
+
+.date {
+  font-size: 14px;
+  color: $text-tertiary;
+}
+
+.card-body {
+  margin: 12px 0;
+
+  p {
+    margin: 8px 0;
+    line-height: 1.5;
+    color: $text-secondary;
+
+    strong {
+      color: $text-primary;
+    }
+  }
+}
+
+.card-footer {
+  display: flex;
+  gap: 8px;
+  margin-top: 12px;
+
+  .ios-btn {
+    flex: 1;
+    padding: 10px;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    text-align: center;
+    transition: all 0.2s ease;
+
+    &.warning {
+      background-color: $warning-color;
+      color: white;
+    }
+
+    &:not(.warning) {
+      background-color: $primary-color;
+      color: white;
+    }
+  }
+}

+ 110 - 0
src/app/pages/customer-service/dashboard/pages/exception-list/exception-list.component.ts

@@ -0,0 +1,110 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+
+@Component({
+  selector: 'app-exception-list',
+  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-card" *ngFor="let item of exceptions">
+          <div class="ios-card-header">
+            <h3>{{item.project}}</h3>
+            <span class="ios-badge">{{item.status}}</span>
+          </div>
+          <p>{{item.issue}}</p>
+          <div class="ios-actions">
+            <button class="ios-btn warning">标记处理</button>
+            <button class="ios-btn">查看详情</button>
+          </div>
+        </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-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-badge {
+      background: #ff9500;
+      color: white;
+      padding: 4px 8px;
+      border-radius: 10px;
+      font-size: 12px;
+    }
+    
+    .ios-actions {
+      display: flex;
+      gap: 8px;
+      margin-top: 12px;
+    }
+    
+    .ios-btn {
+      background: #007aff;
+      color: white;
+      border: none;
+      padding: 8px 16px;
+      border-radius: 8px;
+      
+      &.warning {
+        background: #ff9500;
+      }
+    }
+  `]
+})
+export class ExceptionListComponent {
+  exceptions = [
+    {project: '现代简约客厅设计', status: '材料缺货', issue: '客户选定的大理石材料缺货,需要更换'},
+    {project: '欧式厨房改造', status: '工期延误', issue: '施工队排期冲突,需要调整工期'}
+  ];
+  
+  goBack() {
+    history.back();
+  }
+}

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

@@ -0,0 +1,56 @@
+<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>

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

@@ -0,0 +1,161 @@
+@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;
+}

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

@@ -0,0 +1,120 @@
+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();
+  }
+}

+ 170 - 0
src/app/pages/customer-service/project-detail/complaint-warning-dialog.ts

@@ -0,0 +1,170 @@
+import { Component, Inject } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
+import { MatIconModule } from '@angular/material/icon';
+
+interface ComplaintWarningData {
+  projectId: string;
+  projectName: string;
+}
+
+@Component({
+  selector: 'app-complaint-warning-dialog',
+  standalone: true,
+  imports: [
+    CommonModule,
+    FormsModule,
+    MatButtonModule,
+    MatDialogModule,
+    MatFormFieldModule,
+    MatInputModule,
+    MatSelectModule,
+    MatIconModule
+  ],
+  template: `
+    <h2 mat-dialog-title>投诉预警</h2>
+    <mat-dialog-content>
+      <div class="dialog-content">
+        <div class="project-info">
+          <p><strong>项目名称:</strong> {{ data.projectName }}</p>
+        </div>
+        
+        <div class="form-group">
+          <label for="complaint-level">预警等级 *</label>
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-select [(ngModel)]="complaintLevel" id="complaint-level" name="complaintLevel" required>
+              <mat-option value="low">低级别</mat-option>
+              <mat-option value="medium">中级别</mat-option>
+              <mat-option value="high">高级别</mat-option>
+            </mat-select>
+          </mat-form-field>
+        </div>
+        
+        <div class="form-group">
+          <label for="complaint-content">投诉内容 *</label>
+          <mat-form-field appearance="outline" class="form-field">
+            <textarea 
+              matInput 
+              [(ngModel)]="complaintContent" 
+              id="complaint-content" 
+              name="complaintContent" 
+              rows="4" 
+              placeholder="请详细描述客户投诉的内容..."
+              required
+            ></textarea>
+          </mat-form-field>
+        </div>
+        
+        <div class="form-group">
+          <label for="potential-risk">潜在风险</label>
+          <mat-form-field appearance="outline" class="form-field">
+            <textarea 
+              matInput 
+              [(ngModel)]="potentialRisk" 
+              id="potential-risk" 
+              name="potential-risk" 
+              rows="2" 
+              placeholder="分析可能带来的影响和风险..."
+            ></textarea>
+          </mat-form-field>
+        </div>
+        
+        <div class="form-group">
+          <label for="suggested-action">建议措施</label>
+          <mat-form-field appearance="outline" class="form-field">
+            <textarea 
+              matInput 
+              [(ngModel)]="suggestedAction" 
+              id="suggested-action" 
+              name="suggested-action" 
+              rows="2" 
+              placeholder="建议采取的解决措施..."
+            ></textarea>
+          </mat-form-field>
+        </div>
+      </div>
+    </mat-dialog-content>
+    <mat-dialog-actions align="end">
+      <button mat-button (click)="onCancel()">取消</button>
+      <button mat-button color="primary" (click)="onSubmit()" [disabled]="!isFormValid()">
+        提交预警
+      </button>
+    </mat-dialog-actions>
+  `,
+  styles: [`
+    .dialog-content {
+      padding: 10px 0;
+    }
+    
+    .project-info {
+      padding: 12px;
+      background-color: #fff3e0;
+      border-radius: 8px;
+      margin-bottom: 16px;
+    }
+    
+    .form-group {
+      margin-bottom: 20px;
+    }
+    
+    label {
+      display: block;
+      margin-bottom: 6px;
+      font-weight: 500;
+      color: rgba(0, 0, 0, 0.87);
+    }
+    
+    p {
+      margin: 0;
+      color: rgba(0, 0, 0, 0.6);
+    }
+    
+    .form-field {
+      width: 100%;
+    }
+    
+    .mat-mdc-dialog-actions {
+      padding: 16px 24px;
+    }
+  `]
+}) 
+export class ComplaintWarningDialog {
+  complaintLevel: string = '';
+  complaintContent: string = '';
+  potentialRisk: string = '';
+  suggestedAction: string = '';
+
+  constructor(
+    public dialogRef: MatDialogRef<ComplaintWarningDialog>,
+    @Inject(MAT_DIALOG_DATA) public data: ComplaintWarningData
+  ) {}
+
+  onCancel(): void {
+    this.dialogRef.close();
+  }
+
+  isFormValid(): boolean {
+    return !!this.complaintLevel && !!this.complaintContent.trim();
+  }
+
+  onSubmit(): void {
+    if (!this.isFormValid()) return;
+    
+    const formData = {
+      projectId: this.data.projectId,
+      projectName: this.data.projectName,
+      complaintLevel: this.complaintLevel,
+      complaintContent: this.complaintContent,
+      potentialRisk: this.potentialRisk,
+      suggestedAction: this.suggestedAction,
+      reportedAt: new Date().toISOString()
+    };
+    
+    this.dialogRef.close({ confirmed: true, data: formData });
+  }
+}

+ 162 - 0
src/app/pages/customer-service/project-detail/modification-request-dialog.ts

@@ -0,0 +1,162 @@
+import { Component, Inject } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
+import { MatIconModule } from '@angular/material/icon';
+
+interface ModificationRequestData {
+  projectId: string;
+  projectName: string;
+  projectStatus?: string;
+}
+
+@Component({
+  selector: 'app-modification-request-dialog',
+  standalone: true,
+  imports: [
+    CommonModule,
+    FormsModule,
+    MatButtonModule,
+    MatDialogModule,
+    MatFormFieldModule,
+    MatInputModule,
+    MatSelectModule,
+    MatIconModule
+  ],
+  template: `
+    <h2 mat-dialog-title>申请修改</h2>
+    <mat-dialog-content>
+      <div class="dialog-content">
+        <div class="project-info">
+          <p><strong>项目名称:</strong> {{ data.projectName }}</p>
+        </div>
+        
+        <div class="form-group">
+          <label for="modification-type">修改类型 *</label>
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-select [(ngModel)]="modificationType" id="modification-type" name="modificationType" required>
+              <mat-option value="content">内容修改</mat-option>
+              <mat-option value="design">设计修改</mat-option>
+              <mat-option value="time">时间调整</mat-option>
+              <mat-option value="other">其他</mat-option>
+            </mat-select>
+          </mat-form-field>
+        </div>
+        
+        <div class="form-group">
+          <label for="description">修改说明 *</label>
+          <mat-form-field appearance="outline" class="form-field">
+            <textarea 
+              matInput 
+              [(ngModel)]="description" 
+              id="description" 
+              name="description" 
+              rows="4" 
+              placeholder="请详细描述需要修改的内容..."
+              required
+            ></textarea>
+          </mat-form-field>
+        </div>
+        
+        <div class="form-group">
+          <label for="expected-time">期望完成时间</label>
+          <mat-form-field appearance="outline" class="form-field">
+            <input 
+              matInput 
+              [(ngModel)]="expectedTime" 
+              id="expected-time" 
+              name="expectedTime" 
+              type="text" 
+              placeholder="如:3天内"
+            >
+          </mat-form-field>
+        </div>
+        
+        <div class="form-group">
+          <label for="contact-info">联系人信息</label>
+          <p>{{ contactInfo }}</p>
+        </div>
+      </div>
+    </mat-dialog-content>
+    <mat-dialog-actions align="end">
+      <button mat-button (click)="onCancel()">取消</button>
+      <button mat-button color="primary" (click)="onSubmit()" [disabled]="!isFormValid()">
+        提交申请
+      </button>
+    </mat-dialog-actions>
+  `,
+  styles: [`
+    .dialog-content {
+      padding: 10px 0;
+    }
+    
+    .project-info {
+      padding: 12px;
+      background-color: #f5f5f5;
+      border-radius: 8px;
+      margin-bottom: 16px;
+    }
+    
+    .form-group {
+      margin-bottom: 20px;
+    }
+    
+    label {
+      display: block;
+      margin-bottom: 6px;
+      font-weight: 500;
+      color: rgba(0, 0, 0, 0.87);
+    }
+    
+    p {
+      margin: 0;
+      color: rgba(0, 0, 0, 0.6);
+    }
+    
+    .form-field {
+      width: 100%;
+    }
+    
+    .mat-mdc-dialog-actions {
+      padding: 16px 24px;
+    }
+  `]
+}) 
+export class ModificationRequestDialog {
+  modificationType: string = '';
+  description: string = '';
+  expectedTime: string = '';
+  contactInfo: string = '客服小李 (电话: 138****6789)';
+
+  constructor(
+    public dialogRef: MatDialogRef<ModificationRequestDialog>,
+    @Inject(MAT_DIALOG_DATA) public data: ModificationRequestData
+  ) {}
+
+  onCancel(): void {
+    this.dialogRef.close();
+  }
+
+  isFormValid(): boolean {
+    return !!this.modificationType && !!this.description.trim();
+  }
+
+  onSubmit(): void {
+    if (!this.isFormValid()) return;
+    
+    const formData = {
+      projectId: this.data.projectId,
+      modificationType: this.modificationType,
+      description: this.description,
+      expectedTime: this.expectedTime,
+      contactInfo: this.contactInfo,
+      submittedAt: new Date().toISOString()
+    };
+    
+    this.dialogRef.close({ confirmed: true, data: formData });
+  }
+}

+ 121 - 136
src/app/pages/customer-service/project-detail/project-detail.html

@@ -1,46 +1,46 @@
 <!-- 项目详情页面内容 -->
-<div class="project-detail-container">
+<div class="project-detail-container ios-style">
   <!-- 顶部导航/Header -->
-  <header class="project-header-blue">
-    <div class="header-content">
-      <div class="project-info">
-        <h1 class="project-title">{{ project()?.name || '现代简约风格三居室设计' }}</h1>
-        <div class="project-meta">
-          <span class="project-status {{ getProjectStatusClass(project()?.status) }}">
-            {{ project()?.status || '进行中' }}
-          </span>
-          <span class="project-stage">当前阶段:{{ project()?.currentStage || '方案修改与确认' }}</span>
-          <span class="project-date">最后更新:{{ formatDate(currentDate()) }}</span>
+    <header class="project-header ios-header">
+      <div class="header-content">
+        <div class="project-info">
+          <h1 class="project-title">{{ project()?.name || '现代简约风格三居室设计' }}</h1>
+          <div class="project-meta">
+            <span class="project-status {{ getProjectStatusClass(project()?.status) }}">
+              {{ project()?.status || '进行中' }}
+            </span>
+            <span class="project-stage">当前阶段:{{ project()?.currentStage || '方案修改与确认' }}</span>
+            <span class="project-date">最后更新:{{ formatDate(currentDate()) }}</span>
+          </div>
+        </div>
+        <div class="header-actions">
+          <button class="secondary-btn btn-hover-effect">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+              <polyline points="14 2 14 8 20 8"></polyline>
+              <line x1="16" y1="13" x2="8" y2="13"></line>
+              <line x1="16" y1="17" x2="8" y2="17"></line>
+              <polyline points="10 9 9 9 8 9"></polyline>
+            </svg>
+            <span>导出报告</span>
+          </button>
+          <button class="primary-btn btn-hover-effect">
+            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
+              <polyline points="22 4 12 14.01 9 11.01"></polyline>
+            </svg>
+            <span>联系客户</span>
+          </button>
         </div>
       </div>
-      <div class="header-actions">
-        <button class="secondary-btn">
-          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
-            <polyline points="14 2 14 8 20 8"></polyline>
-            <line x1="16" y1="13" x2="8" y2="13"></line>
-            <line x1="16" y1="17" x2="8" y2="17"></line>
-            <polyline points="10 9 9 9 8 9"></polyline>
-          </svg>
-          <span>导出报告</span>
-        </button>
-        <button class="primary-btn">
-          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
-            <polyline points="22 4 12 14.01 9 11.01"></polyline>
-          </svg>
-          <span>联系客户</span>
-        </button>
-      </div>
-    </div>
-  </header>
+    </header>
 
   <!-- 主要内容区域 -->
-  <div class="main-content-area">
+  <div class="main-content-area ios-content">
     <!-- 主内容区 (居中) -->
-    <div class="project-content-main">
+    <div class="project-content-main ios-main">
       <!-- 项目进度卡片 -->
-      <div class="progress-card">
+      <div class="card progress-card">
         <div class="progress-header">
           <h3>项目进度</h3>
           <span class="progress-percentage">{{ completionProgress() }}%</span>
@@ -53,9 +53,60 @@
           <span>预计完成:{{ formatDate(project()?.deadline || '2023-07-15') }}</span>
         </div>
       </div>
+      
+      <!-- 历史服务记录 -->
+      <div class="card historical-records-card">
+        <div class="records-header">
+          <h3>历史服务记录</h3>
+        </div>
+        
+        <!-- 过往咨询记录 -->
+        <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">{{ record.status }}</div>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 合作项目 -->
+        <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>
+          </div>
+        </div>
+        
+        <!-- 历史反馈/评价 -->
+        <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 }" style="color: #FFD700;"></i>
+                </span>
+              </div>
+              <div class="feedback-content">{{ feedback.content }}</div>
+              <div class="feedback-response" *ngIf="feedback.response">
+                <strong>回复:</strong>{{ feedback.response }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
 
       <!-- 进度时间轴 -->
-      <div class="timeline-card">
+      <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">
@@ -90,87 +141,33 @@
       </div>
 
       <!-- 项目详情标签页 -->
-      <div class="project-tabs">
-        <div class="tab-header">
-          <button class="tab-btn" [class.active]="activeTab() === 'overview'" (click)="switchTab('overview')">
-            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-              <path d="M5 12h14M12 5l7 7-7 7"></path>
-            </svg>
-            <span>概览</span>
-          </button>
-          <button class="tab-btn" [class.active]="activeTab() === 'milestones'" (click)="switchTab('milestones')">
-            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-              <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
-            </svg>
-            <span>里程碑</span>
-          </button>
-          <button class="tab-btn" [class.active]="activeTab() === 'tasks'" (click)="switchTab('tasks')">
-            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-              <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
-            </svg>
-            <span>任务</span>
-          </button>
-          <button class="tab-btn" [class.active]="activeTab() === 'messages'" (click)="switchTab('messages')">
-            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-              <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
-            </svg>
-            <span>消息</span>
-          </button>
-          <button class="tab-btn" [class.active]="activeTab() === 'files'" (click)="switchTab('files')">
-            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-              <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
-              <polyline points="14 2 14 8 20 8"></polyline>
-              <line x1="16" y1="13" x2="8" y2="13"></line>
-              <line x1="16" y1="17" x2="8" y2="17"></line>
-              <polyline points="10 9 9 9 8 9"></polyline>
-            </svg>
-            <span>文件</span>
-          </button>
-        </div>
-
-      <!-- 项目进度卡片 -->
-      <div class="progress-card">
-        <div class="progress-header">
-          <h3>项目进度</h3>
-          <span class="progress-percentage">{{ completionProgress() }}%</span>
-        </div>
-        <div class="progress-bar">
-          <div class="progress-fill" [style.width]="progressFillWidth()"></div>
-        </div>
-        <div class="progress-meta">
-          <span>开始时间:{{ formatDate(project()?.createdAt || '2023-06-01') }}</span>
-          <span>预计完成:{{ formatDate(project()?.deadline || '2023-07-15') }}</span>
-        </div>
-      </div>
-
-      <!-- 项目详情标签页 -->
-      <div class="project-tabs">
+      <div class="project-tabs ios-tabs">
         <div class="tab-header">
-          <button class="tab-btn" [class.active]="activeTab() === 'overview'" (click)="switchTab('overview')">
+          <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'overview'" (click)="switchTab('overview')">
             <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
               <path d="M5 12h14M12 5l7 7-7 7"></path>
             </svg>
             <span>概览</span>
           </button>
-          <button class="tab-btn" [class.active]="activeTab() === 'milestones'" (click)="switchTab('milestones')">
+          <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'milestones'" (click)="switchTab('milestones')">
             <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
               <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
             </svg>
             <span>里程碑</span>
           </button>
-          <button class="tab-btn" [class.active]="activeTab() === 'tasks'" (click)="switchTab('tasks')">
+          <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'tasks'" (click)="switchTab('tasks')">
             <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
               <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
             </svg>
             <span>任务</span>
           </button>
-          <button class="tab-btn" [class.active]="activeTab() === 'messages'" (click)="switchTab('messages')">
+          <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'messages'" (click)="switchTab('messages')">
             <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
               <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
             </svg>
             <span>消息</span>
           </button>
-          <button class="tab-btn" [class.active]="activeTab() === 'files'" (click)="switchTab('files')">
+          <button class="tab-btn btn-hover-effect" [class.active]="activeTab() === 'files'" (click)="switchTab('files')">
             <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
               <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
               <polyline points="14 2 14 8 20 8"></polyline>
@@ -182,14 +179,24 @@
           </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>
             </div>
             <div class="message-input-area">
-              <!-- 只使用单向绑定 + input事件,移除双向绑定 -->
               <textarea 
                 [value]="newMessage()"
                 (input)="onMessageInput($event)"
@@ -199,14 +206,14 @@
                 (keydown.enter)="sendMessage()"
               ></textarea>
               <div class="message-actions">
-                <button class="secondary-btn">
+                <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" (click)="sendMessage()" [disabled]="!newMessage().trim()">
+                <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>
@@ -291,29 +298,6 @@
               <h4 class="card-title">客户反馈</h4>
               <div class="feedback-list">
                 <div *ngFor="let feedback of feedbacks()" class="feedback-item">
-                  <!-- 修改前 -->
-                  <div class="feedback-item">
-                    <div class="feedback-header">
-                      <div class="feedback-author">{{ feedback?.customerName || '未知客户' }}</div>
-                      <div class="feedback-rating">
-                        <span class="rating-stars">★★★★☆</span>
-                        <span class="rating-number">{{ feedback?.rating || 0 }}.0</span>
-                      </div>
-                    </div>
-                    <div class="feedback-content">{{ feedback?.content || '' }}</div>
-                    <div class="feedback-response" *ngIf="feedback?.response">
-                      <div class="response-label">客服回复:</div>
-                      <div class="response-text">{{ feedback.response }}</div>
-                    </div>
-                    <div class="feedback-meta">
-                      <span class="feedback-date">{{ formatDate(feedback?.createdAt) }}</span>
-                      <span class="feedback-status" [class.status-processed]="feedback?.status === '已解决'" [class.status-pending]="feedback?.status === '待处理'">
-                        {{ feedback?.status || '未知状态' }}
-                      </span>
-                    </div>
-                  </div>
-                  
-                  <!-- 修改后 -->
                   <div class="feedback-item">
                     <div class="feedback-header">
                       <div class="feedback-author">{{ getFeedbackCustomerName(feedback) }}</div>
@@ -334,7 +318,8 @@
                       </span>
                     </div>
                   </div>
-                  <button class="view-all-btn" *ngIf="feedbacks().length > 0">查看全部反馈</button>
+                </div>
+                <button class="view-all-btn btn-hover-effect" *ngIf="feedbacks().length > 0">查看全部反馈</button>
               </div>
             </div>
           </div>
@@ -365,7 +350,7 @@
                   </div>
                 </div>
                 <div class="milestone-actions" *ngIf="!milestone.isCompleted">
-                  <button class="primary-btn small" (click)="completeMilestone(milestone.id)">
+                  <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>
@@ -514,12 +499,12 @@
                 </div>
               </div>
               <div class="file-actions">
-                <button class="action-btn" (click)="previewFile(file)" title="预览">
+                <button class="action-btn btn-hover-effect" (click)="previewFile(file)" title="预览">
                   <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                     <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
                   </svg>
                 </button>
-                <button class="action-btn" (click)="downloadFile(file)" title="下载">
+                <button class="action-btn btn-hover-effect" (click)="downloadFile(file)" title="下载">
                   <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                     <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
                     <polyline points="7 10 12 15 17 10"></polyline>
@@ -534,7 +519,7 @@
     </div>
 
     <!-- 右侧边栏 - 企业微信聊天集成 -->
-    <div class="wechat-sidebar">
+    <div class="wechat-sidebar ios-sidebar">
       <div class="wechat-header">
         <h3>项目群聊</h3>
         <div class="wechat-actions">
@@ -572,14 +557,14 @@
       <!-- 消息输入框 -->
       <div class="wechat-input-area">
         <div class="input-actions">
-          <button class="action-btn">
+          <button class="action-btn btn-hover-effect">
             <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
               <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
               <circle cx="8.5" cy="8.5" r="1.5"></circle>
               <polyline points="21 15 16 10 5 21"></polyline>
             </svg>
           </button>
-          <button class="action-btn">
+          <button class="action-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>
@@ -593,7 +578,7 @@
           class="wechat-input"
           (keydown.enter)="sendWechatMessage()"
         />
-        <button class="send-btn" (click)="sendWechatMessage()" [disabled]="!wechatInput.trim()">
+        <button class="send-btn btn-hover-effect" (click)="sendWechatMessage()" [disabled]="!wechatInput.trim()">
           发送
         </button>
       </div>
@@ -601,16 +586,16 @@
   </div>
 
   <!-- 售后处理入口 (固定在底部) -->
-  <div class="after-sales-actions">
+  <div class="after-sales-actions ios-actions">
     <div class="actions-container">
-      <button class="action-btn primary" (click)="openModificationRequest()">
+      <button class="action-btn primary btn-hover-effect" (click)="openModificationRequest()">
         <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <path d="M12 20h9"></path>
           <path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
         </svg>
         <span>申请修改</span>
       </button>
-      <button class="action-btn warning" (click)="openComplaintWarning()">
+      <button class="action-btn warning btn-hover-effect" (click)="openComplaintWarning()">
         <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
           <line x1="12" y1="9" x2="12" y2="13"></line>
@@ -618,7 +603,7 @@
         </svg>
         <span>投诉预警</span>
       </button>
-      <button class="action-btn secondary" (click)="openRefundRequest()">
+      <button class="action-btn secondary btn-hover-effect" (click)="openRefundRequest()">
         <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor">
           <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
         </svg>

+ 201 - 23
src/app/pages/customer-service/project-detail/project-detail.scss

@@ -1,21 +1,111 @@
-// 全局变量
-$primary-color: #165DFF;
-$primary-dark: #0d2f5e;
-$success-color: #00B42A;
-$warning-color: #FF7D00;
-$danger-color: #F53F3F;
-$text-primary: #1D2129;
-$text-secondary: #4E5969;
-$text-tertiary: #86909C;
-$border-color: #E5E6EB;
-$background-primary: #FFFFFF;
-$background-secondary: #F2F3F5;
-$background-tertiary: #F7F8FA;
-$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
-$shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
-$shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.1);
-$border-radius: 8px;
-$transition: all 0.3s ease;
+// iOS风格全局变量
+$primary-color: #007AFF;
+$primary-dark: #0047AB;
+$success-color: #34C759;
+$warning-color: #FF9500;
+$danger-color: #FF3B30;
+$text-primary: #1C1C1E;
+$text-secondary: #636366;
+$text-tertiary: #8E8E93;
+$border-color: #D1D1D6;
+$background-primary: #F2F2F7;
+$background-secondary: #FFFFFF;
+$background-tertiary: #E5E5EA;
+$shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
+$shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
+$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);
+
+// iOS风格卡片
+.card {
+  background: $background-secondary;
+  border-radius: $border-radius;
+  padding: 16px;
+  margin-bottom: 16px;
+  box-shadow: $shadow-sm;
+  transition: $transition;
+  
+  &:hover {
+    box-shadow: $shadow-md;
+  }
+}
+
+// iOS风格按钮
+.button {
+  border-radius: $border-radius;
+  padding: 12px 20px;
+  font-weight: 500;
+  transition: $transition;
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  
+  &-primary {
+    background: $primary-color;
+    color: white;
+    
+    &:active {
+      background: #0E42CB;
+    }
+  }
+  
+  &-secondary {
+    background: $background-tertiary;
+    color: $text-primary;
+    
+    &:active {
+      background: #D1D1D6;
+    }
+  }
+}
+
+// 按钮点击反馈效果
+.btn-hover-effect {
+  transition: $transition;
+  transform: scale(1);
+  position: relative;
+  overflow: hidden;
+}
+
+.btn-hover-effect:active {
+  transform: scale(0.95);
+  transition: all 0.1s ease;
+}
+
+.btn-hover-effect::after {
+  content: '';
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  width: 0;
+  height: 0;
+  border-radius: 50%;
+  background-color: rgba(255, 255, 255, 0.3);
+  transform: translate(-50%, -50%);
+  transition: width 0.6s, height 0.6s;
+}
+
+.btn-hover-effect:active::after {
+  width: 300px;
+  height: 300px;
+}
+
+// 主要按钮的特定点击效果
+.primary-btn.btn-hover-effect:active {
+  background-color: #0E42CB;
+}
+
+// 次要按钮的特定点击效果
+.secondary-btn.btn-hover-effect:active {
+  background-color: #E5E6EB;
+}
+
+// 操作按钮的特定点击效果
+.action-btn.btn-hover-effect:active {
+  transform: scale(0.9);
+  background-color: color-mix(in srgb, $primary-color 5%, transparent);
+}
 
 // 主容器
 .project-detail-container {
@@ -560,19 +650,81 @@ $transition: all 0.3s ease;
   padding: 20px;
 }
 
-// 动画效果
+// iOS风格动画效果
 @keyframes pulse {
   0% {
-    box-shadow: 0 0 0 0 rgba(22, 93, 255, 0.4);
+    transform: scale(1);
+    opacity: 1;
   }
-  70% {
-    box-shadow: 0 0 0 6px rgba(22, 93, 255, 0);
+  50% {
+    transform: scale(0.95);
+    opacity: 0.8;
   }
   100% {
-    box-shadow: 0 0 0 0 rgba(22, 93, 255, 0);
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
   }
 }
 
+@keyframes sending {
+  0% {
+    transform: scale(1);
+  }
+  50% {
+    transform: scale(0.9);
+  }
+  100% {
+    transform: scale(1);
+  }
+}
+
+@keyframes completing {
+  0% {
+    transform: translateY(0);
+    opacity: 1;
+  }
+  100% {
+    transform: translateY(-20px);
+    opacity: 0;
+  }
+}
+
+// 按钮发送动画
+.sending {
+  animation: sending 0.3s ease;
+}
+
+// 任务完成动画
+.completing {
+  animation: completing 0.5s ease forwards;
+}
+
+// 消息进入动画
+.message-item {
+  animation: fadeIn 0.3s ease;
+}
+
+// iOS风格弹性动画
+.ios-bounce {
+  transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+}
+
+// 标签切换动画
+.tab-content {
+  animation: fadeIn 0.3s ease;
+}
+
 // 响应式设计
 @media (max-width: 1200px) {
   .wechat-sidebar {
@@ -1707,6 +1859,32 @@ $text-light: $text-tertiary;
   }
 }
 
+// iOS风格按钮点击效果
+.button {
+  &:active {
+    transform: scale(0.96);
+    transition: transform 0.1s ease;
+  }
+  
+  &-primary:active {
+    background: #0E42CB;
+  }
+  
+  &-secondary:active {
+    background: #D1D1D6;
+  }
+}
+
+// 卡片悬停效果
+.card {
+  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+  
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: $shadow-md;
+  }
+}
+
 // 响应式设计
 @media (max-width: 1024px) {
   .overview-grid {

+ 197 - 16
src/app/pages/customer-service/project-detail/project-detail.ts

@@ -2,8 +2,12 @@ import { Component, OnInit, signal, computed, ViewChild, AfterViewChecked } from
 import { CommonModule } from '@angular/common';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule, ActivatedRoute } 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 { ModificationRequestDialog } from './modification-request-dialog';
+import { ComplaintWarningDialog } from './complaint-warning-dialog';
+import { RefundRequestDialog } from './refund-request-dialog';
 
 // 定义项目阶段接口
 interface ProjectStage {
@@ -23,10 +27,37 @@ interface WechatMessage {
   timestamp: Date;
 }
 
+// 定义历史咨询记录接口
+interface ConsultationRecord {
+  id: string;
+  date: Date;
+  content: string;
+  status: string;
+}
+
+// 定义合作项目接口
+interface CooperationProject {
+  id: string;
+  name: string;
+  startDate: Date;
+  endDate?: Date;
+  status: string;
+  description: string;
+}
+
+// 定义历史反馈接口
+interface HistoricalFeedback {
+  id: string;
+  date: Date;
+  content: string;
+  rating: number;
+  response?: string;
+}
+
 @Component({
   selector: 'app-project-detail',
   standalone: true,
-  imports: [CommonModule, FormsModule, ReactiveFormsModule, RouterModule],
+  imports: [CommonModule, FormsModule, ReactiveFormsModule, RouterModule, MatDialogModule],
   templateUrl: './project-detail.html',
   styleUrls: ['./project-detail.scss', '../customer-service-styles.scss']
 })
@@ -126,6 +157,11 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
   wechatInput = '';
   scrollToBottom = false;
   
+  // 历史服务记录相关
+  consultationRecords = signal<ConsultationRecord[]>([]);
+  cooperationProjects = signal<CooperationProject[]>([]);
+  historicalFeedbacks = signal<HistoricalFeedback[]>([]);
+  
   // 售后处理弹窗状态
   showModificationRequest = false;
   showComplaintWarning = false;
@@ -149,7 +185,8 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
   
   constructor(
     private route: ActivatedRoute,
-    private projectService: ProjectService
+    private projectService: ProjectService,
+    private dialog: MatDialog
   ) {
     // 获取路由参数中的项目ID
     this.route.paramMap.subscribe(params => {
@@ -180,6 +217,9 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
     // 初始化企业微信聊天
     this.initWechatMessages();
     
+    // 初始化历史服务记录
+    this.initHistoricalServiceRecords();
+    
     // 模拟里程碑数据
     this.milestones.set([
       { 
@@ -417,9 +457,16 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
     this.activeTab.set(tab);
   }
   
-  // 发送消息
+  // 增强版发送消息功能
   sendMessage(): void {
     if (this.newMessage().trim()) {
+      // 添加发送动画效果
+      const sendBtn = document.querySelector('.message-actions .primary-btn');
+      if (sendBtn) {
+        sendBtn.classList.add('sending');
+        setTimeout(() => sendBtn.classList.remove('sending'), 300);
+      }
+
       const newMsg: Message = {
         id: `msg${Date.now()}`,
         sender: '客服小李',
@@ -431,19 +478,36 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
       
       this.messages.set([...this.messages(), newMsg]);
       this.newMessage.set('');
+      
+      // 自动滚动到底部
+      setTimeout(() => {
+        const container = document.querySelector('.messages-list');
+        if (container) {
+          container.scrollTop = container.scrollHeight;
+        }
+      }, 100);
     }
   }
   
-  // 完成任务
-  // 修复 completeTask 方法中的类型问题
+  // 增强版完成任务功能
   completeTask(taskId: string): void {
+    // 添加完成动画效果
+    const taskElement = document.querySelector(`.task-item[data-id="${taskId}"]`);
+    if (taskElement) {
+      taskElement.classList.add('completing');
+      setTimeout(() => taskElement.classList.remove('completing'), 500);
+    }
+
     this.tasks.set(
       this.tasks().map(task => 
         task.id === taskId 
-          ? { ...task, isCompleted: true, completedDate: new Date(), isOverdue: false } // 添加 isOverdue 确保类型安全
+          ? { ...task, isCompleted: true, completedDate: new Date(), isOverdue: false }
           : task
       )
     );
+    
+    // 播放完成音效
+    this.playSound('complete');
   }
   
   // 修复 completeMilestone 方法中的类型问题
@@ -594,10 +658,17 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
     }, 100);
   }
   
-  // 发送企业微信消息
+  // 增强版发送企业微信消息
   sendWechatMessage(): void {
     if (!this.wechatInput.trim()) return;
     
+    // 添加发送动画
+    const sendBtn = document.querySelector('.wechat-input-area .send-btn');
+    if (sendBtn) {
+      sendBtn.classList.add('sending');
+      setTimeout(() => sendBtn.classList.remove('sending'), 300);
+    }
+    
     const newMessage: WechatMessage = {
       sender: '客服小李',
       content: this.wechatInput.trim(),
@@ -608,6 +679,9 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
     this.wechatInput = '';
     this.scrollToBottom = true;
     
+    // 播放发送音效
+    this.playSound('message');
+    
     // 模拟对方回复
     setTimeout(() => {
       const replyMessage: WechatMessage = {
@@ -618,8 +692,80 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
       
       this.wechatMessagesList = [...this.wechatMessagesList, replyMessage];
       this.scrollToBottom = true;
+      
+      // 播放接收音效
+      this.playSound('notification');
     }, 2000);
   }
+
+  // 新增播放音效方法
+  playSound(type: 'message' | 'notification' | 'complete'): void {
+    // 实际项目中这里会播放对应的音效
+    console.log(`播放${type}音效`);
+  }
+  
+  // 初始化历史服务记录
+  initHistoricalServiceRecords(): void {
+    // 模拟过往咨询记录
+    this.consultationRecords.set([
+      {
+        id: 'cons1',
+        date: new Date('2023-01-15'),
+        content: '咨询关于厨房改造的可行性和预算',
+        status: '已解决'
+      },
+      {
+        id: 'cons2',
+        date: new Date('2023-03-20'),
+        content: '询问装修材料环保认证相关问题',
+        status: '已解决'
+      },
+      {
+        id: 'cons3',
+        date: new Date('2023-05-10'),
+        content: '了解装修分期付款方案',
+        status: '已解决'
+      }
+    ]);
+    
+    // 模拟合作项目记录
+    this.cooperationProjects.set([
+      {
+        id: 'proj1',
+        name: '2022年现代简约卧室设计项目',
+        startDate: new Date('2022-08-15'),
+        endDate: new Date('2022-10-30'),
+        status: '已完成',
+        description: '为客户设计并实施了现代简约风格的卧室改造,包括定制衣柜和床头背景墙'
+      },
+      {
+        id: 'proj2',
+        name: '2023年欧式厨房设计项目',
+        startDate: new Date('2023-02-01'),
+        endDate: new Date('2023-04-15'),
+        status: '已完成',
+        description: '设计并安装了全套欧式风格厨房,包括橱柜、台面和电器选型'
+      }
+    ]);
+    
+    // 模拟历史反馈/评价
+    this.historicalFeedbacks.set([
+      {
+        id: 'fb1',
+        date: new Date('2022-11-05'),
+        content: '卧室设计非常满意,空间利用合理,风格符合预期',
+        rating: 5,
+        response: '感谢您的好评,我们会继续努力为您提供优质服务'
+      },
+      {
+        id: 'fb2',
+        date: new Date('2023-04-20'),
+        content: '厨房装修质量很好,但工期比预期稍长',
+        rating: 4,
+        response: '感谢您的反馈,我们已经优化了施工流程,会在后续项目中改进'
+      }
+    ]);
+  }
   
   // 格式化为时间显示
   formatTime(date: Date): string {
@@ -637,20 +783,55 @@ export class ProjectDetail implements OnInit, AfterViewChecked {
   
   // 售后处理入口方法
   openModificationRequest(): void {
-    this.showModificationRequest = true;
-    // 实际项目中这里可以打开申请修改的模态框或抽屉组件
-    console.log('打开申请修改弹窗');
+    const dialogRef = this.dialog.open(ModificationRequestDialog, {
+      width: '500px',
+      data: {
+        projectId: this.projectId,
+        projectName: this.project()?.name || '现代简约风格三居室设计',
+        projectStatus: this.project()?.status || '进行中'
+      }
+    });
+
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        console.log('申请修改提交成功', result);
+        // 这里可以添加提交成功后的处理逻辑,如刷新数据、显示通知等
+      }
+    });
   }
   
   openComplaintWarning(): void {
-    this.showComplaintWarning = true;
-    // 实际项目中这里可以打开投诉预警的模态框或抽屉组件
-    console.log('打开投诉预警弹窗');
+    const dialogRef = this.dialog.open(ComplaintWarningDialog, {
+      width: '500px',
+      data: {
+        projectId: this.projectId,
+        projectName: this.project()?.name || '现代简约风格三居室设计'
+      }
+    });
+
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        console.log('投诉预警提交成功', result);
+        // 这里可以添加提交成功后的处理逻辑
+      }
+    });
   }
   
   openRefundRequest(): void {
-    this.showRefundRequest = true;
-    // 实际项目中这里可以打开申请退款的模态框或抽屉组件
-    console.log('打开申请退款弹窗');
+    const dialogRef = this.dialog.open(RefundRequestDialog, {
+      width: '500px',
+      data: {
+        projectId: this.projectId,
+        projectName: this.project()?.name || '现代简约风格三居室设计',
+        projectAmount: 85000 // 模拟项目金额
+      }
+    });
+
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        console.log('申请退款提交成功', result);
+        // 这里可以添加提交成功后的处理逻辑
+      }
+    });
   }
 }

+ 185 - 0
src/app/pages/customer-service/project-detail/refund-request-dialog.ts

@@ -0,0 +1,185 @@
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { Component, Inject } from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
+import { MatIconModule } from '@angular/material/icon';
+
+interface RefundRequestData {
+  projectId: string;
+  projectName: string;
+  projectAmount: number;
+}
+
+@Component({
+  selector: 'app-refund-request-dialog',
+  standalone: true,
+  imports: [
+    CommonModule,
+    FormsModule,
+    MatButtonModule,
+    MatDialogModule,
+    MatFormFieldModule,
+    MatInputModule,
+    MatSelectModule,
+    MatIconModule
+  ],
+  template: `
+    <h2 mat-dialog-title>申请退款</h2>
+    <mat-dialog-content>
+      <div class="dialog-content">
+        <div class="project-info">
+          <p><strong>项目名称:</strong> {{ data.projectName }}</p>
+          <p><strong>总金额:</strong> ¥{{ data.projectAmount.toLocaleString() }}</p>
+        </div>
+        
+        <div class="form-group">
+          <label for="refund-type">退款类型 *</label>
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-select [(ngModel)]="refundType" id="refund-type" name="refundType" required>
+              <mat-option value="full">全额退款</mat-option>
+              <mat-option value="partial">部分退款</mat-option>
+            </mat-select>
+          </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>
+        
+        <div class="form-group">
+          <label for="refund-reason">退款原因 *</label>
+          <mat-form-field appearance="outline" class="form-field">
+            <textarea 
+              matInput 
+              [(ngModel)]="refundReason" 
+              id="refund-reason" 
+              name="refundReason" 
+              rows="4" 
+              placeholder="请详细描述退款原因..."
+              required
+            ></textarea>
+          </mat-form-field>
+        </div>
+        
+        <div class="form-group">
+          <label for="refund-account">退款账户信息</label>
+          <mat-form-field appearance="outline" class="form-field">
+            <input 
+              matInput 
+              [(ngModel)]="refundAccount" 
+              id="refund-account" 
+              name="refundAccount" 
+              type="text" 
+              placeholder="请提供退款账户信息(如银行卡号/支付宝等)"
+            >
+          </mat-form-field>
+        </div>
+      </div>
+    </mat-dialog-content>
+    <mat-dialog-actions align="end">
+      <button mat-button (click)="onCancel()">取消</button>
+      <button mat-button color="primary" (click)="onSubmit()" [disabled]="!isFormValid()">
+        提交申请
+      </button>
+    </mat-dialog-actions>
+  `,
+  styles: [`
+    .dialog-content {
+      padding: 10px 0;
+    }
+    
+    .project-info {
+      padding: 12px;
+      background-color: #ffebee;
+      border-radius: 8px;
+      margin-bottom: 16px;
+    }
+    
+    .form-group {
+      margin-bottom: 20px;
+    }
+    
+    label {
+      display: block;
+      margin-bottom: 6px;
+      font-weight: 500;
+      color: rgba(0, 0, 0, 0.87);
+    }
+    
+    p {
+      margin: 0;
+      color: rgba(0, 0, 0, 0.6);
+    }
+    
+    .form-field {
+      width: 100%;
+    }
+    
+    .mat-mdc-dialog-actions {
+      padding: 16px 24px;
+    }
+  `]
+}) 
+export class RefundRequestDialog {
+  refundType: string = 'full';
+  refundAmount: number = 0;
+  refundReason: string = '';
+  refundAccount: string = '';
+
+  constructor(
+    public dialogRef: MatDialogRef<RefundRequestDialog>,
+    @Inject(MAT_DIALOG_DATA) public data: RefundRequestData
+  ) {}
+
+  onCancel(): void {
+    this.dialogRef.close();
+  }
+
+  isFormValid(): boolean {
+    if (!this.refundType || !this.refundReason.trim()) {
+      return false;
+    }
+    
+    if (this.refundType === 'partial' && (this.refundAmount <= 0 || this.refundAmount > this.data.projectAmount)) {
+      return false;
+    }
+    
+    return true;
+  }
+
+  onSubmit(): void {
+    if (!this.isFormValid()) return;
+    
+    const formData = {
+      projectId: this.data.projectId,
+      projectName: this.data.projectName,
+      refundType: this.refundType,
+      totalAmount: this.data.projectAmount,
+      refundAmount: this.refundType === 'full' ? this.data.projectAmount : this.refundAmount,
+      refundReason: this.refundReason,
+      refundAccount: this.refundAccount,
+      requestedAt: new Date().toISOString()
+    };
+    
+    this.dialogRef.close({ confirmed: true, data: formData });
+  }
+}

+ 42 - 26
src/app/pages/customer-service/project-list/project-list.scss

@@ -611,6 +611,10 @@ $transition: all 0.3s ease;
           &:hover {
             background-color: #1565c0;
           }
+          
+          &:active {
+            background-color: #0E42CB;
+          }
         }
 
         .secondary-btn {
@@ -621,6 +625,10 @@ $transition: all 0.3s ease;
           &:hover {
             background-color: $border-color;
           }
+          
+          &:active {
+            background-color: #D1D1D6;
+          }
         }
 
         .card-action {
@@ -642,36 +650,44 @@ $transition: all 0.3s ease;
     box-shadow: $box-shadow;
 
     .pagination-btn {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      width: 36px;
-      height: 36px;
-      border: 1px solid $border-color;
-      background-color: $bg-white;
-      color: $text-secondary;
-      border-radius: 4px;
-      cursor: pointer;
-      transition: $transition;
-      font-size: 14px;
-      font-weight: 500;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 36px;
+        height: 36px;
+        border: 1px solid $border-color;
+        background-color: $bg-white;
+        color: $text-secondary;
+        border-radius: 4px;
+        cursor: pointer;
+        transition: $transition;
+        font-size: 14px;
+        font-weight: 500;
 
-      &:hover:not(:disabled) {
-        color: $primary-color;
-        border-color: $primary-color;
-      }
+        &:hover:not(:disabled) {
+          color: $primary-color;
+          border-color: $primary-color;
+        }
 
-      &.active {
-        color: white;
-        background-color: $primary-color;
-        border-color: $primary-color;
-      }
+        &:active:not(:disabled):not(.active) {
+          background-color: $primary-light;
+        }
 
-      &:disabled {
-        opacity: 0.5;
-        cursor: not-allowed;
+        &.active {
+          color: white;
+          background-color: $primary-color;
+          border-color: $primary-color;
+        }
+
+        &.active:active {
+          background-color: #0E42CB;
+        }
+
+        &:disabled {
+          opacity: 0.5;
+          cursor: not-allowed;
+        }
       }
-    }
 
     .pagination-ellipsis {
       font-size: 14px;

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

@@ -290,7 +290,7 @@ export class ProjectList implements OnInit {
   goToPage(page: number): void {
     if (page >= 1 && page <= this.totalPages() && page !== this.currentPage()) {
       this.currentPage.set(page);
-      this.loadProjects();
+      // 不需要重新加载整个项目列表,currentPage变更后computed属性会自动更新
     }
   }
 

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

@@ -450,4 +450,38 @@ export class ProjectService {
     console.log('记录材料下载:', materialId);
     return of(void 0);
   }
+
+  // 创建项目
+  createProject(projectData: {
+    customerId: string;
+    customerName: string;
+    requirement: any;
+    referenceCases: any[];
+    tags: {
+      demandType?: string;
+      preferenceTags?: string[];
+      followUpStatus?: string;
+    };
+  }): Observable<{ success: boolean; projectId: string }> {
+    // 模拟API调用
+    console.log('创建项目:', projectData);
+    // 模拟返回创建的项目ID
+    return of({ success: true, projectId: 'new-project-' + Date.now() });
+  }
+
+  // 创建项目群
+  createProjectGroup(groupData: {
+    customerId: string;
+    customerName: string;
+    tags?: {
+      demandType?: string;
+      preferenceTags?: string[];
+      followUpStatus?: string;
+    };
+  }): Observable<{ success: boolean; groupId: string }> {
+    // 模拟API调用
+    console.log('创建项目群:', groupData);
+    // 模拟返回创建的群组ID
+    return of({ success: true, groupId: 'new-group-' + Date.now() });
+  }
 }

+ 7 - 1
src/styles.scss

@@ -6,13 +6,16 @@
 // custom components at https://material.angular.dev/guide/theming
 @use '@angular/material' as mat;
 
+// 自定义字体配置
+@include mat.core();
+
 html {
   @include mat.theme((
     color: (
       primary: mat.$azure-palette,
       tertiary: mat.$blue-palette,
     ),
-    typography: Roboto,
+    // 不指定typography配置,避免自动加载
     density: 0,
   ));
 }
@@ -33,6 +36,9 @@ body {
   // Reset the user agent margin.
   margin: 0;
 }
+/* 从本地npm包引入Roboto字体,避免网络请求 */
+@import 'roboto-fontface/css/roboto/roboto-fontface.css';
+
 /* You can add global styles to this file, and also import other style files */
 
 html, body { height: 100%; }

部分文件因为文件数量过多而无法显示