瀏覽代碼

feet:admin

徐福静0235668 11 小時之前
父節點
當前提交
939fd4df65
共有 44 個文件被更改,包括 13673 次插入4 次删除
  1. 449 0
      docs/admin-interface-implementation.md
  2. 34 2
      package-lock.json
  3. 2 0
      package.json
  4. 28 2
      src/app/app.routes.ts
  5. 108 0
      src/app/pages/admin/admin-layout/admin-layout.html
  6. 264 0
      src/app/pages/admin/admin-layout/admin-layout.scss
  7. 20 0
      src/app/pages/admin/admin-layout/admin-layout.ts
  8. 219 0
      src/app/pages/admin/api-integrations/api-dialog/api-dialog.html
  9. 490 0
      src/app/pages/admin/api-integrations/api-dialog/api-dialog.scss
  10. 215 0
      src/app/pages/admin/api-integrations/api-dialog/api-dialog.ts
  11. 589 0
      src/app/pages/admin/api-integrations/api-integrations.html
  12. 836 0
      src/app/pages/admin/api-integrations/api-integrations.scss
  13. 748 0
      src/app/pages/admin/api-integrations/api-integrations.ts
  14. 211 0
      src/app/pages/admin/dashboard/dashboard.html
  15. 352 0
      src/app/pages/admin/dashboard/dashboard.scss
  16. 115 0
      src/app/pages/admin/dashboard/dashboard.service.ts
  17. 130 0
      src/app/pages/admin/dashboard/dashboard.ts
  18. 390 0
      src/app/pages/admin/logs/logs.html
  19. 616 0
      src/app/pages/admin/logs/logs.scss
  20. 524 0
      src/app/pages/admin/logs/logs.ts
  21. 127 0
      src/app/pages/admin/project-management/project-dialog/project-dialog.html
  22. 250 0
      src/app/pages/admin/project-management/project-dialog/project-dialog.scss
  23. 121 0
      src/app/pages/admin/project-management/project-dialog/project-dialog.ts
  24. 324 0
      src/app/pages/admin/project-management/project-management.html
  25. 496 0
      src/app/pages/admin/project-management/project-management.scss
  26. 327 0
      src/app/pages/admin/project-management/project-management.ts
  27. 229 0
      src/app/pages/admin/system-settings/setting-dialog/setting-dialog.html
  28. 383 0
      src/app/pages/admin/system-settings/setting-dialog/setting-dialog.scss
  29. 215 0
      src/app/pages/admin/system-settings/setting-dialog/setting-dialog.ts
  30. 519 0
      src/app/pages/admin/system-settings/system-settings.html
  31. 759 0
      src/app/pages/admin/system-settings/system-settings.scss
  32. 560 0
      src/app/pages/admin/system-settings/system-settings.ts
  33. 119 0
      src/app/pages/admin/user-management/role-dialog/role-dialog.html
  34. 360 0
      src/app/pages/admin/user-management/role-dialog/role-dialog.scss
  35. 223 0
      src/app/pages/admin/user-management/role-dialog/role-dialog.ts
  36. 164 0
      src/app/pages/admin/user-management/user-dialog/user-dialog.html
  37. 255 0
      src/app/pages/admin/user-management/user-dialog/user-dialog.scss
  38. 134 0
      src/app/pages/admin/user-management/user-dialog/user-dialog.ts
  39. 493 0
      src/app/pages/admin/user-management/user-management.html
  40. 567 0
      src/app/pages/admin/user-management/user-management.scss
  41. 562 0
      src/app/pages/admin/user-management/user-management.ts
  42. 106 0
      src/app/services/auth.service.ts
  43. 2 0
      src/index.html
  44. 38 0
      src/styles.scss

+ 449 - 0
docs/admin-interface-implementation.md

@@ -0,0 +1,449 @@
+# 映三色项目管理系统 - 总管理员界面实现指南
+
+## 整体架构概述
+
+基于 Angular 20.1.0 框架,为总管理员角色创建一个功能完整、视觉专业的后台管理界面。采用模块化设计,实现权限控制、数据看板、项目管理、人员管理等核心功能。
+
+## 页面布局架构
+
+### 1. 布局基础
+- **三栏式响应式布局**:固定左侧导航 + 顶部操作栏 + 主内容区
+- **主色调**:白色为主,深蓝色 (#165DFF) 为强调色,灰色系为辅助色
+- **字体规范**:确保字体大小适中,主要内容文本不小于 14px
+- **间距规范**:采用 8px 网格系统,确保视觉一致性
+
+### 2. 左侧导航栏实现
+
+```typescript
+// admin-layout/admin-layout.ts
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule, RouterOutlet } from '@angular/router';
+
+@Component({
+  selector: 'app-admin-layout',
+  standalone: true,
+  imports: [CommonModule, RouterModule, RouterOutlet],
+  templateUrl: './admin-layout.html',
+  styleUrl: './admin-layout.scss'
+}) 
+export class AdminLayout {
+  sidebarOpen = true;
+  currentUser = { name: '超级管理员', avatar: 'https://picsum.photos/id/1/40/40' };
+  currentDate = new Date();
+
+  toggleSidebar() {
+    this.sidebarOpen = !this.sidebarOpen;
+  }
+}
+```
+
+```html
+<!-- admin-layout/admin-layout.html -->
+<div class="admin-layout">
+  <!-- 顶部导航栏 -->
+  <header class="top-navbar">
+    <div class="navbar-left">
+      <button class="menu-toggle" (click)="toggleSidebar()">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <line x1="3" y1="12" x2="21" y2="12"></line>
+          <line x1="3" y1="6" x2="21" y2="6"></line>
+          <line x1="3" y1="18" x2="21" y2="18"></line>
+        </svg>
+      </button>
+      <h1 class="app-title">系统管理后台</h1>
+    </div>
+    
+    <div class="navbar-right">
+      <div class="date-display">{{ currentDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }) }}</div>
+      <div class="user-profile">
+        <img [src]="currentUser.avatar" alt="用户头像" class="user-avatar">
+        <span class="user-name">{{ currentUser.name }}</span>
+      </div>
+    </div>
+  </header>
+
+  <!-- 主要内容区 -->
+  <main class="main-content">
+    <!-- 左侧侧边栏 -->
+    <aside class="sidebar" [class.collapsed]="!sidebarOpen">
+      <nav class="sidebar-nav">
+        <div class="nav-section">
+          <h3 class="section-title">核心功能</h3>
+          <a href="/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">
+            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <line x1="8" y1="6" x2="21" y2="6"></line>
+              <line x1="8" y1="12" x2="21" y2="12"></line>
+              <line x1="8" y1="18" x2="21" y2="18"></line>
+              <line x1="3" y1="6" x2="3.01" y2="6"></line>
+              <line x1="3" y1="12" x2="3.01" y2="12"></line>
+              <line x1="3" y1="18" x2="3.01" y2="18"></line>
+            </svg>
+            <span>项目管理</span>
+          </a>
+          <a href="/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>
+              <path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
+              <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+            </svg>
+            <span>设计师管理</span>
+          </a>
+          <a href="/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">
+            <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>
+            </svg>
+            <span>财务管理</span>
+          </a>
+        </div>
+        
+        <div class="nav-section">
+          <h3 class="section-title">系统设置</h3>
+          <a href="/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">
+            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+              <polyline points="14 2 14 8 20 8"></polyline>
+              <line x1="16" y1="13" x2="8" y2="13"></line>
+              <line x1="16" y1="17" x2="8" y2="17"></line>
+              <polyline points="10 9 9 9 8 9"></polyline>
+            </svg>
+            <span>系统日志</span>
+          </a>
+          <a href="/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>
+            </svg>
+            <span>API集成管理</span>
+          </a>
+        </div>
+      </nav>
+    </aside>
+
+    <!-- 中间内容区 -->
+    <div class="content-wrapper" [class.expanded]="!sidebarOpen">
+      <router-outlet />
+    </div>
+  </main>
+</div>
+```
+
+## 模块实现指南
+
+### 1. 总览看板模块
+
+```typescript
+// admin/dashboard/dashboard.ts
+import { Component, OnInit, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import * as echarts from 'echarts';
+import { AdminDashboardService } from './dashboard.service';
+
+@Component({
+  selector: 'app-admin-dashboard',
+  standalone: true,
+  imports: [CommonModule, RouterModule],
+  templateUrl: './dashboard.html',
+  styleUrl: './dashboard.scss'
+}) 
+export class AdminDashboard implements OnInit {
+  // 统计数据
+  stats = {
+    totalProjects: signal(128),
+    activeProjects: signal(86),
+    completedProjects: signal(42),
+    totalDesigners: signal(24),
+    totalCustomers: signal(356),
+    totalRevenue: signal(1258000)
+  };
+
+  constructor(private dashboardService: AdminDashboardService) {}
+
+  ngOnInit(): void {
+    this.loadDashboardData();
+    this.initCharts();
+  }
+
+  loadDashboardData(): void {
+    // 从服务加载数据
+    this.dashboardService.getDashboardStats().subscribe(data => {
+      // 更新统计数据
+    });
+  }
+
+  initCharts(): void {
+    // 初始化项目趋势图
+    const projectChart = echarts.init(document.getElementById('projectTrendChart')!);
+    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 revenueChart = echarts.init(document.getElementById('revenueChart')!);
+    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)'
+          }
+        }
+      }]
+    });
+
+    // 响应窗口大小变化
+    window.addEventListener('resize', () => {
+      projectChart.resize();
+      revenueChart.resize();
+    });
+  }
+}
+```
+
+### 2. 项目管理模块
+
+创建项目列表和项目详情组件,实现项目的创建、分配、编辑和关闭功能。
+
+### 3. 用户与角色管理模块
+
+实现用户管理、角色管理和权限分配功能。
+
+### 4. 系统设置模块
+
+实现全局设置、流程配置、报价规则等系统级配置功能。
+
+### 5. 系统日志模块
+
+实现操作日志查询、筛选和导出功能。
+
+### 6. API集成管理模块
+
+实现与第三方服务的接口配置功能。
+
+## 组件化设计
+
+创建以下可复用组件:
+
+1. **数据卡片组件**
+```typescript
+// shared/components/stats-card/stats-card.ts
+import { Component, Input } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+@Component({
+  selector: 'app-stats-card',
+  standalone: true,
+  imports: [CommonModule],
+  template: `
+    <div class="stats-card">
+      <div class="stat-icon" [ngClass]="iconColor">
+        <ng-content select="[icon]"></ng-content>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ value }}</div>
+        <div class="stat-label">{{ label }}</div>
+      </div>
+      <div class="stat-trend" [ngClass]="trendClass">
+        <span>{{ trend }}</span>
+      </div>
+    </div>
+  `,
+  styles: [`
+    /* 样式定义 */
+  `]
+}) 
+export class StatsCard {
+  @Input() value: string | number = 0;
+  @Input() label: string = '';
+  @Input() trend: string = '';
+  @Input() trendType: 'positive' | 'negative' | 'neutral' = 'neutral';
+  @Input() iconColor: string = 'primary';
+
+  get trendClass() {
+    return `stat-trend ${this.trendType}`;
+  }
+}
+```
+
+2. **表格组件、筛选组件、表单组件等
+
+## 状态管理与数据流
+
+1. **使用 RxJS 管理数据流**
+```typescript
+// admin/services/admin-dashboard.service.ts
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable, map } from 'rxjs';
+import { DashboardStats } from '../models/admin.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class AdminDashboardService {
+  constructor(private http: HttpClient) {}
+
+  getDashboardStats(): Observable<DashboardStats> {
+    // 模拟API调用
+    return this.http.get<DashboardStats>('/api/admin/dashboard/stats')
+      .pipe(
+        map(response => {
+          // 数据转换处理
+          return response;
+        })
+      );
+  }
+}
+```
+
+## 权限控制实现
+
+创建角色守卫实现路由权限控制:
+
+```typescript
+// guards/role.guard.ts
+import { inject } from '@angular/core';
+import { CanActivateFn, Router } from '@angular/router';
+import { AuthService } from '../services/auth.service';
+
+export const roleGuard: CanActivateFn = (route, state) => {
+  const authService = inject(AuthService);
+  const router = inject(Router);
+  
+  const requiredRoles = route.data['roles'] as string[];
+  const userRoles = authService.getUserRoles();
+  
+  // 检查用户是否有管理权限
+  const hasAccess = requiredRoles.some(role => userRoles.includes(role));
+  
+  if (!hasAccess) {
+    router.navigate(['/']);
+    return false;
+  }
+  
+  return true;
+};
+```
+
+## 路由配置
+
+```typescript
+// app.routes.ts
+import { Routes } from '@angular/router';
+// ... 其他导入
+import { AdminLayout } from './pages/admin/admin-layout/admin-layout';
+import { AdminDashboard } from './pages/admin/dashboard/dashboard';
+import { ProjectManagement } from './pages/admin/project-management/project-management';
+import { UserManagement } from './pages/admin/user-management/user-management';
+import { SystemSettings } from './pages/admin/system-settings/system-settings';
+import { SystemLogs } from './pages/admin/system-logs/system-logs';
+import { ApiIntegrations } from './pages/admin/api-integrations/api-integrations';
+import { roleGuard } from './guards/role.guard';
+
+export const routes: Routes = [
+  // ... 其他路由
+  
+  // 管理员路由
+  {
+    path: 'admin',
+    component: AdminLayout,
+    canActivate: [roleGuard],
+    data: { roles: ['admin'] },
+    children: [
+      { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
+      { path: 'dashboard', component: AdminDashboard, title: '总览看板' },
+      { path: 'project-management', component: ProjectManagement, title: '项目管理' },
+      { path: 'user-management', component: UserManagement, title: '用户与角色管理' },
+      { path: 'system-settings', component: SystemSettings, title: '系统设置' },
+      { path: 'logs', component: SystemLogs, title: '系统日志' },
+      { path: 'api-integrations', component: ApiIntegrations, title: 'API集成管理' }
+    ]
+  },
+  
+  // 默认路由重定向
+  { path: '', redirectTo: '/customer-service/dashboard', pathMatch: 'full' },
+  { path: '**', redirectTo: '/customer-service/dashboard' }
+];
+```
+
+## UI/UX 设计指南
+
+1. **视觉层次**:通过卡片、阴影、边框创建清晰的视觉层次
+2. **交互反馈**:为所有可点击元素添加悬停效果,操作结果提供明确反馈
+3. **加载状态**:实现骨架屏或加载动画
+4. **错误处理**:提供友好的错误提示和引导
+5. **响应式设计**:确保在不同屏幕尺寸下的良好体验
+
+## 技术实现建议
+
+1. **安装依赖**:
+```bash
+# 安装 Angular Material
+npm install @angular/material @angular/cdk @angular/animations
+
+# 安装 ECharts 和 Angular 包装器
+npm install echarts ngx-echarts
+
+# 安装 NgRx 用于状态管理
+npm install @ngrx/store @ngrx/effects @ngrx/entity @ngrx/store-devtools
+```
+
+2. **模块懒加载**:使用 `loadChildren` 实现模块懒加载优化性能
+
+3. **国际化支持**:为管理界面添加多语言支持
+
+4. **单元测试**:为核心组件和服务编写单元测试
+
+5. **性能优化**:实现虚拟滚动、图片懒加载等性能优化措施
+
+通过以上实现指南,可以创建一个功能完整、体验良好的总管理员界面,满足所有需求并确保代码质量和可维护性。

+ 34 - 2
package-lock.json

@@ -8,10 +8,12 @@
       "name": "yss-project",
       "version": "0.0.0",
       "dependencies": {
+        "@angular/cdk": "^20.2.2",
         "@angular/common": "^20.1.0",
         "@angular/compiler": "^20.1.0",
         "@angular/core": "^20.1.0",
         "@angular/forms": "^20.1.0",
+        "@angular/material": "^20.2.2",
         "@angular/platform-browser": "^20.1.0",
         "@angular/router": "^20.1.0",
         "echarts": "^6.0.0",
@@ -526,6 +528,21 @@
         }
       }
     },
+    "node_modules/@angular/cdk": {
+      "version": "20.2.2",
+      "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.2.tgz",
+      "integrity": "sha512-jLvIMmFI8zoi6vAu1Aszua59GmhqBOtsVfkwLUGg5Hi86DI/inJr9BznNX2EKDtaulYMGZCmDgsltXQXeqP5Lg==",
+      "license": "MIT",
+      "dependencies": {
+        "parse5": "^8.0.0",
+        "tslib": "^2.3.0"
+      },
+      "peerDependencies": {
+        "@angular/common": "^20.0.0 || ^21.0.0",
+        "@angular/core": "^20.0.0 || ^21.0.0",
+        "rxjs": "^6.5.3 || ^7.4.0"
+      }
+    },
     "node_modules/@angular/cli": {
       "version": "20.2.2",
       "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.2.2.tgz",
@@ -665,6 +682,23 @@
         "rxjs": "^6.5.3 || ^7.4.0"
       }
     },
+    "node_modules/@angular/material": {
+      "version": "20.2.2",
+      "resolved": "https://registry.npmjs.org/@angular/material/-/material-20.2.2.tgz",
+      "integrity": "sha512-ovLk6h6XIw3qtSjp2bSqFn7ANYvWOIh2zTrRPdAB78siOpqs11d8YdyD4LUEuUrcZoInNgK7AMJsfldDkHwhnA==",
+      "license": "MIT",
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "peerDependencies": {
+        "@angular/cdk": "20.2.2",
+        "@angular/common": "^20.0.0 || ^21.0.0",
+        "@angular/core": "^20.0.0 || ^21.0.0",
+        "@angular/forms": "^20.0.0 || ^21.0.0",
+        "@angular/platform-browser": "^20.0.0 || ^21.0.0",
+        "rxjs": "^6.5.3 || ^7.4.0"
+      }
+    },
     "node_modules/@angular/platform-browser": {
       "version": "20.2.4",
       "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.2.4.tgz",
@@ -9699,7 +9733,6 @@
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
       "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "entities": "^6.0.0"
@@ -9753,7 +9786,6 @@
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
       "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
-      "dev": true,
       "license": "BSD-2-Clause",
       "engines": {
         "node": ">=0.12"

+ 2 - 0
package.json

@@ -21,10 +21,12 @@
   },
   "private": true,
   "dependencies": {
+    "@angular/cdk": "^20.2.2",
     "@angular/common": "^20.1.0",
     "@angular/compiler": "^20.1.0",
     "@angular/core": "^20.1.0",
     "@angular/forms": "^20.1.0",
+    "@angular/material": "^20.2.2",
     "@angular/platform-browser": "^20.1.0",
     "@angular/router": "^20.1.0",
     "echarts": "^6.0.0",

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

@@ -1,5 +1,6 @@
 import { Routes } from '@angular/router';
 
+
 // 客服页面
 import { CustomerServiceLayout } from './pages/customer-service/customer-service-layout/customer-service-layout';
 import { Dashboard as CustomerServiceDashboard } from './pages/customer-service/dashboard/dashboard';
@@ -31,7 +32,14 @@ import { Attendance } from './pages/hr/attendance/attendance';
 import { Assets } from './pages/hr/assets/assets';
 
 // 管理员页面
+import { AdminLayout } from './pages/admin/admin-layout/admin-layout';
+import { AdminDashboard } from './pages/admin/dashboard/dashboard';
 import { SystemManagement } from './pages/admin/system-management/system-management';
+import { ProjectManagement } from './pages/admin/project-management/project-management';
+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';
 
 export const routes: Routes = [
   // 客服路由
@@ -97,9 +105,27 @@ export const routes: Routes = [
   // 管理员路由
   {
     path: 'admin',
+    component: AdminLayout,
+    
+    data: { roles: ['admin'] },
     children: [
-      { path: '', redirectTo: 'system-management', pathMatch: 'full' },
-      { path: 'system-management', component: SystemManagement, title: '系统管理' }
+      { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
+      { path: 'dashboard', component: AdminDashboard, title: '总览看板' },
+      
+      // 项目管理相关路由
+      { path: 'project-management', component: ProjectManagement, title: '项目管理' },
+      
+      // 用户与角色管理相关路由
+      { path: 'user-management', component: UserManagement, title: '用户与角色管理' },
+      
+      // 系统设置相关路由
+      { path: 'system-settings', component: SystemSettings, title: '系统设置' },
+      
+      // 系统日志相关路由
+      { path: 'logs', component: Logs, title: '系统日志' },
+      
+      // API集成管理相关路由
+      { path: 'api-integrations', component: ApiIntegrations, title: 'API集成管理' }
     ]
   },
 

+ 108 - 0
src/app/pages/admin/admin-layout/admin-layout.html

@@ -0,0 +1,108 @@
+<div class="admin-layout">
+  <!-- 顶部导航栏 -->
+  <header class="top-navbar">
+    <div class="navbar-left">
+      <button class="menu-toggle" (click)="toggleSidebar()">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+          <line x1="3" y1="12" x2="21" y2="12"></line>
+          <line x1="3" y1="6" x2="21" y2="6"></line>
+          <line x1="3" y1="18" x2="21" y2="18"></line>
+        </svg>
+      </button>
+      <h1 class="app-title">系统管理后台</h1>
+    </div>
+    
+    <div class="navbar-right">
+      <div class="date-display">{{ currentDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' }) }}</div>
+      <div class="user-profile">
+        <img [src]="currentUser.avatar" alt="用户头像" class="user-avatar">
+        <span class="user-name">{{ currentUser.name }}</span>
+      </div>
+    </div>
+  </header>
+
+  <!-- 主要内容区 -->
+  <main class="main-content">
+    <!-- 左侧侧边栏 -->
+    <aside class="sidebar" [class.collapsed]="!sidebarOpen">
+      <nav class="sidebar-nav">
+        <div class="nav-section">
+          <h3 class="section-title">核心功能</h3>
+          <a href="/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">
+            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <line x1="8" y1="6" x2="21" y2="6"></line>
+              <line x1="8" y1="12" x2="21" y2="12"></line>
+              <line x1="8" y1="18" x2="21" y2="18"></line>
+              <line x1="3" y1="6" x2="3.01" y2="6"></line>
+              <line x1="3" y1="12" x2="3.01" y2="12"></line>
+              <line x1="3" y1="18" x2="3.01" y2="18"></line>
+            </svg>
+            <span>项目管理</span>
+          </a>
+          <a href="/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>
+              <path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
+              <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+            </svg>
+            <span>设计师管理</span>
+          </a>
+          <a href="/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">
+            <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>
+            </svg>
+            <span>财务管理</span>
+          </a>
+        </div>
+        
+        <div class="nav-section">
+          <h3 class="section-title">系统设置</h3>
+          <a href="/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">
+            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+              <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+              <polyline points="14 2 14 8 20 8"></polyline>
+              <line x1="16" y1="13" x2="8" y2="13"></line>
+              <line x1="16" y1="17" x2="8" y2="17"></line>
+              <polyline points="10 9 9 9 8 9"></polyline>
+            </svg>
+            <span>系统日志</span>
+          </a>
+          <a href="/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>
+            </svg>
+            <span>API集成管理</span>
+          </a>
+        </div>
+      </nav>
+    </aside>
+
+    <!-- 中间内容区 -->
+    <div class="content-wrapper" [class.expanded]="!sidebarOpen">
+      <router-outlet />
+    </div>
+  </main>
+</div>

+ 264 - 0
src/app/pages/admin/admin-layout/admin-layout.scss

@@ -0,0 +1,264 @@
+// 全局变量定义
+$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 1px 2px rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
+$shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.1);
+$border-radius: 8px;
+$transition: all 0.3s ease;
+
+// 主容器
+.admin-layout {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  overflow: hidden;
+  background-color: $background-secondary;
+}
+
+// 顶部导航栏
+.top-navbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 24px;
+  height: 64px;
+  background-color: $primary-dark;
+  border-bottom: 1px solid $border-color;
+  box-shadow: $shadow-sm;
+  position: sticky;
+  top: 0;
+  z-index: 1000;
+
+  .navbar-left {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+  }
+
+  .menu-toggle {
+    background: none;
+    border: none;
+    cursor: pointer;
+    color: white;
+    padding: 8px;
+    transition: $transition;
+
+    &:hover {
+      background-color: rgba(255, 255, 255, 0.1);
+      border-radius: 4px;
+    }
+  }
+
+  .app-title {
+    font-size: 20px;
+    font-weight: 600;
+    color: white;
+    margin: 0;
+  }
+
+  .navbar-right {
+    display: flex;
+    align-items: center;
+    gap: 24px;
+  }
+
+  .date-display {
+    font-size: 14px;
+    color: rgba(255, 255, 255, 0.9);
+  }
+
+  .user-profile {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 6px 12px;
+    background-color: rgba(255, 255, 255, 0.1);
+    border-radius: $border-radius;
+    cursor: pointer;
+    transition: $transition;
+
+    &:hover {
+      background-color: rgba(255, 255, 255, 0.2);
+    }
+
+    .user-avatar {
+      width: 36px;
+      height: 36px;
+      border-radius: 50%;
+      object-fit: cover;
+    }
+
+    .user-name {
+      font-size: 14px;
+      font-weight: 500;
+      color: white;
+    }
+  }
+}
+
+// 主要内容区
+.main-content {
+  display: flex;
+  flex: 1;
+  overflow: hidden;
+}
+
+// 左侧侧边栏
+.sidebar {
+  width: 240px;
+  background-color: $background-primary;
+  border-right: 1px solid $border-color;
+  display: flex;
+  flex-direction: column;
+  transition: $transition;
+  overflow-y: auto;
+
+  .sidebar-nav {
+    flex: 1;
+    padding: 20px 0;
+  }
+
+  .nav-section {
+    margin-bottom: 24px;
+
+    .section-title {
+      font-size: 12px;
+      font-weight: 600;
+      color: $text-tertiary;
+      text-transform: uppercase;
+      padding: 0 24px 8px 24px;
+      margin: 0;
+      letter-spacing: 0.5px;
+    }
+  }
+
+  .nav-item {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 12px 24px;
+    color: $text-secondary;
+    text-decoration: none;
+    border-left: 3px solid transparent;
+    transition: $transition;
+    font-size: 14px;
+
+    &:hover {
+      background-color: $background-tertiary;
+      color: $primary-color;
+    }
+
+    &.active {
+      color: $primary-color;
+      background-color: color-mix(in srgb, $primary-color 5%, transparent);
+      border-left-color: $primary-color;
+      font-weight: 500;
+    }
+  }
+}
+
+// 侧边栏折叠样式
+.sidebar.collapsed {
+  width: 64px;
+
+  .section-title,
+  .nav-item span {
+    display: none;
+  }
+
+  .nav-item {
+    justify-content: center;
+    padding: 12px;
+  }
+}
+
+// 中间内容区
+.content-wrapper {
+  flex: 1;
+  overflow-y: auto;
+  padding: 24px;
+  transition: $transition;
+  background-color: $background-secondary;
+}
+
+// 内容区展开样式
+.content-wrapper.expanded {
+  padding-left: 24px;
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .sidebar {
+    width: 220px;
+  }
+}
+
+@media (max-width: 768px) {
+  .top-navbar {
+    padding: 0 16px;
+
+    .app-title {
+      font-size: 18px;
+    }
+
+    .navbar-right {
+      gap: 16px;
+    }
+  }
+
+  .sidebar {
+    position: fixed;
+    top: 64px;
+    left: 0;
+    height: calc(100vh - 64px);
+    z-index: 900;
+    transform: translateX(0);
+
+    &.collapsed {
+      transform: translateX(-100%);
+    }
+  }
+
+  .content-wrapper {
+    padding: 16px;
+  }
+}
+
+@media (max-width: 480px) {
+  .top-navbar {
+    height: 56px;
+
+    .app-title {
+      font-size: 16px;
+    }
+
+    .navbar-right {
+      gap: 12px;
+    }
+
+    .user-profile .user-name {
+      display: none;
+    }
+  }
+
+  .sidebar {
+    top: 56px;
+    height: calc(100vh - 56px);
+  }
+
+  .content-wrapper {
+    padding: 12px;
+  }
+}

+ 20 - 0
src/app/pages/admin/admin-layout/admin-layout.ts

@@ -0,0 +1,20 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule, RouterOutlet } from '@angular/router';
+
+@Component({
+  selector: 'app-admin-layout',
+  standalone: true,
+  imports: [CommonModule, RouterModule, RouterOutlet],
+  templateUrl: './admin-layout.html',
+  styleUrl: './admin-layout.scss'
+}) 
+export class AdminLayout {
+  sidebarOpen = true;
+  currentUser = { name: '超级管理员', avatar: 'https://picsum.photos/id/1/40/40' };
+  currentDate = new Date();
+
+  toggleSidebar() {
+    this.sidebarOpen = !this.sidebarOpen;
+  }
+}

+ 219 - 0
src/app/pages/admin/api-integrations/api-dialog/api-dialog.html

@@ -0,0 +1,219 @@
+<div class="api-dialog">
+  <!-- 对话框标题 -->
+  <div class="dialog-header">
+    <h2 class="dialog-title">{{ dialogTitle }}</h2>
+    <button mat-icon-button class="close-btn" (click)="onCancel()">
+      <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="18" y1="6" x2="6" y2="18"></line>
+        <line x1="6" y1="6" x2="18" y2="18"></line>
+      </svg>
+    </button>
+  </div>
+
+  <!-- 表单内容 -->
+  <div class="dialog-content">
+    <form class="api-form">
+      <!-- 基本信息标签页 -->
+      <div class="form-section">
+        <h3 class="section-title">基本信息</h3>
+        
+        <!-- 集成名称 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>集成名称 *</mat-label>
+            <input matInput type="text" [(ngModel)]="integration.name" name="name"
+                   placeholder="请输入集成名称">
+            <mat-icon matSuffix>label</mat-icon>
+            <mat-error *ngIf="errors['name']">{{ errors['name'] }}</mat-error>
+          </mat-form-field>
+        </div>
+
+        <!-- 集成类型 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>集成类型 *</mat-label>
+            <mat-select [(ngModel)]="integration.type" name="type">
+              <mat-option value="">请选择集成类型</mat-option>
+              <mat-option *ngFor="let type of integrationTypes" [value]="type">
+                {{ type }}
+              </mat-option>
+            </mat-select>
+            <mat-icon matSuffix>category</mat-icon>
+            <mat-error *ngIf="errors['type']">{{ errors['type'] }}</mat-error>
+          </mat-form-field>
+        </div>
+
+        <!-- API端点 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>API端点 *</mat-label>
+            <input matInput type="text" [(ngModel)]="integration.endpoint" name="endpoint"
+                   placeholder="例如:https://api.example.com/v1">
+            <mat-icon matSuffix>public</mat-icon>
+            <mat-error *ngIf="errors['endpoint']">{{ errors['endpoint'] }}</mat-error>
+          </mat-form-field>
+        </div>
+
+        <!-- 认证方式 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>认证方式 *</mat-label>
+            <mat-select [(ngModel)]="integration.authentication" name="authentication">
+              <mat-option value="">请选择认证方式</mat-option>
+              <mat-option *ngFor="let method of authenticationMethods" [value]="method">
+                {{ method }}
+              </mat-option>
+            </mat-select>
+            <mat-icon matSuffix>lock</mat-icon>
+            <mat-error *ngIf="errors['authentication']">{{ errors['authentication'] }}</mat-error>
+          </mat-form-field>
+        </div>
+
+        <!-- 描述 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>描述 *</mat-label>
+            <textarea matInput [(ngModel)]="integration.description" name="description"
+                      placeholder="请输入集成描述" rows="3"></textarea>
+            <mat-icon matSuffix>description</mat-icon>
+            <mat-error *ngIf="errors['description']">{{ errors['description'] }}</mat-error>
+          </mat-form-field>
+        </div>
+
+        <!-- 激活状态 -->
+        <div class="form-group">
+          <div class="toggle-wrapper">
+            <mat-slide-toggle
+              [checked]="integration.status === 'active'"
+              (change)="integration.status = $event.checked ? 'active' : 'inactive'"
+              class="active-toggle">
+              <span class="toggle-label">启用集成</span>
+            </mat-slide-toggle>
+          </div>
+        </div>
+      </div>
+
+      <!-- 配置详情标签页 -->
+      <div class="form-section">
+        <h3 class="section-title">配置详情</h3>
+        
+        <!-- 配置字段说明 -->
+        <div class="config-description">
+          <p>添加API集成所需的配置参数。根据不同的认证方式,可能需要配置API密钥、OAuth客户端信息等。</p>
+        </div>
+
+        <!-- 配置字段列表 -->
+        <div class="config-fields">
+          <div *ngFor="let key of configKeys; let i = index" class="config-field-item">
+            <div class="config-field-header">
+              <span class="field-index">配置项 {{ i + 1 }}</span>
+              <button mat-icon-button class="remove-btn"
+                      title="删除配置项"
+                      (click)="removeConfigField(i)">
+                <svg width="16" height="16" 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="config-field-content">
+              <mat-form-field appearance="outline" class="key-field">
+                <mat-label>配置键</mat-label>
+                <input matInput type="text" [(ngModel)]="configKeys[i]" 
+                       placeholder="例如:apiKey">
+                <mat-error *ngIf="errors[`config_key_${i}`]">{{ errors[`config_key_${i}`] }}</mat-error>
+              </mat-form-field>
+              
+              <mat-form-field appearance="outline" class="value-field">
+                <mat-label>配置值</mat-label>
+                <textarea matInput [(ngModel)]="configValues[i]" 
+                          placeholder="例如:abcdef123456" rows="2"></textarea>
+                <mat-error *ngIf="errors[`config_value_${i}`]">{{ errors[`config_value_${i}`] }}</mat-error>
+              </mat-form-field>
+            </div>
+          </div>
+
+          <!-- 添加新配置字段 -->
+          <div class="add-config-field">
+            <div class="add-field-content">
+              <mat-form-field appearance="outline" class="key-field">
+                <mat-label>新配置键</mat-label>
+                <input matInput type="text" [(ngModel)]="newConfigKey" 
+                       placeholder="输入配置键">
+              </mat-form-field>
+              
+              <mat-form-field appearance="outline" class="value-field">
+                <mat-label>新配置值</mat-label>
+                <input matInput type="text" [(ngModel)]="newConfigValue" 
+                       placeholder="输入配置值">
+              </mat-form-field>
+              
+              <button mat-button color="primary" class="add-btn"
+                      (click)="addConfigField()" [disabled]="!newConfigKey.trim() || !newConfigValue.trim()">
+                <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>
+            </div>
+          </div>
+
+          <!-- 无配置提示 -->
+          <div *ngIf="configKeys.length === 0" class="empty-config">
+            <p>暂无配置项,请点击上方添加按钮添加配置参数</p>
+          </div>
+        </div>
+
+        <!-- 配置提示 -->
+        <div class="config-tips">
+          <h4 class="tips-title">配置提示</h4>
+          <ul class="tips-list">
+            <li>敏感信息(如密码、密钥)将被加密存储</li>
+            <li>对于嵌套的JSON配置,可以在值中输入JSON格式字符串</li>
+            <li>配置参数将在API调用时作为请求参数或头部信息传递</li>
+          </ul>
+        </div>
+      </div>
+
+      <!-- 信息展示区(编辑模式) -->
+      <div class="form-section" *ngIf="isEditMode">
+        <h3 class="section-title">元信息</h3>
+        
+        <div class="meta-info">
+          <div class="meta-item">
+            <label>创建人:</label>
+            <span class="meta-value">{{ integration.createdBy }}</span>
+          </div>
+          
+          <div class="meta-item">
+            <label>创建时间:</label>
+            <span class="meta-value">{{ integration.createdAt }}</span>
+          </div>
+          
+          <div class="meta-item">
+            <label>更新时间:</label>
+            <span class="meta-value">{{ integration.updatedAt }}</span>
+          </div>
+          
+          <div class="meta-item" *ngIf="integration.lastTested">
+            <label>最后测试:</label>
+            <span class="meta-value">{{ integration.lastTested }}</span>
+          </div>
+        </div>
+      </div>
+    </form>
+  </div>
+
+  <!-- 操作按钮 -->
+  <div class="dialog-footer">
+    <button mat-button class="cancel-btn" (click)="onCancel()">
+      取消
+    </button>
+    <button mat-raised-button color="primary" class="save-btn" 
+            (click)="onSave()">
+      {{ isEditMode ? '保存修改' : '创建集成' }}
+    </button>
+  </div>
+</div>

+ 490 - 0
src/app/pages/admin/api-integrations/api-dialog/api-dialog.scss

@@ -0,0 +1,490 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$secondary-color: #6B7280;
+$success-color: #00B42A;
+$warning-color: #FFAA00;
+$error-color: #F53F3F;
+$text-primary: #333333;
+$text-secondary: #666666;
+$text-tertiary: #999999;
+$border-color: #E5E7EB;
+$bg-light: #F9FAFB;
+$bg-white: #FFFFFF;
+$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+
+// 对话框容器
+.api-dialog {
+  min-width: 700px;
+  max-width: 900px;
+  max-height: 90vh;
+  border-radius: 12px;
+  overflow: hidden;
+  background-color: $bg-white;
+  display: flex;
+  flex-direction: column;
+}
+
+// 对话框头部
+.dialog-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px 24px;
+  border-bottom: 1px solid $border-color;
+  background-color: $bg-light;
+  flex-shrink: 0;
+
+  .dialog-title {
+    font-size: 18px;
+    font-weight: 600;
+    color: $text-primary;
+    margin: 0;
+  }
+
+  .close-btn {
+    color: $text-tertiary;
+    padding: 4px;
+    border-radius: 6px;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: #E5E7EB;
+      color: $text-secondary;
+    }
+  }
+}
+
+// 对话框内容
+.dialog-content {
+  padding: 24px;
+  overflow-y: auto;
+  flex: 1;
+}
+
+// 表单区域
+.form-section {
+  margin-bottom: 24px;
+  padding-bottom: 24px;
+  border-bottom: 1px solid $border-color;
+
+  &:last-child {
+    border-bottom: none;
+    margin-bottom: 0;
+    padding-bottom: 0;
+  }
+
+  .section-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: $text-primary;
+    margin-bottom: 16px;
+  }
+
+  .config-description,
+  .empty-config {
+    background-color: #F0F9FF;
+    border: 1px solid #D1E9FF;
+    border-radius: 6px;
+    padding: 12px 16px;
+    margin-bottom: 16px;
+
+    p {
+      font-size: 13px;
+      color: $text-secondary;
+      margin: 0;
+      line-height: 1.4;
+    }
+  }
+
+  .empty-config {
+    text-align: center;
+    color: $text-tertiary;
+    background-color: $bg-light;
+    border-color: $border-color;
+  }
+}
+
+// 表单组
+.form-group {
+  margin-bottom: 16px;
+
+  .form-field {
+    width: 100%;
+    .mat-form-field-wrapper {
+      .mat-form-field-flex {
+        border-radius: 8px;
+      }
+
+      .mat-form-field-outline {
+        border-radius: 8px;
+      }
+
+      .mat-form-field-infix {
+        padding: 10px 0;
+
+        input,
+        textarea {
+          font-size: 14px;
+          color: $text-primary;
+        }
+
+        .mat-input-element {
+          &::placeholder {
+            color: $text-tertiary;
+          }
+        }
+
+        textarea {
+          resize: vertical;
+          min-height: 80px;
+        }
+      }
+
+      .mat-form-field-label {
+        font-size: 14px;
+        color: $text-secondary;
+      }
+
+      .mat-form-field-suffix {
+        .mat-icon {
+          color: $text-tertiary;
+        }
+      }
+
+      .mat-form-field-error {
+        font-size: 12px;
+        color: $error-color;
+      }
+    }
+  }
+
+  // 激活状态切换
+  .toggle-wrapper {
+    .active-toggle {
+      display: flex;
+      justify-content: flex-start;
+      .mat-slide-toggle-content {
+        font-size: 14px;
+        color: $text-secondary;
+      }
+    }
+  }
+}
+
+// 配置字段区域
+.config-fields {
+  margin-bottom: 24px;
+}
+
+.config-field-item {
+  background-color: $bg-light;
+  border-radius: 8px;
+  padding: 16px;
+  margin-bottom: 16px;
+  border: 1px solid $border-color;
+
+  .config-field-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 12px;
+
+    .field-index {
+      font-size: 12px;
+      font-weight: 500;
+      color: $text-secondary;
+      background-color: $bg-white;
+      padding: 4px 12px;
+      border-radius: 12px;
+    }
+
+    .remove-btn {
+      color: $error-color;
+      padding: 4px;
+    }
+  }
+
+  .config-field-content {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 12px;
+
+    .key-field,
+    .value-field {
+      .mat-form-field-wrapper {
+        .mat-form-field-flex {
+          border-radius: 6px;
+        }
+        .mat-form-field-outline {
+          border-radius: 6px;
+        }
+        .mat-form-field-infix {
+          .mat-input-element {
+            font-size: 13px;
+          }
+        }
+        .mat-form-field-label {
+          font-size: 13px;
+        }
+        .mat-form-field-error {
+          font-size: 11px;
+          color: $error-color;
+        }
+      }
+    }
+
+    .value-field {
+      .mat-form-field-wrapper {
+        .mat-form-field-infix {
+          textarea {
+            resize: vertical;
+            min-height: 60px;
+          }
+        }
+      }
+    }
+  }
+}
+
+// 添加配置字段
+.add-config-field {
+  background-color: #F0F9FF;
+  border: 2px dashed $primary-color;
+  border-radius: 8px;
+  padding: 16px;
+
+  .add-field-content {
+    display: grid;
+    grid-template-columns: 1fr 1fr auto;
+    gap: 12px;
+    align-items: end;
+
+    .key-field,
+    .value-field {
+      .mat-form-field-wrapper {
+        .mat-form-field-flex {
+          border-radius: 6px;
+        }
+        .mat-form-field-outline {
+          border-radius: 6px;
+        }
+        .mat-form-field-infix {
+          .mat-input-element {
+            font-size: 13px;
+          }
+        }
+        .mat-form-field-label {
+          font-size: 13px;
+        }
+      }
+    }
+
+    .add-btn {
+      height: 40px;
+      min-width: auto;
+      width: 40px;
+      padding: 0;
+      border-radius: 6px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      &:disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+      }
+    }
+  }
+}
+
+// 配置提示
+.config-tips {
+  background-color: $bg-light;
+  border-radius: 8px;
+  padding: 16px;
+  border: 1px solid $border-color;
+
+  .tips-title {
+    font-size: 13px;
+    font-weight: 600;
+    color: $text-secondary;
+    margin-bottom: 8px;
+  }
+
+  .tips-list {
+    list-style-type: none;
+    padding: 0;
+    margin: 0;
+
+    li {
+      font-size: 12px;
+      color: $text-tertiary;
+      margin-bottom: 4px;
+      padding-left: 16px;
+      position: relative;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+
+      &::before {
+        content: "•";
+        position: absolute;
+        left: 0;
+        color: $primary-color;
+        font-weight: bold;
+      }
+    }
+  }
+}
+
+// 元信息区域
+.meta-info {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 12px;
+
+  .meta-item {
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+    padding: 12px;
+    background-color: $bg-light;
+    border-radius: 6px;
+    border: 1px solid $border-color;
+
+    label {
+      font-size: 12px;
+      color: $text-secondary;
+      font-weight: 500;
+    }
+
+    .meta-value {
+      font-size: 13px;
+      color: $text-primary;
+      font-family: 'Courier New', monospace;
+    }
+  }
+}
+
+// 对话框底部
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  padding: 20px 24px;
+  border-top: 1px solid $border-color;
+  background-color: $bg-light;
+  flex-shrink: 0;
+
+  .cancel-btn {
+    padding: 8px 24px;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    color: $text-secondary;
+    background-color: $bg-white;
+    border: 1px solid $border-color;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: #F3F4F6;
+      border-color: $text-tertiary;
+    }
+  }
+
+  .save-btn {
+    padding: 8px 24px;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    background-color: $primary-color;
+    color: $bg-white;
+    transition: all 0.2s ease;
+    box-shadow: $shadow-sm;
+
+    &:hover {
+      background-color: #0E4BD8;
+      box-shadow: $shadow-md;
+      transform: translateY(-1px);
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 992px) {
+  .api-dialog {
+    min-width: auto;
+    width: 95vw;
+    max-width: 800px;
+    max-height: 95vh;
+  }
+
+  .config-field-content,
+  .add-field-content {
+    grid-template-columns: 1fr !important;
+  }
+
+  .add-field-content {
+    gap: 8px !important;
+  }
+
+  .add-btn {
+    width: 100% !important;
+    justify-content: center;
+  }
+}
+
+@media (max-width: 768px) {
+  .dialog-header,
+  .dialog-content,
+  .dialog-footer {
+    padding: 16px;
+  }
+
+  .dialog-title {
+    font-size: 16px !important;
+  }
+
+  .meta-info {
+    grid-template-columns: 1fr !important;
+  }
+}
+
+@media (max-width: 480px) {
+  .api-dialog {
+    width: 100vw;
+    max-width: none;
+    border-radius: 0;
+    max-height: 100vh;
+  }
+
+  .cancel-btn,
+  .save-btn {
+    padding: 6px 16px !important;
+    font-size: 13px !important;
+  }
+}
+
+// 滚动条样式
+.dialog-content {
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: $bg-light;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: $border-color;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb:hover {
+    background: $text-tertiary;
+  }
+}

+ 215 - 0
src/app/pages/admin/api-integrations/api-dialog/api-dialog.ts

@@ -0,0 +1,215 @@
+import { Component, Inject, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
+import { MatIconModule } from '@angular/material/icon';
+import { MatSlideToggleModule } from '@angular/material/slide-toggle';
+import { MatTabsModule } from '@angular/material/tabs';
+import { MatExpansionModule } from '@angular/material/expansion';
+
+interface ApiIntegration {
+  id: string;
+  name: string;
+  type: string;
+  status: 'active' | 'inactive' | 'error';
+  endpoint: string;
+  authentication: string;
+  lastTested?: string;
+  description: string;
+  configuration: Record<string, any>;
+  createdBy: string;
+  createdAt: string;
+  updatedAt: string;
+}
+
+interface DialogData {
+  item: ApiIntegration;
+  integrationTypes: string[];
+}
+
+@Component({
+  selector: 'app-api-dialog',
+  standalone: true,
+  imports: [
+    CommonModule,
+    FormsModule,
+    MatButtonModule,
+    MatDialogModule,
+    MatFormFieldModule,
+    MatInputModule,
+    MatSelectModule,
+    MatIconModule,
+    MatSlideToggleModule,
+    MatTabsModule,
+    MatExpansionModule
+  ],
+  templateUrl: './api-dialog.html',
+  styleUrl: './api-dialog.scss'
+})
+export class ApiDialogComponent implements OnInit {
+  integration: ApiIntegration;
+  isEditMode: boolean;
+  integrationTypes: string[] = [];
+  
+  // 认证方式选项
+  authenticationMethods = ['API Key', 'OAuth 2.0', 'Basic Auth', 'Bearer Token', 'Webhook', '其他'];
+  
+  // 配置键值对
+  configKeys: string[] = [];
+  configValues: string[] = [];
+  newConfigKey = '';
+  newConfigValue = '';
+  
+  // 错误提示
+  errors: Record<string, string> = {};
+
+  constructor(
+    public dialogRef: MatDialogRef<ApiDialogComponent>,
+    @Inject(MAT_DIALOG_DATA) public data: DialogData
+  ) {
+    this.integration = { ...data.item };
+    this.isEditMode = !!this.integration.id;
+    this.integrationTypes = data.integrationTypes || [];
+  }
+
+  ngOnInit(): void {
+    // 初始化配置键值对
+    this.initializeConfigFields();
+  }
+
+  // 初始化配置字段
+  initializeConfigFields(): void {
+    this.configKeys = Object.keys(this.integration.configuration);
+    this.configValues = Object.values(this.integration.configuration).map(value => 
+      typeof value === 'object' ? JSON.stringify(value) : value.toString()
+    );
+  }
+
+  onCancel(): void {
+    this.dialogRef.close();
+  }
+
+  onSave(): void {
+    if (this.isFormValid()) {
+      // 保存配置到integration对象
+      this.saveConfigFields();
+      
+      // 更新时间
+      this.integration.updatedAt = new Date().toLocaleString('zh-CN');
+      
+      // 关闭对话框并返回数据
+      this.dialogRef.close(this.integration);
+    }
+  }
+
+  isFormValid(): boolean {
+    // 重置错误提示
+    this.errors = {};
+    
+    // 验证必填字段
+    if (!this.integration.name || !this.integration.name.trim()) {
+      this.errors['name'] = '请输入集成名称';
+    }
+    
+    if (!this.integration.type) {
+      this.errors['type'] = '请选择集成类型';
+    }
+    
+    if (!this.integration.endpoint || !this.integration.endpoint.trim()) {
+      this.errors['endpoint'] = '请输入API端点';
+    } else if (!this.isValidUrl(this.integration.endpoint)) {
+      this.errors['endpoint'] = '请输入有效的URL';
+    }
+    
+    if (!this.integration.authentication) {
+      this.errors['authentication'] = '请选择认证方式';
+    }
+    
+    if (!this.integration.description || !this.integration.description.trim()) {
+      this.errors['description'] = '请输入描述';
+    }
+    
+    // 验证配置字段
+    for (let i = 0; i < this.configKeys.length; i++) {
+      if (!this.configKeys[i] || !this.configKeys[i].trim()) {
+        this.errors[`config_key_${i}`] = '配置键不能为空';
+      }
+      if (!this.configValues[i] || !this.configValues[i].trim()) {
+        this.errors[`config_value_${i}`] = '配置值不能为空';
+      }
+    }
+    
+    // 返回是否有错误
+    return Object.keys(this.errors).length === 0;
+  }
+
+  // 验证URL有效性
+  isValidUrl(url: string): boolean {
+    try {
+      new URL(url);
+      return true;
+    } catch (e) {
+      return false;
+    }
+  }
+
+  // 保存配置字段到integration对象
+  saveConfigFields(): void {
+    const config: Record<string, any> = {};
+    
+    for (let i = 0; i < this.configKeys.length; i++) {
+      const key = this.configKeys[i].trim();
+      let value = this.configValues[i].trim();
+      
+      // 尝试解析JSON格式的值
+      try {
+        value = JSON.parse(value);
+      } catch (e) {
+        // 如果不是JSON格式,则保持原样
+      }
+      
+      config[key] = value;
+    }
+    
+    this.integration.configuration = config;
+  }
+
+  // 添加新的配置字段
+  addConfigField(): void {
+    if (this.newConfigKey.trim() && this.newConfigValue.trim()) {
+      this.configKeys.push(this.newConfigKey.trim());
+      this.configValues.push(this.newConfigValue.trim());
+      this.newConfigKey = '';
+      this.newConfigValue = '';
+      
+      // 清除配置字段的错误
+      Object.keys(this.errors).forEach(key => {
+        if (key.startsWith('config_')) {
+          delete this.errors[key];
+        }
+      });
+    }
+  }
+
+  // 删除配置字段
+  removeConfigField(index: number): void {
+    this.configKeys.splice(index, 1);
+    this.configValues.splice(index, 1);
+    
+    // 清除配置字段的错误
+    Object.keys(this.errors).forEach(key => {
+      if (key.startsWith('config_')) {
+        delete this.errors[key];
+      }
+    });
+  }
+
+  // 获取对话框标题
+  get dialogTitle(): string {
+    return this.isEditMode ? '编辑API集成' : '添加新API集成';
+  }
+}

+ 589 - 0
src/app/pages/admin/api-integrations/api-integrations.html

@@ -0,0 +1,589 @@
+<div class="api-integrations">
+  <!-- 页面标题 -->
+  <div class="page-header">
+    <h2 class="page-title">API集成管理</h2>
+    <p class="page-description">配置与渲染农场等第三方服务的接口集成</p>
+  </div>
+
+  <!-- 标签页导航 -->
+  <div class="tab-navigation">
+    <button class="tab-btn" [class.active]="activeTab === 'integrations'"
+            (click)="switchTab('integrations')">
+      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+        <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>
+      </svg>
+      API集成列表
+    </button>
+    <button class="tab-btn" [class.active]="activeTab === 'logs'"
+            (click)="switchTab('logs')">
+      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+        <path d="M14 2H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 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>
+      API调用日志
+    </button>
+  </div>
+
+  <!-- API集成列表标签内容 -->
+  <div *ngIf="activeTab === 'integrations'" class="tab-content">
+    <!-- 搜索和筛选区域 -->
+    <div class="search-filter-section">
+      <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>
+          <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+        </svg>
+        <input matInput type="text" placeholder="搜索集成名称、描述或端点..."
+               [(ngModel)]="integrationSearchTerm" (keyup.enter)="onIntegrationSearch()"
+               class="search-input">
+        <button mat-button *ngIf="integrationSearchTerm" class="clear-search-btn"
+                (click)="integrationSearchTerm = ''; onIntegrationSearch()">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+            <line x1="18" y1="6" x2="6" y2="18"></line>
+            <line x1="6" y1="6" x2="18" y2="18"></line>
+          </svg>
+        </button>
+      </div>
+      
+      <div class="filter-controls">
+        <div class="filter-wrapper">
+          <mat-select [(ngModel)]="integrationTypeFilter" (selectionChange)="onIntegrationFilterChange()"
+                     placeholder="集成类型" class="filter-select">
+            <mat-option value="">全部类型</mat-option>
+            <mat-option *ngFor="let type of integrationTypes" [value]="type">
+              {{ type }}
+            </mat-option>
+          </mat-select>
+        </div>
+        
+        <div class="filter-wrapper">
+          <mat-select [(ngModel)]="integrationStatusFilter" (selectionChange)="onIntegrationFilterChange()"
+                     placeholder="状态" class="filter-select">
+            <mat-option value="">全部状态</mat-option>
+            <mat-option *ngFor="let status of integrationStatuses" [value]="status.value">
+              {{ status.label }}
+            </mat-option>
+          </mat-select>
+        </div>
+        
+        <button mat-button class="filter-btn" (click)="onIntegrationSearch()">
+          <svg 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>
+            <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+          </svg>
+          筛选
+        </button>
+        
+        <button mat-button class="clear-filter-btn" (click)="clearIntegrationFilters()">
+          <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="18" y1="6" x2="6" y2="18"></line>
+            <line x1="6" y1="6" x2="18" y2="18"></line>
+          </svg>
+          清除
+        </button>
+        
+        <button mat-raised-button color="primary" class="create-btn"
+                (click)="openApiDialog()">
+          <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>
+      </div>
+    </div>
+
+    <!-- 集成统计卡片 -->
+    <div class="stats-cards">
+      <div class="stat-card">
+        <div class="stat-value">{{ apiIntegrations().length }}</div>
+        <div class="stat-label">总集成数</div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-value">{{ activeIntegrationsCount }}</div>
+        <div class="stat-label">已激活</div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-value">{{ inactiveIntegrationsCount }}</div>
+        <div class="stat-label">已禁用</div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-value">{{ errorIntegrationsCount }}</div>
+        <div class="stat-label">有错误</div>
+      </div>
+    </div>
+
+    <!-- API集成列表 -->
+    <div class="integrations-list">
+      <div *ngFor="let integration of paginatedIntegrations" class="integration-item">
+        <mat-accordion class="integration-accordion">
+          <!-- 集成头部 -->
+          <mat-expansion-panel class="integration-panel">
+            <mat-expansion-panel-header class="integration-header">
+              <div class="header-left">
+                <div class="status-badge" [style.backgroundColor]="statusColors[integration.status]">
+                  <mat-icon class="status-icon">{{ statusIcons[integration.status] }}</mat-icon>
+                  {{ getStatusText(integration.status) }}
+                </div>
+                <div class="integration-info">
+                  <h3 class="integration-name">{{ integration.name }}</h3>
+                  <div class="integration-meta">
+                    <span class="meta-item type">{{ integration.type }}</span>
+                    <span class="meta-item auth">{{ integration.authentication }}</span>
+                    <span class="meta-item last-tested" *ngIf="integration.lastTested">
+                      最后测试:{{ formatDate(integration.lastTested) }}
+                    </span>
+                  </div>
+                </div>
+              </div>
+              
+              <div class="header-right">
+                <div class="action-buttons">
+                  <button mat-icon-button class="action-btn" color="primary"
+                          title="测试集成"
+                          (click)="testIntegration(integration.id); $event.stopPropagation()">
+                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                      <path d="M14 2H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 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>
+                  </button>
+                  <button mat-icon-button class="action-btn" color="primary"
+                          title="编辑集成"
+                          (click)="openApiDialog(integration); $event.stopPropagation()">
+                    <svg width="20" height="20" 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)="deleteIntegration(integration.id); $event.stopPropagation()">
+                    <svg width="20" height="20" 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>
+            </mat-expansion-panel-header>
+            
+            <!-- 集成详情 -->
+            <div class="integration-content">
+              <div class="detail-section">
+                <h4 class="section-title">基本信息</h4>
+                <div class="detail-grid">
+                  <div class="detail-item">
+                    <label>API端点:</label>
+                    <span class="detail-value">{{ integration.endpoint }}</span>
+                  </div>
+                  <div class="detail-item">
+                    <label>认证方式:</label>
+                    <span class="detail-value">{{ integration.authentication }}</span>
+                  </div>
+                  <div class="detail-item">
+                    <label>创建人:</label>
+                    <span class="detail-value">{{ integration.createdBy }}</span>
+                  </div>
+                  <div class="detail-item">
+                    <label>创建时间:</label>
+                    <span class="detail-value">{{ formatDate(integration.createdAt) }}</span>
+                  </div>
+                  <div class="detail-item">
+                    <label>更新时间:</label>
+                    <span class="detail-value">{{ formatDate(integration.updatedAt) }}</span>
+                  </div>
+                </div>
+              </div>
+              
+              <div class="detail-section">
+                <h4 class="section-title">描述</h4>
+                <p class="description-text">{{ integration.description }}</p>
+              </div>
+              
+              <div class="detail-section">
+                <h4 class="section-title">配置详情</h4>
+                <div class="configuration-details">
+                  <div class="config-item" *ngFor="let item of objectEntries(integration.configuration)">
+                    <label>{{ item[0] }}:</label>
+                    <span class="config-value">{{ item[1] }}</span>
+                  </div>
+                </div>
+              </div>
+              
+              <div class="detail-section actions">
+                <div class="status-toggle-wrapper">
+                  <mat-slide-toggle
+                    [checked]="integration.status === 'active'"
+                    (change)="toggleIntegrationStatus(integration.id, $event.source.checked ? 'active' : 'inactive');"
+                    class="status-toggle">
+                    <span class="toggle-label">{{ integration.status === 'active' ? '禁用集成' : '启用集成' }}</span>
+                  </mat-slide-toggle>
+                </div>
+                
+                <button mat-button color="primary" class="test-btn"
+                        (click)="testIntegration(integration.id)">
+                  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                    <path d="M14 2H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 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>
+                  测试连接
+                </button>
+              </div>
+            </div>
+          </mat-expansion-panel>
+        </mat-accordion>
+      </div>
+    </div>
+
+    <!-- 无数据状态 -->
+    <div *ngIf="paginatedIntegrations.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">
+        <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>
+      </svg>
+      <p>没有找到符合条件的API集成</p>
+      <button mat-button color="primary" (click)="clearIntegrationFilters()">
+        清除筛选条件
+      </button>
+    </div>
+
+    <!-- 分页控件 -->
+    <div class="pagination-controls">
+      <div class="pagination-info">
+        显示 {{ mathMin(integrationCurrentPage * integrationPageSize + 1, filteredIntegrations().length) }} -
+        {{ mathMin((integrationCurrentPage + 1) * integrationPageSize, filteredIntegrations().length) }} 共 
+        {{ filteredIntegrations().length }} 项
+      </div>
+      <div class="pagination-buttons">
+        <button mat-button [disabled]="integrationCurrentPage === 0" (click)="onIntegrationPageChange(0)">
+          首页
+        </button>
+        <button mat-button [disabled]="integrationCurrentPage === 0" (click)="onIntegrationPageChange(integrationCurrentPage - 1)">
+          上一页
+        </button>
+        
+        <!-- 页码按钮 -->
+        <div class="page-numbers">
+          <button mat-button *ngFor="let page of getPageNumbers()" [class.active]="page === integrationCurrentPage"
+                  (click)="onIntegrationPageChange(page)">
+            {{ page + 1 }}
+          </button>
+        </div>
+        
+        <button mat-button [disabled]="integrationCurrentPage === totalIntegrationPages - 1" (click)="onIntegrationPageChange(integrationCurrentPage + 1)">
+          下一页
+        </button>
+        <button mat-button [disabled]="integrationCurrentPage === totalIntegrationPages - 1" (click)="onIntegrationPageChange(totalIntegrationPages - 1)">
+          末页
+        </button>
+      </div>
+    </div>
+  </div>
+
+  <!-- API调用日志标签内容 -->
+  <div *ngIf="activeTab === 'logs'" class="tab-content">
+    <!-- 搜索和筛选区域 -->
+    <div class="search-filter-section">
+      <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>
+          <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+        </svg>
+        <input matInput type="text" placeholder="搜索集成名称、请求地址或错误信息..."
+               [(ngModel)]="logSearchTerm" (keyup.enter)="onLogSearch()"
+               class="search-input">
+        <button mat-button *ngIf="logSearchTerm" class="clear-search-btn"
+                (click)="logSearchTerm = ''; onLogSearch()">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+            <line x1="18" y1="6" x2="6" y2="18"></line>
+            <line x1="6" y1="6" x2="18" y2="18"></line>
+          </svg>
+        </button>
+      </div>
+      
+      <div class="filter-controls">
+        <div class="filter-wrapper">
+          <mat-select [(ngModel)]="logIntegrationFilter" (selectionChange)="onLogFilterChange()"
+                     placeholder="API集成" class="filter-select">
+            <mat-option value="">全部集成</mat-option>
+            <mat-option *ngFor="let integration of integrationIdOptions" [value]="integration.id">
+              {{ integration.name }}
+            </mat-option>
+          </mat-select>
+        </div>
+        
+        <div class="filter-wrapper">
+          <mat-select [(ngModel)]="logStatusFilter" (selectionChange)="onLogFilterChange()"
+                     placeholder="状态" class="filter-select">
+            <mat-option value="">全部状态</mat-option>
+            <mat-option *ngFor="let status of logStatuses" [value]="status.value">
+              {{ status.label }}
+            </mat-option>
+          </mat-select>
+        </div>
+        
+        <div class="date-filter-wrapper">
+          <mat-form-field appearance="outline" class="date-field">
+            <mat-label>开始日期</mat-label>
+            <input matInput [matDatepicker]="startDatePicker" [(ngModel)]="logStartDate" (dateChange)="onLogFilterChange()">
+            <mat-datepicker-toggle matSuffix [for]="startDatePicker"></mat-datepicker-toggle>
+            <mat-datepicker #startDatePicker></mat-datepicker>
+          </mat-form-field>
+        </div>
+        
+        <div class="date-filter-wrapper">
+          <mat-form-field appearance="outline" class="date-field">
+            <mat-label>结束日期</mat-label>
+            <input matInput [matDatepicker]="endDatePicker" [(ngModel)]="logEndDate" (dateChange)="onLogFilterChange()">
+            <mat-datepicker-toggle matSuffix [for]="endDatePicker"></mat-datepicker-toggle>
+            <mat-datepicker #endDatePicker></mat-datepicker>
+          </mat-form-field>
+        </div>
+        
+        <button mat-button class="filter-btn" (click)="onLogSearch()">
+          <svg 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>
+            <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+          </svg>
+          筛选
+        </button>
+        
+        <button mat-button class="clear-filter-btn" (click)="clearLogFilters()">
+          <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="18" y1="6" x2="6" y2="18"></line>
+            <line x1="6" y1="6" x2="18" y2="18"></line>
+          </svg>
+          清除
+        </button>
+        
+        <button mat-raised-button color="primary" class="export-btn"
+                (click)="exportLogs()" [disabled]="filteredLogs().length === 0">
+          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+            <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>
+      </div>
+    </div>
+
+    <!-- 日志统计卡片 -->
+    <div class="stats-cards">
+      <div class="stat-card">
+        <div class="stat-value">{{ totalApiLogsCount }}</div>
+        <div class="stat-label">总调用次数</div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-value">{{ successLogsCount }}</div>
+        <div class="stat-label">成功调用</div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-value">{{ errorLogsCount }}</div>
+        <div class="stat-label">失败调用</div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-value">{{ averageResponseTime }}ms</div>
+        <div class="stat-label">平均响应时间</div>
+      </div>
+    </div>
+
+    <!-- 日志表格 -->
+    <div class="logs-table-container">
+      <table mat-table [dataSource]="paginatedLogs" class="logs-table">
+        <!-- 时间戳列 -->
+        <ng-container matColumnDef="timestamp">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onLogSort('timestamp')">
+            <div class="header-content">
+              <span>时间戳</span>
+              <div class="sort-icon">
+                <svg *ngIf="logSortColumn === 'timestamp'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let log" class="table-cell">
+            {{ formatDate(log.timestamp) }}
+          </td>
+        </ng-container>
+
+        <!-- 集成列 -->
+        <ng-container matColumnDef="integrationName">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onLogSort('integrationName')">
+            <div class="header-content">
+              <span>API集成</span>
+              <div class="sort-icon">
+                <svg *ngIf="logSortColumn === 'integrationName'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let log" class="table-cell">
+            <div class="integration-name">{{ log.integrationName }}</div>
+          </td>
+        </ng-container>
+
+        <!-- 请求方法列 -->
+        <ng-container matColumnDef="requestMethod">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onLogSort('requestMethod')">
+            <div class="header-content">
+              <span>请求方法</span>
+              <div class="sort-icon">
+                <svg *ngIf="logSortColumn === 'requestMethod'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let log" class="table-cell">
+            <div class="method-badge">{{ log.requestMethod }}</div>
+          </td>
+        </ng-container>
+
+        <!-- 请求URL列 -->
+        <ng-container matColumnDef="requestUrl">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onLogSort('requestUrl')">
+            <div class="header-content">
+              <span>请求地址</span>
+              <div class="sort-icon">
+                <svg *ngIf="logSortColumn === 'requestUrl'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let log" class="table-cell">
+            <div class="url-content" [title]="log.requestUrl">{{ log.requestUrl }}</div>
+          </td>
+        </ng-container>
+
+        <!-- 状态码列 -->
+        <ng-container matColumnDef="statusCode">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onLogSort('statusCode')">
+            <div class="header-content">
+              <span>状态码</span>
+              <div class="sort-icon">
+                <svg *ngIf="logSortColumn === 'statusCode'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let log" class="table-cell">
+            <div class="status-code-badge" [class.success]="log.statusCode >= 200 && log.statusCode < 300" [class.error]="log.statusCode >= 400">
+              {{ log.statusCode }}
+            </div>
+          </td>
+        </ng-container>
+
+        <!-- 响应时间列 -->
+        <ng-container matColumnDef="duration">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onLogSort('duration')">
+            <div class="header-content">
+              <span>响应时间</span>
+              <div class="sort-icon">
+                <svg *ngIf="logSortColumn === 'duration'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let log" class="table-cell">
+            <div class="duration-value">{{ log.duration }}ms</div>
+          </td>
+        </ng-container>
+
+        <!-- 状态列 -->
+        <ng-container matColumnDef="status">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onLogSort('status')">
+            <div class="header-content">
+              <span>状态</span>
+              <div class="sort-icon">
+                <svg *ngIf="logSortColumn === 'status'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let log" class="table-cell">
+            <div class="log-status-badge" [class.success]="log.status === 'success'" [class.error]="log.status === 'error'">
+              {{ log.status === 'success' ? '成功' : '失败' }}
+            </div>
+            <div class="error-message" *ngIf="log.errorMessage">
+              {{ log.errorMessage }}
+            </div>
+          </td>
+        </ng-container>
+
+        <tr mat-header-row *matHeaderRowDef="['timestamp', 'integrationName', 'requestMethod', 'requestUrl', 'statusCode', 'duration', 'status']"></tr>
+        <tr mat-row *matRowDef="let row; columns: ['timestamp', 'integrationName', 'requestMethod', 'requestUrl', 'statusCode', 'duration', 'status']"></tr>
+      </table>
+
+      <!-- 无数据状态 -->
+      <div *ngIf="paginatedLogs.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">
+          <path d="M14 2H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 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>
+        <p>没有找到符合条件的API日志记录</p>
+        <button mat-button color="primary" (click)="clearLogFilters()">
+          清除筛选条件
+        </button>
+      </div>
+    </div>
+
+    <!-- 分页控件 -->
+    <div class="pagination-controls">
+      <div class="pagination-info">
+        显示 {{ mathMin(logCurrentPage * logPageSize + 1, filteredLogs().length) }} -
+        {{ mathMin((logCurrentPage + 1) * logPageSize, filteredLogs().length) }} 共 
+        {{ filteredLogs().length }} 项
+      </div>
+      <div class="pagination-buttons">
+        <button mat-button [disabled]="logCurrentPage === 0" (click)="onLogPageChange(0)">
+          首页
+        </button>
+        <button mat-button [disabled]="logCurrentPage === 0" (click)="onLogPageChange(logCurrentPage - 1)">
+          上一页
+        </button>
+        
+        <!-- 页码按钮 -->
+        <div class="page-numbers">
+          <button mat-button *ngFor="let page of getPageNumbers()" [class.active]="page === logCurrentPage"
+                  (click)="onLogPageChange(page)">
+            {{ page + 1 }}
+          </button>
+        </div>
+        
+        <button mat-button [disabled]="logCurrentPage === totalLogPages - 1" (click)="onLogPageChange(logCurrentPage + 1)">
+          下一页
+        </button>
+        <button mat-button [disabled]="logCurrentPage === totalLogPages - 1" (click)="onLogPageChange(totalLogPages - 1)">
+          末页
+        </button>
+      </div>
+    </div>
+  </div>
+</div>

+ 836 - 0
src/app/pages/admin/api-integrations/api-integrations.scss

@@ -0,0 +1,836 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$secondary-color: #6B7280;
+$success-color: #00B42A;
+$warning-color: #FFAA00;
+$error-color: #F53F3F;
+$text-primary: #333333;
+$text-secondary: #666666;
+$text-tertiary: #999999;
+$border-color: #E5E7EB;
+$bg-light: #F9FAFB;
+$bg-white: #FFFFFF;
+$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+
+// 主容器
+.api-integrations {
+  padding: 24px;
+  min-height: 100vh;
+  background-color: $bg-light;
+}
+
+// 页面标题区域
+.page-header {
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 24px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid $border-color;
+
+  .page-title {
+    font-size: 24px;
+    font-weight: 600;
+    color: $text-primary;
+    margin: 0 0 4px 0;
+  }
+
+  .page-description {
+    font-size: 14px;
+    color: $text-tertiary;
+    margin: 0;
+  }
+}
+
+// 标签页导航
+.tab-navigation {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 24px;
+  background-color: $bg-white;
+  border-radius: 12px;
+  padding: 4px;
+  box-shadow: $shadow-sm;
+
+  .tab-btn {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 12px 24px;
+    border: none;
+    background-color: transparent;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    color: $text-secondary;
+    cursor: pointer;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: $bg-light;
+      color: $text-primary;
+    }
+
+    &.active {
+      background-color: $primary-color;
+      color: $bg-white;
+    }
+  }
+}
+
+// 标签页内容
+.tab-content {
+  animation: fadeIn 0.3s ease-in-out;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+// 搜索和筛选区域
+.search-filter-section {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 24px;
+  flex-wrap: wrap;
+
+  .search-input-wrapper {
+    flex: 1;
+    min-width: 300px;
+    position: relative;
+    display: flex;
+    align-items: center;
+    background-color: $bg-white;
+    border-radius: 8px;
+    box-shadow: $shadow-sm;
+    overflow: hidden;
+
+    .search-icon {
+      position: absolute;
+      left: 16px;
+      color: $text-tertiary;
+      z-index: 1;
+    }
+
+    .search-input {
+      flex: 1;
+      padding: 12px 16px 12px 48px;
+      border: none;
+      background-color: transparent;
+      font-size: 14px;
+      color: $text-primary;
+      outline: none;
+
+      &::placeholder {
+        color: $text-tertiary;
+      }
+    }
+
+    .clear-search-btn {
+      position: absolute;
+      right: 16px;
+      color: $text-tertiary;
+      padding: 8px;
+    }
+  }
+
+  .filter-controls {
+    display: flex;
+    gap: 12px;
+    align-items: center;
+    flex-wrap: wrap;
+
+    .filter-wrapper {
+      .filter-select {
+        min-width: 160px;
+        border: 1px solid $border-color;
+        border-radius: 6px;
+        background-color: $bg-white;
+        box-shadow: $shadow-sm;
+
+        .mat-select-trigger {
+          height: 40px;
+          font-size: 14px;
+          color: $text-primary;
+        }
+      }
+    }
+
+    .date-filter-wrapper {
+      .date-field {
+        width: 140px;
+        .mat-form-field-wrapper {
+          .mat-form-field-flex {
+            border-radius: 6px;
+          }
+          .mat-form-field-outline {
+            border-radius: 6px;
+          }
+          .mat-form-field-infix {
+            padding: 8px 0;
+            .mat-input-element {
+              font-size: 14px;
+              color: $text-primary;
+            }
+          }
+          .mat-form-field-label {
+            font-size: 14px;
+            color: $text-secondary;
+          }
+        }
+      }
+    }
+
+    .filter-btn,
+    .clear-filter-btn {
+      display: flex;
+      align-items: center;
+      gap: 6px;
+      color: $text-secondary;
+      border: 1px solid $border-color;
+      border-radius: 6px;
+      padding: 8px 16px;
+      font-size: 14px;
+      font-weight: 500;
+      background-color: $bg-white;
+      transition: all 0.2s ease;
+
+      &:hover {
+        background-color: $bg-light;
+        border-color: $text-tertiary;
+        color: $text-primary;
+      }
+    }
+
+    .create-btn,
+    .export-btn {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      background-color: $primary-color;
+      color: $bg-white;
+      border-radius: 6px;
+      padding: 8px 16px;
+      font-size: 14px;
+      font-weight: 500;
+      transition: all 0.2s ease;
+      box-shadow: $shadow-sm;
+
+      &:hover:not(:disabled) {
+        background-color: #0E4BD8;
+        box-shadow: $shadow-md;
+        transform: translateY(-1px);
+      }
+
+      &:disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+        transform: none;
+        box-shadow: none;
+      }
+    }
+  }
+}
+
+// 统计卡片区域
+.stats-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+  gap: 16px;
+  margin-bottom: 24px;
+
+  .stat-card {
+    background-color: $bg-white;
+    border-radius: 12px;
+    padding: 20px;
+    box-shadow: $shadow-sm;
+    text-align: center;
+    transition: all 0.2s ease;
+
+    &:hover {
+      box-shadow: $shadow-md;
+      transform: translateY(-2px);
+    }
+
+    .stat-value {
+      font-size: 28px;
+      font-weight: 700;
+      color: $text-primary;
+      margin-bottom: 4px;
+    }
+
+    .stat-label {
+      font-size: 14px;
+      color: $text-tertiary;
+    }
+  }
+}
+
+// API集成列表
+.integrations-list {
+  margin-bottom: 24px;
+}
+
+.integration-item {
+  margin-bottom: 16px;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+.integration-panel {
+  background-color: $bg-white;
+  border-radius: 12px;
+  box-shadow: $shadow-sm;
+  overflow: hidden;
+  border: 1px solid $border-color;
+
+  .mat-expansion-panel-header {
+    padding: 0;
+    height: auto;
+    min-height: auto;
+  }
+}
+
+.integration-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px;
+  background-color: $bg-white;
+  cursor: pointer;
+  transition: background-color 0.2s ease;
+
+  &:hover {
+    background-color: $bg-light;
+  }
+
+  .header-left {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    flex: 1;
+  }
+
+  .status-badge {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    padding: 4px 12px;
+    border-radius: 16px;
+    font-size: 12px;
+    font-weight: 500;
+    color: $bg-white;
+    text-align: center;
+    white-space: nowrap;
+
+    .status-icon {
+      font-size: 14px;
+    }
+  }
+
+  .integration-info {
+    .integration-name {
+      font-size: 16px;
+      font-weight: 600;
+      color: $text-primary;
+      margin: 0 0 8px 0;
+    }
+
+    .integration-meta {
+      display: flex;
+      gap: 16px;
+      flex-wrap: wrap;
+
+      .meta-item {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        font-size: 12px;
+        color: $text-tertiary;
+
+        &.type,
+        &.auth {
+          padding: 2px 8px;
+          background-color: $bg-light;
+          border-radius: 12px;
+        }
+      }
+    }
+  }
+
+  .header-right {
+    .action-buttons {
+      display: flex;
+      gap: 8px;
+
+      .action-btn {
+        width: 36px;
+        height: 36px;
+        border-radius: 6px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        transition: all 0.2s ease;
+        background-color: transparent;
+        border: 1px solid $border-color;
+        color: $text-secondary;
+
+        &:hover {
+          border-color: currentColor;
+          transform: translateY(-1px);
+        }
+
+        &.mat-primary {
+          color: $primary-color;
+        }
+
+        &.mat-warn {
+          color: $error-color;
+        }
+      }
+    }
+  }
+}
+
+// 集成详情内容
+.integration-content {
+  padding: 20px;
+  border-top: 1px solid $border-color;
+
+  .detail-section {
+    margin-bottom: 24px;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+
+    &.actions {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      flex-wrap: wrap;
+      gap: 16px;
+      padding-top: 16px;
+      border-top: 1px solid $border-color;
+    }
+
+    .section-title {
+      font-size: 14px;
+      font-weight: 600;
+      color: $text-secondary;
+      margin-bottom: 12px;
+    }
+
+    .detail-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+      gap: 16px;
+
+      .detail-item {
+        display: flex;
+        flex-direction: column;
+        gap: 4px;
+
+        label {
+          font-size: 12px;
+          color: $text-tertiary;
+          font-weight: 500;
+        }
+
+        .detail-value {
+          font-size: 14px;
+          color: $text-primary;
+          word-break: break-all;
+        }
+      }
+    }
+
+    .description-text {
+      font-size: 14px;
+      color: $text-primary;
+      line-height: 1.5;
+      margin: 0;
+    }
+
+    .configuration-details {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+      gap: 12px;
+
+      .config-item {
+        display: flex;
+        flex-direction: column;
+        gap: 4px;
+        padding: 12px;
+        background-color: $bg-light;
+        border-radius: 6px;
+
+        label {
+          font-size: 12px;
+          color: $text-secondary;
+          font-weight: 500;
+        }
+
+        .config-value {
+          font-size: 13px;
+          color: $text-primary;
+          font-family: 'Courier New', monospace;
+          word-break: break-all;
+        }
+      }
+    }
+
+    .status-toggle-wrapper {
+      .status-toggle {
+        .mat-slide-toggle-content {
+          font-size: 14px;
+        }
+      }
+    }
+
+    .test-btn {
+      display: flex;
+      align-items: center;
+      gap: 6px;
+      padding: 8px 16px;
+      font-size: 14px;
+    }
+  }
+}
+
+// 日志表格容器
+.logs-table-container {
+  background-color: $bg-white;
+  border-radius: 12px;
+  box-shadow: $shadow-sm;
+  overflow: hidden;
+  margin-bottom: 24px;
+
+  .logs-table {
+    width: 100%;
+    border-collapse: collapse;
+
+    .table-header {
+      background-color: $bg-light;
+      font-weight: 600;
+      color: $text-secondary;
+      font-size: 13px;
+      text-align: left;
+      padding: 14px 16px;
+      border-bottom: 1px solid $border-color;
+      cursor: pointer;
+      transition: background-color 0.2s ease;
+
+      &:hover {
+        background-color: #F3F4F6;
+      }
+
+      .header-content {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+      }
+
+      .sort-icon {
+        opacity: 0.5;
+      }
+    }
+
+    .table-cell {
+      padding: 12px 16px;
+      border-bottom: 1px solid $border-color;
+      font-size: 13px;
+      color: $text-primary;
+      vertical-align: middle;
+
+      .integration-name {
+        font-weight: 500;
+      }
+
+      .method-badge {
+        display: inline-block;
+        padding: 4px 10px;
+        background-color: $primary-color;
+        color: $bg-white;
+        border-radius: 12px;
+        font-size: 11px;
+        font-weight: 600;
+        text-transform: uppercase;
+      }
+
+      .url-content {
+        max-width: 250px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        font-family: 'Courier New', monospace;
+      }
+
+      .status-code-badge {
+        display: inline-block;
+        padding: 4px 10px;
+        background-color: $border-color;
+        color: $text-secondary;
+        border-radius: 12px;
+        font-size: 12px;
+        font-weight: 600;
+        font-family: 'Courier New', monospace;
+
+        &.success {
+          background-color: #E6F7EF;
+          color: $success-color;
+        }
+
+        &.error {
+          background-color: #FFF1F0;
+          color: $error-color;
+        }
+      }
+
+      .duration-value {
+        font-family: 'Courier New', monospace;
+        color: $text-secondary;
+      }
+
+      .log-status-badge {
+        display: inline-block;
+        padding: 4px 12px;
+        border-radius: 16px;
+        font-size: 12px;
+        font-weight: 500;
+
+        &.success {
+          background-color: #E6F7EF;
+          color: $success-color;
+        }
+
+        &.error {
+          background-color: #FFF1F0;
+          color: $error-color;
+        }
+      }
+
+      .error-message {
+        font-size: 12px;
+        color: $error-color;
+        margin-top: 4px;
+        font-family: 'Courier New', monospace;
+        word-break: break-all;
+      }
+    }
+
+    tr:last-child .table-cell {
+      border-bottom: none;
+    }
+
+    tr:hover .table-cell {
+      background-color: $bg-light;
+    }
+  }
+
+  // 无数据状态
+  .empty-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 80px 20px;
+    text-align: center;
+
+    svg {
+      margin-bottom: 16px;
+      opacity: 0.5;
+    }
+
+    p {
+      font-size: 16px;
+      color: $text-tertiary;
+      margin-bottom: 24px;
+    }
+
+    button {
+      border-radius: 8px;
+      padding: 8px 24px;
+      font-size: 14px;
+      font-weight: 500;
+    }
+  }
+}
+
+// 分页控件
+.pagination-controls {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px;
+  background-color: $bg-white;
+  border-radius: 12px;
+  box-shadow: $shadow-sm;
+
+  .pagination-info {
+    font-size: 14px;
+    color: $text-tertiary;
+  }
+
+  .pagination-buttons {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+
+    button {
+      padding: 6px 12px;
+      border-radius: 6px;
+      font-size: 13px;
+      font-weight: 500;
+      transition: all 0.2s ease;
+      min-width: auto;
+
+      &:disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+      }
+
+      &.active {
+        background-color: $primary-color;
+        color: $bg-white;
+      }
+    }
+
+    .page-numbers {
+      display: flex;
+      gap: 4px;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .search-filter-section {
+    flex-direction: column;
+    align-items: stretch;
+  }
+
+  .search-input-wrapper {
+    min-width: 100% !important;
+  }
+
+  .logs-table {
+    .table-header,
+    .table-cell {
+      padding: 12px;
+    }
+  }
+}
+
+@media (max-width: 992px) {
+  .api-integrations {
+    padding: 16px;
+  }
+
+  .filter-controls {
+    flex-wrap: wrap;
+  }
+
+  .integration-header {
+    flex-direction: column;
+    align-items: stretch;
+    gap: 16px;
+  }
+
+  .header-right {
+    justify-content: flex-end;
+  }
+
+  .detail-grid,
+  .configuration-details {
+    grid-template-columns: 1fr !important;
+  }
+
+  .logs-table-container {
+    overflow-x: auto;
+  }
+
+  .pagination-controls {
+    flex-direction: column;
+    gap: 16px;
+
+    .pagination-buttons {
+      flex-wrap: wrap;
+      justify-content: center;
+    }
+  }
+
+  .stats-cards {
+    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+  }
+}
+
+@media (max-width: 768px) {
+  .page-title {
+    font-size: 20px !important;
+  }
+
+  .detail-section.actions {
+    flex-direction: column;
+    align-items: stretch !important;
+  }
+
+  .stat-card {
+    padding: 16px;
+
+    .stat-value {
+      font-size: 24px;
+    }
+  }
+}
+
+@media (max-width: 480px) {
+  .api-integrations {
+    padding: 12px;
+  }
+
+  .tab-btn {
+    padding: 10px 16px !important;
+    font-size: 13px !important;
+  }
+
+  .create-btn,
+  .export-btn {
+    padding: 8px 16px !important;
+    font-size: 13px !important;
+    width: 100%;
+    justify-content: center;
+  }
+}
+
+// 滚动条样式
+.integration-content,
+.logs-table-container {
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: $bg-light;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: $border-color;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb:hover {
+    background: $text-tertiary;
+  }
+}

+ 748 - 0
src/app/pages/admin/api-integrations/api-integrations.ts

@@ -0,0 +1,748 @@
+import { Component, OnInit, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { FormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { MatTableModule } from '@angular/material/table';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatDialogModule, MatDialog } from '@angular/material/dialog';
+import { MatSortModule } from '@angular/material/sort';
+import { MatTabsModule } from '@angular/material/tabs';
+import { MatExpansionModule } from '@angular/material/expansion';
+import { MatSlideToggleModule } from '@angular/material/slide-toggle';
+import { MatDatepickerModule } from '@angular/material/datepicker';
+import { MatNativeDateModule } from '@angular/material/core';
+import { ApiDialogComponent } from './api-dialog/api-dialog';
+
+interface ApiIntegration {
+  id: string;
+  name: string;
+  type: string;
+  status: 'active' | 'inactive' | 'error';
+  endpoint: string;
+  authentication: string;
+  lastTested?: string;
+  description: string;
+  configuration: Record<string, any>;
+  createdBy: string;
+  createdAt: string;
+  updatedAt: string;
+}
+
+interface ApiLog {
+  id: string;
+  timestamp: string;
+  integrationId: string;
+  integrationName: string;
+  requestMethod: string;
+  requestUrl: string;
+  statusCode: number;
+  duration: number;
+  status: 'success' | 'error';
+  errorMessage?: string;
+}
+
+@Component({
+  selector: 'app-api-integrations',
+  standalone: true,
+  imports: [
+    CommonModule,
+    RouterModule,
+    FormsModule,
+    MatButtonModule,
+    MatIconModule,
+    MatTableModule,
+    MatInputModule,
+    MatSelectModule,
+    MatPaginatorModule,
+    MatDialogModule,
+    MatSortModule,
+    MatTabsModule,
+    MatExpansionModule,
+    MatSlideToggleModule,
+    MatDatepickerModule,
+    MatNativeDateModule,
+    ApiDialogComponent
+  ],
+  templateUrl: './api-integrations.html',
+  styleUrl: './api-integrations.scss'
+})
+export class ApiIntegrations implements OnInit {
+  // 激活的标签页
+  activeTab = 'integrations';
+
+  // API集成数据
+  apiIntegrations = signal<ApiIntegration[]>([]);
+  filteredIntegrations = signal<ApiIntegration[]>([]);
+  integrationSearchTerm = '';
+  integrationTypeFilter = '';
+  integrationStatusFilter = '';
+  integrationSortColumn = 'createdAt';
+  integrationSortDirection = 'desc';
+  integrationPageSize = 10;
+  integrationCurrentPage = 0;
+
+  // API日志数据
+  apiLogs = signal<ApiLog[]>([]);
+  filteredLogs = signal<ApiLog[]>([]);
+  logSearchTerm = '';
+  logIntegrationFilter = '';
+  logStatusFilter = '';
+  logStartDate: Date | null = null;
+  logEndDate: Date | null = null;
+  logSortColumn = 'timestamp';
+  logSortDirection = 'desc';
+  logPageSize = 20;
+  logCurrentPage = 0;
+
+  // 可用的筛选选项
+  integrationTypes = ['渲染农场', '设计素材库', '客户管理', '财务系统', '项目管理', '人力资源', '其他系统'];
+  integrationStatuses = [
+    { value: 'active', label: '已激活' },
+    { value: 'inactive', label: '已禁用' },
+    { value: 'error', label: '错误' }
+  ];
+  logStatuses = [
+    { value: 'success', label: '成功' },
+    { value: 'error', label: '失败' }
+  ];
+
+  // 状态颜色映射
+  statusColors: Record<string, string> = {
+    'active': '#00B42A',
+    'inactive': '#F53F3F',
+    'error': '#FFAA00'
+  };
+
+  // 状态图标映射
+  statusIcons: Record<string, string> = {
+    'active': 'check_circle',
+    'inactive': 'cancel',
+    'error': 'error'
+  };
+
+  constructor(private dialog: MatDialog) {}
+
+  ngOnInit(): void {
+    this.loadApiIntegrations();
+    this.loadApiLogs();
+  }
+
+  // 加载API集成数据
+  loadApiIntegrations(): void {
+    // 模拟API集成数据
+    this.apiIntegrations.set([
+      {
+        id: '1',
+        name: '渲染农场API',
+        type: '渲染农场',
+        status: 'active',
+        endpoint: 'https://render-farm.example.com/api/v1',
+        authentication: 'API Key',
+        lastTested: '2025-09-15 10:30:00',
+        description: '与外部渲染农场服务的集成,用于提交和管理渲染任务',
+        configuration: {
+          apiKey: '********',
+          timeout: 300,
+          retryCount: 3,
+          priority: 'medium'
+        },
+        createdBy: '超级管理员',
+        createdAt: '2025-09-01 14:20:00',
+        updatedAt: '2025-09-10 09:15:00'
+      },
+      {
+        id: '2',
+        name: '设计素材库API',
+        type: '设计素材库',
+        status: 'active',
+        endpoint: 'https://design-assets.example.com/api/v2',
+        authentication: 'OAuth 2.0',
+        lastTested: '2025-09-14 16:45:00',
+        description: '与设计素材库服务的集成,用于搜索和获取设计素材',
+        configuration: {
+          clientId: 'design_assets_client',
+          clientSecret: '********',
+          scope: 'read write',
+          tokenUrl: 'https://design-assets.example.com/oauth/token'
+        },
+        createdBy: '超级管理员',
+        createdAt: '2025-08-25 11:30:00',
+        updatedAt: '2025-09-05 15:20:00'
+      },
+      {
+        id: '3',
+        name: '客户管理系统API',
+        type: '客户管理',
+        status: 'error',
+        endpoint: 'https://crm.example.com/api',
+        authentication: 'API Key',
+        lastTested: '2025-09-15 08:10:00',
+        description: '与客户管理系统的集成,用于同步客户信息和订单数据',
+        configuration: {
+          apiKey: '********',
+          syncInterval: 'daily',
+          syncFields: ['name', 'email', 'phone', 'address']
+        },
+        createdBy: '超级管理员',
+        createdAt: '2025-08-20 09:45:00',
+        updatedAt: '2025-09-12 14:30:00'
+      },
+      {
+        id: '4',
+        name: '财务系统API',
+        type: '财务系统',
+        status: 'inactive',
+        endpoint: 'https://finance.example.com/api/v3',
+        authentication: 'OAuth 2.0',
+        lastTested: '2025-09-10 11:20:00',
+        description: '与财务系统的集成,用于同步项目财务数据和生成报表',
+        configuration: {
+          clientId: 'finance_api_client',
+          clientSecret: '********',
+          scope: 'read',
+          tokenUrl: 'https://finance.example.com/oauth/token'
+        },
+        createdBy: '超级管理员',
+        createdAt: '2025-08-15 16:00:00',
+        updatedAt: '2025-09-01 10:15:00'
+      },
+      {
+        id: '5',
+        name: '项目管理工具API',
+        type: '项目管理',
+        status: 'active',
+        endpoint: 'https://project-tool.example.com/api',
+        authentication: 'Webhook',
+        lastTested: '2025-09-15 13:40:00',
+        description: '与项目管理工具的集成,用于创建和更新项目任务',
+        configuration: {
+          webhookUrl: 'https://example.com/webhooks/project-updates',
+          secretToken: '********',
+          eventTypes: ['task_created', 'task_updated', 'task_completed']
+        },
+        createdBy: '超级管理员',
+        createdAt: '2025-08-10 14:50:00',
+        updatedAt: '2025-09-08 16:25:00'
+      }
+    ]);
+
+    this.filteredIntegrations = this.apiIntegrations;
+    this.applyIntegrationFilters();
+  }
+
+  // 加载API日志数据
+  loadApiLogs(): void {
+    // 模拟API日志数据
+    const mockLogs: ApiLog[] = [
+      {
+        id: '1',
+        timestamp: '2025-09-15 14:30:25',
+        integrationId: '1',
+        integrationName: '渲染农场API',
+        requestMethod: 'POST',
+        requestUrl: '/render-jobs',
+        statusCode: 200,
+        duration: 1250,
+        status: 'success'
+      },
+      {
+        id: '2',
+        timestamp: '2025-09-15 14:25:10',
+        integrationId: '2',
+        integrationName: '设计素材库API',
+        requestMethod: 'GET',
+        requestUrl: '/assets?category=3d-models',
+        statusCode: 200,
+        duration: 850,
+        status: 'success'
+      },
+      {
+        id: '3',
+        timestamp: '2025-09-15 14:20:45',
+        integrationId: '3',
+        integrationName: '客户管理系统API',
+        requestMethod: 'POST',
+        requestUrl: '/customers/sync',
+        statusCode: 401,
+        duration: 450,
+        status: 'error',
+        errorMessage: '认证失败:无效的API密钥'
+      },
+      {
+        id: '4',
+        timestamp: '2025-09-15 14:15:30',
+        integrationId: '1',
+        integrationName: '渲染农场API',
+        requestMethod: 'GET',
+        requestUrl: '/render-jobs/12345/status',
+        statusCode: 200,
+        duration: 620,
+        status: 'success'
+      },
+      {
+        id: '5',
+        timestamp: '2025-09-15 14:10:20',
+        integrationId: '5',
+        integrationName: '项目管理工具API',
+        requestMethod: 'POST',
+        requestUrl: '/webhooks/task-created',
+        statusCode: 200,
+        duration: 380,
+        status: 'success'
+      },
+      {
+        id: '6',
+        timestamp: '2025-09-15 14:05:15',
+        integrationId: '2',
+        integrationName: '设计素材库API',
+        requestMethod: 'POST',
+        requestUrl: '/assets/upload',
+        statusCode: 201,
+        duration: 2350,
+        status: 'success'
+      },
+      {
+        id: '7',
+        timestamp: '2025-09-15 14:00:55',
+        integrationId: '3',
+        integrationName: '客户管理系统API',
+        requestMethod: 'GET',
+        requestUrl: '/customers?updatedSince=2025-09-01',
+        statusCode: 401,
+        duration: 320,
+        status: 'error',
+        errorMessage: '认证失败:无效的API密钥'
+      },
+      {
+        id: '8',
+        timestamp: '2025-09-15 13:55:30',
+        integrationId: '1',
+        integrationName: '渲染农场API',
+        requestMethod: 'PUT',
+        requestUrl: '/render-jobs/12345/cancel',
+        statusCode: 200,
+        duration: 580,
+        status: 'success'
+      }
+    ];
+
+    // 生成更多日志数据以模拟大量记录
+    const generatedLogs: ApiLog[] = [];
+    let idCounter = 9;
+
+    // 复制模拟数据多次以创建大量日志
+    for (let i = 0; i < 3; i++) {
+      mockLogs.forEach(log => {
+        // 为每条日志创建一个变体,更改时间戳
+        const logDate = new Date(log.timestamp);
+        logDate.setHours(logDate.getHours() - i * 2);
+        logDate.setMinutes(Math.floor(Math.random() * 60));
+        logDate.setSeconds(Math.floor(Math.random() * 60));
+
+        generatedLogs.push({
+          ...log,
+          id: idCounter.toString(),
+          timestamp: logDate.toLocaleString('zh-CN', {
+            year: 'numeric',
+            month: '2-digit',
+            day: '2-digit',
+            hour: '2-digit',
+            minute: '2-digit',
+            second: '2-digit'
+          }).replace(/\//g, '-')
+        });
+        idCounter++;
+      });
+    }
+
+    // 合并原始数据和生成的数据
+    this.apiLogs.set([...mockLogs, ...generatedLogs].sort((a, b) => 
+      new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
+    ));
+
+    this.filteredLogs = this.apiLogs;
+    this.applyLogFilters();
+  }
+
+  // 标签页切换
+  switchTab(tab: string): void {
+    this.activeTab = tab;
+  }
+
+  // API集成筛选方法
+  applyIntegrationFilters(): void {
+    let result = [...this.apiIntegrations()];
+
+    // 搜索词筛选
+    if (this.integrationSearchTerm) {
+      const term = this.integrationSearchTerm.toLowerCase();
+      result = result.filter(integration => 
+        integration.name.toLowerCase().includes(term) ||
+        integration.description.toLowerCase().includes(term) ||
+        integration.endpoint.toLowerCase().includes(term)
+      );
+    }
+
+    // 类型筛选
+    if (this.integrationTypeFilter) {
+      result = result.filter(integration => integration.type === this.integrationTypeFilter);
+    }
+
+    // 状态筛选
+    if (this.integrationStatusFilter) {
+      result = result.filter(integration => integration.status === this.integrationStatusFilter);
+    }
+
+    // 排序
+    result.sort((a, b) => {
+      if (this.integrationSortColumn === 'createdAt' || this.integrationSortColumn === 'updatedAt' || this.integrationSortColumn === 'lastTested') {
+        return this.integrationSortDirection === 'asc' 
+          ? new Date(a[this.integrationSortColumn] || 0).getTime() - new Date(b[this.integrationSortColumn] || 0).getTime()
+          : new Date(b[this.integrationSortColumn] || 0).getTime() - new Date(a[this.integrationSortColumn] || 0).getTime();
+      } else {
+        const valueA = a[this.integrationSortColumn as keyof ApiIntegration]?.toString().toLowerCase() || '';
+        const valueB = b[this.integrationSortColumn as keyof ApiIntegration]?.toString().toLowerCase() || '';
+        return this.integrationSortDirection === 'asc' 
+          ? valueA.localeCompare(valueB)
+          : valueB.localeCompare(valueA);
+      }
+    });
+
+    this.filteredIntegrations.set(result);
+    this.integrationCurrentPage = 0; // 重置到第一页
+  }
+
+  // API日志筛选方法
+  applyLogFilters(): void {
+    let result = [...this.apiLogs()];
+
+    // 搜索词筛选
+    if (this.logSearchTerm) {
+      const term = this.logSearchTerm.toLowerCase();
+      result = result.filter(log => 
+        log.integrationName.toLowerCase().includes(term) ||
+        log.requestUrl.toLowerCase().includes(term) ||
+        (log.errorMessage && log.errorMessage.toLowerCase().includes(term))
+      );
+    }
+
+    // 集成筛选
+    if (this.logIntegrationFilter) {
+      result = result.filter(log => log.integrationId === this.logIntegrationFilter);
+    }
+
+    // 状态筛选
+    if (this.logStatusFilter) {
+      result = result.filter(log => log.status === this.logStatusFilter);
+    }
+
+    // 日期范围筛选
+    if (this.logStartDate) {
+      const startDateTime = new Date(this.logStartDate).setHours(0, 0, 0, 0);
+      result = result.filter(log => new Date(log.timestamp).getTime() >= startDateTime);
+    }
+
+    if (this.logEndDate) {
+      const endDateTime = new Date(this.logEndDate).setHours(23, 59, 59, 999);
+      result = result.filter(log => new Date(log.timestamp).getTime() <= endDateTime);
+    }
+
+    // 排序
+    result.sort((a, b) => {
+      if (this.logSortColumn === 'timestamp') {
+        return this.logSortDirection === 'asc' 
+          ? new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
+          : new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
+      } else if (this.logSortColumn === 'duration' || this.logSortColumn === 'statusCode') {
+        // 安全地获取数字值
+        const valueA = Number(a[this.logSortColumn as keyof ApiLog]) || 0;
+        const valueB = Number(b[this.logSortColumn as keyof ApiLog]) || 0;
+        return this.logSortDirection === 'asc' 
+          ? valueA - valueB
+          : valueB - valueA;
+      } else {
+        const valueA = a[this.logSortColumn as keyof ApiLog]?.toString().toLowerCase() || '';
+        const valueB = b[this.logSortColumn as keyof ApiLog]?.toString().toLowerCase() || '';
+        return this.logSortDirection === 'asc' 
+          ? valueA.localeCompare(valueB)
+          : valueB.localeCompare(valueA);
+      }
+    });
+
+    this.filteredLogs.set(result);
+    this.logCurrentPage = 0; // 重置到第一页
+  }
+
+  // 集成相关方法
+  onIntegrationSearch(): void {
+    this.applyIntegrationFilters();
+  }
+
+  onIntegrationFilterChange(): void {
+    this.applyIntegrationFilters();
+  }
+
+  onIntegrationSort(column: string): void {
+    if (this.integrationSortColumn === column) {
+      this.integrationSortDirection = this.integrationSortDirection === 'asc' ? 'desc' : 'asc';
+    } else {
+      this.integrationSortColumn = column;
+      this.integrationSortDirection = 'asc';
+    }
+    this.applyIntegrationFilters();
+  }
+
+  // 日志相关方法
+  onLogSearch(): void {
+    this.applyLogFilters();
+  }
+
+  onLogFilterChange(): void {
+    this.applyLogFilters();
+  }
+
+  onLogSort(column: string): void {
+    if (this.logSortColumn === column) {
+      this.logSortDirection = this.logSortDirection === 'asc' ? 'desc' : 'asc';
+    } else {
+      this.logSortColumn = column;
+      this.logSortDirection = 'asc';
+    }
+    this.applyLogFilters();
+  }
+
+  // 打开API对话框
+  openApiDialog(integration?: ApiIntegration): void {
+    const dialogRef = this.dialog.open(ApiDialogComponent, {
+      width: '600px',
+      data: integration ? { ...integration } : {
+        id: '',
+        name: '',
+        type: '',
+        endpoint: '',
+        authentication: 'none',
+        description: '',
+        configuration: {},
+        status: 'inactive',
+        lastTested: '',
+        createdBy: '超级管理员',
+        createdAt: new Date().toLocaleString('zh-CN'),
+        updatedAt: new Date().toLocaleString('zh-CN')
+      }
+    });
+
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        if (result.id) {
+          // 更新集成
+          this.updateIntegration(result);
+        } else {
+          // 创建新集成
+          this.createIntegration(result);
+        }
+      }
+    });
+  }
+
+  // 集成操作方法
+  createIntegration(integrationData: Omit<ApiIntegration, 'id'>): void {
+    const newIntegration: ApiIntegration = {
+      ...integrationData,
+      id: (this.apiIntegrations().length + 1).toString(),
+      createdAt: new Date().toLocaleString('zh-CN'),
+      updatedAt: new Date().toLocaleString('zh-CN')
+    };
+
+    this.apiIntegrations.set([newIntegration, ...this.apiIntegrations()]);
+    this.applyIntegrationFilters();
+  }
+
+  updateIntegration(updatedIntegration: ApiIntegration): void {
+    this.apiIntegrations.set(this.apiIntegrations().map(integration => 
+      integration.id === updatedIntegration.id ? updatedIntegration : integration
+    ));
+    this.applyIntegrationFilters();
+  }
+
+  deleteIntegration(id: string): void {
+    if (confirm('确定要删除这个API集成吗?')) {
+      this.apiIntegrations.set(this.apiIntegrations().filter(integration => integration.id !== id));
+      this.applyIntegrationFilters();
+    }
+  }
+
+  toggleIntegrationStatus(id: string, status: 'active' | 'inactive'): void {
+    this.apiIntegrations.set(this.apiIntegrations().map(integration => 
+      integration.id === id ? { ...integration, status, updatedAt: new Date().toLocaleString('zh-CN') } : integration
+    ));
+    this.applyIntegrationFilters();
+  }
+
+  testIntegration(id: string): void {
+    // 模拟测试操作
+    alert(`正在测试API集成...\n测试完成!`);
+    this.apiIntegrations.set(this.apiIntegrations().map(integration => 
+      integration.id === id ? { ...integration, lastTested: new Date().toLocaleString('zh-CN') } : integration
+    ));
+    this.applyIntegrationFilters();
+  }
+
+  // 分页相关方法
+  get paginatedIntegrations(): ApiIntegration[] {
+    const startIndex = this.integrationCurrentPage * this.integrationPageSize;
+    return this.filteredIntegrations().slice(startIndex, startIndex + this.integrationPageSize);
+  }
+
+  get totalIntegrationPages(): number {
+    return Math.ceil(this.filteredIntegrations().length / this.integrationPageSize);
+  }
+
+  onIntegrationPageChange(page: number): void {
+    this.integrationCurrentPage = page;
+  }
+
+  get paginatedLogs(): ApiLog[] {
+    const startIndex = this.logCurrentPage * this.logPageSize;
+    return this.filteredLogs().slice(startIndex, startIndex + this.logPageSize);
+  }
+
+  get totalLogPages(): number {
+    return Math.ceil(this.filteredLogs().length / this.logPageSize);
+  }
+
+  onLogPageChange(page: number): void {
+    this.logCurrentPage = page;
+  }
+
+  // 格式化日期显示
+  formatDate(dateString: string): string {
+    const date = new Date(dateString);
+    return date.toLocaleString('zh-CN', {
+      year: 'numeric',
+      month: '2-digit',
+      day: '2-digit',
+      hour: '2-digit',
+      minute: '2-digit',
+      second: '2-digit'
+    });
+  }
+
+  // 获取状态文本
+  getStatusText(status: string): string {
+    const statusMap = {
+      'active': '已激活',
+      'inactive': '已禁用',
+      'error': '错误'
+    };
+    return statusMap[status as keyof typeof statusMap] || status;
+  }
+
+  // 导出日志
+  exportLogs(): void {
+    // 模拟导出功能
+    alert(`已导出 ${this.filteredLogs().length} 条API日志记录`);
+  }
+
+  // 清除筛选条件
+  clearIntegrationFilters(): void {
+    this.integrationSearchTerm = '';
+    this.integrationTypeFilter = '';
+    this.integrationStatusFilter = '';
+    this.applyIntegrationFilters();
+  }
+
+  clearLogFilters(): void {
+    this.logSearchTerm = '';
+    this.logIntegrationFilter = '';
+    this.logStatusFilter = '';
+    this.logStartDate = null;
+    this.logEndDate = null;
+    this.applyLogFilters();
+  }
+
+  // 获取集成ID选项
+  get integrationIdOptions(): { id: string; name: string }[] {
+    return this.apiIntegrations().map(integration => ({
+      id: integration.id,
+      name: integration.name
+    }));
+  }
+
+  // 获取页码数组
+  getPageNumbers(): number[] {
+    const pages = [];
+    const totalPages = this.totalIntegrationPages;
+    const currentPage = this.integrationCurrentPage;
+    const maxVisiblePages = 5;
+
+    let startPage = Math.max(0, currentPage - Math.floor(maxVisiblePages / 2));
+    let endPage = startPage + maxVisiblePages - 1;
+
+    if (endPage >= totalPages) {
+      endPage = totalPages - 1;
+      startPage = Math.max(0, endPage - maxVisiblePages + 1);
+    }
+
+    for (let i = startPage; i <= endPage; i++) {
+      pages.push(i);
+    }
+
+    return pages;
+  }
+
+  // 计算平均响应时间
+  calculateAverageDuration(): number {
+    if (this.apiLogs().length === 0) return 0;
+    const totalDuration = this.apiLogs().reduce((sum, log) => sum + log.duration, 0);
+    return Math.round(totalDuration / this.apiLogs().length);
+  }
+
+  // 封装Math对象的方法
+  mathMin(a: number, b: number): number {
+    return Math.min(a, b);
+  }
+
+  // 封装Object对象的方法
+  objectEntries(obj: Record<string, any>): [string, any][] {
+    return Object.entries(obj);
+  }
+  
+  // 计算活跃集成数量
+  get activeIntegrationsCount(): number {
+    return this.apiIntegrations().filter(i => i.status === 'active').length;
+  }
+  
+  // 计算禁用集成数量
+  get inactiveIntegrationsCount(): number {
+    return this.apiIntegrations().filter(i => i.status === 'inactive').length;
+  }
+  
+  // 计算错误集成数量
+  get errorIntegrationsCount(): number {
+    return this.apiIntegrations().filter(i => i.status === 'error').length;
+  }
+  
+  // 计算成功日志数量
+  get successLogsCount(): number {
+    return this.apiLogs().filter(l => l.status === 'success').length;
+  }
+  
+  // 计算失败日志数量
+  get errorLogsCount(): number {
+    return this.apiLogs().filter(l => l.status === 'error').length;
+  }
+  
+  // 计算总API日志数量
+  get totalApiLogsCount(): number {
+    return this.apiLogs().length;
+  }
+  
+  // 计算平均响应时间(计算属性版本)
+  get averageResponseTime(): number {
+    return this.calculateAverageDuration();
+  }
+}

+ 211 - 0
src/app/pages/admin/dashboard/dashboard.html

@@ -0,0 +1,211 @@
+<div class="admin-dashboard">
+  <!-- 页面标题 -->
+  <div class="page-header">
+    <h2 class="page-title">总览看板</h2>
+    <p class="page-description">系统运营数据总览和趋势分析</p>
+  </div>
+
+  <!-- 统计卡片区域 -->
+  <div class="stats-grid">
+    <!-- 总项目数 -->
+    <div class="stat-card">
+      <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>
+          <line x1="8" y1="12" x2="21" y2="12"></line>
+          <line x1="8" y1="18" x2="21" y2="18"></line>
+          <line x1="3" y1="6" x2="3.01" y2="6"></line>
+          <line x1="3" y1="12" x2="3.01" y2="12"></line>
+          <line x1="3" y1="18" x2="3.01" y2="18"></line>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ stats.totalProjects() }}</div>
+        <div class="stat-label">总项目数</div>
+      </div>
+      <div class="stat-trend positive">
+        <span>+12%</span>
+      </div>
+    </div>
+
+    <!-- 进行中项目 -->
+    <div class="stat-card">
+      <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>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ stats.activeProjects() }}</div>
+        <div class="stat-label">进行中项目</div>
+      </div>
+      <div class="stat-trend positive">
+        <span>+8%</span>
+      </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">
+          <polyline points="20 6 9 17 4 12"></polyline>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ stats.completedProjects() }}</div>
+        <div class="stat-label">已完成项目</div>
+      </div>
+      <div class="stat-trend positive">
+        <span>+15%</span>
+      </div>
+    </div>
+
+    <!-- 设计师总数 -->
+    <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="M16 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="M22 21v-2a4 4 0 0 0-3-3.87"></path>
+          <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ stats.totalDesigners() }}</div>
+        <div class="stat-label">设计师总数</div>
+      </div>
+      <div class="stat-trend neutral">
+        <span>持平</span>
+      </div>
+    </div>
+
+    <!-- 客户总数 -->
+    <div class="stat-card">
+      <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>
+          <circle cx="12" cy="7" r="4"></circle>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ stats.totalCustomers() }}</div>
+        <div class="stat-label">客户总数</div>
+      </div>
+      <div class="stat-trend positive">
+        <span>+20%</span>
+      </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">
+          <line x1="12" y1="1" x2="12" y2="23"></line>
+          <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ formatCurrency(stats.totalRevenue()) }}</div>
+        <div class="stat-label">总收入</div>
+      </div>
+      <div class="stat-trend positive">
+        <span>+28%</span>
+      </div>
+    </div>
+  </div>
+
+  <!-- 图表区域 -->
+  <div class="charts-grid">
+    <!-- 项目趋势图 -->
+    <div class="chart-card">
+      <div class="chart-header">
+        <h3>项目数量趋势</h3>
+        <div class="chart-period">
+          <button class="period-btn active">近6个月</button>
+          <button class="period-btn">近12个月</button>
+        </div>
+      </div>
+      <div id="projectTrendChart" class="chart-container"></div>
+    </div>
+
+    <!-- 收入统计图 -->
+    <div class="chart-card">
+      <div class="chart-header">
+        <h3>季度收入统计</h3>
+        <div class="chart-period">
+          <button class="period-btn active">本季度</button>
+          <button class="period-btn">全年</button>
+        </div>
+      </div>
+      <div id="revenueChart" class="chart-container"></div>
+    </div>
+  </div>
+
+  <!-- 最近活动 -->
+  <div class="recent-activities">
+    <div class="section-header">
+      <h3>最近活动</h3>
+      <a href="/admin/logs" class="view-all-link">查看全部</a>
+    </div>
+    
+    <div class="activities-list">
+      <div class="activity-item">
+        <div class="activity-icon">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
+          </svg>
+        </div>
+        <div class="activity-content">
+          <div class="activity-text">
+            <span class="activity-user">系统</span> 创建了新项目 <span class="activity-project">现代简约风格三居室设计</span>
+          </div>
+          <div class="activity-time">今天 10:30</div>
+        </div>
+      </div>
+      
+      <div class="activity-item">
+        <div class="activity-icon">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
+            <polyline points="22 4 12 14.01 9 11.01"></polyline>
+          </svg>
+        </div>
+        <div class="activity-content">
+          <div class="activity-text">
+            <span class="activity-user">张设计师</span> 完成了任务 <span class="activity-task">设计初稿</span>
+          </div>
+          <div class="activity-time">今天 09:15</div>
+        </div>
+      </div>
+      
+      <div class="activity-item">
+        <div class="activity-icon">
+          <svg width="16" height="16" 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>
+          </svg>
+        </div>
+        <div class="activity-content">
+          <div class="activity-text">
+            <span class="activity-user">客服小李</span> 新增了客户 <span class="activity-customer">王先生</span>
+          </div>
+          <div class="activity-time">昨天 16:45</div>
+        </div>
+      </div>
+      
+      <div class="activity-item">
+        <div class="activity-icon">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
+            <polyline points="20 6 9 17 4 12"></polyline>
+          </svg>
+        </div>
+        <div class="activity-content">
+          <div class="activity-text">
+            <span class="activity-user">系统</span> 完成了项目 <span class="activity-project">北欧风格两居室设计</span>
+          </div>
+          <div class="activity-time">昨天 15:30</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>

+ 352 - 0
src/app/pages/admin/dashboard/dashboard.scss

@@ -0,0 +1,352 @@
+// 全局变量定义
+$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 1px 2px rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
+$shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.1);
+$border-radius: 8px;
+$transition: all 0.3s ease;
+
+// 主容器
+.admin-dashboard {
+  padding: 20px 0;
+}
+
+// 页面标题
+.page-header {
+  margin-bottom: 24px;
+
+  .page-title {
+    font-size: 28px;
+    font-weight: 600;
+    color: $text-primary;
+    margin: 0 0 8px 0;
+  }
+
+  .page-description {
+    font-size: 16px;
+    color: $text-secondary;
+    margin: 0;
+  }
+}
+
+// 统计卡片网格
+.stats-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+  gap: 20px;
+  margin-bottom: 32px;
+}
+
+// 统计卡片
+.stat-card {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  padding: 20px;
+  box-shadow: $shadow-sm;
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  transition: $transition;
+  border: 1px solid $border-color;
+
+  &:hover {
+    box-shadow: $shadow-md;
+    transform: translateY(-2px);
+  }
+
+  .stat-icon {
+    width: 48px;
+    height: 48px;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: white;
+
+    &.primary {
+      background-color: $primary-color;
+    }
+
+    &.secondary {
+      background-color: $secondary-color;
+    }
+
+    &.success {
+      background-color: $success-color;
+    }
+
+    &.warning {
+      background-color: $warning-color;
+    }
+
+    &.danger {
+      background-color: $danger-color;
+    }
+  }
+
+  .stat-content {
+    flex: 1;
+
+    .stat-value {
+      font-size: 28px;
+      font-weight: 600;
+      color: $text-primary;
+      margin-bottom: 4px;
+    }
+
+    .stat-label {
+      font-size: 14px;
+      color: $text-secondary;
+    }
+  }
+
+  .stat-trend {
+    font-size: 12px;
+    font-weight: 500;
+    padding: 4px 8px;
+    border-radius: 4px;
+    white-space: nowrap;
+
+    &.positive {
+      background-color: color-mix(in srgb, $success-color 5%, transparent);
+      color: $success-color;
+    }
+
+    &.negative {
+      background-color: color-mix(in srgb, $danger-color 5%, transparent);
+      color: $danger-color;
+    }
+
+    &.neutral {
+      background-color: $background-tertiary;
+      color: $text-secondary;
+    }
+  }
+}
+
+// 图表网格
+.charts-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
+  gap: 20px;
+  margin-bottom: 32px;
+}
+
+// 图表卡片
+.chart-card {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  box-shadow: $shadow-sm;
+  overflow: hidden;
+  border: 1px solid $border-color;
+
+  .chart-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 20px;
+    border-bottom: 1px solid $border-color;
+
+    h3 {
+      font-size: 16px;
+      font-weight: 600;
+      color: $text-primary;
+      margin: 0;
+    }
+
+    .chart-period {
+      display: flex;
+      gap: 8px;
+
+      .period-btn {
+        padding: 6px 12px;
+        border: 1px solid $border-color;
+        background-color: $background-primary;
+        border-radius: 4px;
+        font-size: 12px;
+        color: $text-secondary;
+        cursor: pointer;
+        transition: $transition;
+
+        &:hover {
+          background-color: $background-tertiary;
+        }
+
+        &.active {
+          background-color: $primary-color;
+          color: white;
+          border-color: $primary-color;
+        }
+      }
+    }
+  }
+
+  .chart-container {
+    height: 300px;
+    padding: 20px;
+  }
+}
+
+// 最近活动区域
+.recent-activities {
+  background-color: $background-primary;
+  border-radius: $border-radius;
+  box-shadow: $shadow-sm;
+  border: 1px solid $border-color;
+
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 20px;
+    border-bottom: 1px solid $border-color;
+
+    h3 {
+      font-size: 16px;
+      font-weight: 600;
+      color: $text-primary;
+      margin: 0;
+    }
+
+    .view-all-link {
+      font-size: 14px;
+      color: $primary-color;
+      text-decoration: none;
+      transition: $transition;
+
+      &:hover {
+        text-decoration: underline;
+      }
+    }
+  }
+
+  .activities-list {
+    padding: 0 20px 20px 20px;
+
+    .activity-item {
+      display: flex;
+      align-items: flex-start;
+      gap: 12px;
+      padding: 16px 0;
+      border-bottom: 1px solid $border-color;
+
+      &:last-child {
+        border-bottom: none;
+      }
+
+      .activity-icon {
+        width: 32px;
+        height: 32px;
+        border-radius: 50%;
+        background-color: $background-tertiary;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        color: $primary-color;
+        flex-shrink: 0;
+      }
+
+      .activity-content {
+        flex: 1;
+
+        .activity-text {
+          font-size: 14px;
+          color: $text-primary;
+          line-height: 1.5;
+          margin-bottom: 4px;
+        }
+
+        .activity-time {
+          font-size: 12px;
+          color: $text-tertiary;
+        }
+
+        .activity-user {
+          font-weight: 600;
+          color: $text-primary;
+        }
+
+        .activity-project,
+        .activity-task,
+        .activity-customer {
+          font-weight: 600;
+          color: $primary-color;
+        }
+      }
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .stats-grid {
+    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+  }
+
+  .charts-grid {
+    grid-template-columns: 1fr;
+  }
+}
+
+@media (max-width: 768px) {
+  .page-title {
+    font-size: 24px !important;
+  }
+
+  .stats-grid {
+    grid-template-columns: 1fr;
+  }
+
+  .chart-container {
+    height: 250px !important;
+  }
+
+  .chart-header {
+    flex-direction: column;
+    gap: 12px;
+    align-items: flex-start !important;
+  }
+
+  .recent-activities {
+    margin-top: 20px;
+  }
+}
+
+@media (max-width: 480px) {
+  .admin-dashboard {
+    padding: 12px 0;
+  }
+
+  .page-title {
+    font-size: 20px !important;
+  }
+
+  .page-description {
+    font-size: 14px !important;
+  }
+
+  .chart-container {
+    height: 200px !important;
+    padding: 12px !important;
+  }
+
+  .stat-card {
+    padding: 16px;
+  }
+
+  .stat-value {
+    font-size: 24px !important;
+  }
+}

+ 115 - 0
src/app/pages/admin/dashboard/dashboard.service.ts

@@ -0,0 +1,115 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable, of } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+// 定义仪表盘统计数据接口
+export interface DashboardStats {
+  totalProjects: number;
+  activeProjects: number;
+  completedProjects: number;
+  totalDesigners: number;
+  totalCustomers: number;
+  totalRevenue: number;
+  projectTrend: { name: string; value: number }[];
+  revenueData: { name: string; value: number }[];
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class AdminDashboardService {
+  constructor(private http: HttpClient) {}
+
+  // 获取仪表盘统计数据
+  getDashboardStats(): Observable<DashboardStats> {
+    // 在实际应用中,这里会调用后端API
+    // return this.http.get<DashboardStats>('/api/admin/dashboard/stats')
+    //   .pipe(map(response => response));
+
+    // 模拟API响应
+    return of({
+      totalProjects: 128,
+      activeProjects: 86,
+      completedProjects: 42,
+      totalDesigners: 24,
+      totalCustomers: 356,
+      totalRevenue: 1258000,
+      projectTrend: [
+        { name: '1月', value: 18 },
+        { name: '2月', value: 25 },
+        { name: '3月', value: 32 },
+        { name: '4月', value: 28 },
+        { name: '5月', value: 42 },
+        { name: '6月', value: 38 }
+      ],
+      revenueData: [
+        { name: '第一季度', value: 350000 },
+        { name: '第二季度', value: 420000 },
+        { name: '第三季度', value: 488000 }
+      ]
+    });
+  }
+
+  // 获取最近活动记录
+  getRecentActivities(): Observable<any[]> {
+    // 模拟数据
+    return of([
+      {
+        id: '1',
+        user: '系统',
+        action: '创建了新项目',
+        target: '现代简约风格三居室设计',
+        targetType: 'project',
+        time: '今天 10:30'
+      },
+      {
+        id: '2',
+        user: '张设计师',
+        action: '完成了任务',
+        target: '设计初稿',
+        targetType: 'task',
+        time: '今天 09:15'
+      },
+      {
+        id: '3',
+        user: '客服小李',
+        action: '新增了客户',
+        target: '王先生',
+        targetType: 'customer',
+        time: '昨天 16:45'
+      },
+      {
+        id: '4',
+        user: '系统',
+        action: '完成了项目',
+        target: '北欧风格两居室设计',
+        targetType: 'project',
+        time: '昨天 15:30'
+      }
+    ]);
+  }
+
+  // 获取项目状态分布
+  getProjectStatusDistribution(): Observable<any[]> {
+    // 模拟数据
+    return of([
+      { name: '进行中', value: 86, color: '#165DFF' },
+      { name: '已完成', value: 42, color: '#00B42A' },
+      { name: '已暂停', value: 15, color: '#FF7D00' },
+      { name: '已延期', value: 5, color: '#F53F3F' }
+    ]);
+  }
+
+  // 获取设计师工作量统计
+  getDesignerWorkloadStats(): Observable<any[]> {
+    // 模拟数据
+    return of([
+      { name: '张设计师', completed: 18, inProgress: 8 },
+      { name: '李设计师', completed: 15, inProgress: 6 },
+      { name: '王设计师', completed: 12, inProgress: 5 },
+      { name: '赵设计师', completed: 10, inProgress: 4 },
+      { name: '陈设计师', completed: 9, inProgress: 3 }
+    ]);
+  }
+}

+ 130 - 0
src/app/pages/admin/dashboard/dashboard.ts

@@ -0,0 +1,130 @@
+import { Component, OnInit, AfterViewInit, signal, OnDestroy } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import * as echarts from 'echarts';
+import { Subscription } from 'rxjs';
+import { AdminDashboardService } from './dashboard.service';
+
+@Component({
+  selector: 'app-admin-dashboard',
+  standalone: true,
+  imports: [CommonModule, RouterModule],
+  templateUrl: './dashboard.html',
+  styleUrl: './dashboard.scss'
+}) 
+export class AdminDashboard implements OnInit, AfterViewInit, OnDestroy {
+  // 统计数据
+  stats = {
+    totalProjects: signal(128),
+    activeProjects: signal(86),
+    completedProjects: signal(42),
+    totalDesigners: signal(24),
+    totalCustomers: signal(356),
+    totalRevenue: signal(1258000)
+  };
+
+  private subscriptions: Subscription = new Subscription();
+  private projectChart: echarts.ECharts | null = null;
+  private revenueChart: echarts.ECharts | null = null;
+
+  constructor(private dashboardService: AdminDashboardService) {}
+
+  ngOnInit(): void {
+    this.loadDashboardData();
+  }
+
+  ngAfterViewInit(): void {
+    this.initCharts();
+    window.addEventListener('resize', this.handleResize);
+  }
+
+  ngOnDestroy(): void {
+    this.subscriptions.unsubscribe();
+    window.removeEventListener('resize', this.handleResize);
+    if (this.projectChart) {
+      this.projectChart.dispose();
+    }
+    if (this.revenueChart) {
+      this.revenueChart.dispose();
+    }
+  }
+
+  loadDashboardData(): void {
+    // 在实际应用中,这里会从服务加载数据
+    // 由于是模拟环境,我们使用模拟数据
+    this.subscriptions.add(
+      this.dashboardService.getDashboardStats().subscribe(data => {
+        // 更新统计数据
+        // this.stats.totalProjects.set(data.totalProjects);
+        // 其他数据更新...
+      })
+    );
+  }
+
+  initCharts(): void {
+    // 初始化项目趋势图
+    const projectChartDom = document.getElementById('projectTrendChart');
+    if (projectChartDom) {
+      this.projectChart = echarts.init(projectChartDom);
+      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);
+      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)'
+            }
+          }
+        }]
+      });
+    }
+  }
+
+  private handleResize = (): void => {
+    if (this.projectChart) {
+      this.projectChart.resize();
+    }
+    if (this.revenueChart) {
+      this.revenueChart.resize();
+    }
+  };
+
+  formatCurrency(amount: number): string {
+    return '¥' + amount.toLocaleString('zh-CN');
+  }
+}

+ 390 - 0
src/app/pages/admin/logs/logs.html

@@ -0,0 +1,390 @@
+<div class="logs">
+  <!-- 页面标题 -->
+  <div class="page-header">
+    <h2 class="page-title">系统日志</h2>
+    <p class="page-description">查看系统操作日志和审计记录,支持筛选、搜索和导出功能</p>
+  </div>
+
+  <!-- 搜索和筛选区域 -->
+  <div class="search-filter-section">
+    <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>
+        <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+      </svg>
+      <input matInput type="text" placeholder="搜索用户、操作、资源、详情或IP地址..."
+             [(ngModel)]="searchTerm" (keyup.enter)="onSearch()"
+             class="search-input">
+      <button mat-button *ngIf="searchTerm" class="clear-search-btn"
+              (click)="searchTerm = ''; onSearch()">
+        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+          <line x1="18" y1="6" x2="6" y2="18"></line>
+          <line x1="6" y1="6" x2="18" y2="18"></line>
+        </svg>
+      </button>
+      <button mat-button class="search-btn" (click)="onSearch()">
+        <svg 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>
+          <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+        </svg>
+        搜索
+      </button>
+    </div>
+  </div>
+
+  <!-- 高级筛选区域 -->
+  <div class="advanced-filters">
+    <div class="filter-grid">
+      <div class="filter-item">
+        <mat-form-field appearance="outline" class="filter-field">
+          <mat-label>操作类型</mat-label>
+          <mat-select [(ngModel)]="actionFilter" (selectionChange)="onFilterChange()">
+            <mat-option value="">全部操作</mat-option>
+            <mat-option *ngFor="let action of actionOptions" [value]="action">
+              {{ action }}
+            </mat-option>
+          </mat-select>
+          <mat-icon matSuffix>filter_list</mat-icon>
+        </mat-form-field>
+      </div>
+      
+      <div class="filter-item">
+        <mat-form-field appearance="outline" class="filter-field">
+          <mat-label>资源类型</mat-label>
+          <mat-select [(ngModel)]="resourceFilter" (selectionChange)="onFilterChange()">
+            <mat-option value="">全部资源</mat-option>
+            <mat-option *ngFor="let resource of resourceOptions" [value]="resource">
+              {{ resource }}
+            </mat-option>
+          </mat-select>
+          <mat-icon matSuffix>category</mat-icon>
+        </mat-form-field>
+      </div>
+      
+      <div class="filter-item">
+        <mat-form-field appearance="outline" class="filter-field">
+          <mat-label>状态</mat-label>
+          <mat-select [(ngModel)]="statusFilter" (selectionChange)="onFilterChange()">
+            <mat-option value="">全部状态</mat-option>
+            <mat-option *ngFor="let status of statusOptions" [value]="status.value">
+              {{ status.label }}
+            </mat-option>
+          </mat-select>
+          <mat-icon matSuffix>flag</mat-icon>
+        </mat-form-field>
+      </div>
+      
+      <div class="filter-item">
+        <mat-form-field appearance="outline" class="filter-field">
+          <mat-label>操作用户</mat-label>
+          <mat-select [(ngModel)]="userFilter" (selectionChange)="onFilterChange()">
+            <mat-option value="">全部用户</mat-option>
+            <mat-option *ngFor="let user of userOptions" [value]="user">
+              {{ user }}
+            </mat-option>
+          </mat-select>
+          <mat-icon matSuffix>person</mat-icon>
+        </mat-form-field>
+      </div>
+    </div>
+    
+    <div class="date-range-filter">
+      <div class="date-filter-item">
+        <mat-form-field appearance="outline" class="date-field">
+          <mat-label>开始日期</mat-label>
+          <input matInput [matDatepicker]="startDatePicker" [(ngModel)]="startDate" (dateChange)="onFilterChange()">
+          <mat-datepicker-toggle matSuffix [for]="startDatePicker"></mat-datepicker-toggle>
+          <mat-datepicker #startDatePicker></mat-datepicker>
+        </mat-form-field>
+      </div>
+      
+      <div class="date-filter-item">
+        <mat-form-field appearance="outline" class="date-field">
+          <mat-label>结束日期</mat-label>
+          <input matInput [matDatepicker]="endDatePicker" [(ngModel)]="endDate" (dateChange)="onFilterChange()">
+          <mat-datepicker-toggle matSuffix [for]="endDatePicker"></mat-datepicker-toggle>
+          <mat-datepicker #endDatePicker></mat-datepicker>
+        </mat-form-field>
+      </div>
+    </div>
+    
+    <div class="filter-actions">
+      <button mat-button class="clear-btn" (click)="clearFilters()">
+        <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="18" y1="6" x2="6" y2="18"></line>
+          <line x1="6" y1="6" x2="18" y2="18"></line>
+        </svg>
+        清除筛选
+      </button>
+      
+      <button mat-button class="expand-btn">
+        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+          <polyline points="4 20 12 12 20 20"></polyline>
+          <polyline points="4 12 12 4 20 12"></polyline>
+          <polyline points="4 4 12 12 20 4"></polyline>
+        </svg>
+        高级筛选
+      </button>
+    </div>
+  </div>
+
+  <!-- 操作按钮 -->
+  <div class="log-actions">
+    <div class="logs-summary">
+      共找到 <span class="highlight">{{ filteredLogs().length }}</span> 条日志记录
+    </div>
+    
+    <div class="action-buttons">
+      <button mat-raised-button color="primary" class="export-btn"
+              (click)="exportLogs()" [disabled]="filteredLogs().length === 0">
+        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+          <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>
+    </div>
+  </div>
+
+  <!-- 日志表格 -->
+  <div class="logs-table-container">
+    <table mat-table [dataSource]="paginatedLogs" class="logs-table">
+      <!-- 时间戳列 -->
+      <ng-container matColumnDef="timestamp">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('timestamp')">
+          <div class="header-content">
+            <span>时间戳</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'timestamp'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let log" class="table-cell">
+          {{ formatDate(log.timestamp) }}
+        </td>
+      </ng-container>
+
+      <!-- 状态列 -->
+      <ng-container matColumnDef="status">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('status')">
+          <div class="header-content">
+            <span>状态</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'status'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let log" class="table-cell">
+          <div class="status-badge" [style.backgroundColor]="statusColors[log.status]">
+            <mat-icon class="status-icon">{{ statusIcons[log.status] }}</mat-icon>
+            {{ getStatusText(log.status) }}
+          </div>
+        </td>
+      </ng-container>
+
+      <!-- 用户列 -->
+      <ng-container matColumnDef="user">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('user')">
+          <div class="header-content">
+            <span>操作用户</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'user'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let log" class="table-cell">
+          <div class="user-info">
+            <span class="user-name">{{ log.user }}</span>
+          </div>
+        </td>
+      </ng-container>
+
+      <!-- 操作列 -->
+      <ng-container matColumnDef="action">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('action')">
+          <div class="header-content">
+            <span>操作类型</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'action'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let log" class="table-cell">
+          <div class="action-badge">{{ log.action }}</div>
+        </td>
+      </ng-container>
+
+      <!-- 资源列 -->
+      <ng-container matColumnDef="resource">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('resource')">
+          <div class="header-content">
+            <span>操作资源</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'resource'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let log" class="table-cell">
+          <div class="resource-badge">{{ log.resource }}</div>
+        </td>
+      </ng-container>
+
+      <!-- 详情列 -->
+      <ng-container matColumnDef="details">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('details')">
+          <div class="header-content">
+            <span>操作详情</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'details'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let log" class="table-cell details-cell">
+          <div class="details-content" [title]="log.details">{{ log.details }}</div>
+        </td>
+      </ng-container>
+
+      <!-- IP地址列 -->
+      <ng-container matColumnDef="ipAddress">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('ipAddress')">
+          <div class="header-content">
+            <span>IP地址</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'ipAddress'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let log" class="table-cell">
+          <div class="ip-address">{{ log.ipAddress }}</div>
+        </td>
+      </ng-container>
+
+      <tr mat-header-row *matHeaderRowDef="['timestamp', 'status', 'user', 'action', 'resource', 'details', 'ipAddress']"></tr>
+      <tr mat-row *matRowDef="let row; columns: ['timestamp', 'status', 'user', 'action', 'resource', 'details', 'ipAddress']"></tr>
+    </table>
+
+    <!-- 无数据状态 -->
+    <div *ngIf="paginatedLogs.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">
+        <path d="M14 2H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 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>
+      <p>没有找到符合条件的日志记录</p>
+      <button mat-button color="primary" (click)="clearFilters()">
+        清除筛选条件
+      </button>
+    </div>
+  </div>
+
+  <!-- 分页控件 -->
+  <div class="pagination-controls">
+    <div class="pagination-info">
+      显示 {{ mathMin(currentPage * pageSize + 1, filteredLogs().length) }} -
+      {{ mathMin((currentPage + 1) * pageSize, filteredLogs().length) }} 共 
+      {{ filteredLogs().length }} 项
+    </div>
+    <div class="pagination-buttons">
+      <button mat-button [disabled]="currentPage === 0" (click)="onPageChange(0)">
+        首页
+      </button>
+      <button mat-button [disabled]="currentPage === 0" (click)="onPageChange(currentPage - 1)">
+        上一页
+      </button>
+      
+      <!-- 页码按钮 -->
+      <div class="page-numbers">
+        <button mat-button *ngFor="let page of getPageNumbers()" [class.active]="page === currentPage"
+                (click)="onPageChange(page)">
+          {{ page + 1 }}
+        </button>
+      </div>
+      
+      <button mat-button [disabled]="currentPage === totalPages - 1" (click)="onPageChange(currentPage + 1)">
+        下一页
+      </button>
+      <button mat-button [disabled]="currentPage === totalPages - 1" (click)="onPageChange(totalPages - 1)">
+        末页
+      </button>
+    </div>
+  </div>
+
+  <!-- 日志统计卡片 -->
+  <div class="log-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" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+          <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ logs().length }}</div>
+        <div class="stat-label">总日志数</div>
+      </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" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+          <polyline points="20 6 9 17 4 12"></polyline>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ successLogsCount }}</div>
+        <div class="stat-label">成功操作</div>
+      </div>
+    </div>
+    
+    <div class="stat-card">
+      <div class="stat-icon error">
+        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+          <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">{{ errorLogsCount }}</div>
+        <div class="stat-label">错误操作</div>
+      </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" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+          <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
+          <line x1="12" y1="9" x2="12" y2="13"></line>
+          <line x1="12" y1="17" x2="12.01" y2="17"></line>
+        </svg>
+      </div>
+      <div class="stat-content">
+        <div class="stat-value">{{ warningLogsCount }}</div>
+        <div class="stat-label">警告信息</div>
+      </div>
+    </div>
+  </div>
+</div>

+ 616 - 0
src/app/pages/admin/logs/logs.scss

@@ -0,0 +1,616 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$secondary-color: #6B7280;
+$success-color: #00B42A;
+$warning-color: #FFAA00;
+$error-color: #F53F3F;
+$text-primary: #333333;
+$text-secondary: #666666;
+$text-tertiary: #999999;
+$border-color: #E5E7EB;
+$bg-light: #F9FAFB;
+$bg-white: #FFFFFF;
+$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+
+// 主容器
+.logs {
+  padding: 24px;
+  min-height: 100vh;
+  background-color: $bg-light;
+}
+
+// 页面标题区域
+.page-header {
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 24px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid $border-color;
+
+  .page-title {
+    font-size: 24px;
+    font-weight: 600;
+    color: $text-primary;
+    margin: 0 0 4px 0;
+  }
+
+  .page-description {
+    font-size: 14px;
+    color: $text-tertiary;
+    margin: 0;
+  }
+}
+
+// 搜索区域
+.search-filter-section {
+  margin-bottom: 24px;
+
+  .search-input-wrapper {
+    position: relative;
+    display: flex;
+    align-items: center;
+    background-color: $bg-white;
+    border-radius: 8px;
+    box-shadow: $shadow-sm;
+    overflow: hidden;
+
+    .search-icon {
+      position: absolute;
+      left: 16px;
+      color: $text-tertiary;
+      z-index: 1;
+    }
+
+    .search-input {
+      flex: 1;
+      padding: 12px 16px 12px 48px;
+      border: none;
+      background-color: transparent;
+      font-size: 14px;
+      color: $text-primary;
+      outline: none;
+
+      &::placeholder {
+        color: $text-tertiary;
+      }
+    }
+
+    .clear-search-btn,
+    .search-btn {
+      padding: 12px 16px;
+      color: $text-secondary;
+      min-width: auto;
+
+      &:hover {
+        background-color: $bg-light;
+        color: $text-primary;
+      }
+    }
+  }
+}
+
+// 高级筛选区域
+.advanced-filters {
+  background-color: $bg-white;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: $shadow-sm;
+  margin-bottom: 24px;
+
+  .filter-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+    gap: 20px;
+    margin-bottom: 20px;
+
+    .filter-item {
+      .filter-field {
+        width: 100%;
+        .mat-form-field-wrapper {
+          .mat-form-field-flex {
+            border-radius: 6px;
+          }
+
+          .mat-form-field-outline {
+            border-radius: 6px;
+          }
+
+          .mat-form-field-infix {
+            padding: 8px 0;
+
+            .mat-input-element {
+              font-size: 14px;
+              color: $text-primary;
+            }
+          }
+
+          .mat-form-field-label {
+            font-size: 14px;
+            color: $text-secondary;
+          }
+
+          .mat-form-field-suffix {
+            .mat-icon {
+              color: $text-tertiary;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .date-range-filter {
+    display: flex;
+    gap: 20px;
+    margin-bottom: 20px;
+    flex-wrap: wrap;
+
+    .date-filter-item {
+      .date-field {
+        width: 200px;
+        .mat-form-field-wrapper {
+          .mat-form-field-flex {
+            border-radius: 6px;
+          }
+
+          .mat-form-field-outline {
+            border-radius: 6px;
+          }
+
+          .mat-form-field-infix {
+            padding: 8px 0;
+
+            .mat-input-element {
+              font-size: 14px;
+              color: $text-primary;
+            }
+          }
+
+          .mat-form-field-label {
+            font-size: 14px;
+            color: $text-secondary;
+          }
+        }
+      }
+    }
+  }
+
+  .filter-actions {
+    display: flex;
+    gap: 12px;
+    justify-content: flex-end;
+
+    .clear-btn,
+    .expand-btn {
+      display: flex;
+      align-items: center;
+      gap: 6px;
+      padding: 8px 16px;
+      font-size: 14px;
+      color: $text-secondary;
+      border: 1px solid $border-color;
+      border-radius: 6px;
+      background-color: $bg-white;
+      transition: all 0.2s ease;
+
+      &:hover {
+        background-color: $bg-light;
+        border-color: $text-tertiary;
+        color: $text-primary;
+      }
+    }
+  }
+}
+
+// 操作按钮区域
+.log-actions {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24px;
+  flex-wrap: wrap;
+  gap: 16px;
+
+  .logs-summary {
+    font-size: 14px;
+    color: $text-secondary;
+
+    .highlight {
+      font-weight: 600;
+      color: $primary-color;
+    }
+  }
+
+  .action-buttons {
+    display: flex;
+    gap: 12px;
+
+    .export-btn {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      padding: 10px 20px;
+      font-size: 14px;
+      font-weight: 500;
+      background-color: $primary-color;
+      color: $bg-white;
+      border-radius: 6px;
+      transition: all 0.2s ease;
+      box-shadow: $shadow-sm;
+
+      &:hover:not(:disabled) {
+        background-color: #0E4BD8;
+        box-shadow: $shadow-md;
+        transform: translateY(-1px);
+      }
+
+      &:disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+        transform: none;
+        box-shadow: none;
+      }
+    }
+  }
+}
+
+// 日志表格容器
+.logs-table-container {
+  background-color: $bg-white;
+  border-radius: 12px;
+  box-shadow: $shadow-sm;
+  overflow: hidden;
+  margin-bottom: 24px;
+
+  .logs-table {
+    width: 100%;
+    border-collapse: collapse;
+
+    .table-header {
+      background-color: $bg-light;
+      font-weight: 600;
+      color: $text-secondary;
+      font-size: 13px;
+      text-align: left;
+      padding: 14px 16px;
+      border-bottom: 1px solid $border-color;
+      cursor: pointer;
+      transition: background-color 0.2s ease;
+
+      &:hover {
+        background-color: #F3F4F6;
+      }
+
+      .header-content {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+      }
+
+      .sort-icon {
+        opacity: 0.5;
+      }
+    }
+
+    .table-cell {
+      padding: 12px 16px;
+      border-bottom: 1px solid $border-color;
+      font-size: 13px;
+      color: $text-primary;
+      vertical-align: middle;
+
+      &.details-cell {
+        max-width: 300px;
+
+        .details-content {
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+      }
+
+      .status-badge {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        padding: 4px 12px;
+        border-radius: 16px;
+        font-size: 12px;
+        font-weight: 500;
+        color: $bg-white;
+        text-align: center;
+        white-space: nowrap;
+
+        .status-icon {
+          font-size: 14px;
+        }
+      }
+
+      .user-info {
+        .user-name {
+          font-weight: 500;
+        }
+      }
+
+      .action-badge,
+      .resource-badge {
+        display: inline-block;
+        padding: 4px 12px;
+        border-radius: 16px;
+        font-size: 12px;
+        font-weight: 500;
+        background-color: #E6F0FF;
+        color: $primary-color;
+        white-space: nowrap;
+      }
+
+      .ip-address {
+        font-family: 'Courier New', monospace;
+        color: $text-secondary;
+      }
+    }
+
+    tr:last-child .table-cell {
+      border-bottom: none;
+    }
+
+    tr:hover .table-cell {
+      background-color: $bg-light;
+    }
+  }
+
+  // 无数据状态
+  .empty-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 80px 20px;
+    text-align: center;
+
+    svg {
+      margin-bottom: 16px;
+      opacity: 0.5;
+    }
+
+    p {
+      font-size: 16px;
+      color: $text-tertiary;
+      margin-bottom: 24px;
+    }
+
+    button {
+      border-radius: 8px;
+      padding: 8px 24px;
+      font-size: 14px;
+      font-weight: 500;
+    }
+  }
+}
+
+// 分页控件
+.pagination-controls {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px;
+  background-color: $bg-white;
+  border-radius: 12px;
+  box-shadow: $shadow-sm;
+
+  .pagination-info {
+    font-size: 14px;
+    color: $text-tertiary;
+  }
+
+  .pagination-buttons {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+
+    button {
+      padding: 6px 12px;
+      border-radius: 6px;
+      font-size: 13px;
+      font-weight: 500;
+      transition: all 0.2s ease;
+      min-width: auto;
+
+      &:disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+      }
+
+      &.active {
+        background-color: $primary-color;
+        color: $bg-white;
+      }
+    }
+
+    .page-numbers {
+      display: flex;
+      gap: 4px;
+    }
+  }
+}
+
+// 日志统计卡片
+.log-stats-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+  gap: 20px;
+  margin-top: 32px;
+
+  .stat-card {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+    background-color: $bg-white;
+    border-radius: 12px;
+    padding: 24px;
+    box-shadow: $shadow-sm;
+    transition: all 0.2s ease;
+
+    &: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;
+      color: $bg-white;
+
+      &.primary {
+        background-color: $primary-color;
+      }
+
+      &.success {
+        background-color: $success-color;
+      }
+
+      &.error {
+        background-color: $error-color;
+      }
+
+      &.warning {
+        background-color: $warning-color;
+      }
+    }
+
+    .stat-content {
+      flex: 1;
+
+      .stat-value {
+        font-size: 28px;
+        font-weight: 700;
+        color: $text-primary;
+        margin-bottom: 4px;
+      }
+
+      .stat-label {
+        font-size: 14px;
+        color: $text-tertiary;
+      }
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .logs-table {
+    .table-header,
+    .table-cell {
+      padding: 12px;
+    }
+  }
+
+  .date-range-filter {
+    flex-direction: column;
+    gap: 16px !important;
+
+    .date-field {
+      width: 100% !important;
+    }
+  }
+}
+
+@media (max-width: 992px) {
+  .logs {
+    padding: 16px;
+  }
+
+  .filter-grid {
+    grid-template-columns: 1fr !important;
+  }
+
+  .logs-table-container {
+    overflow-x: auto;
+  }
+
+  .pagination-controls {
+    flex-direction: column;
+    gap: 16px;
+
+    .pagination-buttons {
+      flex-wrap: wrap;
+      justify-content: center;
+    }
+  }
+
+  .log-stats-cards {
+    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+  }
+}
+
+@media (max-width: 768px) {
+  .advanced-filters {
+    padding: 16px;
+  }
+
+  .log-actions {
+    flex-direction: column;
+    align-items: stretch;
+    gap: 12px;
+  }
+
+  .action-buttons {
+    justify-content: center;
+  }
+
+  .stat-card {
+    padding: 20px;
+
+    .stat-icon {
+      width: 40px;
+      height: 40px;
+    }
+
+    .stat-value {
+      font-size: 24px;
+    }
+  }
+}
+
+@media (max-width: 480px) {
+  .logs {
+    padding: 12px;
+  }
+
+  .page-title {
+    font-size: 20px !important;
+  }
+
+  .log-stats-cards {
+    grid-template-columns: 1fr;
+    gap: 16px;
+  }
+}
+
+// 滚动条样式
+.logs-table-container,
+.advanced-filters {
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: $bg-light;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: $border-color;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb:hover {
+    background: $text-tertiary;
+  }
+}

+ 524 - 0
src/app/pages/admin/logs/logs.ts

@@ -0,0 +1,524 @@
+import { Component, OnInit, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { FormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { MatTableModule } from '@angular/material/table';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatSortModule } from '@angular/material/sort';
+import { MatDatepickerModule } from '@angular/material/datepicker';
+import { MatFormFieldModule } from '@angular/material/form-field';
+
+interface LogEntry {
+  id: string;
+  timestamp: string;
+  user: string;
+  action: string;
+  resource: string;
+  details: string;
+  ipAddress: string;
+  status: 'success' | 'error' | 'warning';
+}
+
+@Component({
+  selector: 'app-logs',
+  standalone: true,
+  imports: [
+    CommonModule,
+    RouterModule,
+    FormsModule,
+    MatButtonModule,
+    MatIconModule,
+    MatTableModule,
+    MatInputModule,
+    MatSelectModule,
+    MatPaginatorModule,
+    MatSortModule,
+    MatDatepickerModule,
+    MatFormFieldModule
+  ],
+  templateUrl: './logs.html',
+  styleUrl: './logs.scss'
+})
+export class Logs implements OnInit {
+  // 日志数据
+  logs = signal<LogEntry[]>([]);
+  filteredLogs = signal<LogEntry[]>([]);
+  
+  // 筛选条件
+  searchTerm = '';
+  actionFilter = '';
+  resourceFilter = '';
+  statusFilter = '';
+  startDate: Date | null = null;
+  endDate: Date | null = null;
+  userFilter = '';
+  
+  // 排序和分页
+  sortColumn = 'timestamp';
+  sortDirection = 'desc';
+  pageSize = 20;
+  currentPage = 0;
+  
+  // 可用的筛选选项
+  actionOptions = ['登录', '退出', '创建', '更新', '删除', '查询', '导出', '导入', '上传', '下载'];
+  resourceOptions = ['项目', '用户', '角色', 'SOP模板', '报价规则', '绩效规则', '系统设置', '客户信息', '案例库'];
+  statusOptions = [
+    { value: 'success', label: '成功' },
+    { value: 'error', label: '错误' },
+    { value: 'warning', label: '警告' }
+  ];
+  userOptions: string[] = [];
+  
+  // 状态颜色映射
+  statusColors: Record<string, string> = {
+    'success': '#00B42A',
+    'error': '#F53F3F',
+    'warning': '#FFAA00'
+  };
+  
+  // 状态图标映射
+  statusIcons: Record<string, string> = {
+    'success': 'check_circle',
+    'error': 'error',
+    'warning': 'warning'
+  };
+  
+  constructor() {}
+  
+  ngOnInit(): void {
+    this.loadLogs();
+  }
+  
+  loadLogs(): void {
+    // 模拟日志数据
+    const mockLogs: LogEntry[] = [
+      {
+        id: '1',
+        timestamp: '2025-09-15 14:30:25',
+        user: '超级管理员',
+        action: '登录',
+        resource: '系统',
+        details: '用户登录成功',
+        ipAddress: '192.168.1.100',
+        status: 'success'
+      },
+      {
+        id: '2',
+        timestamp: '2025-09-15 14:25:10',
+        user: '客服小李',
+        action: '创建',
+        resource: '项目',
+        details: '创建新项目:现代简约风格三居室设计',
+        ipAddress: '192.168.1.101',
+        status: 'success'
+      },
+      {
+        id: '3',
+        timestamp: '2025-09-15 14:20:45',
+        user: '设计师小张',
+        action: '更新',
+        resource: '项目',
+        details: '更新项目状态为:进行中',
+        ipAddress: '192.168.1.102',
+        status: 'success'
+      },
+      {
+        id: '4',
+        timestamp: '2025-09-15 14:15:30',
+        user: '超级管理员',
+        action: '创建',
+        resource: '用户',
+        details: '创建新用户:设计师小陈',
+        ipAddress: '192.168.1.100',
+        status: 'success'
+      },
+      {
+        id: '5',
+        timestamp: '2025-09-15 14:10:20',
+        user: '财务小陈',
+        action: '导出',
+        resource: '财务报表',
+        details: '导出9月份财务报表',
+        ipAddress: '192.168.1.103',
+        status: 'success'
+      },
+      {
+        id: '6',
+        timestamp: '2025-09-15 14:05:15',
+        user: '系统',
+        action: '更新',
+        resource: '系统设置',
+        details: '自动备份系统数据',
+        ipAddress: '127.0.0.1',
+        status: 'success'
+      },
+      {
+        id: '7',
+        timestamp: '2025-09-15 14:00:55',
+        user: '客服小王',
+        action: '上传',
+        resource: '案例库',
+        details: '上传新案例:北欧风格两居室设计',
+        ipAddress: '192.168.1.104',
+        status: 'success'
+      },
+      {
+        id: '8',
+        timestamp: '2025-09-15 13:55:30',
+        user: '未知用户',
+        action: '登录',
+        resource: '系统',
+        details: '用户登录失败:密码错误',
+        ipAddress: '203.0.113.10',
+        status: 'error'
+      },
+      {
+        id: '9',
+        timestamp: '2025-09-15 13:50:20',
+        user: '组长老王',
+        action: '审核',
+        resource: '项目',
+        details: '审核通过项目设计方案',
+        ipAddress: '192.168.1.105',
+        status: 'success'
+      },
+      {
+        id: '10',
+        timestamp: '2025-09-15 13:45:15',
+        user: '设计师小李',
+        action: '更新',
+        resource: '任务',
+        details: '完成任务:深化设计',
+        ipAddress: '192.168.1.106',
+        status: 'success'
+      },
+      {
+        id: '11',
+        timestamp: '2025-09-15 13:40:40',
+        user: '超级管理员',
+        action: '更新',
+        resource: '角色',
+        details: '修改角色权限:财务',
+        ipAddress: '192.168.1.100',
+        status: 'success'
+      },
+      {
+        id: '12',
+        timestamp: '2025-09-15 13:35:25',
+        user: '客服小李',
+        action: '查询',
+        resource: '客户信息',
+        details: '查询客户:王先生',
+        ipAddress: '192.168.1.101',
+        status: 'success'
+      },
+      {
+        id: '13',
+        timestamp: '2025-09-15 13:30:10',
+        user: '系统',
+        action: '警告',
+        resource: '系统资源',
+        details: '磁盘空间不足,请及时清理',
+        ipAddress: '127.0.0.1',
+        status: 'warning'
+      },
+      {
+        id: '14',
+        timestamp: '2025-09-15 13:25:55',
+        user: '设计师小张',
+        action: '上传',
+        resource: '项目文件',
+        details: '上传设计文件:客厅效果图.png',
+        ipAddress: '192.168.1.102',
+        status: 'success'
+      },
+      {
+        id: '15',
+        timestamp: '2025-09-15 13:20:30',
+        user: '财务小陈',
+        action: '更新',
+        resource: '项目财务',
+        details: '更新项目预算:+20000元',
+        ipAddress: '192.168.1.103',
+        status: 'success'
+      },
+      {
+        id: '16',
+        timestamp: '2025-09-15 13:15:20',
+        user: '人事小郑',
+        action: '更新',
+        resource: '用户',
+        details: '更新用户信息:设计师小李',
+        ipAddress: '192.168.1.107',
+        status: 'success'
+      },
+      {
+        id: '17',
+        timestamp: '2025-09-15 13:10:15',
+        user: '超级管理员',
+        action: '删除',
+        resource: '用户',
+        details: '删除用户:测试账号',
+        ipAddress: '192.168.1.100',
+        status: 'success'
+      },
+      {
+        id: '18',
+        timestamp: '2025-09-15 13:05:50',
+        user: '系统',
+        action: '错误',
+        resource: '数据库',
+        details: '数据库连接临时中断,已自动恢复',
+        ipAddress: '127.0.0.1',
+        status: 'error'
+      },
+      {
+        id: '19',
+        timestamp: '2025-09-15 13:00:35',
+        user: '客服小王',
+        action: '更新',
+        resource: '客户信息',
+        details: '更新客户联系方式',
+        ipAddress: '192.168.1.104',
+        status: 'success'
+      },
+      {
+        id: '20',
+        timestamp: '2025-09-15 12:55:20',
+        user: '组长老王',
+        action: '查询',
+        resource: '团队绩效',
+        details: '查看团队本月绩效报表',
+        ipAddress: '192.168.1.105',
+        status: 'success'
+      }
+    ];
+    
+    // 生成更多日志数据以模拟大量记录
+    const generatedLogs: LogEntry[] = [];
+    let idCounter = 21;
+    
+    // 复制模拟数据多次以创建大量日志
+    for (let i = 0; i < 5; i++) {
+      mockLogs.forEach(log => {
+        // 为每条日志创建一个变体,更改时间戳
+        const logDate = new Date(log.timestamp);
+        logDate.setHours(logDate.getHours() - i * 2);
+        logDate.setMinutes(Math.floor(Math.random() * 60));
+        logDate.setSeconds(Math.floor(Math.random() * 60));
+        
+        generatedLogs.push({
+          ...log,
+          id: idCounter.toString(),
+          timestamp: logDate.toLocaleString('zh-CN', {
+            year: 'numeric',
+            month: '2-digit',
+            day: '2-digit',
+            hour: '2-digit',
+            minute: '2-digit',
+            second: '2-digit'
+          }).replace(/\//g, '-'),
+          // 随机修改一些细节
+          details: i % 2 === 0 ? log.details : `${log.details} (变体${i})`
+        });
+        idCounter++;
+      });
+    }
+    
+    // 合并原始数据和生成的数据
+    this.logs.set([...mockLogs, ...generatedLogs].sort((a, b) => 
+      new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
+    ));
+    
+    // 提取唯一的用户列表用于筛选
+    this.userOptions = Array.from(new Set(this.logs().map(log => log.user))).sort();
+    
+    // 应用初始筛选
+    this.applyFilters();
+  }
+  
+  applyFilters(): void {
+    let result = [...this.logs()];
+    
+    // 搜索词筛选
+    if (this.searchTerm) {
+      const term = this.searchTerm.toLowerCase();
+      result = result.filter(log => 
+        log.user.toLowerCase().includes(term) ||
+        log.action.toLowerCase().includes(term) ||
+        log.resource.toLowerCase().includes(term) ||
+        log.details.toLowerCase().includes(term) ||
+        log.ipAddress.toLowerCase().includes(term)
+      );
+    }
+    
+    // 操作类型筛选
+    if (this.actionFilter) {
+      result = result.filter(log => log.action === this.actionFilter);
+    }
+    
+    // 资源类型筛选
+    if (this.resourceFilter) {
+      result = result.filter(log => log.resource === this.resourceFilter);
+    }
+    
+    // 状态筛选
+    if (this.statusFilter) {
+      result = result.filter(log => log.status === this.statusFilter);
+    }
+    
+    // 用户筛选
+    if (this.userFilter) {
+      result = result.filter(log => log.user === this.userFilter);
+    }
+    
+    // 日期范围筛选
+    if (this.startDate) {
+      const startDateTime = new Date(this.startDate).setHours(0, 0, 0, 0);
+      result = result.filter(log => new Date(log.timestamp).getTime() >= startDateTime);
+    }
+    
+    if (this.endDate) {
+      const endDateTime = new Date(this.endDate).setHours(23, 59, 59, 999);
+      result = result.filter(log => new Date(log.timestamp).getTime() <= endDateTime);
+    }
+    
+    // 排序
+    result.sort((a, b) => {
+      if (this.sortColumn === 'timestamp') {
+        return this.sortDirection === 'asc' 
+          ? new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
+          : new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
+      } else {
+        const valueA = a[this.sortColumn as keyof LogEntry]?.toString().toLowerCase() || '';
+        const valueB = b[this.sortColumn as keyof LogEntry]?.toString().toLowerCase() || '';
+        return this.sortDirection === 'asc' 
+          ? valueA.localeCompare(valueB)
+          : valueB.localeCompare(valueA);
+      }
+    });
+    
+    this.filteredLogs.set(result);
+    this.currentPage = 0; // 重置到第一页
+  }
+  
+  // 筛选相关方法
+  onSearch(): void {
+    this.applyFilters();
+  }
+  
+  onFilterChange(): void {
+    this.applyFilters();
+  }
+  
+  onSort(column: string): void {
+    if (this.sortColumn === column) {
+      this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
+    } else {
+      this.sortColumn = column;
+      this.sortDirection = 'asc';
+    }
+    this.applyFilters();
+  }
+  
+  // 分页相关方法
+  get paginatedLogs(): LogEntry[] {
+    const startIndex = this.currentPage * this.pageSize;
+    return this.filteredLogs().slice(startIndex, startIndex + this.pageSize);
+  }
+  
+  get totalPages(): number {
+    return Math.ceil(this.filteredLogs().length / this.pageSize);
+  }
+  
+  onPageChange(page: number): void {
+    this.currentPage = page;
+  }
+  
+  // 导出日志
+  exportLogs(): void {
+    // 模拟导出功能
+    alert(`已导出 ${this.filteredLogs().length} 条日志记录`);
+  }
+  
+  // 清除筛选条件
+  clearFilters(): void {
+    this.searchTerm = '';
+    this.actionFilter = '';
+    this.resourceFilter = '';
+    this.statusFilter = '';
+    this.startDate = null;
+    this.endDate = null;
+    this.userFilter = '';
+    this.applyFilters();
+  }
+  
+  // 格式化日期显示
+  formatDate(dateString: string): string {
+    const date = new Date(dateString);
+    return date.toLocaleString('zh-CN', {
+      year: 'numeric',
+      month: '2-digit',
+      day: '2-digit',
+      hour: '2-digit',
+      minute: '2-digit',
+      second: '2-digit'
+    });
+  }
+  
+  // 获取状态文本
+  getStatusText(status: string): string {
+    const statusMap = {
+      'success': '成功',
+      'error': '错误',
+      'warning': '警告'
+    };
+    return statusMap[status as keyof typeof statusMap] || status;
+  }
+  
+  // 计算成功日志数量
+  get successLogsCount(): number {
+    return this.logs().filter(log => log.status === 'success').length;
+  }
+  
+  // 计算错误日志数量
+  get errorLogsCount(): number {
+    return this.logs().filter(log => log.status === 'error').length;
+  }
+  
+  // 计算警告日志数量
+  get warningLogsCount(): number {
+    return this.logs().filter(log => log.status === 'warning').length;
+  }
+  
+  // 封装Math对象的方法
+  mathMin(a: number, b: number): number {
+    return Math.min(a, b);
+  }
+  
+  // 获取页码数组
+  getPageNumbers(): number[] {
+    const pages = [];
+    const totalPages = this.totalPages;
+    const currentPage = this.currentPage;
+    const maxVisiblePages = 5;
+
+    let startPage = Math.max(0, currentPage - Math.floor(maxVisiblePages / 2));
+    let endPage = startPage + maxVisiblePages - 1;
+
+    if (endPage >= totalPages) {
+      endPage = totalPages - 1;
+      startPage = Math.max(0, endPage - maxVisiblePages + 1);
+    }
+
+    for (let i = startPage; i <= endPage; i++) {
+      pages.push(i);
+    }
+
+    return pages;
+  }
+}

+ 127 - 0
src/app/pages/admin/project-management/project-dialog/project-dialog.html

@@ -0,0 +1,127 @@
+<div class="project-dialog">
+  <!-- 对话框标题 -->
+  <div class="dialog-header">
+    <h2 class="dialog-title">{{ isEditMode ? '编辑项目' : '创建新项目' }}</h2>
+    <button mat-icon-button class="close-btn" (click)="onCancel()">
+      <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="18" y1="6" x2="6" y2="18"></line>
+        <line x1="6" y1="6" x2="18" y2="18"></line>
+      </svg>
+    </button>
+  </div>
+
+  <!-- 表单内容 -->
+  <div class="dialog-content">
+    <form class="project-form">
+      <!-- 项目名称 -->
+      <div class="form-group">
+        <mat-form-field appearance="outline" class="form-field">
+          <mat-label>项目名称 *</mat-label>
+          <input matInput type="text" [(ngModel)]="project.name" name="name"
+                 placeholder="请输入项目名称">
+          <mat-icon matSuffix>title</mat-icon>
+        </mat-form-field>
+      </div>
+
+      <!-- 客户信息 -->
+      <div class="form-group">
+        <mat-form-field appearance="outline" class="form-field">
+          <mat-label>客户名称 *</mat-label>
+          <input matInput type="text" [(ngModel)]="project.customer" name="customer"
+                 placeholder="请输入客户名称">
+          <mat-icon matSuffix>person</mat-icon>
+        </mat-form-field>
+      </div>
+
+      <!-- 设计师分配 -->
+      <div class="form-group">
+        <mat-form-field appearance="outline" class="form-field">
+          <mat-label>分配设计师 *</mat-label>
+          <mat-select [(ngModel)]="project.designer" name="designer">
+            <mat-option *ngFor="let designer of designers" [value]="designer">
+              {{ designer }}
+            </mat-option>
+          </mat-select>
+          <mat-icon matSuffix>assignment_ind</mat-icon>
+        </mat-form-field>
+      </div>
+
+      <!-- 项目状态 -->
+      <div class="form-group">
+        <mat-form-field appearance="outline" class="form-field">
+          <mat-label>项目状态</mat-label>
+          <mat-select [(ngModel)]="project.status" name="status">
+            <mat-option *ngFor="let status of statuses" [value]="status.value">
+              {{ status.label }}
+            </mat-option>
+          </mat-select>
+          <mat-icon matSuffix>track_changes</mat-icon>
+        </mat-form-field>
+      </div>
+
+      <!-- 日期选择 -->
+      <div class="form-row">
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>开始日期 *</mat-label>
+            <input matInput [matDatepicker]="startDatePicker" [(ngModel)]="project.startDate" name="startDate"
+                   (dateChange)="onStartDateChange($event.value)">
+            <mat-datepicker-toggle matSuffix [for]="startDatePicker"></mat-datepicker-toggle>
+            <mat-datepicker #startDatePicker></mat-datepicker>
+          </mat-form-field>
+        </div>
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>结束日期 *</mat-label>
+            <input matInput [matDatepicker]="endDatePicker" [(ngModel)]="project.endDate" name="endDate"
+                   (dateChange)="onEndDateChange($event.value)">
+            <mat-datepicker-toggle matSuffix [for]="endDatePicker"></mat-datepicker-toggle>
+            <mat-datepicker #endDatePicker></mat-datepicker>
+          </mat-form-field>
+        </div>
+      </div>
+
+      <!-- 预算和进度 -->
+      <div class="form-row">
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>项目预算 *</mat-label>
+            <input matInput type="number" [(ngModel)]="project.budget" name="budget"
+                   placeholder="请输入项目预算" min="0" step="100">
+            <span matSuffix>¥</span>
+          </mat-form-field>
+        </div>
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>完成进度</mat-label>
+            <input matInput type="number" [(ngModel)]="project.progress" name="progress"
+                   placeholder="请输入完成进度" min="0" max="100" step="5"
+                   (change)="onProgressChange()">
+            <span matSuffix>%</span>
+          </mat-form-field>
+        </div>
+      </div>
+
+      <!-- 进度条显示 -->
+      <div class="progress-display">
+        <div class="progress-header">
+          <span class="progress-label">当前进度: {{ project.progress }}%</span>
+        </div>
+        <div class="progress-bar-container">
+          <div class="progress-bar" [style.width]="project.progress + '%'"></div>
+        </div>
+      </div>
+    </form>
+  </div>
+
+  <!-- 操作按钮 -->
+  <div class="dialog-footer">
+    <button mat-button class="cancel-btn" (click)="onCancel()">
+      取消
+    </button>
+    <button mat-raised-button color="primary" class="save-btn" 
+            [disabled]="!isFormValid()" (click)="onSave()">
+      {{ isEditMode ? '保存修改' : '创建项目' }}
+    </button>
+  </div>
+</div>

+ 250 - 0
src/app/pages/admin/project-management/project-dialog/project-dialog.scss

@@ -0,0 +1,250 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$secondary-color: #6B7280;
+$success-color: #00B42A;
+$warning-color: #FFAA00;
+$error-color: #F53F3F;
+$text-primary: #333333;
+$text-secondary: #666666;
+$text-tertiary: #999999;
+$border-color: #E5E7EB;
+$bg-light: #F9FAFB;
+$bg-white: #FFFFFF;
+$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+
+// 对话框容器
+.project-dialog {
+  min-width: 700px;
+  border-radius: 12px;
+  overflow: hidden;
+  background-color: $bg-white;
+}
+
+// 对话框头部
+.dialog-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px 24px;
+  border-bottom: 1px solid $border-color;
+  background-color: $bg-light;
+
+  .dialog-title {
+    font-size: 18px;
+    font-weight: 600;
+    color: $text-primary;
+    margin: 0;
+  }
+
+  .close-btn {
+    color: $text-tertiary;
+    padding: 4px;
+    border-radius: 6px;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: #E5E7EB;
+      color: $text-secondary;
+    }
+  }
+}
+
+// 对话框内容
+.dialog-content {
+  padding: 24px;
+  max-height: 60vh;
+  overflow-y: auto;
+}
+
+// 表单样式
+.project-form {
+  .form-group {
+    margin-bottom: 20px;
+
+    .form-field {
+      width: 100%;
+      .mat-form-field-wrapper {
+        .mat-form-field-flex {
+          border-radius: 8px;
+        }
+
+        .mat-form-field-outline {
+          border-radius: 8px;
+        }
+
+        .mat-form-field-infix {
+          padding: 10px 0;
+
+          input {
+            font-size: 14px;
+            color: $text-primary;
+          }
+
+          .mat-input-element {
+            &::placeholder {
+              color: $text-tertiary;
+            }
+          }
+        }
+
+        .mat-form-field-label {
+          font-size: 14px;
+          color: $text-secondary;
+        }
+
+        .mat-form-field-suffix {
+          .mat-icon {
+            color: $text-tertiary;
+          }
+          span {
+            color: $text-tertiary;
+            font-size: 14px;
+          }
+        }
+      }
+    }
+  }
+
+  .form-row {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 20px;
+  }
+}
+
+// 进度显示区域
+.progress-display {
+  margin-top: 8px;
+  margin-bottom: 20px;
+
+  .progress-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 8px;
+
+    .progress-label {
+      font-size: 13px;
+      color: $text-secondary;
+      font-weight: 500;
+    }
+  }
+
+  .progress-bar-container {
+    height: 8px;
+    background-color: $border-color;
+    border-radius: 4px;
+    overflow: hidden;
+
+    .progress-bar {
+      height: 100%;
+      background-color: $primary-color;
+      border-radius: 4px;
+      transition: width 0.3s ease;
+    }
+  }
+}
+
+// 对话框底部
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  padding: 20px 24px;
+  border-top: 1px solid $border-color;
+  background-color: $bg-light;
+
+  .cancel-btn {
+    padding: 8px 24px;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    color: $text-secondary;
+    background-color: $bg-white;
+    border: 1px solid $border-color;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: #F3F4F6;
+      border-color: $text-tertiary;
+    }
+  }
+
+  .save-btn {
+    padding: 8px 24px;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    background-color: $primary-color;
+    color: $bg-white;
+    transition: all 0.2s ease;
+    box-shadow: $shadow-sm;
+
+    &:hover:not(:disabled) {
+      background-color: #0E4BD8;
+      box-shadow: $shadow-md;
+      transform: translateY(-1px);
+    }
+
+    &:disabled {
+      opacity: 0.5;
+      cursor: not-allowed;
+      transform: none;
+      box-shadow: none;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .project-dialog {
+    min-width: auto;
+    width: 90vw;
+    max-width: 600px;
+  }
+
+  .form-row {
+    grid-template-columns: 1fr !important;
+  }
+}
+
+@media (max-width: 480px) {
+  .dialog-header,
+  .dialog-content,
+  .dialog-footer {
+    padding: 16px;
+  }
+
+  .dialog-title {
+    font-size: 16px !important;
+  }
+
+  .cancel-btn,
+  .save-btn {
+    padding: 6px 16px !important;
+    font-size: 13px !important;
+  }
+}
+
+// 滚动条样式
+.dialog-content {
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: $bg-light;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: $border-color;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb:hover {
+    background: $text-tertiary;
+  }
+}

+ 121 - 0
src/app/pages/admin/project-management/project-dialog/project-dialog.ts

@@ -0,0 +1,121 @@
+import { Component, Inject } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
+import { MatDatepickerModule } from '@angular/material/datepicker';
+import { MatNativeDateModule } from '@angular/material/core';
+import { MatIconModule } from '@angular/material/icon';
+
+interface Project {
+  id: string;
+  name: string;
+  customer: string;
+  status: 'pending' | 'in-progress' | 'completed' | 'on-hold';
+  designer: string;
+  startDate: string;
+  endDate: string;
+  budget: number;
+  progress: number;
+}
+
+@Component({
+  selector: 'app-project-dialog',
+  standalone: true,
+  imports: [
+    CommonModule,
+    FormsModule,
+    MatButtonModule,
+    MatDialogModule,
+    MatFormFieldModule,
+    MatInputModule,
+    MatSelectModule,
+    MatDatepickerModule,
+    MatNativeDateModule,
+    MatIconModule
+  ],
+  templateUrl: './project-dialog.html',
+  styleUrl: './project-dialog.scss'
+})
+export class ProjectDialogComponent {
+  project: Project;
+  isEditMode: boolean;
+  designers: string[] = ['张设计师', '刘设计师', '王设计师', '陈设计师', '杨设计师'];
+  statuses: { value: string; label: string }[] = [
+    { value: 'pending', label: '待开始' },
+    { value: 'in-progress', label: '进行中' },
+    { value: 'completed', label: '已完成' },
+    { value: 'on-hold', label: '暂停中' }
+  ];
+
+  constructor(
+    public dialogRef: MatDialogRef<ProjectDialogComponent>,
+    @Inject(MAT_DIALOG_DATA) public data: Project
+  ) {
+    this.isEditMode = !!data.id;
+    this.project = {
+      id: data.id || '',
+      name: data.name || '',
+      customer: data.customer || '',
+      status: data.status || 'pending',
+      designer: data.designer || '',
+      startDate: data.startDate || '',
+      endDate: data.endDate || '',
+      budget: data.budget || 0,
+      progress: data.progress || 0
+    };
+  }
+
+  onCancel(): void {
+    this.dialogRef.close();
+  }
+
+  onSave(): void {
+    if (this.isFormValid()) {
+      this.dialogRef.close(this.project);
+    }
+  }
+
+  isFormValid(): boolean {
+    return this.project.name.trim() !== '' &&
+           this.project.customer.trim() !== '' &&
+           this.project.designer.trim() !== '' &&
+           this.project.startDate !== '' &&
+           this.project.endDate !== '' &&
+           this.project.budget > 0 &&
+           this.project.progress >= 0 &&
+           this.project.progress <= 100;
+  }
+
+  onStartDateChange(date: Date): void {
+    if (date) {
+      this.project.startDate = date.toISOString().split('T')[0];
+    } else {
+      this.project.startDate = '';
+    }
+  }
+
+  onEndDateChange(date: Date): void {
+    if (date) {
+      this.project.endDate = date.toISOString().split('T')[0];
+    } else {
+      this.project.endDate = '';
+    }
+  }
+
+  onProgressChange(): void {
+    // 确保进度在0-100之间
+    this.project.progress = Math.max(0, Math.min(100, this.project.progress));
+  }
+
+  formatCurrency(amount: number): string {
+    return new Intl.NumberFormat('zh-CN', {
+      style: 'currency',
+      currency: 'CNY',
+      minimumFractionDigits: 0
+    }).format(amount);
+  }
+}

+ 324 - 0
src/app/pages/admin/project-management/project-management.html

@@ -0,0 +1,324 @@
+<div class="project-management">
+  <!-- 页面标题和操作按钮 -->
+  <div class="page-header">
+    <div class="header-left">
+      <h2 class="page-title">项目管理</h2>
+      <p class="page-description">管理所有项目的创建、分配和状态</p>
+    </div>
+    <div class="header-right">
+      <button mat-raised-button color="primary" class="create-btn"
+              (click)="openProjectDialog()">
+        <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>
+    </div>
+  </div>
+
+  <!-- 搜索和筛选区域 -->
+  <div class="search-filter-section">
+    <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>
+        <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+      </svg>
+      <input matInput type="text" placeholder="搜索项目名称、客户或设计师..."
+             [(ngModel)]="searchTerm" (keyup.enter)="onSearch()"
+             class="search-input">
+      <button mat-button *ngIf="searchTerm" class="clear-search-btn"
+              (click)="searchTerm = ''; onSearch()">
+        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+          <line x1="18" y1="6" x2="6" y2="18"></line>
+          <line x1="6" y1="6" x2="18" y2="18"></line>
+        </svg>
+      </button>
+    </div>
+    
+    <div class="filter-controls">
+      <div class="status-filter-wrapper">
+        <mat-select [(ngModel)]="statusFilter" (selectionChange)="onStatusFilterChange()"
+                   placeholder="项目状态" class="status-filter">
+          <mat-option value="">全部状态</mat-option>
+          <mat-option value="pending">待开始</mat-option>
+          <mat-option value="in-progress">进行中</mat-option>
+          <mat-option value="completed">已完成</mat-option>
+          <mat-option value="on-hold">暂停中</mat-option>
+        </mat-select>
+      </div>
+      
+      <button mat-button class="filter-btn" (click)="onSearch()">
+        <svg 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>
+          <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+        </svg>
+        筛选
+      </button>
+    </div>
+  </div>
+
+  <!-- 项目统计卡片 -->
+  <div class="stats-cards">
+    <div class="stat-card">
+      <div class="stat-value">{{ projects().length }}</div>
+      <div class="stat-label">总项目数</div>
+    </div>
+    <div class="stat-card">
+      <div class="stat-value">{{ projects().filter(p => p.status === 'in-progress').length }}</div>
+      <div class="stat-label">进行中</div>
+    </div>
+    <div class="stat-card">
+      <div class="stat-value">{{ projects().filter(p => p.status === 'completed').length }}</div>
+      <div class="stat-label">已完成</div>
+    </div>
+    <div class="stat-card">
+      <div class="stat-value">{{ projects().filter(p => p.status === 'pending').length }}</div>
+      <div class="stat-label">待开始</div>
+    </div>
+    <div class="stat-card">
+      <div class="stat-value">{{ formatCurrency(projects().reduce((sum, p) => sum + p.budget, 0)) }}</div>
+      <div class="stat-label">总预算</div>
+    </div>
+  </div>
+
+  <!-- 项目列表表格 -->
+  <div class="project-table-container">
+    <table mat-table [dataSource]="paginatedProjects" class="project-table">
+      <!-- 项目名称列 -->
+      <ng-container matColumnDef="name">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('name')">
+          <div class="header-content">
+            <span>项目名称</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'name'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let project" class="table-cell">
+          <div class="project-name">
+            <a [routerLink]="['/admin/project-detail', project.id]">{{ project.name }}</a>
+          </div>
+        </td>
+      </ng-container>
+
+      <!-- 客户列 -->
+      <ng-container matColumnDef="customer">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('customer')">
+          <div class="header-content">
+            <span>客户</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'customer'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let project" class="table-cell">
+          {{ project.customer }}
+        </td>
+      </ng-container>
+
+      <!-- 设计师列 -->
+      <ng-container matColumnDef="designer">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('designer')">
+          <div class="header-content">
+            <span>设计师</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'designer'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let project" class="table-cell">
+          {{ project.designer }}
+        </td>
+      </ng-container>
+
+      <!-- 状态列 -->
+      <ng-container matColumnDef="status">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('status')">
+          <div class="header-content">
+            <span>状态</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'status'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let project" class="table-cell">
+          <div class="status-badge" [style.backgroundColor]="statusColors[project.status]">
+            {{ statusTexts[project.status] }}
+          </div>
+        </td>
+      </ng-container>
+
+      <!-- 进度列 -->
+      <ng-container matColumnDef="progress">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('progress')">
+          <div class="header-content">
+            <span>进度</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'progress'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let project" class="table-cell">
+          <div class="progress-container">
+            <div class="progress-bar" [style.width]="project.progress + '%'" [style.backgroundColor]="statusColors[project.status]"></div>
+            <span class="progress-text">{{ project.progress }}%</span>
+          </div>
+        </td>
+      </ng-container>
+
+      <!-- 预算列 -->
+      <ng-container matColumnDef="budget">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('budget')">
+          <div class="header-content">
+            <span>预算</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'budget'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let project" class="table-cell">
+          {{ formatCurrency(project.budget) }}
+        </td>
+      </ng-container>
+
+      <!-- 开始日期列 -->
+      <ng-container matColumnDef="startDate">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('startDate')">
+          <div class="header-content">
+            <span>开始日期</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'startDate'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let project" class="table-cell">
+          {{ project.startDate }}
+        </td>
+      </ng-container>
+
+      <!-- 结束日期列 -->
+      <ng-container matColumnDef="endDate">
+        <th mat-header-cell *matHeaderCellDef class="table-header"
+            (click)="onSort('endDate')">
+          <div class="header-content">
+            <span>结束日期</span>
+            <div class="sort-icon">
+              <svg *ngIf="sortColumn === 'endDate'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+              </svg>
+            </div>
+          </div>
+        </th>
+        <td mat-cell *matCellDef="let project" class="table-cell">
+          {{ project.endDate }}
+        </td>
+      </ng-container>
+
+      <!-- 操作列 -->
+      <ng-container matColumnDef="actions">
+        <th mat-header-cell *matHeaderCellDef class="table-header">操作</th>
+        <td mat-cell *matCellDef="let project" class="table-cell">
+          <div class="action-buttons">
+            <button mat-icon-button class="action-btn" color="primary"
+                    title="查看详情"
+                    [routerLink]="['/admin/project-detail', project.id]">
+              <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
+                <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
+              </svg>
+            </button>
+            <button mat-icon-button class="action-btn" color="accent"
+                    title="编辑项目"
+                    (click)="openProjectDialog(project)">
+              <svg width="20" height="20" 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)="deleteProject(project.id)">
+              <svg width="20" height="20" 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>
+        </td>
+      </ng-container>
+
+      <tr mat-header-row *matHeaderRowDef="['name', 'customer', 'designer', 'status', 'progress', 'budget', 'startDate', 'endDate', 'actions']"></tr>
+      <tr mat-row *matRowDef="let row; columns: ['name', 'customer', 'designer', 'status', 'progress', 'budget', 'startDate', 'endDate', 'actions']"></tr>
+    </table>
+
+    <!-- 无数据状态 -->
+    <div *ngIf="paginatedProjects.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">
+        <line x1="8" y1="6" x2="21" y2="6"></line>
+        <line x1="8" y1="12" x2="21" y2="12"></line>
+        <line x1="8" y1="18" x2="21" y2="18"></line>
+        <line x1="3" y1="6" x2="3.01" y2="6"></line>
+        <line x1="3" y1="12" x2="3.01" y2="12"></line>
+        <line x1="3" y1="18" x2="3.01" y2="18"></line>
+      </svg>
+      <p>没有找到符合条件的项目</p>
+      <button mat-button color="primary" (click)="searchTerm = ''; statusFilter = ''; onSearch()">
+        清除筛选条件
+      </button>
+    </div>
+  </div>
+
+  <!-- 分页控件 -->
+  <div class="pagination-controls">
+    <div class="pagination-info">
+      显示 {{ Math.min(currentPage * pageSize + 1, filteredProjects().length) }} - 
+      {{ Math.min((currentPage + 1) * pageSize, filteredProjects().length) }} 共 
+      {{ filteredProjects().length }} 项
+    </div>
+    <div class="pagination-buttons">
+      <button mat-button [disabled]="currentPage === 0" (click)="onPageChange(0)">
+        首页
+      </button>
+      <button mat-button [disabled]="currentPage === 0" (click)="onPageChange(currentPage - 1)">
+        上一页
+      </button>
+      
+      <!-- 页码按钮 -->
+      <div class="page-numbers">
+        <button mat-button *ngFor="let page of getPageNumbers()" [class.active]="page === currentPage"
+                (click)="onPageChange(page)">
+          {{ page + 1 }}
+        </button>
+      </div>
+      
+      <button mat-button [disabled]="currentPage === totalPages - 1" (click)="onPageChange(currentPage + 1)">
+        下一页
+      </button>
+      <button mat-button [disabled]="currentPage === totalPages - 1" (click)="onPageChange(totalPages - 1)">
+        末页
+      </button>
+    </div>
+  </div>
+</div>

+ 496 - 0
src/app/pages/admin/project-management/project-management.scss

@@ -0,0 +1,496 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$secondary-color: #6B7280;
+$success-color: #00B42A;
+$warning-color: #FFAA00;
+$error-color: #F53F3F;
+$text-primary: #333333;
+$text-secondary: #666666;
+$text-tertiary: #999999;
+$border-color: #E5E7EB;
+$bg-light: #F9FAFB;
+$bg-white: #FFFFFF;
+$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+
+// 主容器
+.project-management {
+  padding: 24px;
+  min-height: 100vh;
+  background-color: $bg-light;
+}
+
+// 页面标题区域
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid $border-color;
+
+  .header-left {
+    .page-title {
+      font-size: 24px;
+      font-weight: 600;
+      color: $text-primary;
+      margin: 0 0 4px 0;
+    }
+
+    .page-description {
+      font-size: 14px;
+      color: $text-tertiary;
+      margin: 0;
+    }
+  }
+
+  .header-right {
+    .create-btn {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      background-color: $primary-color;
+      color: $bg-white;
+      border-radius: 8px;
+      padding: 8px 16px;
+      font-size: 14px;
+      font-weight: 500;
+      transition: all 0.2s ease;
+      box-shadow: $shadow-sm;
+
+      &:hover {
+        background-color: #0E4BD8;
+        box-shadow: $shadow-md;
+        transform: translateY(-1px);
+      }
+    }
+  }
+}
+
+// 搜索和筛选区域
+.search-filter-section {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 24px;
+  flex-wrap: wrap;
+
+  .search-input-wrapper {
+    flex: 1;
+    min-width: 300px;
+    position: relative;
+    display: flex;
+    align-items: center;
+
+    .search-icon {
+      position: absolute;
+      left: 12px;
+      color: $text-tertiary;
+      z-index: 1;
+    }
+
+    .search-input {
+      width: 100%;
+      padding: 10px 12px 10px 40px;
+      border: 1px solid $border-color;
+      border-radius: 8px;
+      font-size: 14px;
+      color: $text-primary;
+      background-color: $bg-white;
+      transition: all 0.2s ease;
+      box-shadow: $shadow-sm;
+
+      &:focus {
+        outline: none;
+        border-color: $primary-color;
+        box-shadow: 0 0 0 3px rgba(22, 93, 255, 0.1);
+      }
+
+      &::placeholder {
+        color: $text-tertiary;
+      }
+    }
+
+    .clear-search-btn {
+      position: absolute;
+      right: 8px;
+      color: $text-tertiary;
+      padding: 4px;
+    }
+  }
+
+  .filter-controls {
+    display: flex;
+    gap: 12px;
+    align-items: center;
+
+    .status-filter-wrapper {
+      .status-filter {
+        min-width: 160px;
+        border: 1px solid $border-color;
+        border-radius: 8px;
+        background-color: $bg-white;
+        box-shadow: $shadow-sm;
+
+        .mat-select-trigger {
+          height: 40px;
+          font-size: 14px;
+          color: $text-primary;
+        }
+      }
+    }
+
+    .filter-btn {
+      display: flex;
+      align-items: center;
+      gap: 6px;
+      color: $primary-color;
+      border: 1px solid $primary-color;
+      border-radius: 8px;
+      padding: 8px 16px;
+      font-size: 14px;
+      font-weight: 500;
+      background-color: $bg-white;
+      transition: all 0.2s ease;
+
+      &:hover {
+        background-color: $primary-color;
+        color: $bg-white;
+      }
+    }
+  }
+}
+
+// 统计卡片区域
+.stats-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+  gap: 16px;
+  margin-bottom: 24px;
+
+  .stat-card {
+    background-color: $bg-white;
+    border-radius: 12px;
+    padding: 20px;
+    box-shadow: $shadow-sm;
+    text-align: center;
+    transition: all 0.2s ease;
+
+    &:hover {
+      box-shadow: $shadow-md;
+      transform: translateY(-2px);
+    }
+
+    .stat-value {
+      font-size: 28px;
+      font-weight: 700;
+      color: $text-primary;
+      margin-bottom: 4px;
+    }
+
+    .stat-label {
+      font-size: 14px;
+      color: $text-tertiary;
+    }
+  }
+}
+
+// 项目表格容器
+.project-table-container {
+  background-color: $bg-white;
+  border-radius: 12px;
+  box-shadow: $shadow-sm;
+  overflow: hidden;
+  margin-bottom: 24px;
+
+  .project-table {
+    width: 100%;
+    border-collapse: collapse;
+
+    .table-header {
+      background-color: $bg-light;
+      font-weight: 600;
+      color: $text-secondary;
+      font-size: 13px;
+      text-align: left;
+      padding: 16px 20px;
+      border-bottom: 1px solid $border-color;
+      cursor: pointer;
+      transition: background-color 0.2s ease;
+
+      &:hover {
+        background-color: #F3F4F6;
+      }
+
+      .header-content {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+      }
+
+      .sort-icon {
+        opacity: 0.5;
+      }
+    }
+
+    .table-cell {
+      padding: 16px 20px;
+      border-bottom: 1px solid $border-color;
+      font-size: 14px;
+      color: $text-primary;
+      vertical-align: middle;
+
+      &:last-child {
+        text-align: right;
+      }
+
+      .project-name {
+        a {
+          color: $primary-color;
+          text-decoration: none;
+          font-weight: 500;
+          transition: color 0.2s ease;
+
+          &:hover {
+            color: #0E4BD8;
+            text-decoration: underline;
+          }
+        }
+      }
+
+      .status-badge {
+        display: inline-block;
+        padding: 4px 12px;
+        border-radius: 16px;
+        font-size: 12px;
+        font-weight: 500;
+        color: $bg-white;
+        text-align: center;
+      }
+
+      .progress-container {
+        display: flex;
+        align-items: center;
+        gap: 12px;
+
+        .progress-bar {
+          flex: 1;
+          height: 8px;
+          border-radius: 4px;
+          background-color: $border-color;
+          overflow: hidden;
+          position: relative;
+
+          &::before {
+            content: '';
+            position: absolute;
+            top: 0;
+            left: 0;
+            height: 100%;
+            border-radius: 4px;
+            transition: width 0.3s ease;
+          }
+        }
+
+        .progress-text {
+          min-width: 40px;
+          font-size: 13px;
+          color: $text-secondary;
+          font-weight: 500;
+        }
+      }
+
+      .action-buttons {
+        display: flex;
+        gap: 8px;
+        justify-content: flex-end;
+
+        .action-btn {
+          width: 36px;
+          height: 36px;
+          border-radius: 6px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          transition: all 0.2s ease;
+          background-color: transparent;
+          border: 1px solid $border-color;
+          color: $text-secondary;
+
+          &:hover {
+            border-color: currentColor;
+            transform: translateY(-1px);
+          }
+
+          &.mat-primary {
+            color: $primary-color;
+          }
+
+          &.mat-accent {
+            color: $warning-color;
+          }
+
+          &.mat-warn {
+            color: $error-color;
+          }
+        }
+      }
+    }
+
+    tr:last-child .table-cell {
+      border-bottom: none;
+    }
+
+    tr:hover .table-cell {
+      background-color: $bg-light;
+    }
+  }
+
+  // 空状态
+  .empty-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 60px 20px;
+    text-align: center;
+
+    svg {
+      margin-bottom: 16px;
+      opacity: 0.5;
+    }
+
+    p {
+      font-size: 16px;
+      color: $text-tertiary;
+      margin-bottom: 24px;
+    }
+
+    button {
+      border-radius: 8px;
+      padding: 8px 24px;
+      font-size: 14px;
+      font-weight: 500;
+    }
+  }
+}
+
+// 分页控件
+.pagination-controls {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 20px;
+  background-color: $bg-white;
+  border-radius: 12px;
+  box-shadow: $shadow-sm;
+
+  .pagination-info {
+    font-size: 14px;
+    color: $text-tertiary;
+  }
+
+  .pagination-buttons {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+
+    button {
+      padding: 6px 12px;
+      border-radius: 6px;
+      font-size: 13px;
+      font-weight: 500;
+      transition: all 0.2s ease;
+      min-width: auto;
+
+      &:disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+      }
+
+      &.active {
+        background-color: $primary-color;
+        color: $bg-white;
+      }
+    }
+
+    .page-numbers {
+      display: flex;
+      gap: 4px;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .project-table {
+    .table-header,
+    .table-cell {
+      padding: 12px 16px;
+    }
+  }
+}
+
+@media (max-width: 992px) {
+  .project-management {
+    padding: 16px;
+  }
+
+  .stats-cards {
+    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+  }
+
+  .search-filter-section {
+    flex-direction: column;
+    gap: 12px;
+  }
+
+  .search-input-wrapper {
+    min-width: 100% !important;
+  }
+
+  .pagination-controls {
+    flex-direction: column;
+    gap: 16px;
+
+    .pagination-buttons {
+      flex-wrap: wrap;
+      justify-content: center;
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .page-header {
+    flex-direction: column;
+    gap: 16px;
+    align-items: flex-start;
+  }
+
+  .stats-cards {
+    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
+    gap: 12px;
+  }
+
+  .stat-card {
+    padding: 16px;
+
+    .stat-value {
+      font-size: 24px;
+    }
+  }
+
+  .project-table-container {
+    overflow-x: auto;
+  }
+}
+
+@media (max-width: 480px) {
+  .project-management {
+    padding: 12px;
+  }
+
+  .stats-cards {
+    grid-template-columns: 1fr 1fr;
+  }
+}

+ 327 - 0
src/app/pages/admin/project-management/project-management.ts

@@ -0,0 +1,327 @@
+import { Component, OnInit, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { FormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { MatTableModule } from '@angular/material/table';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatDialogModule, MatDialog } from '@angular/material/dialog';
+import { MatSortModule } from '@angular/material/sort';
+import { ProjectDialogComponent } from './project-dialog/project-dialog';
+
+interface Project {
+  id: string;
+  name: string;
+  customer: string;
+  status: 'pending' | 'in-progress' | 'completed' | 'on-hold';
+  designer: string;
+  startDate: string;
+  endDate: string;
+  budget: number;
+  progress: number;
+}
+
+@Component({
+  selector: 'app-project-management',
+  standalone: true,
+  imports: [
+    CommonModule,
+    RouterModule,
+    FormsModule,
+    MatButtonModule,
+    MatIconModule,
+    MatTableModule,
+    MatInputModule,
+    MatSelectModule,
+    MatPaginatorModule,
+    MatDialogModule,
+    MatSortModule,
+    ProjectDialogComponent
+  ],
+  templateUrl: './project-management.html',
+  styleUrl: './project-management.scss'
+})
+export class ProjectManagement implements OnInit {
+  projects = signal<Project[]>([]);
+  filteredProjects = signal<Project[]>([]);
+  searchTerm = '';
+  statusFilter = '';
+  sortColumn = 'startDate';
+  sortDirection = 'desc';
+  pageSize = 10;
+  currentPage = 0;
+
+  // 状态颜色映射
+  statusColors: Record<string, string> = {
+    'pending': '#FFAA00',
+    'in-progress': '#165DFF',
+    'completed': '#00B42A',
+    'on-hold': '#F53F3F'
+  };
+
+  // 状态文本映射
+  statusTexts: Record<string, string> = {
+    'pending': '待开始',
+    'in-progress': '进行中',
+    'completed': '已完成',
+    'on-hold': '暂停中'
+  };
+
+  constructor(private dialog: MatDialog) {}
+
+  ngOnInit(): void {
+    this.loadProjects();
+  }
+
+  loadProjects(): void {
+    // 模拟项目数据
+    this.projects.set([
+      {
+        id: '1',
+        name: '现代简约风格三居室设计',
+        customer: '王先生',
+        status: 'in-progress',
+        designer: '张设计师',
+        startDate: '2025-09-01',
+        endDate: '2025-10-15',
+        budget: 85000,
+        progress: 65
+      },
+      {
+        id: '2',
+        name: '北欧风格两居室设计',
+        customer: '李女士',
+        status: 'completed',
+        designer: '刘设计师',
+        startDate: '2025-08-15',
+        endDate: '2025-09-30',
+        budget: 68000,
+        progress: 100
+      },
+      {
+        id: '3',
+        name: '工业风办公室设计',
+        customer: '赵先生',
+        status: 'pending',
+        designer: '王设计师',
+        startDate: '2025-09-15',
+        endDate: '2025-11-05',
+        budget: 120000,
+        progress: 0
+      },
+      {
+        id: '4',
+        name: '新中式别墅设计',
+        customer: '钱女士',
+        status: 'on-hold',
+        designer: '张设计师',
+        startDate: '2025-08-20',
+        endDate: '2025-11-20',
+        budget: 250000,
+        progress: 40
+      },
+      {
+        id: '5',
+        name: '日式小户型设计',
+        customer: '孙先生',
+        status: 'in-progress',
+        designer: '刘设计师',
+        startDate: '2025-09-05',
+        endDate: '2025-10-20',
+        budget: 55000,
+        progress: 35
+      },
+      {
+        id: '6',
+        name: '美式乡村风格设计',
+        customer: '周女士',
+        status: 'in-progress',
+        designer: '王设计师',
+        startDate: '2025-09-08',
+        endDate: '2025-10-30',
+        budget: 75000,
+        progress: 25
+      },
+      {
+        id: '7',
+        name: '极简主义公寓设计',
+        customer: '吴先生',
+        status: 'pending',
+        designer: '张设计师',
+        startDate: '2025-09-20',
+        endDate: '2025-11-10',
+        budget: 60000,
+        progress: 0
+      },
+      {
+        id: '8',
+        name: '地中海风格别墅设计',
+        customer: '郑女士',
+        status: 'completed',
+        designer: '刘设计师',
+        startDate: '2025-08-01',
+        endDate: '2025-09-15',
+        budget: 180000,
+        progress: 100
+      },
+      {
+        id: '9',
+        name: '后现代风格办公室设计',
+        customer: '王总',
+        status: 'in-progress',
+        designer: '王设计师',
+        startDate: '2025-08-25',
+        endDate: '2025-10-15',
+        budget: 95000,
+        progress: 50
+      },
+      {
+        id: '10',
+        name: '新古典主义住宅设计',
+        customer: '陈女士',
+        status: 'on-hold',
+        designer: '张设计师',
+        startDate: '2025-08-10',
+        endDate: '2025-11-01',
+        budget: 130000,
+        progress: 30
+      }
+    ]);
+    
+    this.applyFilters();
+  }
+
+  applyFilters(): void {
+    let result = [...this.projects()];
+    
+    // 搜索过滤
+    if (this.searchTerm) {
+      const term = this.searchTerm.toLowerCase();
+      result = result.filter(project => 
+        project.name.toLowerCase().includes(term) ||
+        project.customer.toLowerCase().includes(term) ||
+        project.designer.toLowerCase().includes(term)
+      );
+    }
+    
+    // 状态过滤
+    if (this.statusFilter) {
+      result = result.filter(project => project.status === this.statusFilter);
+    }
+    
+    // 排序
+    result.sort((a, b) => {
+      if (this.sortColumn === 'startDate' || this.sortColumn === 'endDate') {
+        const dateA = new Date(a[this.sortColumn]).getTime();
+        const dateB = new Date(b[this.sortColumn]).getTime();
+        return this.sortDirection === 'asc' ? dateA - dateB : dateB - dateA;
+      } else if (this.sortColumn === 'budget' || this.sortColumn === 'progress') {
+        return this.sortDirection === 'asc' ? a[this.sortColumn] - b[this.sortColumn] : b[this.sortColumn] - a[this.sortColumn];
+      } else {
+        const valueA = a[this.sortColumn as keyof Project]?.toString().toLowerCase() || '';
+        const valueB = b[this.sortColumn as keyof Project]?.toString().toLowerCase() || '';
+        return this.sortDirection === 'asc' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA);
+      }
+    });
+    
+    this.filteredProjects.set(result);
+  }
+
+  onSearch(): void {
+    this.applyFilters();
+  }
+
+  onStatusFilterChange(): void {
+    this.applyFilters();
+  }
+
+  onSort(column: string): void {
+    if (this.sortColumn === column) {
+      this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
+    } else {
+      this.sortColumn = column;
+      this.sortDirection = 'asc';
+    }
+    this.applyFilters();
+  }
+
+  openProjectDialog(project?: Project): void {
+    const dialogRef = this.dialog.open(ProjectDialogComponent, {
+      width: '600px',
+      data: project ? { ...project } : {
+        id: '',
+        name: '',
+        client: '',
+        designer: '',
+        status: 'pending',
+        priority: 'medium',
+        startDate: new Date().toISOString().split('T')[0],
+        endDate: '',
+        budget: 0,
+        progress: 0,
+        description: '',
+        tasks: []
+      }
+    });
+
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        if (result.id) {
+          // 更新项目
+          this.updateProject(result);
+        } else {
+          // 创建新项目
+          this.createProject(result);
+        }
+      }
+    });
+  }
+
+  createProject(projectData: Omit<Project, 'id'>): void {
+    const newProject: Project = {
+      ...projectData,
+      id: (this.projects().length + 1).toString()
+    };
+    
+    this.projects.set([newProject, ...this.projects()]);
+    this.applyFilters();
+  }
+
+  updateProject(updatedProject: Project): void {
+    this.projects.set(this.projects().map(project => 
+      project.id === updatedProject.id ? updatedProject : project
+    ));
+    this.applyFilters();
+  }
+
+  deleteProject(id: string): void {
+    if (confirm('确定要删除这个项目吗?')) {
+      this.projects.set(this.projects().filter(project => project.id !== id));
+      this.applyFilters();
+    }
+  }
+
+  formatCurrency(amount: number): string {
+    return new Intl.NumberFormat('zh-CN', {
+      style: 'currency',
+      currency: 'CNY',
+      minimumFractionDigits: 0
+    }).format(amount);
+  }
+
+  get paginatedProjects(): Project[] {
+    const startIndex = this.currentPage * this.pageSize;
+    return this.filteredProjects().slice(startIndex, startIndex + this.pageSize);
+  }
+
+  get totalPages(): number {
+    return Math.ceil(this.filteredProjects().length / this.pageSize);
+  }
+
+  onPageChange(page: number): void {
+    this.currentPage = page;
+  }
+}

+ 229 - 0
src/app/pages/admin/system-settings/setting-dialog/setting-dialog.html

@@ -0,0 +1,229 @@
+<div class="setting-dialog">
+  <!-- 对话框标题 -->
+  <div class="dialog-header">
+    <h2 class="dialog-title">{{ dialogTitle }}</h2>
+    <button mat-icon-button class="close-btn" (click)="onCancel()">
+      <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="18" y1="6" x2="6" y2="18"></line>
+        <line x1="6" y1="6" x2="18" y2="18"></line>
+      </svg>
+    </button>
+  </div>
+
+  <!-- 表单内容 -->
+  <div class="dialog-content">
+    <form class="setting-form">
+      <!-- 基本信息 -->
+      <div class="form-section">
+        <h3 class="section-title">基本信息</h3>
+        
+        <!-- 名称 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>名称 *</mat-label>
+            <input matInput type="text" [(ngModel)]="item.name" name="name"
+                   placeholder="请输入名称">
+            <mat-icon matSuffix *ngIf="type === 'workflow'">track_changes</mat-icon>
+            <mat-icon matSuffix *ngIf="type === 'sop'">playlist_add_check</mat-icon>
+            <mat-icon matSuffix *ngIf="type === 'pricing'">attach_money</mat-icon>
+            <mat-icon matSuffix *ngIf="type === 'performance'">bar_chart</mat-icon>
+          </mat-form-field>
+        </div>
+
+        <!-- 描述 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>描述 *</mat-label>
+            <textarea matInput [(ngModel)]="item.description" name="description"
+                      placeholder="请输入描述" rows="3"></textarea>
+            <mat-icon matSuffix>description</mat-icon>
+          </mat-form-field>
+        </div>
+
+        <!-- 激活状态 -->
+        <div class="form-group">
+          <div class="toggle-wrapper">
+            <mat-slide-toggle
+              [(ngModel)]="item.isActive"
+              name="isActive"
+              class="active-toggle">
+              <span class="toggle-label">启用</span>
+            </mat-slide-toggle>
+          </div>
+        </div>
+      </div>
+
+      <!-- 类型特定信息 -->
+      <div class="form-section" *ngIf="type === 'workflow'">
+        <h3 class="section-title">阶段配置</h3>
+        
+        <!-- 阶段顺序 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>阶段顺序 *</mat-label>
+            <input matInput type="number" [(ngModel)]="(item as WorkflowStage).order" name="order"
+                   placeholder="请输入阶段顺序" min="1" step="1">
+            <mat-icon matSuffix>sort</mat-icon>
+            <mat-hint>数值越小,阶段越靠前</mat-hint>
+          </mat-form-field>
+        </div>
+      </div>
+
+      <div class="form-section" *ngIf="type === 'sop'">
+        <h3 class="section-title">模板配置</h3>
+        
+        <!-- 模板分类 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>模板分类 *</mat-label>
+            <mat-select [(ngModel)]="(item as SOPTemplate).category" name="category">
+              <mat-option *ngFor="let category of categories" [value]="category">
+                {{ category }}
+              </mat-option>
+            </mat-select>
+            <mat-icon matSuffix>category</mat-icon>
+          </mat-form-field>
+        </div>
+
+        <!-- 流程步骤 -->
+        <div class="form-group">
+          <div class="steps-container">
+            <div class="steps-header">
+              <span class="steps-label">流程步骤 *</span>
+              <div class="step-input-wrapper">
+                <mat-form-field appearance="outline" class="step-input">
+                  <input matInput type="text" [(ngModel)]="currentStep" 
+                         placeholder="输入步骤名称">
+                </mat-form-field>
+                <button mat-button color="primary" class="add-step-btn"
+                        (click)="addStep()">
+                  <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>
+              </div>
+            </div>
+            
+            <!-- 步骤列表 -->
+            <div class="steps-list">
+              <div *ngFor="let step of (item as SOPTemplate).steps; let i = index" class="step-item">
+                <div class="step-number">{{ i + 1 }}</div>
+                <div class="step-name">{{ step }}</div>
+                <div class="step-actions">
+                  <button mat-icon-button class="action-btn" color="primary"
+                          title="上移"
+                          (click)="moveStep(i, i - 1)"
+                          [disabled]="i === 0">
+                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                      <polyline points="18 15 12 9 6 15"></polyline>
+                    </svg>
+                  </button>
+                  <button mat-icon-button class="action-btn" color="primary"
+                          title="下移"
+                          (click)="moveStep(i, i + 1)"
+                          [disabled]="i === (item as SOPTemplate).steps.length - 1">
+                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                      <polyline points="6 9 12 15 18 9"></polyline>
+                    </svg>
+                  </button>
+                  <button mat-icon-button class="action-btn" color="warn"
+                          title="删除"
+                          (click)="removeStep(i)">
+                    <svg width="16" height="16" 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>
+              
+              <!-- 空步骤提示 -->
+              <div *ngIf="(item as SOPTemplate).steps.length === 0" class="empty-steps">
+                <p>暂无流程步骤,请点击上方添加按钮添加步骤</p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div class="form-section" *ngIf="type === 'pricing'">
+        <h3 class="section-title">报价配置</h3>
+        
+        <!-- 报价分类 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>报价分类 *</mat-label>
+            <mat-select [(ngModel)]="pricingItem.category" name="category">
+              <mat-option *ngFor="let category of categories" [value]="category">
+                {{ category }}
+              </mat-option>
+            </mat-select>
+            <mat-icon matSuffix>category</mat-icon>
+          </mat-form-field>
+        </div>
+
+        <!-- 计算公式 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>计算公式 *</mat-label>
+            <input matInput type="text" [(ngModel)]="pricingItem.formula" name="formula"
+                   placeholder="例如:面积 * 单价 + 服务费">
+            <mat-icon matSuffix>functions</mat-icon>
+            <mat-hint>使用简单的数学表达式表示报价计算规则</mat-hint>
+          </mat-form-field>
+        </div>
+      </div>
+
+      <div class="form-section" *ngIf="type === 'performance'">
+        <h3 class="section-title">绩效配置</h3>
+        
+        <!-- 绩效指标 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>绩效指标 *</mat-label>
+            <mat-select [(ngModel)]="performanceItem.metric" name="metric">
+              <mat-option *ngFor="let metric of metrics" [value]="metric">
+                {{ metric }}
+              </mat-option>
+            </mat-select>
+            <mat-icon matSuffix>bar_chart</mat-icon>
+          </mat-form-field>
+        </div>
+
+        <!-- 阈值 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>阈值 (%) *</mat-label>
+            <input matInput type="number" [(ngModel)]="performanceItem.threshold" name="threshold"
+                   placeholder="请输入阈值" min="0" max="100" step="1">
+            <span matSuffix>%</span>
+            <mat-hint>达到该百分比时触发奖励</mat-hint>
+          </mat-form-field>
+        </div>
+
+        <!-- 奖励方式 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>奖励方式 *</mat-label>
+            <input matInput type="text" [(ngModel)]="performanceItem.reward" name="reward"
+                   placeholder="例如:基础绩效 * 1.2">
+            <mat-icon matSuffix>stars</mat-icon>
+            <mat-hint>描述达到阈值后的奖励计算方式</mat-hint>
+          </mat-form-field>
+        </div>
+      </div>
+    </form>
+  </div>
+
+  <!-- 操作按钮 -->
+  <div class="dialog-footer">
+    <button mat-button class="cancel-btn" (click)="onCancel()">
+      取消
+    </button>
+    <button mat-raised-button color="primary" class="save-btn" 
+            (click)="onSave()">
+      {{ isEditMode ? '保存修改' : '创建' }}
+    </button>
+  </div>
+</div>

+ 383 - 0
src/app/pages/admin/system-settings/setting-dialog/setting-dialog.scss

@@ -0,0 +1,383 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$secondary-color: #6B7280;
+$success-color: #00B42A;
+$warning-color: #FFAA00;
+$error-color: #F53F3F;
+$text-primary: #333333;
+$text-secondary: #666666;
+$text-tertiary: #999999;
+$border-color: #E5E7EB;
+$bg-light: #F9FAFB;
+$bg-white: #FFFFFF;
+$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+
+// 对话框容器
+.setting-dialog {
+  min-width: 600px;
+  max-width: 800px;
+  border-radius: 12px;
+  overflow: hidden;
+  background-color: $bg-white;
+}
+
+// 对话框头部
+.dialog-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px 24px;
+  border-bottom: 1px solid $border-color;
+  background-color: $bg-light;
+
+  .dialog-title {
+    font-size: 18px;
+    font-weight: 600;
+    color: $text-primary;
+    margin: 0;
+  }
+
+  .close-btn {
+    color: $text-tertiary;
+    padding: 4px;
+    border-radius: 6px;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: #E5E7EB;
+      color: $text-secondary;
+    }
+  }
+}
+
+// 对话框内容
+.dialog-content {
+  padding: 24px;
+  max-height: 60vh;
+  overflow-y: auto;
+}
+
+// 表单区域
+.form-section {
+  margin-bottom: 24px;
+  padding-bottom: 24px;
+  border-bottom: 1px solid $border-color;
+
+  &:last-child {
+    border-bottom: none;
+    margin-bottom: 0;
+    padding-bottom: 0;
+  }
+
+  .section-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: $text-primary;
+    margin-bottom: 16px;
+  }
+}
+
+// 表单组
+.form-group {
+  margin-bottom: 16px;
+
+  .form-field {
+    width: 100%;
+    .mat-form-field-wrapper {
+      .mat-form-field-flex {
+        border-radius: 8px;
+      }
+
+      .mat-form-field-outline {
+        border-radius: 8px;
+      }
+
+      .mat-form-field-infix {
+        padding: 10px 0;
+
+        input,
+        textarea {
+          font-size: 14px;
+          color: $text-primary;
+        }
+
+        .mat-input-element {
+          &::placeholder {
+            color: $text-tertiary;
+          }
+        }
+
+        textarea {
+          resize: vertical;
+          min-height: 80px;
+        }
+      }
+
+      .mat-form-field-label {
+        font-size: 14px;
+        color: $text-secondary;
+      }
+
+      .mat-form-field-suffix {
+        .mat-icon,
+        span {
+          color: $text-tertiary;
+        }
+      }
+
+      .mat-form-field-hint {
+        font-size: 12px;
+        color: $text-tertiary;
+        margin-top: 4px;
+      }
+    }
+  }
+
+  // 激活状态切换
+  .toggle-wrapper {
+    .active-toggle {
+      display: flex;
+      justify-content: flex-start;
+      .mat-slide-toggle-content {
+        font-size: 14px;
+        color: $text-secondary;
+      }
+    }
+  }
+}
+
+// 步骤容器
+.steps-container {
+  .steps-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+    flex-wrap: wrap;
+    gap: 12px;
+
+    .steps-label {
+      font-size: 14px;
+      font-weight: 500;
+      color: $text-primary;
+    }
+
+    .step-input-wrapper {
+      display: flex;
+      gap: 8px;
+      align-items: flex-end;
+
+      .step-input {
+        width: 200px;
+        .mat-form-field-wrapper {
+          .mat-form-field-flex {
+            border-radius: 6px;
+          }
+          .mat-form-field-outline {
+            border-radius: 6px;
+          }
+        }
+      }
+
+      .add-step-btn {
+        height: 40px;
+        border-radius: 6px;
+        min-width: auto;
+        width: 40px;
+        padding: 0;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
+    }
+  }
+
+  // 步骤列表
+  .steps-list {
+    background-color: $bg-light;
+    border-radius: 8px;
+    padding: 16px;
+    max-height: 300px;
+    overflow-y: auto;
+
+    .step-item {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 12px;
+      background-color: $bg-white;
+      border-radius: 6px;
+      border: 1px solid $border-color;
+      margin-bottom: 8px;
+      transition: all 0.2s ease;
+
+      &:hover {
+        border-color: $primary-color;
+        box-shadow: 0 2px 4px rgba(22, 93, 255, 0.1);
+      }
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+
+      .step-number {
+        flex-shrink: 0;
+        width: 24px;
+        height: 24px;
+        border-radius: 50%;
+        background-color: $primary-color;
+        color: $bg-white;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 12px;
+        font-weight: 600;
+      }
+
+      .step-name {
+        flex: 1;
+        font-size: 14px;
+        color: $text-primary;
+      }
+
+      .step-actions {
+        display: flex;
+        gap: 4px;
+
+        .action-btn {
+          width: 28px;
+          height: 28px;
+          padding: 0;
+          min-width: auto;
+
+          &:disabled {
+            opacity: 0.3;
+            cursor: not-allowed;
+          }
+        }
+      }
+    }
+
+    // 空步骤提示
+    .empty-steps {
+      text-align: center;
+      padding: 40px 20px;
+      color: $text-tertiary;
+      font-size: 14px;
+      border: 2px dashed $border-color;
+      border-radius: 6px;
+    }
+  }
+}
+
+// 对话框底部
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  padding: 20px 24px;
+  border-top: 1px solid $border-color;
+  background-color: $bg-light;
+
+  .cancel-btn {
+    padding: 8px 24px;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    color: $text-secondary;
+    background-color: $bg-white;
+    border: 1px solid $border-color;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: #F3F4F6;
+      border-color: $text-tertiary;
+    }
+  }
+
+  .save-btn {
+    padding: 8px 24px;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    background-color: $primary-color;
+    color: $bg-white;
+    transition: all 0.2s ease;
+    box-shadow: $shadow-sm;
+
+    &:hover {
+      background-color: #0E4BD8;
+      box-shadow: $shadow-md;
+      transform: translateY(-1px);
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .setting-dialog {
+    min-width: auto;
+    width: 90vw;
+    max-width: 600px;
+  }
+
+  .steps-header {
+    flex-direction: column;
+    align-items: stretch !important;
+  }
+
+  .step-input-wrapper {
+    flex-direction: column;
+    align-items: stretch !important;
+  }
+
+  .step-input {
+    width: 100% !important;
+  }
+
+  .add-step-btn {
+    width: 100% !important;
+  }
+}
+
+@media (max-width: 480px) {
+  .dialog-header,
+  .dialog-content,
+  .dialog-footer {
+    padding: 16px;
+  }
+
+  .dialog-title {
+    font-size: 16px !important;
+  }
+
+  .cancel-btn,
+  .save-btn {
+    padding: 6px 16px !important;
+    font-size: 13px !important;
+  }
+}
+
+// 滚动条样式
+.dialog-content,
+.steps-list {
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: $bg-light;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: $border-color;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb:hover {
+    background: $text-tertiary;
+  }
+}

+ 215 - 0
src/app/pages/admin/system-settings/setting-dialog/setting-dialog.ts

@@ -0,0 +1,215 @@
+import { Component, Inject, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
+import { MatIconModule } from '@angular/material/icon';
+import { MatSlideToggleModule } from '@angular/material/slide-toggle';
+
+interface WorkflowStage {
+  id: string;
+  name: string;
+  order: number;
+  description: string;
+  isActive: boolean;
+}
+
+interface SOPTemplate {
+  id: string;
+  name: string;
+  description: string;
+  steps: string[];
+  category: string;
+  isActive: boolean;
+}
+
+interface PricingRule {
+  id: string;
+  name: string;
+  description: string;
+  formula: string;
+  category: string;
+  isActive: boolean;
+}
+
+interface PerformanceRule {
+  id: string;
+  name: string;
+  description: string;
+  metric: string;
+  threshold: number;
+  reward: string;
+  isActive: boolean;
+}
+
+interface DialogData {
+  type: 'workflow' | 'sop' | 'pricing' | 'performance';
+  item: WorkflowStage | SOPTemplate | PricingRule | PerformanceRule;
+  categories?: string[];
+  metrics?: string[];
+}
+
+@Component({
+  selector: 'app-setting-dialog',
+  standalone: true,
+  imports: [
+    CommonModule,
+    FormsModule,
+    MatButtonModule,
+    MatDialogModule,
+    MatFormFieldModule,
+    MatInputModule,
+    MatSelectModule,
+    MatIconModule,
+    MatSlideToggleModule
+  ],
+  templateUrl: './setting-dialog.html',
+  styleUrl: './setting-dialog.scss'
+})
+export class SettingDialogComponent implements OnInit {
+  type: 'workflow' | 'sop' | 'pricing' | 'performance';
+  item: WorkflowStage | SOPTemplate | PricingRule | PerformanceRule;
+  categories: string[] = [];
+  metrics: string[] = [];
+  currentStep: string = '';
+  isEditMode: boolean = false;
+
+  // 安全访问绩效规则属性的getter
+  get performanceItem(): PerformanceRule {
+    return this.item as PerformanceRule;
+  }
+  
+  // 安全访问报价规则属性的getter
+  get pricingItem(): PricingRule {
+    return this.item as PricingRule;
+  }
+
+  constructor(
+    public dialogRef: MatDialogRef<SettingDialogComponent>,
+    @Inject(MAT_DIALOG_DATA) public data: DialogData
+  ) {
+    this.type = data.type;
+    this.item = { ...data.item };
+    this.categories = data.categories || [];
+    this.metrics = data.metrics || [];
+    this.isEditMode = !!this.item.id;
+  }
+
+  ngOnInit(): void {
+    // 根据类型初始化特殊处理
+    if (this.type === 'sop' && !(this.item as SOPTemplate).steps) {
+      (this.item as SOPTemplate).steps = [];
+    }
+  }
+
+  onCancel(): void {
+    this.dialogRef.close();
+  }
+
+  onSave(): void {
+    if (this.isFormValid()) {
+      this.dialogRef.close(this.item);
+    }
+  }
+
+  isFormValid(): boolean {
+    // 通用验证 - 名称和描述
+    if (!this.item.name || !this.item.name.trim()) {
+      alert('请输入名称');
+      return false;
+    }
+
+    if (!this.item.description || !this.item.description.trim()) {
+      alert('请输入描述');
+      return false;
+    }
+
+    // 根据类型进行特定验证
+    switch (this.type) {
+      case 'workflow':
+        const workflowItem = this.item as WorkflowStage;
+        if (!workflowItem.order || workflowItem.order <= 0) {
+          alert('请输入有效的阶段顺序');
+          return false;
+        }
+        break;
+      case 'sop':
+        const sopItem = this.item as SOPTemplate;
+        if (!sopItem.category) {
+          alert('请选择模板分类');
+          return false;
+        }
+        if (!sopItem.steps || sopItem.steps.length === 0) {
+          alert('请至少添加一个流程步骤');
+          return false;
+        }
+        break;
+      case 'pricing':
+        const pricingItem = this.item as PricingRule;
+        if (!pricingItem.category) {
+          alert('请选择报价分类');
+          return false;
+        }
+        if (!pricingItem.formula || !pricingItem.formula.trim()) {
+          alert('请输入计算公式');
+          return false;
+        }
+        break;
+      case 'performance':
+        const performanceItem = this.item as PerformanceRule;
+        if (!performanceItem.metric) {
+          alert('请选择绩效指标');
+          return false;
+        }
+        if (performanceItem.threshold === undefined || performanceItem.threshold < 0 || performanceItem.threshold > 100) {
+          alert('请输入有效的阈值(0-100)');
+          return false;
+        }
+        if (!performanceItem.reward || !performanceItem.reward.trim()) {
+          alert('请输入奖励方式');
+          return false;
+        }
+        break;
+    }
+
+    return true;
+  }
+
+  // SOP模板步骤管理
+  addStep(): void {
+    if (this.type === 'sop' && this.currentStep.trim()) {
+      const sopItem = this.item as SOPTemplate;
+      sopItem.steps.push(this.currentStep.trim());
+      this.currentStep = '';
+    }
+  }
+
+  removeStep(index: number): void {
+    if (this.type === 'sop') {
+      const sopItem = this.item as SOPTemplate;
+      sopItem.steps.splice(index, 1);
+    }
+  }
+
+  moveStep(fromIndex: number, toIndex: number): void {
+    if (this.type === 'sop') {
+      const sopItem = this.item as SOPTemplate;
+      const step = sopItem.steps.splice(fromIndex, 1)[0];
+      sopItem.steps.splice(toIndex, 0, step);
+    }
+  }
+
+  // 获取对话框标题
+  get dialogTitle(): string {
+    const typeTitles = {
+      workflow: this.isEditMode ? '编辑项目阶段' : '添加项目阶段',
+      sop: this.isEditMode ? '编辑SOP模板' : '添加SOP模板',
+      pricing: this.isEditMode ? '编辑报价规则' : '添加报价规则',
+      performance: this.isEditMode ? '编辑绩效规则' : '添加绩效规则'
+    };
+    return typeTitles[this.type];
+  }
+}

+ 519 - 0
src/app/pages/admin/system-settings/system-settings.html

@@ -0,0 +1,519 @@
+<div class="system-settings">
+  <!-- 页面标题 -->
+  <div class="page-header">
+    <h2 class="page-title">系统设置</h2>
+    <p class="page-description">配置项目状态流、SOP流程、报价规则、绩效规则等系统设置</p>
+  </div>
+
+  <!-- 标签页导航 -->
+  <div class="tab-navigation">
+    <button class="tab-btn" [class.active]="activeTab === 'workflow'"
+            (click)="switchTab('workflow')">
+      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+        <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
+      </svg>
+      项目状态流
+    </button>
+    <button class="tab-btn" [class.active]="activeTab === 'sop'"
+            (click)="switchTab('sop')">
+      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+        <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
+      </svg>
+      SOP流程模板
+    </button>
+    <button class="tab-btn" [class.active]="activeTab === 'pricing'"
+            (click)="switchTab('pricing')">
+      <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="1" x2="12" y2="23"></line>
+        <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
+      </svg>
+      报价规则
+    </button>
+    <button class="tab-btn" [class.active]="activeTab === 'performance'"
+            (click)="switchTab('performance')">
+      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+        <path d="M18 8h1a4 4 0 0 1 0 8h-1"></path>
+        <path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"></path>
+        <line x1="6" y1="1" x2="6" y2="4"></line>
+        <line x1="10" y1="1" x2="10" y2="4"></line>
+        <line x1="14" y1="1" x2="14" y2="4"></line>
+      </svg>
+      绩效规则
+    </button>
+    <button class="tab-btn" [class.active]="activeTab === 'system'"
+            (click)="switchTab('system')">
+      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+        <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
+        <line x1="8" y1="21" x2="16" y2="21"></line>
+        <line x1="12" y1="17" x2="12" y2="21"></line>
+      </svg>
+      系统配置
+    </button>
+  </div>
+
+  <!-- 项目状态流标签内容 -->
+  <div *ngIf="activeTab === 'workflow'" class="tab-content">
+    <!-- 搜索区域 -->
+    <div class="search-section">
+      <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>
+          <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+        </svg>
+        <input matInput type="text" placeholder="搜索阶段名称或描述..."
+               [(ngModel)]="workflowSearchTerm" (keyup.enter)="onWorkflowSearch()"
+               class="search-input">
+        <button mat-button *ngIf="workflowSearchTerm" class="clear-search-btn"
+                (click)="workflowSearchTerm = ''; onWorkflowSearch()">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+            <line x1="18" y1="6" x2="6" y2="18"></line>
+            <line x1="6" y1="6" x2="18" y2="18"></line>
+          </svg>
+        </button>
+      </div>
+      <button mat-raised-button color="primary" class="create-btn"
+              (click)="openWorkflowDialog()">
+        <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>
+    </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">
+                  <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">
+                  <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>
+        </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">
+        <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
+      </svg>
+      <p>没有找到符合条件的项目阶段</p>
+      <button mat-button color="primary" (click)="workflowSearchTerm = ''; onWorkflowSearch()">
+        清除筛选条件
+      </button>
+    </div>
+  </div>
+
+  <!-- SOP流程模板标签内容 -->
+  <div *ngIf="activeTab === 'sop'" class="tab-content">
+    <!-- 搜索区域 -->
+    <div class="search-section">
+      <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>
+          <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+        </svg>
+        <input matInput type="text" placeholder="搜索模板名称、描述或分类..."
+               [(ngModel)]="sopSearchTerm" (keyup.enter)="onSOPSearch()"
+               class="search-input">
+        <button mat-button *ngIf="sopSearchTerm" class="clear-search-btn"
+                (click)="sopSearchTerm = ''; onSOPSearch()">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+            <line x1="18" y1="6" x2="6" y2="18"></line>
+            <line x1="6" y1="6" x2="18" y2="18"></line>
+          </svg>
+        </button>
+      </div>
+      <button mat-raised-button color="primary" class="create-btn"
+              (click)="openSOPDialog()">
+        <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>
+    </div>
+
+    <!-- SOP模板列表 -->
+    <div class="sop-templates-list">
+      <div *ngFor="let template of filteredSOPTemplates" class="template-item" [class.disabled]="!template.isActive">
+        <div class="template-header">
+          <div class="template-info">
+            <div class="template-title-row">
+              <h3 class="template-name">{{ template.name }}</h3>
+              <span class="template-category">{{ template.category }}</span>
+            </div>
+            <p class="template-description">{{ template.description }}</p>
+          </div>
+          <div class="template-controls">
+            <mat-slide-toggle
+              [checked]="template.isActive"
+              (change)="toggleActive('sop', template.id, $event.checked)"
+              class="template-toggle">
+              {{ template.isActive ? '启用' : '禁用' }}
+            </mat-slide-toggle>
+            <div class="action-buttons">
+              <button mat-icon-button class="action-btn" color="primary"
+                      title="编辑模板"
+                      (click)="openSOPDialog(template)">
+                <svg width="20" height="20" 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('sop', template.id)">
+                <svg width="20" height="20" 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>
+        </div>
+        <div class="template-steps">
+          <div class="steps-header">流程步骤:</div>
+          <div class="steps-list">
+            <div *ngFor="let step of template.steps; let i = index" class="step-item">
+              <div class="step-number">{{ i + 1 }}</div>
+              <div class="step-name">{{ step }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 无数据状态 -->
+    <div *ngIf="filteredSOPTemplates.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">
+        <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
+      </svg>
+      <p>没有找到符合条件的SOP模板</p>
+      <button mat-button color="primary" (click)="sopSearchTerm = ''; onSOPSearch()">
+        清除筛选条件
+      </button>
+    </div>
+  </div>
+
+  <!-- 报价规则标签内容 -->
+  <div *ngIf="activeTab === 'pricing'" class="tab-content">
+    <!-- 搜索区域 -->
+    <div class="search-section">
+      <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>
+          <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+        </svg>
+        <input matInput type="text" placeholder="搜索规则名称、描述或分类..."
+               [(ngModel)]="pricingSearchTerm" (keyup.enter)="onPricingSearch()"
+               class="search-input">
+        <button mat-button *ngIf="pricingSearchTerm" class="clear-search-btn"
+                (click)="pricingSearchTerm = ''; onPricingSearch()">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+            <line x1="18" y1="6" x2="6" y2="18"></line>
+            <line x1="6" y1="6" x2="18" y2="18"></line>
+          </svg>
+        </button>
+      </div>
+      <button mat-raised-button color="primary" class="create-btn"
+              (click)="openPricingDialog()">
+        <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>
+    </div>
+
+    <!-- 报价规则列表 -->
+    <div class="pricing-rules-list">
+      <div *ngFor="let rule of filteredPricingRules" class="rule-item" [class.disabled]="!rule.isActive">
+        <div class="rule-header">
+          <div class="rule-info">
+            <div class="rule-title-row">
+              <h3 class="rule-name">{{ rule.name }}</h3>
+              <span class="rule-category">{{ rule.category }}</span>
+            </div>
+            <p class="rule-description">{{ rule.description }}</p>
+          </div>
+          <div class="rule-controls">
+            <mat-slide-toggle
+              [checked]="rule.isActive"
+              (change)="toggleActive('pricing', rule.id, $event.checked)"
+              class="rule-toggle">
+              {{ rule.isActive ? '启用' : '禁用' }}
+            </mat-slide-toggle>
+            <div class="action-buttons">
+              <button mat-icon-button class="action-btn" color="primary"
+                      title="编辑规则"
+                      (click)="openPricingDialog(rule)">
+                <svg width="20" height="20" 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('pricing', rule.id)">
+                <svg width="20" height="20" 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>
+        </div>
+        <div class="rule-formula">
+          <div class="formula-label">计算公式:</div>
+          <div class="formula-content">{{ rule.formula }}</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 无数据状态 -->
+    <div *ngIf="filteredPricingRules.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">
+        <line x1="12" y1="1" x2="12" y2="23"></line>
+        <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
+      </svg>
+      <p>没有找到符合条件的报价规则</p>
+      <button mat-button color="primary" (click)="pricingSearchTerm = ''; onPricingSearch()">
+        清除筛选条件
+      </button>
+    </div>
+  </div>
+
+  <!-- 绩效规则标签内容 -->
+  <div *ngIf="activeTab === 'performance'" class="tab-content">
+    <!-- 搜索区域 -->
+    <div class="search-section">
+      <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>
+          <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+        </svg>
+        <input matInput type="text" placeholder="搜索规则名称、描述或指标..."
+               [(ngModel)]="performanceSearchTerm" (keyup.enter)="onPerformanceSearch()"
+               class="search-input">
+        <button mat-button *ngIf="performanceSearchTerm" class="clear-search-btn"
+                (click)="performanceSearchTerm = ''; onPerformanceSearch()">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+            <line x1="18" y1="6" x2="6" y2="18"></line>
+            <line x1="6" y1="6" x2="18" y2="18"></line>
+          </svg>
+        </button>
+      </div>
+      <button mat-raised-button color="primary" class="create-btn"
+              (click)="openPerformanceDialog()">
+        <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>
+    </div>
+
+    <!-- 绩效规则列表 -->
+    <div class="performance-rules-list">
+      <div *ngFor="let rule of filteredPerformanceRules" class="rule-item" [class.disabled]="!rule.isActive">
+        <div class="rule-header">
+          <div class="rule-info">
+            <div class="rule-title-row">
+              <h3 class="rule-name">{{ rule.name }}</h3>
+              <span class="rule-metric">{{ rule.metric }}</span>
+            </div>
+            <p class="rule-description">{{ rule.description }}</p>
+          </div>
+          <div class="rule-controls">
+            <mat-slide-toggle
+              [checked]="rule.isActive"
+              (change)="toggleActive('performance', rule.id, $event.checked)"
+              class="rule-toggle">
+              {{ rule.isActive ? '启用' : '禁用' }}
+            </mat-slide-toggle>
+            <div class="action-buttons">
+              <button mat-icon-button class="action-btn" color="primary"
+                      title="编辑规则"
+                      (click)="openPerformanceDialog(rule)">
+                <svg width="20" height="20" 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('performance', rule.id)">
+                <svg width="20" height="20" 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>
+        </div>
+        <div class="rule-details">
+          <div class="detail-row">
+            <div class="detail-label">阈值:</div>
+            <div class="detail-value">{{ rule.threshold }}%</div>
+          </div>
+          <div class="detail-row">
+            <div class="detail-label">奖励方式:</div>
+            <div class="detail-value">{{ rule.reward }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 无数据状态 -->
+    <div *ngIf="filteredPerformanceRules.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">
+        <path d="M18 8h1a4 4 0 0 1 0 8h-1"></path>
+        <path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"></path>
+        <line x1="6" y1="1" x2="6" y2="4"></line>
+        <line x1="10" y1="1" x2="10" y2="4"></line>
+        <line x1="14" y1="1" x2="14" y2="4"></line>
+      </svg>
+      <p>没有找到符合条件的绩效规则</p>
+      <button mat-button color="primary" (click)="performanceSearchTerm = ''; onPerformanceSearch()">
+        清除筛选条件
+      </button>
+    </div>
+  </div>
+
+  <!-- 系统配置标签内容 -->
+  <div *ngIf="activeTab === 'system'" class="tab-content">
+    <div class="system-config-section">
+      <h3 class="section-title">基础配置</h3>
+      
+      <div class="config-items">
+        <div class="config-item">
+          <div class="config-info">
+            <div class="config-name">启用系统审计日志</div>
+            <div class="config-description">记录用户操作日志,用于安全审计和问题追踪</div>
+          </div>
+          <mat-slide-toggle
+            [(ngModel)]="systemOptions.enableAuditLog"
+            class="config-toggle">
+          </mat-slide-toggle>
+        </div>
+        
+        <div class="config-item">
+          <div class="config-info">
+            <div class="config-name">启用系统通知</div>
+            <div class="config-description">向用户发送项目状态变更、任务提醒等通知</div>
+          </div>
+          <mat-slide-toggle
+            [(ngModel)]="systemOptions.enableNotification"
+            class="config-toggle">
+          </mat-slide-toggle>
+        </div>
+        
+        <div class="config-item">
+          <div class="config-info">
+            <div class="config-name">启用自动备份</div>
+            <div class="config-description">定期自动备份系统数据,确保数据安全</div>
+          </div>
+          <mat-slide-toggle
+            [(ngModel)]="systemOptions.autoBackupEnabled"
+            class="config-toggle">
+          </mat-slide-toggle>
+        </div>
+      </div>
+    </div>
+
+    <div class="system-config-section">
+      <h3 class="section-title">数据管理</h3>
+      
+      <div class="config-items">
+        <div class="config-item">
+          <div class="config-info">
+            <div class="config-name">备份频率</div>
+            <div class="config-description">设置数据自动备份的时间间隔</div>
+          </div>
+          <mat-select [(ngModel)]="systemOptions.backupFrequency" class="config-select">
+            <mat-option value="daily">每日</mat-option>
+            <mat-option value="weekly">每周</mat-option>
+            <mat-option value="monthly">每月</mat-option>
+          </mat-select>
+        </div>
+        
+        <div class="config-item">
+          <div class="config-info">
+            <div class="config-name">数据保留天数</div>
+            <div class="config-description">设置系统日志和备份数据的保留期限</div>
+          </div>
+          <mat-form-field appearance="outline" class="config-input">
+            <input matInput type="number" [(ngModel)]="systemOptions.dataRetentionDays" min="7" max="3650">
+            <span matSuffix>天</span>
+          </mat-form-field>
+        </div>
+        
+        <div class="config-item">
+          <div class="config-info">
+            <div class="config-name">启用数据导出</div>
+            <div class="config-description">允许用户导出系统数据为Excel等格式</div>
+          </div>
+          <mat-slide-toggle
+            [(ngModel)]="systemOptions.enableDataExport"
+            class="config-toggle">
+          </mat-slide-toggle>
+        </div>
+      </div>
+    </div>
+
+    <div class="system-config-actions">
+      <button mat-raised-button color="primary" class="save-btn"
+              (click)="saveSystemOptions()">
+        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+          <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
+          <polyline points="17 21 17 13 7 13 7 21"></polyline>
+          <polyline points="7 3 7 8 15 8"></polyline>
+        </svg>
+        保存配置
+      </button>
+      <button mat-button class="reset-btn" (click)="loadWorkflowStages(); loadSOPTemplates(); loadPricingRules(); loadPerformanceRules()">
+        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+          <polyline points="1 4 1 10 7 10"></polyline>
+          <polyline points="23 20 23 14 17 14"></polyline>
+          <path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path>
+        </svg>
+        重置设置
+      </button>
+    </div>
+  </div>
+</div>

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

@@ -0,0 +1,759 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$secondary-color: #6B7280;
+$success-color: #00B42A;
+$warning-color: #FFAA00;
+$error-color: #F53F3F;
+$text-primary: #333333;
+$text-secondary: #666666;
+$text-tertiary: #999999;
+$border-color: #E5E7EB;
+$bg-light: #F9FAFB;
+$bg-white: #FFFFFF;
+$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+
+// 主容器
+.system-settings {
+  padding: 24px;
+  min-height: 100vh;
+  background-color: $bg-light;
+}
+
+// 页面标题区域
+.page-header {
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 24px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid $border-color;
+
+  .page-title {
+    font-size: 24px;
+    font-weight: 600;
+    color: $text-primary;
+    margin: 0 0 4px 0;
+  }
+
+  .page-description {
+    font-size: 14px;
+    color: $text-tertiary;
+    margin: 0;
+  }
+}
+
+// 标签页导航
+.tab-navigation {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 24px;
+  background-color: $bg-white;
+  border-radius: 12px;
+  padding: 4px;
+  box-shadow: $shadow-sm;
+  overflow-x: auto;
+  scrollbar-width: thin;
+  scrollbar-color: $border-color $bg-light;
+
+  &::-webkit-scrollbar {
+    height: 6px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: $bg-light;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: $border-color;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb:hover {
+    background: $text-tertiary;
+  }
+
+  .tab-btn {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 12px 24px;
+    border: none;
+    background-color: transparent;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    color: $text-secondary;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    white-space: nowrap;
+
+    &:hover {
+      background-color: $bg-light;
+      color: $text-primary;
+    }
+
+    &.active {
+      background-color: $primary-color;
+      color: $bg-white;
+    }
+  }
+}
+
+// 标签页内容
+.tab-content {
+  animation: fadeIn 0.3s ease-in-out;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+// 搜索区域
+.search-section {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 24px;
+  flex-wrap: wrap;
+
+  .search-input-wrapper {
+    flex: 1;
+    min-width: 300px;
+    position: relative;
+    display: flex;
+    align-items: center;
+
+    .search-icon {
+      position: absolute;
+      left: 12px;
+      color: $text-tertiary;
+      z-index: 1;
+    }
+
+    .search-input {
+      width: 100%;
+      padding: 10px 12px 10px 40px;
+      border: 1px solid $border-color;
+      border-radius: 8px;
+      font-size: 14px;
+      color: $text-primary;
+      background-color: $bg-white;
+      transition: all 0.2s ease;
+      box-shadow: $shadow-sm;
+
+      &:focus {
+        outline: none;
+        border-color: $primary-color;
+        box-shadow: 0 0 0 3px rgba(22, 93, 255, 0.1);
+      }
+
+      &::placeholder {
+        color: $text-tertiary;
+      }
+    }
+
+    .clear-search-btn {
+      position: absolute;
+      right: 8px;
+      color: $text-tertiary;
+      padding: 4px;
+    }
+  }
+
+  .create-btn {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    background-color: $primary-color;
+    color: $bg-white;
+    border-radius: 8px;
+    padding: 8px 16px;
+    font-size: 14px;
+    font-weight: 500;
+    transition: all 0.2s ease;
+    box-shadow: $shadow-sm;
+
+    &:hover {
+      background-color: #0E4BD8;
+      box-shadow: $shadow-md;
+      transform: translateY(-1px);
+    }
+  }
+}
+
+// 项目状态流样式
+.workflow-stages-list {
+  background-color: $bg-white;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: $shadow-sm;
+
+  .stage-item {
+    position: relative;
+    margin-bottom: 24px;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+
+    &.disabled {
+      opacity: 0.6;
+    }
+
+    .stage-header {
+      display: flex;
+      align-items: flex-start;
+      gap: 16px;
+
+      .stage-order {
+        flex-shrink: 0;
+        width: 32px;
+        height: 32px;
+        border-radius: 50%;
+        background-color: $primary-color;
+        color: $bg-white;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 14px;
+        font-weight: 600;
+        margin-top: 4px;
+      }
+
+      .stage-info {
+        flex: 1;
+
+        .stage-name {
+          font-size: 16px;
+          font-weight: 600;
+          color: $text-primary;
+          margin: 0 0 4px 0;
+        }
+
+        .stage-description {
+          font-size: 14px;
+          color: $text-secondary;
+          margin: 0;
+          line-height: 1.5;
+        }
+      }
+
+      .stage-controls {
+        display: flex;
+        align-items: center;
+        gap: 16px;
+        flex-shrink: 0;
+
+        .stage-toggle {
+          .mat-slide-toggle-content {
+            font-size: 14px;
+          }
+        }
+
+        .action-buttons {
+          display: flex;
+          gap: 8px;
+
+          .action-btn {
+            width: 36px;
+            height: 36px;
+            border-radius: 6px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            transition: all 0.2s ease;
+            background-color: transparent;
+            border: 1px solid $border-color;
+            color: $text-secondary;
+
+            &:hover {
+              border-color: currentColor;
+              transform: translateY(-1px);
+            }
+
+            &.mat-primary {
+              color: $primary-color;
+            }
+
+            &.mat-warn {
+              color: $error-color;
+            }
+          }
+        }
+      }
+    }
+
+    .stage-divider {
+      position: relative;
+      height: 48px;
+      margin-left: 16px;
+      padding-left: 16px;
+      border-left: 2px dashed $border-color;
+
+      svg {
+        position: absolute;
+        top: 0;
+        left: -10px;
+        transform: translateY(-50%);
+        background-color: $bg-white;
+        padding: 4px;
+        border-radius: 50%;
+        border: 2px solid $bg-white;
+      }
+    }
+  }
+}
+
+// SOP模板样式
+.sop-templates-list,
+.pricing-rules-list,
+.performance-rules-list {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(600px, 1fr));
+  gap: 20px;
+
+  .template-item,
+  .rule-item {
+    background-color: $bg-white;
+    border-radius: 12px;
+    padding: 24px;
+    box-shadow: $shadow-sm;
+    transition: all 0.2s ease;
+
+    &:hover {
+      box-shadow: $shadow-md;
+      transform: translateY(-2px);
+    }
+
+    &.disabled {
+      opacity: 0.6;
+    }
+
+    .template-header,
+    .rule-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: flex-start;
+      margin-bottom: 16px;
+      gap: 20px;
+
+      .template-info,
+      .rule-info {
+        flex: 1;
+
+        .template-title-row,
+        .rule-title-row {
+          display: flex;
+          align-items: center;
+          gap: 12px;
+          margin-bottom: 8px;
+
+          .template-name,
+          .rule-name {
+            font-size: 16px;
+            font-weight: 600;
+            color: $text-primary;
+            margin: 0;
+            flex: 1;
+          }
+
+          .template-category,
+          .rule-category,
+          .rule-metric {
+            padding: 4px 12px;
+            border-radius: 16px;
+            font-size: 12px;
+            font-weight: 500;
+            color: $primary-color;
+            background-color: #E6F0FF;
+            white-space: nowrap;
+          }
+        }
+
+        .template-description,
+        .rule-description {
+          font-size: 14px;
+          color: $text-secondary;
+          margin: 0;
+          line-height: 1.5;
+        }
+      }
+
+      .template-controls,
+      .rule-controls {
+        display: flex;
+        align-items: center;
+        gap: 16px;
+        flex-shrink: 0;
+
+        .template-toggle,
+        .rule-toggle {
+          .mat-slide-toggle-content {
+            font-size: 14px;
+          }
+        }
+
+        .action-buttons {
+          display: flex;
+          gap: 8px;
+
+          .action-btn {
+            width: 36px;
+            height: 36px;
+            border-radius: 6px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            transition: all 0.2s ease;
+            background-color: transparent;
+            border: 1px solid $border-color;
+            color: $text-secondary;
+
+            &:hover {
+              border-color: currentColor;
+              transform: translateY(-1px);
+            }
+
+            &.mat-primary {
+              color: $primary-color;
+            }
+
+            &.mat-warn {
+              color: $error-color;
+            }
+          }
+        }
+      }
+    }
+
+    .template-steps,
+    .rule-formula,
+    .rule-details {
+      background-color: $bg-light;
+      border-radius: 8px;
+      padding: 16px;
+
+      .steps-header,
+      .formula-label,
+      .detail-row {
+        font-size: 13px;
+        font-weight: 600;
+        color: $text-secondary;
+        margin-bottom: 12px;
+      }
+
+      .steps-list {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 8px;
+
+        .step-item {
+          display: flex;
+          align-items: center;
+          gap: 6px;
+          padding: 6px 12px;
+          background-color: $bg-white;
+          border-radius: 16px;
+          border: 1px solid $border-color;
+          font-size: 13px;
+          color: $text-secondary;
+
+          .step-number {
+            width: 20px;
+            height: 20px;
+            border-radius: 50%;
+            background-color: $primary-color;
+            color: $bg-white;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: 11px;
+            font-weight: 600;
+            flex-shrink: 0;
+          }
+
+          .step-name {
+            white-space: nowrap;
+          }
+        }
+      }
+
+      .formula-content {
+        font-size: 14px;
+        color: $text-primary;
+        font-family: 'Courier New', monospace;
+        background-color: $bg-white;
+        padding: 8px 12px;
+        border-radius: 4px;
+        border: 1px solid $border-color;
+      }
+
+      .detail-row {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 8px;
+
+        &:last-child {
+          margin-bottom: 0;
+        }
+
+        .detail-value {
+          font-size: 14px;
+          color: $text-primary;
+          font-weight: 500;
+        }
+      }
+    }
+  }
+}
+
+// 系统配置样式
+.system-config-section {
+  background-color: $bg-white;
+  border-radius: 12px;
+  padding: 24px;
+  box-shadow: $shadow-sm;
+  margin-bottom: 24px;
+
+  .section-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: $text-primary;
+    margin: 0 0 20px 0;
+    padding-bottom: 12px;
+    border-bottom: 1px solid $border-color;
+  }
+
+  .config-items {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+  }
+
+  .config-item {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 16px;
+    border-radius: 8px;
+    background-color: $bg-light;
+
+    .config-info {
+      flex: 1;
+      margin-right: 20px;
+
+      .config-name {
+        font-size: 14px;
+        font-weight: 500;
+        color: $text-primary;
+        margin-bottom: 4px;
+      }
+
+      .config-description {
+        font-size: 12px;
+        color: $text-tertiary;
+        line-height: 1.4;
+      }
+    }
+
+    .config-toggle {
+      .mat-slide-toggle-content {
+        font-size: 14px;
+      }
+    }
+
+    .config-select {
+      min-width: 160px;
+      border-radius: 6px;
+    }
+
+    .config-input {
+      width: 120px;
+      .mat-form-field-wrapper {
+        .mat-form-field-flex {
+          border-radius: 6px;
+        }
+        .mat-form-field-outline {
+          border-radius: 6px;
+        }
+      }
+    }
+  }
+}
+
+// 系统配置操作按钮
+.system-config-actions {
+  display: flex;
+  gap: 12px;
+  justify-content: flex-end;
+  padding: 20px;
+  background-color: $bg-white;
+  border-radius: 12px;
+  box-shadow: $shadow-sm;
+
+  .save-btn {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    background-color: $primary-color;
+    color: $bg-white;
+    border-radius: 8px;
+    padding: 10px 20px;
+    font-size: 14px;
+    font-weight: 500;
+    transition: all 0.2s ease;
+    box-shadow: $shadow-sm;
+
+    &:hover {
+      background-color: #0E4BD8;
+      box-shadow: $shadow-md;
+      transform: translateY(-1px);
+    }
+  }
+
+  .reset-btn {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    color: $text-secondary;
+    background-color: transparent;
+    border: 1px solid $border-color;
+    border-radius: 8px;
+    padding: 10px 20px;
+    font-size: 14px;
+    font-weight: 500;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: $bg-light;
+      border-color: $text-tertiary;
+      color: $text-primary;
+    }
+  }
+}
+
+// 无数据状态
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 80px 20px;
+  text-align: center;
+  background-color: $bg-white;
+  border-radius: 12px;
+  box-shadow: $shadow-sm;
+
+  svg {
+    margin-bottom: 16px;
+    opacity: 0.5;
+  }
+
+  p {
+    font-size: 16px;
+    color: $text-tertiary;
+    margin-bottom: 24px;
+  }
+
+  button {
+    border-radius: 8px;
+    padding: 8px 24px;
+    font-size: 14px;
+    font-weight: 500;
+  }
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .sop-templates-list,
+  .pricing-rules-list,
+  .performance-rules-list {
+    grid-template-columns: 1fr;
+  }
+}
+
+@media (max-width: 992px) {
+  .system-settings {
+    padding: 16px;
+  }
+
+  .search-section {
+    flex-direction: column;
+    gap: 12px;
+  }
+
+  .search-input-wrapper {
+    min-width: 100% !important;
+  }
+
+  .stage-header,
+  .template-header,
+  .rule-header,
+  .config-item {
+    flex-direction: column;
+    align-items: flex-start !important;
+    gap: 12px !important;
+  }
+
+  .stage-info,
+  .template-info,
+  .rule-info,
+  .config-info {
+    margin-right: 0 !important;
+    width: 100% !important;
+  }
+
+  .stage-controls,
+  .template-controls,
+  .rule-controls {
+    width: 100% !important;
+    justify-content: space-between;
+  }
+
+  .system-config-actions {
+    flex-direction: column;
+    align-items: stretch;
+  }
+}
+
+@media (max-width: 768px) {
+  .workflow-stages-list,
+  .sop-templates-list,
+  .pricing-rules-list,
+  .performance-rules-list,
+  .system-config-section,
+  .system-config-actions {
+    padding: 16px;
+  }
+
+  .page-title {
+    font-size: 20px !important;
+  }
+}
+
+@media (max-width: 480px) {
+  .system-settings {
+    padding: 12px;
+  }
+
+  .tab-btn {
+    padding: 10px 16px !important;
+    font-size: 13px !important;
+  }
+
+  .create-btn,
+  .save-btn,
+  .reset-btn {
+    padding: 8px 16px !important;
+    font-size: 13px !important;
+  }
+}

+ 560 - 0
src/app/pages/admin/system-settings/system-settings.ts

@@ -0,0 +1,560 @@
+import { Component, OnInit, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { FormsModule } from '@angular/forms';
+import { SettingDialogComponent } from './setting-dialog/setting-dialog';
+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';
+
+interface WorkflowStage {
+  id: string;
+  name: string;
+  order: number;
+  description: string;
+  isActive: boolean;
+}
+
+interface SOPTemplate {
+  id: string;
+  name: string;
+  description: string;
+  steps: string[];
+  category: string;
+  isActive: boolean;
+}
+
+interface PricingRule {
+  id: string;
+  name: string;
+  description: string;
+  formula: string;
+  category: string;
+  isActive: boolean;
+}
+
+interface PerformanceRule {
+  id: string;
+  name: string;
+  description: string;
+  metric: string;
+  threshold: number;
+  reward: string;
+  isActive: boolean;
+}
+
+@Component({
+  selector: 'app-system-settings',
+  standalone: true,
+  imports: [
+    CommonModule,
+    RouterModule,
+    FormsModule,
+    SettingDialogComponent,
+    MatSelectModule,
+    MatFormFieldModule,
+    MatSlideToggleModule
+  ],
+  templateUrl: './system-settings.html',
+  styleUrl: './system-settings.scss'
+})
+export class SystemSettings implements OnInit {
+  // 激活的标签页
+  activeTab = 'workflow';
+
+  // 工作流阶段数据
+  workflowStages: WorkflowStage[] = [];
+  filteredWorkflowStages: WorkflowStage[] = [];
+  workflowSearchTerm = '';
+
+  // SOP模板数据
+  sopTemplates: SOPTemplate[] = [];
+  filteredSOPTemplates: SOPTemplate[] = [];
+  sopSearchTerm = '';
+  sopCategories = ['设计流程', '客户服务', '项目管理', '财务流程'];
+
+  // 报价规则数据
+  pricingRules: PricingRule[] = [];
+  filteredPricingRules: PricingRule[] = [];
+  pricingSearchTerm = '';
+  pricingCategories = ['基础设计', '高级设计', '加急服务', '增值服务'];
+
+  // 绩效规则数据
+  performanceRules: PerformanceRule[] = [];
+  filteredPerformanceRules: PerformanceRule[] = [];
+  performanceSearchTerm = '';
+  performanceMetrics = ['项目完成率', '客户满意度', '按时交付率', '产值'];
+
+  // 系统配置选项
+  systemOptions = {
+    enableAuditLog: true,
+    enableNotification: true,
+    autoBackupEnabled: true,
+    backupFrequency: 'daily',
+    dataRetentionDays: 365,
+    enableDataExport: true
+  };
+
+  constructor(private dialog: MatDialog) {}
+
+  ngOnInit(): void {
+    this.loadWorkflowStages();
+    this.loadSOPTemplates();
+    this.loadPricingRules();
+    this.loadPerformanceRules();
+  }
+
+  // 加载工作流阶段数据
+  loadWorkflowStages(): void {
+    this.workflowStages = [
+      {
+        id: '1',
+        name: '需求沟通',
+        order: 1,
+        description: '与客户沟通需求,了解项目背景和期望',
+        isActive: true
+      },
+      {
+        id: '2',
+        name: '方案设计',
+        order: 2,
+        description: '根据需求设计初步方案',
+        isActive: true
+      },
+      {
+        id: '3',
+        name: '方案确认',
+        order: 3,
+        description: '与客户确认设计方案,收集反馈',
+        isActive: true
+      },
+      {
+        id: '4',
+        name: '深化设计',
+        order: 4,
+        description: '根据确认的方案进行深化设计',
+        isActive: true
+      },
+      {
+        id: '5',
+        name: '设计评审',
+        order: 5,
+        description: '内部评审设计成果',
+        isActive: true
+      },
+      {
+        id: '6',
+        name: '客户验收',
+        order: 6,
+        description: '客户验收最终设计成果',
+        isActive: true
+      },
+      {
+        id: '7',
+        name: '项目交付',
+        order: 7,
+        description: '交付设计文件和相关资料',
+        isActive: true
+      },
+      {
+        id: '8',
+        name: '售后跟踪',
+        order: 8,
+        description: '项目完成后的售后跟踪和维护',
+        isActive: true
+      }
+    ];
+    
+    this.filteredWorkflowStages = [...this.workflowStages];
+  }
+
+  // 加载SOP模板数据
+  loadSOPTemplates(): void {
+    this.sopTemplates = [
+      {
+        id: '1',
+        name: '标准设计流程',
+        description: '适用于大多数设计项目的标准流程',
+        steps: ['需求分析', '概念设计', '方案设计', '深化设计', '施工图设计', '交付验收'],
+        category: '设计流程',
+        isActive: true
+      },
+      {
+        id: '2',
+        name: '客户初次咨询流程',
+        description: '客户初次咨询的标准服务流程',
+        steps: ['客户接待', '需求了解', '方案报价', '合同签订', '项目启动'],
+        category: '客户服务',
+        isActive: true
+      },
+      {
+        id: '3',
+        name: '项目变更管理流程',
+        description: '处理项目变更的标准流程',
+        steps: ['变更申请', '变更评估', '变更审批', '变更实施', '变更确认'],
+        category: '项目管理',
+        isActive: true
+      },
+      {
+        id: '4',
+        name: '财务结算流程',
+        description: '项目财务结算的标准流程',
+        steps: ['项目验收', '费用核算', '发票开具', '款项收取', '财务归档'],
+        category: '财务流程',
+        isActive: true
+      }
+    ];
+    
+    this.filteredSOPTemplates = [...this.sopTemplates];
+  }
+
+  // 加载报价规则数据
+  loadPricingRules(): void {
+    this.pricingRules = [
+      {
+        id: '1',
+        name: '基础设计报价',
+        description: '基础设计服务的报价规则',
+        formula: '面积 * 单价 + 基础服务费',
+        category: '基础设计',
+        isActive: true
+      },
+      {
+        id: '2',
+        name: '高级设计报价',
+        description: '高级设计服务的报价规则',
+        formula: '面积 * (单价 * 1.5) + 高级服务费',
+        category: '高级设计',
+        isActive: true
+      },
+      {
+        id: '3',
+        name: '加急服务报价',
+        description: '加急项目的额外费用计算规则',
+        formula: '基础报价 * 1.3',
+        category: '加急服务',
+        isActive: true
+      },
+      {
+        id: '4',
+        name: '增值服务报价',
+        description: '增值服务的报价规则',
+        formula: '按单项服务定价',
+        category: '增值服务',
+        isActive: true
+      }
+    ];
+    
+    this.filteredPricingRules = [...this.pricingRules];
+  }
+
+  // 加载绩效规则数据
+  loadPerformanceRules(): void {
+    this.performanceRules = [
+      {
+        id: '1',
+        name: '项目完成率奖励',
+        description: '根据项目完成率发放的绩效奖励',
+        metric: '项目完成率',
+        threshold: 90,
+        reward: '基础绩效 * 1.2',
+        isActive: true
+      },
+      {
+        id: '2',
+        name: '客户满意度奖励',
+        description: '根据客户满意度评分发放的绩效奖励',
+        metric: '客户满意度',
+        threshold: 95,
+        reward: '额外奖励1000元',
+        isActive: true
+      },
+      {
+        id: '3',
+        name: '按时交付奖励',
+        description: '项目按时交付的绩效奖励',
+        metric: '按时交付率',
+        threshold: 95,
+        reward: '基础绩效 * 1.1',
+        isActive: true
+      },
+      {
+        id: '4',
+        name: '产值提成规则',
+        description: '根据项目产值计算的提成规则',
+        metric: '产值',
+        threshold: 100000,
+        reward: '超出部分 * 0.05',
+        isActive: true
+      }
+    ];
+    
+    this.filteredPerformanceRules = [...this.performanceRules];
+  }
+
+  // 标签页切换
+  switchTab(tab: string): void {
+    this.activeTab = tab;
+  }
+
+  // 搜索过滤方法
+  onWorkflowSearch(): void {
+    if (this.workflowSearchTerm) {
+      const term = this.workflowSearchTerm.toLowerCase();
+      this.filteredWorkflowStages = this.workflowStages.filter(stage => 
+        stage.name.toLowerCase().includes(term) ||
+        stage.description.toLowerCase().includes(term)
+      );
+    } else {
+      this.filteredWorkflowStages = [...this.workflowStages];
+    }
+  }
+
+  onSOPSearch(): void {
+    if (this.sopSearchTerm) {
+      const term = this.sopSearchTerm.toLowerCase();
+      this.filteredSOPTemplates = this.sopTemplates.filter(template => 
+        template.name.toLowerCase().includes(term) ||
+        template.description.toLowerCase().includes(term) ||
+        template.category.toLowerCase().includes(term)
+      );
+    } else {
+      this.filteredSOPTemplates = [...this.sopTemplates];
+    }
+  }
+
+  onPricingSearch(): void {
+    if (this.pricingSearchTerm) {
+      const term = this.pricingSearchTerm.toLowerCase();
+      this.filteredPricingRules = this.pricingRules.filter(rule => 
+        rule.name.toLowerCase().includes(term) ||
+        rule.description.toLowerCase().includes(term) ||
+        rule.category.toLowerCase().includes(term)
+      );
+    } else {
+      this.filteredPricingRules = [...this.pricingRules];
+    }
+  }
+
+  onPerformanceSearch(): void {
+    if (this.performanceSearchTerm) {
+      const term = this.performanceSearchTerm.toLowerCase();
+      this.filteredPerformanceRules = this.performanceRules.filter(rule => 
+        rule.name.toLowerCase().includes(term) ||
+        rule.description.toLowerCase().includes(term) ||
+        rule.metric.toLowerCase().includes(term)
+      );
+    } else {
+      this.filteredPerformanceRules = [...this.performanceRules];
+    }
+  }
+
+  // 对话框方法
+  openWorkflowDialog(stage?: WorkflowStage): void {
+    const dialogRef = this.dialog.open(SettingDialogComponent, {
+      width: '600px',
+      data: {
+        type: 'workflow',
+        item: stage ? { ...stage } : {
+          id: '',
+          name: '',
+          description: '',
+          order: 0,
+          isActive: true
+        }
+      }
+    });
+
+    dialogRef.afterClosed().subscribe((result: any) => {
+      if (result) {
+        this.loadWorkflowStages();
+      }
+    });
+  }
+
+  openSOPDialog(template?: SOPTemplate): void {
+    const dialogRef = this.dialog.open(SettingDialogComponent, {
+      width: '600px',
+      data: {
+        type: 'sop',
+        item: template ? { ...template } : {
+          id: '',
+          name: '',
+          description: '',
+          steps: [],
+          isActive: true
+        }
+      }
+    });
+
+    dialogRef.afterClosed().subscribe((result: any) => {
+      if (result) {
+        this.loadSOPTemplates();
+      }
+    });
+  }
+
+  openPricingDialog(rule?: PricingRule): void {
+    const dialogRef = this.dialog.open(SettingDialogComponent, {
+      width: '600px',
+      data: {
+        type: 'pricing',
+        item: rule ? { ...rule } : {
+          id: '',
+          name: '',
+          description: '',
+          formula: '',
+          baseAmount: 0,
+          isActive: true
+        }
+      }
+    });
+
+    dialogRef.afterClosed().subscribe((result: any) => {
+      if (result) {
+        this.loadPricingRules();
+      }
+    });
+  }
+
+  openPerformanceDialog(rule?: PerformanceRule): void {
+    const dialogRef = this.dialog.open(SettingDialogComponent, {
+      width: '600px',
+      data: {
+        type: 'performance',
+        item: rule ? { ...rule } : {
+          id: '',
+          name: '',
+          description: '',
+          criteria: {},
+          isActive: true
+        }
+      }
+    });
+
+    dialogRef.afterClosed().subscribe((result: any) => {
+      if (result) {
+        this.loadPerformanceRules();
+      }
+    });
+  }
+
+  // 处理设置更新
+  handleSettingUpdate(type: string, updatedItem: any): void {
+    switch (type) {
+      case 'workflow':
+        if (updatedItem.id) {
+          // 更新现有阶段
+          this.workflowStages = this.workflowStages.map(stage => 
+            stage.id === updatedItem.id ? updatedItem : stage
+          );
+        } else {
+          // 添加新阶段
+          updatedItem.id = (this.workflowStages.length + 1).toString();
+          this.workflowStages.push(updatedItem);
+        }
+        this.onWorkflowSearch();
+        break;
+      case 'sop':
+        if (updatedItem.id) {
+          // 更新现有模板
+          this.sopTemplates = this.sopTemplates.map(template => 
+            template.id === updatedItem.id ? updatedItem : template
+          );
+        } else {
+          // 添加新模板
+          updatedItem.id = (this.sopTemplates.length + 1).toString();
+          this.sopTemplates.push(updatedItem);
+        }
+        this.onSOPSearch();
+        break;
+      case 'pricing':
+        if (updatedItem.id) {
+          // 更新现有规则
+          this.pricingRules = this.pricingRules.map(rule => 
+            rule.id === updatedItem.id ? updatedItem : rule
+          );
+        } else {
+          // 添加新规则
+          updatedItem.id = (this.pricingRules.length + 1).toString();
+          this.pricingRules.push(updatedItem);
+        }
+        this.onPricingSearch();
+        break;
+      case 'performance':
+        if (updatedItem.id) {
+          // 更新现有规则
+          this.performanceRules = this.performanceRules.map(rule => 
+            rule.id === updatedItem.id ? updatedItem : rule
+          );
+        } else {
+          // 添加新规则
+          updatedItem.id = (this.performanceRules.length + 1).toString();
+          this.performanceRules.push(updatedItem);
+        }
+        this.onPerformanceSearch();
+        break;
+    }
+  }
+
+  // 删除设置项
+  deleteSetting(type: string, id: string): void {
+    if (confirm('确定要删除此项设置吗?')) {
+      switch (type) {
+        case 'workflow':
+          this.workflowStages = this.workflowStages.filter(stage => stage.id !== id);
+          this.onWorkflowSearch();
+          break;
+        case 'sop':
+          this.sopTemplates = this.sopTemplates.filter(template => template.id !== id);
+          this.onSOPSearch();
+          break;
+        case 'pricing':
+          this.pricingRules = this.pricingRules.filter(rule => rule.id !== id);
+          this.onPricingSearch();
+          break;
+        case 'performance':
+          this.performanceRules = this.performanceRules.filter(rule => rule.id !== id);
+          this.onPerformanceSearch();
+          break;
+      }
+    }
+  }
+
+  // 更新设置项的激活状态
+  toggleActive(type: string, id: string, isActive: boolean): void {
+    switch (type) {
+      case 'workflow':
+        this.workflowStages = this.workflowStages.map(stage => 
+          stage.id === id ? { ...stage, isActive } : stage
+        );
+        this.onWorkflowSearch();
+        break;
+      case 'sop':
+        this.sopTemplates = this.sopTemplates.map(template => 
+          template.id === id ? { ...template, isActive } : template
+        );
+        this.onSOPSearch();
+        break;
+      case 'pricing':
+        this.pricingRules = this.pricingRules.map(rule => 
+          rule.id === id ? { ...rule, isActive } : rule
+        );
+        this.onPricingSearch();
+        break;
+      case 'performance':
+        this.performanceRules = this.performanceRules.map(rule => 
+          rule.id === id ? { ...rule, isActive } : rule
+        );
+        this.onPerformanceSearch();
+        break;
+    }
+  }
+
+  // 保存系统配置
+  saveSystemOptions(): void {
+    // 模拟保存操作
+    alert('系统配置已保存!');
+  }
+}

+ 119 - 0
src/app/pages/admin/user-management/role-dialog/role-dialog.html

@@ -0,0 +1,119 @@
+<div class="role-dialog">
+  <!-- 对话框标题 -->
+  <div class="dialog-header">
+    <h2 class="dialog-title">{{ isEditMode ? '编辑角色' : '添加新角色' }}</h2>
+    <button mat-icon-button class="close-btn" (click)="onCancel()">
+      <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="18" y1="6" x2="6" y2="18"></line>
+        <line x1="6" y1="6" x2="18" y2="18"></line>
+      </svg>
+    </button>
+  </div>
+
+  <!-- 表单内容 -->
+  <div class="dialog-content">
+    <form class="role-form">
+      <!-- 角色基本信息 -->
+      <div class="form-section">
+        <h3 class="section-title">基本信息</h3>
+        
+        <!-- 角色名称 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>角色名称 *</mat-label>
+            <input matInput type="text" [(ngModel)]="role.name" name="name"
+                   placeholder="请输入角色名称" [disabled]="role.name === '超级管理员'">
+            <mat-icon matSuffix>supervised_user_circle</mat-icon>
+            <mat-hint *ngIf="role.name === '超级管理员'">超级管理员角色名称不可修改</mat-hint>
+          </mat-form-field>
+        </div>
+
+        <!-- 角色描述 -->
+        <div class="form-group">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>角色描述 *</mat-label>
+            <textarea matInput [(ngModel)]="role.description" name="description"
+                      placeholder="请输入角色描述" rows="3"></textarea>
+            <mat-icon matSuffix>description</mat-icon>
+          </mat-form-field>
+        </div>
+
+        <!-- 用户数量显示 -->
+        <div class="form-group" *ngIf="isEditMode">
+          <mat-form-field appearance="outline" class="form-field">
+            <mat-label>关联用户数</mat-label>
+            <input matInput type="text" value="{{ role.usersCount }}" disabled>
+            <mat-icon matSuffix>people</mat-icon>
+            <mat-hint>当前有 {{ role.usersCount }} 个用户分配了此角色</mat-hint>
+          </mat-form-field>
+        </div>
+      </div>
+
+      <!-- 权限设置 -->
+      <div class="form-section">
+        <div class="section-header">
+          <h3 class="section-title">权限设置</h3>
+          <div class="permission-summary" [class.warning]="selectedPermissionsCount === 0">
+            已选择 {{ selectedPermissionsCount }}/{{ totalPermissionsCount }} 项权限
+          </div>
+        </div>
+
+        <!-- 权限组列表 -->
+        <div class="permission-groups">
+          <!-- 超级管理员特殊提示 -->
+          <div class="permission-tip" *ngIf="role.name === '超级管理员'">
+            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#FFAA00" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+              <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
+              <line x1="12" y1="9" x2="12" y2="13"></line>
+              <line x1="12" y1="17" x2="12.01" y2="17"></line>
+            </svg>
+            超级管理员角色默认拥有所有权限,无需手动选择
+          </div>
+
+          <!-- 权限组 -->
+          <div *ngIf="role.name !== '超级管理员'" class="permission-group">
+            <div *ngFor="let group of permissionGroups" class="permission-group-item">
+              <!-- 权限组头部 -->
+              <div class="group-header">
+                <mat-checkbox
+                  [checked]="isPermissionGroupSelected(group)"
+                  [indeterminate]="isPermissionGroupIndeterminate(group)"
+                  (change)="togglePermissionGroup(group)"
+                  class="group-checkbox">
+                  <span class="group-name">{{ group.name }}</span>
+                  <span class="group-count">({{ group.permissions.length }})</span>
+                </mat-checkbox>
+              </div>
+              
+              <!-- 权限列表 -->
+              <div class="permissions-list">
+                <div *ngFor="let permission of group.permissions" class="permission-item">
+                  <mat-checkbox
+                    [checked]="isPermissionSelected(permission.key)"
+                    (change)="togglePermission(permission.key)"
+                    class="permission-checkbox">
+                    <div class="permission-info">
+                      <div class="permission-name">{{ permission.name }}</div>
+                      <div class="permission-description">{{ permission.description }}</div>
+                    </div>
+                  </mat-checkbox>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </form>
+  </div>
+
+  <!-- 操作按钮 -->
+  <div class="dialog-footer">
+    <button mat-button class="cancel-btn" (click)="onCancel()">
+      取消
+    </button>
+    <button mat-raised-button color="primary" class="save-btn" 
+            (click)="onSave()">
+      {{ isEditMode ? '保存修改' : '创建角色' }}
+    </button>
+  </div>
+</div>

+ 360 - 0
src/app/pages/admin/user-management/role-dialog/role-dialog.scss

@@ -0,0 +1,360 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$secondary-color: #6B7280;
+$success-color: #00B42A;
+$warning-color: #FFAA00;
+$error-color: #F53F3F;
+$text-primary: #333333;
+$text-secondary: #666666;
+$text-tertiary: #999999;
+$border-color: #E5E7EB;
+$bg-light: #F9FAFB;
+$bg-white: #FFFFFF;
+$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+
+// 对话框容器
+.role-dialog {
+  min-width: 600px;
+  border-radius: 12px;
+  overflow: hidden;
+  background-color: $bg-white;
+}
+
+// 对话框头部
+.dialog-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px 24px;
+  border-bottom: 1px solid $border-color;
+  background-color: $bg-light;
+
+  .dialog-title {
+    font-size: 18px;
+    font-weight: 600;
+    color: $text-primary;
+    margin: 0;
+  }
+
+  .close-btn {
+    color: $text-tertiary;
+    padding: 4px;
+    border-radius: 6px;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: #E5E7EB;
+      color: $text-secondary;
+    }
+  }
+}
+
+// 对话框内容
+.dialog-content {
+  padding: 24px;
+  max-height: 60vh;
+  overflow-y: auto;
+}
+
+// 表单区域
+.form-section {
+  margin-bottom: 24px;
+  padding-bottom: 24px;
+  border-bottom: 1px solid $border-color;
+
+  &:last-child {
+    border-bottom: none;
+    margin-bottom: 0;
+    padding-bottom: 0;
+  }
+
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+  }
+
+  .section-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: $text-primary;
+    margin: 0;
+  }
+
+  .permission-summary {
+    font-size: 13px;
+    color: $text-secondary;
+    font-weight: 500;
+    padding: 4px 12px;
+    border-radius: 16px;
+    background-color: #F0F9FF;
+
+    &.warning {
+      background-color: #FFF9EB;
+      color: $warning-color;
+    }
+  }
+}
+
+// 表单组
+.form-group {
+  margin-bottom: 16px;
+
+  .form-field {
+    width: 100%;
+    .mat-form-field-wrapper {
+      .mat-form-field-flex {
+        border-radius: 8px;
+      }
+
+      .mat-form-field-outline {
+        border-radius: 8px;
+      }
+
+      .mat-form-field-infix {
+        padding: 10px 0;
+
+        input,
+        textarea {
+          font-size: 14px;
+          color: $text-primary;
+        }
+
+        .mat-input-element {
+          &::placeholder {
+            color: $text-tertiary;
+          }
+
+          &:disabled {
+            color: $text-tertiary;
+          }
+        }
+
+        textarea {
+          resize: vertical;
+          min-height: 80px;
+        }
+      }
+
+      .mat-form-field-label {
+        font-size: 14px;
+        color: $text-secondary;
+      }
+
+      .mat-form-field-suffix {
+        .mat-icon {
+          color: $text-tertiary;
+        }
+      }
+
+      .mat-form-field-hint {
+        font-size: 12px;
+        color: $text-tertiary;
+        margin-top: 4px;
+      }
+    }
+  }
+}
+
+// 权限组
+.permission-groups {
+  .permission-tip {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 16px;
+    background-color: #FFF9EB;
+    border: 1px solid #FFE7BA;
+    border-radius: 8px;
+    margin-bottom: 20px;
+    font-size: 14px;
+    color: #D46B08;
+
+    svg {
+      flex-shrink: 0;
+    }
+  }
+
+  .permission-group-item {
+    margin-bottom: 20px;
+    background-color: $bg-light;
+    border-radius: 8px;
+    overflow: hidden;
+
+    &:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  .group-header {
+    padding: 12px 16px;
+    background-color: #F3F4F6;
+    border-bottom: 1px solid $border-color;
+
+    .group-checkbox {
+      .mat-checkbox-layout {
+        margin: 0;
+
+        .mat-checkbox-label {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+          font-size: 14px;
+          font-weight: 600;
+          color: $text-primary;
+
+          .group-name {
+            flex: 1;
+          }
+
+          .group-count {
+            font-size: 12px;
+            color: $text-tertiary;
+            font-weight: 400;
+          }
+        }
+      }
+    }
+  }
+
+  .permissions-list {
+    max-height: 200px;
+    overflow-y: auto;
+
+    .permission-item {
+      padding: 8px 16px;
+      border-bottom: 1px solid $border-color;
+
+      &:last-child {
+        border-bottom: none;
+      }
+
+      .permission-checkbox {
+        .mat-checkbox-layout {
+          margin: 0;
+          width: 100%;
+
+          .mat-checkbox-label {
+            display: block;
+            font-size: 14px;
+            color: $text-primary;
+          }
+        }
+      }
+
+      .permission-info {
+        .permission-name {
+          font-weight: 500;
+          margin-bottom: 2px;
+        }
+
+        .permission-description {
+          font-size: 12px;
+          color: $text-tertiary;
+          line-height: 1.4;
+        }
+      }
+    }
+  }
+}
+
+// 对话框底部
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  padding: 20px 24px;
+  border-top: 1px solid $border-color;
+  background-color: $bg-light;
+
+  .cancel-btn {
+    padding: 8px 24px;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    color: $text-secondary;
+    background-color: $bg-white;
+    border: 1px solid $border-color;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: #F3F4F6;
+      border-color: $text-tertiary;
+    }
+  }
+
+  .save-btn {
+    padding: 8px 24px;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    background-color: $primary-color;
+    color: $bg-white;
+    transition: all 0.2s ease;
+    box-shadow: $shadow-sm;
+
+    &:hover {
+      background-color: #0E4BD8;
+      box-shadow: $shadow-md;
+      transform: translateY(-1px);
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .role-dialog {
+    min-width: auto;
+    width: 90vw;
+    max-width: 500px;
+  }
+
+  .section-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 8px;
+  }
+}
+
+@media (max-width: 480px) {
+  .dialog-header,
+  .dialog-content,
+  .dialog-footer {
+    padding: 16px;
+  }
+
+  .dialog-title {
+    font-size: 16px !important;
+  }
+
+  .cancel-btn,
+  .save-btn {
+    padding: 6px 16px !important;
+    font-size: 13px !important;
+  }
+}
+
+// 滚动条样式
+.dialog-content,
+.permissions-list {
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: $bg-light;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: $border-color;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb:hover {
+    background: $text-tertiary;
+  }
+}

+ 223 - 0
src/app/pages/admin/user-management/role-dialog/role-dialog.ts

@@ -0,0 +1,223 @@
+import { Component, Inject } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { MatIconModule } from '@angular/material/icon';
+import { MatCheckboxModule } from '@angular/material/checkbox';
+
+interface Role {
+  id: string;
+  name: string;
+  description: string;
+  permissions: string[];
+  usersCount: number;
+}
+
+interface PermissionGroup {
+  name: string;
+  permissions: Permission[];
+}
+
+interface Permission {
+  key: string;
+  name: string;
+  description: string;
+}
+
+@Component({
+  selector: 'app-role-dialog',
+  standalone: true,
+  imports: [
+    CommonModule,
+    FormsModule,
+    MatButtonModule,
+    MatDialogModule,
+    MatFormFieldModule,
+    MatInputModule,
+    MatIconModule,
+    MatCheckboxModule
+  ],
+  templateUrl: './role-dialog.html',
+  styleUrl: './role-dialog.scss'
+})
+export class RoleDialogComponent {
+  role: Role;
+  isEditMode: boolean;
+  
+  // 权限组列表
+  permissionGroups: PermissionGroup[] = [
+    {
+      name: '系统管理',
+      permissions: [
+        { key: 'system:admin', name: '超级管理员权限', description: '拥有系统内所有功能的完全访问权限' },
+        { key: 'system:view', name: '系统概览', description: '查看系统整体运行状态和统计数据' }
+      ]
+    },
+    {
+      name: '项目管理',
+      permissions: [
+        { key: 'project:manage', name: '项目管理', description: '创建、编辑、删除和分配项目' },
+        { key: 'project:view', name: '项目查看', description: '查看项目详情和进度' },
+        { key: 'project:review', name: '项目审核', description: '审核和审批项目设计方案' },
+        { key: 'project:quality', name: '质量管理', description: '管理项目质量和验收流程' }
+      ]
+    },
+    {
+      name: '用户管理',
+      permissions: [
+        { key: 'user:manage', name: '用户管理', description: '创建、编辑、删除用户账号' },
+        { key: 'user:view', name: '用户查看', description: '查看用户信息和状态' },
+        { key: 'role:manage', name: '角色管理', description: '创建、编辑、删除角色和权限' }
+      ]
+    },
+    {
+      name: '财务管理',
+      permissions: [
+        { key: 'finance:manage', name: '财务管理', description: '管理项目预算、收支和结算' },
+        { key: 'finance:view', name: '财务查看', description: '查看财务报表和统计数据' },
+        { key: 'finance:export', name: '财务导出', description: '导出财务数据和报表' }
+      ]
+    },
+    {
+      name: '客户管理',
+      permissions: [
+        { key: 'customer:manage', name: '客户管理', description: '创建、编辑、删除客户信息' },
+        { key: 'customer:view', name: '客户查看', description: '查看客户信息和历史记录' },
+        { key: 'case:view', name: '案例查看', description: '查看设计案例库' }
+      ]
+    },
+    {
+      name: '系统监控',
+      permissions: [
+        { key: 'log:view', name: '日志查看', description: '查看系统操作日志' },
+        { key: 'log:export', name: '日志导出', description: '导出系统操作日志' }
+      ]
+    },
+    {
+      name: 'API集成',
+      permissions: [
+        { key: 'api:manage', name: 'API管理', description: '配置和管理第三方API集成' },
+        { key: 'api:view', name: 'API查看', description: '查看API配置和状态' }
+      ]
+    }
+  ];
+
+  constructor(
+    public dialogRef: MatDialogRef<RoleDialogComponent>,
+    @Inject(MAT_DIALOG_DATA) public data: Role
+  ) {
+    this.isEditMode = !!data.id;
+    this.role = {
+      id: data.id || '',
+      name: data.name || '',
+      description: data.description || '',
+      permissions: data.permissions ? [...data.permissions] : [],
+      usersCount: data.usersCount || 0
+    };
+  }
+
+  onCancel(): void {
+    this.dialogRef.close();
+  }
+
+  onSave(): void {
+    if (this.isFormValid()) {
+      this.dialogRef.close(this.role);
+    }
+  }
+
+  isFormValid(): boolean {
+    // 验证角色名称
+    if (!this.role.name.trim()) {
+      alert('请输入角色名称');
+      return false;
+    }
+
+    // 验证角色描述
+    if (!this.role.description.trim()) {
+      alert('请输入角色描述');
+      return false;
+    }
+
+    // 验证权限选择
+    if (this.role.permissions.length === 0 && this.role.name !== '超级管理员') {
+      alert('请至少选择一项权限');
+      return false;
+    }
+
+    // 超级管理员默认拥有所有权限
+    if (this.role.name === '超级管理员') {
+      const allPermissions = this.permissionGroups
+        .flatMap(group => group.permissions)
+        .map(permission => permission.key);
+      this.role.permissions = allPermissions;
+    }
+
+    return true;
+  }
+
+  // 权限选择方法
+  togglePermission(permissionKey: string): void {
+    const index = this.role.permissions.indexOf(permissionKey);
+    if (index > -1) {
+      // 取消选择
+      this.role.permissions.splice(index, 1);
+    } else {
+      // 添加选择
+      this.role.permissions.push(permissionKey);
+    }
+  }
+
+  // 检查权限是否已选择
+  isPermissionSelected(permissionKey: string): boolean {
+    return this.role.permissions.includes(permissionKey);
+  }
+
+  // 全选/取消全选权限组
+  togglePermissionGroup(group: PermissionGroup): void {
+    const groupPermissionKeys = group.permissions.map(p => p.key);
+    const allSelected = groupPermissionKeys.every(key => this.isPermissionSelected(key));
+    
+    if (allSelected) {
+      // 取消全选
+      this.role.permissions = this.role.permissions.filter(key => 
+        !groupPermissionKeys.includes(key)
+      );
+    } else {
+      // 全选
+      groupPermissionKeys.forEach(key => {
+        if (!this.isPermissionSelected(key)) {
+          this.role.permissions.push(key);
+        }
+      });
+    }
+  }
+
+  // 检查权限组是否全部已选择
+  isPermissionGroupSelected(group: PermissionGroup): boolean {
+    return group.permissions.every(permission => 
+      this.isPermissionSelected(permission.key)
+    );
+  }
+
+  // 检查权限组是否部分已选择
+  isPermissionGroupIndeterminate(group: PermissionGroup): boolean {
+    const selectedCount = group.permissions.filter(permission => 
+      this.isPermissionSelected(permission.key)
+    ).length;
+    return selectedCount > 0 && selectedCount < group.permissions.length;
+  }
+
+  // 获取当前已选择的权限数量
+  get selectedPermissionsCount(): number {
+    return this.role.permissions.length;
+  }
+
+  // 获取所有可选择的权限数量
+  get totalPermissionsCount(): number {
+    return this.permissionGroups.flatMap(group => group.permissions).length;
+  }
+}

+ 164 - 0
src/app/pages/admin/user-management/user-dialog/user-dialog.html

@@ -0,0 +1,164 @@
+<div class="user-dialog">
+  <!-- 对话框标题 -->
+  <div class="dialog-header">
+    <h2 class="dialog-title">{{ isEditMode ? '编辑用户' : '添加新用户' }}</h2>
+    <button mat-icon-button class="close-btn" (click)="onCancel()">
+      <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="18" y1="6" x2="6" y2="18"></line>
+        <line x1="6" y1="6" x2="18" y2="18"></line>
+      </svg>
+    </button>
+  </div>
+
+  <!-- 表单内容 -->
+  <div class="dialog-content">
+    <form class="user-form">
+      <!-- 基本信息 -->
+      <div class="form-section">
+        <h3 class="section-title">基本信息</h3>
+        
+        <!-- 用户名 -->
+        <div class="form-row">
+          <div class="form-group">
+            <mat-form-field appearance="outline" class="form-field">
+              <mat-label>用户名 *</mat-label>
+              <input matInput type="text" [(ngModel)]="user.username" name="username"
+                     placeholder="请输入用户名" [disabled]="isEditMode">
+              <mat-icon matSuffix>person</mat-icon>
+              <mat-hint>至少3个字符,编辑模式下不可修改</mat-hint>
+            </mat-form-field>
+          </div>
+          
+          <!-- 真实姓名 -->
+          <div class="form-group">
+            <mat-form-field appearance="outline" class="form-field">
+              <mat-label>真实姓名 *</mat-label>
+              <input matInput type="text" [(ngModel)]="user.realName" name="realName"
+                     placeholder="请输入真实姓名">
+              <mat-icon matSuffix>face</mat-icon>
+            </mat-form-field>
+          </div>
+        </div>
+
+        <!-- 联系方式 -->
+        <div class="form-row">
+          <div class="form-group">
+            <mat-form-field appearance="outline" class="form-field">
+              <mat-label>电子邮箱 *</mat-label>
+              <input matInput type="email" [(ngModel)]="user.email" name="email"
+                     placeholder="请输入电子邮箱">
+              <mat-icon matSuffix>email</mat-icon>
+            </mat-form-field>
+          </div>
+          
+          <div class="form-group">
+            <mat-form-field appearance="outline" class="form-field">
+              <mat-label>手机号码 *</mat-label>
+              <input matInput type="tel" [(ngModel)]="user.phone" name="phone"
+                     placeholder="请输入手机号码">
+              <mat-icon matSuffix>phone</mat-icon>
+            </mat-form-field>
+          </div>
+        </div>
+
+        <!-- 密码生成按钮 -->
+        <div class="form-group">
+          <button mat-button class="generate-password-btn" (click)="generateRandomPassword()"
+                  type="button">
+            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+              <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
+              <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
+            </svg>
+            生成随机密码
+          </button>
+          <mat-hint>新用户创建时请务必保存生成的密码</mat-hint>
+        </div>
+      </div>
+
+      <!-- 工作信息 -->
+      <div class="form-section">
+        <h3 class="section-title">工作信息</h3>
+        
+        <div class="form-row">
+          <!-- 角色选择 -->
+          <div class="form-group">
+            <mat-form-field appearance="outline" class="form-field">
+              <mat-label>用户角色 *</mat-label>
+              <mat-select [(ngModel)]="user.role" name="role" (selectionChange)="onRoleChange()">
+                <mat-option *ngFor="let role of roles" [value]="role">
+                  {{ role }}
+                </mat-option>
+              </mat-select>
+              <mat-icon matSuffix>supervised_user_circle</mat-icon>
+            </mat-form-field>
+          </div>
+          
+          <!-- 部门选择 -->
+          <div class="form-group">
+            <mat-form-field appearance="outline" class="form-field">
+              <mat-label>所属部门 *</mat-label>
+              <mat-select [(ngModel)]="user.department" name="department">
+                <mat-option *ngFor="let dept of departments" [value]="dept">
+                  {{ dept }}
+                </mat-option>
+              </mat-select>
+              <mat-icon matSuffix>business</mat-icon>
+            </mat-form-field>
+          </div>
+        </div>
+
+        <!-- 状态选择 -->
+        <div class="form-row">
+          <div class="form-group">
+            <mat-form-field appearance="outline" class="form-field">
+              <mat-label>账号状态</mat-label>
+              <mat-select [(ngModel)]="user.status" name="status">
+                <mat-option *ngFor="let status of statuses" [value]="status.value">
+                  {{ status.label }}
+                </mat-option>
+              </mat-select>
+              <mat-icon matSuffix>toggle_on</mat-icon>
+            </mat-form-field>
+          </div>
+          
+          <!-- 创建日期 -->
+          <div class="form-group">
+            <mat-form-field appearance="outline" class="form-field">
+              <mat-label>创建日期</mat-label>
+              <input matInput [matDatepicker]="datePicker" [(ngModel)]="user.createdAt" name="createdAt"
+                     [disabled]="isEditMode">
+              <mat-datepicker-toggle matSuffix [for]="datePicker"></mat-datepicker-toggle>
+              <mat-datepicker #datePicker></mat-datepicker>
+            </mat-form-field>
+          </div>
+        </div>
+      </div>
+
+      <!-- 额外信息 -->
+      <div class="form-section" *ngIf="isEditMode">
+        <h3 class="section-title">额外信息</h3>
+        
+        <div class="form-row">
+          <div class="form-group">
+            <mat-form-field appearance="outline" class="form-field">
+              <mat-label>最后登录时间</mat-label>
+              <input matInput type="text" [(ngModel)]="user.lastLogin" name="lastLogin" disabled>
+              <mat-icon matSuffix>access_time</mat-icon>
+            </mat-form-field>
+          </div>
+        </div>
+      </div>
+    </form>
+  </div>
+
+  <!-- 操作按钮 -->
+  <div class="dialog-footer">
+    <button mat-button class="cancel-btn" (click)="onCancel()">
+      取消
+    </button>
+    <button mat-raised-button color="primary" class="save-btn" 
+            (click)="onSave()">
+      {{ isEditMode ? '保存修改' : '创建用户' }}
+    </button>
+  </div>
+</div>

+ 255 - 0
src/app/pages/admin/user-management/user-dialog/user-dialog.scss

@@ -0,0 +1,255 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$secondary-color: #6B7280;
+$success-color: #00B42A;
+$warning-color: #FFAA00;
+$error-color: #F53F3F;
+$text-primary: #333333;
+$text-secondary: #666666;
+$text-tertiary: #999999;
+$border-color: #E5E7EB;
+$bg-light: #F9FAFB;
+$bg-white: #FFFFFF;
+$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+
+// 对话框容器
+.user-dialog {
+  min-width: 600px;
+  border-radius: 12px;
+  overflow: hidden;
+  background-color: $bg-white;
+}
+
+// 对话框头部
+.dialog-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px 24px;
+  border-bottom: 1px solid $border-color;
+  background-color: $bg-light;
+
+  .dialog-title {
+    font-size: 18px;
+    font-weight: 600;
+    color: $text-primary;
+    margin: 0;
+  }
+
+  .close-btn {
+    color: $text-tertiary;
+    padding: 4px;
+    border-radius: 6px;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: #E5E7EB;
+      color: $text-secondary;
+    }
+  }
+}
+
+// 对话框内容
+.dialog-content {
+  padding: 24px;
+  max-height: 60vh;
+  overflow-y: auto;
+}
+
+// 表单区域
+.form-section {
+  margin-bottom: 24px;
+  padding-bottom: 24px;
+  border-bottom: 1px solid $border-color;
+
+  &:last-child {
+    border-bottom: none;
+    margin-bottom: 0;
+    padding-bottom: 0;
+  }
+
+  .section-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: $text-primary;
+    margin-bottom: 16px;
+  }
+}
+
+// 表单行
+.form-row {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 20px;
+  margin-bottom: 16px;
+}
+
+// 表单组
+.form-group {
+  .form-field {
+    width: 100%;
+    .mat-form-field-wrapper {
+      .mat-form-field-flex {
+        border-radius: 8px;
+      }
+
+      .mat-form-field-outline {
+        border-radius: 8px;
+      }
+
+      .mat-form-field-infix {
+        padding: 10px 0;
+
+        input {
+          font-size: 14px;
+          color: $text-primary;
+        }
+
+        .mat-input-element {
+          &::placeholder {
+            color: $text-tertiary;
+          }
+
+          &:disabled {
+            color: $text-tertiary;
+          }
+        }
+      }
+
+      .mat-form-field-label {
+        font-size: 14px;
+        color: $text-secondary;
+      }
+
+      .mat-form-field-suffix {
+        .mat-icon {
+          color: $text-tertiary;
+        }
+      }
+
+      .mat-form-field-hint {
+        font-size: 12px;
+        color: $text-tertiary;
+        margin-top: 4px;
+      }
+    }
+  }
+}
+
+// 密码生成按钮
+.generate-password-btn {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  color: $primary-color;
+  border: 1px solid $primary-color;
+  border-radius: 8px;
+  padding: 8px 16px;
+  font-size: 14px;
+  font-weight: 500;
+  background-color: $bg-white;
+  transition: all 0.2s ease;
+  margin-bottom: 8px;
+
+  &:hover {
+    background-color: $primary-color;
+    color: $bg-white;
+  }
+}
+
+// 对话框底部
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  padding: 20px 24px;
+  border-top: 1px solid $border-color;
+  background-color: $bg-light;
+
+  .cancel-btn {
+    padding: 8px 24px;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    color: $text-secondary;
+    background-color: $bg-white;
+    border: 1px solid $border-color;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: #F3F4F6;
+      border-color: $text-tertiary;
+    }
+  }
+
+  .save-btn {
+    padding: 8px 24px;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    background-color: $primary-color;
+    color: $bg-white;
+    transition: all 0.2s ease;
+    box-shadow: $shadow-sm;
+
+    &:hover {
+      background-color: #0E4BD8;
+      box-shadow: $shadow-md;
+      transform: translateY(-1px);
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .user-dialog {
+    min-width: auto;
+    width: 90vw;
+    max-width: 500px;
+  }
+
+  .form-row {
+    grid-template-columns: 1fr !important;
+  }
+}
+
+@media (max-width: 480px) {
+  .dialog-header,
+  .dialog-content,
+  .dialog-footer {
+    padding: 16px;
+  }
+
+  .dialog-title {
+    font-size: 16px !important;
+  }
+
+  .cancel-btn,
+  .save-btn {
+    padding: 6px 16px !important;
+    font-size: 13px !important;
+  }
+}
+
+// 滚动条样式
+.dialog-content {
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: $bg-light;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: $border-color;
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb:hover {
+    background: $text-tertiary;
+  }
+}

+ 134 - 0
src/app/pages/admin/user-management/user-dialog/user-dialog.ts

@@ -0,0 +1,134 @@
+import { Component, Inject } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
+import { MatDatepickerModule } from '@angular/material/datepicker';
+import { MatNativeDateModule } from '@angular/material/core';
+import { MatIconModule } from '@angular/material/icon';
+
+interface User {
+  id: string;
+  username: string;
+  realName: string;
+  email: string;
+  phone: string;
+  role: string;
+  department: string;
+  status: 'active' | 'inactive';
+  createdAt: string;
+  lastLogin: string;
+}
+
+@Component({
+  selector: 'app-user-dialog',
+  standalone: true,
+  imports: [
+    CommonModule,
+    FormsModule,
+    MatButtonModule,
+    MatDialogModule,
+    MatFormFieldModule,
+    MatInputModule,
+    MatSelectModule,
+    MatDatepickerModule,
+    MatNativeDateModule,
+    MatIconModule
+  ],
+  templateUrl: './user-dialog.html',
+  styleUrl: './user-dialog.scss'
+})
+export class UserDialogComponent {
+  user: User;
+  isEditMode: boolean;
+  roles: string[] = ['超级管理员', '客服', '设计师', '组长', '财务', '人事'];
+  departments: string[] = ['管理层', '客户服务部', '设计部', '财务部', '人力资源部', '行政部'];
+  statuses: { value: string; label: string }[] = [
+    { value: 'active', label: '激活' },
+    { value: 'inactive', label: '禁用' }
+  ];
+
+  constructor(
+    public dialogRef: MatDialogRef<UserDialogComponent>,
+    @Inject(MAT_DIALOG_DATA) public data: User
+  ) {
+    this.isEditMode = !!data.id;
+    this.user = {
+      id: data.id || '',
+      username: data.username || '',
+      realName: data.realName || '',
+      email: data.email || '',
+      phone: data.phone || '',
+      role: data.role || '',
+      department: data.department || '',
+      status: data.status || 'active',
+      createdAt: data.createdAt || new Date().toISOString().split('T')[0],
+      lastLogin: data.lastLogin || ''
+    };
+  }
+
+  onCancel(): void {
+    this.dialogRef.close();
+  }
+
+  onSave(): void {
+    if (this.isFormValid()) {
+      this.dialogRef.close(this.user);
+    }
+  }
+
+  isFormValid(): boolean {
+    // 验证用户名
+    if (!this.user.username.trim() || this.user.username.length < 3) {
+      alert('用户名至少需要3个字符');
+      return false;
+    }
+
+    // 验证姓名
+    if (!this.user.realName.trim()) {
+      alert('请输入真实姓名');
+      return false;
+    }
+
+    // 验证邮箱
+    const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+    if (!emailRegex.test(this.user.email)) {
+      alert('请输入有效的邮箱地址');
+      return false;
+    }
+
+    // 验证手机号
+    const phoneRegex = /^1[3-9]\d{9}$/;
+    if (!phoneRegex.test(this.user.phone)) {
+      alert('请输入有效的手机号码');
+      return false;
+    }
+
+    // 验证角色和部门
+    if (!this.user.role || !this.user.department) {
+      alert('请选择角色和部门');
+      return false;
+    }
+
+    return true;
+  }
+
+  generateRandomPassword(): void {
+    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()';
+    let password = '';
+    for (let i = 0; i < 12; i++) {
+      password += chars.charAt(Math.floor(Math.random() * chars.length));
+    }
+    alert(`生成的随机密码:${password}\n请妥善保存!`);
+  }
+
+  onRoleChange(): void {
+    // 超级管理员只能属于管理层
+    if (this.user.role === '超级管理员') {
+      this.user.department = '管理层';
+    }
+  }
+}

+ 493 - 0
src/app/pages/admin/user-management/user-management.html

@@ -0,0 +1,493 @@
+<div class="user-management">
+  <!-- 页面标题和操作按钮 -->
+  <div class="page-header">
+    <div class="header-left">
+      <h2 class="page-title">用户与角色管理</h2>
+      <p class="page-description">管理系统用户账号和角色权限配置</p>
+    </div>
+  </div>
+
+  <!-- 标签页导航 -->
+  <div class="tab-navigation">
+    <button class="tab-btn" [class.active]="activeTab === 'users'"
+            (click)="switchTab('users')">
+      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+        <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>
+        <path d="M22 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>
+    <button class="tab-btn" [class.active]="activeTab === 'roles'"
+            (click)="switchTab('roles')">
+      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+        <path d="M12 14v4"></path>
+        <path d="M16 2v4"></path>
+        <path d="M8 2v4"></path>
+        <path d="M3 10h18"></path>
+        <path d="M12 14a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z"></path>
+        <path d="M12 22v-6"></path>
+      </svg>
+      角色管理
+    </button>
+  </div>
+
+  <!-- 用户管理内容 -->
+  <div *ngIf="activeTab === 'users'" class="tab-content">
+    <!-- 搜索和筛选区域 -->
+    <div class="search-filter-section">
+      <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>
+          <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+        </svg>
+        <input matInput type="text" placeholder="搜索用户名、姓名、邮箱或手机号..."
+               [(ngModel)]="userSearchTerm" (keyup.enter)="onUserSearch()"
+               class="search-input">
+        <button mat-button *ngIf="userSearchTerm" class="clear-search-btn"
+                (click)="userSearchTerm = ''; onUserSearch()">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+            <line x1="18" y1="6" x2="6" y2="18"></line>
+            <line x1="6" y1="6" x2="18" y2="18"></line>
+          </svg>
+        </button>
+      </div>
+      
+      <div class="filter-controls">
+        <div class="filter-wrapper">
+          <mat-select [(ngModel)]="userRoleFilter" (selectionChange)="onUserRoleFilterChange()"
+                     placeholder="角色" class="filter-select">
+            <mat-option value="">全部角色</mat-option>
+            <mat-option *ngFor="let role of roleList" [value]="role">
+              {{ role }}
+            </mat-option>
+          </mat-select>
+        </div>
+        
+        <div class="filter-wrapper">
+          <mat-select [(ngModel)]="userStatusFilter" (selectionChange)="onUserStatusFilterChange()"
+                     placeholder="状态" class="filter-select">
+            <mat-option value="">全部状态</mat-option>
+            <mat-option value="active">激活</mat-option>
+            <mat-option value="inactive">禁用</mat-option>
+          </mat-select>
+        </div>
+        
+        <button mat-button class="filter-btn" (click)="onUserSearch()">
+          <svg 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>
+            <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+          </svg>
+          筛选
+        </button>
+        
+        <button mat-raised-button color="primary" class="create-btn"
+                (click)="openUserDialog()">
+          <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>
+      </div>
+    </div>
+
+    <!-- 用户统计卡片 -->
+    <div class="stats-cards">
+      <div class="stat-card">
+        <div class="stat-value">{{ users().length }}</div>
+        <div class="stat-label">总用户数</div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-value">{{ activeUsersCount }}</div>
+        <div class="stat-label">活跃用户</div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-value">{{ roles().length }}</div>
+        <div class="stat-label">角色总数</div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-value">{{ activeUsersPercentage }}%</div>
+        <div class="stat-label">活跃率</div>
+      </div>
+    </div>
+
+    <!-- 用户列表表格 -->
+    <div class="user-table-container">
+      <table mat-table [dataSource]="paginatedUsers" class="user-table">
+        <!-- 用户名列 -->
+        <ng-container matColumnDef="username">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onUserSort('username')">
+            <div class="header-content">
+              <span>用户名</span>
+              <div class="sort-icon">
+                <svg *ngIf="userSortColumn === 'username'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let user" class="table-cell">
+            <div class="user-info">
+              <div class="username">{{ user.username }}</div>
+              <div class="real-name">{{ user.realName }}</div>
+            </div>
+          </td>
+        </ng-container>
+
+        <!-- 联系方式列 -->
+        <ng-container matColumnDef="contact">
+          <th mat-header-cell *matHeaderCellDef class="table-header">联系方式</th>
+          <td mat-cell *matCellDef="let user" class="table-cell">
+            <div class="contact-info">
+              <div class="email">{{ user.email }}</div>
+              <div class="phone">{{ user.phone }}</div>
+            </div>
+          </td>
+        </ng-container>
+
+        <!-- 角色列 -->
+        <ng-container matColumnDef="role">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onUserSort('role')">
+            <div class="header-content">
+              <span>角色</span>
+              <div class="sort-icon">
+                <svg *ngIf="userSortColumn === 'role'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let user" class="table-cell">
+            <div class="role-badge">{{ user.role }}</div>
+          </td>
+        </ng-container>
+
+        <!-- 部门列 -->
+        <ng-container matColumnDef="department">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onUserSort('department')">
+            <div class="header-content">
+              <span>部门</span>
+              <div class="sort-icon">
+                <svg *ngIf="userSortColumn === 'department'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let user" class="table-cell">
+            {{ user.department }}
+          </td>
+        </ng-container>
+
+        <!-- 状态列 -->
+        <ng-container matColumnDef="status">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onUserSort('status')">
+            <div class="header-content">
+              <span>状态</span>
+              <div class="sort-icon">
+                <svg *ngIf="userSortColumn === 'status'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let user" class="table-cell">
+            <div class="status-badge" [style.backgroundColor]="statusColors[user.status]">
+              {{ statusTexts[user.status] }}
+            </div>
+          </td>
+        </ng-container>
+
+        <!-- 创建日期列 -->
+        <ng-container matColumnDef="createdAt">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onUserSort('createdAt')">
+            <div class="header-content">
+              <span>创建日期</span>
+              <div class="sort-icon">
+                <svg *ngIf="userSortColumn === 'createdAt'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let user" class="table-cell">
+            {{ user.createdAt }}
+          </td>
+        </ng-container>
+
+        <!-- 最后登录列 -->
+        <ng-container matColumnDef="lastLogin">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onUserSort('lastLogin')">
+            <div class="header-content">
+              <span>最后登录</span>
+              <div class="sort-icon">
+                <svg *ngIf="userSortColumn === 'lastLogin'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let user" class="table-cell">
+            {{ user.lastLogin || '未登录' }}
+          </td>
+        </ng-container>
+
+        <!-- 操作列 -->
+        <ng-container matColumnDef="actions">
+          <th mat-header-cell *matHeaderCellDef class="table-header">操作</th>
+          <td mat-cell *matCellDef="let user" class="table-cell">
+            <div class="action-buttons">
+              <button mat-icon-button class="action-btn" color="primary"
+                      title="编辑用户"
+                      (click)="openUserDialog(user)">
+                <svg width="20" height="20" 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)="deleteUser(user.id)"
+                      [disabled]="user.role === '超级管理员'">
+                <svg width="20" height="20" 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>
+          </td>
+        </ng-container>
+
+        <tr mat-header-row *matHeaderRowDef="['username', 'contact', 'role', 'department', 'status', 'createdAt', 'lastLogin', 'actions']"></tr>
+        <tr mat-row *matRowDef="let row; columns: ['username', 'contact', 'role', 'department', 'status', 'createdAt', 'lastLogin', 'actions']"></tr>
+      </table>
+
+      <!-- 无数据状态 -->
+      <div *ngIf="paginatedUsers.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">
+          <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>
+          <path d="M22 21v-2a4 4 0 0 0-3-3.87"></path>
+          <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
+        </svg>
+        <p>没有找到符合条件的用户</p>
+        <button mat-button color="primary" (click)="userSearchTerm = ''; userRoleFilter = ''; userStatusFilter = ''; onUserSearch()">
+          清除筛选条件
+        </button>
+      </div>
+    </div>
+
+    <!-- 分页控件 -->
+    <div class="pagination-controls">
+      <div class="pagination-info">
+        显示 {{ Math.min(userCurrentPage * userPageSize + 1, filteredUsers().length) }} - 
+        {{ Math.min((userCurrentPage + 1) * userPageSize, filteredUsers().length) }} 共 
+        {{ filteredUsers().length }} 项
+      </div>
+      <div class="pagination-buttons">
+        <button mat-button [disabled]="userCurrentPage === 0" (click)="onUserPageChange(0)">
+          首页
+        </button>
+        <button mat-button [disabled]="userCurrentPage === 0" (click)="onUserPageChange(userCurrentPage - 1)">
+          上一页
+        </button>
+        
+        <!-- 页码按钮 -->
+        <div class="page-numbers">
+          <button mat-button *ngFor="let page of getPageNumbers(totalUserPages, userCurrentPage)" [class.active]="page === userCurrentPage"
+                  (click)="onUserPageChange(page)">
+            {{ page + 1 }}
+          </button>
+        </div>
+        
+        <button mat-button [disabled]="userCurrentPage === totalUserPages - 1" (click)="onUserPageChange(userCurrentPage + 1)">
+          下一页
+        </button>
+        <button mat-button [disabled]="userCurrentPage === totalUserPages - 1" (click)="onUserPageChange(totalUserPages - 1)">
+          末页
+        </button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 角色管理内容 -->
+  <div *ngIf="activeTab === 'roles'" class="tab-content">
+    <!-- 搜索和筛选区域 -->
+    <div class="search-filter-section">
+      <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>
+          <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+        </svg>
+        <input matInput type="text" placeholder="搜索角色名称或描述..."
+               [(ngModel)]="roleSearchTerm" (keyup.enter)="onRoleSearch()"
+               class="search-input">
+        <button mat-button *ngIf="roleSearchTerm" class="clear-search-btn"
+                (click)="roleSearchTerm = ''; onRoleSearch()">
+          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+            <line x1="18" y1="6" x2="6" y2="18"></line>
+            <line x1="6" y1="6" x2="18" y2="18"></line>
+          </svg>
+        </button>
+      </div>
+      
+      <div class="filter-controls">
+        <button mat-raised-button color="primary" class="create-btn"
+                (click)="openRoleDialog()">
+          <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>
+      </div>
+    </div>
+
+    <!-- 角色列表表格 -->
+    <div class="role-table-container">
+      <table mat-table [dataSource]="paginatedRoles" class="role-table">
+        <!-- 角色名称列 -->
+        <ng-container matColumnDef="name">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onRoleSort('name')">
+            <div class="header-content">
+              <span>角色名称</span>
+              <div class="sort-icon">
+                <svg *ngIf="roleSortColumn === 'name'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let role" class="table-cell">
+            <div class="role-info">
+              <div class="role-name">{{ role.name }}</div>
+              <div class="role-description">{{ role.description }}</div>
+            </div>
+          </td>
+        </ng-container>
+
+        <!-- 权限数量列 -->
+        <ng-container matColumnDef="permissionsCount">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onRoleSort('permissionsCount')">
+            <div class="header-content">
+              <span>权限数量</span>
+              <div class="sort-icon">
+                <svg *ngIf="roleSortColumn === 'permissionsCount'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let role" class="table-cell">
+            {{ role.permissions.length }}
+          </td>
+        </ng-container>
+
+        <!-- 用户数量列 -->
+        <ng-container matColumnDef="usersCount">
+          <th mat-header-cell *matHeaderCellDef class="table-header"
+              (click)="onRoleSort('usersCount')">
+            <div class="header-content">
+              <span>用户数量</span>
+              <div class="sort-icon">
+                <svg *ngIf="roleSortColumn === 'usersCount'" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
+                </svg>
+              </div>
+            </div>
+          </th>
+          <td mat-cell *matCellDef="let role" class="table-cell">
+            <div class="user-count-badge">{{ role.usersCount }}</div>
+          </td>
+        </ng-container>
+
+        <!-- 操作列 -->
+        <ng-container matColumnDef="actions">
+          <th mat-header-cell *matHeaderCellDef class="table-header">操作</th>
+          <td mat-cell *matCellDef="let role" class="table-cell">
+            <div class="action-buttons">
+              <button mat-icon-button class="action-btn" color="primary"
+                      title="编辑角色"
+                      (click)="openRoleDialog(role)">
+                <svg width="20" height="20" 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)="deleteRole(role.id)"
+                      [disabled]="role.name === '超级管理员'">
+                <svg width="20" height="20" 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>
+          </td>
+        </ng-container>
+
+        <tr mat-header-row *matHeaderRowDef="['name', 'permissionsCount', 'usersCount', 'actions']"></tr>
+        <tr mat-row *matRowDef="let row; columns: ['name', 'permissionsCount', 'usersCount', 'actions']"></tr>
+      </table>
+
+      <!-- 无数据状态 -->
+      <div *ngIf="paginatedRoles.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">
+          <path d="M12 14v4"></path>
+          <path d="M16 2v4"></path>
+          <path d="M8 2v4"></path>
+          <path d="M3 10h18"></path>
+          <path d="M12 14a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z"></path>
+          <path d="M12 22v-6"></path>
+        </svg>
+        <p>没有找到符合条件的角色</p>
+        <button mat-button color="primary" (click)="roleSearchTerm = ''; onRoleSearch()">
+          清除筛选条件
+        </button>
+      </div>
+    </div>
+
+    <!-- 分页控件 -->
+    <div class="pagination-controls">
+      <div class="pagination-info">
+        显示 {{ Math.min(roleCurrentPage * rolePageSize + 1, filteredRoles().length) }} - 
+        {{ Math.min((roleCurrentPage + 1) * rolePageSize, filteredRoles().length) }} 共 
+        {{ filteredRoles().length }} 项
+      </div>
+      <div class="pagination-buttons">
+        <button mat-button [disabled]="roleCurrentPage === 0" (click)="onRolePageChange(0)">
+          首页
+        </button>
+        <button mat-button [disabled]="roleCurrentPage === 0" (click)="onRolePageChange(roleCurrentPage - 1)">
+          上一页
+        </button>
+        
+        <!-- 页码按钮 -->
+        <div class="page-numbers">
+          <button mat-button *ngFor="let page of getPageNumbers(totalRolePages, roleCurrentPage)" [class.active]="page === roleCurrentPage"
+                  (click)="onRolePageChange(page)">
+            {{ page + 1 }}
+          </button>
+        </div>
+        
+        <button mat-button [disabled]="roleCurrentPage === totalRolePages - 1" (click)="onRolePageChange(roleCurrentPage + 1)">
+          下一页
+        </button>
+        <button mat-button [disabled]="roleCurrentPage === totalRolePages - 1" (click)="onRolePageChange(totalRolePages - 1)">
+          末页
+        </button>
+      </div>
+    </div>
+  </div>
+</div>

+ 567 - 0
src/app/pages/admin/user-management/user-management.scss

@@ -0,0 +1,567 @@
+// 全局变量定义
+$primary-color: #165DFF;
+$secondary-color: #6B7280;
+$success-color: #00B42A;
+$warning-color: #FFAA00;
+$error-color: #F53F3F;
+$text-primary: #333333;
+$text-secondary: #666666;
+$text-tertiary: #999999;
+$border-color: #E5E7EB;
+$bg-light: #F9FAFB;
+$bg-white: #FFFFFF;
+$shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+$shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+$shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+
+// 主容器
+.user-management {
+  padding: 24px;
+  min-height: 100vh;
+  background-color: $bg-light;
+}
+
+// 页面标题区域
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid $border-color;
+
+  .header-left {
+    .page-title {
+      font-size: 24px;
+      font-weight: 600;
+      color: $text-primary;
+      margin: 0 0 4px 0;
+    }
+
+    .page-description {
+      font-size: 14px;
+      color: $text-tertiary;
+      margin: 0;
+    }
+  }
+}
+
+// 标签页导航
+.tab-navigation {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 24px;
+  background-color: $bg-white;
+  border-radius: 12px;
+  padding: 4px;
+  box-shadow: $shadow-sm;
+
+  .tab-btn {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 12px 24px;
+    border: none;
+    background-color: transparent;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 500;
+    color: $text-secondary;
+    cursor: pointer;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background-color: $bg-light;
+      color: $text-primary;
+    }
+
+    &.active {
+      background-color: $primary-color;
+      color: $bg-white;
+    }
+  }
+}
+
+// 标签页内容
+.tab-content {
+  animation: fadeIn 0.3s ease-in-out;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(10px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+// 搜索和筛选区域
+.search-filter-section {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 24px;
+  flex-wrap: wrap;
+
+  .search-input-wrapper {
+    flex: 1;
+    min-width: 300px;
+    position: relative;
+    display: flex;
+    align-items: center;
+
+    .search-icon {
+      position: absolute;
+      left: 12px;
+      color: $text-tertiary;
+      z-index: 1;
+    }
+
+    .search-input {
+      width: 100%;
+      padding: 10px 12px 10px 40px;
+      border: 1px solid $border-color;
+      border-radius: 8px;
+      font-size: 14px;
+      color: $text-primary;
+      background-color: $bg-white;
+      transition: all 0.2s ease;
+      box-shadow: $shadow-sm;
+
+      &:focus {
+        outline: none;
+        border-color: $primary-color;
+        box-shadow: 0 0 0 3px rgba(22, 93, 255, 0.1);
+      }
+
+      &::placeholder {
+        color: $text-tertiary;
+      }
+    }
+
+    .clear-search-btn {
+      position: absolute;
+      right: 8px;
+      color: $text-tertiary;
+      padding: 4px;
+    }
+  }
+
+  .filter-controls {
+    display: flex;
+    gap: 12px;
+    align-items: center;
+    flex-wrap: wrap;
+
+    .filter-wrapper {
+      .filter-select {
+        min-width: 160px;
+        border: 1px solid $border-color;
+        border-radius: 8px;
+        background-color: $bg-white;
+        box-shadow: $shadow-sm;
+
+        .mat-select-trigger {
+          height: 40px;
+          font-size: 14px;
+          color: $text-primary;
+        }
+      }
+    }
+
+    .filter-btn {
+      display: flex;
+      align-items: center;
+      gap: 6px;
+      color: $primary-color;
+      border: 1px solid $primary-color;
+      border-radius: 8px;
+      padding: 8px 16px;
+      font-size: 14px;
+      font-weight: 500;
+      background-color: $bg-white;
+      transition: all 0.2s ease;
+
+      &:hover {
+        background-color: $primary-color;
+        color: $bg-white;
+      }
+    }
+
+    .create-btn {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      background-color: $primary-color;
+      color: $bg-white;
+      border-radius: 8px;
+      padding: 8px 16px;
+      font-size: 14px;
+      font-weight: 500;
+      transition: all 0.2s ease;
+      box-shadow: $shadow-sm;
+
+      &:hover {
+        background-color: #0E4BD8;
+        box-shadow: $shadow-md;
+        transform: translateY(-1px);
+      }
+    }
+  }
+}
+
+// 统计卡片区域
+.stats-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+  gap: 16px;
+  margin-bottom: 24px;
+
+  .stat-card {
+    background-color: $bg-white;
+    border-radius: 12px;
+    padding: 20px;
+    box-shadow: $shadow-sm;
+    text-align: center;
+    transition: all 0.2s ease;
+
+    &:hover {
+      box-shadow: $shadow-md;
+      transform: translateY(-2px);
+    }
+
+    .stat-value {
+      font-size: 28px;
+      font-weight: 700;
+      color: $text-primary;
+      margin-bottom: 4px;
+    }
+
+    .stat-label {
+      font-size: 14px;
+      color: $text-tertiary;
+    }
+  }
+}
+
+// 表格容器通用样式
+.user-table-container,
+.role-table-container {
+  background-color: $bg-white;
+  border-radius: 12px;
+  box-shadow: $shadow-sm;
+  overflow: hidden;
+  margin-bottom: 24px;
+
+  .user-table,
+  .role-table {
+    width: 100%;
+    border-collapse: collapse;
+
+    .table-header {
+      background-color: $bg-light;
+      font-weight: 600;
+      color: $text-secondary;
+      font-size: 13px;
+      text-align: left;
+      padding: 16px 20px;
+      border-bottom: 1px solid $border-color;
+      cursor: pointer;
+      transition: background-color 0.2s ease;
+
+      &:hover {
+        background-color: #F3F4F6;
+      }
+
+      .header-content {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+      }
+
+      .sort-icon {
+        opacity: 0.5;
+      }
+    }
+
+    .table-cell {
+      padding: 16px 20px;
+      border-bottom: 1px solid $border-color;
+      font-size: 14px;
+      color: $text-primary;
+      vertical-align: middle;
+
+      &:last-child {
+        text-align: right;
+      }
+
+      .user-info {
+        .username {
+          font-weight: 500;
+          margin-bottom: 2px;
+        }
+        .real-name {
+          font-size: 12px;
+          color: $text-tertiary;
+        }
+      }
+
+      .contact-info {
+        .email {
+          margin-bottom: 2px;
+        }
+        .phone {
+          font-size: 12px;
+          color: $text-tertiary;
+        }
+      }
+
+      .role-info {
+        .role-name {
+          font-weight: 500;
+          margin-bottom: 2px;
+        }
+        .role-description {
+          font-size: 12px;
+          color: $text-tertiary;
+          line-height: 1.4;
+        }
+      }
+
+      .role-badge {
+        display: inline-block;
+        padding: 4px 12px;
+        background-color: #E6F0FF;
+        color: $primary-color;
+        border-radius: 16px;
+        font-size: 12px;
+        font-weight: 500;
+      }
+
+      .status-badge {
+        display: inline-block;
+        padding: 4px 12px;
+        border-radius: 16px;
+        font-size: 12px;
+        font-weight: 500;
+        color: $bg-white;
+        text-align: center;
+      }
+
+      .user-count-badge {
+        display: inline-block;
+        padding: 4px 12px;
+        background-color: #F0F9FF;
+        color: $success-color;
+        border-radius: 16px;
+        font-size: 12px;
+        font-weight: 500;
+      }
+
+      .action-buttons {
+        display: flex;
+        gap: 8px;
+        justify-content: flex-end;
+
+        .action-btn {
+          width: 36px;
+          height: 36px;
+          border-radius: 6px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          transition: all 0.2s ease;
+          background-color: transparent;
+          border: 1px solid $border-color;
+          color: $text-secondary;
+
+          &:hover:not(:disabled) {
+            border-color: currentColor;
+            transform: translateY(-1px);
+          }
+
+          &:disabled {
+            opacity: 0.5;
+            cursor: not-allowed;
+          }
+
+          &.mat-primary {
+            color: $primary-color;
+          }
+
+          &.mat-warn {
+            color: $error-color;
+          }
+        }
+      }
+    }
+
+    tr:last-child .table-cell {
+      border-bottom: none;
+    }
+
+    tr:hover .table-cell {
+      background-color: $bg-light;
+    }
+  }
+
+  // 空状态
+  .empty-state {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 60px 20px;
+    text-align: center;
+
+    svg {
+      margin-bottom: 16px;
+      opacity: 0.5;
+    }
+
+    p {
+      font-size: 16px;
+      color: $text-tertiary;
+      margin-bottom: 24px;
+    }
+
+    button {
+      border-radius: 8px;
+      padding: 8px 24px;
+      font-size: 14px;
+      font-weight: 500;
+    }
+  }
+}
+
+// 分页控件
+.pagination-controls {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px 20px;
+  background-color: $bg-white;
+  border-radius: 12px;
+  box-shadow: $shadow-sm;
+
+  .pagination-info {
+    font-size: 14px;
+    color: $text-tertiary;
+  }
+
+  .pagination-buttons {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+
+    button {
+      padding: 6px 12px;
+      border-radius: 6px;
+      font-size: 13px;
+      font-weight: 500;
+      transition: all 0.2s ease;
+      min-width: auto;
+
+      &:disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+      }
+
+      &.active {
+        background-color: $primary-color;
+        color: $bg-white;
+      }
+    }
+
+    .page-numbers {
+      display: flex;
+      gap: 4px;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 1200px) {
+  .user-table,
+  .role-table {
+    .table-header,
+    .table-cell {
+      padding: 12px 16px;
+    }
+  }
+}
+
+@media (max-width: 992px) {
+  .user-management {
+    padding: 16px;
+  }
+
+  .stats-cards {
+    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+  }
+
+  .search-filter-section {
+    flex-direction: column;
+    gap: 12px;
+  }
+
+  .search-input-wrapper {
+    min-width: 100% !important;
+  }
+
+  .pagination-controls {
+    flex-direction: column;
+    gap: 16px;
+
+    .pagination-buttons {
+      flex-wrap: wrap;
+      justify-content: center;
+    }
+  }
+}
+
+@media (max-width: 768px) {
+  .tab-navigation {
+    flex-direction: column;
+    gap: 0;
+    padding: 0;
+    background-color: transparent;
+    box-shadow: none;
+
+    .tab-btn {
+      margin-bottom: 8px;
+      background-color: $bg-white;
+      box-shadow: $shadow-sm;
+    }
+  }
+
+  .stats-cards {
+    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
+    gap: 12px;
+  }
+
+  .stat-card {
+    padding: 16px;
+
+    .stat-value {
+      font-size: 24px;
+    }
+  }
+
+  .user-table-container,
+  .role-table-container {
+    overflow-x: auto;
+  }
+}
+
+@media (max-width: 480px) {
+  .user-management {
+    padding: 12px;
+  }
+
+  .stats-cards {
+    grid-template-columns: 1fr 1fr;
+  }
+}

+ 562 - 0
src/app/pages/admin/user-management/user-management.ts

@@ -0,0 +1,562 @@
+import { Component, OnInit, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { FormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { MatTableModule } from '@angular/material/table';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatDialogModule, MatDialog } from '@angular/material/dialog';
+import { MatSortModule } from '@angular/material/sort';
+import { UserDialogComponent } from './user-dialog/user-dialog';
+import { RoleDialogComponent } from './role-dialog/role-dialog';
+
+interface User {
+  id: string;
+  username: string;
+  realName: string;
+  email: string;
+  phone: string;
+  role: string;
+  department: string;
+  status: 'active' | 'inactive';
+  createdAt: string;
+  lastLogin: string;
+}
+
+interface Role {
+  id: string;
+  name: string;
+  description: string;
+  permissions: string[];
+  usersCount: number;
+}
+
+@Component({
+  selector: 'app-user-management',
+  standalone: true,
+  imports: [
+    CommonModule,
+    RouterModule,
+    FormsModule,
+    MatButtonModule,
+    MatIconModule,
+    MatTableModule,
+    MatInputModule,
+    MatSelectModule,
+    MatPaginatorModule,
+    MatDialogModule,
+    MatSortModule,
+    UserDialogComponent,
+    RoleDialogComponent
+  ],
+  templateUrl: './user-management.html',
+  styleUrl: './user-management.scss'
+})
+export class UserManagement implements OnInit {
+  // 提供Math对象给模板使用
+  readonly Math = Math;
+  
+  // 用户相关数据
+  users = signal<User[]>([]);
+  filteredUsers = signal<User[]>([]);
+  userSearchTerm = '';
+  userRoleFilter = '';
+  userStatusFilter = '';
+  userSortColumn = 'createdAt';
+  userSortDirection = 'desc';
+  userPageSize = 10;
+  userCurrentPage = 0;
+
+  // 角色相关数据
+  roles = signal<Role[]>([]);
+  filteredRoles = signal<Role[]>([]);
+  roleSearchTerm = '';
+  roleSortColumn = 'name';
+  roleSortDirection = 'asc';
+  rolePageSize = 10;
+  roleCurrentPage = 0;
+
+  // 当前激活的标签页
+  activeTab = 'users';
+
+  // 角色列表(用于下拉选择)
+  roleList: string[] = [];
+
+  // 状态颜色映射
+  statusColors: Record<string, string> = {
+    'active': '#00B42A',
+    'inactive': '#F53F3F'
+  };
+
+  // 状态文本映射
+  statusTexts: Record<string, string> = {
+    'active': '激活',
+    'inactive': '禁用'
+  };
+
+  constructor(private dialog: MatDialog) {}
+
+  ngOnInit(): void {
+    this.loadUsers();
+    this.loadRoles();
+  }
+
+  loadUsers(): void {
+    // 模拟用户数据
+    this.users.set([
+      {
+        id: '1',
+        username: 'admin',
+        realName: '超级管理员',
+        email: 'admin@example.com',
+        phone: '13800138000',
+        role: '超级管理员',
+        department: '管理层',
+        status: 'active',
+        createdAt: '2025-01-01',
+        lastLogin: '2025-09-15 14:30'
+      },
+      {
+        id: '2',
+        username: 'customer_service_1',
+        realName: '客服小李',
+        email: 'cs1@example.com',
+        phone: '13800138001',
+        role: '客服',
+        department: '客户服务部',
+        status: 'active',
+        createdAt: '2025-02-15',
+        lastLogin: '2025-09-15 10:15'
+      },
+      {
+        id: '3',
+        username: 'designer_1',
+        realName: '张设计师',
+        email: 'designer1@example.com',
+        phone: '13800138002',
+        role: '设计师',
+        department: '设计部',
+        status: 'active',
+        createdAt: '2025-03-10',
+        lastLogin: '2025-09-14 16:45'
+      },
+      {
+        id: '4',
+        username: 'team_leader_1',
+        realName: '王组长',
+        email: 'leader1@example.com',
+        phone: '13800138003',
+        role: '组长',
+        department: '设计部',
+        status: 'active',
+        createdAt: '2025-02-20',
+        lastLogin: '2025-09-15 09:00'
+      },
+      {
+        id: '5',
+        username: 'finance_1',
+        realName: '财务小陈',
+        email: 'finance1@example.com',
+        phone: '13800138004',
+        role: '财务',
+        department: '财务部',
+        status: 'active',
+        createdAt: '2025-03-05',
+        lastLogin: '2025-09-13 15:20'
+      },
+      {
+        id: '6',
+        username: 'hr_1',
+        realName: '人事小郑',
+        email: 'hr1@example.com',
+        phone: '13800138005',
+        role: '人事',
+        department: '人力资源部',
+        status: 'active',
+        createdAt: '2025-03-20',
+        lastLogin: '2025-09-14 11:30'
+      },
+      {
+        id: '7',
+        username: 'designer_2',
+        realName: '刘设计师',
+        email: 'designer2@example.com',
+        phone: '13800138006',
+        role: '设计师',
+        department: '设计部',
+        status: 'active',
+        createdAt: '2025-04-01',
+        lastLogin: '2025-09-15 13:45'
+      },
+      {
+        id: '8',
+        username: 'designer_3',
+        realName: '陈设计师',
+        email: 'designer3@example.com',
+        phone: '13800138007',
+        role: '设计师',
+        department: '设计部',
+        status: 'inactive',
+        createdAt: '2025-04-15',
+        lastLogin: '2025-08-20 10:00'
+      },
+      {
+        id: '9',
+        username: 'customer_service_2',
+        realName: '客服小王',
+        email: 'cs2@example.com',
+        phone: '13800138008',
+        role: '客服',
+        department: '客户服务部',
+        status: 'active',
+        createdAt: '2025-05-01',
+        lastLogin: '2025-09-15 11:20'
+      },
+      {
+        id: '10',
+        username: 'team_leader_2',
+        realName: '李组长',
+        email: 'leader2@example.com',
+        phone: '13800138009',
+        role: '组长',
+        department: '设计部',
+        status: 'active',
+        createdAt: '2025-04-20',
+        lastLogin: '2025-09-15 08:45'
+      }
+    ]);
+    
+    this.applyUserFilters();
+  }
+
+  loadRoles(): void {
+    // 模拟角色数据
+    this.roles.set([
+      {
+        id: '1',
+        name: '超级管理员',
+        description: '系统最高权限管理员,可访问和管理所有功能',
+        permissions: ['system:admin', 'project:manage', 'user:manage', 'role:manage', 'finance:view', 'log:view', 'api:manage'],
+        usersCount: 1
+      },
+      {
+        id: '2',
+        name: '客服',
+        description: '负责客户咨询、项目跟进等工作',
+        permissions: ['project:view', 'customer:manage', 'case:view'],
+        usersCount: 2
+      },
+      {
+        id: '3',
+        name: '设计师',
+        description: '负责项目设计、任务执行等工作',
+        permissions: ['project:view', 'task:manage', 'file:upload'],
+        usersCount: 3
+      },
+      {
+        id: '4',
+        name: '组长',
+        description: '负责团队管理、项目审核等工作',
+        permissions: ['project:manage', 'user:view', 'quality:manage', 'team:manage'],
+        usersCount: 2
+      },
+      {
+        id: '5',
+        name: '财务',
+        description: '负责财务数据管理、报表生成等工作',
+        permissions: ['finance:manage', 'project:view'],
+        usersCount: 1
+      },
+      {
+        id: '6',
+        name: '人事',
+        description: '负责人事管理、考勤统计等工作',
+        permissions: ['hr:manage', 'user:view'],
+        usersCount: 1
+      }
+    ]);
+    
+    // 更新角色列表
+    this.roleList = this.roles().map(role => role.name);
+    
+    this.applyRoleFilters();
+  }
+
+  applyUserFilters(): void {
+    let result = [...this.users()];
+    
+    // 搜索过滤
+    if (this.userSearchTerm) {
+      const term = this.userSearchTerm.toLowerCase();
+      result = result.filter(user => 
+        user.username.toLowerCase().includes(term) ||
+        user.realName.toLowerCase().includes(term) ||
+        user.email.toLowerCase().includes(term) ||
+        user.phone.includes(term)
+      );
+    }
+    
+    // 角色过滤
+    if (this.userRoleFilter) {
+      result = result.filter(user => user.role === this.userRoleFilter);
+    }
+    
+    // 状态过滤
+    if (this.userStatusFilter) {
+      result = result.filter(user => user.status === this.userStatusFilter);
+    }
+    
+    // 排序
+    result.sort((a, b) => {
+      if (this.userSortColumn === 'createdAt' || this.userSortColumn === 'lastLogin') {
+        const dateA = new Date(a[this.userSortColumn]).getTime();
+        const dateB = new Date(b[this.userSortColumn]).getTime();
+        return this.userSortDirection === 'asc' ? dateA - dateB : dateB - dateA;
+      } else {
+        const valueA = a[this.userSortColumn as keyof User]?.toString().toLowerCase() || '';
+        const valueB = b[this.userSortColumn as keyof User]?.toString().toLowerCase() || '';
+        return this.userSortDirection === 'asc' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA);
+      }
+    });
+    
+    this.filteredUsers.set(result);
+  }
+
+  applyRoleFilters(): void {
+    let result = [...this.roles()];
+    
+    // 搜索过滤
+    if (this.roleSearchTerm) {
+      const term = this.roleSearchTerm.toLowerCase();
+      result = result.filter(role => 
+        role.name.toLowerCase().includes(term) ||
+        role.description.toLowerCase().includes(term)
+      );
+    }
+    
+    // 排序
+    result.sort((a, b) => {
+      const valueA = a[this.roleSortColumn as keyof Role]?.toString().toLowerCase() || '';
+      const valueB = b[this.roleSortColumn as keyof Role]?.toString().toLowerCase() || '';
+      return this.roleSortDirection === 'asc' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA);
+    });
+    
+    this.filteredRoles.set(result);
+  }
+
+  // 用户管理方法
+  onUserSearch(): void {
+    this.applyUserFilters();
+  }
+
+  onUserRoleFilterChange(): void {
+    this.applyUserFilters();
+  }
+
+  onUserStatusFilterChange(): void {
+    this.applyUserFilters();
+  }
+
+  onUserSort(column: string): void {
+    if (this.userSortColumn === column) {
+      this.userSortDirection = this.userSortDirection === 'asc' ? 'desc' : 'asc';
+    } else {
+      this.userSortColumn = column;
+      this.userSortDirection = 'asc';
+    }
+    this.applyUserFilters();
+  }
+
+  // 角色管理方法
+  onRoleSearch(): void {
+    this.applyRoleFilters();
+  }
+
+  onRoleSort(column: string): void {
+    if (this.roleSortColumn === column) {
+      this.roleSortDirection = this.roleSortDirection === 'asc' ? 'desc' : 'asc';
+    } else {
+      this.roleSortColumn = column;
+      this.roleSortDirection = 'asc';
+    }
+    this.applyRoleFilters();
+  }
+
+  // 对话框方法
+  openUserDialog(user?: User): void {
+    const dialogRef = this.dialog.open(UserDialogComponent, {
+      width: '600px',
+      data: user ? { ...user } : {
+        id: '',
+        username: '',
+        realName: '',
+        email: '',
+        phone: '',
+        role: '',
+        department: '',
+        status: 'active',
+        createdAt: new Date().toISOString().split('T')[0],
+        lastLogin: ''
+      }
+    });
+
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        if (result.id) {
+          // 更新用户
+          this.updateUser(result);
+        } else {
+          // 创建新用户
+          this.createUser(result);
+        }
+      }
+    });
+  }
+
+  openRoleDialog(role?: Role): void {
+    const dialogRef = this.dialog.open(RoleDialogComponent, {
+      width: '600px',
+      data: role ? { ...role } : {
+        id: '',
+        name: '',
+        description: '',
+        permissions: [],
+        usersCount: 0
+      }
+    });
+
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        if (result.id) {
+          // 更新角色
+          this.updateRole(result);
+        } else {
+          // 创建新角色
+          this.createRole(result);
+        }
+      }
+    });
+  }
+
+  // 用户操作方法
+  createUser(userData: Omit<User, 'id' | 'lastLogin'>): void {
+    const newUser: User = {
+      ...userData,
+      id: (this.users().length + 1).toString(),
+      lastLogin: ''
+    };
+    
+    this.users.set([newUser, ...this.users()]);
+    this.applyUserFilters();
+  }
+
+  updateUser(updatedUser: User): void {
+    this.users.set(this.users().map(user => 
+      user.id === updatedUser.id ? updatedUser : user
+    ));
+    this.applyUserFilters();
+  }
+
+  deleteUser(id: string): void {
+    if (confirm('确定要删除这个用户吗?')) {
+      this.users.set(this.users().filter(user => user.id !== id));
+      this.applyUserFilters();
+    }
+  }
+
+  // 角色操作方法
+  createRole(roleData: Omit<Role, 'id' | 'usersCount'>): void {
+    const newRole: Role = {
+      ...roleData,
+      id: (this.roles().length + 1).toString(),
+      usersCount: 0
+    };
+    
+    this.roles.set([newRole, ...this.roles()]);
+    this.roleList = this.roles().map(role => role.name);
+    this.applyRoleFilters();
+  }
+
+  updateRole(updatedRole: Role): void {
+    this.roles.set(this.roles().map(role => 
+      role.id === updatedRole.id ? updatedRole : role
+    ));
+    this.roleList = this.roles().map(role => role.name);
+    this.applyRoleFilters();
+  }
+
+  deleteRole(id: string): void {
+    // 检查是否有用户正在使用该角色
+    const role = this.roles().find(r => r.id === id);
+    if (role && role.usersCount > 0) {
+      alert('该角色下有用户正在使用,无法删除!');
+      return;
+    }
+    
+    if (confirm('确定要删除这个角色吗?')) {
+      this.roles.set(this.roles().filter(role => role.id !== id));
+      this.roleList = this.roles().map(role => role.name);
+      this.applyRoleFilters();
+    }
+  }
+
+  // 分页相关方法
+  get paginatedUsers(): User[] {
+    const startIndex = this.userCurrentPage * this.userPageSize;
+    return this.filteredUsers().slice(startIndex, startIndex + this.userPageSize);
+  }
+
+  // 获取活跃用户数量
+  get activeUsersCount() {
+    return this.users().filter(u => u.status === 'active').length;
+  }
+  
+  // 获取活跃用户百分比
+  get activeUsersPercentage() {
+    const allUsers = this.users().length;
+    if (allUsers === 0) return 0;
+    return Math.round((this.activeUsersCount / allUsers) * 100);
+  }
+
+  get totalUserPages(): number {
+    return Math.ceil(this.filteredUsers().length / this.userPageSize);
+  }
+
+  onUserPageChange(page: number): void {
+    this.userCurrentPage = page;
+  }
+
+  get paginatedRoles(): Role[] {
+    const startIndex = this.roleCurrentPage * this.rolePageSize;
+    return this.filteredRoles().slice(startIndex, startIndex + this.rolePageSize);
+  }
+
+  get totalRolePages(): number {
+    return Math.ceil(this.filteredRoles().length / this.rolePageSize);
+  }
+
+  onRolePageChange(page: number): void {
+    this.roleCurrentPage = page;
+  }
+
+  // 标签页切换
+  switchTab(tab: string): void {
+    this.activeTab = tab;
+  }
+
+  // 辅助方法
+  getPageNumbers(totalPages: number, currentPage: number): number[] {
+    const pageNumbers = [];
+    const maxVisiblePages = 5;
+    let startPage = Math.max(0, Math.min(currentPage - Math.floor(maxVisiblePages / 2), totalPages - maxVisiblePages));
+    
+    for (let i = startPage; i < Math.min(startPage + maxVisiblePages, totalPages); i++) {
+      pageNumbers.push(i);
+    }
+    
+    return pageNumbers;
+  }
+}

+ 106 - 0
src/app/services/auth.service.ts

@@ -0,0 +1,106 @@
+import { Injectable } from '@angular/core';
+import { BehaviorSubject, Observable } from 'rxjs';
+
+// 用户信息接口
+interface UserInfo {
+  id: string;
+  name: string;
+  avatar: string;
+  roles: string[];
+  permissions?: string[];
+  lastLogin?: string;
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class AuthService {
+  // 用户登录状态的BehaviorSubject
+  private currentUserSubject: BehaviorSubject<UserInfo | null>;
+  public currentUser: Observable<UserInfo | null>;
+
+  constructor() {
+    // 从本地存储获取用户信息,如果没有则为null
+    const storedUser = localStorage.getItem('currentUser');
+    this.currentUserSubject = new BehaviorSubject<UserInfo | null>(
+      storedUser ? JSON.parse(storedUser) : null
+    );
+    this.currentUser = this.currentUserSubject.asObservable();
+  }
+
+  // 获取当前用户信息
+  public get currentUserValue(): UserInfo | null {
+    return this.currentUserSubject.value;
+  }
+
+  // 检查用户是否已登录
+  isLoggedIn(): boolean {
+    return this.currentUserValue !== null;
+  }
+
+  // 获取用户角色
+  getUserRoles(): string[] {
+    return this.currentUserValue?.roles || [];
+  }
+
+  // 检查用户是否具有指定角色
+  hasRole(role: string): boolean {
+    return this.getUserRoles().includes(role);
+  }
+
+  // 检查用户是否具有管理员角色
+  isAdmin(): boolean {
+    return this.hasRole('admin');
+  }
+
+  // 登录方法
+  login(username: string, password: string): Observable<boolean> {
+    // 在实际应用中,这里会调用后端API进行身份验证
+    // 这里使用模拟数据
+    return new Observable<boolean>(observer => {
+      // 模拟API延迟
+      setTimeout(() => {
+        // 模拟成功登录
+        // 实际应用中,这里会根据后端返回的用户信息设置currentUser
+        const mockUser: UserInfo = {
+          id: '1',
+          name: '超级管理员',
+          avatar: 'https://picsum.photos/id/1/40/40',
+          roles: ['admin', 'user'],
+          permissions: ['view-all', 'edit-all', 'delete-all'],
+          lastLogin: new Date().toISOString()
+        };
+
+        // 存储用户信息到本地存储
+        localStorage.setItem('currentUser', JSON.stringify(mockUser));
+        // 更新用户状态
+        this.currentUserSubject.next(mockUser);
+        observer.next(true);
+        observer.complete();
+      }, 1000);
+    });
+  }
+
+  // 登出方法
+  logout(): void {
+    // 移除本地存储中的用户信息
+    localStorage.removeItem('currentUser');
+    // 更新用户状态为null
+    this.currentUserSubject.next(null);
+  }
+
+  // 更新用户信息
+  updateUserInfo(userInfo: Partial<UserInfo>): void {
+    const currentUser = this.currentUserValue;
+    if (currentUser) {
+      const updatedUser = { ...currentUser, ...userInfo };
+      localStorage.setItem('currentUser', JSON.stringify(updatedUser));
+      this.currentUserSubject.next(updatedUser);
+    }
+  }
+
+  // 检查用户是否有权限执行某个操作
+  hasPermission(permission: string): boolean {
+    return this.currentUserValue?.permissions?.includes(permission) || false;
+  }
+}

+ 2 - 0
src/index.html

@@ -6,6 +6,8 @@
   <base href="/">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="icon" type="image/x-icon" href="favicon.ico">
+  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
+  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
 </head>
 <body>
   <app-root></app-root>

+ 38 - 0
src/styles.scss

@@ -1 +1,39 @@
+
+// Include theming for Angular Material with `mat.theme()`.
+// This Sass mixin will define CSS variables that are used for styling Angular Material
+// components according to the Material 3 design spec.
+// Learn more about theming and how to use it for your application's
+// custom components at https://material.angular.dev/guide/theming
+@use '@angular/material' as mat;
+
+html {
+  @include mat.theme((
+    color: (
+      primary: mat.$azure-palette,
+      tertiary: mat.$blue-palette,
+    ),
+    typography: Roboto,
+    density: 0,
+  ));
+}
+
+body {
+  // Default the application to a light color theme. This can be changed to
+  // `dark` to enable the dark color theme, or to `light dark` to defer to the
+  // user's system settings.
+  color-scheme: light;
+
+  // Set a default background, font and text colors for the application using
+  // Angular Material's system-level CSS variables. Learn more about these
+  // variables at https://material.angular.dev/guide/system-variables
+  background-color: var(--mat-sys-surface);
+  color: var(--mat-sys-on-surface);
+  font: var(--mat-sys-body-medium);
+
+  // Reset the user agent margin.
+  margin: 0;
+}
 /* You can add global styles to this file, and also import other style files */
+
+html, body { height: 100%; }
+body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }