徐福静0235668 hai 3 días
pai
achega
bad0e181d3

+ 9 - 0
src/app/app.routes.ts

@@ -46,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 = [
   // 客服路由
@@ -128,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();
+  }
+}

+ 0 - 168
src/app/pages/admin/dashboard/components/write-content/write-content.component.html

@@ -1,168 +0,0 @@
-<div class="write-content">
-  <!-- 步骤 1: 选择内容类型 -->
-  <div class="step-content" *ngIf="currentStep() === 1">
-    <div class="content-types-grid">
-      <div 
-        class="content-type-card" 
-        *ngFor="let type of contentTypes"
-        (click)="selectContentType(type)"
-        [style.border-color]="type.color"
-      >
-        <div class="card-icon" [style.background-color]="type.color">
-          <svg viewBox="0 0 24 24">
-            <path [attr.d]="type.icon"/>
-          </svg>
-        </div>
-        <div class="card-content">
-          <h3 class="card-title">{{ type.title }}</h3>
-          <p class="card-description">{{ type.description }}</p>
-        </div>
-        <div class="card-arrow">
-          <svg viewBox="0 0 24 24">
-            <path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/>
-          </svg>
-        </div>
-      </div>
-    </div>
-  </div>
-
-  <!-- 步骤 2: 填写表单 -->
-  <div class="step-content" *ngIf="currentStep() === 2">
-    <div class="form-header">
-      <button class="back-button" (click)="goBack()">
-        <svg viewBox="0 0 24 24">
-          <path d="M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"/>
-        </svg>
-        返回
-      </button>
-      <div class="selected-type">
-        <div class="type-icon" [style.background-color]="selectedContentType()?.color">
-          <svg viewBox="0 0 24 24">
-            <path [attr.d]="selectedContentType()?.icon"/>
-          </svg>
-        </div>
-        <span>{{ selectedContentType()?.title }}</span>
-      </div>
-    </div>
-
-    <form class="content-form" (ngSubmit)="submitForm()">
-      <!-- 标题 -->
-      <div class="form-group">
-        <label for="title" class="form-label">标题 *</label>
-        <input 
-            type="text" 
-            id="title" 
-            class="form-input"
-            placeholder="请输入标题"
-            [value]="formData().title"
-            (input)="updateFormField('title', $any($event.target).value || '')"
-            required
-          >
-      </div>
-
-      <!-- 描述 -->
-      <div class="form-group">
-        <label for="description" class="form-label">描述 *</label>
-        <textarea 
-          id="description" 
-          class="form-textarea"
-          placeholder="请输入描述"
-          rows="3"
-          [value]="formData().description"
-          (input)="updateFormField('description', $any($event.target).value || '')"
-          required
-        ></textarea>
-      </div>
-
-      <!-- 详细内容 -->
-      <div class="form-group">
-        <label for="content" class="form-label">详细内容</label>
-        <textarea 
-          id="content" 
-          class="form-textarea content-textarea"
-          placeholder="请输入详细内容"
-          rows="6"
-          [value]="formData().content"
-          (input)="updateFormField('content', $any($event.target).value || '')"
-        ></textarea>
-      </div>
-
-      <!-- 标签 -->
-      <div class="form-group">
-        <label class="form-label">标签</label>
-        <div class="tags-container">
-          <div class="tags-list">
-            <span 
-              class="tag" 
-              *ngFor="let tag of formData().tags"
-              (click)="removeTag(tag)"
-            >
-              {{ tag }}
-              <svg class="tag-remove" viewBox="0 0 24 24">
-                <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
-              </svg>
-            </span>
-          </div>
-          <div class="tag-input-container">
-            <input 
-              type="text" 
-              class="tag-input"
-              placeholder="添加标签"
-              [value]="newTag()"
-              (input)="newTag.set($any($event.target).value || '')"
-              (keypress)="onTagKeyPress($event)"
-            >
-            <button type="button" class="add-tag-button" (click)="addTag()" [disabled]="!newTag().trim()">
-              <svg viewBox="0 0 24 24">
-                <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
-              </svg>
-            </button>
-          </div>
-        </div>
-      </div>
-
-      <!-- 优先级和截止日期 -->
-      <div class="form-row">
-        <div class="form-group">
-          <label for="priority" class="form-label">优先级</label>
-          <select 
-            id="priority" 
-            class="form-select"
-            [value]="formData().priority"
-            (change)="updateFormField('priority', $any($event.target).value || '')"
-          >
-            <option value="low">低</option>
-            <option value="medium">中</option>
-            <option value="high">高</option>
-          </select>
-        </div>
-
-        <div class="form-group">
-          <label for="dueDate" class="form-label">截止日期</label>
-          <input 
-            type="date" 
-            id="dueDate" 
-            class="form-input"
-            [value]="formData().dueDate"
-            (input)="updateFormField('dueDate', $any($event.target).value || '')"
-          >
-        </div>
-      </div>
-
-      <!-- 提交按钮 -->
-      <div class="form-actions">
-        <button type="button" class="cancel-button" (click)="onClose()">
-          取消
-        </button>
-        <button 
-          type="submit" 
-          class="submit-button"
-          [disabled]="!isFormValid()"
-          [style.background-color]="selectedContentType()?.color"
-        >
-          创建{{ selectedContentType()?.title }}
-        </button>
-      </div>
-    </form>
-  </div>
-</div>

+ 0 - 448
src/app/pages/admin/dashboard/components/write-content/write-content.component.scss

@@ -1,448 +0,0 @@
-// 变量定义
-$primary-color: #165DFF;
-$success-color: #00B42A;
-$warning-color: #FF7D00;
-$danger-color: #F53F3F;
-$text-color: #1D2129;
-$text-color-secondary: #4E5969;
-$border-color: #E5E6EB;
-$bg-color: #F7F8FA;
-$card-bg: #FFFFFF;
-$shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
-$shadow-hover: 0 8px 24px rgba(0, 0, 0, 0.12);
-$border-radius: 12px;
-$transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-
-.write-content {
-  .step-content {
-    animation: slideIn 0.3s ease;
-  }
-
-  // 内容类型选择网格
-  .content-types-grid {
-    display: grid;
-    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
-    gap: 20px;
-    padding: 8px;
-  }
-
-  // 内容类型卡片
-  .content-type-card {
-    background: $card-bg;
-    border: 2px solid $border-color;
-    border-radius: $border-radius;
-    padding: 24px;
-    cursor: pointer;
-    transition: $transition;
-    display: flex;
-    align-items: center;
-    gap: 16px;
-    position: relative;
-    overflow: hidden;
-
-    &::before {
-      content: '';
-      position: absolute;
-      top: 0;
-      left: 0;
-      right: 0;
-      height: 4px;
-      background: currentColor;
-      transform: scaleX(0);
-      transition: $transition;
-    }
-
-    &:hover {
-      transform: translateY(-4px);
-      box-shadow: $shadow-hover;
-      border-color: currentColor;
-
-      &::before {
-        transform: scaleX(1);
-      }
-
-      .card-arrow {
-        transform: translateX(4px);
-      }
-    }
-
-    .card-icon {
-      width: 48px;
-      height: 48px;
-      border-radius: 12px;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      flex-shrink: 0;
-
-      svg {
-        width: 24px;
-        height: 24px;
-        fill: white;
-      }
-    }
-
-    .card-content {
-      flex: 1;
-
-      .card-title {
-        font-size: 18px;
-        font-weight: 700;
-        color: $text-color;
-        margin: 0 0 8px 0;
-      }
-
-      .card-description {
-        font-size: 14px;
-        color: $text-color-secondary;
-        margin: 0;
-        line-height: 1.4;
-      }
-    }
-
-    .card-arrow {
-      width: 24px;
-      height: 24px;
-      color: $text-color-secondary;
-      transition: $transition;
-
-      svg {
-        width: 100%;
-        height: 100%;
-        fill: currentColor;
-      }
-    }
-  }
-
-  // 表单头部
-  .form-header {
-    display: flex;
-    align-items: center;
-    gap: 16px;
-    margin-bottom: 32px;
-    padding-bottom: 16px;
-    border-bottom: 2px solid $border-color;
-
-    .back-button {
-      display: flex;
-      align-items: center;
-      gap: 8px;
-      background: none;
-      border: 1px solid $border-color;
-      border-radius: 8px;
-      padding: 8px 16px;
-      color: $text-color-secondary;
-      cursor: pointer;
-      transition: $transition;
-      font-size: 14px;
-      font-weight: 500;
-
-      &:hover {
-        background: $bg-color;
-        border-color: $primary-color;
-        color: $primary-color;
-      }
-
-      svg {
-        width: 16px;
-        height: 16px;
-        fill: currentColor;
-      }
-    }
-
-    .selected-type {
-      display: flex;
-      align-items: center;
-      gap: 12px;
-      font-size: 16px;
-      font-weight: 600;
-      color: $text-color;
-
-      .type-icon {
-        width: 32px;
-        height: 32px;
-        border-radius: 8px;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-
-        svg {
-          width: 16px;
-          height: 16px;
-          fill: white;
-        }
-      }
-    }
-  }
-
-  // 表单样式
-  .content-form {
-    .form-group {
-      margin-bottom: 24px;
-
-      .form-label {
-        display: block;
-        font-size: 14px;
-        font-weight: 600;
-        color: $text-color;
-        margin-bottom: 8px;
-      }
-
-      .form-input,
-      .form-textarea,
-      .form-select {
-        width: 100%;
-        padding: 12px 16px;
-        border: 2px solid $border-color;
-        border-radius: 8px;
-        font-size: 14px;
-        color: $text-color;
-        background: $card-bg;
-        transition: $transition;
-        font-family: inherit;
-
-        &:focus {
-          outline: none;
-          border-color: $primary-color;
-          box-shadow: 0 0 0 3px rgba(22, 93, 255, 0.1);
-        }
-
-        &::placeholder {
-          color: $text-color-secondary;
-        }
-      }
-
-      .form-textarea {
-        resize: vertical;
-        min-height: 80px;
-
-        &.content-textarea {
-          min-height: 120px;
-        }
-      }
-    }
-
-    .form-row {
-      display: grid;
-      grid-template-columns: 1fr 1fr;
-      gap: 20px;
-
-      @media (max-width: 600px) {
-        grid-template-columns: 1fr;
-      }
-    }
-  }
-
-  // 标签容器
-  .tags-container {
-    .tags-list {
-      display: flex;
-      flex-wrap: wrap;
-      gap: 8px;
-      margin-bottom: 12px;
-
-      .tag {
-        display: inline-flex;
-        align-items: center;
-        gap: 6px;
-        background: rgba(22, 93, 255, 0.1);
-        color: $primary-color;
-        padding: 6px 12px;
-        border-radius: 20px;
-        font-size: 12px;
-        font-weight: 500;
-        cursor: pointer;
-        transition: $transition;
-
-        &:hover {
-          background: rgba(22, 93, 255, 0.2);
-
-          .tag-remove {
-            opacity: 1;
-          }
-        }
-
-        .tag-remove {
-          width: 14px;
-          height: 14px;
-          fill: currentColor;
-          opacity: 0.6;
-          transition: $transition;
-        }
-      }
-    }
-
-    .tag-input-container {
-      display: flex;
-      gap: 8px;
-
-      .tag-input {
-        flex: 1;
-        padding: 8px 12px;
-        border: 1px solid $border-color;
-        border-radius: 6px;
-        font-size: 13px;
-
-        &:focus {
-          outline: none;
-          border-color: $primary-color;
-        }
-      }
-
-      .add-tag-button {
-        width: 36px;
-        height: 36px;
-        background: $primary-color;
-        border: none;
-        border-radius: 6px;
-        color: white;
-        cursor: pointer;
-        transition: $transition;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-
-        &:hover:not(:disabled) {
-          background: darken($primary-color, 10%);
-          transform: scale(1.05);
-        }
-
-        &:disabled {
-          background: $border-color;
-          cursor: not-allowed;
-        }
-
-        svg {
-          width: 16px;
-          height: 16px;
-          fill: currentColor;
-        }
-      }
-    }
-  }
-
-  // 表单操作按钮
-  .form-actions {
-    display: flex;
-    gap: 16px;
-    justify-content: flex-end;
-    margin-top: 32px;
-    padding-top: 24px;
-    border-top: 1px solid $border-color;
-
-    .cancel-button,
-    .submit-button {
-      padding: 12px 24px;
-      border-radius: 8px;
-      font-size: 14px;
-      font-weight: 600;
-      cursor: pointer;
-      transition: $transition;
-      border: none;
-    }
-
-    .cancel-button {
-      background: $bg-color;
-      color: $text-color-secondary;
-      border: 1px solid $border-color;
-
-      &:hover {
-        background: darken($bg-color, 5%);
-        border-color: $text-color-secondary;
-      }
-    }
-
-    .submit-button {
-      background: $primary-color;
-      color: white;
-      min-width: 120px;
-
-      &:hover:not(:disabled) {
-        transform: translateY(-2px);
-        box-shadow: 0 4px 12px rgba(22, 93, 255, 0.3);
-      }
-
-      &:disabled {
-        background: $border-color;
-        cursor: not-allowed;
-        transform: none;
-        box-shadow: none;
-      }
-    }
-  }
-}
-
-// 动画
-@keyframes slideIn {
-  from {
-    opacity: 0;
-    transform: translateX(20px);
-  }
-  to {
-    opacity: 1;
-    transform: translateX(0);
-  }
-}
-
-// 响应式设计
-@media (max-width: 768px) {
-  .write-content {
-    .content-types-grid {
-      grid-template-columns: 1fr;
-    }
-
-    .content-type-card {
-      padding: 20px;
-
-      .card-icon {
-        width: 40px;
-        height: 40px;
-
-        svg {
-          width: 20px;
-          height: 20px;
-        }
-      }
-
-      .card-content {
-        .card-title {
-          font-size: 16px;
-        }
-
-        .card-description {
-          font-size: 13px;
-        }
-      }
-    }
-
-    .form-actions {
-      flex-direction: column;
-
-      .cancel-button,
-      .submit-button {
-        width: 100%;
-      }
-    }
-  }
-}
-
-@media (max-width: 480px) {
-  .write-content {
-    .content-type-card {
-      padding: 16px;
-      flex-direction: column;
-      text-align: center;
-      gap: 12px;
-
-      .card-arrow {
-        transform: rotate(90deg);
-      }
-    }
-
-    .form-header {
-      flex-direction: column;
-      align-items: flex-start;
-      gap: 12px;
-    }
-  }
-}

+ 0 - 150
src/app/pages/admin/dashboard/components/write-content/write-content.component.ts

@@ -1,150 +0,0 @@
-import { Component, EventEmitter, Output, signal } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { FormsModule } from '@angular/forms';
-
-interface ContentType {
-  id: string;
-  title: string;
-  description: string;
-  icon: string;
-  color: string;
-}
-
-interface FormData {
-  title: string;
-  description: string;
-  content: string;
-  tags: string[];
-  priority: 'low' | 'medium' | 'high';
-  dueDate: string;
-}
-
-@Component({
-  selector: 'app-write-content',
-  standalone: true,
-  imports: [CommonModule, FormsModule],
-  templateUrl: './write-content.component.html',
-  styleUrl: './write-content.component.scss'
-})
-export class WriteContentComponent {
-  @Output() close = new EventEmitter<void>();
-
-  currentStep = signal(1);
-  selectedContentType = signal<ContentType | null>(null);
-  
-  formData = signal<FormData>({
-    title: '',
-    description: '',
-    content: '',
-    tags: [],
-    priority: 'medium',
-    dueDate: ''
-  });
-
-  newTag = signal('');
-
-  contentTypes: ContentType[] = [
-    {
-      id: 'project',
-      title: '新建项目',
-      description: '创建一个新的设计项目',
-      icon: 'M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z',
-      color: '#165DFF'
-    },
-    {
-      id: 'task',
-      title: '创建任务',
-      description: '为项目添加新的任务',
-      icon: 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z',
-      color: '#00B42A'
-    },
-    {
-      id: 'note',
-      title: '记录笔记',
-      description: '记录重要的想法和备忘',
-      icon: 'M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z',
-      color: '#FF7D00'
-    },
-    {
-      id: 'announcement',
-      title: '发布公告',
-      description: '向团队发布重要通知',
-      icon: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z',
-      color: '#F53F3F'
-    }
-  ];
-
-  selectContentType(type: ContentType): void {
-    this.selectedContentType.set(type);
-    this.currentStep.set(2);
-  }
-
-  goBack(): void {
-    if (this.currentStep() === 2) {
-      this.currentStep.set(1);
-      this.selectedContentType.set(null);
-    }
-  }
-
-  addTag(): void {
-    const tag = this.newTag().trim();
-    if (tag && !this.formData().tags.includes(tag)) {
-      const currentData = this.formData();
-      this.formData.set({
-        ...currentData,
-        tags: [...currentData.tags, tag]
-      });
-      this.newTag.set('');
-    }
-  }
-
-  removeTag(tag: string): void {
-    const currentData = this.formData();
-    this.formData.set({
-      ...currentData,
-      tags: currentData.tags.filter(t => t !== tag)
-    });
-  }
-
-  onTagKeyPress(event: KeyboardEvent): void {
-    if (event.key === 'Enter') {
-      event.preventDefault();
-      this.addTag();
-    }
-  }
-
-  updateFormField(field: keyof FormData, value: any): void {
-    const currentData = this.formData();
-    this.formData.set({
-      ...currentData,
-      [field]: value
-    });
-  }
-
-  submitForm(): void {
-    const data = {
-      type: this.selectedContentType()?.id,
-      ...this.formData()
-    };
-    
-    console.log('提交数据:', data);
-    
-    // 这里可以调用服务来保存数据
-    // this.contentService.createContent(data).subscribe(...);
-    
-    // 显示成功消息
-    alert('内容创建成功!');
-    
-    // 关闭模态框
-    this.close.emit();
-  }
-
-  isFormValid(): boolean {
-    const data = this.formData();
-    return !!(data.title.trim() && data.description.trim());
-  }
-
-  onClose(): void {
-    this.close.emit();
-  }
-}

+ 70 - 32
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,29 +208,67 @@
       </div>
     </div>
   </div>
-</div>
 
-<!-- 编写按钮 -->
-<button class="write-button" (click)="openWriteModal()" title="创建新内容">
-  <svg viewBox="0 0 24 24">
-    <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
-  </svg>
-</button>
-
-<!-- 模态框 -->
-<div class="modal-overlay" *ngIf="showWriteModal" (click)="closeWriteModal()">
-  <div class="modal-content" (click)="$event.stopPropagation()">
-    <div class="modal-header">
-      <h2 class="modal-title">创建新内容</h2>
-      <p class="modal-subtitle">选择您要创建的内容类型</p>
-      <button class="close-button" (click)="closeWriteModal()">
-        <svg viewBox="0 0 24 24">
-          <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
-        </svg>
-      </button>
-    </div>
-    <div class="modal-body">
-      <app-write-content (close)="closeWriteModal()"></app-write-content>
+  <!-- 详情抽屉 -->
+  <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>

+ 176 - 31
src/app/pages/admin/dashboard/dashboard.scss

@@ -205,6 +205,20 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
   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: '';
@@ -352,43 +366,170 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
     }
 
     .chart-period {
-      display: flex;
-      gap: 4px;
-      background: $background-secondary;
-      padding: 4px;
-      border-radius: 8px;
-
+      display: inline-flex;
+      gap: 8px;
       .period-btn {
-        padding: 8px 16px;
-        border: none;
-        background: transparent;
-        border-radius: 6px;
-        font-size: 13px;
-        color: $text-secondary;
-        cursor: pointer;
-        transition: $transition;
-        font-weight: 500;
-
-        &:hover {
-          background: $background-primary;
-          color: $text-primary;
-        }
-
+        padding: 6px 12px;
+        border-radius: 8px;
+        border: 1px solid rgba(0,0,0,0.06);
+        background: #fff;
+        color: #334155;
+        font-size: 12px;
         &.active {
-          background: $primary-color;
-          color: white;
-          box-shadow: 0 2px 4px rgba(22, 93, 255, 0.3);
+          background: #2563eb;
+          color: #fff;
+          border-color: #2563eb;
+          box-shadow: 0 6px 14px rgba(37, 99, 235, 0.25);
         }
       }
     }
   }
-
   .chart-container {
+    width: 100%;
     height: 320px;
-    padding: 24px;
   }
 }
 
+/* 抽屉样式 */
+.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; }
+      }
+    }
+  }
+}
+
+@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: $background-primary;
@@ -568,27 +709,31 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
 
   .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: 280px !important;
   }
-
   .chart-header {
     flex-direction: column;
     gap: 12px;
     align-items: flex-start !important;
   }
-
   .recent-activities {
     margin-top: 20px;
     padding: 24px;

+ 476 - 87
src/app/pages/admin/dashboard/dashboard.ts

@@ -1,18 +1,14 @@
 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 { WriteContentComponent } from './components/write-content/write-content.component';
 // 使用全局echarts变量,不导入模块
 
-// 确保@Component装饰器存在
-import { Component, OnInit, AfterViewInit, OnDestroy } from '@angular/core';
-
 @Component({
   selector: 'app-admin-dashboard',
   standalone: true,
-  imports: [CommonModule, RouterModule, WriteContentComponent],
+  imports: [CommonModule, RouterModule],
   templateUrl: './dashboard.html',
   styleUrl: './dashboard.scss'
 }) 
@@ -27,12 +23,104 @@ export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
     totalRevenue: signal(1258000)
   };
 
-  // 模态框控制
-  showWriteModal = signal(false);
+  // 图表周期切换
+  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) {}
 
@@ -48,105 +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]
+      };
+    }
+    // 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 (this.revenueChart) {
-      this.revenueChart.resize();
+
+    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');
   }
 
-  // 模态框控制方法
-  openWriteModal(): void {
-    this.showWriteModal.set(true);
-    document.body.style.overflow = 'hidden';
+  // 兼容旧模板调用(已调整为 showPanel)
+  showProjectDetails(status: 'active' | 'completed'): void {
+    this.showPanel(status);
   }
+  showCustomersDetails(): void { this.showPanel('customers'); }
+  showFinanceDetails(): void { this.showPanel('revenue'); }
 
-  closeWriteModal(): void {
-    this.showWriteModal.set(false);
-    document.body.style.overflow = 'auto';
+  // ====== 明细数据:加载、列配置、导出与分页 ======
+  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);
   }
-}
+}

+ 185 - 177
src/app/pages/admin/designers/designers.html

@@ -1,226 +1,234 @@
 <div class="designers-page">
-  <!-- 页面标题 -->
+  <!-- 页面标题和操作按钮 -->
   <div class="page-header">
     <div class="header-left">
       <h2 class="page-title">设计师管理</h2>
-      <p class="page-description">管理设计师团队,查看工作状态和绩效数据</p>
+      <p class="page-description">管理设计师档案、技能等级与项目绩效</p>
     </div>
     <div class="header-actions">
-      <button class="btn btn-primary" (click)="addDesigner()">
-        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-          <line x1="12" y1="5" x2="12" y2="19"></line>
-          <line x1="5" y1="12" x2="19" y2="12"></line>
-        </svg>
-        添加设计师
-      </button>
-      <button class="btn btn-secondary" (click)="exportDesigners()">
-        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-          <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
-          <polyline points="7 10 12 15 17 10"></polyline>
-          <line x1="12" y1="15" x2="12" y2="3"></line>
-        </svg>
-        导出数据
-      </button>
+      <button class="btn primary" (click)="addDesigner()">+ 新建设计师</button>
+      <button class="btn" (click)="exportDesigners()">导出</button>
     </div>
   </div>
 
   <!-- 统计卡片 -->
-  <div class="stats-overview">
+  <div class="stats-cards">
     <div class="stat-card">
-      <div class="stat-icon primary">
-        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-          <path d="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="stat-content">
-        <div class="stat-value">{{ totalDesigners() }}</div>
-        <div class="stat-label">设计师总数</div>
-      </div>
+      <div class="stat-label">设计师总数</div>
+      <div class="stat-value">{{ totalDesigners() }}</div>
     </div>
-
-    <div class="stat-card">
-      <div class="stat-icon success">
-        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-          <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
-          <polyline points="22 4 12 14.01 9 11.01"></polyline>
-        </svg>
-      </div>
-      <div class="stat-content">
-        <div class="stat-value">{{ activeDesigners() }}</div>
-        <div class="stat-label">在线设计师</div>
-      </div>
+    <div class="stat-card green">
+      <div class="stat-label">在线</div>
+      <div class="stat-value">{{ activeDesigners() }}</div>
     </div>
-
-    <div class="stat-card">
-      <div class="stat-icon warning">
-        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-          <circle cx="12" cy="12" r="10"></circle>
-          <line x1="12" y1="8" x2="12" y2="12"></line>
-          <line x1="12" y1="16" x2="12.01" y2="16"></line>
-        </svg>
-      </div>
-      <div class="stat-content">
-        <div class="stat-value">{{ busyDesigners() }}</div>
-        <div class="stat-label">忙碌设计师</div>
-      </div>
+    <div class="stat-card orange">
+      <div class="stat-label">忙碌</div>
+      <div class="stat-value">{{ busyDesigners() }}</div>
     </div>
-
-    <div class="stat-card">
-      <div class="stat-icon danger">
-        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-          <circle cx="12" cy="12" r="10"></circle>
-          <line x1="15" y1="9" x2="9" y2="15"></line>
-          <line x1="9" y1="9" x2="15" y2="15"></line>
-        </svg>
-      </div>
-      <div class="stat-content">
-        <div class="stat-value">{{ inactiveDesigners() }}</div>
-        <div class="stat-label">离线设计师</div>
-      </div>
+    <div class="stat-card gray">
+      <div class="stat-label">离线</div>
+      <div class="stat-value">{{ inactiveDesigners() }}</div>
     </div>
   </div>
 
-  <!-- 筛选和搜索 -->
-  <div class="filters-section">
-    <div class="search-box">
-      <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 21l-4.35-4.35"></path>
-      </svg>
-      <input 
-        type="text" 
-        placeholder="搜索设计师姓名或邮箱..."
-        [(ngModel)]="searchTerm"
-        (input)="searchTerm.set($any($event.target).value)"
-      >
+  <!-- 工具栏:搜索与筛选 -->
+  <div class="toolbar">
+    <div class="search">
+      <input placeholder="搜索姓名/邮箱" (input)="searchTerm.set($any($event.target).value)" [value]="searchTerm()" />
     </div>
-
-    <div class="filter-controls">
-      <select [(ngModel)]="selectedDepartment" (change)="selectedDepartment.set($any($event.target).value)">
+    <div class="filters">
+      <select (change)="selectedDepartment.set($any($event.target).value)">
         <option value="all">全部部门</option>
-        <option *ngFor="let dept of departments" [value]="dept">{{ dept }}</option>
+        <option *ngFor="let d of departments" [value]="d">{{ d }}</option>
       </select>
-
-      <select [(ngModel)]="selectedLevel" (change)="selectedLevel.set($any($event.target).value)">
-        <option value="all">全部等级</option>
+      <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 [(ngModel)]="selectedStatus" (change)="selectedStatus.set($any($event.target).value)">
+      <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>
 
-      <button class="btn btn-outline" (click)="resetFilters()">
-        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-          <polyline points="1 4 1 10 7 10"></polyline>
-          <path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path>
-        </svg>
-        重置
-      </button>
+  <!-- 表格 -->
+  <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="designers-grid">
-    <div class="designer-card" *ngFor="let designer of filteredDesigners">
-      <div class="designer-header">
-        <div class="designer-avatar">
-          <img [src]="designer.avatar" [alt]="designer.name">
-          <div class="status-indicator" [class]="getStatusClass(designer.status)"></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="designer-info">
-          <h3 class="designer-name">{{ designer.name }}</h3>
-          <p class="designer-department">{{ designer.department }}</p>
-          <div class="designer-level">
-            <span class="level-badge" [class]="'level-' + designer.level">
-              {{ getLevelText(designer.level) }}
-            </span>
-            <span class="status-text" [class]="getStatusClass(designer.status)">
-              {{ getStatusText(designer.status) }}
-            </span>
+        <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="designer-actions">
-          <button class="action-btn" (click)="viewDesigner(designer)" title="查看详情">
-            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-              <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
-              <circle cx="12" cy="12" r="3"></circle>
-            </svg>
-          </button>
-          <button class="action-btn" (click)="editDesigner(designer)" title="编辑">
-            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-              <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 class="action-btn danger" (click)="deleteDesigner(designer)" title="删除">
-            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-              <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 2v2"></path>
-            </svg>
-          </button>
+        <div class="detail-section">
+          <label>项目数</label>
+          <span>{{ currentDesigner.projectCount }}</span>
         </div>
-      </div>
-
-      <div class="designer-stats">
-        <div class="stat-item">
-          <span class="stat-label">项目数量</span>
-          <span class="stat-value">{{ designer.projectCount }}</span>
+        <div class="detail-section">
+          <label>完成率</label>
+          <span>{{ currentDesigner.completionRate }}%</span>
         </div>
-        <div class="stat-item">
-          <span class="stat-label">完成率</span>
-          <span class="stat-value">{{ designer.completionRate }}%</span>
+        <div class="detail-section">
+          <label>满意度</label>
+          <span>{{ currentDesigner.satisfactionScore }}/5</span>
         </div>
-        <div class="stat-item">
-          <span class="stat-label">满意度</span>
-          <span class="stat-value">{{ designer.satisfactionScore }}/5.0</span>
+        <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="designer-skills">
-        <span class="skill-tag" *ngFor="let skill of designer.skills">{{ skill }}</span>
-      </div>
-
-      <div class="designer-contact">
-        <div class="contact-item">
-          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
-            <polyline points="22 6 12 13 2 6"></polyline>
-          </svg>
-          <span>{{ designer.email }}</span>
-        </div>
-        <div class="contact-item">
-          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
-          </svg>
-          <span>{{ designer.phone }}</span>
-        </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="designer-footer">
-        <span class="join-date">入职时间:{{ designer.joinDate | date:'yyyy-MM-dd' }}</span>
-        <span class="last-active">最后活跃:{{ designer.lastActiveDate | date:'MM-dd HH:mm' }}</span>
+      <!-- 面板操作按钮 -->
+      <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 class="empty-state" *ngIf="filteredDesigners.length === 0">
-    <svg width="64" height="64" 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>
-    <h3>暂无设计师数据</h3>
-    <p>没有找到符合条件的设计师,请调整筛选条件或添加新的设计师。</p>
-    <button class="btn btn-primary" (click)="addDesigner()">
-      添加设计师
-    </button>
-  </div>
 </div>

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

@@ -1,644 +1,28 @@
-// 全局变量定义
-$primary-color: #165DFF;
-$primary-dark: #0d2f5e;
-$secondary-color: #4E5BA6;
-$success-color: #00B42A;
-$warning-color: #FF7D00;
-$danger-color: #F53F3F;
-$text-primary: #1D2129;
-$text-secondary: #4E5969;
-$text-tertiary: #86909C;
-$border-color: #E5E6EB;
-$background-primary: #FFFFFF;
-$background-secondary: #F2F3F5;
-$background-tertiary: #F7F8FA;
-$shadow-sm: 0 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);
-
-.designers-page {
-  padding: 32px;
-  background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
-  min-height: 100vh;
-
-  // 页面标题
-  .page-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: flex-start;
-    margin-bottom: 32px;
-    padding-bottom: 24px;
-    border-bottom: 1px solid $border-color;
-
-    .header-left {
-      .page-title {
-        font-size: 32px;
-        font-weight: 700;
-        color: $text-primary;
-        margin: 0 0 8px 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;
-        color: $text-secondary;
-        margin: 0;
-      }
-    }
-
-    .header-actions {
-      display: flex;
-      gap: 12px;
-    }
-  }
-
-  // 按钮样式
-  .btn {
-    display: inline-flex;
-    align-items: center;
-    gap: 8px;
-    padding: 12px 20px;
-    border: none;
-    border-radius: 8px;
-    font-size: 14px;
-    font-weight: 500;
-    cursor: pointer;
-    transition: $transition;
-    text-decoration: none;
-
-    &.btn-primary {
-      background: linear-gradient(135deg, $primary-color, #7c3aed);
-      color: white;
-      box-shadow: 0 4px 12px rgba(22, 93, 255, 0.3);
-
-      &:hover {
-        transform: translateY(-2px);
-        box-shadow: 0 6px 16px rgba(22, 93, 255, 0.4);
-      }
-    }
-
-    &.btn-secondary {
-      background: $background-primary;
-      color: $text-primary;
-      border: 1px solid $border-color;
-
-      &:hover {
-        background: $background-secondary;
-        border-color: $primary-color;
-      }
-    }
-
-    &.btn-outline {
-      background: transparent;
-      color: $text-secondary;
-      border: 1px solid $border-color;
-      padding: 8px 16px;
-      font-size: 13px;
-
-      &:hover {
-        background: $background-secondary;
-        border-color: $primary-color;
-        color: $primary-color;
-      }
-    }
-  }
-
-  // 统计卡片
-  .stats-overview {
-    display: grid;
-    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
-    gap: 20px;
-    margin-bottom: 32px;
-
-    .stat-card {
-      background: $background-primary;
-      border-radius: $border-radius;
-      padding: 24px;
-      box-shadow: $shadow-sm;
-      display: flex;
-      align-items: center;
-      gap: 16px;
-      transition: $transition;
-      border: 1px solid rgba(255, 255, 255, 0.8);
-
-      &:hover {
-        box-shadow: $shadow-md;
-        transform: translateY(-2px);
-      }
-
-      .stat-icon {
-        width: 48px;
-        height: 48px;
-        border-radius: 12px;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        flex-shrink: 0;
-
-        &.primary {
-          background: linear-gradient(135deg, rgba(22, 93, 255, 0.1), rgba(124, 58, 237, 0.1));
-          color: $primary-color;
-        }
-
-        &.success {
-          background: rgba(0, 180, 42, 0.1);
-          color: $success-color;
-        }
-
-        &.warning {
-          background: rgba(255, 125, 0, 0.1);
-          color: $warning-color;
-        }
-
-        &.danger {
-          background: rgba(245, 63, 63, 0.1);
-          color: $danger-color;
-        }
-      }
-
-      .stat-content {
-        .stat-value {
-          font-size: 28px;
-          font-weight: 700;
-          color: $text-primary;
-          margin: 0 0 4px 0;
-        }
-
-        .stat-label {
-          font-size: 14px;
-          color: $text-secondary;
-          margin: 0;
-        }
-      }
-    }
-  }
-
-  // 筛选区域
-  .filters-section {
-    background: $background-primary;
-    border-radius: $border-radius;
-    padding: 24px;
-    margin-bottom: 24px;
-    box-shadow: $shadow-sm;
-    display: flex;
-    gap: 20px;
-    align-items: center;
-    flex-wrap: wrap;
-
-    .search-box {
-      position: relative;
-      flex: 1;
-      min-width: 300px;
-
-      svg {
-        position: absolute;
-        left: 12px;
-        top: 50%;
-        transform: translateY(-50%);
-        color: $text-tertiary;
-      }
-
-      input {
-        width: 100%;
-        padding: 12px 12px 12px 40px;
-        border: 1px solid $border-color;
-        border-radius: 8px;
-        font-size: 14px;
-        transition: $transition;
-
-        &:focus {
-          outline: none;
-          border-color: $primary-color;
-          box-shadow: 0 0 0 3px rgba(22, 93, 255, 0.1);
-        }
-
-        &::placeholder {
-          color: $text-tertiary;
-        }
-      }
-    }
-
-    .filter-controls {
-      display: flex;
-      gap: 12px;
-      align-items: center;
-
-      select {
-        padding: 10px 12px;
-        border: 1px solid $border-color;
-        border-radius: 8px;
-        font-size: 14px;
-        background: $background-primary;
-        color: $text-primary;
-        cursor: pointer;
-        transition: $transition;
-
-        &:focus {
-          outline: none;
-          border-color: $primary-color;
-          box-shadow: 0 0 0 3px rgba(22, 93, 255, 0.1);
-        }
-      }
-    }
-  }
-
-  // 设计师网格
-  .designers-grid {
-    display: grid;
-    grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
-    gap: 24px;
-    margin-bottom: 32px;
-
-    .designer-card {
-      background: $background-primary;
-      border-radius: $border-radius;
-      padding: 24px;
-      box-shadow: $shadow-sm;
-      transition: $transition;
-      border: 1px solid rgba(255, 255, 255, 0.8);
-      position: relative;
-      overflow: hidden;
-
-      &::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(-4px);
-
-        &::before {
-          transform: scaleX(1);
-        }
-      }
-
-      .designer-header {
-        display: flex;
-        align-items: flex-start;
-        gap: 16px;
-        margin-bottom: 20px;
-
-        .designer-avatar {
-          position: relative;
-          flex-shrink: 0;
-
-          img {
-            width: 60px;
-            height: 60px;
-            border-radius: 50%;
-            object-fit: cover;
-            border: 3px solid $background-secondary;
-          }
-
-          .status-indicator {
-            position: absolute;
-            bottom: 2px;
-            right: 2px;
-            width: 16px;
-            height: 16px;
-            border-radius: 50%;
-            border: 2px solid $background-primary;
-
-            &.status-active {
-              background: $success-color;
-            }
-
-            &.status-busy {
-              background: $warning-color;
-            }
-
-            &.status-inactive {
-              background: $text-tertiary;
-            }
-          }
-        }
-
-        .designer-info {
-          flex: 1;
-
-          .designer-name {
-            font-size: 18px;
-            font-weight: 600;
-            color: $text-primary;
-            margin: 0 0 4px 0;
-          }
-
-          .designer-department {
-            font-size: 14px;
-            color: $text-secondary;
-            margin: 0 0 8px 0;
-          }
-
-          .designer-level {
-            display: flex;
-            gap: 8px;
-            align-items: center;
-
-            .level-badge {
-              padding: 4px 8px;
-              border-radius: 6px;
-              font-size: 12px;
-              font-weight: 500;
-
-              &.level-junior {
-                background: rgba(0, 180, 42, 0.1);
-                color: $success-color;
-              }
-
-              &.level-intermediate {
-                background: rgba(22, 93, 255, 0.1);
-                color: $primary-color;
-              }
-
-              &.level-senior {
-                background: rgba(255, 125, 0, 0.1);
-                color: $warning-color;
-              }
-
-              &.level-expert {
-                background: rgba(124, 58, 237, 0.1);
-                color: #7c3aed;
-              }
-            }
-
-            .status-text {
-              font-size: 12px;
-              font-weight: 500;
-
-              &.status-active {
-                color: $success-color;
-              }
-
-              &.status-busy {
-                color: $warning-color;
-              }
-
-              &.status-inactive {
-                color: $text-tertiary;
-              }
-            }
-          }
-        }
-
-        .designer-actions {
-          display: flex;
-          gap: 8px;
-
-          .action-btn {
-            width: 32px;
-            height: 32px;
-            border: none;
-            border-radius: 6px;
-            background: $background-secondary;
-            color: $text-secondary;
-            cursor: pointer;
-            transition: $transition;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-
-            &:hover {
-              background: $primary-color;
-              color: white;
-            }
-
-            &.danger:hover {
-              background: $danger-color;
-              color: white;
-            }
-          }
-        }
-      }
-
-      .designer-stats {
-        display: grid;
-        grid-template-columns: repeat(3, 1fr);
-        gap: 16px;
-        margin-bottom: 16px;
-        padding: 16px;
-        background: $background-tertiary;
-        border-radius: 8px;
-
-        .stat-item {
-          text-align: center;
-
-          .stat-label {
-            display: block;
-            font-size: 12px;
-            color: $text-tertiary;
-            margin-bottom: 4px;
-          }
-
-          .stat-value {
-            font-size: 16px;
-            font-weight: 600;
-            color: $text-primary;
-          }
-        }
-      }
-
-      .designer-skills {
-        display: flex;
-        flex-wrap: wrap;
-        gap: 6px;
-        margin-bottom: 16px;
-
-        .skill-tag {
-          padding: 4px 8px;
-          background: rgba(22, 93, 255, 0.1);
-          color: $primary-color;
-          border-radius: 4px;
-          font-size: 12px;
-          font-weight: 500;
-        }
-      }
-
-      .designer-contact {
-        margin-bottom: 16px;
-
-        .contact-item {
-          display: flex;
-          align-items: center;
-          gap: 8px;
-          margin-bottom: 8px;
-          font-size: 13px;
-          color: $text-secondary;
-
-          svg {
-            color: $text-tertiary;
-          }
-
-          &:last-child {
-            margin-bottom: 0;
-          }
-        }
-      }
-
-      .designer-footer {
-        display: flex;
-        justify-content: space-between;
-        font-size: 12px;
-        color: $text-tertiary;
-        padding-top: 16px;
-        border-top: 1px solid $border-color;
-      }
-    }
-  }
-
-  // 空状态
-  .empty-state {
-    text-align: center;
-    padding: 80px 20px;
-    background: $background-primary;
-    border-radius: $border-radius;
-    box-shadow: $shadow-sm;
-
-    svg {
-      color: $text-tertiary;
-      margin-bottom: 24px;
-    }
-
-    h3 {
-      font-size: 20px;
-      font-weight: 600;
-      color: $text-primary;
-      margin: 0 0 8px 0;
-    }
-
-    p {
-      font-size: 14px;
-      color: $text-secondary;
-      margin: 0 0 24px 0;
-      max-width: 400px;
-      margin-left: auto;
-      margin-right: auto;
-    }
-  }
-}
-
-// 响应式设计
-@media (max-width: 1200px) {
-  .designers-page {
-    .designers-grid {
-      grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
-    }
-  }
-}
-
-@media (max-width: 768px) {
-  .designers-page {
-    padding: 20px;
-
-    .page-header {
-      flex-direction: column;
-      gap: 16px;
-      align-items: stretch;
-
-      .header-actions {
-        justify-content: flex-start;
-      }
-    }
-
-    .stats-overview {
-      grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
-      gap: 16px;
-
-      .stat-card {
-        padding: 16px;
-        flex-direction: column;
-        text-align: center;
-
-        .stat-icon {
-          width: 40px;
-          height: 40px;
-        }
-
-        .stat-content .stat-value {
-          font-size: 24px;
-        }
-      }
-    }
-
-    .filters-section {
-      flex-direction: column;
-      align-items: stretch;
-      gap: 16px;
-
-      .search-box {
-        min-width: auto;
-      }
-
-      .filter-controls {
-        flex-wrap: wrap;
-      }
-    }
-
-    .designers-grid {
-      grid-template-columns: 1fr;
-      gap: 16px;
-
-      .designer-card {
-        padding: 20px;
-
-        .designer-header {
-          .designer-avatar img {
-            width: 50px;
-            height: 50px;
-          }
-
-          .designer-actions {
-            flex-direction: column;
-          }
-        }
-
-        .designer-stats {
-          grid-template-columns: 1fr;
-          gap: 12px;
-        }
-      }
-    }
-  }
-}
-
-@media (max-width: 480px) {
-  .designers-page {
-    padding: 16px;
-
-    .page-header .page-title {
-      font-size: 24px;
-    }
-
-    .btn {
-      padding: 10px 16px;
-      font-size: 13px;
-    }
-
-    .designers-grid .designer-card {
-      padding: 16px;
-
-      .designer-header {
-        flex-direction: column;
-        align-items: center;
-        text-align: center;
-        gap: 12px;
-
-        .designer-actions {
-          flex-direction: row;
-        }
-      }
-    }
-  }
-}
+.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}

+ 164 - 11
src/app/pages/admin/designers/designers.ts

@@ -96,6 +96,12 @@ export class Designers implements OnInit {
   // 部门列表
   departments = ['室内设计部', '建筑设计部', '景观设计部', '平面设计部'];
 
+  // 侧边面板状态
+  showPanel: boolean = false;
+  panelMode: 'add' | 'detail' | 'edit' = 'add';
+  currentDesigner: Designer | null = null;
+  formModel: (Partial<Designer> & { skillsInput?: string }) = {};
+
   ngOnInit(): void {
     this.loadDesigners();
   }
@@ -146,14 +152,120 @@ export class Designers implements OnInit {
 
   // 查看设计师详情
   viewDesigner(designer: Designer): void {
-    console.log('查看设计师详情:', designer);
-    // 在实际应用中,这里会跳转到设计师详情页面
+    this.currentDesigner = designer;
+    this.panelMode = 'detail';
+    this.showPanel = true;
   }
 
   // 编辑设计师
   editDesigner(designer: Designer): void {
-    console.log('编辑设计师:', designer);
-    // 在实际应用中,这里会打开编辑对话框
+    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;
   }
 
   // 删除设计师
@@ -165,16 +277,57 @@ export class Designers implements OnInit {
     }
   }
 
-  // 添加新设计师
-  addDesigner(): void {
-    console.log('添加新设计师');
-    // 在实际应用中,这里会打开添加设计师对话框
-  }
+  // (已移除重复的新增方法,保留通过侧边面板新增的流程)
 
   // 导出设计师数据
   exportDesigners(): void {
-    console.log('导出设计师数据');
-    // 在实际应用中,这里会导出Excel或CSV文件
+    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);
   }
 
   // 重置筛选条件

+ 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

@@ -140,8 +140,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>
@@ -158,61 +158,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">
@@ -262,6 +294,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>
@@ -384,10 +417,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;
 }

+ 283 - 33
src/app/pages/admin/system-settings/system-settings.ts

@@ -6,7 +6,7 @@ declare global {
 }
 
 import { Component, OnInit, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { CommonModule, NgIf, NgForOf } from '@angular/common';
 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
@@ -14,6 +14,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 {
@@ -30,6 +35,8 @@ interface SOPTemplate {
   name: string;
   description: string;
   steps: string[];
+  // 新增:模板分类,供对话框使用
+  category?: string;
   isActive: boolean;
 }
 
@@ -38,8 +45,9 @@ interface PricingRule {
   id: string;
   name: string;
   description: string;
-  condition: string;
-  price: number;
+  // 调整为与对话框一致的字段
+  formula: string;
+  category: string;
   isActive: boolean;
 }
 
@@ -59,11 +67,18 @@ interface PerformanceRule {
   standalone: true,
   imports: [
     CommonModule,
+    NgIf,
+    NgForOf,
     RouterModule,
     FormsModule,
     MatFormFieldModule,
     MatSelectModule,
-    MatSlideToggleModule
+    MatSlideToggleModule,
+    MatDialogModule,
+    MatButtonModule,
+    MatInputModule,
+    MatMenuModule,
+    MatCheckboxModule
   ],
   templateUrl: './system-settings.html'
 })
@@ -89,8 +104,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;
@@ -222,6 +248,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模板数据
@@ -239,6 +269,7 @@ export class SystemSettings implements OnInit {
           '客户信息录入系统',
           '启动项目'
         ],
+        category: '客户接入',
         isActive: true
       },
       {
@@ -255,20 +286,21 @@ export class SystemSettings implements OnInit {
           '用户验收测试',
           '上线部署'
         ],
+        category: '项目开发',
         isActive: true
       },
       {
         id: '3',
-        name: '问题处理流程',
-        description: '处理客户反馈问题的标准流程',
+        name: '上线运维流程',
+        description: '项目上线后的运维与监控流程',
         steps: [
-          '问题记录与分类',
-          '问题评估与优先级确定',
-          '问题分析与解决',
-          '验证解决方案',
-          '更新文档',
-          '反馈给客户'
+          '上线准备',
+          '监控配置',
+          '告警设置',
+          '备份策略',
+          '应急预案'
         ],
+        category: '运维保障',
         isActive: true
       }
     ];
@@ -280,26 +312,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
       }
     ];
@@ -348,26 +380,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);
   }
   
   // 保存设置项
@@ -518,7 +767,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)
       );
     }
   }

+ 416 - 283
src/app/pages/customer-service/consultation-order/consultation-order.html

@@ -1,312 +1,445 @@
 <div class="consultation-order-container">
-  <!-- iOS风格页面标题 -->
-  <div class="page-header">
-    <h1>
-      <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-        <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
-      </svg>
-      客户咨询与下单
-    </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>
-        <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>
+  <!-- 现代化页面头部 -->
+  <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>
-        客户信息
-      </h2>
-      <div class="section-actions">
-        <button *ngIf="selectedCustomer()" class="clear-customer-btn" (click)="clearSelectedCustomer()">
-          清除客户信息
-        </button>
       </div>
-    </div>
-    
-    <!-- 客户搜索 -->
-    <div *ngIf="!selectedCustomer()" class="customer-search">
-      <div class="search-input-group">
-        <input 
-          type="text" 
-          [(ngModel)]="searchKeyword" 
-          (input)="searchCustomer()"
-          placeholder="搜索客户姓名或手机号..."
-          class="search-input"
-          autocomplete="off"
-        />
-        <button class="search-btn">
-          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-            <circle cx="11" cy="11" r="8"></circle>
-            <path d="m21 21-4.35-4.35"></path>
-          </svg>
-        </button>
-      </div>
-      
-      <!-- 搜索结果 -->
-      <div *ngIf="searchResults().length > 0" class="search-results">
-        <div *ngFor="let customer of searchResults()" class="customer-item" (click)="selectCustomer(customer)">
-          <img [src]="customer.avatar" [alt]="customer.name" class="customer-avatar">
-          <div class="customer-details">
-            <div class="customer-name">{{ customer.name }}</div>
-            <div class="customer-phone">{{ customer.phone }}</div>
-            <div class="customer-tags">
-              <span *ngIf="customer.customerType" class="tag">{{ customer.customerType }}</span>
-              <span *ngIf="customer.source" class="tag source">{{ customer.source }}</span>
-            </div>
-          </div>
-        </div>
+      <div 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 *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>
-      <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 class="form-group">
-          <label for="demandType">需求类型</label>
-          <select id="demandType" formControlName="demandType">
-            <option value="">请选择需求类型</option>
-            <option *ngFor="let type of demandTypes" [value]="type.value">{{ type.label }}</option>
-          </select>
+    </div>
+  </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>
-      <div class="form-row">
-        <div class="form-group">
-          <label for="followUpStatus">跟进状态</label>
-          <select id="followUpStatus" formControlName="followUpStatus">
-            <option value="">请选择跟进状态</option>
-            <option *ngFor="let status of followUpStatus" [value]="status.value">{{ status.label }}</option>
-          </select>
+        <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-group full-width">
-        <label>偏好标签</label>
-        <div class="tags-container">
-          <mat-chip-grid #chipList aria-label="偏好标签">
-              <mat-chip
-                *ngFor="let tag of preferenceTags"
-                removable
-                (removed)="removePreferenceTag(tag)"
-                class="preference-tag"
-              >
-                {{ tag }}
-                <mat-icon matChipRemove>cancel</mat-icon>
-              </mat-chip>
-              <input
-                placeholder="添加自定义标签..."
-                [matChipInputFor]="chipList"
-                [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
-                [matChipInputAddOnBlur]="addOnBlur"
-                (matChipInputTokenEnd)="addPreferenceTag($event)"
-                class="tag-input"
+      <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"
               />
-            </mat-chip-grid>
+            </div>
+          </div>
           
-          <!-- 预设标签选项 -->
-          <div class="preset-tags">
-            <span class="preset-label">快速添加:</span>
-            <button
-              *ngFor="let tag of preferenceTagOptions"
-              type="button"
-              class="preset-tag-btn"
-              (click)="addFromPreset(tag)"
-              [class.active]="preferenceTags.includes(tag)"
-            >
-              {{ tag }}
-            </button>
+          <!-- 搜索结果 -->
+          <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-group full-width">
-        <label for="remark">备注</label>
-        <textarea id="remark" formControlName="remark" rows="2" placeholder="请输入其他备注信息"></textarea>
-      </div>
-    </form>
-  </section>
+    </section>
 
-  <!-- 需求信息区域 -->
-  <section class="requirement-section">
-    <div class="section-header">
-      <h2>
-        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-          <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
-          <polyline points="14 2 14 8 20 8"></polyline>
-          <line x1="16" y1="13" x2="8" y2="13"></line>
-          <line x1="16" y1="17" x2="8" y2="17"></line>
-          <polyline points="10 9 9 9 8 9"></polyline>
-        </svg>
-        需求信息
-      </h2>
-    </div>
-    
-    <form [formGroup]="requirementForm" class="requirement-form">
-      <div class="form-row">
-        <div class="form-group">
-          <label for="style">装修风格 <span class="required">*</span></label>
-          <select id="style" formControlName="style">
-            <option value="">请选择装修风格</option>
-            <option *ngFor="let style of styleOptions" [value]="style">{{ style }}</option>
-          </select>
-        </div>
-        <div class="form-group">
-          <label for="budget">预算范围 <span class="required">*</span></label>
-          <select id="budget" formControlName="budget">
-            <option value="">请选择预算范围</option>
-            <option value="5-10万">5-10万</option>
-            <option value="10-20万">10-20万</option>
-            <option value="20-30万">20-30万</option>
-            <option value="30-50万">30-50万</option>
-            <option value="50万以上">50万以上</option>
-          </select>
-        </div>
-        <div class="form-group">
-          <label for="area">房屋面积 <span class="required">*</span></label>
-          <input type="number" id="area" formControlName="area" placeholder="请输入房屋面积(㎡)">
+    <!-- 需求信息卡片 -->
+    <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-row">
-        <div class="form-group">
-          <label for="houseType">户型 <span class="required">*</span></label>
-          <select id="houseType" formControlName="houseType">
-            <option value="">请选择户型</option>
-            <option *ngFor="let houseType of houseTypeOptions" [value]="houseType">{{ houseType }}</option>
-          </select>
-        </div>
-        <div class="form-group">
-          <label for="floor">楼层</label>
-          <input type="number" id="floor" formControlName="floor" placeholder="请输入楼层">
-        </div>
-        <div class="form-group">
-          <label for="decorationType">装修类型 <span class="required">*</span></label>
-          <select id="decorationType" formControlName="decorationType">
-            <option value="">请选择装修类型</option>
-            <option *ngFor="let type of decorationTypeOptions" [value]="type">{{ type }}</option>
-          </select>
-        </div>
+      
+      <div 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>
-      <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 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 class="form-group full-width">
-        <label for="specialRequirements">特殊需求</label>
-        <textarea id="specialRequirements" formControlName="specialRequirements" rows="3" placeholder="请描述特殊需求或关注点"></textarea>
-      </div>
-    </form>
-  </section>
-
-  <!-- iOS风格报价与案例匹配区域 -->
-  <section class="price-match-section">
-    <div class="section-header">
-      <h2>
-        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-          <rect x="2" y="6" width="20" height="12" rx="2"></rect>
-          <path d="M12 6v4"></path>
-          <path d="M2 10h20"></path>
-        </svg>
-        报价与案例匹配
-      </h2>
-    </div>
-    
-    <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 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 class="similarity-badge">匹配度 {{ caseItem.similarity }}%</div>
           </div>
         </div>
-      </div>
-    </div>
-    
-    <!-- 已选参考案例 -->
-    <div *ngIf="requirementForm.get('referenceCases')?.value.length > 0" class="selected-cases">
-      <h3>已选参考案例</h3>
-      <div class="selected-cases-list">
-        <div *ngFor="let caseId of requirementForm.get('referenceCases')?.value" class="selected-case-item">
-          <span>案例 #{{ caseId }}</span>
-          <button class="remove-case-btn" (click)="removeReferenceCase(caseId)">
-            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
-              <line x1="18" y1="6" x2="6" y2="18"></line>
-              <line x1="6" y1="6" x2="18" y2="18"></line>
-            </svg>
-          </button>
+        
+        <!-- 匹配案例区域 -->
+        <div 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>
 
-  <!-- iOS风格操作按钮区域 -->
-  <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>

+ 708 - 138
src/app/pages/customer-service/consultation-order/consultation-order.scss

@@ -22,68 +22,88 @@ $heading-font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-ser
 $grid-gap: 16px;
 $card-padding: 16px;
 
-// iOS风格主容器
+// 主容器样式
 .consultation-order-container {
-  max-width: 100%;
-  margin: 0 auto;
-  padding: 16px;
-  color: $text-primary;
-  font-family: $main-font-family;
-  font-size: 17px;
-  line-height: 1.5;
+  background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
   min-height: 100vh;
-  background-color: $background-secondary;
+  padding: 0;
+}
+
+.main-content {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 24px;
   
-  @media (min-width: 768px) {
-    padding: 20px;
-    display: grid;
-    grid-template-columns: 1fr;
-    gap: $grid-gap;
-  }
+  display: grid;
+  grid-template-columns: 1fr;
+  gap: 32px;
   
-  @media (min-width: 1024px) {
-    grid-template-columns: 280px 1fr;
-    max-width: 1200px;
+  @media (max-width: 768px) {
+    padding: 16px;
+    gap: 20px;
   }
 }
 
-// 页面标题 - 财务风格
+// 页面标题样式
 .page-header {
-  grid-column: 1 / -1;
-  margin-bottom: $grid-gap;
-  padding-bottom: 16px;
-  border-bottom: 1px solid $border-color;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  padding: 40px 0;
+  margin: -24px -24px 0 -24px;
+  border-radius: 0 0 24px 24px;
+  box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
   
-  h1 {
-    font-size: 24px;
-    font-weight: 500;
-    margin: 0 0 8px 0;
-    color: $text-primary;
-    font-family: $heading-font-family;
-    display: flex;
-    align-items: center;
-    gap: 12px;
+  @media (max-width: 768px) {
+    margin: -16px -16px 0 -16px;
+    padding: 32px 0;
   }
   
-  p {
-    font-size: 14px;
-    color: $text-secondary;
+  .header-content {
+    max-width: 1200px;
+    margin: 0 auto;
+    padding: 0 24px;
+    text-align: center;
+    
+    @media (max-width: 768px) {
+      padding: 0 16px;
+    }
+  }
+  
+  h1 {
+    font-size: 2.5rem;
+    font-weight: 700;
     margin: 0;
+    text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+    
+    @media (max-width: 768px) {
+      font-size: 2rem;
+    }
+  }
+  
+  .subtitle {
+    font-size: 1.1rem;
+    margin-top: 8px;
+    font-weight: 400;
+    opacity: 0.9;
   }
 }
 
-// 成功提示
+// 成功提示样式
 .success-message {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  padding: 12px 16px;
-  background-color: color-mix(in srgb, $success-color 10%, transparent);
-  color: $success-color;
-  border-radius: $border-radius;
-  border-left: 4px solid $success-color;
-  margin-bottom: 24px;
-  animation: slideIn 0.3s ease;
+  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+  color: white;
+  padding: 20px 24px;
+  border-radius: 16px;
+  box-shadow: 0 8px 32px rgba(16, 185, 129, 0.3);
+  text-align: center;
+  font-weight: 500;
+  animation: slideInFromTop 0.5s ease-out;
+  border: 1px solid rgba(255, 255, 255, 0.2);
+  
+  .success-icon {
+    margin-right: 8px;
+    vertical-align: middle;
+  }
 }
 
 @keyframes slideIn {
@@ -97,27 +117,197 @@ $card-padding: 16px;
   }
 }
 
-// 通用区域样式 - 财务风格
-.section-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16px;
+// 现代化信息卡片样式
+.info-card {
+  background: white;
+  border-radius: 20px;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
+  border: 1px solid rgba(0, 0, 0, 0.05);
+  overflow: hidden;
+  transition: all 0.3s ease;
   
-  h2 {
-    font-size: 18px;
-    font-weight: 500;
-    color: $text-primary;
-    font-family: $heading-font-family;
-    margin: 0;
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 12px 48px rgba(0, 0, 0, 0.12);
+  }
+}
+
+// 卡片头部样式
+.card-header {
+  padding: 24px 32px 20px;
+  border-bottom: 1px solid #f1f5f9;
+  background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+  
+  @media (max-width: 768px) {
+    padding: 20px 24px 16px;
+  }
+  
+  .header-left {
     display: flex;
     align-items: center;
-    gap: 8px;
+    gap: 16px;
   }
   
-  .section-actions {
+  .icon-wrapper {
+    width: 48px;
+    height: 48px;
+    border-radius: 12px;
     display: flex;
-    gap: 8px;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+    
+    &.customer-icon {
+      background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
+      color: white;
+    }
+    
+    &.requirement-icon {
+      background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
+      color: white;
+    }
+    
+    &.price-icon {
+      background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
+      color: white;
+    }
+  }
+  
+  .header-text {
+    h2 {
+      font-size: 1.5rem;
+      font-weight: 700;
+      color: #1e293b;
+      margin: 0;
+      line-height: 1.2;
+    }
+    
+    p {
+      font-size: 0.875rem;
+      color: #64748b;
+      margin: 4px 0 0;
+      line-height: 1.4;
+    }
+  }
+}
+
+// 卡片内容样式
+.card-content {
+  padding: 32px;
+  
+  @media (max-width: 768px) {
+    padding: 24px;
+  }
+}
+
+// 表单分组样式
+.form-section {
+  margin-bottom: 32px;
+  
+  &:last-child {
+    margin-bottom: 0;
+  }
+  
+  .section-title {
+    font-size: 1.125rem;
+    font-weight: 600;
+    color: #1e293b;
+    margin: 0 0 20px;
+    padding-bottom: 8px;
+    border-bottom: 2px solid #e2e8f0;
+    position: relative;
+    
+    &::after {
+      content: '';
+      position: absolute;
+      bottom: -2px;
+      left: 0;
+      width: 40px;
+      height: 2px;
+      background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
+      border-radius: 1px;
+    }
+  }
+}
+
+// 表单网格布局
+.form-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+  gap: 24px;
+  
+  @media (max-width: 768px) {
+    grid-template-columns: 1fr;
+    gap: 20px;
+  }
+}
+
+// 表单字段样式
+.form-field {
+  display: flex;
+  flex-direction: column;
+  
+  &.full-width {
+    grid-column: 1 / -1;
+  }
+  
+  .field-label {
+    font-size: 0.875rem;
+    font-weight: 600;
+    color: #374151;
+    margin-bottom: 8px;
+    
+    .required {
+      color: #ef4444;
+      margin-left: 2px;
+    }
+  }
+  
+  .field-input,
+  .field-select,
+  .field-textarea {
+    padding: 12px 16px;
+    border: 2px solid #e5e7eb;
+    border-radius: 12px;
+    font-size: 0.875rem;
+    transition: all 0.2s ease;
+    background: white;
+    
+    &:focus {
+      outline: none;
+      border-color: #3b82f6;
+      box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+    }
+    
+    &::placeholder {
+      color: #9ca3af;
+    }
+  }
+  
+  .field-textarea {
+    resize: vertical;
+    min-height: 100px;
+    font-family: inherit;
+  }
+}
+
+// 带单位的输入框
+.input-with-unit {
+  position: relative;
+  display: flex;
+  align-items: center;
+  
+  .field-input {
+    flex: 1;
+    padding-right: 50px;
+  }
+  
+  .input-unit {
+    position: absolute;
+    right: 16px;
+    font-size: 0.875rem;
+    color: #6b7280;
+    font-weight: 500;
   }
 }
 
@@ -151,9 +341,130 @@ $card-padding: 16px;
   @extend .highlight;
 }
 
-// 客户搜索
-.customer-search {
+// 客户搜索区域样式
+.search-section {
   margin-bottom: 24px;
+  
+  .search-input-group {
+    display: flex;
+    gap: 12px;
+    align-items: flex-end;
+    
+    @media (max-width: 768px) {
+      flex-direction: column;
+      align-items: stretch;
+    }
+    
+    .search-field {
+      flex: 1;
+    }
+    
+    .search-btn {
+      padding: 12px 24px;
+      background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
+      color: white;
+      border: none;
+      border-radius: 12px;
+      font-weight: 600;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      white-space: nowrap;
+      
+      &:hover {
+        transform: translateY(-1px);
+        box-shadow: 0 4px 16px rgba(59, 130, 246, 0.3);
+      }
+      
+      &:disabled {
+        opacity: 0.6;
+        cursor: not-allowed;
+        transform: none;
+      }
+    }
+  }
+}
+
+// 搜索结果样式
+.search-results {
+  margin-top: 20px;
+  
+  .results-list {
+    display: grid;
+    gap: 12px;
+  }
+  
+  .result-item {
+    background: #f8fafc;
+    border: 2px solid #e2e8f0;
+    border-radius: 12px;
+    padding: 16px;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    
+    &:hover {
+      border-color: #3b82f6;
+      background: #eff6ff;
+    }
+    
+    .result-header {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      margin-bottom: 8px;
+      
+      .customer-avatar {
+        width: 40px;
+        height: 40px;
+        border-radius: 10px;
+        background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        color: white;
+        font-weight: 600;
+        font-size: 0.875rem;
+      }
+      
+      .customer-name {
+        font-weight: 600;
+        color: #1e293b;
+        font-size: 1rem;
+      }
+    }
+    
+    .result-details {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 16px;
+      font-size: 0.875rem;
+      color: #64748b;
+      
+      .detail-item {
+        display: flex;
+        align-items: center;
+        gap: 4px;
+      }
+    }
+    
+    .result-tags {
+      margin-top: 12px;
+      display: flex;
+      flex-wrap: wrap;
+      gap: 6px;
+      
+      .tag {
+        background: #dbeafe;
+        color: #1d4ed8;
+        padding: 4px 8px;
+        border-radius: 6px;
+        font-size: 0.75rem;
+        font-weight: 500;
+      }
+    }
+  }
 }
 
 .search-input-group {
@@ -265,74 +576,64 @@ $card-padding: 16px;
 }
 
 // 偏好标签样式
-.tags-container {
-  margin-top: 12px;
-}
-
-.preference-tag {
-  background-color: color-mix(in srgb, $primary-color 15%, transparent);
-  color: $primary-color;
-  font-size: 16px;
-  padding: 8px 16px;
-  margin-right: 12px;
-  margin-bottom: 12px;
-  font-weight: 500;
-  border-radius: 24px;
-  transition: all 0.3s ease;
-  &:hover {
-    background-color: color-mix(in srgb, $primary-color 30%, transparent);
-  }
-}
-
-.tag-input {
-  font-size: 16px;
-  padding: 8px;
-  margin-top: 8px;
-  width: 100%;
-  border: 2px solid $border-color;
-  border-radius: 8px;
-  transition: border-color 0.3s ease;
-  &:focus {
-    outline: none;
-    border-color: $primary-color;
+.preference-tags {
+  .tags-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+    gap: 12px;
+    margin-bottom: 20px;
+    
+    @media (max-width: 768px) {
+      grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
+      gap: 8px;
+    }
   }
-}
-
-// 预设标签样式
-.preset-tags {
-  margin-top: 16px;
-  display: flex;
-  flex-wrap: wrap;
-  align-items: center;
-  gap: 12px;
-}
-
-.preset-label {
-  font-size: 16px;
-  font-weight: 600;
-  color: $text-primary;
-  min-width: 100px;
-}
-
-.preset-tag-btn {
-  padding: 8px 16px;
-  background-color: $background-tertiary;
-  color: $text-secondary;
-  border: 2px solid $border-color;
-  border-radius: 24px;
-  font-size: 14px;
-  cursor: pointer;
-  transition: all 0.3s ease;
-  &:hover {
-    background-color: color-mix(in srgb, $primary-color 10%, transparent);
-    color: $primary-color;
-    border-color: $primary-color;
-    transform: translateY(-2px);
+  
+  .tag-chip {
+    background: #f1f5f9;
+    border: 2px solid #e2e8f0;
+    border-radius: 20px;
+    padding: 8px 16px;
+    text-align: center;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    font-size: 0.875rem;
+    font-weight: 500;
+    color: #475569;
+    
+    &:hover {
+      border-color: #3b82f6;
+      background: #eff6ff;
+      color: #1d4ed8;
+    }
+    
+    &.selected {
+      background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
+      border-color: #1d4ed8;
+      color: white;
+    }
   }
-  &.active {
-    background-color: $primary-color;
-    color: white;
-    border-color: $primary-color;
+  
+  .preset-tags {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    
+    .preset-tag-btn {
+      background: #f8fafc;
+      border: 1px solid #e2e8f0;
+      border-radius: 16px;
+      padding: 6px 12px;
+      font-size: 0.75rem;
+      color: #64748b;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      
+      &:hover {
+        background: #e2e8f0;
+        color: #374151;
+      }
+    }
   }
 }
 
@@ -409,6 +710,61 @@ $card-padding: 16px;
   @extend .card;
 }
 
+// 报价区域样式
+.price-section {
+  margin-bottom: 32px;
+  
+  .price-card {
+    background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+    border: 2px solid #0ea5e9;
+    border-radius: 16px;
+    padding: 24px;
+    text-align: center;
+    
+    .price-header {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: 8px;
+      margin-bottom: 12px;
+      color: #0369a1;
+      font-weight: 600;
+      font-size: 0.875rem;
+    }
+    
+    .price-value {
+      font-size: 1.5rem;
+      font-weight: 700;
+      color: #0c4a6e;
+      line-height: 1.2;
+    }
+  }
+}
+
+// 案例区域样式
+.cases-section {
+  margin-bottom: 32px;
+  
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+    
+    h3 {
+      font-size: 1.125rem;
+      font-weight: 600;
+      color: #1e293b;
+      margin: 0;
+    }
+    
+    .section-subtitle {
+      font-size: 0.875rem;
+      color: #64748b;
+    }
+  }
+}
+
 // 报价与案例匹配区域
 .price-match-section {
   @extend .card;
@@ -449,23 +805,80 @@ $card-padding: 16px;
   }
 }
 
+// 案例网格样式
 .cases-grid {
   display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
-  gap: 16px;
+  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+  gap: 20px;
+  
+  @media (max-width: 768px) {
+    grid-template-columns: 1fr;
+    gap: 16px;
+  }
 }
 
+// 案例卡片样式
 .case-card {
-  background-color: $background-tertiary;
-  border: 2px solid transparent;
-  border-radius: $border-radius;
+  background: white;
+  border: 2px solid #e2e8f0;
+  border-radius: 16px;
   overflow: hidden;
   cursor: pointer;
-  transition: $transition;
+  transition: all 0.3s ease;
+  
   &:hover {
-    border-color: $primary-color;
-    transform: translateY(-2px);
-    box-shadow: $shadow-md;
+    transform: translateY(-4px);
+    border-color: #3b82f6;
+    box-shadow: 0 12px 32px rgba(59, 130, 246, 0.15);
+  }
+  
+  .case-image-wrapper {
+    position: relative;
+    
+    .case-image {
+      width: 100%;
+      height: 200px;
+      object-fit: cover;
+    }
+    
+    .similarity-badge {
+      position: absolute;
+      top: 12px;
+      right: 12px;
+      background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+      color: white;
+      padding: 6px 12px;
+      border-radius: 20px;
+      font-size: 0.75rem;
+      font-weight: 600;
+      box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
+    }
+  }
+  
+  .case-content {
+    padding: 20px;
+    
+    .case-name {
+      font-size: 1.125rem;
+      font-weight: 600;
+      color: #1e293b;
+      margin: 0 0 12px 0;
+      line-height: 1.3;
+    }
+    
+    .case-meta {
+      display: flex;
+      gap: 16px;
+      font-size: 0.875rem;
+      color: #64748b;
+      
+      .case-designer,
+      .case-area {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+      }
+    }
   }
 }
 
@@ -508,6 +921,79 @@ $card-padding: 16px;
   font-weight: 500;
 }
 
+// 已选案例区域样式
+.selected-cases-section {
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+    
+    h3 {
+      font-size: 1.125rem;
+      font-weight: 600;
+      color: #1e293b;
+      margin: 0;
+    }
+    
+    .section-subtitle {
+      font-size: 0.875rem;
+      color: #64748b;
+      background: #f1f5f9;
+      padding: 4px 12px;
+      border-radius: 12px;
+    }
+  }
+  
+  .selected-cases-list {
+    display: grid;
+    gap: 12px;
+  }
+  
+  .selected-case-item {
+    background: #f8fafc;
+    border: 2px solid #e2e8f0;
+    border-radius: 12px;
+    padding: 16px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    transition: all 0.2s ease;
+    
+    &:hover {
+      border-color: #10b981;
+      background: #f0fdf4;
+    }
+    
+    .case-info {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      color: #059669;
+      font-weight: 500;
+      font-size: 0.875rem;
+    }
+    
+    .remove-case-btn {
+      background: none;
+      border: none;
+      color: #ef4444;
+      cursor: pointer;
+      padding: 8px;
+      border-radius: 8px;
+      transition: all 0.2s ease;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      
+      &:hover {
+        background: #fef2f2;
+        color: #dc2626;
+      }
+    }
+  }
+}
+
 // 已选参考案例
 .selected-cases {
   margin-top: 24px;
@@ -548,6 +1034,90 @@ $card-padding: 16px;
   }
 }
 
+// 操作按钮区域样式
+.action-section {
+  margin-top: 40px;
+  padding-top: 32px;
+  border-top: 1px solid #e2e8f0;
+  
+  .action-buttons {
+    display: flex;
+    gap: 16px;
+    justify-content: center;
+    
+    @media (max-width: 768px) {
+      flex-direction: column;
+      gap: 12px;
+    }
+  }
+  
+  .btn-primary,
+  .btn-secondary {
+    padding: 16px 32px;
+    border: none;
+    border-radius: 16px;
+    font-size: 1rem;
+    font-weight: 600;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 12px;
+    min-width: 180px;
+    
+    &.btn-lg {
+      padding: 18px 36px;
+      font-size: 1.125rem;
+    }
+    
+    &:disabled {
+      opacity: 0.6;
+      cursor: not-allowed;
+      transform: none;
+    }
+    
+    .animate-spin {
+      animation: spin 1s linear infinite;
+    }
+  }
+  
+  .btn-primary {
+    background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
+    color: white;
+    box-shadow: 0 8px 32px rgba(59, 130, 246, 0.3);
+    
+    &:hover:not(:disabled) {
+      transform: translateY(-2px);
+      box-shadow: 0 12px 48px rgba(59, 130, 246, 0.4);
+    }
+  }
+  
+  .btn-secondary {
+    background: white;
+    color: #3b82f6;
+    border: 2px solid #3b82f6;
+    box-shadow: 0 4px 16px rgba(59, 130, 246, 0.1);
+    
+    &:hover:not(:disabled) {
+      background: #3b82f6;
+      color: white;
+      transform: translateY(-2px);
+      box-shadow: 0 8px 32px rgba(59, 130, 246, 0.3);
+    }
+  }
+}
+
+// 旋转动画
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
 // iOS风格操作按钮区域
 .action-section {
   background-color: $background-tertiary;